ALSA: PCM: channel mapping API implementation
This patch implements the basic data types for the standard channel mapping API handling. - The definitions of the channel positions and the new TLV types are added in sound/asound.h and sound/tlv.h, so that they can be referred from user-space. - Introduced a new helper function snd_pcm_add_chmap_ctls() to create control elements representing the channel maps for each PCM (sub)stream. - Some standard pre-defined channel maps are provided for convenience. Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
parent
a8d372f171
commit
2d3391ec0e
|
@ -472,6 +472,36 @@ enum {
|
||||||
SNDRV_PCM_TSTAMP_TYPE_LAST = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC,
|
SNDRV_PCM_TSTAMP_TYPE_LAST = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* channel positions */
|
||||||
|
enum {
|
||||||
|
SNDRV_CHMAP_UNKNOWN = 0,
|
||||||
|
SNDRV_CHMAP_FL, /* front left */
|
||||||
|
SNDRV_CHMAP_FC, /* front center */
|
||||||
|
SNDRV_CHMAP_FR, /* front right */
|
||||||
|
SNDRV_CHMAP_FLC, /* front left center */
|
||||||
|
SNDRV_CHMAP_FRC, /* front right center */
|
||||||
|
SNDRV_CHMAP_RL, /* rear left */
|
||||||
|
SNDRV_CHMAP_RC, /* rear center */
|
||||||
|
SNDRV_CHMAP_RR, /* rear right */
|
||||||
|
SNDRV_CHMAP_RLC, /* rear left center */
|
||||||
|
SNDRV_CHMAP_RRC, /* rear right center */
|
||||||
|
SNDRV_CHMAP_SL, /* side left */
|
||||||
|
SNDRV_CHMAP_SR, /* side right */
|
||||||
|
SNDRV_CHMAP_LFE, /* LFE */
|
||||||
|
SNDRV_CHMAP_FLW, /* front left wide */
|
||||||
|
SNDRV_CHMAP_FRW, /* front right wide */
|
||||||
|
SNDRV_CHMAP_FLH, /* front left high */
|
||||||
|
SNDRV_CHMAP_FCH, /* front center high */
|
||||||
|
SNDRV_CHMAP_FRH, /* front right high */
|
||||||
|
SNDRV_CHMAP_TC, /* top center */
|
||||||
|
SNDRV_CHMAP_NA, /* N/A, silent */
|
||||||
|
SNDRV_CHMAP_LAST = SNDRV_CHMAP_NA,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SNDRV_CHMAP_POSITION_MASK 0xffff
|
||||||
|
#define SNDRV_CHMAP_PHASE_INVERSE (0x01 << 16)
|
||||||
|
#define SNDRV_CHMAP_DRIVER_SPEC (0x02 << 16)
|
||||||
|
|
||||||
#define SNDRV_PCM_IOCTL_PVERSION _IOR('A', 0x00, int)
|
#define SNDRV_PCM_IOCTL_PVERSION _IOR('A', 0x00, int)
|
||||||
#define SNDRV_PCM_IOCTL_INFO _IOR('A', 0x01, struct snd_pcm_info)
|
#define SNDRV_PCM_IOCTL_INFO _IOR('A', 0x01, struct snd_pcm_info)
|
||||||
#define SNDRV_PCM_IOCTL_TSTAMP _IOW('A', 0x02, int)
|
#define SNDRV_PCM_IOCTL_TSTAMP _IOW('A', 0x02, int)
|
||||||
|
|
|
@ -437,6 +437,7 @@ struct snd_pcm_str {
|
||||||
struct snd_info_entry *proc_xrun_debug_entry;
|
struct snd_info_entry *proc_xrun_debug_entry;
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
struct snd_kcontrol *chmap_kctl; /* channel-mapping controls */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct snd_pcm {
|
struct snd_pcm {
|
||||||
|
@ -1086,4 +1087,51 @@ static inline const char *snd_pcm_stream_str(struct snd_pcm_substream *substream
|
||||||
return "Capture";
|
return "Capture";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PCM channel-mapping control API
|
||||||
|
*/
|
||||||
|
/* array element of channel maps */
|
||||||
|
struct snd_pcm_chmap_elem {
|
||||||
|
unsigned char channels;
|
||||||
|
unsigned char map[15];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* channel map information; retrieved via snd_kcontrol_chip() */
|
||||||
|
struct snd_pcm_chmap {
|
||||||
|
struct snd_pcm *pcm; /* assigned PCM instance */
|
||||||
|
int stream; /* PLAYBACK or CAPTURE */
|
||||||
|
struct snd_kcontrol *kctl;
|
||||||
|
const struct snd_pcm_chmap_elem *chmap;
|
||||||
|
unsigned int max_channels;
|
||||||
|
unsigned int channel_mask; /* optional: active channels bitmask */
|
||||||
|
void *private_data; /* optional: private data pointer */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* get the PCM substream assigned to the given chmap info */
|
||||||
|
static inline struct snd_pcm_substream *
|
||||||
|
snd_pcm_chmap_substream(struct snd_pcm_chmap *info, unsigned int idx)
|
||||||
|
{
|
||||||
|
struct snd_pcm_substream *s;
|
||||||
|
for (s = info->pcm->streams[info->stream].substream; s; s = s->next)
|
||||||
|
if (s->number == idx)
|
||||||
|
return s;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ALSA-standard channel maps (RL/RR prior to C/LFE) */
|
||||||
|
extern const struct snd_pcm_chmap_elem snd_pcm_std_chmaps[];
|
||||||
|
/* Other world's standard channel maps (C/LFE prior to RL/RR) */
|
||||||
|
extern const struct snd_pcm_chmap_elem snd_pcm_alt_chmaps[];
|
||||||
|
|
||||||
|
/* bit masks to be passed to snd_pcm_chmap.channel_mask field */
|
||||||
|
#define SND_PCM_CHMAP_MASK_24 ((1U << 2) | (1U << 4))
|
||||||
|
#define SND_PCM_CHMAP_MASK_246 (SND_PCM_CHMAP_MASK_24 | (1U << 6))
|
||||||
|
#define SND_PCM_CHMAP_MASK_2468 (SND_PCM_CHMAP_MASK_246 | (1U << 8))
|
||||||
|
|
||||||
|
int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream,
|
||||||
|
const struct snd_pcm_chmap_elem *chmap,
|
||||||
|
int max_channels,
|
||||||
|
unsigned long private_value,
|
||||||
|
struct snd_pcm_chmap **info_ret);
|
||||||
|
|
||||||
#endif /* __SOUND_PCM_H */
|
#endif /* __SOUND_PCM_H */
|
||||||
|
|
|
@ -86,4 +86,12 @@
|
||||||
|
|
||||||
#define TLV_DB_GAIN_MUTE -9999999
|
#define TLV_DB_GAIN_MUTE -9999999
|
||||||
|
|
||||||
|
/*
|
||||||
|
* channel-mapping TLV items
|
||||||
|
* TLV length must match with num_channels
|
||||||
|
*/
|
||||||
|
#define SNDRV_CTL_TLVT_CHMAP_FIXED 0x101 /* fixed channel position */
|
||||||
|
#define SNDRV_CTL_TLVT_CHMAP_VAR 0x102 /* channels freely swappable */
|
||||||
|
#define SNDRV_CTL_TLVT_CHMAP_PAIRED 0x103 /* pair-wise swappable */
|
||||||
|
|
||||||
#endif /* __SOUND_TLV_H */
|
#endif /* __SOUND_TLV_H */
|
||||||
|
|
|
@ -1105,6 +1105,10 @@ static int snd_pcm_dev_disconnect(struct snd_device *device)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
snd_unregister_device(devtype, pcm->card, pcm->device);
|
snd_unregister_device(devtype, pcm->card, pcm->device);
|
||||||
|
if (pcm->streams[cidx].chmap_kctl) {
|
||||||
|
snd_ctl_remove(pcm->card, pcm->streams[cidx].chmap_kctl);
|
||||||
|
pcm->streams[cidx].chmap_kctl = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
unlock:
|
unlock:
|
||||||
mutex_unlock(®ister_mutex);
|
mutex_unlock(®ister_mutex);
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include <linux/export.h>
|
#include <linux/export.h>
|
||||||
#include <sound/core.h>
|
#include <sound/core.h>
|
||||||
#include <sound/control.h>
|
#include <sound/control.h>
|
||||||
|
#include <sound/tlv.h>
|
||||||
#include <sound/info.h>
|
#include <sound/info.h>
|
||||||
#include <sound/pcm.h>
|
#include <sound/pcm.h>
|
||||||
#include <sound/pcm_params.h>
|
#include <sound/pcm_params.h>
|
||||||
|
@ -2302,3 +2303,217 @@ snd_pcm_sframes_t snd_pcm_lib_readv(struct snd_pcm_substream *substream,
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPORT_SYMBOL(snd_pcm_lib_readv);
|
EXPORT_SYMBOL(snd_pcm_lib_readv);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* standard channel mapping helpers
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* default channel maps for multi-channel playbacks, up to 8 channels */
|
||||||
|
const struct snd_pcm_chmap_elem snd_pcm_std_chmaps[] = {
|
||||||
|
{ .channels = 1,
|
||||||
|
.map = { SNDRV_CHMAP_FC } },
|
||||||
|
{ .channels = 2,
|
||||||
|
.map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
|
||||||
|
{ .channels = 4,
|
||||||
|
.map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
|
||||||
|
SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
|
||||||
|
{ .channels = 6,
|
||||||
|
.map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
|
||||||
|
SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
|
||||||
|
SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } },
|
||||||
|
{ .channels = 8,
|
||||||
|
.map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
|
||||||
|
SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
|
||||||
|
SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
|
||||||
|
SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
EXPORT_SYMBOL_GPL(snd_pcm_std_chmaps);
|
||||||
|
|
||||||
|
/* alternative channel maps with CLFE <-> surround swapped for 6/8 channels */
|
||||||
|
const struct snd_pcm_chmap_elem snd_pcm_alt_chmaps[] = {
|
||||||
|
{ .channels = 1,
|
||||||
|
.map = { SNDRV_CHMAP_FC } },
|
||||||
|
{ .channels = 2,
|
||||||
|
.map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
|
||||||
|
{ .channels = 4,
|
||||||
|
.map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
|
||||||
|
SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
|
||||||
|
{ .channels = 6,
|
||||||
|
.map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
|
||||||
|
SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
|
||||||
|
SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
|
||||||
|
{ .channels = 8,
|
||||||
|
.map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
|
||||||
|
SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
|
||||||
|
SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
|
||||||
|
SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
EXPORT_SYMBOL_GPL(snd_pcm_alt_chmaps);
|
||||||
|
|
||||||
|
static bool valid_chmap_channels(const struct snd_pcm_chmap *info, int ch)
|
||||||
|
{
|
||||||
|
if (ch > info->max_channels)
|
||||||
|
return false;
|
||||||
|
return !info->channel_mask || (info->channel_mask & (1U << ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pcm_chmap_ctl_info(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_info *uinfo)
|
||||||
|
{
|
||||||
|
struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
|
||||||
|
|
||||||
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||||
|
uinfo->count = 0;
|
||||||
|
uinfo->count = info->max_channels;
|
||||||
|
uinfo->value.integer.min = 0;
|
||||||
|
uinfo->value.integer.max = SNDRV_CHMAP_LAST;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* get callback for channel map ctl element
|
||||||
|
* stores the channel position firstly matching with the current channels
|
||||||
|
*/
|
||||||
|
static int pcm_chmap_ctl_get(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_value *ucontrol)
|
||||||
|
{
|
||||||
|
struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
|
||||||
|
unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
|
||||||
|
struct snd_pcm_substream *substream;
|
||||||
|
const struct snd_pcm_chmap_elem *map;
|
||||||
|
|
||||||
|
if (snd_BUG_ON(!info->chmap))
|
||||||
|
return -EINVAL;
|
||||||
|
substream = snd_pcm_chmap_substream(info, idx);
|
||||||
|
if (!substream)
|
||||||
|
return -ENODEV;
|
||||||
|
memset(ucontrol->value.integer.value, 0,
|
||||||
|
sizeof(ucontrol->value.integer.value));
|
||||||
|
if (!substream->runtime)
|
||||||
|
return 0; /* no channels set */
|
||||||
|
for (map = info->chmap; map->channels; map++) {
|
||||||
|
int i;
|
||||||
|
if (map->channels == substream->runtime->channels &&
|
||||||
|
valid_chmap_channels(info, map->channels)) {
|
||||||
|
for (i = 0; i < map->channels; i++)
|
||||||
|
ucontrol->value.integer.value[i] = map->map[i];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tlv callback for channel map ctl element
|
||||||
|
* expands the pre-defined channel maps in a form of TLV
|
||||||
|
*/
|
||||||
|
static int pcm_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
|
||||||
|
unsigned int size, unsigned int __user *tlv)
|
||||||
|
{
|
||||||
|
struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
|
||||||
|
const struct snd_pcm_chmap_elem *map;
|
||||||
|
unsigned int __user *dst;
|
||||||
|
int c, count = 0;
|
||||||
|
|
||||||
|
if (snd_BUG_ON(!info->chmap))
|
||||||
|
return -EINVAL;
|
||||||
|
if (size < 8)
|
||||||
|
return -ENOMEM;
|
||||||
|
if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv))
|
||||||
|
return -EFAULT;
|
||||||
|
size -= 8;
|
||||||
|
dst = tlv + 2;
|
||||||
|
for (map = info->chmap; map->channels; map++) {
|
||||||
|
int chs_bytes = map->channels * 4;
|
||||||
|
if (!valid_chmap_channels(info, map->channels))
|
||||||
|
continue;
|
||||||
|
if (size < 8)
|
||||||
|
return -ENOMEM;
|
||||||
|
if (put_user(SNDRV_CTL_TLVT_CHMAP_FIXED, dst) ||
|
||||||
|
put_user(chs_bytes, dst + 1))
|
||||||
|
return -EFAULT;
|
||||||
|
dst += 2;
|
||||||
|
size -= 8;
|
||||||
|
count += 8;
|
||||||
|
if (size < chs_bytes)
|
||||||
|
return -ENOMEM;
|
||||||
|
size -= chs_bytes;
|
||||||
|
count += chs_bytes;
|
||||||
|
for (c = 0; c < map->channels; c++) {
|
||||||
|
if (put_user(map->map[c], dst))
|
||||||
|
return -EFAULT;
|
||||||
|
dst++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (put_user(count, tlv + 1))
|
||||||
|
return -EFAULT;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pcm_chmap_ctl_private_free(struct snd_kcontrol *kcontrol)
|
||||||
|
{
|
||||||
|
struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
|
||||||
|
info->pcm->streams[info->stream].chmap_kctl = NULL;
|
||||||
|
kfree(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* snd_pcm_add_chmap_ctls - create channel-mapping control elements
|
||||||
|
* @pcm: the assigned PCM instance
|
||||||
|
* @stream: stream direction
|
||||||
|
* @chmap: channel map elements (for query)
|
||||||
|
* @max_channels: the max number of channels for the stream
|
||||||
|
* @private_value: the value passed to each kcontrol's private_value field
|
||||||
|
* @info_ret: store struct snd_pcm_chmap instance if non-NULL
|
||||||
|
*
|
||||||
|
* Create channel-mapping control elements assigned to the given PCM stream(s).
|
||||||
|
* Returns zero if succeed, or a negative error value.
|
||||||
|
*/
|
||||||
|
int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream,
|
||||||
|
const struct snd_pcm_chmap_elem *chmap,
|
||||||
|
int max_channels,
|
||||||
|
unsigned long private_value,
|
||||||
|
struct snd_pcm_chmap **info_ret)
|
||||||
|
{
|
||||||
|
struct snd_pcm_chmap *info;
|
||||||
|
struct snd_kcontrol_new knew = {
|
||||||
|
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
||||||
|
.access = SNDRV_CTL_ELEM_ACCESS_READ |
|
||||||
|
SNDRV_CTL_ELEM_ACCESS_VOLATILE | /* no notification */
|
||||||
|
SNDRV_CTL_ELEM_ACCESS_TLV_READ |
|
||||||
|
SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK,
|
||||||
|
.info = pcm_chmap_ctl_info,
|
||||||
|
.get = pcm_chmap_ctl_get,
|
||||||
|
.tlv.c = pcm_chmap_ctl_tlv,
|
||||||
|
};
|
||||||
|
int err;
|
||||||
|
|
||||||
|
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
||||||
|
if (!info)
|
||||||
|
return -ENOMEM;
|
||||||
|
info->pcm = pcm;
|
||||||
|
info->stream = stream;
|
||||||
|
info->chmap = chmap;
|
||||||
|
info->max_channels = max_channels;
|
||||||
|
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||||
|
knew.name = "Playback Channel Map";
|
||||||
|
else
|
||||||
|
knew.name = "Capture Channel Map";
|
||||||
|
knew.device = pcm->device;
|
||||||
|
knew.count = pcm->streams[stream].substream_count;
|
||||||
|
knew.private_value = private_value;
|
||||||
|
info->kctl = snd_ctl_new1(&knew, info);
|
||||||
|
if (!info->kctl) {
|
||||||
|
kfree(info);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
info->kctl->private_free = pcm_chmap_ctl_private_free;
|
||||||
|
err = snd_ctl_add(pcm->card, info->kctl);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
pcm->streams[stream].chmap_kctl = info->kctl;
|
||||||
|
if (info_ret)
|
||||||
|
*info_ret = info;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(snd_pcm_add_chmap_ctls);
|
||||||
|
|
Loading…
Reference in a new issue