Sunday, February 12, 2012

Alsa: multiple output, multiple sound cards, multiple users

I had some difficulties setting up alsa as I wanted, because I encountered some troubles:
  • when more than one application was playing sound, the first one played nicely but the others wasn't playing at all, complaining about a "busy resource"
  • solved this one, I had to setup the whole thing to work with two sound cards at the same time
  • eventually, I wanted to enable concurrent sound for multiple users
Alsa Wiki's guides were useful only for a few things, but none of them gave me the whole solution. So after a deep digging, I ended up with this config:
pcm.USBmic {
    type hw
    card CNF8215
}

ctl.USBmic {
    type hw
    card CNF8215
}

pcm.internal {
    type hw
    card SB
}

ctl.internal {
    type hw
    card SB
}

pcm.wireless {
    type hw
    card Transceiver
}

ctl.wireless {
    type hw
    card Transceiver
}

pcm.internalDmixed {
   type dmix
   ipc_key 1024
   ipc_key_add_uid false
   ipc_perm 0666
   slave {
       pcm "internal"
       period_time 0
       period_size 2048
       channels 4
    }
    bindings {
       0 0
       1 1
       2 2
       3 3
    }
}

pcm.wirelessDmixed {
   type dmix
   ipc_key 2048
   ipc_key_add_uid false
   ipc_perm 0666
   slave {
       pcm "wireless"
       period_time 0
       period_size 2048
       channels 2
    }
    bindings {
       0 0
       1 1
    }
}

pcm.both {
    type route;
    slave.pcm {
        type multi;
        slaves.a.pcm "wirelessDmixed";
        slaves.b.pcm "internalDmixed";
        slaves.a.channels 2;
        slaves.b.channels 4;
        bindings.0.slave a;
        bindings.0.channel 0;
        bindings.1.slave a;
        bindings.1.channel 1;
      
        bindings.2.slave b;
        bindings.2.channel 0;
        bindings.3.slave b;
        bindings.3.channel 1;
        bindings.4.slave b;
        bindings.4.channel 2;
        bindings.5.slave b;
        bindings.5.channel 3;
    }
  
    ttable.0.0 1;
    ttable.1.1 1;
  
    ttable.0.2 1;
    ttable.1.3 1;
    ttable.2.4 1;
    ttable.3.5 1;
}

pcm.!default {
        type plug
        slave {
                pcm both
        }
}

ctl.!default {
        type hw
        card SB
}

How does this strange thing work? Let's look at it step by step:


Understanding which cards you want to use


To get your cards names, the easiest way is to use aplay -l command. When I execute it I get this output:
:~$ aplay -l  
**** List of PLAYBACK Hardware Devices ****
card 0: Transceiver [2.4G Wireless Transceiver], device 0: USB Audio [USB Audio]
  Subdevices: 0/1
  Subdevice #0: subdevice #0
card 2: SB [HDA ATI SB], device 0: ALC662 rev1 Analog [ALC662 rev1 Analog]
  Subdevices: 0/1
  Subdevice #0: subdevice #0
card 2: SB [HDA ATI SB], device 1: ALC662 rev1 Digital [ALC662 rev1 Digital]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 3: HDMI [HDA ATI HDMI], device 3: HDMI 0 [HDMI 0]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
If you use your card's name as reference for alsa is better, because it will always connect to the right card no matter what the cards' order is. Above the card names are the ones I bolded (yours will be different).

As you can see, I have  a lot of devices here, but the ones I care about are the first two, Transceiver and SB. SB is the internal card, with four speakers plugged, and Transceiver is a USB wireless sound card, transmitting to two speakers in another room.

Setup basic pcms


Once you know your card's names, you have to setup basic hardware pcms and ctls. A pcm is an input/output device, and a ctl is a control for a device. So I wrote this:
pcm.USBmic {
    type hw
    card CNF8215
}

ctl.USBmic {
    type hw
    card CNF8215
}

pcm.internal {
    type hw
    card SB
}

ctl.internal {
    type hw
    card SB
}

pcm.wireless {
    type hw
    card Transceiver
}

ctl.wireless {
    type hw
    card Transceiver
}
As you can see there are one pcm and one ctl for each card I want to use, defined by type.devicename. So now if I want to output sound on my internal card I can use aplay -Dinternal sound.wav (thanks to pcm), and alsamixer -Dinternal to manage the volume only for that card (thanks to ctl).

Dmix: concurrent sound from more than one application


Now we have all this pcms, but we still have that resource busy error hanging around. How do we solve it? We setup a few more pcms:
pcm.internalDmixed {
   type dmix
   ipc_key 1024
   ipc_key_add_uid false
   ipc_perm 0666
   slave {
       pcm "internal"
       period_time 0
       period_size 2048
       channels 4
    }
    bindings {
       0 0
       1 1
       2 2
       3 3
    }
}

pcm.wirelessDmixed {
   type dmix
   ipc_key 2048
   ipc_key_add_uid false
   ipc_perm 0666
   slave {
       pcm "wireless"
       period_time 0
       period_size 2048
       channels 2
    }
    bindings {
       0 0
       1 1
    }
}
Here I define two more pcms, using the previously defined ones as slaves. In this way I can use dmix to mix various outputs channels and get rid of that awful resource busy error.

More than one user?


This one was hard to find but easy to apply. If you look at the two dmixed pcms you will see two lines, "ipc_key_add_uid false" and "ipc_perm 0666". The first one disables user id for the device (or so I suppose) and the second one sets permissions for the device. The two combined makes the device usable for more than one user at the same time.

Multiple outputs on multiple cards


And here comes the difficult part: getting these two cards to output concurrent sound. How to do it? Luckily I found a good article about it, and after trying and trying again I ended up merging everything together. The trick here is creating another pcm device, named "both", to merge the two dmixed devices:

pcm.both {
    type route;
    slave.pcm {
        type multi;
        slaves.a.pcm "wirelessDmixed";
        slaves.b.pcm "internalDmixed";
        slaves.a.channels 2;
        slaves.b.channels 4;
        bindings.0.slave a;
        bindings.0.channel 0;
        bindings.1.slave a;
        bindings.1.channel 1;
      
        bindings.2.slave b;
        bindings.2.channel 0;
        bindings.3.slave b;
        bindings.3.channel 1;
        bindings.4.slave b;
        bindings.4.channel 2;
        bindings.5.slave b;
        bindings.5.channel 3;
    }
  
    ttable.0.0 1;
    ttable.1.1 1;
  
    ttable.0.2 1;
    ttable.1.3 1;
    ttable.2.4 1;
    ttable.3.5 1;
}
Aaaand... it has surround too! You can try it using the command "speaker-test -c6 -D both -t wav". Here you define a 6-channel device, using the two channels of the first sound card as the first two channels (0-1) and the four channels of the second sound card as the last four channels (2-5). Then using ttable the four channels of the second sound card are copied to the first four channels, so we will have the same otput on the first card and at the same time on the two front speakers of the second card. The last two channels, front center and rear center, will not output sound ad all, but I didn't understand how to change this pcm into a 4-channel one (comments are welcome).

If all the 6 speakers were in the same room, I would have done something like this:
pcm.both {
    type route;
    slave.pcm {
        type multi;
        slaves.a.pcm "wirelessDmixed";
        slaves.b.pcm "internalDmixed";
        slaves.a.channels 2;
        slaves.b.channels 4;
        bindings.0.slave a;
        bindings.0.channel 0;
        bindings.1.slave a;
        bindings.1.channel 1;
      
        bindings.2.slave b;
        bindings.2.channel 0;
        bindings.3.slave b;
        bindings.3.channel 1;
        bindings.4.slave b;
        bindings.4.channel 2;
        bindings.5.slave b;
        bindings.5.channel 3;
    }
  
    ttable.0.0 1;
    ttable.1.1 1;
  
    ttable.2.2 1;
    ttable.3.3 1;
    ttable.4.4 1;
    ttable.5.5 1;
}

merging the 2 speakers on one card and 4 speakers on the other into an unique 6-channel setting.

Set the default output device


Ok, now that this monstrous multichannel stream-mixing device is ready, the last thing to do is set it as the default output device. Here it is:
pcm.!default {
        type plug
        slave {
                pcm both
        }
}

ctl.!default {
        type hw
        card SB
}
So simple that doesn't need explanation at all. The ctl here is a bit picky, so it is set to control the main hardware card. Now if you try "speaker-test -c4 -D default -t wav" you will be amazed by the result.

Making it work with Skype and Amarok 1.4


Skype is easy: just set your mic pcm as the input device (in my case, USBMic), and the default pcm as the output device.

Amarok doesn't like the default alsa device, but it's not so hard to fix. Just go in Settings > Configure Amarok > Engine, select xine-engine, select alsa as output plugin, select your speaker arrangment and then set all the fields in "ALSA Devices Configuration" to plug:both.



Here, done! :)

If you want one application to output sound only to one card, remember to use always the dmixed pcm and not the basic one, otherwise you will be stuck again with the resource busy error.

System-wide or single user config


To make this setting available for all users, just copy it into /etc/asound.conf (as root), otherwise you can write it into .asoundrc (in the home dir of one or more users), but I fear that the multiuser setting will result useless in this case.

To have the system-wide settings work, just restart alsa with /etc/rc.d/rc.alsa restart (as root). If you edited .asoundrc you have to logout and login again.

2 comments:


  1. Thank you very much for putting this together for all of us...sound setup has been the biggest problem I have had in making a permanent move over to Linux.

    I have a somewhat similar setup to your configuration. My first question is how did you find the name of your USB MIC? It is not among the list of devices in your aplay -l output.

    Thank you again for this tutorial.

    -Rick Evans

    ReplyDelete
  2. Its wok, thank you very much

    ReplyDelete