aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2012-07-27 12:27:00 -0400
committerTakashi Iwai <tiwai@suse.de>2012-09-06 12:01:16 -0400
commit2d3391ec0ecca37efb6bc995906292f47522b471 (patch)
tree129e8bce7b18bb9de48d6bc0e63806d22b40ad99
parenta8d372f171db9b90a64778fbcd9237c9bc256e06 (diff)
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>
-rw-r--r--include/sound/asound.h30
-rw-r--r--include/sound/pcm.h48
-rw-r--r--include/sound/tlv.h8
-rw-r--r--sound/core/pcm.c4
-rw-r--r--sound/core/pcm_lib.c215
5 files changed, 305 insertions, 0 deletions
diff --git a/include/sound/asound.h b/include/sound/asound.h
index 0876a1e76aef..376e75632e07 100644
--- a/include/sound/asound.h
+++ b/include/sound/asound.h
@@ -472,6 +472,36 @@ enum {
472 SNDRV_PCM_TSTAMP_TYPE_LAST = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC, 472 SNDRV_PCM_TSTAMP_TYPE_LAST = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC,
473}; 473};
474 474
475/* channel positions */
476enum {
477 SNDRV_CHMAP_UNKNOWN = 0,
478 SNDRV_CHMAP_FL, /* front left */
479 SNDRV_CHMAP_FC, /* front center */
480 SNDRV_CHMAP_FR, /* front right */
481 SNDRV_CHMAP_FLC, /* front left center */
482 SNDRV_CHMAP_FRC, /* front right center */
483 SNDRV_CHMAP_RL, /* rear left */
484 SNDRV_CHMAP_RC, /* rear center */
485 SNDRV_CHMAP_RR, /* rear right */
486 SNDRV_CHMAP_RLC, /* rear left center */
487 SNDRV_CHMAP_RRC, /* rear right center */
488 SNDRV_CHMAP_SL, /* side left */
489 SNDRV_CHMAP_SR, /* side right */
490 SNDRV_CHMAP_LFE, /* LFE */
491 SNDRV_CHMAP_FLW, /* front left wide */
492 SNDRV_CHMAP_FRW, /* front right wide */
493 SNDRV_CHMAP_FLH, /* front left high */
494 SNDRV_CHMAP_FCH, /* front center high */
495 SNDRV_CHMAP_FRH, /* front right high */
496 SNDRV_CHMAP_TC, /* top center */
497 SNDRV_CHMAP_NA, /* N/A, silent */
498 SNDRV_CHMAP_LAST = SNDRV_CHMAP_NA,
499};
500
501#define SNDRV_CHMAP_POSITION_MASK 0xffff
502#define SNDRV_CHMAP_PHASE_INVERSE (0x01 << 16)
503#define SNDRV_CHMAP_DRIVER_SPEC (0x02 << 16)
504
475#define SNDRV_PCM_IOCTL_PVERSION _IOR('A', 0x00, int) 505#define SNDRV_PCM_IOCTL_PVERSION _IOR('A', 0x00, int)
476#define SNDRV_PCM_IOCTL_INFO _IOR('A', 0x01, struct snd_pcm_info) 506#define SNDRV_PCM_IOCTL_INFO _IOR('A', 0x01, struct snd_pcm_info)
477#define SNDRV_PCM_IOCTL_TSTAMP _IOW('A', 0x02, int) 507#define SNDRV_PCM_IOCTL_TSTAMP _IOW('A', 0x02, int)
diff --git a/include/sound/pcm.h b/include/sound/pcm.h
index cdca2ab1e711..669c85a7fb03 100644
--- a/include/sound/pcm.h
+++ b/include/sound/pcm.h
@@ -437,6 +437,7 @@ struct snd_pcm_str {
437 struct snd_info_entry *proc_xrun_debug_entry; 437 struct snd_info_entry *proc_xrun_debug_entry;
438#endif 438#endif
439#endif 439#endif
440 struct snd_kcontrol *chmap_kctl; /* channel-mapping controls */
440}; 441};
441 442
442struct snd_pcm { 443struct snd_pcm {
@@ -1086,4 +1087,51 @@ static inline const char *snd_pcm_stream_str(struct snd_pcm_substream *substream
1086 return "Capture"; 1087 return "Capture";
1087} 1088}
1088 1089
1090/*
1091 * PCM channel-mapping control API
1092 */
1093/* array element of channel maps */
1094struct snd_pcm_chmap_elem {
1095 unsigned char channels;
1096 unsigned char map[15];
1097};
1098
1099/* channel map information; retrieved via snd_kcontrol_chip() */
1100struct snd_pcm_chmap {
1101 struct snd_pcm *pcm; /* assigned PCM instance */
1102 int stream; /* PLAYBACK or CAPTURE */
1103 struct snd_kcontrol *kctl;
1104 const struct snd_pcm_chmap_elem *chmap;
1105 unsigned int max_channels;
1106 unsigned int channel_mask; /* optional: active channels bitmask */
1107 void *private_data; /* optional: private data pointer */
1108};
1109
1110/* get the PCM substream assigned to the given chmap info */
1111static inline struct snd_pcm_substream *
1112snd_pcm_chmap_substream(struct snd_pcm_chmap *info, unsigned int idx)
1113{
1114 struct snd_pcm_substream *s;
1115 for (s = info->pcm->streams[info->stream].substream; s; s = s->next)
1116 if (s->number == idx)
1117 return s;
1118 return NULL;
1119}
1120
1121/* ALSA-standard channel maps (RL/RR prior to C/LFE) */
1122extern const struct snd_pcm_chmap_elem snd_pcm_std_chmaps[];
1123/* Other world's standard channel maps (C/LFE prior to RL/RR) */
1124extern const struct snd_pcm_chmap_elem snd_pcm_alt_chmaps[];
1125
1126/* bit masks to be passed to snd_pcm_chmap.channel_mask field */
1127#define SND_PCM_CHMAP_MASK_24 ((1U << 2) | (1U << 4))
1128#define SND_PCM_CHMAP_MASK_246 (SND_PCM_CHMAP_MASK_24 | (1U << 6))
1129#define SND_PCM_CHMAP_MASK_2468 (SND_PCM_CHMAP_MASK_246 | (1U << 8))
1130
1131int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream,
1132 const struct snd_pcm_chmap_elem *chmap,
1133 int max_channels,
1134 unsigned long private_value,
1135 struct snd_pcm_chmap **info_ret);
1136
1089#endif /* __SOUND_PCM_H */ 1137#endif /* __SOUND_PCM_H */
diff --git a/include/sound/tlv.h b/include/sound/tlv.h
index a64d8fe3f855..28c65e1ada21 100644
--- a/include/sound/tlv.h
+++ b/include/sound/tlv.h
@@ -86,4 +86,12 @@
86 86
87#define TLV_DB_GAIN_MUTE -9999999 87#define TLV_DB_GAIN_MUTE -9999999
88 88
89/*
90 * channel-mapping TLV items
91 * TLV length must match with num_channels
92 */
93#define SNDRV_CTL_TLVT_CHMAP_FIXED 0x101 /* fixed channel position */
94#define SNDRV_CTL_TLVT_CHMAP_VAR 0x102 /* channels freely swappable */
95#define SNDRV_CTL_TLVT_CHMAP_PAIRED 0x103 /* pair-wise swappable */
96
89#endif /* __SOUND_TLV_H */ 97#endif /* __SOUND_TLV_H */
diff --git a/sound/core/pcm.c b/sound/core/pcm.c
index 1a3070b4e5b5..f2991940b271 100644
--- a/sound/core/pcm.c
+++ b/sound/core/pcm.c
@@ -1105,6 +1105,10 @@ static int snd_pcm_dev_disconnect(struct snd_device *device)
1105 break; 1105 break;
1106 } 1106 }
1107 snd_unregister_device(devtype, pcm->card, pcm->device); 1107 snd_unregister_device(devtype, pcm->card, pcm->device);
1108 if (pcm->streams[cidx].chmap_kctl) {
1109 snd_ctl_remove(pcm->card, pcm->streams[cidx].chmap_kctl);
1110 pcm->streams[cidx].chmap_kctl = NULL;
1111 }
1108 } 1112 }
1109 unlock: 1113 unlock:
1110 mutex_unlock(&register_mutex); 1114 mutex_unlock(&register_mutex);
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
index 7ae671923393..565102705eda 100644
--- a/sound/core/pcm_lib.c
+++ b/sound/core/pcm_lib.c
@@ -26,6 +26,7 @@
26#include <linux/export.h> 26#include <linux/export.h>
27#include <sound/core.h> 27#include <sound/core.h>
28#include <sound/control.h> 28#include <sound/control.h>
29#include <sound/tlv.h>
29#include <sound/info.h> 30#include <sound/info.h>
30#include <sound/pcm.h> 31#include <sound/pcm.h>
31#include <sound/pcm_params.h> 32#include <sound/pcm_params.h>
@@ -2302,3 +2303,217 @@ snd_pcm_sframes_t snd_pcm_lib_readv(struct snd_pcm_substream *substream,
2302} 2303}
2303 2304
2304EXPORT_SYMBOL(snd_pcm_lib_readv); 2305EXPORT_SYMBOL(snd_pcm_lib_readv);
2306
2307/*
2308 * standard channel mapping helpers
2309 */
2310
2311/* default channel maps for multi-channel playbacks, up to 8 channels */
2312const struct snd_pcm_chmap_elem snd_pcm_std_chmaps[] = {
2313 { .channels = 1,
2314 .map = { SNDRV_CHMAP_FC } },
2315 { .channels = 2,
2316 .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
2317 { .channels = 4,
2318 .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
2319 SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
2320 { .channels = 6,
2321 .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
2322 SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
2323 SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } },
2324 { .channels = 8,
2325 .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
2326 SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
2327 SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
2328 SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } },
2329 { }
2330};
2331EXPORT_SYMBOL_GPL(snd_pcm_std_chmaps);
2332
2333/* alternative channel maps with CLFE <-> surround swapped for 6/8 channels */
2334const struct snd_pcm_chmap_elem snd_pcm_alt_chmaps[] = {
2335 { .channels = 1,
2336 .map = { SNDRV_CHMAP_FC } },
2337 { .channels = 2,
2338 .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
2339 { .channels = 4,
2340 .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
2341 SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
2342 { .channels = 6,
2343 .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
2344 SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
2345 SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
2346 { .channels = 8,
2347 .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
2348 SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
2349 SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
2350 SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } },
2351 { }
2352};
2353EXPORT_SYMBOL_GPL(snd_pcm_alt_chmaps);
2354
2355static bool valid_chmap_channels(const struct snd_pcm_chmap *info, int ch)
2356{
2357 if (ch > info->max_channels)
2358 return false;
2359 return !info->channel_mask || (info->channel_mask & (1U << ch));
2360}
2361
2362static int pcm_chmap_ctl_info(struct snd_kcontrol *kcontrol,
2363 struct snd_ctl_elem_info *uinfo)
2364{
2365 struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
2366
2367 uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
2368 uinfo->count = 0;
2369 uinfo->count = info->max_channels;
2370 uinfo->value.integer.min = 0;
2371 uinfo->value.integer.max = SNDRV_CHMAP_LAST;
2372 return 0;
2373}
2374
2375/* get callback for channel map ctl element
2376 * stores the channel position firstly matching with the current channels
2377 */
2378static int pcm_chmap_ctl_get(struct snd_kcontrol *kcontrol,
2379 struct snd_ctl_elem_value *ucontrol)
2380{
2381 struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
2382 unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
2383 struct snd_pcm_substream *substream;
2384 const struct snd_pcm_chmap_elem *map;
2385
2386 if (snd_BUG_ON(!info->chmap))
2387 return -EINVAL;
2388 substream = snd_pcm_chmap_substream(info, idx);
2389 if (!substream)
2390 return -ENODEV;
2391 memset(ucontrol->value.integer.value, 0,
2392 sizeof(ucontrol->value.integer.value));
2393 if (!substream->runtime)
2394 return 0; /* no channels set */
2395 for (map = info->chmap; map->channels; map++) {
2396 int i;
2397 if (map->channels == substream->runtime->channels &&
2398 valid_chmap_channels(info, map->channels)) {
2399 for (i = 0; i < map->channels; i++)
2400 ucontrol->value.integer.value[i] = map->map[i];
2401 return 0;
2402 }
2403 }
2404 return -EINVAL;
2405}
2406
2407/* tlv callback for channel map ctl element
2408 * expands the pre-defined channel maps in a form of TLV
2409 */
2410static int pcm_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
2411 unsigned int size, unsigned int __user *tlv)
2412{
2413 struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
2414 const struct snd_pcm_chmap_elem *map;
2415 unsigned int __user *dst;
2416 int c, count = 0;
2417
2418 if (snd_BUG_ON(!info->chmap))
2419 return -EINVAL;
2420 if (size < 8)
2421 return -ENOMEM;
2422 if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv))
2423 return -EFAULT;
2424 size -= 8;
2425 dst = tlv + 2;
2426 for (map = info->chmap; map->channels; map++) {
2427 int chs_bytes = map->channels * 4;
2428 if (!valid_chmap_channels(info, map->channels))
2429 continue;
2430 if (size < 8)
2431 return -ENOMEM;
2432 if (put_user(SNDRV_CTL_TLVT_CHMAP_FIXED, dst) ||
2433 put_user(chs_bytes, dst + 1))
2434 return -EFAULT;
2435 dst += 2;
2436 size -= 8;
2437 count += 8;
2438 if (size < chs_bytes)
2439 return -ENOMEM;
2440 size -= chs_bytes;
2441 count += chs_bytes;
2442 for (c = 0; c < map->channels; c++) {
2443 if (put_user(map->map[c], dst))
2444 return -EFAULT;
2445 dst++;
2446 }
2447 }
2448 if (put_user(count, tlv + 1))
2449 return -EFAULT;
2450 return 0;
2451}
2452
2453static void pcm_chmap_ctl_private_free(struct snd_kcontrol *kcontrol)
2454{
2455 struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
2456 info->pcm->streams[info->stream].chmap_kctl = NULL;
2457 kfree(info);
2458}
2459
2460/**
2461 * snd_pcm_add_chmap_ctls - create channel-mapping control elements
2462 * @pcm: the assigned PCM instance
2463 * @stream: stream direction
2464 * @chmap: channel map elements (for query)
2465 * @max_channels: the max number of channels for the stream
2466 * @private_value: the value passed to each kcontrol's private_value field
2467 * @info_ret: store struct snd_pcm_chmap instance if non-NULL
2468 *
2469 * Create channel-mapping control elements assigned to the given PCM stream(s).
2470 * Returns zero if succeed, or a negative error value.
2471 */
2472int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream,
2473 const struct snd_pcm_chmap_elem *chmap,
2474 int max_channels,
2475 unsigned long private_value,
2476 struct snd_pcm_chmap **info_ret)
2477{
2478 struct snd_pcm_chmap *info;
2479 struct snd_kcontrol_new knew = {
2480 .iface = SNDRV_CTL_ELEM_IFACE_PCM,
2481 .access = SNDRV_CTL_ELEM_ACCESS_READ |
2482 SNDRV_CTL_ELEM_ACCESS_VOLATILE | /* no notification */
2483 SNDRV_CTL_ELEM_ACCESS_TLV_READ |
2484 SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK,
2485 .info = pcm_chmap_ctl_info,
2486 .get = pcm_chmap_ctl_get,
2487 .tlv.c = pcm_chmap_ctl_tlv,
2488 };
2489 int err;
2490
2491 info = kzalloc(sizeof(*info), GFP_KERNEL);
2492 if (!info)
2493 return -ENOMEM;
2494 info->pcm = pcm;
2495 info->stream = stream;
2496 info->chmap = chmap;
2497 info->max_channels = max_channels;
2498 if (stream == SNDRV_PCM_STREAM_PLAYBACK)
2499 knew.name = "Playback Channel Map";
2500 else
2501 knew.name = "Capture Channel Map";
2502 knew.device = pcm->device;
2503 knew.count = pcm->streams[stream].substream_count;
2504 knew.private_value = private_value;
2505 info->kctl = snd_ctl_new1(&knew, info);
2506 if (!info->kctl) {
2507 kfree(info);
2508 return -ENOMEM;
2509 }
2510 info->kctl->private_free = pcm_chmap_ctl_private_free;
2511 err = snd_ctl_add(pcm->card, info->kctl);
2512 if (err < 0)
2513 return err;
2514 pcm->streams[stream].chmap_kctl = info->kctl;
2515 if (info_ret)
2516 *info_ret = info;
2517 return 0;
2518}
2519EXPORT_SYMBOL_GPL(snd_pcm_add_chmap_ctls);