Haiku BSoundPlayer: Callback Playback, Timing, and Shutdown
Use Haiku BSoundPlayer for raw-buffer or BSound playback with checked formats, bounded callbacks, performance-time scheduling, and deterministic stop behavior.
BSoundPlayer is a high-level Haiku class for playing raw audio supplied by a callback or a BSound object. It hides much of the Media Kit node setup while still requiring the application to understand format negotiation, callback timing, object lifetime, and shutdown. It is different from the Game Kit’s BGameSound classes, which target game-oriented preloaded or streaming sound effects.
A callback-based player is useful for generated audio, a decoder that produces frames incrementally, or a small application that does not need a custom Media Kit graph. It is not permission to perform arbitrary work in an audio deadline path. A callback that allocates, blocks, logs synchronously, or waits for the UI can cause audible gaps and can prevent shutdown from completing.
Initialize and inspect the actual format
Construct BSoundPlayer with a name and optional buffer-player callback, event notifier, and cookie, or provide a requested raw-audio format. Another constructor targets a particular media node and can accept a multi-audio format and input. Check InitCheck() before starting playback. If initialization fails, preserve the status and report that output is unavailable instead of repeatedly calling Start().
Format() returns the raw-audio format the player is using. Use the actual rate, channel count, sample format, byte order, and buffer interpretation supplied to the callback. Do not fill samples using assumptions from the requested format if the final negotiated format differs. Validate the size and format before calculating frames; guard multiplication overflow and avoid writing beyond the callback buffer.
The system output device and available formats can differ across machines. Avoid advertising exact latency or sample-rate guarantees based on one test computer. Record the Haiku revision, device, negotiated format, and buffer behavior when diagnosing dropouts.
Keep PlayBuffer() deadline-safe
The buffer-player callback receives a cookie, writable buffer pointer, byte size, and media_raw_audio_format. Fill only that memory and return promptly. Do not retain the buffer pointer after the callback. Do not block on disk, network, or a BLocker held by the UI thread. Precompute coefficients, decode ahead into a bounded ring buffer, and use a nonblocking handoff from the producer thread.
If no audio is ready, write silence in the negotiated sample representation or follow the callback contract for the target API. Leaving old bytes in the buffer can replay stale audio. Treat an underrun as an observable event: increment a counter and let a non-real-time thread report it. Avoid a synchronous log write for every callback.
The EventNotifier callback receives lifecycle notifications such as started, stopped, and sound-done. Keep notifier work short as well. If it needs to update controls, send a message to the owning window and check delivery. The callback’s cookie pointer must remain valid until the player can no longer invoke either callback.
Start, stop, and one-shot sounds
Start() begins the player’s streaming operation after initialization. Stop(block, flush) has parameters controlling whether it blocks and whether queued data is flushed. Choose deliberately: a blocking stop can be inappropriate on a UI event thread if the node needs time to drain or stop; a nonblocking stop requires the owner to manage later completion and object lifetime.
StartPlaying(BSound*, atTime) schedules a sound and returns a play_id; the overload can set a volume for that playback. Keep the BSound and player lifetime consistent with the API’s ownership contract. Do not free a sound object while an active playback still depends on it unless the class explicitly retains its data. Use IsPlaying(), StopPlaying(), and WaitForSound() as status operations, and avoid waiting from a callback that must be allowed to run for the sound to finish.
The atTime value is a Media Kit performance time. Use the player/time-source methods and documented time units rather than a calendar timestamp or a guessed wall-clock value. A time of zero is the API’s default immediate scheduling form; it does not establish sample-accurate synchronization with an unrelated video clock.
Volume and latency are operational controls
The API exposes linear Volume() in the documented range and decibel controls, along with latency access and parameter information. Clamp UI input to supported ranges and handle silence as a special case when converting linear gain to decibels, since log10(0) is not finite. Volume readback may involve cached state or hardware parameters; do not interpret it as proof that every device changed its output exactly as requested.
Latency() is useful for diagnostics and synchronization, but it is not automatically the end-to-end latency from a button press through the hardware to a speaker. Buffering, scheduler delay, converters, and device behavior contribute. Measure audible or loopback latency when a feature needs a user-facing timing claim.
Preroll() prepares the output node for playback. It can expose initialization or graph errors before presenting content. If it fails, do not mark playback as active. Distinguish a node that could not preroll from a sound that finished naturally or a callback that produced silence.
Synchronize callback state safely
SetBufferPlayer(), SetNotifier(), and SetCallbacks() alter callbacks and cookie state. The implementation uses internal locking around callback invocation. Still, the application must ensure that the cookie’s pointed-to state remains valid for callbacks already in progress. A safe teardown sequence prevents new callbacks, stops the player, waits for or otherwise synchronizes with callbacks as required, then destroys the cookie.
Avoid changing the callback from inside itself unless the API explicitly documents that operation. The callback is invoked under internal synchronization in the current implementation; reentering a setter that takes the same lock could deadlock. Update callback configuration from a control thread at a safe boundary.
If a worker fills a ring buffer, define producer-consumer semantics: fixed capacity, memory ordering, underrun/overrun counters, and a shutdown signal. Do not let a producer overwrite data still being consumed. For format changes, rebuild the ring buffer using the new frame geometry only after the old callback path has stopped.
A minimal procedural checklist
- Construct the player with the intended format or output node.
- Check
InitCheck()and inspectFormat(). - Allocate bounded producer state that outlives the callbacks.
- Start only after the callback can produce valid frames or silence.
- Monitor underruns, event status, and actual playback state.
- Stop with a policy appropriate to the thread, then release callback state only when no callback can use it.
The numbered list is not a replacement for the class’s ownership contract. Validate behavior against the target Haiku API and test both start failure and device loss. Do not create one BSoundPlayer per UI click when a single long-lived player with bounded state is appropriate.
Test under audio and lifecycle stress
Test the normal output device, a missing or unavailable output, unsupported format requests, callback underrun, rapid start/stop, one-shot overlapping playback, volume zero and maximum, and shutdown while sound is active. Exercise WaitForSound() only from a thread that cannot block the callback. Measure callback duration and verify it remains within the available buffer time.
Run long playback while resizing windows, changing workspaces, and closing the app. Confirm that the UI remains responsive and that no callback uses freed state. Repeat with the requested format altered or a device changed. Record actual sample format, buffer size, Latency(), underrun count, and return status for reproducible reports.
BSoundPlayer is a useful bridge between simple application audio and the full Media Kit graph. Its convenience does not remove real-time constraints: use the negotiated format, keep callbacks bounded, use performance time, and make stop and object lifetime explicit. That produces audio that behaves predictably instead of merely starting once on a development machine.
Related:
- Haiku Game Kit Sound: Preload, Stream, and Control Playback
- Fixing Audio That Isn’t Working on Haiku
Sources: