Haiku BMediaDecoder: Chunk Input, Codec Selection, and Decoded Frames
Use Haiku BMediaDecoder to select a codec, refine encoded input, negotiate decoded output, and honor frame counts and media-header timing.
BMediaDecoder is a codec adapter for applications that already have encoded media chunks and need decoded audio or video. It connects an input-format description to an available decoder add-on and presents a frame-oriented Decode() call. It does not parse an arbitrary file container for you, locate every packet boundary, or guarantee that a particular codec is installed. Keep the file/stream parser, codec selection, decoded-memory layout, and user-visible playback policy as separate responsibilities.
Pick a decoder and check initialization
The class can be initialized from a media_format plus optional codec-specific information, or from a media_codec_info. The format constructor asks the plugin manager to create a compatible decoder. SetTo() replaces the current decoder; current implementation destroys the previous decoder before attempting the replacement. If selection fails, the object becomes uninitialized and InitCheck() reports failure. Check status immediately and do not reuse an old decode configuration after a failed SetTo().
Starting with a format is useful when the stream provides a meaningful media type and codec description. Starting with a codec-info record can make the codec explicit, but it still does not validate that incoming bytes follow the advertised codec bitstream. Preserve the source’s format metadata and any codec-specific information from its container or stream negotiation; do not invent codec private data from a file extension.
The library’s available decoder add-ons are environment-dependent. Report “no compatible decoder” separately from “the decoder rejected this packet” and “the decoded output buffer was too small.” Those are different failure stages and lead to different remedies.
Separate decoder choice from input-format refinement
SetInputFormat() updates the input format on an already selected decoder. The implementation comments explicitly warn that it does not select a new codec. Use SetTo() when the codec itself must change. Use SetInputFormat() only when refining details that are compatible with the current decoder, and check its result. A common bug is changing the codec identity through SetInputFormat() and then continuing to call the old decoder.
Many media formats contain wildcard or partially specified fields during discovery. Before decoding, settle the actual input format and supply any required codec information. A decoder may accept the type yet still fail during setup if essential details are absent. Validate sample rate, channel count, frame or image dimensions, encoded subtype, and codec data according to the media type in use.
SetOutputFormat() asks the decoder to negotiate an output format. The output structure is modified to the format the decoder will actually emit, which can differ when wildcards were supplied. Allocate output storage based on that negotiated result, not on the request. For audio, the source documentation describes output amount in terms of the format’s buffer size; for video, it describes a frame as height times row bytes. Check arithmetic for overflow before allocating large dimensions.
Feed complete chunks from a parser
The base decoder obtains input through the subclass’s GetNextChunk() callback. That method returns a pointer and byte length plus a media_header. The parser therefore owns packetization: it must know where each encoded chunk begins and ends and attach the timing and seek metadata supported by the stream. A decoder is not a substitute for AVI, QuickTime, WAV, or another container demultiplexer.
status_t MyDecoder::GetNextChunk(const void** data, size_t* length,
media_header* header)
{
EncodedPacket packet;
status_t status = demuxer.ReadNext(packet);
if (status != B_OK)
return status;
currentPacket = std::move(packet); // keeps bytes alive for this decode step
*data = currentPacket.bytes.data();
*length = currentPacket.bytes.size();
*header = currentPacket.header;
return B_OK;
}
The example assumes a parser API that returns complete packets and a member that owns the backing bytes. Adapt the lifetime to the actual decoder add-on’s call pattern; the chunk pointer must remain valid for the decode operation that consumes it. Map end-of-stream and I/O errors to meaningful statuses rather than returning a zero-length “successful” packet unless the codec contract expressly uses that representation.
If input comes from a network stream, bound packet size and parser buffering. A malicious or corrupt length field must not trigger an unbounded allocation. Handle truncated chunks as corrupt input, and keep container timestamps distinct from the machine’s current time. If the parser is asynchronous, serialize calls into the decoder unless the API or codec explicitly guarantees safe concurrent use.
Decode with negotiated output capacity
Decode() accepts an output buffer, an in/out frame count, a media_header, and media_decode_info. The latter supplies decode parameters; the decoder returns frame-count and header information. Check the returned status and frame count before consuming output. Do not assume a successful call always produces a complete displayable video frame or a fixed number of audio samples; inspect the negotiated format and actual output metadata.
The method signature does not receive the output buffer’s byte capacity. That means the caller must allocate sufficient storage from the accepted output format and the codec’s documented per-call requirements. Keep the capacity alongside the buffer and validate the returned size/layout before reading. Avoid treating a media_header as a substitute for validating the memory extent.
media_decode_info should be initialized according to the public definition and codec requirements; do not pass an uninitialized stack structure. If a codec needs a keyframe, reference frame, or codec-specific option, use the supported metadata path. The generic wrapper does not promise that any arbitrary packet can be decoded independently. Inter-frame codecs may require earlier state and correct packet order.
Use the buffer decoder for one in-memory chunk
BMediaBufferDecoder is a convenience subclass that supplies input from a buffer and exposes DecodeBuffer(). It is useful for a self-contained chunk or a test fixture. It does not make a multi-packet container into a valid single input. The caller still has to provide the right input format, enough output capacity, and meaningful decode metadata; check InitCheck() and every decode status.
For streaming or multi-chunk work, a subclass with an explicit packet source is clearer. Define how end-of-stream, seek, discontinuity, and parser reset work. If the source is seekable, flush or reconstruct decoder state only through a documented codec/API workflow; do not assume changing the file offset resets an inter-frame decoder.
If an input format changes midstream, treat it as a new decode configuration. Stop feeding the old packet sequence, retain enough stream metadata to select and initialize the new decoder, negotiate its output again, and replace output storage only after the transition succeeds. Do not let one worker decode old-format chunks while another thread changes the decoder’s format. If a reset operation is not present in the public wrapper you are using, recreate the decoder object rather than inventing an undocumented reset call.
For user-facing applications, keep decoder selection out of the drawing or audio callback. Probe available codecs during setup, report unsupported media with the actual format/codec identity, and leave the original input untouched when trying fallback decoders. That makes “no decoder” distinguishable from corruption and prevents a failed probe from destroying the only copy of an input stream.
Error boundaries and observability
Separate four layers in logs: source/container parsing, decoder selection/setup, chunk delivery, and decoded-output validation. Record codec ID, encoded media type, input chunk length, packet timestamp, decode status, returned frame count, output format, and stream position. Avoid logging raw media payloads. A crash or malformed output should be reproducible with a small sanitized sample and the exact negotiated format.
Exercise a valid keyframe, delta frame, corrupt payload, empty stream, truncated last packet, unsupported codec, unsupported output format, changing sample rate/dimensions, end-of-stream, and seek/discontinuity. Confirm SetTo() failure cannot leave application code believing an old decoder remains active. Test output-buffer calculations against large dimensions and channel counts.
For audio, compare decoded sample counts and channel layout against a trusted fixture; for video, validate row bytes and pixel format before handing frames to a renderer. Use known-good and intentionally damaged media, and record the exact decoder add-on used so an environment-dependent plugin result can be reproduced.
BMediaDecoder is a codec boundary. The application still owns parsing, packet lifetime, stream state, allocation, and presentation. Select the codec deliberately, negotiate output before allocating, preserve media headers, and treat every decode result as data to validate rather than as an automatic success.
Related:
- Haiku BMediaFormats: Format Mapping and Encoder Discovery
- Haiku BMediaFile and BMediaTrack: Container and Track Workflows
Sources: