Haiku BMediaRoster: Discovering Nodes and Building Media Graphs
Build Haiku Media Kit graphs with BMediaRoster: enumerate nodes, negotiate connections, schedule performance time, and release node references safely.
Haiku’s Media Kit is a graph of media nodes, not a single player object that owns every stage. A producer can supply audio or video, a consumer can accept it, and intermediate nodes can transform or mix it. BMediaRoster is the system-facing coordinator used to discover nodes, retain references, connect outputs to inputs, and schedule node operations. Knowing where the roster’s authority ends is as important as knowing the method names: a successful graph operation is not proof that a microphone is unmuted, a camera sees an image, or a downstream device is physically healthy.
Obtain the roster deliberately
BMediaRoster::Roster() returns the shared roster and creates an instance when one is not already available. Its optional status_t* reports initialization failure. CurrentRoster() only returns an already-existing instance and has a different concurrency contract: the header says it is not thread-safe if called at the same time as Roster(). Prefer Roster() for ordinary application setup, check the returned status, and do not build a second competing notion of the system media graph.
The roster is a BLooper; it communicates with the Media Server and with nodes through the Kit’s messaging/port machinery. Do not call a slow graph operation while holding a UI lock or an application-wide mutex. Keep the user interface responsive, serialize your own graph mutations, and report the status returned by each operation instead of assuming a call succeeded because it returned quickly.
Distinguish node identity from a node reference
A media_node value identifies a node endpoint in the graph, while APIs such as GetNodeFor() and the convenience getters obtain references that must be balanced with ReleaseNode(). Common getters include audio input, audio output, audio mixer, video input, video output, and time source. The API header specifically advises using the mixer rather than connecting directly to the audio output in common cases.
Treat every successful acquired reference as an owned resource. Store the media_node alongside a flag that says whether your code acquired it; release only references you own, and release them on all error paths. ReleaseNode() can free a node when no references remain, so retaining a clone indefinitely can keep a node alive after the UI that displayed it has closed. Conversely, using a node after releasing its last owned reference is a lifetime bug.
For add-on-provided nodes, distinguish a dormant description from an instantiated live node. A dormant node describes a flavor that can be instantiated; it is not already a running graph participant. Instantiate it through the roster, check the result, then later stop/disconnect as appropriate and release the live reference. Discovery, instantiation, connection, and execution are separate stages and deserve separate error reporting.
Discover compatible inputs and outputs
Use the roster’s live-node and input/output enumeration APIs to discover what is currently available. Enumeration is a snapshot, not a permanent promise: an add-on can disappear, a device can be removed, and nodes can expose additional free endpoints as existing endpoints become occupied. Match media type and format, not just a display name. Keep the node identity and endpoint IDs from the returned structures; do not synthesize a media_source or media_destination from a label.
A graph UI should represent discovery as refreshable state. A device listed a moment ago may fail to instantiate or connect now. Conversely, a node can exist but have no currently free input matching the desired format. When a connection fails, preserve the requested source/destination, requested format, and status code in diagnostics, then refresh the endpoint list before presenting a retry.
Connect by negotiating a format
Connect() takes a source and destination as hints, an in/out media_format, and output structures for the connection that actually becomes active. The roster asks the producer to propose or refine a format and asks the consumer to accept it before finalizing the connection. The accepted format may differ from the initial wildcard request. Always inspect the returned media_output and media_input structures and use their actual source/destination after success. The header explicitly warns that the original source and destination arguments are only hints and should not be reused as if they were the established connection.
media_format requested = desiredFormat;
media_output connectedOutput;
media_input connectedInput;
status_t status = roster->Connect(source, destination, &requested,
&connectedOutput, &connectedInput);
if (status == B_OK) {
// Keep the returned endpoints and negotiated format for teardown.
activeOutput = connectedOutput;
activeInput = connectedInput;
activeFormat = requested;
}
The example shows the shape of the call; the endpoint types and destination must come from real nodes, and the format must be initialized for the media type being requested. Do not treat B_OK as evidence that data has already flowed. A successful connection establishes graph wiring. Starting nodes, selecting a device, permissions or hardware state, and receiving valid buffers are separate checks.
If a node reports that it cannot accept a format, do not blindly retry with a different byte layout guessed from a name. Query its supported formats or outputs, request a compatible format, and let the negotiation callbacks run. A format change later in the graph is also a coordinated event; producers and consumers must agree before the producer sends buffers in the new format.
Schedule with performance time
StartNode() and StopNode() accept a performance-time value. That time belongs to the media time-source model; it is not a wall-clock timestamp from system_time() and should not be substituted casually. Use the intended time source and scheduling model for the graph. A node can have startup latency, and PrerollNode() is documented as synchronous, so keep it off latency-sensitive UI paths.
For a basic interactive action, query or retain the relevant time source, use the documented roster operation, and surface errors. For synchronized recording or playback, schedule all related nodes against the same performance-time plan rather than starting each on unrelated real-time calls. Do not claim sample-accurate synchronization without measuring the actual graph and understanding each node’s latency contract.
SyncToNode() can wait for a node to reach a requested time and has a timeout parameter. A timeout is a bounded wait result, not proof that the node is permanently broken. Include the node ID, requested time, and timeout in logs, then decide whether to stop, retry, or let the user cancel. Avoid waiting forever in a window’s message loop.
Make teardown the reverse of setup
Track every established connection and every acquired node reference. Disconnect using the returned endpoints (or the matching node IDs and endpoints) before releasing the nodes. Stop nodes according to the application’s policy, disconnect consumers/producers, release any instantiated dormant node, and finally release other clones acquired through the roster. Make cleanup idempotent so a partial setup failure can unwind only the resources that were actually acquired.
The order matters when a graph is only partially assembled. If the second of two connections fails, do not tear down an uninitialized endpoint structure. Record a small state machine such as discovered, instantiated, connected, and running; transitions occur only after successful API results. Destructors and window-close paths should converge on one cleanup routine so a user closing a panel during a pending operation does not leak references.
Do not assume disconnecting a graph resets device settings or restores a user’s prior system selection. Graph topology and global media settings are related but different. If your application changes a system input/output selection, preserve and restore it only through the documented APIs for the target release and with explicit user consent.
Operational verification
Test the graph with no Media Server, no matching producer, an occupied input, an incompatible format, a device removed between discovery and connection, and a node that connects but emits no buffers. Confirm that every error path releases acquired references. Log node IDs, endpoint IDs, media type, negotiated format, scheduling time, and operation status. Verify that an output is audible or visible with an independent observation rather than treating graph membership as end-to-end success.
The roster is a graph coordinator and node-reference API. Reliable applications treat its calls as fallible state transitions, keep endpoint identities returned by successful negotiation, balance references, and verify real media flow independently.
Related:
- Haiku BMediaAddOn: Media Node Factories and Flavor Discovery
- Haiku BMediaFormats: Format Mapping and Encoder Discovery
Sources: