bastibe/PySoundCard

Sound device with multiple DACs/ ADCs: Multichannel Output/Recording

Opened this issue · 6 comments

Is it possible to play different outputs at the same time with two analogue audio jacks on one Sound device with multiple DACs using one or more pysoundcard.Streams?

I use PySoundCard with the RaspberryPi and an external soundcard (the same as in https://www.raspberrypi.org/forums/viewtopic.php?t=113584&p=778961).

Simply choosing 'channels=3' doesn't work and e.g. 'device=2' (pysoundcard.device_info()) seems only to change the sounddevice as a whole from the external sound device to the internal or HDMI.

If there is an implementation in PortAudio but not in soundcard jet, i would like to start a pull request (and learn C).

Related:

Perhaps it has something to do with ALSA. To initialize the different outputs and inputs of the soundcard there are some shell scripts.

Here is an example:

###!/bin/bash

# $1 added to support 1st line argument. i.e. "./Playback_to_Headset.sh -q" will stop all the control information being displayed on screen

# Playback from AP to Headset
# Set path gain to -6dB for safety. ie max 0.5Vrms output level.
amixer $1 -Dhw:sndrpiwsp cset name='HPOUT1 Digital Volume' 116
# Clear the HPOUT1 Input 1 and 2  mixers. This will ensure no previous paths are connected to the HPOUT1.
# This doesn't include Inputs 3 and 4.
amixer $1 -Dhw:sndrpiwsp cset name='HPOUT1L Input 1' None
amixer $1 -Dhw:sndrpiwsp cset name='HPOUT1R Input 1' None
amixer $1 -Dhw:sndrpiwsp cset name='HPOUT1L Input 2' None
amixer $1 -Dhw:sndrpiwsp cset name='HPOUT1R Input 2' None
# Setup HPOUT1 input path and volume
amixer $1 -Dhw:sndrpiwsp cset name='HPOUT1L Input 1' AIF1RX1
amixer $1 -Dhw:sndrpiwsp cset name='HPOUT1L Input 1 Volume' 32
amixer $1 -Dhw:sndrpiwsp cset name='HPOUT1R Input 1' AIF1RX2
amixer $1 -Dhw:sndrpiwsp cset name='HPOUT1R Input 1 Volume' 32
# Unmute the HPOUT1 Outputs
amixer $1 -Dhw:sndrpiwsp cset name='HPOUT1 Digital Switch' on


# The following command can be used to test
# aplay -Dhw:sndrpiwsp -r 44100 -c 2 -f S32_LE <file>

Initializing the 'Headset' and the 'LineOut' together with the given scripts leads to the same output through both of them.

The devices in an python3 session are then as follows:

Python 3.4.2 (default, Oct 19 2014, 13:31:11) 
[GCC 4.9.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pysoundcard
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.front
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround21
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround21
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround40
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround41
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround50
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround51
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround71
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.iec958
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.iec958
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.iec958
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started

>>> pysoundcard.device_info(0)
/usr/local/lib/python3.4/dist-packages/cffi/model.py:526: UserWarning: 'enum PaHostApiTypeId' has no values explicitly defined; next version will refuse to guess which integer type it is meant to be (unsigned/signed, int/long)
  % self._get_c_name())
{'default_high_input_latency': 0.034829931972789115, 'hostapi': 0, 'default_samplerate': 44100.0, 'max_output_channels': 2, 'default_low_input_latency': 0.005804988662131519, 'default_low_output_latency': 0.005804988662131519, 'name': 'snd_rpi_wsp: - (hw:0,0)', 'default_high_output_latency': 0.034829931972789115, 'max_input_channels': 2}

>>> pysoundcard.device_info(1)
{'default_high_input_latency': 0.034829931972789115, 'hostapi': 0, 'default_samplerate': 44100.0, 'max_output_channels': 128, 'default_low_input_latency': 0.005804988662131519, 'default_low_output_latency': 0.005804988662131519, 'name': 'sysdefault', 'default_high_output_latency': 0.034829931972789115, 'max_input_channels': 128}

>>> pysoundcard.device_info(2)
{'default_high_input_latency': -1.0, 'hostapi': 0, 'default_samplerate': 48000.0, 'max_output_channels': 2, 'default_low_input_latency': -1.0, 'default_low_output_latency': 0.021333333333333333, 'name': 'dmix', 'default_high_output_latency': 0.021333333333333333, 'max_input_channels': 0}

>>> pysoundcard.device_info(3)
{'default_high_input_latency': 0.034829931972789115, 'hostapi': 0, 'default_samplerate': 44100.0, 'max_output_channels': 2, 'default_low_input_latency': 0.005804988662131519, 'default_low_output_latency': 0.005804988662131519, 'name': 'default', 'default_high_output_latency': 0.034829931972789115, 'max_input_channels': 2}

>>> pysoundcard.device_info(4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.4/dist-packages/pysoundcard.py", line 267, in device_info
    raise RuntimeError("Invalid device")
RuntimeError: Invalid device

I hope this is not off-topic.

Portaudio represents every sound card as one Stream. While you can configure each Stream to have fewer than its maximum number of channels, you can only select the-first-N channels, i.e. the first 2, or the first 4, or the first 64 channels. If you then write to these Streams, you have to write to all selected channels simultaneously.

In other words, it is sadly impossible to open one Stream for the first channel, and one Stream for the second channel. That being said, since you are only using Linux, you might just use pulseaudio directly. Have a look at this Gist for a very simple audio player in Python using pulseaudio and CFFI. Pulseaudio does support channel maps, so your application should be feasible.

As an aside, I would love to rewrite PySoundCard in terms of Pulseaudio, Coreaudio and Mediafoundation directly, as opposed to using these interfaces through Portaudio. But alas, I lack the time to do so right now. The above Gist was a proof of concept that this would be possible with just Python and CFFI.

Thank you very much for the fast answer. I need some numpy-calculations for my problem. I definitely will look at this in detail and report in case of success.

Please do report, as I am very interested in this problem as well!

Hey bastibe,
I started with IntBes to implement the developer examples from PulseAudio into CFFI. But as a beginner with C it is quit hard :p

If you like have a look: https://github.com/LibrEars/pyPulseAudio

Very interesting!

I've added you to a private repo, where I started doing something similar. So far, I have implemented a proof-of-concept access to WASAPI (Windows), CoreAudio (Mac) and Pulseaudio (Linux), although audio playback only works with Pulse so far.

Maybe this can serve as inspiration for you (Is there any way of giving you read access but not write access?)

Sadly, my own development of this will not be quick, since I have a number of more pressing tasks to work on first.