alsa-lib/src/pcm/pcm_route.c
Maciej S. Szmigiero 0c081ebbed pcm: linear, route: handle linear formats with 20-bit sample on 4 bytes
The previous patch has added 20-bit PCM formats SND_PCM_FORMAT_{S,U}20 to
alsa-lib.
We need to extend the linear format conversion code with handling of these
sample formats so they can also be utilized by applications that only
recognize the more typical ones like SND_PCM_FORMAT_S16.

Since the conversion arrays are indexed by a format bit width divided by 8
the easiest way to handle these formats is to treat them like they were
40-bit wide (the next free integer multiple of 8 bits).
This doesn't create a collision risk with a future format since there can't
really be a 40-bit sample format that occupies 4 bytes.

Make sure we use the getput conversion method for these formats since a
direct conversion from / to them is not supported.

Signed-off-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
Reviewed-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2017-12-18 15:41:08 +01:00

1397 lines
38 KiB
C

/**
* \file pcm/pcm_route.c
* \ingroup PCM_Plugins
* \brief PCM Route & Volume Plugin Interface
* \author Abramo Bagnara <abramo@alsa-project.org>
* \date 2000-2001
*/
/*
* PCM - Route & Volume Plugin
* Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
*
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "bswap.h"
#include <math.h>
#include "pcm_local.h"
#include "pcm_plugin.h"
#include "plugin_ops.h"
#ifndef PIC
/* entry for static linking */
const char *_snd_module_pcm_route = "";
#endif
#ifndef DOC_HIDDEN
/* The best possible hack to support missing optimization in gcc 2.7.2.3 */
#if SND_PCM_PLUGIN_ROUTE_RESOLUTION & (SND_PCM_PLUGIN_ROUTE_RESOLUTION - 1) != 0
#define div(a) a /= SND_PCM_PLUGIN_ROUTE_RESOLUTION
#elif SND_PCM_PLUGIN_ROUTE_RESOLUTION == 16
#define div(a) a >>= 4
#else
#error "Add some code here"
#endif
typedef struct {
int channel;
int as_int;
#if SND_PCM_PLUGIN_ROUTE_FLOAT
float as_float;
#endif
} snd_pcm_route_ttable_src_t;
typedef struct snd_pcm_route_ttable_dst snd_pcm_route_ttable_dst_t;
typedef struct {
enum {UINT64, FLOAT} sum_idx;
unsigned int get_idx;
unsigned int put_idx;
unsigned int conv_idx;
int use_getput;
unsigned int src_size;
snd_pcm_format_t dst_sfmt;
unsigned int nsrcs;
unsigned int ndsts;
snd_pcm_route_ttable_dst_t *dsts;
} snd_pcm_route_params_t;
typedef void (*route_f)(const snd_pcm_channel_area_t *dst_area,
snd_pcm_uframes_t dst_offset,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset,
unsigned int src_channels,
snd_pcm_uframes_t frames,
const snd_pcm_route_ttable_dst_t *ttable,
const snd_pcm_route_params_t *params);
struct snd_pcm_route_ttable_dst {
int att; /* Attenuated */
unsigned int nsrcs;
snd_pcm_route_ttable_src_t* srcs;
route_f func;
};
typedef union {
int32_t as_sint32;
int64_t as_sint64;
#if SND_PCM_PLUGIN_ROUTE_FLOAT
float as_float;
#endif
} sum_t;
typedef struct {
/* This field need to be the first */
snd_pcm_plugin_t plug;
snd_pcm_format_t sformat;
int schannels;
snd_pcm_route_params_t params;
snd_pcm_chmap_t *chmap;
} snd_pcm_route_t;
#endif /* DOC_HIDDEN */
static void snd_pcm_route_convert1_zero(const snd_pcm_channel_area_t *dst_area,
snd_pcm_uframes_t dst_offset,
const snd_pcm_channel_area_t *src_areas ATTRIBUTE_UNUSED,
snd_pcm_uframes_t src_offset ATTRIBUTE_UNUSED,
unsigned int src_channels ATTRIBUTE_UNUSED,
snd_pcm_uframes_t frames,
const snd_pcm_route_ttable_dst_t* ttable ATTRIBUTE_UNUSED,
const snd_pcm_route_params_t *params)
{
snd_pcm_area_silence(dst_area, dst_offset, frames, params->dst_sfmt);
}
#ifndef DOC_HIDDEN
static void snd_pcm_route_convert1_one(const snd_pcm_channel_area_t *dst_area,
snd_pcm_uframes_t dst_offset,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset,
unsigned int src_channels,
snd_pcm_uframes_t frames,
const snd_pcm_route_ttable_dst_t* ttable,
const snd_pcm_route_params_t *params)
{
#define CONV_LABELS
#include "plugin_ops.h"
#undef CONV_LABELS
void *conv;
const snd_pcm_channel_area_t *src_area = 0;
unsigned int srcidx;
const char *src;
char *dst;
int src_step, dst_step;
for (srcidx = 0; srcidx < ttable->nsrcs && srcidx < src_channels; ++srcidx) {
unsigned int channel = ttable->srcs[srcidx].channel;
if (channel >= src_channels)
continue;
src_area = &src_areas[channel];
if (src_area->addr != NULL)
break;
}
if (srcidx == ttable->nsrcs || srcidx == src_channels) {
snd_pcm_route_convert1_zero(dst_area, dst_offset,
src_areas, src_offset,
src_channels,
frames, ttable, params);
return;
}
conv = conv_labels[params->conv_idx];
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);
while (frames-- > 0) {
goto *conv;
#define CONV_END after
#include "plugin_ops.h"
#undef CONV_END
after:
src += src_step;
dst += dst_step;
}
}
static void snd_pcm_route_convert1_one_getput(const snd_pcm_channel_area_t *dst_area,
snd_pcm_uframes_t dst_offset,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset,
unsigned int src_channels,
snd_pcm_uframes_t frames,
const snd_pcm_route_ttable_dst_t* ttable,
const snd_pcm_route_params_t *params)
{
#define CONV24_LABELS
#include "plugin_ops.h"
#undef CONV24_LABELS
void *get, *put;
const snd_pcm_channel_area_t *src_area = 0;
unsigned int srcidx;
const char *src;
char *dst;
int src_step, dst_step;
uint32_t sample = 0;
for (srcidx = 0; srcidx < ttable->nsrcs && srcidx < src_channels; ++srcidx) {
unsigned int channel = ttable->srcs[srcidx].channel;
if (channel >= src_channels)
continue;
src_area = &src_areas[channel];
if (src_area->addr != NULL)
break;
}
if (srcidx == ttable->nsrcs || srcidx == src_channels) {
snd_pcm_route_convert1_zero(dst_area, dst_offset,
src_areas, src_offset,
src_channels,
frames, ttable, params);
return;
}
get = get32_labels[params->get_idx];
put = put32_labels[params->put_idx];
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);
while (frames-- > 0) {
goto *get;
#define CONV24_END after
#include "plugin_ops.h"
#undef CONV24_END
after:
src += src_step;
dst += dst_step;
}
}
static void snd_pcm_route_convert1_many(const snd_pcm_channel_area_t *dst_area,
snd_pcm_uframes_t dst_offset,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset,
unsigned int src_channels,
snd_pcm_uframes_t frames,
const snd_pcm_route_ttable_dst_t* ttable,
const snd_pcm_route_params_t *params)
{
#define GET32_LABELS
#define PUT32_LABELS
#include "plugin_ops.h"
#undef GET32_LABELS
#undef PUT32_LABELS
static void *const zero_labels[2] = {
&&zero_int64,
#if SND_PCM_PLUGIN_ROUTE_FLOAT
&&zero_float
#endif
};
/* sum_type att */
static void *const add_labels[2 * 2] = {
&&add_int64_noatt, &&add_int64_att,
#if SND_PCM_PLUGIN_ROUTE_FLOAT
&&add_float_noatt, &&add_float_att
#endif
};
/* sum_type att */
static void *const norm_labels[2 * 2] = {
&&norm_int64_noatt,
&&norm_int64_att,
#if SND_PCM_PLUGIN_ROUTE_FLOAT
&&norm_float,
&&norm_float,
#endif
};
void *zero, *get32, *add, *norm, *put32;
int nsrcs = ttable->nsrcs;
char *dst;
int dst_step;
const char *srcs[nsrcs];
int src_steps[nsrcs];
snd_pcm_route_ttable_src_t src_tt[nsrcs];
int32_t sample = 0;
int srcidx, srcidx1 = 0;
for (srcidx = 0; srcidx < nsrcs && (unsigned)srcidx < src_channels; ++srcidx) {
const snd_pcm_channel_area_t *src_area;
unsigned int channel = ttable->srcs[srcidx].channel;
if (channel >= src_channels)
continue;
src_area = &src_areas[channel];
srcs[srcidx1] = snd_pcm_channel_area_addr(src_area, src_offset);
src_steps[srcidx1] = snd_pcm_channel_area_step(src_area);
src_tt[srcidx1] = ttable->srcs[srcidx];
srcidx1++;
}
nsrcs = srcidx1;
if (nsrcs == 0) {
snd_pcm_route_convert1_zero(dst_area, dst_offset,
src_areas, src_offset,
src_channels,
frames, ttable, params);
return;
} else if (nsrcs == 1 && src_tt[0].as_int == SND_PCM_PLUGIN_ROUTE_RESOLUTION) {
if (params->use_getput)
snd_pcm_route_convert1_one_getput(dst_area, dst_offset,
src_areas, src_offset,
src_channels,
frames, ttable, params);
else
snd_pcm_route_convert1_one(dst_area, dst_offset,
src_areas, src_offset,
src_channels,
frames, ttable, params);
return;
}
zero = zero_labels[params->sum_idx];
get32 = get32_labels[params->get_idx];
add = add_labels[params->sum_idx * 2 + ttable->att];
norm = norm_labels[params->sum_idx * 2 + ttable->att];
put32 = put32_labels[params->put_idx];
dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
dst_step = snd_pcm_channel_area_step(dst_area);
while (frames-- > 0) {
snd_pcm_route_ttable_src_t *ttp = src_tt;
sum_t sum;
/* Zero sum */
goto *zero;
zero_int64:
sum.as_sint64 = 0;
goto zero_end;
#if SND_PCM_PLUGIN_ROUTE_FLOAT
zero_float:
sum.as_float = 0.0;
goto zero_end;
#endif
zero_end:
for (srcidx = 0; srcidx < nsrcs; ++srcidx) {
const char *src = srcs[srcidx];
/* Get sample */
goto *get32;
#define GET32_END after_get
#include "plugin_ops.h"
#undef GET32_END
after_get:
/* Sum */
goto *add;
add_int64_att:
sum.as_sint64 += (int64_t) sample * ttp->as_int;
goto after_sum;
add_int64_noatt:
if (ttp->as_int)
sum.as_sint64 += sample;
goto after_sum;
#if SND_PCM_PLUGIN_ROUTE_FLOAT
add_float_att:
sum.as_float += sample * ttp->as_float;
goto after_sum;
add_float_noatt:
if (ttp->as_int)
sum.as_float += sample;
goto after_sum;
#endif
after_sum:
srcs[srcidx] += src_steps[srcidx];
ttp++;
}
/* Normalization */
goto *norm;
norm_int64_att:
div(sum.as_sint64);
/* fallthru */
norm_int64_noatt:
if (sum.as_sint64 > (int64_t)0x7fffffff)
sample = 0x7fffffff; /* maximum positive value */
else if (sum.as_sint64 < -(int64_t)0x80000000)
sample = 0x80000000; /* maximum negative value */
else
sample = sum.as_sint64;
goto after_norm;
#if SND_PCM_PLUGIN_ROUTE_FLOAT
norm_float:
sum.as_float = rint(sum.as_float);
if (sum.as_float > (int64_t)0x7fffffff)
sample = 0x7fffffff; /* maximum positive value */
else if (sum.as_float < -(int64_t)0x80000000)
sample = 0x80000000; /* maximum negative value */
else
sample = sum.as_float;
goto after_norm;
#endif
after_norm:
/* Put sample */
goto *put32;
#define PUT32_END after_put32
#include "plugin_ops.h"
#undef PUT32_END
after_put32:
dst += dst_step;
}
}
#endif /* DOC_HIDDEN */
static void snd_pcm_route_convert(const snd_pcm_channel_area_t *dst_areas,
snd_pcm_uframes_t dst_offset,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset,
unsigned int src_channels,
unsigned int dst_channels,
snd_pcm_uframes_t frames,
snd_pcm_route_params_t *params)
{
unsigned int dst_channel;
snd_pcm_route_ttable_dst_t *dstp;
const snd_pcm_channel_area_t *dst_area;
dstp = params->dsts;
dst_area = dst_areas;
for (dst_channel = 0; dst_channel < dst_channels; ++dst_channel) {
if (dst_channel >= params->ndsts)
snd_pcm_route_convert1_zero(dst_area, dst_offset,
src_areas, src_offset,
src_channels,
frames, dstp, params);
else
dstp->func(dst_area, dst_offset,
src_areas, src_offset,
src_channels,
frames, dstp, params);
dstp++;
dst_area++;
}
}
static int snd_pcm_route_close(snd_pcm_t *pcm)
{
snd_pcm_route_t *route = pcm->private_data;
snd_pcm_route_params_t *params = &route->params;
unsigned int dst_channel;
if (params->dsts) {
for (dst_channel = 0; dst_channel < params->ndsts; ++dst_channel) {
free(params->dsts[dst_channel].srcs);
}
free(params->dsts);
}
free(route->chmap);
return snd_pcm_generic_close(pcm);
}
static int snd_pcm_route_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)
{
int err;
snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
snd_pcm_format_mask_t format_mask = { SND_PCM_FMTBIT_LINEAR };
err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
&access_mask);
if (err < 0)
return err;
err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_FORMAT,
&format_mask);
if (err < 0)
return err;
err = _snd_pcm_hw_params_set_subformat(params, SND_PCM_SUBFORMAT_STD);
if (err < 0)
return err;
err = _snd_pcm_hw_param_set_min(params, SND_PCM_HW_PARAM_CHANNELS, 1, 0);
if (err < 0)
return err;
params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
return 0;
}
static int snd_pcm_route_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams)
{
snd_pcm_route_t *route = pcm->private_data;
snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
_snd_pcm_hw_params_any(sparams);
_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
&saccess_mask);
if (route->sformat != SND_PCM_FORMAT_UNKNOWN) {
_snd_pcm_hw_params_set_format(sparams, route->sformat);
_snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD);
}
if (route->schannels >= 0) {
_snd_pcm_hw_param_set(sparams, SND_PCM_HW_PARAM_CHANNELS,
(unsigned int) route->schannels, 0);
}
return 0;
}
static int snd_pcm_route_hw_refine_schange(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
snd_pcm_hw_params_t *sparams)
{
snd_pcm_route_t *route = pcm->private_data;
int err;
unsigned int links = (SND_PCM_HW_PARBIT_RATE |
SND_PCM_HW_PARBIT_PERIODS |
SND_PCM_HW_PARBIT_PERIOD_SIZE |
SND_PCM_HW_PARBIT_PERIOD_TIME |
SND_PCM_HW_PARBIT_BUFFER_SIZE |
SND_PCM_HW_PARBIT_BUFFER_TIME |
SND_PCM_HW_PARBIT_TICK_TIME);
if (route->sformat == SND_PCM_FORMAT_UNKNOWN)
links |= (SND_PCM_HW_PARBIT_FORMAT |
SND_PCM_HW_PARBIT_SUBFORMAT |
SND_PCM_HW_PARBIT_SAMPLE_BITS);
if (route->schannels < 0)
links |= SND_PCM_HW_PARBIT_CHANNELS;
err = _snd_pcm_hw_params_refine(sparams, links, params);
if (err < 0)
return err;
return 0;
}
static int snd_pcm_route_hw_refine_cchange(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
snd_pcm_hw_params_t *sparams)
{
snd_pcm_route_t *route = pcm->private_data;
int err;
unsigned int links = (SND_PCM_HW_PARBIT_RATE |
SND_PCM_HW_PARBIT_PERIODS |
SND_PCM_HW_PARBIT_PERIOD_SIZE |
SND_PCM_HW_PARBIT_PERIOD_TIME |
SND_PCM_HW_PARBIT_BUFFER_SIZE |
SND_PCM_HW_PARBIT_BUFFER_TIME |
SND_PCM_HW_PARBIT_TICK_TIME);
if (route->sformat == SND_PCM_FORMAT_UNKNOWN)
links |= (SND_PCM_HW_PARBIT_FORMAT |
SND_PCM_HW_PARBIT_SUBFORMAT |
SND_PCM_HW_PARBIT_SAMPLE_BITS);
if (route->schannels < 0)
links |= SND_PCM_HW_PARBIT_CHANNELS;
err = _snd_pcm_hw_params_refine(params, links, sparams);
if (err < 0)
return err;
return 0;
}
static int snd_pcm_route_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
{
return snd_pcm_hw_refine_slave(pcm, params,
snd_pcm_route_hw_refine_cprepare,
snd_pcm_route_hw_refine_cchange,
snd_pcm_route_hw_refine_sprepare,
snd_pcm_route_hw_refine_schange,
snd_pcm_generic_hw_refine);
}
static int snd_pcm_route_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
{
snd_pcm_route_t *route = pcm->private_data;
snd_pcm_t *slave = route->plug.gen.slave;
snd_pcm_format_t src_format, dst_format;
int err = snd_pcm_hw_params_slave(pcm, params,
snd_pcm_route_hw_refine_cchange,
snd_pcm_route_hw_refine_sprepare,
snd_pcm_route_hw_refine_schange,
snd_pcm_generic_hw_params);
if (err < 0)
return err;
if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
err = INTERNAL(snd_pcm_hw_params_get_format)(params, &src_format);
dst_format = slave->format;
} else {
src_format = slave->format;
err = INTERNAL(snd_pcm_hw_params_get_format)(params, &dst_format);
}
if (err < 0)
return err;
/* 3 bytes or 20-bit formats? */
route->params.use_getput =
(snd_pcm_format_physical_width(src_format) + 7) / 8 == 3 ||
(snd_pcm_format_physical_width(dst_format) + 7) / 8 == 3 ||
snd_pcm_format_width(src_format) == 20 ||
snd_pcm_format_width(dst_format) == 20;
route->params.get_idx = snd_pcm_linear_get_index(src_format, SND_PCM_FORMAT_S32);
route->params.put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S32, dst_format);
route->params.conv_idx = snd_pcm_linear_convert_index(src_format, dst_format);
route->params.src_size = snd_pcm_format_width(src_format) / 8;
route->params.dst_sfmt = dst_format;
#if SND_PCM_PLUGIN_ROUTE_FLOAT
route->params.sum_idx = FLOAT;
#else
route->params.sum_idx = UINT64;
#endif
return 0;
}
static snd_pcm_uframes_t
snd_pcm_route_write_areas(snd_pcm_t *pcm,
const snd_pcm_channel_area_t *areas,
snd_pcm_uframes_t offset,
snd_pcm_uframes_t size,
const snd_pcm_channel_area_t *slave_areas,
snd_pcm_uframes_t slave_offset,
snd_pcm_uframes_t *slave_sizep)
{
snd_pcm_route_t *route = pcm->private_data;
snd_pcm_t *slave = route->plug.gen.slave;
if (size > *slave_sizep)
size = *slave_sizep;
snd_pcm_route_convert(slave_areas, slave_offset,
areas, offset,
pcm->channels,
slave->channels,
size, &route->params);
*slave_sizep = size;
return size;
}
static snd_pcm_uframes_t
snd_pcm_route_read_areas(snd_pcm_t *pcm,
const snd_pcm_channel_area_t *areas,
snd_pcm_uframes_t offset,
snd_pcm_uframes_t size,
const snd_pcm_channel_area_t *slave_areas,
snd_pcm_uframes_t slave_offset,
snd_pcm_uframes_t *slave_sizep)
{
snd_pcm_route_t *route = pcm->private_data;
snd_pcm_t *slave = route->plug.gen.slave;
if (size > *slave_sizep)
size = *slave_sizep;
snd_pcm_route_convert(areas, offset,
slave_areas, slave_offset,
slave->channels,
pcm->channels,
size, &route->params);
*slave_sizep = size;
return size;
}
static snd_pcm_chmap_t *snd_pcm_route_get_chmap(snd_pcm_t *pcm)
{
snd_pcm_route_t *route = pcm->private_data;
snd_pcm_chmap_t *map, *slave_map;
unsigned int src, dst, nsrcs;
slave_map = snd_pcm_generic_get_chmap(pcm);
if (!slave_map)
return NULL;
nsrcs = route->params.nsrcs;
map = calloc(4, nsrcs + 1);
if (!map) {
free(slave_map);
return NULL;
}
map->channels = nsrcs;
for (src = 0; src < nsrcs; src++)
map->pos[src] = SND_CHMAP_NA;
for (dst = 0; dst < route->params.ndsts; dst++) {
snd_pcm_route_ttable_dst_t *d = &route->params.dsts[dst];
for (src = 0; src < d->nsrcs; src++) {
unsigned int c = d->srcs[src].channel;
if (c < nsrcs && map->pos[c] == SND_CHMAP_NA)
map->pos[c] = slave_map->pos[dst];
}
}
free(slave_map);
return map;
}
static snd_pcm_chmap_query_t **snd_pcm_route_query_chmaps(snd_pcm_t *pcm)
{
snd_pcm_chmap_query_t **maps;
snd_pcm_chmap_t *map = snd_pcm_route_get_chmap(pcm);
if (!map)
return NULL;
maps = _snd_pcm_make_single_query_chmaps(map);
free(map);
return maps;
}
static void snd_pcm_route_dump(snd_pcm_t *pcm, snd_output_t *out)
{
snd_pcm_route_t *route = pcm->private_data;
unsigned int dst;
if (route->sformat == SND_PCM_FORMAT_UNKNOWN)
snd_output_printf(out, "Route conversion PCM\n");
else
snd_output_printf(out, "Route conversion PCM (sformat=%s)\n",
snd_pcm_format_name(route->sformat));
snd_output_puts(out, " Transformation table:\n");
for (dst = 0; dst < route->params.ndsts; dst++) {
snd_pcm_route_ttable_dst_t *d = &route->params.dsts[dst];
unsigned int src;
snd_output_printf(out, " %d <- ", dst);
if (d->nsrcs == 0) {
snd_output_printf(out, "none\n");
continue;
}
src = 0;
while (1) {
snd_pcm_route_ttable_src_t *s = &d->srcs[src];
if (d->att)
#if SND_PCM_PLUGIN_ROUTE_FLOAT
snd_output_printf(out, "%d*%g", s->channel, s->as_float);
#else
snd_output_printf(out, "%d*%g", s->channel, (double)s->as_int / (double)SND_PCM_PLUGIN_ROUTE_RESOLUTION);
#endif
else
snd_output_printf(out, "%d", s->channel);
src++;
if (src == d->nsrcs)
break;
snd_output_puts(out, " + ");
}
snd_output_putc(out, '\n');
}
if (pcm->setup) {
snd_output_printf(out, "Its setup is:\n");
snd_pcm_dump_setup(pcm, out);
}
snd_output_printf(out, "Slave: ");
snd_pcm_dump(route->plug.gen.slave, out);
}
/*
* Converts a string to an array of channel indices:
* - Given a number, the result is an array with one element,
* containing that number
* - Given a channel name (e g "FL") and a chmap,
* it will look this up in the chmap and return all matches
* - Given a channel name and no chmap, the result is an array with one element,
containing alsa standard channel map. Note that this might be a negative
number in case of "UNKNOWN", "NA" or "MONO".
* Return value is number of matches written.
*/
static int strtochannel(const char *id, snd_pcm_chmap_t *chmap,
long *channel, int channel_size)
{
int ch;
if (safe_strtol(id, channel) >= 0)
return 1;
ch = (int) snd_pcm_chmap_from_string(id);
if (ch == -1)
return -EINVAL;
if (chmap) {
int i, r = 0;
/* Start with highest channel to simplify implementation of
determine ttable size */
for (i = chmap->channels - 1; i >= 0; i--) {
if ((int) chmap->pos[i] != ch)
continue;
if (r >= channel_size)
continue;
channel[r++] = i;
}
return r;
}
else {
/* Assume ALSA standard channel mapping */
*channel = ch - SND_CHMAP_FL;
return 1;
}
}
#define MAX_CHMAP_CHANNELS 256
static int determine_chmap(snd_config_t *tt, snd_pcm_chmap_t **tt_chmap)
{
snd_config_iterator_t i, inext;
snd_pcm_chmap_t *chmap;
assert(tt && tt_chmap);
chmap = malloc(sizeof(snd_pcm_chmap_t) +
MAX_CHMAP_CHANNELS * sizeof(unsigned int));
chmap->channels = 0;
snd_config_for_each(i, inext, tt) {
const char *id;
snd_config_iterator_t j, jnext;
snd_config_t *in = snd_config_iterator_entry(i);
if (snd_config_get_id(in, &id) < 0)
continue;
if (snd_config_get_type(in) != SND_CONFIG_TYPE_COMPOUND)
goto err;
snd_config_for_each(j, jnext, in) {
int ch, k, found;
long schannel;
snd_config_t *jnode = snd_config_iterator_entry(j);
if (snd_config_get_id(jnode, &id) < 0)
continue;
if (safe_strtol(id, &schannel) >= 0)
continue;
ch = (int) snd_pcm_chmap_from_string(id);
if (ch == -1)
goto err;
found = 0;
for (k = 0; k < (int) chmap->channels; k++)
if (ch == (int) chmap->pos[k]) {
found = 1;
break;
}
if (found)
continue;
if (chmap->channels >= MAX_CHMAP_CHANNELS) {
SNDERR("Too many channels in ttable chmap");
goto err;
}
chmap->pos[chmap->channels++] = ch;
}
}
if (chmap->channels == 0) {
free(chmap);
chmap = NULL;
}
*tt_chmap = chmap;
return 0;
err:
*tt_chmap = NULL;
free(chmap);
return -EINVAL;
}
static int find_matching_chmap(snd_pcm_t *spcm, snd_pcm_chmap_t *tt_chmap,
snd_pcm_chmap_t **found_chmap, int *schannels)
{
snd_pcm_chmap_query_t** chmaps = snd_pcm_query_chmaps(spcm);
int i;
*found_chmap = NULL;
if (chmaps == NULL)
return 0; /* chmap API not supported for this slave */
for (i = 0; chmaps[i]; i++) {
unsigned int j, k;
int match = 1;
snd_pcm_chmap_t *c = &chmaps[i]->map;
if (*schannels >= 0 && (int) c->channels != *schannels)
continue;
for (j = 0; j < tt_chmap->channels; j++) {
int found = 0;
unsigned int ch = tt_chmap->pos[j];
for (k = 0; k < c->channels; k++)
if (c->pos[k] == ch) {
found = 1;
break;
}
if (!found) {
match = 0;
break;
}
}
if (match) {
int size = sizeof(snd_pcm_chmap_t) + c->channels * sizeof(unsigned int);
*found_chmap = malloc(size);
if (!*found_chmap) {
snd_pcm_free_chmaps(chmaps);
return -ENOMEM;
}
memcpy(*found_chmap, c, size);
*schannels = c->channels;
break;
}
}
snd_pcm_free_chmaps(chmaps);
if (*found_chmap == NULL) {
SNDERR("Found no matching channel map");
return -EINVAL;
}
return 0;
}
static int route_chmap_init(snd_pcm_t *pcm)
{
int set_map = 0;
snd_pcm_chmap_t *current;
snd_pcm_route_t *route = pcm->private_data;
if (!route->chmap)
return 0;
if (__snd_pcm_state(pcm) != SND_PCM_STATE_PREPARED)
return 0;
/* Check if we really need to set the chmap or not.
This is important in case set_chmap is not implemented. */
current = snd_pcm_get_chmap(route->plug.gen.slave);
if (!current)
return -ENOSYS;
if (current->channels != route->chmap->channels)
set_map = 1;
else
set_map = memcmp(current->pos, route->chmap->pos,
current->channels);
free(current);
if (!set_map)
return 0;
return snd_pcm_set_chmap(route->plug.gen.slave, route->chmap);
}
static const snd_pcm_ops_t snd_pcm_route_ops = {
.close = snd_pcm_route_close,
.info = snd_pcm_generic_info,
.hw_refine = snd_pcm_route_hw_refine,
.hw_params = snd_pcm_route_hw_params,
.hw_free = snd_pcm_generic_hw_free,
.sw_params = snd_pcm_generic_sw_params,
.channel_info = snd_pcm_generic_channel_info,
.dump = snd_pcm_route_dump,
.nonblock = snd_pcm_generic_nonblock,
.async = snd_pcm_generic_async,
.mmap = snd_pcm_generic_mmap,
.munmap = snd_pcm_generic_munmap,
.query_chmaps = snd_pcm_route_query_chmaps,
.get_chmap = snd_pcm_route_get_chmap,
.set_chmap = NULL, /* NYI */
};
static int route_load_ttable(snd_pcm_route_params_t *params, snd_pcm_stream_t stream,
unsigned int tt_ssize,
snd_pcm_route_ttable_entry_t *ttable,
unsigned int tt_cused, unsigned int tt_sused)
{
unsigned int src_channel, dst_channel;
snd_pcm_route_ttable_dst_t *dptr;
unsigned int sused, dused, smul, dmul;
if (stream == SND_PCM_STREAM_PLAYBACK) {
sused = tt_cused;
dused = tt_sused;
smul = tt_ssize;
dmul = 1;
} else {
sused = tt_sused;
dused = tt_cused;
smul = 1;
dmul = tt_ssize;
}
params->ndsts = dused;
params->nsrcs = sused;
dptr = calloc(dused, sizeof(*params->dsts));
if (!dptr)
return -ENOMEM;
params->dsts = dptr;
for (dst_channel = 0; dst_channel < dused; ++dst_channel) {
snd_pcm_route_ttable_entry_t t = 0;
int att = 0;
int nsrcs = 0;
snd_pcm_route_ttable_src_t srcs[sused];
for (src_channel = 0; src_channel < sused; ++src_channel) {
snd_pcm_route_ttable_entry_t v;
v = ttable[src_channel * smul + dst_channel * dmul];
if (v != 0) {
srcs[nsrcs].channel = src_channel;
#if SND_PCM_PLUGIN_ROUTE_FLOAT
/* Also in user space for non attenuated */
srcs[nsrcs].as_int = (v == SND_PCM_PLUGIN_ROUTE_FULL ? SND_PCM_PLUGIN_ROUTE_RESOLUTION : 0);
srcs[nsrcs].as_float = v;
#else
assert(v >= 0 && v <= SND_PCM_PLUGIN_ROUTE_FULL);
srcs[nsrcs].as_int = v;
#endif
if (v != SND_PCM_PLUGIN_ROUTE_FULL)
att = 1;
t += v;
nsrcs++;
}
}
#if 0
assert(t <= SND_PCM_PLUGIN_ROUTE_FULL);
#endif
dptr->att = att;
dptr->nsrcs = nsrcs;
if (nsrcs == 0)
dptr->func = snd_pcm_route_convert1_zero;
else
dptr->func = snd_pcm_route_convert1_many;
if (nsrcs > 0) {
dptr->srcs = calloc((unsigned int) nsrcs, sizeof(*srcs));
if (!dptr->srcs)
return -ENOMEM;
memcpy(dptr->srcs, srcs, sizeof(*srcs) * nsrcs);
} else
dptr->srcs = 0;
dptr++;
}
return 0;
}
/**
* \brief Creates a new Route & Volume PCM
* \param pcmp Returns created PCM handle
* \param name Name of PCM
* \param sformat Slave format
* \param schannels Slave channels
* \param ttable Attenuation table
* \param tt_ssize Attenuation table - slave size
* \param tt_cused Attenuation table - client used count
* \param tt_sused Attenuation table - slave used count
* \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
* \warning Using of this function might be dangerous in the sense
* of compatibility reasons. The prototype might be freely
* changed in future.
*/
int snd_pcm_route_open(snd_pcm_t **pcmp, const char *name,
snd_pcm_format_t sformat, int schannels,
snd_pcm_route_ttable_entry_t *ttable,
unsigned int tt_ssize,
unsigned int tt_cused, unsigned int tt_sused,
snd_pcm_t *slave, int close_slave)
{
snd_pcm_t *pcm;
snd_pcm_route_t *route;
int err;
assert(pcmp && slave && ttable);
if (sformat != SND_PCM_FORMAT_UNKNOWN &&
snd_pcm_format_linear(sformat) != 1)
return -EINVAL;
route = calloc(1, sizeof(snd_pcm_route_t));
if (!route) {
return -ENOMEM;
}
snd_pcm_plugin_init(&route->plug);
route->sformat = sformat;
route->schannels = schannels;
route->plug.read = snd_pcm_route_read_areas;
route->plug.write = snd_pcm_route_write_areas;
route->plug.undo_read = snd_pcm_plugin_undo_read_generic;
route->plug.undo_write = snd_pcm_plugin_undo_write_generic;
route->plug.gen.slave = slave;
route->plug.gen.close_slave = close_slave;
route->plug.init = route_chmap_init;
err = snd_pcm_new(&pcm, SND_PCM_TYPE_ROUTE, name, slave->stream, slave->mode);
if (err < 0) {
free(route);
return err;
}
pcm->ops = &snd_pcm_route_ops;
pcm->fast_ops = &snd_pcm_plugin_fast_ops;
pcm->private_data = route;
pcm->poll_fd = slave->poll_fd;
pcm->poll_events = slave->poll_events;
pcm->tstamp_type = slave->tstamp_type;
snd_pcm_set_hw_ptr(pcm, &route->plug.hw_ptr, -1, 0);
snd_pcm_set_appl_ptr(pcm, &route->plug.appl_ptr, -1, 0);
err = route_load_ttable(&route->params, pcm->stream, tt_ssize, ttable, tt_cused, tt_sused);
if (err < 0) {
snd_pcm_close(pcm);
return err;
}
*pcmp = pcm;
return 0;
}
static int _snd_pcm_route_determine_ttable(snd_config_t *tt,
unsigned int *tt_csize,
unsigned int *tt_ssize,
snd_pcm_chmap_t *chmap)
{
snd_config_iterator_t i, inext;
long csize = 0, ssize = 0;
int err;
assert(tt && tt_csize && tt_ssize);
snd_config_for_each(i, inext, tt) {
snd_config_t *in = snd_config_iterator_entry(i);
snd_config_iterator_t j, jnext;
long cchannel;
const char *id;
if (snd_config_get_id(in, &id) < 0)
continue;
err = safe_strtol(id, &cchannel);
if (err < 0) {
SNDERR("Invalid client channel: %s", id);
return -EINVAL;
}
if (cchannel + 1 > csize)
csize = cchannel + 1;
if (snd_config_get_type(in) != SND_CONFIG_TYPE_COMPOUND)
return -EINVAL;
snd_config_for_each(j, jnext, in) {
snd_config_t *jnode = snd_config_iterator_entry(j);
long schannel;
const char *id;
if (snd_config_get_id(jnode, &id) < 0)
continue;
err = strtochannel(id, chmap, &schannel, 1);
if (err < 0) {
SNDERR("Invalid slave channel: %s", id);
return -EINVAL;
}
if (schannel + 1 > ssize)
ssize = schannel + 1;
}
}
if (csize == 0 || ssize == 0) {
SNDERR("Invalid null ttable configuration");
return -EINVAL;
}
*tt_csize = csize;
*tt_ssize = ssize;
return 0;
}
/**
* \brief Determine route matrix sizes
* \param tt Configuration root describing route matrix
* \param tt_csize Returned client size in elements
* \param tt_ssize Returned slave size in elements
* \retval zero on success otherwise a negative error code
*/
int snd_pcm_route_determine_ttable(snd_config_t *tt,
unsigned int *tt_csize,
unsigned int *tt_ssize)
{
return _snd_pcm_route_determine_ttable(tt, tt_csize, tt_ssize, NULL);
}
/**
* \brief Load route matrix
* \param tt Configuration root describing route matrix
* \param ttable Returned route matrix
* \param tt_csize Client size in elements
* \param tt_ssize Slave size in elements
* \param tt_cused Used client elements
* \param tt_sused Used slave elements
* \param schannels Slave channels
* \retval zero on success otherwise a negative error code
*/
static int _snd_pcm_route_load_ttable(snd_config_t *tt, snd_pcm_route_ttable_entry_t *ttable,
unsigned int tt_csize, unsigned int tt_ssize,
unsigned int *tt_cused, unsigned int *tt_sused,
int schannels, snd_pcm_chmap_t *chmap)
{
int cused = -1;
int sused = -1;
snd_config_iterator_t i, inext;
unsigned int k;
int err;
for (k = 0; k < tt_csize * tt_ssize; ++k)
ttable[k] = 0.0;
snd_config_for_each(i, inext, tt) {
snd_config_t *in = snd_config_iterator_entry(i);
snd_config_iterator_t j, jnext;
long cchannel;
const char *id;
if (snd_config_get_id(in, &id) < 0)
continue;
err = safe_strtol(id, &cchannel);
if (err < 0 ||
cchannel < 0 || (unsigned int) cchannel > tt_csize) {
SNDERR("Invalid client channel: %s", id);
return -EINVAL;
}
if (snd_config_get_type(in) != SND_CONFIG_TYPE_COMPOUND)
return -EINVAL;
snd_config_for_each(j, jnext, in) {
snd_config_t *jnode = snd_config_iterator_entry(j);
double value;
int ss;
long *scha = alloca(tt_ssize * sizeof(long));
const char *id;
if (snd_config_get_id(jnode, &id) < 0)
continue;
ss = strtochannel(id, chmap, scha, tt_ssize);
if (ss < 0) {
SNDERR("Invalid slave channel: %s", id);
return -EINVAL;
}
err = snd_config_get_real(jnode, &value);
if (err < 0) {
long v;
err = snd_config_get_integer(jnode, &v);
if (err < 0) {
SNDERR("Invalid type for %s", id);
return -EINVAL;
}
value = v;
}
for (k = 0; (int) k < ss; k++) {
long schannel = scha[k];
if (schannel < 0 || (unsigned int) schannel > tt_ssize ||
(schannels > 0 && schannel >= schannels)) {
SNDERR("Invalid slave channel: %s", id);
return -EINVAL;
}
ttable[cchannel * tt_ssize + schannel] = value;
if (schannel > sused)
sused = schannel;
}
}
if (cchannel > cused)
cused = cchannel;
}
*tt_sused = sused + 1;
*tt_cused = cused + 1;
return 0;
}
/**
* \brief Load route matrix
* \param tt Configuration root describing route matrix
* \param ttable Returned route matrix
* \param tt_csize Client size in elements
* \param tt_ssize Slave size in elements
* \param tt_cused Used client elements
* \param tt_sused Used slave elements
* \param schannels Slave channels
* \retval zero on success otherwise a negative error code
*/
int snd_pcm_route_load_ttable(snd_config_t *tt, snd_pcm_route_ttable_entry_t *ttable,
unsigned int tt_csize, unsigned int tt_ssize,
unsigned int *tt_cused, unsigned int *tt_sused,
int schannels)
{
return _snd_pcm_route_load_ttable(tt, ttable, tt_csize, tt_ssize,
tt_cused, tt_sused, schannels, NULL);
}
/*! \page pcm_plugins
\section pcm_plugins_route Plugin: Route & Volume
This plugin converts channels and applies volume during the conversion.
The format and rate must match for both of them.
SCHANNEL can be a channel name instead of a number (e g FL, LFE).
If so, a matching channel map will be selected for the slave.
\code
pcm.name {
type route # Route & Volume conversion PCM
slave STR # Slave name
# or
slave { # Slave definition
pcm STR # Slave PCM name
# or
pcm { } # Slave PCM definition
[format STR] # Slave format
[channels INT] # Slave channels
}
ttable { # Transfer table (bi-dimensional compound of cchannels * schannels numbers)
CCHANNEL {
SCHANNEL REAL # route value (0.0 - 1.0)
}
}
}
\endcode
\subsection pcm_plugins_route_funcref Function reference
<UL>
<LI>snd_pcm_route_open()
<LI>_snd_pcm_route_open()
</UL>
*/
/**
* \brief Creates a new Route & Volume PCM
* \param pcmp Returns created PCM handle
* \param name Name of PCM
* \param root Root configuration node
* \param conf Configuration node with Route & Volume PCM description
* \param stream Stream type
* \param mode Stream mode
* \retval zero on success otherwise a negative error code
* \warning Using of this function might be dangerous in the sense
* of compatibility reasons. The prototype might be freely
* changed in future.
*/
int _snd_pcm_route_open(snd_pcm_t **pcmp, const char *name,
snd_config_t *root, snd_config_t *conf,
snd_pcm_stream_t stream, int mode)
{
snd_config_iterator_t i, next;
int err;
snd_pcm_t *spcm;
snd_config_t *slave = NULL, *sconf;
snd_pcm_chmap_t *tt_chmap = NULL, *chmap = NULL;
snd_pcm_format_t sformat = SND_PCM_FORMAT_UNKNOWN;
int schannels = -1;
snd_config_t *tt = NULL;
snd_pcm_route_ttable_entry_t *ttable = NULL;
unsigned int csize, ssize;
unsigned int cused, sused;
snd_config_for_each(i, next, conf) {
snd_config_t *n = snd_config_iterator_entry(i);
const char *id;
if (snd_config_get_id(n, &id) < 0)
continue;
if (snd_pcm_conf_generic_id(id))
continue;
if (strcmp(id, "slave") == 0) {
slave = n;
continue;
}
if (strcmp(id, "ttable") == 0) {
if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
SNDERR("Invalid type for %s", id);
return -EINVAL;
}
tt = n;
continue;
}
SNDERR("Unknown field %s", id);
return -EINVAL;
}
if (!slave) {
SNDERR("slave is not defined");
return -EINVAL;
}
if (!tt) {
SNDERR("ttable is not defined");
return -EINVAL;
}
err = snd_pcm_slave_conf(root, slave, &sconf, 2,
SND_PCM_HW_PARAM_FORMAT, 0, &sformat,
SND_PCM_HW_PARAM_CHANNELS, 0, &schannels);
if (err < 0)
return err;
if (sformat != SND_PCM_FORMAT_UNKNOWN &&
snd_pcm_format_linear(sformat) != 1) {
snd_config_delete(sconf);
SNDERR("slave format is not linear");
return -EINVAL;
}
err = determine_chmap(tt, &tt_chmap);
if (err < 0) {
free(ttable);
return err;
}
err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
snd_config_delete(sconf);
if (err < 0) {
free(tt_chmap);
free(ttable);
return err;
}
if (tt_chmap) {
err = find_matching_chmap(spcm, tt_chmap, &chmap, &schannels);
free(tt_chmap);
if (err < 0) {
snd_pcm_close(spcm);
return err;
}
}
err = _snd_pcm_route_determine_ttable(tt, &csize, &ssize, chmap);
if (err < 0) {
free(chmap);
snd_pcm_close(spcm);
return err;
}
ttable = malloc(csize * ssize * sizeof(snd_pcm_route_ttable_entry_t));
if (ttable == NULL) {
free(chmap);
snd_pcm_close(spcm);
return -ENOMEM;
}
err = _snd_pcm_route_load_ttable(tt, ttable, csize, ssize,
&cused, &sused, schannels, chmap);
if (err < 0) {
free(chmap);
free(ttable);
snd_pcm_close(spcm);
return err;
}
err = snd_pcm_route_open(pcmp, name, sformat, schannels,
ttable, ssize,
cused, sused,
spcm, 1);
free(ttable);
if (err < 0) {
free(chmap);
snd_pcm_close(spcm);
} else {
((snd_pcm_route_t*) (*pcmp)->private_data)->chmap = chmap;
}
return err;
}
#ifndef DOC_HIDDEN
SND_DLSYM_BUILD_VERSION(_snd_pcm_route_open, SND_PCM_DLSYM_VERSION);
#endif