Skip to content
Haiku OSDeep Dive Published Updated 7 min readViews unavailable

Haiku BFileInterface: File-Backed Media Nodes and Reference Semantics

Implement Haiku BFileInterface nodes with format enumeration, sniffing, reference replacement, duration reporting, and explicit file I/O failure handling.

BFileInterface is a Media Kit mix-in for a node that can read media from a file or write media to one. It is not the same thing as BMediaFile and BMediaTrack, which are application-facing APIs for reading and writing media containers and tracks. A node implementing BFileInterface tells the Media Server and clients how a file-backed node identifies formats, selects a file reference, reports duration, and handles create-versus-open requests.

The class derives virtually from BMediaNode and has protected pure virtual hooks. Its public header explicitly says the node must also implement BBufferConsumer or BBufferProducer, as appropriate to the node’s role. BFileInterface does not provide a decoder, encoder, buffer path, or general-purpose file picker; it defines a control interface around a file that the media node itself understands.

Decide which side of the file boundary you own

Start by drawing the data path. A decoder-like node might accept a file reference, read and interpret the container, then produce buffers. A recorder-like node might create a destination, consume buffers, and write a supported format. In both cases, BFileInterface concerns the node’s current referenced file, while producer/consumer callbacks carry actual media data through the graph.

Do not advertise BFileInterface merely because an application has a file URL or writes a sidecar. The contract is for a Media Kit node that reads or writes media data to disk. If an application owns the file operation and sends decoded content into a separate node, the application’s BFile or media-container API may be the correct owner instead.

The public interface asks the node to implement format enumeration, cookie disposal, duration, sniffing, setting the reference, and getting the current reference. These hooks should share one internal file-state object so the reported MIME type, duration, selected reference, and active reader/writer cannot disagree. Treat replacement as a state transition: validate and open the candidate first, then publish it as current, or preserve the previous valid reference if the operation fails.

Enumerate formats as a cursor protocol

GetNextFileFormat() receives a caller-owned integer cookie and a pointer to a media_file_format output structure. The API documentation describes the first call with cookie zero and successive calls returning each supported format. A successful result must fill the output record and update the cookie so the next call can continue. When there are no more formats, return an appropriate non-success status; do not return B_OK with an uninitialized structure.

The exact cookie strategy belongs to the implementation. For a small immutable table, the cookie can identify a stable index. If the supported list is generated dynamically, retain only the state needed to resume safely and ensure a concurrent or repeated enumeration cannot invalidate another caller’s cursor. DisposeFileFormatCookie() exists to release data associated with that cookie; it should tolerate the lifecycle your implementation documents, including cleanup after partial enumeration.

Make format metadata conservative. A file format record should describe what the node truly supports, not what a downstream codec might someday support. If a format is read-only or write-only, do not imply both directions. If capabilities change by file extension, codec, or build option, expose only the formats currently usable by the node and ensure SetRef() rejects combinations it cannot actually process.

Sniff content, not just filenames

SniffRef() receives an entry_ref, a caller-provided MIME type buffer, and a quality output. The current public header comments that the MIME buffer has 256 bytes. Validate the file’s content using bounded reads and return the MIME type that matches the actual supported format. A suffix can guide a first check, but it is not proof: renamed or truncated files are common operational cases.

Quality communicates confidence or suitability, not a user-facing percentage and not a promise that full decoding will succeed. The current Haiku documentation describes values from 0.0 for no support to 1.0 for full control of the format. Set a nonzero quality only when the node can plausibly process the content, and use the documented no-handler status when it cannot. A high-quality sniff must still be followed by normal parse and codec error handling.

Because sniffing may be called while the Media Roster is searching for a handler, keep it bounded and free of irreversible side effects. Do not create an output file, change the active reference, or start graph processing during identification. Limit reads to signatures and structural headers where possible. A corrupt file that passes a signature check should fail later with a useful status rather than hanging the roster during probing.

Make SetRef() transactional

SetRef(file, create, duration) is the point at which a client asks the node to use a file. When create is false, the file should already exist and the node should open it, validate it, and return the media duration. When create is true, the node should create and initialize a new file for writing, and initialize the duration output as documented. Haiku’s API reference says an existing destination is overwritten in the create case; that is a destructive operation and must not be disguised as a harmless open.

Use the entry_ref as the selected identity instead of storing a stale display path and reopening it later without checks. A reference identifies a filesystem entry through Haiku’s Storage Kit, but the file can still be deleted, replaced, moved, or become inaccessible. Revalidate the file each time a new operation requires it. Report a missing entry or permission failure rather than silently switching to a similarly named file.

SetRef(ref, create, duration):
    reject a null duration output
    open one candidate file using the exact requested create/open mode
    if opening fails, leave the current node state unchanged
    if create is false, parse and validate the candidate and its duration
    if validation fails, close the candidate and preserve the old active file
    if create is true, initialize the new container and set duration to zero
    atomically replace the node's active file state with the candidate
    return success

This is deliberately protocol pseudocode, not a drop-in BFile implementation: the public BFileInterface contract does not prescribe a C++ move or swap operation for the concrete file object. Implement the candidate-state transfer with the ownership primitive supported by your target SDK, and ensure create mode truncates or replaces a destination only once. A format-specific validator must understand the actual container. For crash-safe output, write to a temporary destination and publish the final name only after the container is finalized.

Keep GetRef() and duration in sync

GetRef() returns the current entry_ref and MIME type. It should describe the same file that the writer or reader currently has open. If no file is selected, return an error and leave output buffers in a documented safe state. Check the caller’s output pointers, respect the fixed MIME buffer size, and always terminate a string that your implementation writes.

Duration should have one unit and one source of truth. The API uses bigtime_t and describes the duration in microseconds. Do not confuse it with a byte count, frame count, or performance-time deadline. If duration is unknown for a live or incomplete stream, return the appropriate status rather than a fabricated zero that clients may interpret as an empty file. Recompute or invalidate cached duration after writes that change the file.

The file reference and graph lifecycle have separate state. Selecting a valid file does not mean the node is connected, started, or producing valid buffers. Starting a graph does not prove that a requested path is still writable. Report errors at the boundary where they occur and avoid overloading GetRef() with device or playback status.

Test destructive and stale-reference cases

Test supported and unsupported formats, a valid signature in a truncated file, malformed metadata, a missing entry, a read-only volume, a full volume, and a destination that already exists. Confirm create mode’s overwrite behavior is explicit and that canceling a replacement leaves the prior active file usable. Test repeated GetNextFileFormat() calls and cookie disposal after both completion and early cancellation.

For a reader, verify that duration matches parsed media and does not become stale after the file is replaced. For a writer, test finalization, interrupted writes, and reopening the result with an independent reader. Check buffer callbacks separately; a successful file selection does not establish correct producer/consumer timing or ownership.

BFileInterface makes a Media Kit node file-aware. Correct implementations treat sniffing as bounded identification, SetRef() as a checked state transition, format cookies as owned enumeration state, and duration and MIME data as outputs that must match the active file. Keep the file contract separate from graph success and from the application-level media container APIs.

Related:

Sources:

Comments