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

Haiku BMediaFile and BMediaTrack: Container and Track Workflows

Read and write media containers in Haiku with BMediaFile and BMediaTrack, managing codecs, metadata, frame timing, track ownership, and finalization.

The Media Kit has two related but distinct application models. A live media graph connects producers, consumers, filters, and time sources. A file workflow uses BMediaFile to open a media container and BMediaTrack to read or write one of its tracks. The file API can use installed extractors and encoders without making the application a real-time node, but it does not erase the differences between container formats, codecs, raw frame formats, and track timelines.

Use BMediaFile when an application needs to inspect, decode, edit, or create a media file or supported stream. Use the graph APIs when the job is to connect live sources and sinks or participate in system timing. A file being readable by the Media Kit does not promise that every codec is installed, every track is decodable, or the same encoder exists on another Haiku system.

Open once, inspect the container, then acquire tracks

For an existing file, construct a BMediaFile from an entry_ref or a BDataIO source and immediately check InitCheck(). Query GetFileFormatInfo() and GetMetaData() for container-level information, then use CountTracks() to enumerate tracks. A container can have multiple audio, video, subtitle, or other tracks; index order is not a user-facing selection policy.

TrackAt(index) returns a BMediaTrack owned through the BMediaFile API. The header explicitly says to call ReleaseTrack() when done; after release, that particular track object must no longer be used. Deleting the BMediaFile also releases its tracks. This ownership rule matters in importers that inspect dozens of candidate tracks or keep a track in a worker beyond the discovery function.

#include <MediaFile.h>
#include <MediaTrack.h>
#include <stdio.h>

status_t InspectTracks(const entry_ref& ref)
{
    BMediaFile file(&ref);
    status_t status = file.InitCheck();
    if (status != B_OK)
        return status;

    media_file_format container;
    status = file.GetFileFormatInfo(&container);
    if (status != B_OK)
        return status;

    for (int32 index = 0; index < file.CountTracks(); ++index) {
        BMediaTrack* track = file.TrackAt(index);
        if (track == nullptr)
            continue;

        status_t trackStatus = track->InitCheck();
        media_format encoded;
        status_t formatStatus = track->EncodedFormat(&encoded);
        if (trackStatus == B_OK && formatStatus == B_OK) {
            printf("track=%" B_PRId32 " frames=%" B_PRId64
            " duration_us=%" B_PRId64 "\n", index,
                track->CountFrames(), track->Duration());
        }

        status = file.ReleaseTrack(track);
        if (status != B_OK)
            return status;
    }

    return B_OK;
}

This example checks both the file and each track rather than assuming that a recognized container implies a usable decoder. A production importer should decide which track to use based on the user’s choice and its media type/format, preserve meaningful metadata, and propagate the first useful failure. It should not silently choose “track zero” if that could select commentary audio, an alternate language, or a track the application cannot decode.

Encoded bytes are not decoded frames

EncodedFormat() describes the track’s encoded representation. DecodedFormat() negotiates a best-fit decoded format with the codec; the frames returned from ReadFrames() use that negotiated result. Applications must size and interpret their buffers according to the chosen raw format, not the compressed size of the file or a guessed bytes-per-frame constant.

ReadFrames() has media-specific behavior: a video track should be read one frame at a time, while an audio track can return a number of samples. At end of file, it can return a partial buffer and updates the actual frame/sample count through the output parameter. The media_header carries timing for returned data. Always use the returned count and header, and handle an end-of-stream condition without parsing bytes that were not produced.

If a codec cannot be found, ReadChunk() can still expose the track’s encoded data in chunks. ReadChunk() and ReadFrames() are mutually exclusive access modes for a particular track: the public header explicitly warns not to mix them. Choose whether the application needs decoded frames or raw encoded chunks before reading, and create a new track object if you must restart with a different access path.

Seeking is not necessarily exact. SeekToTime() and SeekToFrame() update the requested position to the one the codec could reach; a video codec may only seek to a key frame. Use the returned position and, if the product needs explicit key-frame behavior, the documented seek flags or FindKeyFrameForTime()/FindKeyFrameForFrame(). For synchronized A/V, keep each track’s time base and timestamp in view rather than assuming that equal frame indices in two tracks identify the same presentation time.

Metadata is useful but not a substitute for validation

Container and track metadata are returned through BMessage using a common naming scheme so applications can query information across formats. Metadata is descriptive input from a file. It may be absent, incomplete, inconsistent with payload data, or unsupported by a particular extractor. Use it for labels and hints, but confirm buffer geometry and actual decoded output from the negotiated format and returned frame count.

Keep metadata handling separate from codec selection. A title or language tag can inform a UI choice, but it does not prove that a track has a compatible decoder. Make the selected track and fallback rule visible to the user when files contain multiple alternatives. If parsing metadata into a long-lived model, normalize values at the boundary and do not assume every file uses the same optional fields.

Enumerate file writers and encoders deliberately

Writing starts by selecting a media_file_format returned by get_next_file_format(). The cookie is initialized to zero before the first call and retained as you enumerate available writers. Filter candidates for the capabilities and media formats your application needs. Then enumerate encoders compatible with the chosen file format and the input media format; do not assume that a container writer includes a codec suitable for every raw format.

Once the format and codec are selected, construct the output BMediaFile, create each output track, check its InitCheck(), and call CommitHeader() after all tracks have been created. The public header’s documented order is significant: create tracks first, commit the header, write data through the tracks, then call CloseFile() after all track data has been written. CloseFile() finalizes the container; a process that exits early or skips it may leave output that cannot be reopened or whose indexes are incomplete.

int32 cookie = 0;
media_file_format candidate;
while (get_next_file_format(&cookie, &candidate) == B_OK) {
    if ((candidate.capabilities & media_file_format::B_WRITABLE) == 0)
        continue;

    printf("writer: %s\n", candidate.pretty_name);
    // Check the writer against the application's required media formats,
    // then enumerate its encoders before selecting it for output.
}

This is a discovery sketch rather than a complete encoder-selection routine. The get_next_encoder() overloads accept input/output media_format constraints and return media_codec_info; use the overload that expresses the actual pipeline. A matching file writer alone does not prove that a compatible encoder can be created for the track you need.

Resource limits, performance, and failure handling

Decode and encode in bounded chunks. A long media file can contain a very large number of frames and can expand dramatically when compressed video or audio is decoded. Do not allocate an entire track based on CountFrames() unless a checked upper bound and memory budget make it safe. Prefer streaming one video frame or a bounded run of audio samples, and preserve the media header’s start time for downstream synchronization.

File I/O and codec work can block, allocate, or call add-ons. Keep it off a GUI looper and off any real-time Media Kit callback. A background worker can report progress and errors through messages while the application retains clear ownership of BMediaFile and its tracks. Coordinate cancellation so the worker does not access a released track or a destroyed file object.

For output, write to a temporary destination when the file format and filesystem workflow permit, call CloseFile(), then reopen and inspect the result before publishing it as complete. This does not make the encoder transactional, but it prevents an interrupted export from being mistaken for a finished artifact. Check every status from CreateTrack(), CommitHeader(), WriteFrames() or WriteChunk(), and CloseFile(); retain the first failure while still performing safe cleanup.

Validation matrix for media files

Test an empty file, a valid container with no supported tracks, multiple audio tracks, multiple video tracks, missing codec, a short read at end of stream, a key-frame-only seek, a file with incomplete metadata, and a malformed input. For each decoder, compare reported format with output size and verify that the code never reads beyond its buffer. For an exporter, reopen the result, compare track count and durations, validate timestamps and representative decoded frames, and test cancellation before and after header commit.

Record the Haiku revision, file format writer/extractor, selected codec, encoded and decoded formats, track index, count/time position, chunk/frame mode, and exact status code. Repeat the tests on a system with a different codec package set. If the same file works on one machine but fails on another, compare the installed media add-ons before attributing the issue to the container API.

BMediaFile provides a practical bridge between containers and Haiku’s media codecs, while BMediaTrack exposes the per-track stream and timeline. Correct use depends on treating format negotiation, ownership, encoded-versus-decoded access, partial reads, and finalization as explicit contracts. That discipline lets a file tool reuse the Media Kit without pretending that file processing is the same as a live media graph.

Related:

Sources:

Comments