diff options
author | Takashi Iwai <tiwai@suse.de> | 2012-07-27 12:27:00 -0400 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2012-09-06 12:01:16 -0400 |
commit | 2d3391ec0ecca37efb6bc995906292f47522b471 (patch) | |
tree | 129e8bce7b18bb9de48d6bc0e63806d22b40ad99 | |
parent | a8d372f171db9b90a64778fbcd9237c9bc256e06 (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.h | 30 | ||||
-rw-r--r-- | include/sound/pcm.h | 48 | ||||
-rw-r--r-- | include/sound/tlv.h | 8 | ||||
-rw-r--r-- | sound/core/pcm.c | 4 | ||||
-rw-r--r-- | sound/core/pcm_lib.c | 215 |
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 */ | ||
476 | enum { | ||
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 | ||
442 | struct snd_pcm { | 443 | struct 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 */ | ||
1094 | struct snd_pcm_chmap_elem { | ||
1095 | unsigned char channels; | ||
1096 | unsigned char map[15]; | ||
1097 | }; | ||
1098 | |||
1099 | /* channel map information; retrieved via snd_kcontrol_chip() */ | ||
1100 | struct 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 */ | ||
1111 | static inline struct snd_pcm_substream * | ||
1112 | snd_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) */ | ||
1122 | extern const struct snd_pcm_chmap_elem snd_pcm_std_chmaps[]; | ||
1123 | /* Other world's standard channel maps (C/LFE prior to RL/RR) */ | ||
1124 | extern 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 | |||
1131 | int 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(®ister_mutex); | 1114 | mutex_unlock(®ister_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 | ||
2304 | EXPORT_SYMBOL(snd_pcm_lib_readv); | 2305 | EXPORT_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 */ | ||
2312 | const 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 | }; | ||
2331 | EXPORT_SYMBOL_GPL(snd_pcm_std_chmaps); | ||
2332 | |||
2333 | /* alternative channel maps with CLFE <-> surround swapped for 6/8 channels */ | ||
2334 | const 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 | }; | ||
2353 | EXPORT_SYMBOL_GPL(snd_pcm_alt_chmaps); | ||
2354 | |||
2355 | static 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 | |||
2362 | static 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 | */ | ||
2378 | static 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 | */ | ||
2410 | static 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 | |||
2453 | static 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 | */ | ||
2472 | int 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 | } | ||
2519 | EXPORT_SYMBOL_GPL(snd_pcm_add_chmap_ctls); | ||