Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@ChunMinChang
Last active September 26, 2021 12:03
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ChunMinChang/03741999c8f5e27dd655368b999870c0 to your computer and use it in GitHub Desktop.
Save ChunMinChang/03741999c8f5e27dd655368b999870c0 to your computer and use it in GitHub Desktop.
Set channel map on ALSA
#include <alsa/asoundlib.h> // for ALSA APIs
#include <stddef.h> // for offsetof
#include <stdio.h> // for fprintf
#include <stdlib.h> // for calloc
#include <string.h> // for strcmp
#define DEBUG 1 // Set 1 to log the debugging messages.
#define LOG(...) DEBUG && fprintf(stdout, __VA_ARGS__)
#define ALSA_DEFAULT_DEVICE_NAME "default"
#define ALSA_ALL_CARDS -1
#define ALSA_MAX_CHANNELS 8
typedef struct {
// For opening a PCM
snd_pcm_t* pcm;
const char* name;
int mode;
snd_pcm_stream_t type;
// channel layout setting
unsigned int channels;
snd_pcm_chmap_t* map;
} alsa_stream;
static void
alsa_enumerate_devices()
{
const char *interfaces[] = {"card", "pcm", "rawmidi", "timer", "seq", "hwdep", 0};
unsigned int i = 0;
char** hints;
char** hint;
char* name;
char* io;
char* desc;
char* desc_tmp;
int r;
snd_config_update();
for (i = 0 ; interfaces[i] ; ++i) {
LOG("\n\t=== Query interface: %s === \n", interfaces[i]);
r = snd_device_name_hint(ALSA_ALL_CARDS, interfaces[i], (void***)&hints);
if (r) {
LOG("[%d] Querying devices failed for %s.\n", r, interfaces[i]);
continue;
}
for (hint = hints ; *hint ; ++hint) {
name = snd_device_name_get_hint(*hint, "NAME");
io = snd_device_name_get_hint(*hint, "IOID");
desc = snd_device_name_get_hint(*hint, "DESC");
LOG("%s\n", name);
LOG("\tIO: %s\n", io);
desc_tmp = strtok(desc, "\n");
while (desc_tmp != NULL) {
LOG("\t%s\n", desc_tmp);
desc_tmp = strtok(NULL, "\n");
}
free(name);
free(desc);
free(io);
}
snd_device_name_free_hint((void**)hints);
}
}
static void
alsa_print_channel_map(snd_pcm_chmap_t* map)
{
assert(map);
unsigned int i;
LOG("\t%d channels\n", map->channels);
for (i = 0 ; i < map->channels ; ++i) {
LOG("\tmap->pos[%d] = %d\n", i, map->pos[i]);
}
}
static void
alsa_get_supported_channel_map(snd_pcm_t *pcm)
{
assert(pcm);
LOG("\n\t=== Query channel maps === \n");
snd_pcm_chmap_query_t** maps = snd_pcm_query_chmaps(pcm);
if (!maps) {
LOG("Couldn't query channel maps.\n");
return;
}
unsigned int i = 0;
snd_pcm_chmap_query_t* map;
for (map = maps[0]; map; map = maps[i++]) {
LOG("channel map %d\n", i);
printf("\ttype: %d\n", map->type);
alsa_print_channel_map(&map->map);
}
snd_pcm_free_chmaps(maps);
}
// static snd_pcm_chmap_t*
static void
alsa_get_current_channel_map(snd_pcm_t *pcm)
{
assert(pcm);
LOG("\n\t=== Get channel map === \n");
snd_pcm_chmap_t* map = snd_pcm_get_chmap(pcm);
if (!map) {
LOG("Couldn't get channel map.\n");
return;
}
alsa_print_channel_map(map);
free(map);
}
static snd_pcm_chmap_t*
alsa_create_channel_map(unsigned int channels)
{
assert(channels);
size_t size = offsetof(snd_pcm_chmap_t, pos[channels]);
assert(size >= sizeof(snd_pcm_chmap_t));
snd_pcm_chmap_t* map = (snd_pcm_chmap_t*) calloc(1, size);
assert(map);
// Using SMPTE layout:
enum snd_pcm_chmap_position layout[ALSA_MAX_CHANNELS][ALSA_MAX_CHANNELS] = {
{ SND_CHMAP_MONO },
{ SND_CHMAP_FL, SND_CHMAP_FR },
{ SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_LFE },
{ SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_SL, SND_CHMAP_SR },
{ SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_FC, SND_CHMAP_SL, SND_CHMAP_SR },
{ SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_FC, SND_CHMAP_LFE, SND_CHMAP_SL, SND_CHMAP_SR },
{ SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_FC, SND_CHMAP_LFE, SND_CHMAP_RC, SND_CHMAP_SL, SND_CHMAP_SR },
{ SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_FC, SND_CHMAP_LFE, SND_CHMAP_RL, SND_CHMAP_RR, SND_CHMAP_SL, SND_CHMAP_SR },
};
map->channels = channels;
unsigned int i;
for (i = 0 ; i < map->channels ; ++i) {
map->pos[i] = layout[map->channels][i];
}
return map;
}
static int
alsa_set_layout(alsa_stream* stream)
{
assert(stream && stream->pcm && stream->channels && !stream->map);
LOG("\n\t=== Set channel map === \n");
stream->map = alsa_create_channel_map(stream->channels);
assert(stream->map);
alsa_print_channel_map(stream->map);
int r;
r = snd_pcm_set_chmap(stream->pcm, stream->map);
if (r) {
LOG("[%d] Set channel map failed.\n", r);
}
free(stream->map);
return r;
}
// static XXX
// alsa_select_device(alsa_stream* stream)
// {
// }
// static XXX
// alsa_set_something(alsa_stream* stream)
// {
// }
static int
alsa_open_stream(alsa_stream* stream) {
return snd_pcm_open(&stream->pcm, stream->name, stream->type, stream->mode);
}
static int
alsa_close_stream(alsa_stream* stream)
{
return snd_pcm_close(stream->pcm);
}
int main()
{
alsa_enumerate_devices();
alsa_stream as = {
// PCM settings
NULL,
ALSA_DEFAULT_DEVICE_NAME,
SND_PCM_STREAM_PLAYBACK, // SND_PCM_STREAM_CAPTURE
SND_PCM_NONBLOCK,
// Channel layout settings
6,
NULL
};
// Should we open a PCM by channel map ?
// alsa_select_device(&as);
alsa_open_stream(&as);
assert(!strcmp(as.name, snd_pcm_name(as.pcm)));
// Or we need to set something before assigning the channel map ?
// alsa_set_something(&as);
alsa_get_supported_channel_map(as.pcm);
alsa_set_layout(&as);
alsa_get_current_channel_map(as.pcm);
alsa_close_stream(&as);
}
CXX=gcc
CFLAGS=-lasound
all:
$(CC) alsa_channel_map.c -o alsa_channel_map $(CFLAGS)
clean:
rm alsa_channel_map
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment