Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Advice on detecting when Sounddevice has stopped playing audio (and using multiple OutputStreams) #541

Open
andrewfr opened this issue Jun 3, 2024 · 1 comment

Comments

@andrewfr
Copy link

andrewfr commented Jun 3, 2024

I am writing the audio component for a digital assistant. An important feature is the ability to pause a "thread" producing audio output, (let's call this T_1), start a new one T_2, then when T_2 ends, resume T_1.

Right now, each "thread" has an associated outputStream (and callback). This seems to work but PortAudio literature states that opening multiple OutputStreams is not a good idea. I also do too many things in the callback.

I am looking at "Proposed Enhancements to PortAudio API" 019 []https://www.portaudio.com/docs/proposals/019-NotifyClientWhenAllBuffersHavePlayed.html

I have tried polling with active(). This doesn't work nor do I understand why it should work. As I understand it, a stream is active if it is not stopped. I don't want to open and close the outputStream.

I've reading PortAudio []https://www.portaudio.com/docs/v19-doxydocs/start_stop_abort.html , I've tried , after the callback has finished outputting, putting a thread to sleep for a time based on the estimated duration of the audio .... and a fudge_factor.

audio_context is shared between callback and the producing thread

    def callback(outdata, frames, time, status):

        assert frames == audio_context.block_size
        if status.output_underflow:
            print("Output underflow: increase blocksize?", file=sys.stderr)
            raise sd.CallbackAbort
        assert not status

        try:
            # reading messages from a queue
            message = audio_context.input_queue.get()
            if message is None:
                raise sd.CallbackStop

            data = message.data

        except queue.Empty as e:
            print("Buffer is empty: increase buffersize?", file=sys.stderr)
            raise sd.CallbackAbort from e

        if len(data) < len(outdata):
            outdata[: len(data)] = data
            outdata[len(data) :].fill(0)
            # this should be the last data written by the callback
            audio_context.event.set()
        else:
            outdata[:] = data

in producing thread

# wait until callback is finished
audio_context.event.wait()
# let the audio finish. The a_fudge_factor is < 1.0
time.sleep(duration * a_fudge_factor)

This sort of works. I can't seem to find a more acceptable way of doing this? Also is using multiple outputStreams that bad if one has the resources? Any advice would be appreciated.

@mgeier
Copy link
Member

mgeier commented Aug 11, 2024

This seems to work but PortAudio literature states that opening multiple OutputStreams is not a good idea.

Yes, this is normally not recommended.

I would try to use a single stream that has a callback function that communicates with the main thread and produces blocks of audio containing parts of T_1 and T_2 as appropriate. If silence is needed in between, the audio signal should contain zero values for as long as desired.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants