Skip to content

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

@andrewfr

Description

@andrewfr

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions