SoundCard
SoundCard copied to clipboard
Running soundcard in a multiprocess on ubuntu
So right now I am testing my system to be compatible with linux as well. Unfortunately this caused some new problems of which I was able to deduct it to issues with soundcard (pulse) and multiprocessing.
It seems when you run SC inside a multiprocessing process it generates this error
Assertion 'o' failed at pulse/operation.c:67, function pa_operation_unref(). Aborting.
I think this happens because it reimports the module when using a multi process causing it to perhaps lock on a shared resource?
The way I tested this was by recreating the scenario in a simple program.
If you run this it will record from all available mics but fail when trying to do this in a process.
To make it work inside you process you need to move the soundcard import inside the process function and remove the normal process() call
from multiprocessing import Process
import time
import soundcard as sc
def process():
for mic in sc.all_microphones(include_loopback=True):
time.sleep(0.2)
with mic.recorder(samplerate=16000, channels=1) as m:
for i in range(0, 2):
m.record(numframes=640)
print("recorded " + mic.name)
print("Running normally")
process()
print("Running inside a process")
proc = Process(target=process, daemon=True)
proc.start()
proc.join()
Is this using the forked processes? Perhaps the fork shares some kind of resource that should not be shared.
Does it work if you import soundcard inside the process
?
Hello, sirs
First of all, thank you, @bastibe for such a great pypackage.
I'm currently migrating some old portaudio code to soundcard and faced exactly this same issue on multiprocessing.
What made it work to me is running the playback/recording in a separate threading.Thread and any calculations about the recorded or played data in a parallel process.
Although I now Threads are not even close to a good choice in audio, it's working glitch free.
EDIT: Importing soundcard inside the parallel process gave me an error as well.
I'm currently on Linux Mint 19.3
Glad I'm not alone in this.
Unfortunately threads are not possible in my current system since it still seems to get blocked and miss audio data.
Importing the soundcard in your parrelel process only works if you import it nowhere else.
Thank you for your further investigations.
You could try setting a unique program name in each process, using the soundcard._pulse.name
property. Perhaps pulse is confused by too many processes sharing the same name?
Or perhaps pulse has a limit on how many processes can access a sound card at the same time?
It might actually help to ask for this on the pulseaudio mailing list. A lot of the error cases of pulseaudio are terribly under-documented. I have to be honest, however, and confess that I don't have the time to do the analysis here. I'll try to assist you as much as I can, but I can't do it myself. I would be incredibly grateful for a pull request!
I've tried setting different names for different processes but it didn't worked as well.
Just for the record, the following code give me a different pulse assertion error:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# scwire.py
import multiprocessing as mp
import soundcard as sc # (1)
class Wire:
def __init__(self, samplerate=48000, blocksize=240, channels={'in': [0, 1], 'out': [0, 1]}):
self.samplerate = samplerate
self.blocksize = blocksize
self.channels = channels
self.running = mp.Event()
self.mic = sc.default_microphone() # (2)
self.spk = sc.default_speaker() # (3)
return
def start(self):
with self.mic.recorder(self.samplerate, self.channels['in'], self.blocksize) as r:
with self.spk.player(self.samplerate, self.channels['out'], self.blocksize) as p:
self.running.set()
while self.running.is_set():
p.play(r.record(self.blocksize//2))
r.flush()
return
def stop(self):
self.running.clear()
return
def list_devices():
import soundcard as sc
spks = dict([(spk.name, spk.id) for spk in sc.all_speakers()])
mics = dict([(mic.name, mic.id) for mic
in sc.all_microphones(include_loopback=True)])
return spks, mics
if __name__ == '__main__':
import sys
# list_devices() # (4)
wire = Wire()
proc = mp.Process(target=wire.start)
proc.start()
while True:
key = input()
if key in ['stop', 'exit', 'q']:
wire.stop()
break
sys.exit(proc.join())
By running python scwire.py
the output is:
Assertion 's' failed at pulse/stream.c:1399, function pa_stream_connect_playback(). Aborting.
But if the lines commented with (1), (2), and (3) are moved inside the start
method, the wire completely works. I'll call it "fix".
So, the error mentioned by @Bob-Thomas about Assertion 'o' failed...
only happens if soundcard is imported on both parent and child processes, like the case if the line (4) is uncommented after the "fix" is made.
I know it don't get us closer to solving this issue, but shows another pulseaudio behaviour.
Thank you, @Chum4k3r!
It seems that pulseaudio has some strange multiprocessing behavior, with state being shared and conflicting between parent and child processes. This makes some amount of sense.
I wish I could find some documentation on the matter.
Same issue here trying to use multiprocessing
here. However, importing soundcard inside the process works as @Bob-Thomas mentioned. I think we can handle as an Exception on python-side.
Tested on Debian 10 64 bits
from multiprocessing import Process, Queue,current_process
def record_only_process():
import soundcard as sc
process_name = current_process().name
start_time = time.time()
x=0
numframes = int(44100*record_seconds)
default_mic = sc.default_microphone()
recorder = default_mic.recorder(44100, channels=1, blocksize=256)
with recorder as r:
while True:
data = r.record(numframes) # record each _x seconds_
# HERE ADD data to a Queue
print('{} RECORD: {}'.format(process_name, str(process_queue)))
if __name__ == '__main__':
p1 = Process(target=record_only_thread, name='[P1]')
p1.start()
p1.join()
By running
python scwire.py
the output is:Assertion 's' failed at pulse/stream.c:1399, function pa_stream_connect_playback(). Aborting.
Have you tried running the latest git version instead of the somewhat outdated PyPi release?