alsa-lib/src/pcm/pcm_route.c

1396 lines
38 KiB
C
Raw Normal View History

2002-01-16 16:42:40 +00:00
/**
* \file pcm/pcm_route.c
* \ingroup PCM_Plugins
* \brief PCM Route & Volume Plugin Interface
* \author Abramo Bagnara <abramo@alsa-project.org>
* \date 2000-2001
*/
2000-09-24 09:57:26 +00:00
/*
2002-01-16 16:42:40 +00:00
* PCM - Route & Volume Plugin
2000-09-24 09:57:26 +00:00
* 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
2000-09-24 09:57:26 +00:00
* 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.
2000-09-24 09:57:26 +00:00
*
* You should have received a copy of the GNU Lesser General Public
2000-09-24 09:57:26 +00:00
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2000-09-24 09:57:26 +00:00
*
*/
#include "bswap.h"
2000-09-24 09:57:26 +00:00
#include <math.h>
#include "pcm_local.h"
#include "pcm_plugin.h"
#include "plugin_ops.h"
2001-10-24 14:14:11 +00:00
#ifndef PIC
/* entry for static linking */
const char *_snd_module_pcm_route = "";
#endif
2002-01-16 16:42:40 +00:00
#ifndef DOC_HIDDEN
2000-09-24 09:57:26 +00:00
/* 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
2000-09-24 09:57:26 +00:00
#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
2000-09-24 09:57:26 +00:00
float as_float;
#endif
2001-01-26 09:56:30 +00:00
} snd_pcm_route_ttable_src_t;
2000-09-24 09:57:26 +00:00
2001-01-26 09:56:30 +00:00
typedef struct snd_pcm_route_ttable_dst snd_pcm_route_ttable_dst_t;
2000-09-24 09:57:26 +00:00
typedef struct {
enum {UINT64, FLOAT} sum_idx;
2001-03-29 17:50:28 +00:00
unsigned int get_idx;
unsigned int put_idx;
unsigned int conv_idx;
int use_getput;
2001-03-29 17:50:28 +00:00
unsigned int src_size;
snd_pcm_format_t dst_sfmt;
unsigned int nsrcs;
unsigned int ndsts;
2001-01-26 09:56:30 +00:00
snd_pcm_route_ttable_dst_t *dsts;
} snd_pcm_route_params_t;
2000-09-24 09:57:26 +00:00
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,
2001-01-26 09:56:30 +00:00
const snd_pcm_route_ttable_dst_t *ttable,
const snd_pcm_route_params_t *params);
2000-09-24 09:57:26 +00:00
2001-01-26 09:56:30 +00:00
struct snd_pcm_route_ttable_dst {
2000-09-24 09:57:26 +00:00
int att; /* Attenuated */
unsigned int nsrcs;
2001-01-26 09:56:30 +00:00
snd_pcm_route_ttable_src_t* srcs;
2000-09-24 09:57:26 +00:00
route_f func;
};
typedef union {
2001-10-25 18:24:22 +00:00
int32_t as_sint32;
int64_t as_sint64;
#if SND_PCM_PLUGIN_ROUTE_FLOAT
2000-09-24 09:57:26 +00:00
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;
2000-09-24 09:57:26 +00:00
int schannels;
2001-01-26 09:56:30 +00:00
snd_pcm_route_params_t params;
snd_pcm_chmap_t *chmap;
2000-09-24 09:57:26 +00:00
} snd_pcm_route_t;
2002-01-16 16:42:40 +00:00
#endif /* DOC_HIDDEN */
2000-09-24 09:57:26 +00:00
2001-03-29 17:50:28 +00:00
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,
2001-03-29 17:50:28 +00:00
snd_pcm_uframes_t frames,
const snd_pcm_route_ttable_dst_t* ttable ATTRIBUTE_UNUSED,
const snd_pcm_route_params_t *params)
2000-09-24 09:57:26 +00:00
{
snd_pcm_area_silence(dst_area, dst_offset, frames, params->dst_sfmt);
}
2002-01-16 16:42:40 +00:00
#ifndef DOC_HIDDEN
2001-03-29 17:50:28 +00:00
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,
2001-03-29 17:50:28 +00:00
snd_pcm_uframes_t frames,
const snd_pcm_route_ttable_dst_t* ttable,
const snd_pcm_route_params_t *params)
2000-09-24 09:57:26 +00:00
{
#define CONV_LABELS
#include "plugin_ops.h"
#undef CONV_LABELS
void *conv;
const snd_pcm_channel_area_t *src_area = 0;
2000-09-24 09:57:26 +00:00
unsigned int srcidx;
const char *src;
char *dst;
2000-09-24 09:57:26 +00:00
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];
2000-09-24 09:57:26 +00:00
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);
2000-09-24 09:57:26 +00:00
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;
u_int32_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;
}
}
2001-03-29 17:50:28 +00:00
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,
2001-03-29 17:50:28 +00:00
snd_pcm_uframes_t frames,
const snd_pcm_route_ttable_dst_t* ttable,
const snd_pcm_route_params_t *params)
2000-09-24 09:57:26 +00:00
{
#define GET32_LABELS
2000-10-22 09:50:20 +00:00
#define PUT32_LABELS
2000-09-24 09:57:26 +00:00
#include "plugin_ops.h"
#undef GET32_LABELS
2000-10-22 09:50:20 +00:00
#undef PUT32_LABELS
static void *const zero_labels[2] = {
&&zero_int64,
#if SND_PCM_PLUGIN_ROUTE_FLOAT
2001-03-17 16:34:43 +00:00
&&zero_float
2000-09-24 09:57:26 +00:00
#endif
};
/* sum_type att */
static void *const add_labels[2 * 2] = {
2001-03-17 16:34:43 +00:00
&&add_int64_noatt, &&add_int64_att,
#if SND_PCM_PLUGIN_ROUTE_FLOAT
2001-03-17 16:34:43 +00:00
&&add_float_noatt, &&add_float_att
2000-09-24 09:57:26 +00:00
#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,
2000-09-24 09:57:26 +00:00
#endif
};
void *zero, *get32, *add, *norm, *put32;
2000-09-24 09:57:26 +00:00
int nsrcs = ttable->nsrcs;
char *dst;
int dst_step;
const char *srcs[nsrcs];
2000-09-24 09:57:26 +00:00
int src_steps[nsrcs];
2001-01-26 09:56:30 +00:00
snd_pcm_route_ttable_src_t src_tt[nsrcs];
2001-10-25 18:24:22 +00:00
int32_t sample = 0;
2000-09-24 09:57:26 +00:00
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];
2000-09-24 09:57:26 +00:00
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);
2000-09-24 09:57:26 +00:00
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);
2000-09-24 09:57:26 +00:00
return;
}
zero = zero_labels[params->sum_idx];
get32 = get32_labels[params->get_idx];
2000-09-24 09:57:26 +00:00
add = add_labels[params->sum_idx * 2 + ttable->att];
norm = norm_labels[params->sum_idx * 2 + ttable->att];
2000-10-22 09:50:20 +00:00
put32 = put32_labels[params->put_idx];
2000-09-24 09:57:26 +00:00
dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
dst_step = snd_pcm_channel_area_step(dst_area);
while (frames-- > 0) {
2001-01-26 09:56:30 +00:00
snd_pcm_route_ttable_src_t *ttp = src_tt;
2000-09-24 09:57:26 +00:00
sum_t sum;
/* Zero sum */
goto *zero;
zero_int64:
2001-10-25 18:24:22 +00:00
sum.as_sint64 = 0;
2000-09-24 09:57:26 +00:00
goto zero_end;
#if SND_PCM_PLUGIN_ROUTE_FLOAT
2000-09-24 09:57:26 +00:00
zero_float:
sum.as_float = 0.0;
goto zero_end;
#endif
zero_end:
for (srcidx = 0; srcidx < nsrcs; ++srcidx) {
const char *src = srcs[srcidx];
2000-09-24 09:57:26 +00:00
/* Get sample */
goto *get32;
#define GET32_END after_get
2000-09-24 09:57:26 +00:00
#include "plugin_ops.h"
#undef GET32_END
2000-09-24 09:57:26 +00:00
after_get:
/* Sum */
goto *add;
add_int64_att:
2002-07-11 09:03:47 +00:00
sum.as_sint64 += (int64_t) sample * ttp->as_int;
2000-09-24 09:57:26 +00:00
goto after_sum;
add_int64_noatt:
if (ttp->as_int)
2001-10-25 18:24:22 +00:00
sum.as_sint64 += sample;
2000-09-24 09:57:26 +00:00
goto after_sum;
#if SND_PCM_PLUGIN_ROUTE_FLOAT
2000-09-24 09:57:26 +00:00
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:
2001-10-25 18:24:22 +00:00
div(sum.as_sint64);
/* fallthru */
norm_int64_noatt:
if (sum.as_sint64 > (int64_t)0x7fffffff)
sample = 0x7fffffff; /* maximum positive value */
2002-07-11 09:03:47 +00:00
else if (sum.as_sint64 < -(int64_t)0x80000000)
sample = 0x80000000; /* maximum negative value */
2000-09-24 09:57:26 +00:00
else
2001-10-25 18:24:22 +00:00
sample = sum.as_sint64;
2000-09-24 09:57:26 +00:00
goto after_norm;
#if SND_PCM_PLUGIN_ROUTE_FLOAT
2000-09-24 09:57:26 +00:00
norm_float:
2002-07-11 08:01:58 +00:00
sum.as_float = rint(sum.as_float);
if (sum.as_float > (int64_t)0x7fffffff)
sample = 0x7fffffff; /* maximum positive value */
2002-07-11 09:03:47 +00:00
else if (sum.as_float < -(int64_t)0x80000000)
sample = 0x80000000; /* maximum negative value */
2000-09-24 09:57:26 +00:00
else
sample = sum.as_float;
goto after_norm;
#endif
after_norm:
/* Put sample */
2000-10-22 09:50:20 +00:00
goto *put32;
#define PUT32_END after_put32
2000-09-24 09:57:26 +00:00
#include "plugin_ops.h"
2000-10-22 09:50:20 +00:00
#undef PUT32_END
after_put32:
2000-09-24 09:57:26 +00:00
dst += dst_step;
}
}
2002-01-16 16:42:40 +00:00
#endif /* DOC_HIDDEN */
2001-03-29 17:50:28 +00:00
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,
2001-03-29 17:50:28 +00:00
snd_pcm_uframes_t frames,
snd_pcm_route_params_t *params)
2000-09-24 09:57:26 +00:00
{
unsigned int dst_channel;
2001-01-26 09:56:30 +00:00
snd_pcm_route_ttable_dst_t *dstp;
const snd_pcm_channel_area_t *dst_area;
2000-09-24 09:57:26 +00:00
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);
2000-09-24 09:57:26 +00:00
else
dstp->func(dst_area, dst_offset,
src_areas, src_offset,
src_channels,
frames, dstp, params);
2000-09-24 09:57:26 +00:00
dstp++;
dst_area++;
}
}
static int snd_pcm_route_close(snd_pcm_t *pcm)
{
snd_pcm_route_t *route = pcm->private_data;
2001-01-26 09:56:30 +00:00
snd_pcm_route_params_t *params = &route->params;
unsigned int dst_channel;
2000-09-24 09:57:26 +00:00
if (params->dsts) {
for (dst_channel = 0; dst_channel < params->ndsts; ++dst_channel) {
free(params->dsts[dst_channel].srcs);
2000-09-24 09:57:26 +00:00
}
free(params->dsts);
}
free(route->chmap);
return snd_pcm_generic_close(pcm);
2000-09-24 09:57:26 +00:00
}
static int snd_pcm_route_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)
2000-09-24 09:57:26 +00:00
{
int err;
2001-03-21 16:31:31 +00:00
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);
2000-12-21 20:44:10 +00:00
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);
2000-12-21 20:44:10 +00:00
if (err < 0)
return err;
err = _snd_pcm_hw_param_set_min(params, SND_PCM_HW_PARAM_CHANNELS, 1, 0);
2000-12-21 20:44:10 +00:00
if (err < 0)
return err;
params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
return 0;
}
2000-12-21 20:44:10 +00:00
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,
2001-03-29 17:50:28 +00:00
(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)
2000-12-21 20:44:10 +00:00
links |= (SND_PCM_HW_PARBIT_FORMAT |
SND_PCM_HW_PARBIT_SUBFORMAT |
SND_PCM_HW_PARBIT_SAMPLE_BITS);
if (route->schannels < 0)
2000-12-21 20:44:10 +00:00
links |= SND_PCM_HW_PARBIT_CHANNELS;
err = _snd_pcm_hw_params_refine(sparams, links, params);
2000-09-24 09:57:26 +00:00
if (err < 0)
return err;
2000-11-20 20:10:46 +00:00
return 0;
2000-09-24 09:57:26 +00:00
}
static int snd_pcm_route_hw_refine_cchange(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
snd_pcm_hw_params_t *sparams)
2000-09-24 09:57:26 +00:00
{
snd_pcm_route_t *route = pcm->private_data;
2000-09-24 09:57:26 +00:00
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 |
2000-12-21 20:44:10 +00:00
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)
2000-12-21 20:44:10 +00:00
links |= (SND_PCM_HW_PARBIT_FORMAT |
SND_PCM_HW_PARBIT_SUBFORMAT |
SND_PCM_HW_PARBIT_SAMPLE_BITS);
if (route->schannels < 0)
2000-12-21 20:44:10 +00:00
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);
}
2000-12-21 20:44:10 +00:00
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);
2000-09-24 09:57:26 +00:00
if (err < 0)
return err;
2000-09-24 09:57:26 +00:00
if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
err = INTERNAL(snd_pcm_hw_params_get_format)(params, &src_format);
2000-11-20 20:10:46 +00:00
dst_format = slave->format;
2000-09-24 09:57:26 +00:00
} else {
2000-11-20 20:10:46 +00:00
src_format = slave->format;
err = INTERNAL(snd_pcm_hw_params_get_format)(params, &dst_format);
2000-09-24 09:57:26 +00:00
}
if (err < 0)
return err;
/* 3 bytes formats? */
route->params.use_getput =
(snd_pcm_format_physical_width(src_format) + 7) / 3 == 3 ||
(snd_pcm_format_physical_width(dst_format) + 7) / 3 == 3;
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);
2001-01-26 09:56:30 +00:00
route->params.conv_idx = snd_pcm_linear_convert_index(src_format, dst_format);
2000-09-24 09:57:26 +00:00
route->params.src_size = snd_pcm_format_width(src_format) / 8;
route->params.dst_sfmt = dst_format;
#if SND_PCM_PLUGIN_ROUTE_FLOAT
2000-09-24 09:57:26 +00:00
route->params.sum_idx = FLOAT;
#else
route->params.sum_idx = UINT64;
2000-09-24 09:57:26 +00:00
#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)
2000-09-24 09:57:26 +00:00
{
snd_pcm_route_t *route = pcm->private_data;
snd_pcm_t *slave = route->plug.gen.slave;
if (size > *slave_sizep)
2000-09-24 09:57:26 +00:00
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;
2000-09-24 09:57:26 +00:00
}
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)
2000-09-24 09:57:26 +00:00
{
snd_pcm_route_t *route = pcm->private_data;
snd_pcm_t *slave = route->plug.gen.slave;
if (size > *slave_sizep)
2000-09-24 09:57:26 +00:00
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;
2000-09-24 09:57:26 +00:00
}
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;
}
2001-01-17 11:00:32 +00:00
static void snd_pcm_route_dump(snd_pcm_t *pcm, snd_output_t *out)
2000-09-24 09:57:26 +00:00
{
snd_pcm_route_t *route = pcm->private_data;
2000-09-24 09:57:26 +00:00
unsigned int dst;
if (route->sformat == SND_PCM_FORMAT_UNKNOWN)
2001-01-17 11:00:32 +00:00
snd_output_printf(out, "Route conversion PCM\n");
2000-09-24 09:57:26 +00:00
else
2001-01-17 11:00:32 +00:00
snd_output_printf(out, "Route conversion PCM (sformat=%s)\n",
2000-11-20 20:10:46 +00:00
snd_pcm_format_name(route->sformat));
snd_output_puts(out, " Transformation table:\n");
2000-09-24 09:57:26 +00:00
for (dst = 0; dst < route->params.ndsts; dst++) {
2001-01-26 09:56:30 +00:00
snd_pcm_route_ttable_dst_t *d = &route->params.dsts[dst];
2000-09-24 09:57:26 +00:00
unsigned int src;
snd_output_printf(out, " %d <- ", dst);
if (d->nsrcs == 0) {
2001-11-19 11:54:06 +00:00
snd_output_printf(out, "none\n");
continue;
}
2000-09-24 09:57:26 +00:00
src = 0;
while (1) {
2001-01-26 09:56:30 +00:00
snd_pcm_route_ttable_src_t *s = &d->srcs[src];
2000-09-24 09:57:26 +00:00
if (d->att)
#if SND_PCM_PLUGIN_ROUTE_FLOAT
2001-01-17 11:00:32 +00:00
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
2000-09-24 09:57:26 +00:00
else
2001-01-17 11:00:32 +00:00
snd_output_printf(out, "%d", s->channel);
2000-09-24 09:57:26 +00:00
src++;
if (src == d->nsrcs)
break;
2001-01-17 11:00:32 +00:00
snd_output_puts(out, " + ");
2000-09-24 09:57:26 +00:00
}
2001-01-17 11:00:32 +00:00
snd_output_putc(out, '\n');
2000-09-24 09:57:26 +00:00
}
2000-11-20 20:10:46 +00:00
if (pcm->setup) {
2001-01-17 11:00:32 +00:00
snd_output_printf(out, "Its setup is:\n");
snd_pcm_dump_setup(pcm, out);
2000-09-24 09:57:26 +00:00
}
2001-01-17 11:00:32 +00:00
snd_output_printf(out, "Slave: ");
snd_pcm_dump(route->plug.gen.slave, out);
2000-09-24 09:57:26 +00:00
}
/*
* 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;
pcm: Add thread-safety to PCM API Traditionally, many of ALSA library functions are supposed to be thread-unsafe, and applications are required to take care of thread safety by themselves. However, people never be careful enough, and almost all applications fail in this regard. This patch is an attempt to harden the thread safety in exported PCM functions in a simplistic way: just wrap some of exported functions with the pthread mutex of each PCM object. Not all API functions are wrapped by the mutex since it doesn't make sense. Instead, the patchset covers only the functions that may be likely called concurrently. The supposedly thread-safe API functions are marked in the document. For achieving the feature, two new fields are added snd_pcm_t when the option is enabled: thread_safe and lock. The former indicates that the plugin is thread-safe that doesn't need this workaround and the latter is the pthread mutex. Currently only hw plugin have thread_safe=1. So, the most of real-time sensitive apps won't be influenced by this patchset. Although the patch covers most of PCM ops, a few snd_pcm_fast_ops are left without the extra mutex locking: namely, the ones that may have blocking behavior, i.e. resume, drain, readi, writei, readn and writen. These are supposed to handle own locking in the callbacks. Also, if anyone wants to disable this new thread-safe API feature, it can be still turned off via --disable-thread-safety configure option. Signed-off-by: Takashi Iwai <tiwai@suse.de>
2016-06-30 15:32:40 +02:00
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 */
2000-09-24 09:57:26 +00:00
};
2001-03-29 17:50:28 +00:00
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)
2000-09-24 09:57:26 +00:00
{
unsigned int src_channel, dst_channel;
2001-01-26 09:56:30 +00:00
snd_pcm_route_ttable_dst_t *dptr;
2000-09-24 09:57:26 +00:00
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;
2000-09-24 09:57:26 +00:00
dptr = calloc(dused, sizeof(*params->dsts));
if (!dptr)
return -ENOMEM;
params->dsts = dptr;
for (dst_channel = 0; dst_channel < dused; ++dst_channel) {
2001-01-26 09:56:30 +00:00
snd_pcm_route_ttable_entry_t t = 0;
2000-09-24 09:57:26 +00:00
int att = 0;
int nsrcs = 0;
2001-01-26 09:56:30 +00:00
snd_pcm_route_ttable_src_t srcs[sused];
2000-09-24 09:57:26 +00:00
for (src_channel = 0; src_channel < sused; ++src_channel) {
2001-01-26 09:56:30 +00:00
snd_pcm_route_ttable_entry_t v;
2000-09-24 09:57:26 +00:00
v = ttable[src_channel * smul + dst_channel * dmul];
if (v != 0) {
srcs[nsrcs].channel = src_channel;
#if SND_PCM_PLUGIN_ROUTE_FLOAT
2000-09-24 09:57:26 +00:00
/* Also in user space for non attenuated */
srcs[nsrcs].as_int = (v == SND_PCM_PLUGIN_ROUTE_FULL ? SND_PCM_PLUGIN_ROUTE_RESOLUTION : 0);
2000-09-24 09:57:26 +00:00
srcs[nsrcs].as_float = v;
#else
assert(v >= 0 && v <= SND_PCM_PLUGIN_ROUTE_FULL);
2000-09-24 09:57:26 +00:00
srcs[nsrcs].as_int = v;
#endif
if (v != SND_PCM_PLUGIN_ROUTE_FULL)
2000-09-24 09:57:26 +00:00
att = 1;
t += v;
nsrcs++;
}
}
#if 0
assert(t <= SND_PCM_PLUGIN_ROUTE_FULL);
2000-09-24 09:57:26 +00:00
#endif
dptr->att = att;
dptr->nsrcs = nsrcs;
if (nsrcs == 0)
2001-01-26 09:56:30 +00:00
dptr->func = snd_pcm_route_convert1_zero;
else
2001-01-26 09:56:30 +00:00
dptr->func = snd_pcm_route_convert1_many;
2000-09-24 09:57:26 +00:00
if (nsrcs > 0) {
2001-03-29 17:50:28 +00:00
dptr->srcs = calloc((unsigned int) nsrcs, sizeof(*srcs));
2000-09-24 09:57:26 +00:00
if (!dptr->srcs)
return -ENOMEM;
memcpy(dptr->srcs, srcs, sizeof(*srcs) * nsrcs);
} else
dptr->srcs = 0;
dptr++;
}
return 0;
}
2002-01-16 16:42:40 +00:00
/**
* \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,
2001-03-29 17:50:28 +00:00
snd_pcm_format_t sformat, int schannels,
2001-01-26 09:56:30 +00:00
snd_pcm_route_ttable_entry_t *ttable,
2000-09-24 09:57:26 +00:00
unsigned int tt_ssize,
unsigned int tt_cused, unsigned int tt_sused,
snd_pcm_t *slave, int close_slave)
{
snd_pcm_t *pcm;
2000-09-24 09:57:26 +00:00
snd_pcm_route_t *route;
int err;
assert(pcmp && slave && ttable);
if (sformat != SND_PCM_FORMAT_UNKNOWN &&
snd_pcm_format_linear(sformat) != 1)
2000-09-24 09:57:26 +00:00
return -EINVAL;
route = calloc(1, sizeof(snd_pcm_route_t));
if (!route) {
return -ENOMEM;
}
snd_pcm_plugin_init(&route->plug);
2000-11-20 20:10:46 +00:00
route->sformat = sformat;
route->schannels = schannels;
2000-09-24 09:57:26 +00:00
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;
2000-09-24 09:57:26 +00:00
err = snd_pcm_new(&pcm, SND_PCM_TYPE_ROUTE, name, slave->stream, slave->mode);
if (err < 0) {
2000-09-24 09:57:26 +00:00
free(route);
return err;
2000-09-24 09:57:26 +00:00
}
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);
2000-09-24 09:57:26 +00:00
if (err < 0) {
snd_pcm_close(pcm);
2000-09-24 09:57:26 +00:00
return err;
}
*pcmp = pcm;
2000-09-24 09:57:26 +00:00
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)
2001-12-12 18:19:43 +00:00
{
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)
2001-12-12 18:19:43 +00:00
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);
2001-12-12 18:19:43 +00:00
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);
}
2002-01-16 16:42:40 +00:00
/**
* \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)
2000-09-24 09:57:26 +00:00
{
int cused = -1;
int sused = -1;
snd_config_iterator_t i, inext;
2000-09-24 09:57:26 +00:00
unsigned int k;
2001-06-06 11:49:52 +00:00
int err;
2000-09-24 09:57:26 +00:00
for (k = 0; k < tt_csize * tt_ssize; ++k)
ttable[k] = 0.0;
snd_config_for_each(i, inext, tt) {
2001-02-07 11:34:33 +00:00
snd_config_t *in = snd_config_iterator_entry(i);
snd_config_iterator_t j, jnext;
2000-09-24 09:57:26 +00:00
long cchannel;
const char *id;
if (snd_config_get_id(in, &id) < 0)
continue;
err = safe_strtol(id, &cchannel);
2001-06-06 11:49:52 +00:00
if (err < 0 ||
2000-11-30 09:40:50 +00:00
cchannel < 0 || (unsigned int) cchannel > tt_csize) {
SNDERR("Invalid client channel: %s", id);
2000-09-24 09:57:26 +00:00
return -EINVAL;
2000-11-30 09:40:50 +00:00
}
2001-02-07 11:34:33 +00:00
if (snd_config_get_type(in) != SND_CONFIG_TYPE_COMPOUND)
2000-09-24 09:57:26 +00:00
return -EINVAL;
snd_config_for_each(j, jnext, in) {
2001-03-29 17:50:28 +00:00
snd_config_t *jnode = snd_config_iterator_entry(j);
2000-09-24 09:57:26 +00:00
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);
2000-09-24 09:57:26 +00:00
return -EINVAL;
2000-11-30 09:40:50 +00:00
}
2001-03-29 17:50:28 +00:00
err = snd_config_get_real(jnode, &value);
2000-09-24 09:57:26 +00:00
if (err < 0) {
long v;
2001-03-29 17:50:28 +00:00
err = snd_config_get_integer(jnode, &v);
2000-11-30 09:40:50 +00:00
if (err < 0) {
SNDERR("Invalid type for %s", id);
2000-09-24 09:57:26 +00:00
return -EINVAL;
2000-11-30 09:40:50 +00:00
}
2000-09-24 09:57:26 +00:00
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;
}
2000-09-24 09:57:26 +00:00
}
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);
}
2002-01-16 16:42:40 +00:00
/*! \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.
2002-01-16 16:42:40 +00:00
\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
2004-04-26 07:40:12 +00:00
[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)
}
2002-01-16 16:42:40 +00:00
}
}
\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)
2000-09-24 09:57:26 +00:00
{
snd_config_iterator_t i, next;
2000-09-24 09:57:26 +00:00
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;
2001-03-17 16:34:43 +00:00
int schannels = -1;
2000-09-24 09:57:26 +00:00
snd_config_t *tt = NULL;
2001-12-12 18:19:43 +00:00
snd_pcm_route_ttable_entry_t *ttable = NULL;
unsigned int csize, ssize;
2000-09-24 09:57:26 +00:00
unsigned int cused, sused;
snd_config_for_each(i, next, conf) {
2001-02-07 11:34:33 +00:00
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))
2000-09-24 09:57:26 +00:00
continue;
2001-03-17 16:34:43 +00:00
if (strcmp(id, "slave") == 0) {
slave = n;
2000-09-24 09:57:26 +00:00
continue;
}
2001-02-07 11:34:33 +00:00
if (strcmp(id, "ttable") == 0) {
if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
SNDERR("Invalid type for %s", id);
2000-09-24 09:57:26 +00:00
return -EINVAL;
2000-11-30 09:40:50 +00:00
}
2000-09-24 09:57:26 +00:00
tt = n;
continue;
}
SNDERR("Unknown field %s", id);
2000-09-24 09:57:26 +00:00
return -EINVAL;
}
2001-03-17 16:34:43 +00:00
if (!slave) {
SNDERR("slave is not defined");
2000-09-24 09:57:26 +00:00
return -EINVAL;
2000-11-30 09:40:50 +00:00
}
if (!tt) {
SNDERR("ttable is not defined");
2000-11-30 09:40:50 +00:00
return -EINVAL;
}
err = snd_pcm_slave_conf(root, slave, &sconf, 2,
2001-03-17 16:34:43 +00:00
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);
2001-03-17 16:34:43 +00:00
SNDERR("slave format is not linear");
return -EINVAL;
}
2000-09-24 09:57:26 +00:00
err = determine_chmap(tt, &tt_chmap);
2001-12-12 18:19:43 +00:00
if (err < 0) {
free(ttable);
2001-12-12 18:19:43 +00:00
return err;
}
err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
snd_config_delete(sconf);
if (err < 0) {
free(tt_chmap);
2001-12-12 18:19:43 +00:00
free(ttable);
2000-09-24 09:57:26 +00:00
return err;
}
2000-09-24 09:57:26 +00:00
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);
2001-12-12 18:19:43 +00:00
if (err < 0) {
free(chmap);
2001-12-12 18:19:43 +00:00
free(ttable);
snd_pcm_close(spcm);
2000-09-24 09:57:26 +00:00
return err;
2001-12-12 18:19:43 +00:00
}
2000-10-10 09:11:07 +00:00
err = snd_pcm_route_open(pcmp, name, sformat, schannels,
2001-12-12 18:19:43 +00:00
ttable, ssize,
2000-09-24 09:57:26 +00:00
cused, sused,
spcm, 1);
2001-12-12 18:19:43 +00:00
free(ttable);
if (err < 0) {
free(chmap);
2000-09-24 09:57:26 +00:00
snd_pcm_close(spcm);
} else {
((snd_pcm_route_t*) (*pcmp)->private_data)->chmap = chmap;
}
2000-09-24 09:57:26 +00:00
return err;
}
2002-01-16 16:42:40 +00:00
#ifndef DOC_HIDDEN
2001-10-24 14:14:11 +00:00
SND_DLSYM_BUILD_VERSION(_snd_pcm_route_open, SND_PCM_DLSYM_VERSION);
2002-01-16 16:42:40 +00:00
#endif