mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	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
					
				
					 5 changed files with 305 additions and 0 deletions
				
			
		| 
						 | 
				
			
			@ -472,6 +472,36 @@ enum {
 | 
			
		|||
	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_INFO		_IOR('A', 0x01, struct snd_pcm_info)
 | 
			
		||||
#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;
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
	struct snd_kcontrol *chmap_kctl; /* channel-mapping controls */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct snd_pcm {
 | 
			
		||||
| 
						 | 
				
			
			@ -1086,4 +1087,51 @@ static inline const char *snd_pcm_stream_str(struct snd_pcm_substream *substream
 | 
			
		|||
		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 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -86,4 +86,12 @@
 | 
			
		|||
 | 
			
		||||
#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 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1105,6 +1105,10 @@ static int snd_pcm_dev_disconnect(struct snd_device *device)
 | 
			
		|||
			break;
 | 
			
		||||
		}
 | 
			
		||||
		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:
 | 
			
		||||
	mutex_unlock(®ister_mutex);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,6 +26,7 @@
 | 
			
		|||
#include <linux/export.h>
 | 
			
		||||
#include <sound/core.h>
 | 
			
		||||
#include <sound/control.h>
 | 
			
		||||
#include <sound/tlv.h>
 | 
			
		||||
#include <sound/info.h>
 | 
			
		||||
#include <sound/pcm.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);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * 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