From f0506f871596ed672d25d30fb8818cb11ad431ed Mon Sep 17 00:00:00 2001 From: Max Heimbrock <43608204+MaxHeimbrock@users.noreply.github.com> Date: Tue, 4 Aug 2026 11:21:46 +0200 Subject: [PATCH 1/4] Only advertise internal H264 decode formats if the decoder works MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GetSupportedFormats() unconditionally appended SupportedH264DecoderCodecs(), so desktop clients claimed 42001f/42e01f decode support in their SDP even though the internal FFmpeg decoder cannot initialize on desktop prebuilts (H.264 codec not compiled in). The SFU then selected Baseline for those subscribers, producing undecodable streams. H264Decoder::IsSupported() only reflects the WEBRTC_USE_H264 build flag — the missing FFmpeg codec surfaces first when Configure() fails at runtime. Probe Create()+Configure() once at factory construction and advertise the internal formats only when the probe succeeds. Platform-factory formats (VideoToolbox, MediaCodec, NVDEC) are unaffected. Co-Authored-By: Claude Fable 5 --- .../include/livekit/video_decoder_factory.h | 1 + webrtc-sys/src/video_decoder_factory.cpp | 32 +++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/webrtc-sys/include/livekit/video_decoder_factory.h b/webrtc-sys/include/livekit/video_decoder_factory.h index 4b1b0ed68..d7b78014b 100644 --- a/webrtc-sys/include/livekit/video_decoder_factory.h +++ b/webrtc-sys/include/livekit/video_decoder_factory.h @@ -35,5 +35,6 @@ class VideoDecoderFactory : public webrtc::VideoDecoderFactory { private: std::vector> factories_; + bool internal_h264_decoder_works_ = false; }; } // namespace livekit_ffi diff --git a/webrtc-sys/src/video_decoder_factory.cpp b/webrtc-sys/src/video_decoder_factory.cpp index a8ac19962..14264814a 100644 --- a/webrtc-sys/src/video_decoder_factory.cpp +++ b/webrtc-sys/src/video_decoder_factory.cpp @@ -41,6 +41,24 @@ namespace livekit_ffi { +namespace { +// H264Decoder::IsSupported() only reflects the WEBRTC_USE_H264 build flag; +// desktop prebuilts link an FFmpeg without the H.264 codec, which only +// surfaces when Configure() fails at runtime ("FFmpeg H.264 decoder not +// found"). Probe once so the SDP does not advertise decode support the +// internal decoder cannot deliver. +bool InternalH264DecoderWorks() { + if (!webrtc::H264Decoder::IsSupported()) + return false; + auto decoder = webrtc::H264Decoder::Create(); + if (!decoder) + return false; + webrtc::VideoDecoder::Settings settings; + settings.set_codec_type(webrtc::kVideoCodecH264); + return decoder->Configure(settings); +} +} // namespace + VideoDecoderFactory::VideoDecoderFactory() { #ifdef __APPLE__ factories_.push_back(livekit_ffi::CreateObjCVideoDecoderFactory()); @@ -55,6 +73,12 @@ VideoDecoderFactory::VideoDecoderFactory() { factories_.push_back(std::make_unique()); } #endif + + internal_h264_decoder_works_ = InternalH264DecoderWorks(); + if (!internal_h264_decoder_works_) { + RTC_LOG(LS_WARNING) << "Internal H264 decoder is unavailable, " + "not advertising its formats"; + } } std::vector VideoDecoderFactory::GetSupportedFormats() @@ -71,9 +95,11 @@ std::vector VideoDecoderFactory::GetSupportedFormats() for (const webrtc::SdpVideoFormat& format : webrtc::SupportedVP9DecoderCodecs()) formats.push_back(format); - for (const webrtc::SdpVideoFormat& h264_format : - webrtc::SupportedH264DecoderCodecs()) - formats.push_back(h264_format); + if (internal_h264_decoder_works_) { + for (const webrtc::SdpVideoFormat& h264_format : + webrtc::SupportedH264DecoderCodecs()) + formats.push_back(h264_format); + } formats.push_back(webrtc::SdpVideoFormat( webrtc::SdpVideoFormat::AV1Profile0(), From 3475c505df423658f21c99ba06cc0327129c9f4d Mon Sep 17 00:00:00 2001 From: Max Heimbrock <43608204+MaxHeimbrock@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:58:26 +0200 Subject: [PATCH 2/4] Gate the internal H264 fallback in Create() on the probe result GetSupportedFormats() no longer advertises the internal H264 formats when the probe fails, but Create() still handed back the broken FFmpeg decoder when the platform factories reject a format (e.g. a profile-level-id VideoToolbox does not match). Gate the fallback on the same probe so that path returns nullptr with a clear "No VideoDecoder found" error instead of a decoder that fails Configure() at runtime. Co-Authored-By: Claude Fable 5 --- webrtc-sys/src/video_decoder_factory.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webrtc-sys/src/video_decoder_factory.cpp b/webrtc-sys/src/video_decoder_factory.cpp index 14264814a..5cd297572 100644 --- a/webrtc-sys/src/video_decoder_factory.cpp +++ b/webrtc-sys/src/video_decoder_factory.cpp @@ -157,7 +157,8 @@ std::unique_ptr VideoDecoderFactory::Create( return webrtc::CreateVp8Decoder(env); if (absl::EqualsIgnoreCase(format.name, webrtc::kVp9CodecName)) return webrtc::VP9Decoder::Create(); - if (absl::EqualsIgnoreCase(format.name, webrtc::kH264CodecName)) + if (absl::EqualsIgnoreCase(format.name, webrtc::kH264CodecName) && + internal_h264_decoder_works_) return webrtc::H264Decoder::Create(); From 9090990afb3f8db1997e053fd1da2079859054be Mon Sep 17 00:00:00 2001 From: Max Heimbrock <43608204+MaxHeimbrock@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:38:10 +0200 Subject: [PATCH 3/4] changeset --- ...rtise_internal_h264_decode_formats_if_the_decoder_w.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/only_advertise_internal_h264_decode_formats_if_the_decoder_w.md diff --git a/.changeset/only_advertise_internal_h264_decode_formats_if_the_decoder_w.md b/.changeset/only_advertise_internal_h264_decode_formats_if_the_decoder_w.md new file mode 100644 index 000000000..3cbeef5a2 --- /dev/null +++ b/.changeset/only_advertise_internal_h264_decode_formats_if_the_decoder_w.md @@ -0,0 +1,8 @@ +--- +libwebrtc: patch +livekit: patch +livekit-ffi: patch +webrtc-sys: patch +--- + +Only advertise internal H264 decode formats if the decoder works - #1313 (@MaxHeimbrock) From ad71ad1e229af35299030da2976ab8263be85b98 Mon Sep 17 00:00:00 2001 From: Max Heimbrock <43608204+MaxHeimbrock@users.noreply.github.com> Date: Wed, 5 Aug 2026 09:47:56 +0200 Subject: [PATCH 4/4] Address review: const probe flag, rename, per-branch logs, braces Co-Authored-By: Claude Fable 5 --- .../include/livekit/video_decoder_factory.h | 2 +- webrtc-sys/src/video_decoder_factory.cpp | 25 +++++++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/webrtc-sys/include/livekit/video_decoder_factory.h b/webrtc-sys/include/livekit/video_decoder_factory.h index d7b78014b..3a3faf377 100644 --- a/webrtc-sys/include/livekit/video_decoder_factory.h +++ b/webrtc-sys/include/livekit/video_decoder_factory.h @@ -35,6 +35,6 @@ class VideoDecoderFactory : public webrtc::VideoDecoderFactory { private: std::vector> factories_; - bool internal_h264_decoder_works_ = false; + const bool internal_h264_decoder_works_; }; } // namespace livekit_ffi diff --git a/webrtc-sys/src/video_decoder_factory.cpp b/webrtc-sys/src/video_decoder_factory.cpp index 5cd297572..d22ae70da 100644 --- a/webrtc-sys/src/video_decoder_factory.cpp +++ b/webrtc-sys/src/video_decoder_factory.cpp @@ -47,19 +47,30 @@ namespace { // surfaces when Configure() fails at runtime ("FFmpeg H.264 decoder not // found"). Probe once so the SDP does not advertise decode support the // internal decoder cannot deliver. -bool InternalH264DecoderWorks() { - if (!webrtc::H264Decoder::IsSupported()) +bool IsInternalH264DecoderAvailable() { + if (!webrtc::H264Decoder::IsSupported()) { + RTC_LOG(LS_WARNING) << "Internal H264 decoder not compiled in " + "(WEBRTC_USE_H264 off)"; return false; + } auto decoder = webrtc::H264Decoder::Create(); - if (!decoder) + if (!decoder) { + RTC_LOG(LS_WARNING) << "H264Decoder::Create() returned null"; return false; + } webrtc::VideoDecoder::Settings settings; settings.set_codec_type(webrtc::kVideoCodecH264); - return decoder->Configure(settings); + if (!decoder->Configure(settings)) { + RTC_LOG(LS_WARNING) << "Internal H264 decoder failed to configure; " + "FFmpeg likely lacks the H.264 codec"; + return false; + } + return true; } } // namespace -VideoDecoderFactory::VideoDecoderFactory() { +VideoDecoderFactory::VideoDecoderFactory() + : internal_h264_decoder_works_(IsInternalH264DecoderAvailable()) { #ifdef __APPLE__ factories_.push_back(livekit_ffi::CreateObjCVideoDecoderFactory()); #endif @@ -74,7 +85,6 @@ VideoDecoderFactory::VideoDecoderFactory() { } #endif - internal_h264_decoder_works_ = InternalH264DecoderWorks(); if (!internal_h264_decoder_works_) { RTC_LOG(LS_WARNING) << "Internal H264 decoder is unavailable, " "not advertising its formats"; @@ -97,8 +107,9 @@ std::vector VideoDecoderFactory::GetSupportedFormats() formats.push_back(format); if (internal_h264_decoder_works_) { for (const webrtc::SdpVideoFormat& h264_format : - webrtc::SupportedH264DecoderCodecs()) + webrtc::SupportedH264DecoderCodecs()) { formats.push_back(h264_format); + } } formats.push_back(webrtc::SdpVideoFormat(