Skip to content
Open
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
84 changes: 84 additions & 0 deletions webrtc-sys/src/video_encoder_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,21 @@
#include "livekit/video_encoder_factory.h"

#include <algorithm>
#include <atomic>
#include <cctype>
#include <cstdlib>
#include <optional>
#include <string_view>
#include <utility>

#include "api/video/video_frame.h"
#include "modules/video_coding/include/video_error_codes.h"

#include "api/environment/environment_factory.h"
#include "api/video_codecs/sdp_video_format.h"
#include "api/video_codecs/video_encoder.h"
#include "api/video_codecs/video_encoder_factory_template.h"
#include "livekit/encoded_video_frame_buffer.h"
#include "livekit/objc_video_factory.h"
#include "livekit/passthrough_video_encoder.h"
#include "livekit/webrtc.h"
Expand Down Expand Up @@ -605,6 +610,80 @@ VideoEncoderFactory::CodecSupport VideoEncoderFactory::QueryCodecSupport(
return internal_factory_->QueryCodecSupport(format, scalability_mode);
}

namespace {

// Real encoders can never consume pre-encoded access units, but frames
// carrying an EncodedVideoFrameBuffer can still reach one in the window
// between stream startup and the sender's encoder selector switching onto
// the pass-through backend. Some platform encoders blind-cast native
// buffers (macOS ObjCVideoEncoder casts to ObjCFrameBuffer and retains a
// garbage pointer), so forwarding such a frame is a crash, not a graceful
// failure. Drop it instead: the selector switches shortly after, and the
// pass-through encoder requests a fresh keyframe when it takes over.
class EncodedFrameGuardEncoder final : public webrtc::VideoEncoder {
public:
explicit EncodedFrameGuardEncoder(
std::unique_ptr<webrtc::VideoEncoder> encoder)
: encoder_(std::move(encoder)) {}

void SetFecControllerOverride(
webrtc::FecControllerOverride* fec_controller_override) override {
encoder_->SetFecControllerOverride(fec_controller_override);
}

int InitEncode(const webrtc::VideoCodec* codec_settings,
const Settings& settings) override {
return encoder_->InitEncode(codec_settings, settings);
}

int32_t RegisterEncodeCompleteCallback(
webrtc::EncodedImageCallback* callback) override {
return encoder_->RegisterEncodeCompleteCallback(callback);
}

int32_t Release() override { return encoder_->Release(); }

int32_t Encode(
const webrtc::VideoFrame& frame,
const std::vector<webrtc::VideoFrameType>* frame_types) override {
if (livekit::EncodedVideoFrameBuffer::FromNative(
frame.video_frame_buffer().get())) {
static std::atomic<bool> logged{false};
if (!logged.exchange(true)) {
RTC_LOG(LS_WARNING)
<< "Dropping pre-encoded access unit sent to a non pass-through "
"encoder; waiting for the sender to switch onto the "
"pass-through backend";
}
return WEBRTC_VIDEO_CODEC_OK;
}
return encoder_->Encode(frame, frame_types);
}
Comment on lines +646 to +661

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.

It feels a bit heavy to re-implement an entire encoder to just redo one of its methods, and then only register that for pre-encoded. I am not familiar with this C++ code very much, but that's my initial read.


void SetRates(const RateControlParameters& parameters) override {
encoder_->SetRates(parameters);
}

void OnPacketLossRateUpdate(float packet_loss_rate) override {
encoder_->OnPacketLossRateUpdate(packet_loss_rate);
}

void OnRttUpdate(int64_t rtt_ms) override { encoder_->OnRttUpdate(rtt_ms); }

void OnLossNotification(const LossNotification& loss_notification) override {
encoder_->OnLossNotification(loss_notification);
}

EncoderInfo GetEncoderInfo() const override {
return encoder_->GetEncoderInfo();
}

private:
std::unique_ptr<webrtc::VideoEncoder> encoder_;
};

} // namespace

std::unique_ptr<webrtc::VideoEncoder> VideoEncoderFactory::Create(
const webrtc::Environment& env,
const webrtc::SdpVideoFormat& format) {
Expand All @@ -614,6 +693,11 @@ std::unique_ptr<webrtc::VideoEncoder> VideoEncoderFactory::Create(
env, internal_factory_.get(), nullptr, format);
}

if (encoder &&
BackendFromFormat(format) != VideoEncoderBackend::PreEncoded) {
encoder = std::make_unique<EncodedFrameGuardEncoder>(std::move(encoder));
}
Comment on lines +696 to +699

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.

🟡 Change ships without the required release note entry

This pull request modifies encoder behavior but does not include a changeset entry (webrtc-sys/src/video_encoder_factory.cpp:696-699), which the repository requires for every change so that affected packages get version-bumped.
Impact: The fix may be released without a version bump or documented note, so downstream consumers won't see it recorded in the release.

Missing changeset per AGENTS.md requirements

AGENTS.md states under "Documenting changes": "Every PR needs a changeset" and "Changeset must list any crates which need to be bumped stemming from the change." This PR only touches webrtc-sys/src/video_encoder_factory.cpp and adds no file under /.changeset, so the native fix (which affects the webrtc-sys crate and its dependents) is undocumented and will not trigger a version bump.

Prompt for agents
AGENTS.md requires every PR to include a changeset (under /.changeset) that lists the crates needing a version bump. This PR fixes a pre-encoded frame segfault in webrtc-sys/src/video_encoder_factory.cpp but adds no changeset. Add a changeset file (e.g. via `knope document-change` or manually in /.changeset) describing the fix and listing the affected crate(s) that should be bumped (at minimum webrtc-sys, plus any downstream crates such as livekit that need to be re-released with the fix).
Open in Devin Review

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


return encoder;
}

Expand Down
Loading