spatialaudio / jackclient-python

🂻 JACK Audio Connection Kit (JACK) Client for Python :snake:
https://jackclient-python.readthedocs.io/
MIT License
131 stars 26 forks source link

jack client closes itself just after start #86

Closed TurBoss closed 4 years ago

TurBoss commented 4 years ago

hello,

I'm trying to figure how to keep the client running but it just shows up for a little

# coding=utf-8

import io
import logging
import sys

import jack
import soundfile as sf
import queue
import threading

BUFFERSIZE = 1024

class AudioLib:
    def __init__(self):

        self.log = logging.getLogger(__name__)
        self.log.info("INIT AUDIO LIB")

        self.buffersize = BUFFERSIZE

        self.queue = queue.Queue(maxsize=self.buffersize)
        self.event = threading.Event()

        self.jack_client = jack.Client('TEST', session_id='Test')

        self.blocksize = self.jack_client.blocksize
        self.samplerate = self.jack_client.samplerate

        self.jack_client.set_xrun_callback(self.xrun)
        self.jack_client.set_shutdown_callback(self.shutdown)
        self.jack_client.set_process_callback(self.process)

        self.voice_output_0 = self.jack_client.outports.register('voice_0')

        print("ACTIVATE")

        self.jack_client.activate()

        # self.jack_client.connect(self.voice_output_0, 'system:playback_1')

    def play(self, format, audio, fin):

        audio_samplerate, bits, channels = format

        with sf.SoundFile(io.BytesIO(audio),
                          channels=channels,
                          samplerate=audio_samplerate,
                          format='RAW',
                          subtype='FLOAT') as audio_stream:

            block_generator = audio_stream.blocks(blocksize=self.blocksize,
                                                  always_2d=True, fill_value=0)
            for _, data in zip(range(self.buffersize), block_generator):
                self.queue.put_nowait(data)  # Pre-fill queue

            timeout = self.blocksize * self.buffersize / self.samplerate
            for data in block_generator:
                self.queue.put(data, timeout=timeout)

            self.queue.put(None, timeout=timeout)  # Signal end of file
            self.event.wait()  # Wait until playback is

    def print_error(self, *args):
        print(*args, file=sys.stderr)

    def xrun(self, delay):
        self.print_error("An xrun occured, increase JACK's period size?")

    def shutdown(self, status, reason):
        self.print_error('JACK shutdown!')
        self.print_error('status:', status)
        self.print_error('reason:', reason)
        self.event.set()

    def stop_callback(self, msg=''):
        if msg:
            self.print_error(msg)
        for port in self.jack_client.outports:
            port.get_array().fill(0)
        self.event.set()
        raise jack.CallbackExit

    def process(self, frames):
        assert frames == self.jack_client.blocksize

        try:
            data = self.queue.get_nowait()
            for channel, port in zip(data.T, self.jack_client.outports):
                port.get_array()[:] = channel
        except queue.Empty:
            self.stop_callback('Buffer is empty: increase buffersize?')

        if data is None:
            self.stop_callback()  # Playback is finished
        else:
            for channel, port in zip(data.T, self.jack_client.outports):
                port.get_array()[:] = channel

this is mostly a rip off play file example

thanks

mgeier commented 4 years ago

You are only defining the class AudioLib here, can you please provide example code that shows how you are using it?

TurBoss commented 4 years ago

hello, here they goes

# coding=utf-8

from ttspico import TtsEngine
from audio_lib import AudioLib

class TtsLib:

    def __init__(self):

        print("INIT TTS LIB")

        self.audio_lib = AudioLib()
        self.tts_engine = TtsEngine("es-ES")
        self.speak("TEST TEST")

    def speak(self, text):
        print("SPEAK")
        self.tts_engine.speak(text, self._callback)

    def _callback(self, audio_format, audio, fin):
        self.audio_lib.play(audio_format, audio, fin)

def main():
    tts = TtsLib()
    tts.speak("hello world")

if __name__ == "__main__":
    main()

thanks

mgeier commented 4 years ago

Thanks for the update.

What do you expect this code to do?

What does it actually do?

Are there any error/warning messages?

TurBoss commented 4 years ago

Hi

What do you expect this code to do?

I would like tts to speak thru jack

What does it actually do? the main app keeps running but the jack_client just goes off :/ I would like to have the jack_client running so I can send many speak commands

it gives this errors from the app:


Buffer is empty: increase buffersize?
AL lib: (EE) alc_cleanup: 2 devices not closed

from jack logs


Sat Mar 14 23:16:14 2020: New client 'TEST' with PID 5341
Sat Mar 14 23:16:14 2020: Connecting 'TEST:voice_0' to 'system:playback_1'
Sat Mar 14 23:16:14 2020: Disconnecting 'TEST:voice_0' from 'system:playback_1'

Thanks!

mgeier commented 4 years ago

Thanks for the update.

I think this is relevant:

Buffer is empty: increase buffersize?

Well, at least the first part.

This message appears when the exception queue.Empty is raised, which in turn means the queue was empty.

The problem is that your process() method is trying to read data from the queue before your play() method has a chance to write data to the queue.

HaHeho commented 4 years ago

Yes, what @mgeier said. This usually happens in the very beginning. And since you stop the playback, you'll not get to hear anything. There should not be a stop, but simply a debugging message instead, so you notice when interrupts (or a delayed start) occurs.

       except queue.Empty:
            self.stop_callback('Buffer is empty: increase buffersize?')

Also, with some fiddling around you can incorporate another Threading.Event to realize Play/Pause behavior. The tricky part is only to find the right spots for the different wait() and exception handling commands. I can provide you with a reference if that is of interest.

TurBoss commented 4 years ago

Hello thanks for your responses,

replaced the stop_callback with a print_error and it worked

it made a noise when i try to play the first sound :) but then it closes the client again

HaHeho commented 4 years ago

I assume that means it is not working as intended?

I noticed BUFFERSIZE (for the queue) is very large. This value is number of blocks, not samples. This might only create some hick-ups in the beginning, since a lot of data is buffered into the queue as fast as possible. Although, if it works, that should not create any problems I guess.

Also, you picked very specific settings for your with sf.SoundFile(). I assume this was a conscious choice, or does that maybe create some problems and not really managing to properly load the entire file?

TurBoss commented 4 years ago

hello, the sound file comes from ttspico and its a text to speech module

TurBoss commented 4 years ago

I merge the 2 files in a single one , can be found here http://dpaste.com/2S9JV39

mgeier commented 4 years ago

@TurBoss I've looked at your code and what you are doing with SoundFile indeed looks a bit strange ...

You seem to have a complete buffer with audio data that you are passing to AudioLib.play(). Inside, you create a BytesIO from that buffer and then in turn a SoundFile object. You seem to use that object only to call the .blocks() method on it. You then send those blocks through a Queue.

To be perfectly blunt, this doesn't make a lot of sense.

Since you already have the whole audio signal available at once, there is no need to split it into blocks and send the blocks through a queue.

It would be much better if you would just send the whole thing as one object through the queue.

In the process callback, you can then read the appropriate block of the signal at each iteration.

TurBoss commented 4 years ago

cool I'm trying thanks for the clarification

TurBoss commented 4 years ago

thanks for the help guys! I'm closing this issue

i'm not spending much time my project :(