cpal icon indicating copy to clipboard operation
cpal copied to clipboard

Is it possible to end the stream from the callback?

Open vincent-sparks opened this issue 1 year ago • 5 comments

I'm writing an audio/video player that does decode in the main thread, then sends the decoded samples via a ring buffer to the callback. I have a way for the ring buffer to signal EOF, and when the callback sees that I would like to tell cpal to stop playing once it has finished the current batch of samples. I could of course hold a reference to the Stream from the main thread and simply destroy it once the decoder hits EOF, but then I would end up not playing whatever audio data was still left in the ring buffer when that happened.

I initially tried to do this by having the stream be owned by the callback, by sending it in via an Arc<Mutex<Option<Stream>>> which the callback sets to None to signify it is done playing; however, the stream is deliberately made !Send for future compatibility with AAudio. I note that AAudio allows the callback to return a sentinel value to signal the stream should end. Would it be feasible to do this with other backends?

vincent-sparks avatar Aug 29 '24 22:08 vincent-sparks

the stream is deliberately made !Send for future compatibility with AAudio

What does this have to do with giving your callbacks ownership of an Arc?

Ralith avatar Aug 30 '24 03:08 Ralith

My original intent, since the callback object must be created before the stream, and any variables moved into it must stay that way, was to create an Arc<Mutex<Option<Stream>>> which I then clone() and pass to the callback. Immediately after creating the stream I *stream_arc.lock().unwrap() = Some(stream). When the callback wants to end the stream, it does *stream_arc.lock().unwrap() = None to destroy the stream object. This, however, does not compile, since the Stream object, by design, cannot be sent between threads and thus cannot be sent into the audio output thread, regardless of mechanism.

There does not seem to be any other way for the callback to decide when the stream should end, as opposed to the thread that created the stream. This issue is a question regarding the cross-platform feasibility of adding one.

vincent-sparks avatar Aug 31 '24 01:08 vincent-sparks

There are no platform-specific concerns. You communicate information from the stream callback the same way you communicate information to it: by passing it through shared memory. Stick an AtomicBool in your Arc.

Ralith avatar Aug 31 '24 03:08 Ralith

So when I hit EOF in the main thread, do I just spinloop until that flag gets set and then destroy the stream? That seems an awfully inefficient solution.

Would it be feasible cross-platform to add a sentinel value that the callback could return to close the stream from the stream thread? I'd be willing to contribute code to make this happen.

vincent-sparks avatar Sep 02 '24 00:09 vincent-sparks

You can use whatever inter-thread notification mechanism you like, so long as you don't block your stream callback for too long. That's what CPAL would have to do to implement the behavior for you anyway. Coordinating threads is a deep subject, but you could start at park.

Ralith avatar Sep 02 '24 00:09 Ralith