Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ add_library(livekit SHARED
src/ffi_handle.cpp
src/ffi_client.cpp
src/ffi_client.h
src/length_prefixed_access_unit_parser.cpp
src/livekit.cpp
src/logging.cpp
src/local_audio_track.cpp
Expand Down
103 changes: 103 additions & 0 deletions include/livekit/length_prefixed_access_unit_parser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright 2026 LiveKit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an “AS IS” BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

#include <cstddef>
#include <cstdint>
#include <optional>
#include <vector>

#include "livekit/encoded_video_source.h"
#include "livekit/video_codec.h"
#include "livekit/visibility.h"

namespace livekit {

/// @brief Splits a length-prefixed H.264 (AVC) or H.265 (HVCC) NAL unit
/// stream into access units ready for EncodedVideoSource::captureFrame.
///
/// This is the framing of `stream-format=avc3` (H.264) and `hev1` (H.265),
/// with parameter sets in-band. Each access unit is converted to Annex-B,
/// which the pre-encoded passthrough requires.
///
/// Push bytes as they arrive (any chunking), then drain complete access
/// units with next(). Call flush() once at the end of the stream. Not
/// thread-safe.
class LIVEKIT_API LengthPrefixedAccessUnitParser final {
public:
struct Options {
/// Size of each big-endian NAL length prefix, 1 to 4 bytes
/// (`lengthSizeMinusOne + 1` from `avcC`/`hvcC`; 4 for most encoders).
std::uint8_t nal_length_size = 4;
/// Complete each access unit as soon as its first slice arrives, instead
/// of when the next access unit begins.
///
/// The end of an access unit is otherwise only known from the NAL that
/// starts the next one, which holds every picture for a full frame
/// interval unless the encoder terminates access units with an AUD.
/// Enable this only for streams with one slice per picture, which is
/// what hardware encoders typically emit.
bool single_slice_flush = false;
/// Fill Frame::metadata from an LKTS `user_data_unregistered` SEI ahead
/// of the slice (the packet-trailer convention of the LiveKit Go SDK).
bool frame_metadata_from_sei = false;
};

/// @throws std::invalid_argument if the codec is not H.264 or H.265, or
/// nal_length_size is not 1 to 4.
explicit LengthPrefixedAccessUnitParser(VideoCodec codec);
/// @throws std::invalid_argument if the codec is not H.264 or H.265, or
/// nal_length_size is not 1 to 4.
LengthPrefixedAccessUnitParser(VideoCodec codec, Options options);

/// @brief Appends stream bytes.
void push(const std::uint8_t* data, std::size_t size);

/// @brief Returns the next complete access unit, if one is buffered.
///
/// Frame::width/height are left at zero (the source resolution) and
/// Frame::timestamp_us at zero (capture time).
/// @throws std::runtime_error on malformed input, or when an access unit
/// exceeds 32 MiB.
std::optional<EncodedVideoSource::Frame> next();

/// @brief Returns the remaining buffered NAL units as the final access unit.
/// Call repeatedly until it returns nullopt.
/// @throws std::runtime_error on a truncated final NAL unit.
std::optional<EncodedVideoSource::Frame> flush();
Comment on lines +42 to +81

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Public parser omits required API tags

The new Options struct lacks @brief; constructors lack @param, and next() and flush() lack @return. These omissions violate the repository's public API documentation rules.

Learn more

The repository's public API documentation rules require @brief for structs and methods, @param for parameters, and @return for non-void methods. The new Options struct has no @brief. Both constructors lack @brief and @param, push lacks @param, and next/flush lack @return.

Example: A consumer viewing the generated reference for push cannot find the required documentation for its data or size arguments.

Recommended fix: Add the missing Doxygen tags to each new public declaration, including both constructor overloads and the push arguments.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.


private:
struct NalRange {
std::size_t start;
std::size_t end;
};

void scanPending();
std::optional<std::size_t> splitIndex() const;
EncodedVideoSource::Frame takeAccessUnit(std::size_t nal_count);

VideoCodec codec_;
Options options_;
std::vector<std::uint8_t> pending_;
// Complete NAL units of the access unit being assembled, as payload
// ranges (after the length prefix) into pending_.
std::vector<NalRange> nals_;
// Offset of the first unparsed length prefix in pending_.
std::size_t scan_cursor_ = 0;
};

} // namespace livekit
1 change: 1 addition & 0 deletions include/livekit/livekit.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "livekit/build.h"
#include "livekit/e2ee.h"
#include "livekit/encoded_video_source.h"
#include "livekit/length_prefixed_access_unit_parser.h"
#include "livekit/local_audio_track.h"
#include "livekit/local_participant.h"
#include "livekit/local_track_publication.h"
Expand Down
Loading
Loading