azlux / pymumble

Mumble client implementation in Python
GNU General Public License v3.0
129 stars 61 forks source link

Error when receiving audio (OSError: [Errno -9999] Unanticipated host error) #111

Closed Piper984 closed 3 years ago

Piper984 commented 3 years ago

Hi, I got pymumble w/ pyaudio working for sending audio. But when I have a client send audio I am getting the following crash:

Expression 'stream->playback.pcm' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 4453
Expression 'alsa_snd_pcm_mmap_begin( self->pcm, &areas, &self->offset, numFrames )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 3983
Expression 'PaAlsaStreamComponent_RegisterChannels( &self->capture, &self->bufferProcessor, &captureFrames, &xrun )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 4098
Expression 'PaAlsaStream_SetUpBuffers( stream, &framesGot, &xrun )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 4426
Traceback (most recent call last):
  File "audio-only_client.py", line 81, in <module>
    data = stream.read(CHUNK, exception_on_overflow=False)
  File "/usr/lib/python3/dist-packages/pyaudio.py", line 608, in read
    return pa.read_stream(self._stream, num_frames, exception_on_overflow)
OSError: [Errno -9999] Unanticipated host error

Here is the modified audio-only_client.py script I am using:

import pymumble_py3 as pymumble_py3
from pymumble_py3.callbacks import PYMUMBLE_CLBK_SOUNDRECEIVED as PCS
import pyaudio

# Connection details for mumble server. Hardcoded for now, will have to be
# command line arguments eventually
pwd = ""  # password
server = "192.168.42.157"  # server address
nick = "audio-only_client"
port = 64738  # port number

# pyaudio set up
CHUNK = 1024
FORMAT = pyaudio.paInt16  # pymumble soundchunk.pcm is 16 bits
CHANNELS = 1
RATE = 48000  # pymumble soundchunk.pcm is 48000Hz

p = pyaudio.PyAudio()
stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,  # enable both talk
                output=True,  # and listen
                output_device_index=1,
                input_device_index=1,
                frames_per_buffer=CHUNK)

# mumble client set up
def sound_received_handler(user, soundchunk):
    """ play sound received from mumble server upon its arrival """
    stream.write(soundchunk.pcm)

# Spin up a client and connect to mumble server
mumble = pymumble_py3.Mumble(server, nick, password=pwd, port=port)
# set up callback called when PCS event occurs
mumble.callbacks.set_callback(PCS, sound_received_handler)
mumble.set_receive_sound(1)  # Enable receiving sound from mumble server
mumble.start()
mumble.is_ready()  # Wait for client is ready

# constant capturing sound and sending it to mumble server
while True:
    data = stream.read(CHUNK, exception_on_overflow=False)
    mumble.sound_output.add_sound(data)

# close the stream and pyaudio instance
stream.stop_stream()
stream.close()
p.terminate()

I assume the error is from the pyaudio library, but I am not sure about that. I've tried setting various output device index to try to make sure I am selecting the right one. I did make a little loop to get the devices reported back from pyaudio:

{'index': 0, 'structVersion': 2, 'name': 'bcm2835 HDMI 1: - (hw:0,0)', 'hostApi': 0, 'maxInputChannels': 0, 'maxOutputChannels': 8, 'defaultLowInputLatency': -1.0, 'defaultLowOutputLatency': 0.0016099773242630386, 'defaultHighInputLatency': -1.0, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}
/n
{'index': 1, 'structVersion': 2, 'name': 'USB Audio Device: - (hw:1,0)', 'hostApi': 0, 'maxInputChannels': 1, 'maxOutputChannels': 2, 'defaultLowInputLatency': 0.008684807256235827, 'defaultLowOutputLatency': 0.008707482993197279, 'defaultHighInputLatency': 0.034829931972789115, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}
/n
{'index': 2, 'structVersion': 2, 'name': 'sysdefault', 'hostApi': 0, 'maxInputChannels': 0, 'maxOutputChannels': 128, 'defaultLowInputLatency': -1.0, 'defaultLowOutputLatency': 0.0016099773242630386, 'defaultHighInputLatency': -1.0, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}
/n
{'index': 3, 'structVersion': 2, 'name': 'default', 'hostApi': 0, 'maxInputChannels': 0, 'maxOutputChannels': 128, 'defaultLowInputLatency': -1.0, 'defaultLowOutputLatency': 0.0016099773242630386, 'defaultHighInputLatency': -1.0, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}
/n
{'index': 4, 'structVersion': 2, 'name': 'dmix', 'hostApi': 0, 'maxInputChannels': 0, 'maxOutputChannels': 2, 'defaultLowInputLatency': -1.0, 'defaultLowOutputLatency': 0.021333333333333333, 'defaultHighInputLatency': -1.0, 'defaultHighOutputLatency': 0.021333333333333333, 'defaultSampleRate': 48000.0}
/n

Thanks for any help!

Piper984 commented 3 years ago

Quick follow up:

if I toggle the input / output booleans and the code as needed, I can get both audio in and audio out working separately, but not at the same time(see error above). Could this be due to driver or maybe CPU usage? I am using a pretty low power CPU (RPi Zero). I've confirmed I can do playback and record at same time w/ the USB Audio device via aplay and arecord utilities.

azlux commented 3 years ago

Hi, Do you still need help on this ticket ?

Piper984 commented 3 years ago

Sorry for delayed reply. I have confirmed this was due to low CPU speed.

Piper984 commented 3 years ago

BTW: I thought to capture a good fix for the issue I found, which is to create two streams. I found this doing a search and it worked well for me. Hope it might be helpful to someone...

#!/usr/bin/python3

import pymumble_py3 as pymumble_py3
from pymumble_py3.callbacks import PYMUMBLE_CLBK_SOUNDRECEIVED as PCS
import pyaudio

# Connection details for mumble server. Hardcoded for now, will have to be
# command line arguments eventually
pwd = ""  # password
server = "192.168.42.10"  # server address
nick = "AudioGateway42"
port = 64738  # port number

# pyaudio set up
CHUNK = 1024
FORMAT = pyaudio.paInt16  # pymumble soundchunk.pcm is 16 bits
CHANNELS = 1
RATE = 48000  # pymumble soundchunk.pcm is 48000Hz

p = pyaudio.PyAudio()
stream_listen = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                #input=True,  # enable both talk
                output=True,  # and listen
                frames_per_buffer=CHUNK)

stream_talk = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,  # enable both talk
                #output=True,  # and listen
                frames_per_buffer=CHUNK)

# mumble client set up
def sound_received_handler(user, soundchunk):
    """ play sound received from mumble server upon its arrival """
    stream_listen.write(soundchunk.pcm)

# Spin up a client and connect to mumble server
mumble = pymumble_py3.Mumble(server, nick, password=pwd, port=port)

# set up callback called when PCS event occurs
mumble.callbacks.set_callback(PCS, sound_received_handler)
mumble.set_receive_sound(1)  # Enable receiving sound from mumble server
mumble.start()
mumble.is_ready()  # Wait for client is ready

# constant capturing sound and sending it to mumble server
while True:
    data = stream_talk.read(CHUNK)
    mumble.sound_output.add_sound(data)

# close the stream and pyaudio instance
stream_talk.stop_stream()
stream_talk.close()
stream_listen.stop_stream()
stream_listen.close()
p.terminate()