alsa-project / alsa-lib

The Advanced Linux Sound Architecture (ALSA) - library
GNU Lesser General Public License v2.1
357 stars 176 forks source link

Inconsistent order of channels on C-Media 7.1 channel device #402

Open victorallume opened 1 month ago

victorallume commented 1 month ago

I have a USB sound device (Startech ICUSBAUDIO7D, which comes up as ID 0d8c:0102 C-Media Electronics, Inc. CM106 Like Sound Device through lsusb) which I'm trying to program the output of for 8 channels. The python code (outputs a 50Hz sine wave) I'm using is below (each channel gets a different amplitude so I can identify them with an oscilloscope).

The sound card has 4 stereo jacks, to which I have a scope connected.

The problem is that on different runs of the program, the output mappings are not consistent - each run of the program might have the order of the channels different to the previous. So running it one time might have amplitudes (0, 0.125) on the first jack, but the next run it might have those (0.75, 0.25) on the first jack (it doesn't appear to be 'paired' per jack)

I initially discovered this when using portaudio, and decided to try using alsa-only and the issue persists.

Is this likely to be a problem with the driver, alsa-lib, or is there something else going on here?

import numpy as np
import alsaaudio

def sine_wave(amplitude=1, freq=50, sample_rate=48000):
    wave_len = sample_rate/freq
    samples = (amplitude * 32767 * np.sin(2 * np.pi * np.arange(wave_len) * freq / sample_rate)).astype(np.int16)
    return samples

def eightchan(freq=50, sample_rate=48000):
    wave_len = sample_rate//freq
    frames = np.zeros((wave_len, 8), dtype=np.int16)
    samples = sine_wave()
    for c in range(8):
        frames[:wave_len,c] = samples[:wave_len]*(c/8)
    return frames.tobytes()

if __name__ == '__main__':
    pcm = alsaaudio.PCM(device='surround71:CARD=ICUSBAUDIO7D,DEV=0', format=alsaaudio.PCM_FORMAT_S16_LE, channels=8, rate=48000, type=alsaaudio.PCM_PLAYBACK, periodsize=960, periods=1)
    frames = eightchan()
    while True:
        pcm.write(frames)

alsa-info output is attached alsa-info.txt

victorallume commented 1 month ago

I've been able to re-create this using aplay and an 8-channel .wav file (I made the wav file in audacity by creating 8 tracks, putting a unique 30-second tone into each channel, then exporting, ensuring each track was mapped to a unique channel). Stopping and re-starting the playback shows different channel pairs appearing on my oscilloscope

victorallume commented 1 month ago

Further to the previous comment with the wav file - it shows the inconsistent mapping when I Ctrl-C the playback and re-start it. However, when I let the file play out, and start it again, the mapping seems to be the same every time (I've repeated this about 10 times and it's worked consistently every time). Similarly, in the original python program, if I call pcm.close() prior to terminating the program, then next time it seems to be fine (again, I repeated this about 10 times).

So I'm thinking that the driver or the device is left in an inconsistent state when the stream is interrupted. Closing the PCM resets this state, but I'm surprised this doesn't happen when the PCM is initialised (either via aplay or the python library).

victorallume commented 1 month ago

As an attempt for a workaround, I tried the following:

  1. Open the PCM
  2. (optionally) write some frames to the PCM, (I tried 1, 2 and 4)
  3. Cleanly close the PCM object
  4. Wait some time (I tried 0, 1 and 5 seconds)
  5. (optionally) delete the PCM object
  6. Re-create the PCM object
  7. Start streaming frames to the PCM object (code is below)

None of this worked, so it seems there's some other initialisation or de-initialisation happening when the process is started

    pcm = alsaaudio.PCM(device='surround71:CARD=ICUSBAUDIO7D,DEV=0', format=alsaaudio.PCM_FORMAT_S16_LE, channels=8, rate=48000, type=alsaaudio.PCM_PLAYBACK, periodsize=960, periods=1)
    for i in range(4):
        pcm.write(frames)
    pcm.close()
    time.sleep(5)
    del pcm
    pcm = alsaaudio.PCM(device='surround71:CARD=ICUSBAUDIO7D,DEV=0', format=alsaaudio.PCM_FORMAT_S16_LE, channels=8, rate=48000, type=alsaaudio.PCM_PLAYBACK, periodsize=960, periods=1)
victorallume commented 1 month ago

I've also tried alsactl init, alsa force-reload and usbreset 0d8c:0102 (and combinations of both of these) and they don't reliably get the card back from an inconsistent state. Rebooting seems to be the only thing that reliably works.

victorallume commented 1 month ago

Actually, rebooting doesn't work reliably - the channel mapping is occassionally different after a reboot. If I close the pcm every time, it seems to reliably open with the same mapping the next run, but the initial run is not always consistent