Haiku BTimeCode: Frame Labels, Drop-Frame Arithmetic, and Clock Boundaries
Use Haiku BTimeCode for frame-address labels without confusing drop-frame arithmetic, nominal rates, media performance time, or captured frames.
BTimeCode represents a timecode label in hours, minutes, seconds, and frames. It is useful for displaying and converting frame-address labels in media applications. It is not a clock, a timestamp service, or proof of the physical rate at which a camera or playback device captured frames. Haiku’s BTimeSource and a node’s performance time answer a different question: when media events are scheduled or considered to occur.
This distinction prevents a common class of synchronization defects. A label such as 01:00:00:00 identifies a position in a chosen timecode convention. It does not by itself establish wall-clock time, audio sample position, dropped packets, or a device’s measured oscillator rate. Store the timecode type alongside frame labels, and preserve the actual media time or frame index separately when the application needs precise synchronization.
Choose the convention before converting
The public timecode_type enum includes default, 100, 75, 30, two drop-frame variants, 25, 24, and 18-frame labels. The header comments associate 75 with CD, 30 with MIDI, 25 with PAL, 24 with film, and 18 with Super 8. The implementation treats the default as NTSC-style drop-frame behavior. These are conventions offered by the API, not a complete description of every modern broadcast or camera format.
get_timecode_description() fills a timecode_info record, including nominal frame divisor and drop-frame fields. Pass that description to conversion functions when a specific convention is required. Do not call conversion with a null description merely because a default looks convenient; the implementation uses a default NTSC-style path in that case. Explicit types make stored values and test expectations understandable.
timecode_info info;
status_t status = get_timecode_description(B_TIMECODE_25, &info);
if (status != B_OK)
return status;
int32 linearFrames;
status = timecode_to_frames(1, 2, 3, 4, &linearFrames, &info);
if (status != B_OK)
return status;
BTimeCode label;
status = label.SetType(B_TIMECODE_25);
if (status != B_OK)
return status;
label.SetLinearFrames(linearFrames);
This example illustrates conversion within one chosen convention. Before accepting user input, validate hours, minutes, seconds, and frame fields against the application’s supported range. In the current implementation, timecode_to_frames() performs the arithmetic without checking those fields and returns B_OK; do not rely on it as an input validator.
Understand drop-frame labels precisely
Drop-frame timecode adjusts label numbering to keep a nominal 30-frame count closer to real elapsed time for a fractional-rate convention. It omits selected frame numbers from the label sequence; it does not delete video frames from the recording. Haiku’s B_TIMECODE_30_DROP_2 and B_TIMECODE_30_DROP_4 values encode different drop counts in the current implementation. A non-drop-frame label and a drop-frame label can therefore refer to different elapsed-time interpretations even if their printed fields look similar.
Never store only the formatted string when the convention matters. Store the frame fields plus the enum or a stable application-level code. Include the type in logs, filenames, interchange records, and UI diagnostics. A bare string that lacks the convention is ambiguous, especially when values are passed between a PAL-oriented workflow and an NTSC-oriented one.
The current implementation uses nominal frame divisors and conversion logic that includes approximate handling for NTSC-style rates. Do not interpret the API’s Microseconds() result as a calibrated hardware measurement. For frame-accurate delivery, preserve a native frame counter and the stream’s actual time base, then use BTimeCode as a representation and conversion helper.
Keep frame count, microseconds, and display separate
The class provides setters and getters for linear frames, microseconds, and the individual label fields. Changing type changes the convention used to interpret the label; it does not resample a media file. Arithmetic operators are convenient for label-domain operations, but the caller still needs to ensure operands share a compatible type and that overflow or negative values have a defined application policy.
For display, GetString() writes a formatted label into a caller-provided buffer. The header explicitly requires at least 24 bytes. Allocate that size or larger, and do not use an undersized fixed array based on a shorter observed string. If an application has a different localization or separator policy, format from the numeric fields instead of modifying the internal convention.
When converting media positions, choose a canonical internal unit first. The public conversion API and BTimeCode::LinearFrames() use int32, so they cannot represent an arbitrarily long frame count; preserve a wider signed frame index and rational time base in applications that need longer timelines. A video editor may keep signed 64-bit frame indices. An audio tool may keep sample positions. Convert to BTimeCode at the display or interchange boundary. This prevents repeated frame-to-microsecond-to-frame conversions from accumulating rounding discrepancies.
Be especially careful with long recordings and seeks near a rate boundary. Convert from the canonical frame index once for display and compare the returned linear-frame value in tests; do not repeatedly add one displayed second and assume every label maps to the same elapsed duration. If negative offsets are meaningful in a timeline, keep the signed offset in the application model and define how it is formatted, because a conventional nonnegative timecode display may not preserve that sign.
Interchange and validation rules
Define whether an external field is drop-frame or non-drop-frame in the interchange schema. Parse separators and fields explicitly, validate the frame number against the selected nominal convention, and reject values outside the app’s supported duration range. Keep a reversible conversion test for known boundary labels around the start of each minute and around the exception interval used by a drop-frame scheme.
When exporting, state whether values are timecode labels, linear frame numbers, or media timestamps. Those are not interchangeable quantities. If importing an ambiguous text value, require a selected convention or preserve it as unparsed input rather than guessing based on punctuation. If converting between formats, document whether the code preserves frame count, elapsed time, or wall-clock alignment, because one conversion cannot always preserve all three.
Failure-oriented tests
Test each supported type with zero, a value near one minute, a value at an hour boundary, and the appropriate drop-frame exception boundary. Test encode-decode round trips and ensure the type survives persistence. Include negative frame input if your application can produce it, maximum supported duration, malformed fields, and a caller buffer smaller than 24 bytes in a wrapper-level test. Verify that no such malformed display path writes beyond its buffer.
For synchronization tests, compare the label against an independent frame count and media time source. Do not use a visually plausible timecode overlay as the sole evidence that audio and video stay synchronized. A correct label can coexist with a drifting clock, and a correct performance clock can coexist with a mislabeled frame convention.
When integrating with an external editor or camera, keep a small fixture of known input labels and expected frame positions from that system. Test the exact import and export separators, drop-frame marker conventions, and rounding policy. If the external format uses a rational frame rate the Haiku enum does not represent directly, preserve that rational value outside BTimeCode and avoid rounding it into a superficially similar enum without documenting the loss.
Build boundary fixtures from independent arithmetic rather than asking the same conversion function to generate its own expected answer. Include labels just before and after a dropped-label minute and confirm that the external system agrees on the linear frame index. If conversion differs, capture whether the disagreement is a convention mismatch, rounding choice, or genuine off-by-one defect before changing stored media positions.
Acceptance criteria
Accept a BTimeCode integration when every value carries an explicit convention, parsing validates user ranges, display uses a sufficiently large buffer, and scheduling uses an appropriate media clock rather than a label. Verify drop-frame boundaries and preserve a canonical frame count or time base for precision-sensitive workflows.
BTimeCode makes frame labels easier to manipulate, but it does not define a capture device’s timing, repair drift, or replace the Media Kit performance-time model.
Related:
- Haiku BTimeSource: Media Performance Time and Clock Drift
- Haiku BMediaRecorder: Capture a Media Source with Bounded Callbacks
Sources: