This repository builds and consumes PulseBeam's pinned WebRTC artifacts. It owns
the private CXX bridge, small native adapters, artifact metadata and selection,
and the low-level Rust API used by pulsebeam-libwebrtc.
Bump [package].version in Cargo.toml, update the root package version in
Cargo.lock, and commit. Then create and push the matching source tag:
git tag vX.Y.Z
git push origin vX.Y.ZThat push is the release operation. CI builds, qualifies, audits, and publishes
all five OS groups independently, creates their derived vX.Y.Z-<os> tags, and
checks real Git consumers. Do not create OS tags, refresh manifest fixture
hashes, edit artifacts.lock.json, or upload assets yourself. Build metadata
and release locks are generated from the tagged sources. The source tag must
match the crate version because consumers derive their release URLs from it.
Source checks also run on pull requests and pushes to main, so source-level
failures can be caught before tagging. Native qualification runs on source tags
or a manual qualification run, not ordinary branch pushes. For publication
retries, use Re-run failed jobs on the original tag-push Actions run, not
Run workflow. The latter only qualifies and never publishes. Publication
reuses the original bundle and accepts only identical existing bytes; workflow
artifacts are retained for one day. If those bytes are no longer available or
conflict with an existing asset, fix the cause and release a new version.
Never move an existing source or OS tag.
The complete local interface is:
just check
just ci-preflight
just ci --help
just ci-release --help
just verify-artifact <archive> <sha256>
just build <core|native> <target>
just refresh-cxxjust ci-preflight checks every release CLI option used by the workflows
against the locally installed gh (also run on the GitHub runner before the
Linux image build). It needs neither network nor a GitHub token. Run it before
starting a long local build; unlike just check, it requires gh on the host.
The single release.yml workflow entry point calls repository-owned commands
for validation, artifact audit, result qualification, and release publication.
Linux retains its golden job graph; four independent calls to os-release.yml
run the macOS, Windows, iOS and Android graphs. Each OS waits only for the common
source checks and its own build, audit, runtime and consumer gates, not another
OS's qualification or publication. YAML retains dependencies, runner selection,
credential transport, caching, artifact transfer and GitHub attestation.
Run the same operations locally with just ci <task> ... (see just ci --help),
for example just ci revision <40-character-revision> or just ci audit linux release-input release-input/SHA256SUMS release-input/LINUX-RELEASE-MANIFEST.json.
just ci-release --help lists publication stages. The release commands require
an authenticated gh and contact GitHub; the local tests fake the CLI and
never publish a release. Bundle preparation and audit run on the host without
building a Podman image. Linux and Android producers use the repository-owned
Linux image; Windows and Apple builds run on their matching native hosts.
Released-consumer matrices use the source tag as a Cargo Git dependency and
require every target and flavor in their OS group without a local override.
A manual workflow_dispatch runs the complete pre-publication qualification
paths, including Linux x86_64 ASan, but cannot create tags or publish releases.
Publication jobs require a canonical source-tag push.
Non-Linux jobs use python -m tools.platform_ci for fixed target/flavor matrices,
native SDK setup and producer orchestration. They pin Rust to 1.96.0, matching
the Linux image. Windows uses an installed supported VS 2022/2026 developer
environment with Windows SDK 10.0.26100.0; no restricted Google Windows
toolchain download is required. Android consumer jobs install the source-pinned
NDK; runtime jobs additionally install platform 26, build tools 35.0.0 and the
API 26 x86_64 emulator image. An initialized, already licensed Xcode can download
and import the exact ARM64 iOS 18.0 runtime when absent. Setup never implicitly
accepts SDK licenses or substitutes a newer minimum-runtime baseline. Missing
licenses, SDK components, acceleration, matching hosts or runner capacity fail
qualification rather than waiving a gate. Public runner labels alone are not
proof of account entitlement, capacity or completed platform qualification.
just check is the fast, offline source/control-plane gate once the pinned Rust
dependencies are cached. It does not require or extract native bytes. Native
runtime, ABI, resolver, and cold-consumer proof is explicit: pass an untracked
core Linux x86_64 archive and its trusted lowercase SHA-256 to just verify-artifact. The archive can be built at this revision with just build core linux-x86_64, using the producer-recorded SHA-256, or be an immutable
release/workflow artifact accompanied by its matching trusted audit checksum.
Non-Linux producer jobs type-check the selected Rust target and flavor before
source acquisition, without bypassing the later real link and runtime gates.
Apple jobs also parse every Bash recipe with /bin/bash before downloading
WebRTC, including the Bash 3.2 shipped by macOS. Local syntax reproduction can
select an older shell with PULSEBEAM_TEST_BASH=/path/to/bash python3 -m unittest tests.test_release_shell_syntax.
The tested Rust-only contract excludes downstream C and C++ compilation. Rust
still invokes the platform linker, and the target must provide the system
libraries and SDK inputs declared by the artifact manifest. Using a compiler
driver such as cc as Rust's linker is allowed; invoking that driver to compile
C or C++ source is not. The metadata check separately rejects a custom build
target on the local cxx runtime and any cc or cxx-build package reachable
from the bridge dependency graph. These checks are reusable by later artifact
download, cache, offline, and release consumer jobs.
just build validates the flavor, target, and host before it synchronizes the
pinned sources, configures and compiles WebRTC, exports the static closure, and
performs C++ and Rust compile/link consumer checks. Linux x86_64 also builds
and loads the native_dynamic shared-library consumer, constructs a peer with
stock PCM/video sources, and retires its native graph. The Linux adapter objects
are position-independent so the same archive supports Rust executables and
native SDK dynamic libraries. This load check does not replace transported-media
runtime qualification. After a failed
gclient sync, it removes only incomplete dependency Git checkouts without a
usable HEAD before retrying; a real upstream timeout can still fail the build.
Source sync has a Python-owned 20-minute deadline on every host, including
macOS without GNU coreutils. Windows batch launches preserve native quoting and
are checked on the Windows producer before source acquisition. The Linux image
provides the unversioned ld.lld required by the ARM64 LLVM source bootstrap.
Every flavor/target artifact carries the same generated bridge and portable
adapter sources, compiled with that job's target toolchain and ABI configuration.
On a canonical version-tag push, Linux publication requires the Rust runtime suite for both flavors on matching x86_64 and ARM64 hosts. ARM64 execution runs before archive upload on its build host; x86_64 also has separate artifact-only runtime jobs. Linux x86_64 AddressSanitizer jobs rebuild both flavors and exercise the lifetime-sensitive environment, provider, peer, channel, video, and teardown tests; only their failure logs are retained, never their instrumented archives. macOS and Windows execute the complete Rust and SDK/bridge C++ runtime suites on every matching build host before archive upload. Mobile OS releases require both flavors to execute the headless minimum baseline scenarios described below; physical-device targets remain compile/link qualified. Each OS independently audits, qualifies, prepares and immutably publishes its own closed release group.
just refresh-cxx is the explicit networked maintenance operation for the
Rust-only CXX runtime. It downloads the version recorded in
vendor/cxx/provenance.json, verifies the crates.io package checksum before
touching the import, copies the selected upstream runtime, header, C++ runtime,
and license files byte-for-byte, and regenerates the packaging-only manifest
from tools/cxx/Cargo.toml.in. just check verifies the recorded inventory,
per-file digests, generated overlay, exact component versions, and dependency
graph without accessing the network.
Artifact builds obtain cxxbridge-cmd from the exact published package also
recorded in vendor/cxx/provenance.json. The package checksum and reported
version are verified before bridge generation. Each manifest records both CXX
package checksums plus the imported runtime/header, bridge definition, and
generated C++ output digests; export rechecks the packaged headers against that
metadata before consumer smoke tests run.
Schema 4 manifests carry the canonical native configuration as well as its SHA-256, including compiler, GN/protobuf, floating-point, bridge and source identities. Consumers reject inconsistent configurations, stale adapters, altered static archives and missing or altered inventoried notices. These checks detect mismatches; they do not authenticate a local artifact supplied by its owner. Older manifests require a matching rebuild, not relabeling.
Android kits also export lib/libunwind.a for the explicit -lunwind dependency
in Rust's Android standard library. The manifest records it as unwind_archive
with a separate SHA-256; audits and consumers verify both native archives, and
Cargo links the bundled unwinder statically. The producer's Chromium NDK input
is stripped of compiler drivers and runtimes, so setting its sysroot alone does
not provide Rust's unwinder dependency.
Linux ARM64 and Android ARM64 kits also merge the pinned Clang target's
compiler-rt builtins into libwebrtc.a. This supplies libyuv's SME ABI helpers,
including __arm_tpidr2_save and their runtime dependencies, even when Rust's
link suppresses the compiler driver's default libraries. SME remains enabled;
consumers do not need the producer's Clang installation to find those helpers.
Apple archives are reindexed as GNU archives before host protoc linking and
artifact merging. Only stray Darwin symbol-index members (__.SYMDEF variants)
are removed. Thin archive paths and duplicate object basenames are retained,
and host protoc's architecture is still checked using the exact listed member.
For development against a matching extracted artifact:
PULSEBEAM_WEBRTC_SYS_ARTIFACT_DIR=.work/package/webrtc-core-linux-x86_64 cargo testConsumers depend on the canonical source tag v<crate version>. Without a
local artifact, build.rs selects exactly one OS release using Cargo TARGET,
not the build host: v<crate version>-linux, -macos, -windows, -android,
or -ios. The native feature selects the flavor within that OS. It fetches
only that release's artifacts.lock.json, checks its bridge identity, exact OS
scope and tag-bound asset URLs, then verifies the selected archive's SHA-256
before safe extraction. There is no root-tag or other-OS fallback. This layout
applies to future matching source/resolver versions; existing source tags keep
their original resolver and release layout.
The release lock is trusted from GitHub over HTTPS and cached by exact OS tag; unlike the archives, its digest is not pinned in the tagged source. A replaced release asset and lock could therefore change what a cold consumer receives. Do not move tags or replace release assets.
The cache defaults to $CARGO_HOME/pulsebeam-webrtc-sys/artifacts-v1; set
PULSEBEAM_WEBRTC_SYS_CACHE_DIR for a hermetic cache. CARGO_NET_OFFLINE=true,
Cargo's --offline, or PULSEBEAM_WEBRTC_SYS_OFFLINE=true disables downloads;
for offline builds, first populate both the release lock and selected archive
while online. Both archive and extraction caches are isolated by the exact OS
release tag. The checked-in lock is an unpublished producer/candidate template
with all 18 selections; it is not used by normal Git consumers. Each released
lock makes available exactly both flavors of every target in its OS:
| Scope | Artifact targets |
|---|---|
linux |
linux-x86_64, linux-arm64 |
macos |
macos-x86_64, macos-arm64 |
windows |
windows-x86_64 |
android |
android-x86_64, android-arm64-v8a |
ios |
ios-arm64, ios-simulator-arm64 |
An OS release that has not published fails with its exact version/OS identity.
A missing release is not cached, so retrying after publication can succeed.
Independent availability is not a promise of atomic publication across OSes.
Local candidate artifacts remain usable before publication, including offline,
through PULSEBEAM_WEBRTC_SYS_ARTIFACT_DIR. Target declarations and scoped
metadata alone do not establish compile/link or runtime support.
Producer audit and bundle commands accept these OS scopes. complete remains
a local full-matrix audit mode, not a publishable OS bundle. Bundle preparation
re-audits the exact archive set; publication accepts matching partial drafts,
rejects unexpected or conflicting bytes, and never repairs incomplete published
releases by replacing assets. just ci-release ensure-tag --source-tag v<version> --scope <os> --commit <sha> creates only a missing derived tag at the source
commit, accepts an identical existing tag, and rejects a different commit.
Generate ignored clangd inputs from a matching extracted Linux x86_64 kit and the pinned SDK checkout:
python3 tools/native_compile_commands.py \
--kit .work/package/webrtc-core-linux-x86_64 \
--checkout-src .work/checkout/src --checkThe SDK's Chromium Clang, Bullseye sysroot, exported definitions and bundled
libc++ headers are required. --check parses the real codec adapter and verifies
that an intentionally invalid SDK member produces a type diagnostic, rather
than a missing-include cascade. Omit it to generate editor inputs only. This does
not replace native compile/link or runtime validation.
Linux defaults to rtc_enable_protobuf=true. Set
PULSEBEAM_WEBRTC_PROTOBUF=false when building either flavor to select the
pinned no-protobuf configuration. Use a separate WEBRTC_WORK and WEBRTC_DIST
for configurations that must coexist. The manifest records the selection. The
built-in event logger writes native encoded events with protobuf enabled; with
protobuf disabled the upstream factory produces its no-op logger and rejects
output start. Ordinary event values, caller-provided loggers, RTP-extension
helpers, software codecs and APM remain available in both configurations.
AddressSanitizer Linux kits require the pinned Clang/lld linker and sanitizer
runtime, including when linking a Rust-only consumer. Set
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER to the checkout's
third_party/llvm-build/Release+Asserts/bin/clang++, and use
RUSTFLAGS="-C link-arg=-fsanitize=address -C link-arg=-fuse-ld=lld".
A host GNU linker cannot consume the LLVM objects in those kits. This does not
require downstream C or C++ compilation. With
PULSEBEAM_WEBRTC_SANITIZER=address, just _runtime-test uses the pinned producer
toolchain for both C++ smokes and Rust links. Ordinary Linux runtime qualification
uses the host compiler without the producer checkout or sysroot.
just _runtime-test <core|native> linux-x86_64 <archive> executes the SDK C++
consumer, the real Rust-linked companion, and the Rust runtime suite against the
matching archive. The SDK consumer exercises every advertised built-in audio
format, VP8/VP9/AV1/H264 encode/decode, native video metadata, processed APM PCM,
and configuration-specific event/RTP helpers. Building alone only compile-links
these consumers. Physical device behavior remains explicitly opt-in and is not
certified by software codec or APM execution.
Session<R> and SessionBuilder<R> use one native resource/operation graph.
The outer harness selects ThreadedRuntime or obtains ManagedRuntime from
ControlledWorld::runtime(), supplies endpoint services, and controls pumping
and virtual time. Application construction and operations do not branch on the
runtime or require a simulated source profile. ProductionSession and its
builder/configuration names remain compatibility aliases for the threaded
instantiation, not a second implementation.
Session<ThreadedRuntime> is move-only Send, non-Sync. It constructs
headless peers on system time and retains WebRTC's existing network, worker and
signaling threads. There is no additional application owner executor. All mutations require &mut self; peers, channels,
encoded sources, RTP handles, DTMF senders, transports and sinks stay private. Copyable session IDs and
owned event/frame values are the application boundary, so an async actor may
retain the session across awaits and migrate between executor threads.
Final drop from an SDK callback moves the entire private resource graph to a
cleanup caller, allowing the callback to return before its native roles retire.
That cleanup is asynchronous; ordinary caller-thread drop remains synchronous.
The cleanup caller does not execute application operations or controlled work.
The current provider implementation reserves one single-use production cleanup
worker after native factory construction. It accepts only a final-owner handoff
from native execution/callback contexts, never application commands. This avoids
waiting on or joining the SDK role that is executing that callback. Ordinary
caller-thread destruction remains synchronous and joins the unused worker after
native teardown. Callback-triggered Drop returns after handoff; native dependency
release finishes asynchronously. Controlled graphs never use this worker, and
failed reservation rejects actor construction rather than falling back. These
lifetime paths passed the final Linux x86_64 normal core/native and ASan
core/native non-device suites. Exact evidence and still-unqualified live capture
gates are recorded in
docs/provider-verification-plan.md.
Session<ManagedRuntime> remains creator-sequence-bound and reserves no cleanup
worker. Its SDK environment aliases retain native retirement tickets, never an
Rc driver owner. Last releases may signal from another thread, but actual
logger/queue and retained provider destruction stays on the creator. Release
owning environment aliases and dependent products before exiting the creator
runtime's scope. Final scope teardown drains native-ready lifecycle work and
waits for outstanding aliases without advancing time. Do not retain an alias
while waiting for that scope to finish.
Headless defaults select stock software audio/video factories and raw PCM/video
in both runtimes. .audio_processing(settings) optionally selects native PCM
processing. Prerecorded Opus input requires the explicit .encoded_audio()
adapter; optional SessionConfig::video_format selects direct encoded H.264 or
VP8 input and encoded-only reception. Those adapters' own constraints do not
restrict stock raw sources. Platform devices additionally require the native
feature and explicit .native_audio(true) or capture selection. Explicit native
backends and arbitrary providers that bypass runtime hooks are outside the
managed deterministic guarantee, never an implicit fallback. See
docs/audio-runtime-contracts.md.
The same application constructor can be used by either outer harness:
use pulsebeam_webrtc_sys::*;
fn application<R: SessionRuntime>(runtime: R) -> Result<Session<R>, PeerError> {
let mut session = Session::with_runtime(runtime, SessionConfig::default())
.audio_processing(AudioProcessingConfig::default())
.build()?;
let source = session.create_pcm_source()?;
let peer = session.create_peer(PeerConfiguration::default())?;
session.publish_pcm(peer, source, "voice", RtpTransceiverDirection::SendOnly, None)?;
Ok(session)
}
let threaded = application(ThreadedRuntime)?;
drop(threaded);
let world = ControlledWorld::acquire(17, std::time::Duration::from_secs(7))?;
let network = world.create_network()?;
let endpoint = network.register_endpoint("192.0.2.1".parse::<std::net::IpAddr>()?)?;
let managed = application(world.runtime()?.with_endpoint(&endpoint)?)?;
world.pump(256); // Harness work, not an application mode branch.
drop(managed);
# Ok::<(), Box<dyn std::error::Error>>(())Standalone consumers use the same runtime selection without constructing a peer factory or capturing an environment from a provider callback:
use pulsebeam_webrtc_sys::*;
fn standalone<R: SessionRuntime>(runtime: &R)
-> Result<AudioProcessingModule, Box<dyn std::error::Error>>
{
let mut factory = runtime.environment_factory()?;
factory.set_field_trials(FieldTrials::parse("MyFeature/Enabled/")?)?;
let environment = runtime.create_environment(&factory)?;
Ok(AudioProcessingModule::builtin(&environment, AudioProcessingConfig::default())?)
}Configure the SDK factory first, then use runtime.create_environment to attach
runtime ownership to the completed selection. Codec and logging factories take
that same environment. Context-free native constructors, such as
AudioMixer::stock, need no runtime parameter. Keep dependent products and SDK
environment aliases inside the caller-owned runtime scope; no executor is added
to make escaped managed products independently progress.
The current common-path proof covers construction, native capabilities, trackless receivers, offer creation, stock logging and dependent teardown. It does not yet qualify all built-in software codec/processing backends for caller-controlled deterministic media replay or establish complete public SDK coverage. Low-level captive handles cannot be imported across session owners. Process-global real/managed hook exclusivity remains.
For raw video, create_raw_video_source, publish_video and push_raw_video
use the selected video encoder. Selecting only an encoder or decoder factory
keeps the stock default for the omitted side; an explicit factory's null or
failed product is never replaced by a stock product. Supply actual capture timestamps mapped into
clock_timestamp's native timeline, not elapsed time from an unrelated epoch.
replace_video_source accepts either raw or supported encoded video sources.
After negotiation, select attach_pcm or attach_raw_video on the receiving
endpoint and consume owned values with try_pcm_frame or
try_raw_video_frame. Raw retention queues require explicit polling; in headless mode a
PCM poll may pull one native playout block. Readiness alone does not drive
headless audio playout or guarantee raw-video notification. One explicit
observation mode is active per receiver. attach_raw_and_encoded_video combines
native-decoded I420 with the existing passive ordered encoded observer on that
same receiver; drain try_raw_video_frame and try_video_frame independently.
It keeps the configured decoder and forwards originals unchanged. Encoded-only
receive factories are refused, without reserving the encoded observer first.
Its dropped_frames count sums both observer queues, not native playout losses.
Use dropped_raw_video_frames and dropped_encoded_video_frames to distinguish
which observer lost retained output; do not infer decoder loss from either.
close_media_sink detaches the active mode before reattachment or an audio-mode
switch; encoded observation retains its existing receiver-lifetime restriction.
Returned frames own their data across source replacement, sink closure, session
migration and shutdown.
For complete decoded callback facts before polling retention, opt into
attach_raw_video_with_observer or
attach_raw_and_encoded_video_with_observer (local tracks use
attach_sink_with_observer). This observes the same native sink with unchanged
wants and queue limits, not an additional decoder or sink. Each synchronous
callback receives owned bounded I420 pixels or an explicit conversion error,
plus the host monotonic callback-entry observed_at and SDK global monotonic
native_observed_at_us. In an exclusive ControlledWorld, the latter uses its
actual controlled clock even outside a host runtime context. Map clock domains
explicitly before freshness/cadence comparisons; custom environment clocks need
not equal the SDK global clock. Neither timestamp reports UI presentation, and
the callback supplies no SSRC. Do not block or synchronously control the SDK from
this callback. Panics retire it and set raw_video_observer_failed (local
VideoSink::observer_failed); polling retention loss remains separate. Observer
state is released after native removal, even when closed sink handles remain
alive to drain accepted control notifications.
Local raw-video sinks expose native DiscardedFrame and ConstraintsChanged
notifications through VideoSink::try_next_event; actor receivers use
try_raw_video_sink_event. Their separate eight-event FIFO retains the newest
notifications, with saturating overflow counts from dropped_events or
dropped_raw_video_sink_events. Constraint optionals and floating values are
copied unchanged. Local accepted notifications remain readable after sink close.
These are native callbacks, not inferred frame loss or source-state changes;
frame polling and frame-retention loss counters remain separate. VideoSourceView
forwards refresh, encoded-output keyframe and constraint requests on the existing
native worker; VideoTrack also forwards refresh requests. Actor equivalents are
request_video_source_refresh, generate_video_source_key_frame,
process_video_source_constraints and request_video_track_refresh. These are
native void requests, not promises of a frame or applied limits. The pinned push
source inherits SDK no-ops; the RTP source forwards keyframe requests while its
native callback remains installed. Constraints are not cached or normalized.
VideoSourceView::attach_encoded_output registers an independent native recordable
encoded-output sink when the source reports support. It is separate from the
receiver transformer slot. RecordableVideoFrame preserves encoded buffer
presence/bytes, codec, resolution, keyframe, optional rotation/color space and
render time; it does not invent RTP metadata absent from that interface.
Source encoded sinks share close state across clones and retain at most eight
frames/64 MiB, with intentional loss counts. Production sessions keep these
registrations captive behind SessionVideoSourceEncodedSinkId.
Drain try_peer_event, try_channel_event, registered DTMF/DTLS/SCTP observer
queues and any attached encoded sinks, then await ready() (or call poll_ready from an executor). Readiness coalesces
activity, may be spurious and never enters the engine or drives a clock. A
single actor waiter is supported; its waker must schedule the actor, not run
engine operations inside a native callback. Native event locks are released
before waking. Each peer admits at most 64 asynchronous operations until their
terminal outcomes are consumed; excess admission fails synchronously with
ResourceExhausted. Closing retains peer observation identities so accepted
pending operations still produce one terminal outcome. Shutdown is idempotent;
owned outputs already returned remain valid. Transport IDs refer to retained
native public facades; each ICE-internal operation reacquires its revocable
pointer on the network role and reports InternalUnavailable after native
teardown. DTLS information and certificate copies are independent owned values;
SCTP snapshots contain the exact callback-time DTLS ID. Repeated endpoint and
transport lookups share actual native identities and observer queues, not string
ID joins. Explicit release_*_transport and release_dtmf_sender calls retire
an individual actor entry and invalidate copies of that ID. close_source(id) idempotently
retires an individual producer and removes its actor-owned entry; existing
native track references can remain until the sender is detached/stopped.
Data channels expose native push delivery and send backpressure. The owned
message inbox is bounded by DataChannel::receive_queue_limits() (also available
on ProductionSession). Newest deliveries exceeding its message or byte limit
are dropped before copying and reported through DataChannelEvent::MessagesDropped.
Overflow does not fabricate a native error or close the channel. Aliases share
one observer, inbox and send-completion budget. Consuming events does not return
SCTP receive-window credit. No binding-only SID-retirement or substitute
receive-credit policy is imposed. Layered and
opaque media remain unqualified; this ownership API alone is not the entire
agent-ready binding contract.
Every supported target has two artifacts built from the same pinned source and with the same public WebRTC API:
coreis an optimized, static, headless C++ engine for simulations, servers, and custom media pipelines. It retains WebRTC transport and media logic, built-in audio codecs, VP8, VP9, AV1, and caller-supplied devices, media sources, sinks, clocks, queues, sockets, and codec factories. It excludes platform capture, playback, and window-system integrations where upstream build switches allow.nativeadds the upstream platform audio, camera, screen/window capture, and native codec paths available for the target. Rendering remains a downstream responsibility so clients can use one consistent implementation across platforms.
Linux x86_64 build configuration enables bundled OpenH264 encoding and FFmpeg
H.264 decoding in both flavors. Explicit VideoEncoderFactoryHandle::builtin()
and VideoDecoderFactoryHandle::builtin() selections expose the artifact's
native discovery/support. Other target configurations still disable bundled
H.264. Codec availability comes from the matching native artifact, not from a
Rust source update alone.
Headless audio sources accept owned, interleaved signed 16-bit PCM with native
sample_rate_hz / 100 samples per channel, at 100..=48000 Hz and 1, 2, 4, 6 or 8
channels. This includes 44.1 kHz without padding or truncation. The legacy
AudioPcmFrame::timestamp_us field is not an absolute capture clock. Use
AudioSource::capture_time_now to read the supported native timeline and
push_frame_at to supply a mapped actual capture instant, at native millisecond
precision. Custom non-controlled clocks have no implicit mapping. Capture
admission also protects the reached signed capture-difference arithmetic on
native sender resume. Native packetization and playout keep their own timing
semantics; successful source delivery is not an encode or wire acknowledgement.
AudioTrack::attach_sink exposes an owning local PCM queue with exact samples,
rate, channel geometry and optional capture milliseconds. Its collector implements
both native callback overloads: legacy callbacks have no capture timestamp;
timestamped callbacks preserve the full optional signed millisecond value. It does not expose the
encoded adapter's private carrier as PCM. DecodedAudioFrame::capture_time_ms
preserves the full native optional signed value; capture_time_us is a checked
projection and can be absent when that unit cannot represent it. Zero remains
distinct from absence. RtpSender::set_audio_track replaces or detaches one
source without rebuilding the sender; native renegotiation semantics remain
unchanged. The production actor provides pcm_capture_time_now, push_pcm_at
and replace_audio_source with the same contracts.
AudioSource::options returns owning native AudioSourceOptions, including
optional processing modes and binary ANA configuration bytes. set_all uses
native merge semantics: absent changes leave fields unchanged, while explicit
false, zero and empty configuration remain present. Editing this value does not
apply it to an engine. AudioSource::set_options and session
set_audio_source_options invoke native source mutation on signaling. Complete
input admission precedes mutation. Jitter-buffer fields retain their full
values here: source options reach the send channel, while this SDK applies
jitter-buffer configuration only on the separate receive channel. Storing a
source jitter option does not reconfigure NetEq.
Successful invocation of this void setter does not promise that every channel
accepted the options, or that its merge/default behavior cleared prior effects.
A source-only notification does not notify an unchanged live track in this SDK;
reattachment makes native SetSend consume updated source options. The existing
track processing request explicitly notifies its track but is narrower; source
options and the factory's effective processing state are distinct.
Source state/remoteness/volume and track state/kind/volume/signal-level queries
read the native objects. Missing signal-level support remains None, not zero.
AudioTrack::status and RtpSender::audio_track_status return owning snapshots;
a detached sender has no audio-track snapshot. ProductionSession exposes the
same source queries and installed-track snapshots by its owning identifiers.
Source/track volume setters preserve the native 0..=10 finite-input contract.
The pinned caller-fed source implements volume as a no-op with a reported value
of 1.0, even after requesting zero; no synthetic attenuation is applied to PCM
or to the encoded adapter's carrier. Setter completion means native invocation,
not a fabricated effective gain.
AudioTrack::source returns an owning native AudioSourceView;
RtpReceiver::audio_track and RtpSender::audio_track distinguish audio from
the existing video-track accessor. AudioSource::as_source_view captures the
same source interface without making another producer. Source-view aliases
retain their origin track/producer, peer where applicable, and factory;
same_resource compares actual native source identity. Remote views do not
invent PCM submission or a stop method. Attachments that would create an
owning peer dependency cycle are rejected before native mutation.
Source/track observe_changes and source-view observe_volume register native
observers with bounded owning event queues. Callbacks copy notifications without
calling Rust on native callback stacks. Overflow drops the oldest event and has
a consumable counter. close unregisters synchronously on signaling while
leaving accepted events consumable; aliases share registration and queue state.
The local source's native no-op volume observer produces no invented events;
remote-source volume events carry the actual native values. Session source-view
and observer identifiers provide the same operations and independent alias
retirement. Releasing an outer view/producer identifier does not invalidate an
observer or discard its queued callbacks. Polling notifications does not pump
media or advance a controlled clock.
VideoSource::as_view and VideoTrack::source retain owning video-source
interfaces without creating another frame producer. Views query native state,
remote/screencast/denoising metadata, encoded-output support and optional source
statistics. Stock push and RTP sources return no source statistics at this pin,
even after frames arrive; requested geometry is not substitute native output.
same_interface compares the actual SDK interface: different track-source
proxies can wrap the same producer without being the same interface.
VideoTrack exposes native kind, receive intent and all four content hints.
Track/source change observers are bounded, unregister synchronously on close,
and retain accepted events and overflow counts. Source views and observers retain
the exact producer/track/peer origin through final native release. Production
sessions provide opaque view/observer IDs and use the existing track IDs for
controls, preserving their captive single-owner migration contract.
The owned push source checks every incoming sink alignment against the real SDK
broadcaster pairs, including later encoder registrations. Its nonzero LCM must
fit the SDK's signed int for every independently retired subset; inactive sinks
and zero alignments cannot mask an unsafe future aggregate. Requests are not
normalized. Unsafe SDK void updates retire that source registration through
native RemoveSink: preserving a prior pair could retain stale black-frame or
geometry expectations after SDK track-local bookkeeping changed. The internal
worker-scoped public attach/update preflight instead preserves prior registrations
on refusal. The source's saturating
rejected_sink_requests count makes those adapter refusals explicit, separate
from native source statistics and discarded-frame callbacks. Owning local source
views retain this diagnostic through producer retirement; remote SDK sources
return None. The production actor exposes
video_source_rejected_sink_requests. Remote RTP sources preflight against their
actual SDK broadcaster pairs on the same worker as their raw mutations. This
guard does not qualify unrelated encoder-side alignment arithmetic.
VideoSinkWants preserves every pinned SDK hint, including optional target and
requested sizes, ordered resolutions and aggregates. Its default comes from the
SDK. VideoTrack::attach_sink_with_wants and VideoSourceView::attach_sink_with_wants
create owning registrations; update_wants forwards complete values. wants
reads the actual per-sink source registration, including the SDK track's
black_frames = !enabled rewrite, and returns None after close or native
retirement. Hints do not promise resizing, rate limiting or generated frames.
Source-interface sinks bypass the track's enabled-state rewrite. Sink clones
share one registration, bounded frame/event queues and close state.
Production sessions expose attach_raw_video_with_wants and
update_raw_video_sink_wants for receiver sinks. Independent track/source
registrations use attach_video_track_sink_with_wants,
attach_video_source_sink_with_wants and SessionVideoSinkId. Their alias,
query, update, poll, close and release operations keep local handles captive
during migration and retain sources after outer source/view IDs are released.
PeerConnectionFactory::create_media_stream creates a typed LocalMediaStream
track container, not SDP routing or receive synchronization. This is distinct
from main's byte-oriented general MediaStream interface. Get/Find return the exact
accepted track owners in native insertion order. Native Add rejects duplicate
IDs per media kind; Remove matches by ID even when passed a different track.
Successful membership retains each track's actual source/factory or received
peer origin, including across factories. Track aliases do not retain the stream.
Bounded stream change observers unregister synchronously on close and keep
accepted notifications and overflow counts consumable. ProductionSession
provides opaque stream/track/observer IDs, source/sender/receiver acquisition,
Get/Find/Add/Remove and sender replacement through returned owning track aliases.
Retiring IDs does not invalidate independently retained aliases or queued output.
AudioTrack::processor queries the native per-track processor and returns an
owning alias only when one exists. The pinned stock tracks return None, even
when the shared voice-engine APM is processing audio; source options and peer
statistics are not substitute processors. AudioTrackProcessor::stats forwards the
remote-track condition and preserves every optional observation, including the
deprecated voice-detection value. This per-track alias is distinct from the
standalone AudioProcessor and processing factories supplied by the audio API.
Borrowed MonoPcm, InterleavedPcm and DeinterleavedPcm views support signed
16-bit and float PCM without owning or copying their storage. Mutable variants
carry exclusive borrows; separate-channel views retain each channel borrow.
Native shape helpers, interleaved copy_from, supported copy_samples and
planar clear preserve active prefixes and leave extra storage untouched.
Interleaved mono access requires one channel; native planar mono access returns
the first channel, including for a multichannel view. No layout conversion or
channel inference is performed.
AudioFrame and callback AudioFrameMut typed interleaved accessors retain the
existing shape, initialized-prefix and mute gates. Shape-taking mutable access
uses native prefix-only mute clearing, not full-buffer clearing. Frames are not
presented as planar storage. The pinned CopySamples template is ill-formed
for deinterleaved operands; these calls report NativePlanarCopyUnavailable
rather than substituting a copy algorithm. Empty copies report EmptyCopy
before native helpers can form invalid element-zero addresses.
A receiver can select one decoded PCM sink or one encoded Opus sink
before packets arrive. Audio sink close releases its reservation for reattachment
or a mode change. Encoded close restores native decoding for future packets;
stop reception before closing when opaque packets must never reach a decoder,
and resume only after a new encoded sink is attached. Dropping an already closed
sink does not affect a replacement. The encoded sink delivers complete Opus packets without
passing them to NetEq's decoder, with RTP timestamp, payload type, SSRC and
48 kHz sample duration, native sequence number, received audio level and optional
RFC6464 voice-activity bit. The pinned native incoming frame's Type() projects
the received V bit when AudioLevel() is present; absent extension means unknown,
not silence. This is not inferred from carrier PCM or decoder speech decisions.
It does not expose RED or other non-Opus payloads;
its bounded queue records dropped packets. The decoded headless sink pulls
10 ms of playout on demand when empty; it does not open a host speaker.
Native artifacts additionally allow opt-in platform audio with
PeerConnectionFactory::builder().native_audio(true): enumerate recording
and playout devices, select them by current index, and make a microphone
track. A native peer can request recording/playout enable or disable with
set_native_audio_enabled(recording, enabled). WebRTC starts and stops the
selected device as matching media streams appear or disappear; the setting is
shared by peers from the same factory. The upstream setter does not report
asynchronous device startup or playout failures. Platform-device methods and
types are not exposed without the native Cargo feature. Enumeration can be
empty and selection can fail while a device is active; CI smoke-tests this
path without requiring hardware.
For prerecorded Opus, select AudioEncoderFactory::with_opus_frames() on the
peer factory, then create an EncodedAudioSource and audio track with
create_encoded_audio_source(channels) (1 for mono, 2 for stereo) and
create_encoded_audio_track(). For stereo, the receiver must request
stereo=1 in its negotiated Opus answer for that audio m-line. Selecting the
sender's advertised stereo capability with set_audio_codec_preferences alone
does not make the built-in receiver answer request stereo; its default answer
requests mono. Confirm the answer's fmtp before pushing stereo packets, since
a mono sender cannot consume a stereo carrier. Push complete Opus packets (without RTP
headers) with a matching mono/stereo TOC as OpusInputFrame, supplying the
48 kHz RTP timestamp and matching 10 to 60 ms duration. Input is limited to
1200 bytes per packet and must fit within 960 bytes per channel per 10 ms
block, minus 36 bytes per block for the internal envelope. The adapter carries those
bytes to an Opus pass-through encoder without calling the Opus encoder;
WebRTC creates the RTP headers,
RTCP and encrypted transport. Each source is independent. The source admits at
most 24 outstanding 10 ms input copies across all attached senders and returns
Backpressure before admitting more, including when no sender is attached.
Each native encoder consumption releases only its own input slot. Detaching a
sender does not release its still-queued copies; if native input is discarded
before encoding, capacity is conservatively retained until source close.
Retry without advancing the input timestamp. This factory is Opus-only and rejects raw PCM sources; use a
separate peer factory for raw PCM tracks. The adapter does not validate that
packet bodies can be decoded. For independent producer loudness and V metadata,
construct OpusAudioLevel::new(level_dbov, voice_activity) and call
push_opus_with_audio_level() (or the explicit mapped capture-time variant).
Level is validated in 0..=127, independently of V, and is never inferred from
Opus bytes or the adapter's private PCM carrier. Native encoder speech and the
supported sender-frame level setter feed native RFC6464 serialization. If the
extension is not negotiated, received level/V remain absent. The ordinary
push_opus() API remains unchanged and does not declare a producer level.
ProductionSession::push_opus_with_audio_level() exposes the same owned-value
submission without leaking sequence-bound handles. For explicit capture stamps,
EncodedAudioSource::capture_time_now() reads the qualified native capture
timeline: controlled world time or default system-clock production time.
push_opus_at() and push_opus_at_with_audio_level() require the actual capture
instant mapped into that timeline, not an unrelated clock epoch or an assumed
enqueue time. Native forwarding has millisecond precision; received capture
values use the negotiated native NTP domain. Custom non-controlled clock domains
remain rejected, and future capture instants fail before admission in both profiles.
The actor exposes opus_capture_time_now(id) and both explicit-time submission
variants without exposing local handles. Legacy unstamped production submission
is unchanged and does not invent a producer capture instant.
Missing or unsupported native handoffs drop the frame and increment AudioEncoderFactory::opus_frame_handoff_failures();
that counter is an adapter failure observation, not a delivery receipt. The
handoff keeps at most one emission per native encoder and uses the provided
encoder queue, not RTP timestamps, SSRCs, or payload hashes, to consume it.
WebRTC cannot
re-encode these packets to match a changing bitrate; the caller controls
source bitrate and pacing. Stock PCM audio uses the selected runtime's native
queues; this externally encoded input adapter remains an explicit option in
both runtimes, not a requirement for controlled audio.
Camera and desktop capture methods, including discovery, reject controlled
execution before contacting devices, display servers or portals. Enabling the
native feature does not permit physical capture in a controlled world.
Native camera devices expose names, IDs, and capture capabilities through
camera_devices() and camera_formats(id). open_camera(id, width, height, fps) starts the closest upstream matching capability and owns a source for
create_video_track(track_id, capture.source()); an unsupported selection
returns an error. Refresh enumeration after device changes rather than caching
indices. CameraCapture::status(timeout) reports starting, streaming, stalled,
closed, or stopped capture; stalled means no frames arrived within the caller's
timeout, not a confirmed device-loss cause. screen_sources() and window_sources() enumerate desktop source
IDs, and open_screen(id) / open_window(id) create caller-paced captures.
Call capture_next_frame() to request a frame, inspect status() and
failed_frames(), and call stop() to end the capture and source. Wayland
portal selection may remain pending and can be cancelled or fail; an open
capture does not prove that frames were delivered. stop() reports failure
when a native device cannot stop or its source cannot end, while Drop is
best-effort. Native device smoke tests run without hardware or a display and
do not qualify actual microphone, camera, X11/Wayland portal, or speaker paths.
examples/native_devices.rs uses the public API for discovery and opt-in
capture. A portal response classified as cancelled by upstream can include a
denial; upstream's generic error does not distinguish missing service from
other failures. Wayland portal session closure has its own capture status.
Both flavors declare the following producer/consumer selection universe. A target declaration is not runtime qualification or a promise that its OS release is already available:
| Platform | Targets | Minimum runtime |
|---|---|---|
| Linux | linux-x86_64, linux-arm64 |
Debian Bullseye sysroot / glibc 2.31 |
| Windows | windows-x86_64 |
Windows 10 |
| macOS | macos-x86_64, macos-arm64 |
macOS 12 |
| Android | android-x86_64, android-arm64-v8a |
API 26; native uses AAudio |
| iOS | ios-arm64, ios-simulator-arm64 |
iOS 18 |
Each target is a separate archive built with its supported toolchain. Android ABIs and Apple device/simulator builds are not combined. Linux musl, 32-bit Android, Windows arm64, x86_64 iOS Simulator, WebAssembly, and other targets are deferred.
python3 -m tools.rust_only_consumer candidate --artifact <archive> --sha256 <sha256> --target <target> --flavor <core|native> checks a cold consumer, verified
cache reuse, offline hits/misses, corrupt downloads, and local overrides against
the selected target. released --repository https://github.com/PulseBeamDev/pulsebeam-libwebrtc-sys.git --tag v<version> --target <target> --flavor <flavor> instead consumes the actual source tag and
its OS release with cold caches and no candidate override.
Install Rust's matching target standard library before running these commands. Desktop proofs require a matching OS/architecture host and execute the linked program. Mobile proofs link a target executable without trying to run it on the build host; emulator/simulator execution is a separate required gate.
| Target family | Standalone consumer inputs |
|---|---|
| Linux x86_64 | System C linker driver, LLD, glibc/system libraries; native also needs the X11, GLib/GIO, GBM and DRM development libraries listed in the manifest |
| Linux ARM64 | Matching ARM64 host, Clang with compiler-rt and LLD; same flavor-specific system libraries as x86_64 |
| Windows x86_64 | MSVC x64 linker environment and Windows SDK; the proof selects static CRT to match the archive; system libraries are supplied by the SDK |
| macOS | Matching Intel/Apple silicon host and selected Xcode command-line SDK via DEVELOPER_DIR; deployment target remains 12.0 |
| Android | Linux x86_64 host and standalone NDK via ANDROID_NDK_HOME; the proof selects its <triple>-linux-android26-clang linker and sysroot for the requested architecture |
| iOS | Apple silicon host and selected Xcode iPhoneOS or iPhoneSimulator SDK; deployment target remains 18.0, and device/simulator selections stay separate |
An explicit CARGO_TARGET_<TRIPLE>_LINKER can select an equivalent standalone
linker. The consumer proof never synchronizes a producer checkout or runs GN,
Ninja, bridge generation, or a C/C++ source compiler. Compiler sentinels are
separate from the target linker, so allowed linking is not mistaken for source
compilation. Windows uses native .cmd sentinels. Link inputs are not bundled
application SDKs or physical-device certification. Running a desktop proof on a
newer host does not imply execution on every supported older OS version.
VideoFrame::i420 owns tightly packed planar I420 bytes. For padded planes,
VideoFrameBuffer::i420_strided copies the visible rows into packed I420.
VideoFrame::nv12 accepts owned Y and interleaved UV planes with explicit
strides and converts to packed I420 before source submission; odd dimensions
round chroma sizes up. A decoded VideoFrame can be converted back into owned,
packed NV12 planes with to_nv12(). Conversions only rearrange bytes, without
scaling or color-space conversion. VideoFrame::with_rotation attaches
clockwise 0/90/180/270-degree metadata without rotating pixels; native source
submission, local sinks, and encoder/decoder callbacks preserve that metadata.
Each strided plane must contain exactly
stride × rows bytes, including padding after the final row; zero dimensions,
short/extra buffers, narrow strides, and overflow are rejected.
Each attached VideoSink or VideoSourceSink holds at most four owning SDK
frames and 16 MiB of CPU plane storage, including strides and sample width.
For opaque native storage, whose allocation size the SDK does not expose, the
byte budget measures packed-I420-equivalent geometry, not GPU allocation.
On overflow it drops oldest frames; frames exceeding the byte budget are
discarded. dropped_frames() reports cumulative retention loss, including after
explicit close. The cap applies per sink; close unused sinks to release frames.
try_next_native_frame() pops a RawVideoFrame with its actual SDK buffer,
complete metadata and retained acquisition source/factory. The legacy
try_next_frame() consumes the same queue and explicitly converts to its
packed-I420 value. Neither polling API promises every frame or advances work.
Native frame copies have independent metadata and share immutable buffer
ownership. Buffer type/geometry/strides, owning 8/16-bit plane samples,
I420 conversion, crop/scale and native mapping/preparation preserve the SDK's
results; mapping a non-native storage type is rejected before its native CHECK.
Preparation owns the real SDK completion callback and has explicit retirement.
WritableI420Buffer has exclusive native ownership, including custom strides.
Uninitialized planes cannot be read, and incomplete buffers cannot be published.
Plane writes, native initialization/black filling, copy/rotation and crop/scale
fill storage before consuming freeze creates immutable shared ownership.
RawVideoFrame::from_buffer, complete RawVideoFrameMetadata replacement,
and VideoSource::push_native_frame retain metadata instead of reducing it to
the legacy pixel/timestamp fields. SDK frame IDs and locally stored metadata
are not promises that those values cross the wire.
ProductionSession keeps these resources captive behind frame, buffer, writer
and preparation IDs, with the same operations and explicit release methods.
PeerConnectionFactory::builder().audio_processing(config) configures the
factory's WebRTC software AEC, NS and AGC module for raw PCM. A local raw
AudioTrack may call set_audio_processing_options to request disabled,
automatic, platform or software AEC, NS and AGC. The setter stores a request;
it does not fail when a platform effect is unavailable. During application,
libwebrtc may leave an unavailable platform-only effect disabled and log a
warning. Platform effects also require opt-in native audio. Inspect
factory.audio_processing_state() after starting capture to distinguish the
request from resolved software/platform effects and availability. A stored
request before attachment is not evidence of active processing. The voice engine is shared by tracks
in one factory, so concurrent per-track options are not independent. Encoded
Opus bypasses PCM processing; the synthetic Opus-frame adapter currently
rejects combining an encoded-input factory with PCM processing or native
microphones, and encoded tracks reject processing options. This is an adapter
constraint, not an Opus limitation.
The exported API retains the upstream extension points required by PulseBeam:
| Requirement | Upstream mechanism |
|---|---|
| Caller-controlled time | EnvironmentFactory |
| Cooperative task queues | TaskQueueFactory |
| Caller-owned threads | PeerConnectionFactoryDependencies |
| Simulated sockets/network | PacketSocketFactory |
| Seeded WebRTC IDs/randomness | SetRandomGenerator |
WebRTC has no usable runtime injection point for deterministic cryptographic entropy. This repository does not patch WebRTC or BoringSSL to add one. Consumers must not expect certificates, DTLS material, SRTP keys, or encrypted bytes to repeat for the same simulation seed.
ControlledPeerDriver borrows the caller's thread for peer network, worker and
signaling roles. Pair it with a matching manual-clock environment, cooperative
task queues, and ControlledSimulatedNetwork to pump multiple peers with
run_ready and next_deadline. Only one driver may own the process-global
WebRTC clock at a time; drop all controlled peers, endpoints, and networks to
release it, including retained SDK environment aliases and provider products.
The common Session::with_runtime path supplies matching runtime dependencies;
stock raw-media construction has no simulation-specific admission flag. The
obsolete controlled_media constructor method has been removed. The earlier
focused encoded-input fixtures explicitly select Opus carrier input with builtin_opus(),
direct VP8 with builtin_vp8(), or direct H.264 with
input.encoded_receive_factory(). The last is an encoded-only adapter: its
no-output decoder lets libwebrtc perform native reference finding and frame-buffer
ordering, while attach_ordered_encoded_sink() receives the ordered encoded units and no
decoded pixels are fabricated. Ordinary
H.264 needs no DD; new_l1t3() requires native DD negotiation and sender L1T3
selection. The caller alone delivers packets, advances time and pumps queues.
Closing an encoded sink is terminal for that receiver; a new receiver is needed
to intercept again. That adapter's receive constraint does not prohibit selecting
the artifact's stock H.264 decoder with raw-media factories. Deterministic
managed execution of every built-in decoding/encoding backend remains an open
qualification obligation, not an implemented whitelist.
The controlled network supports externally scheduled UDP delivery, client-side
TCP connect/data decisions, and caller-provided DNS answers for STUN/TURN
servers. Unknown names fail deterministically. A delivered TCP connect succeeds;
dropping it fails the socket, while dropping TCP data discards those bytes. The
external driver must implement any server response and inject received bytes.
TURN/TLS runs over the same caller-delivered TCP bytes with certificate
validation; TCP listeners remain unsupported. See
tests/controlled_driver.rs for gathered-SDP
connectivity under virtual time, tests/controlled_turn_tls.rs
for TLS and authenticated TURN relay candidate gathering, and
docs/capability-matrix.md for remaining evidence gaps.
This adapter tests what it owns: public Rust/CXX construction and configuration, representative raw/encoded payload and metadata mapping, negotiation and rejection paths, backpressure, and resource shutdown. For simulcast/SVC and H.264, use focused adapter integration or smoke tests, not an exhaustive matrix of codec profiles, layers, packetization, and RTP metadata combinations. The pinned libwebrtc project's QA is responsible for its codec, packetization, and media-engine correctness. Physical device and desktop portal behavior remains the upstream project's qualification responsibility; this repository does not establish that upstream qualification for the pinned revision. Our headless smoke tests report service-dependent scenarios they cannot exercise rather than count those scenarios as passing. This division does not waive missing adapter capabilities, broken supported paths, controlled-media deadlocks, or representative adapter-side metadata checks. See the capability matrix for tested paths and known gaps. The approved Linux spec and its acceptance requirements are not amended by this guidance.
Linux x86_64 core and native configurations use proprietary_codecs=true,
ffmpeg_branding="Chrome", rtc_use_h264=true and
rtc_system_openh264=false. The FFmpeg branding is required for actual H.264
decoding, not just advertised formats. Use matching exports built with these
arguments, not archives produced with the historical disabled configuration.
The shipped-archive C++ smoke fixture checks actual H.264 encode/decode output;
tests/video.rs exercises Rust peer transport with the built-in factories.
Use explicit VideoEncoderFactoryHandle::builtin() and
VideoDecoderFactoryHandle::builtin() peer-builder selections for the stock
video codecs. Omitted selections do not silently install video factories, and
custom creation failure does not fall back to a stock codec. Controlled media
still admits only its documented selections; packaged H.264 is not a proof of
caller-pumped decoding with no extra threads.
The export includes dependency notices, WebRTC PATENTS and configuration provenance. OpenH264/FFmpeg licenses and H.264 patent obligations must be reviewed for the consuming product's use and distribution. Technical enablement and passing tests are not patent clearance or publication authorization. Consumers may instead explicitly select a hardware-backed or separately licensed factory. Direct encoded H.264 transport uses the native packetizer with caller-provided access units and does not require a compression or decoding implementation.
Each build writes one dist/webrtc-<flavor>-<target>.tar.gz containing:
include/ same-revision source and generated headers
lib/libwebrtc.a self-contained static library (non-Windows)
lib/webrtc.lib self-contained static library (Windows)
link.txt required system libraries, frameworks, and link flags
LICENSES/ applicable notices and licenses
build.txt source repository/revision, flavor, target, toolchain, GN arguments,
and exported C++ definitions
manifest.json versioned source and CXX producer provenance, native
configuration, ABI, archive, ordered link-input, and license
inventory identity
Headers and libraries always come from the same immutable WebRTC revision. The
archive contains the WebRTC-owned static link closure but does not redistribute
the operating-system runtime: Linux supplies glibc, Windows uses the static
multithreaded CRT (/MT), Apple targets use platform libc++ and frameworks, and
Android uses its pinned API/NDK system libraries. link.txt remains a
human-readable producer report. The versioned, ordered, typed entries in
manifest.json are the Rust crate's authoritative system-library, framework,
weak-framework, and raw-linker contract.
The generated CXX C++ half and repository-owned adapters are compiled during artifact production with WebRTC's target compiler and ABI settings, then merged into the single native archive. The pinned CXX Rust runtime is vendored without its compiler-running Cargo build script; its matching C++ runtime is also part of the native archive. Cargo consumers therefore compile only Rust. CXX and STL types stay private; the public crate surface uses ordinary Rust values.
Schema-4 manifests also require bridge.native_adapter_sha256, a path-aware
SHA-256 of every native/*.cc/native/*.h input and Justfile. It participates
in native configuration identity and is checked against the current source
package by producer provenance, release audit and Rust-only consumers. Matching
CXX declarations alone cannot qualify an archive containing stale adapters or
build recipes; older manifest schemas are deliberately rejected.
This repository does not produce AARs, JARs, frameworks, or XCFrameworks.
consumer/mobile is a producer-owned acceptance host, not a downstream SDK or
shipping application. It links the selected archive into a disposable APK or
simulator application. The Rust bridge scenario checks identity, controlled
clock behavior, software PCM processing, factory/peer/channel behavior and
shutdown. The SDK C++ companion exercises real software audio encoding/decoding,
VP8/VP9/AV1 encoding/decoding and PCM processing against the exported headers.
Linux x86_64 retains its additional mandatory H.264 and event-log checks.
The applications request no microphone or camera permission and do not exercise
hardware codecs, native device backends, rendering, or physical devices.
Android runtime qualification requires an x86_64 Linux host with acceleration,
a standalone NDK 30.0.14608247 (the source pin), Android platform 26, build tools
35.0.0, platform tools, command-line tools and the
system-images;android-26;google_apis;x86_64 emulator image already installed and
licensed. Set ANDROID_NDK_HOME and ANDROID_SDK_ROOT; Java and Cargo must be
available. The runner creates an isolated, accelerated headless AVD unless
--android-serial emulator-NNNN selects an existing emulator. Either way, a real
API 26 x86_64 emulator is mandatory. A newer API is not minimum-baseline proof.
The iOS runner requires an ARM64 macOS host, Xcode's simulator SDK and an available iOS 18.0 simulator runtime. It creates and deletes its own iPhone 16 simulator and checks the OS version reported by the executed application. A newer simulator runtime, a physical iPhone, or an Intel host cannot substitute.
# Install the matching Rust target first. SDK/runtime installation is separate.
just ci rust-target android-x86_64
python3 -m tools.mobile_runtime \\
--archive dist/webrtc-core-android-x86_64.tar.gz --sha256 <archive-sha256> \\
--target android-x86_64 --flavor core --output .work/android-core-runtime.json
# On ARM64 macOS with the iOS 18.0 runtime installed:
just ci rust-target ios-simulator-arm64
python3 -m tools.mobile_runtime \\
--archive dist/webrtc-native-ios-simulator-arm64.tar.gz --sha256 <archive-sha256> \\
--target ios-simulator-arm64 --flavor native --output .work/ios-native-runtime.jsonThe equivalent CI entry is just ci mobile-runtime <flavor> <target> <checksums> <output>. Success requires a fresh guest result matching a per-run
nonce and the exact archive SHA-256, not merely a zero launcher exit code. It
writes JSON evidence only after execution succeeds and refuses to overwrite
existing evidence. Both flavors must execute on Android x86_64 and the ARM64 iOS
simulator. Android ARM64 and physical-device iOS ARM64 still require compile and
link qualification, not physical-device execution. These commands implement the
qualification path; their availability is not evidence that the full platform
matrix has passed.
The immutable commit
ba469aa2093ba950066258ca0a59a6fbd1295582 is the current WebRTC source
identity; m150_release is branch context, not an input to the build. The source includes three always-required pinned deltas:
patches/encoder-preference-order-fallback.patch corrects a contradictory SDK
assertion for negotiated-codec fallback,
patches/runtime-codec-core-budget.patch applies the configured codec-core
budget, and patches/recordable-encoded-frame-rtp-identity.patch exposes the
post-ordering RTP timestamp and receive-stream SSRC needed to associate a
recordable encoded frame with its exact native input. The additional immutable
patches/core-ios-remove-framework-objc.patch applies only to core iOS device
and simulator artifacts to remove sdk:framework_objc; native iOS and every
other artifact omit that iOS-only patch. build.txt and manifest.json record
all patch digests and the iOS patch's applied/pristine state. Source-state
checks reject tracked WebRTC source changes outside the exact pinned patches,
and only optimized release artifacts are published.
Linux publication requires the entire core/native by
linux-x86_64/linux-arm64 group. It does not advertise a partial architecture
or flavor set. Registry publication remains deferred (pulsebeam-webrtc-sys
is not a crates.io dependency).
Native Linux builds run in the locally built, repository-owned development image. Build the image explicitly with Podman, then run the normal build or runtime recipe inside it; the checkout is mounted read/write so generated outputs stay owned by the calling developer.
just ci-preflight
just linux-image pulsebeam-linux
just linux-run pulsebeam-linux just build core linux-x86_64
just linux-run pulsebeam-linux just _runtime-test core linux-x86_64 dist/webrtc-core-linux-x86_64.tar.gz
just linux-run pulsebeam-linux just build native linux-x86_64
just linux-run pulsebeam-linux just _runtime-test native linux-x86_64 dist/webrtc-native-linux-x86_64.tar.gzFor a manual upgrade rehearsal, run python3 tools/select_upgrade_pin.py <40-character-commit> in a disposable checkout before building the image.
This changes the local Justfile pin; the single hosted workflow has no separate upgrade-rehearsal mode.
After publication, use the same version tag that published the release
(replace <tag> with that tag, for example v0.5.6):
[dependencies]
pulsebeam-webrtc-sys = { git = "https://github.com/PulseBeamDev/pulsebeam-libwebrtc-sys.git", tag = "<tag>" }
# For native capture/playback, add: features = ["native"]Once the matching OS release is available, a normal Linux cargo run downloads
and verifies the selected architecture/flavor automatically. System linker and
library prerequisites still apply.
- One CI workflow runs fast checks on pull requests and pushes to
main. Push a newv*canonical version tag to run Linux core/native qualification on matching x86_64 and ARM64 hosts, with runtime tests, x86_64 ASan, audit, and cold candidate consumers. Derived OS tags are excluded from source-release triggers. Before building any container or native code, the first job checks that the repository is the canonical producer, the tag resolves to the checked-out commit, and it equalsv<package.version>inCargo.toml. Pull requests and branch pushes skip this tag-only check. Bump the crate version (and updateCargo.lock) before creating a new tag; for example,v0.6.0cannot release a crate still declaring0.5.6.just ci release-tag <tag> <commit>runs the same check locally without a container, GitHub authentication, or marker files. The tag must already exist in the local checkout. Publication starts automatically only if all Linux qualification gates pass. The resulting bundle contains four archives,SHA256SUMS,LINUX-RELEASE-MANIFEST.json, the repository license, and a schema-2artifacts.lock.jsonwith four Linux URLs/digests and fourteen explicitly unavailable selections. Linux publishes atv<version>-linux, while consumers continue depending on the canonicalv<version>source tag. OS suffixes are reserved for derived release tags. - The workflow first verifies or creates the immutable derived tag at the canonical source commit, then creates a draft if no release exists, downloads and hashes any existing assets, and uploads only missing byte-verified assets. It never deletes, replaces, or clobbers an asset; a conflicting or unexpected asset fails closed. It rechecks the complete inventory, attests the closed bundle, and only then advertises the release. Recover an interrupted draft with Re-run failed jobs on the original tag-push Actions run while its original bundle is retained (one day). A new manual workflow run only qualifies; it cannot publish. Retrying accepts only identical bytes.
- After publication, CI tests both architectures and flavors using the source tag as the Cargo Git dependency, with fresh checkouts and cold release-lock and archive caches. It reports the usable tag in the workflow summary. The tag works only after the matching release and its lock have been published.
Never replace an asset on an existing release. If publication is incorrect or cannot resume with its original bundle, use a new producer tag. Provenance, rather than byte-for-byte archive identity across runners, is the reproducibility contract.
The automated host suite validates the portable API and exported static link closure. It does not claim physical camera, microphone, speaker, GPU, platform UI, or hardware-codec qualification; physical-device qualification remains a downstream application responsibility.
PeerConnectionFactory::builder().options(FactoryOptions { .. }) applies
native factory options before peer creation. factory.set_options(options)
replaces options for subsequent peers; existing peers retain their construction
options. ProductionSession::set_factory_options() provides the same operation
under exclusive actor access, including before the first peer and after caller
migration. Native SetOptions has no validation outcome or readback getter;
the actor method can still reject a closed session.
The defaults preserve encryption, the native network-monitor choice, a
loopback-only ignored-adapter mask, and the native DTLS 1.3 maximum.
NetworkAdapterMask supports bitwise composition and retains unknown native
bits. SslProtocolVersion includes the native unspecified and legacy values,
with DTLS_10, DTLS_12 and DTLS_13 aliases. The pinned engine has a DTLS 1.2
minimum, so legacy selectors do not enable obsolete protocols. DTLS 1.3 remains
subject to native build support. These options do not configure TURN TLS.
Setting disable_encryption is insecure and prevents DTLS-dependent SCTP
channels. See factory configuration contracts.
Both local factory and common session builders accept
.execution_resources(ExecutionResources { codec_core_budget: Some(CodecCoreBudget::new(1)?), }).
Without registration, caller-driven peer factories supply a one-core default and
threaded factories retain native host detection. Explicit configurations, including
an absent budget, remain unchanged. This does not select a codec profile, impose
a global worker cap or by itself qualify every multiworker setting. The desktop
Linux x86_64 managed VP8 backend also supports persistent native encoder/filter
workers for its scoped explicit multi-core configuration. See
native Call execution resources for settings and limits.
CryptoOptions provides owned native crypto settings and native SRTP cipher-order
queries. KeyExchangeGroup queries the selected binary's supported groups and
names while retaining open protocol identifiers. KeyExchangeGroups::update
applies native field-trial defaults through a synchronously borrowed environment;
the resulting values can be moved into actor configuration. Querying an empty
SRTP profile selection returns an error instead of triggering native RTC_CHECK.
These helpers do not install frame providers or configure a peer by themselves.
See crypto option contracts.
Certificate provides owned native generation, PEM/chain import and export,
expiration queries, chain snapshots, and native fingerprint lookup.
CertificateKeyParams preserves native RSA parameter validation and P-256
generation. Clones retain native identity; imported PEM creates a distinct
resource with native value equality. Immutable resources may cross actor
caller threads under the qualified pinned BoringSSL backend. Synchronous
generation uses native system epoch time and crypto randomness, not a manual
environment clock. Exported PEM contains sensitive private-key material.
Retained resources can be supplied through RtcConfiguration::options.certificates;
this does not expose an async generator. See
certificate contracts.
RtcConfiguration exposes owned native safe/aggressive presets, full
RtcIceServer values, certificates, crypto, SDP/ICE policies, timing,
network/VPN, allocator, SCTP and media-related configuration values.
create_peer_connection_with_configuration and actor
create_peer_with_configuration accept it without changing existing
PeerConfiguration and IceServer literals. Native configuration and
set_configuration operations have actor counterparts and preserve native
update errors and certificate resource identity. The candidate-pool range is
0..=65535, with invalid creation input rejected before allocator work.
Provider and environment dependencies are separate remaining parity work.
See peer configuration contracts.
PeerConfiguration accepts STUN/TURN URLs with credentials and an explicit
IceTransportPolicy::{All, RelayOnly}. turn: URLs support UDP or TCP via
?transport=udp or ?transport=tcp; turns: uses TLS with certificate
and hostname verification. By default, the pinned WebRTC roots are trusted.
PeerConfiguration::turn_tls_ca_pem can add a PEM-encoded CA for that peer's
TURN/TLS connections without disabling WebRTC's existing roots or hostname
checks. This option widens trust for all TLS servers configured on that peer;
it is not a per-server CA restriction. An invalid CA fails peer construction.
PeerConnection::create_offer(), create_ice_restart_offer(), create_answer(),
description setters and add_ice_candidate() return
Result<OperationId, PeerError>. Admission retains at most 64 pending operations
plus unconsumed terminal outcomes per peer, also in the native adapter.
Only one SDP operation (offer/answer/description setter) may be pending per peer.
A second SDP operation fails with ResourceExhausted until the native callback
completes, avoiding the pinned engine's queued-SDP shutdown callback hazard.
Completed outcomes still share the 64-slot admission budget until consumed.
Exhaustion fails synchronously with ResourceExhausted, and closed submission
fails with Closed; neither creates another queued outcome. Native shutdown
may report its own error before binding cancellation; accepted outcomes are not
guaranteed to have the Closed error kind. An accepted
offer's SDP arrives in OperationComplete. For non-trickle signaling,
set each local description, wait for IceGatheringState::Complete, then copy
its gathered SDP with PeerConnection::descriptions() and send that description
to the other peer. Applications own signaling; forwarding per-candidate events
is not required. PeerConnection::descriptions() copies current and pending
local/remote SDP in one signaling-thread snapshot; an empty-SDP Rollback
description restores
stable negotiation state, while a nonempty rollback SDP is rejected. The
snapshot's local() and remote() accessors prefer pending SDP over current
SDP, matching native effective-description access.
create_offer_with_options() and create_answer_with_options() take
OfferAnswerOptions, including native receive counts, voice-activity detection,
ICE restart, BUNDLE, packetization, legacy SCTP syntax, and Plan B simulcast
options. Defaults match the engine. Counts outside native integer range fail
before admission; native option validation otherwise arrives in the operation
completion. set_local_description_auto() lets signaling state choose,
generate and apply an offer or answer without manual SDP handling. Its success
completion carries no description; copy the resulting descriptions() snapshot.
It shares ordinary SDP admission, cancellation and controlled-media restrictions.
restart_ice() marks future offers for restart without generating SDP. Use
should_fire_negotiation_needed(event_id) to validate an observed negotiation
notification before delivering it: native operations or later state changes can
make a queued ID stale. add_ice_candidate_sync() and remove_ice_candidate()
preserve the native synchronous choices. They do not create operation IDs or
wait behind the asynchronous SDP operations chain. Candidate parsing errors
carry PeerErrorKind::Syntax; native acknowledgment or rejection is an
IceCandidateDisposition, since this native API supplies no error detail for
rejection. Acceptance is not connectivity evidence or proof that a removed
candidate previously existed. All these operations also have owned-ID
ProductionSession counterparts and retain native sequencing after actor
migration.
Existing senders also support generic set_media_track(), including audio
replacement and None detachment. ProductionSession::set_sender_track() uses
owned track IDs. Native boolean rejection remains distinct from binding errors;
accepted replacement retains the original source dependencies across aliases.
The existing video convenience shares its admitted-source/layout checks rather
than providing a bypass for controlled-media profiles.
reconfigure_bandwidth_estimation() accepts typed
BandwidthEstimationSettings, including allow_probe_without_media. It invokes
the SDK's asynchronous worker reconfiguration, which can restart estimation.
Success is not proof of a probe or a bandwidth allocation. Padding probes still
require an appropriate sending RTX-capable native transceiver.
set_data_channel_message_observation() installs or replaces the SDK's
peer-wide message observer independently of individual channel observers.
DataChannelMessage peer events retain native Unix-millisecond timestamps,
native SCTP channel IDs, label bytes, send/receive direction, text/binary kind
and payload bytes. A native channel ID is not a SessionChannelId; no identity
join is guessed from IDs or labels. Copied observations use the existing bounded
peer queue and its loss counters, not transport buffering or consumption credit.
Disabling from an ordinary caller fences SDK network-thread replacement while
retaining queued copies. Caller-owned controlled roles must execute their own
posted SDK task; the binding does not pump for them. Replacement from an SDK
network callback is rejected to avoid a signaling/network blocking-invoke cycle.
SDK-owned callback state remains valid until native replacement or teardown.
Peer events also expose actual legacy RenegotiationNeeded callbacks and native
InterestingUsage integers. The latter uses the SDK's configured reporting delay
and heuristics, not a binding-generated report. The
IceConnectionReceivingChanged hook is wired, but this SDK pin has no source
invoker for it; use live ICE receiving-state reads rather than expecting a
synthetic callback. Renegotiation/receiving advisories are bounded and coalesced
with observable accounting. Interesting-usage log occurrences instead retain
distinct records subject to the bounded control queue and its drop counters.
The deprecated description observer overloads are available as
set_local_description_legacy(), set_remote_description_legacy() and
set_local_description_auto_legacy(). They invoke the SDK's delayed observer
path and share the same bounded admission and exactly-one terminal result
contract. A completion can describe an earlier state; use current description
snapshots for current state.
Factories can create owning byte-ID MediaStream values. Native track
add/remove results remain distinct from peer add_stream() acceptance and the
void remove_stream() invocation. The latter operations and local/remote peer
collections require Plan B semantics; Unified Plan is rejected before the SDK's
fatal precondition. Source leases survive aliases, stream getters and accepted
peer retention without peer-retaining track cycles. StreamAdded,
StreamRemoved and TrackAdded { receiver, streams } preserve actual callback
references, including the deprecated callbacks alongside Track. Arrivals
share the existing bounded queue; oversized stream-association vectors are
dropped observably rather than truncated. Actor counterparts retain IDs for the
same native objects, and baseline Opus/video track constructors do not require
implicitly publishing an auxiliary transceiver. senders(), receivers()
and transceivers() copy one actual native collection snapshot; the latter
requires Unified Plan. transceiver_mid(id) reads the native transceiver's
optional MID; it remains absent until description application associates the
transceiver with an m-line. Stream source leases follow native collection and
sender identity, including the SDK's ID-based track removal semantics.
Retained ICE access is revocable: IceTransportInternal reacquires the internal
transport inside each network-role call. It exposes actual configuration,
credentials/role/gathering controls, candidate mutation, candidate-pair and
connection statistics, packet send/options/errors, and network routes.
IceConfig keeps optional native defaults distinct from explicit timers;
resolved_defaults() uses the SDK helpers. Native validation errors are
RtcError values. A successful void configuration setter means invocation,
not that the SDK applied every field; read config() for effective state.
TransportCandidate owns the full SDK candidate value, distinct from a JSEP
IceCandidate. Copies preserve its generated ID and byte-bearing fields.
Parsing and verify() are separate native operations. Packet sends retain
the exact native return and last error without claiming that the last error
describes every failed operation. Empty successful P2P datagrams are unsupported.
ICE packet observers copy borrowed payload/address/timestamp metadata and use a
bounded drop-oldest queue with an observable loss counter. Writable, ready,
gathering, received/sent packet and route subscriptions retire on the network
role. Engine-owned candidate/error/removal/selected-pair wiring stays intact;
those notifications use peer events, including
IceSelectedCandidatePairChanged. The pin has no individual cancellation API
for receiving-state or internal ICE-state hooks, so transient observers do not
install them; receiving() and ice_transport_state() remain live reads.
Copied packet observations remain consumable after revocation. Actor/session
counterparts use retained transport IDs and the same native operations.
Channel labels, protocols and stats string values retain native bytes, including
non-UTF-8 and embedded NUL. Creation and channel errors retain the message bytes
exposed by the SDK. Use explicit UTF-8 decoding when text is required. PeerConnection::create_data_channel_bytes()
and ProductionSession::create_channel_bytes() accept an optional typed
configuration; None passes the native null/default configuration. The existing
UTF-8 label conveniences pass an explicit configuration. Signed optional
retransmission inputs reach native normalization and validation: negative values
disable that setting and values above 65535 are clamped before mutual-exclusion
validation. The deprecated initialization reliable flag is ignored upstream;
configuration snapshots expose the actual native reliability property.
Data-channel advisory events have bounded retention: each channel retains at
most one callback-time state snapshot, one buffered-amount notification and one
negotiated maximum-message-size snapshot. Intermediate states may coalesce.
Buffered-amount notifications accumulate bytes removed from the local send queue
since the preceding notification, saturating
at u64::MAX; they do not establish remote application delivery. Polling returns
send completions before advisory events, then messages. Each queue retains FIFO
ordering. Consuming the terminal closed snapshot discards queued message deliveries
and suppresses later observer events, but send outcomes remain available. These
advisory bounds do not bound received-message storage or establish
consumption-driven SCTP receive backpressure.
DataChannel::send_queue_capacity() and
ProductionSession::channel_send_queue_capacity() expose the native per-channel
send-queue limit. It is not a negotiated maximum message size or receive budget.
Sent denotes local admission only. DataChannel::error() and
ProductionSession::channel_error() copy the current native error into an owned
value, preserving its kind, diagnostic message, optional detail and optional
SCTP cause code. No error on an ordinarily closed channel is not a failure.
DataChannel::send_async() and ProductionSession::send_async() return a
channel-local DataChannelSendId. Consume DataChannelEvent::SendComplete for
exactly one owned native result, including the native error detail and SCTP
cause code when provided. Success means native local admission, not remote
application delivery. If the engine discards the callback (for example after
transport detachment), the outcome is DataChannelSendError::Canceled, not a
fabricated native failure. Up to 64 operations per channel can be outstanding,
including completed outcomes not yet consumed. Admission exhaustion or a
payload larger than the native send queue returns ResourceExhausted without
allocating an id. Native negotiated size/state errors arrive asynchronously.
Close does not discard admitted outcomes while the channel remains retained.
Dropping the channel releases application observability; in-flight callbacks
retain only their owned completion storage, not a raw handle or application
owner. Completions still notify readiness with observer registration disabled.
Controlled sends post tasks and require the caller's driver pump.
DataChannel::counters() copies cumulative native message/byte counters; the
fields are independently sampled, not an atomic multi-field snapshot. Receipt
counts native delivery, not Rust inbox consumption. reliable() queries native
reliability. The session exposes equivalent owned queries together with label,
configuration, state and buffered amount. Priorities support the four convenient
presets and DataChannelPriority::Custom(u16) for the full native value range.
Native handling of explicitly assigned in-band ids and unassigned negotiated
ids is preserved rather than rejected by a binding policy.
The pinned PeerConnection data-channel path is push-based. Native dcSCTP
reassembly has its own receive window; native pre-observer/connecting delivery
also has an engine-owned queue which can close a channel with ResourceExhausted
on byte overflow. Neither is a Rust consumption-credit API. Once our observer
receives a message, its owned copy waits in the Rust inbox until consumed or
shutdown discards it. A paused Rust consumer can therefore accumulate message
storage; application consumption policy remains outside the binding. The
generic dcSCTP socket pull API is not exposed as a stock PeerConnection capability.
Native allocation, reset and stream-ID reuse behavior are preserved.
DataChannel::set_event_observation and the session counterpart expose native
observer registration/unregistration. Disabling quiesces new channel callbacks,
not transport; already-owned Rust events remain available. While disabled, the
engine can buffer messages or fail on its own overflow limit, and channel
observer activity does not notify readiness. Send completions still do.
Re-enabling drains native queued messages
when open and samples current state. This is not a consumption-credit API.
Peer state/negotiation advisories retain only their latest callback-time owned
snapshot, not a complete transition history or a later sample of engine state.
Non-advisory controls retain at most 128 records/256 KiB of string payloads,
plus four scalar advisory slots. Arrival maps share a 128-handle cap.
PeerConnection::event_observation() and
ProductionSession::peer_event_observation() expose coalescing, overflow and
current retention counters. Overflow drops a new candidate/error/arrival
observation, never an accepted operation terminal or a native SCTP channel.
This is binding observation capacity, not a transport window or a guarantee
that every remote resource is delivered to the application. Explicit close
discards queued controls/arrival handles, while preserving accepted terminals;
previously returned owned outputs remain readable.
PeerConnection::request_stats() returns Result<OperationId, PeerError>, then a typed
PeerConnectionEvent::Stats snapshot or a terminal operation error. The
snapshot's report retains the actual complete immutable SDK report, without
a 256-record truncation. Its records field remains a convenience projection
of candidate-pair, transport, inbound/outbound RTP and data-channel records.
Take each result event before requesting another; one pending or unconsumed
report retains admission per peer. Sender/receiver selectors and native cache
clearing are exposed locally and through ProductionSession. Optional selector
methods preserve the native empty-report result for null or foreign selectors;
they do not substitute a full peer report or fabricate a selector error.
RtcStatsReport supports native get/type filtering, ordered iteration, shared
retention, deep copying and exact native serialization. RtcStatsReportBuilder
owns native add/try-add/take/transfer operations and freezes without leaving
mutable aliases. Duplicate insertion/merge preconditions are guarded rather
than reaching native checks. RtcStats exposes all 20 pinned classes' typed
public fields, including inherited/deprecated fields, native defaults, copies
and valid base copies. Five base classes have protected SDK constructors;
constructing them directly returns None, while native base copying is usable.
RtcStatsAttribute preserves all 16 typed optional scalar/container variants,
full-width integers, byte strings and map keys. Undefined is not zero or an
empty container. Native ToJson/ToString retain upstream quoting/precision
limitations; use typed values for exact data. JSON serialization requires finite
object timestamps. Reports and owned values remain readable after peer close
and can move independently between caller threads. Upstream RTT/jitter values
are seconds; available/target bitrates are bits per second.
request_legacy_stats(Standard | Debug) on peers and ProductionSession
uses the deprecated native collector and emits a separate LegacyStats result.
It selects all tracks. request_legacy_stats_with_track also forwards an
optional owning MediaTrack, obtained from local audio/video tracks or actual
sender/receiver track_reference() getters. The actor uses opaque
SessionMediaTrackId resources canonicalized by actual SDK object identity,
not textual track IDs. Native false rejects synchronously without a callback;
the SDK can accept a foreign object whose ID matches a remembered report.
Accepted selections retain the track's own source and role context through
SDK task-capture retirement. Controlled close/drop cancels only the known
observer continuation without advancing time or dispatching queued work.
PeerConnection::add_media_transceiver_with_init and the common
Session<R>::add_media_transceiver_with_init install existing owning track
references, including receiver tracks, with the full RtpTransceiverInit value.
Tracks must share the peer's factory. Local typed sources retain their existing
source checks; receiver-track leases retain native track/factory ownership
without retaining the receiving Rust peer. Native errors remain distinct from
binding safety failures. Closing the receiving peer can end the forwarded
track's native source; retaining a sender does not promise continued media.
Downstream allocator-priority numerical safety remains an open qualification
gap, described in the RTP contracts.
Owning endpoint MediaTrack references also expose typed source and capability
reads. Receiver streams() returns owning native MediaStream references with
byte-exact identity, audio/video traversal and native find operations. Track and
stream observe(NonZeroUsize) calls explicitly create independent SDK notifier
subscriptions, with bounded pending changes and observable overflow; aliases do
not subscribe as a side effect. ProductionSession canonicalizes the corresponding
reference IDs by native object identity and exposes the same reads and polling.
Remote source reads and notifier retirement run on their retained native role.
Source state is read from the SDK, including explicit receiver-stop transitions;
channel-survival policy is not a promise that peer close leaves a source live.
Absent processor/signal/stat values are not replaced with defaults.
Reports preserve native timestamps in milliseconds, report/metric names, all seven typed value variants, byte strings and native formatting. SDK Value references stay on signaling; copied payloads and retained immutable SDK IDs remain usable after peer teardown and caller-thread migration. ID equality uses the SDK comparison, not rendered strings. Legacy and modern requests share the one-pending-or-unconsumed-stats-result admission limit.
PeerConnection::video_transceivers(), video_senders(), and
video_receivers() return owned video-only handles from snapshots of upstream
transceivers. mid() is absent before negotiation and
may become absent after rollback. stop() initiates standard stopping; a
subsequent negotiation completes it, after which upstream removes the
transceiver from enumeration. An existing handle remains readable.
RtpSender::set_track(Some(&track)) replaces a live local video track from
the same peer factory without replacing the sender; set_track(None) detaches
it. The peer retains local tracks and their sources until replacement, removal,
close, or drop, even if caller-owned track and source handles are dropped.
Renegotiate if the remote track identity must change. A sender's track()
returns the retained local track when present. Endpoint aliases and local-track
leases use actual native sender identity, not textual track/sender IDs. A stopped
sender retained across completed renegotiation remains distinct from a new
sender even when the SDK reuses its textual ID.
RtpSender::dtmf_sender() is source-wired for the native audio sender's owning
DTMF proxy (native video senders return None). It preserves native default
and explicit comma-delay insertion, capability/getters, and both observer
callback forms through bounded polling, with an explicit retained guard against
consumed signed-delay overflow. Repeated getters share a registration; the
handle retains its peer/factory origin. New fixtures are unrun, not qualification.
See DTMF contracts for sequence, lifetime and loss rules.
video_sender_capabilities() and video_receiver_capabilities() expose
actual video RTP codec profiles, FMTP parameters, RTCP feedback and scalability
modes for each peer's factory. Select and order discovered sender capabilities
with RtpTransceiver::set_codec_preferences() before negotiating. Empty
preferences restore upstream defaults; unknown or repeated profiles fail.
Discovery includes resiliency codecs such as RTX, and does not install missing
H.264 encoder or decoder implementations.
VideoCodecFormat::scalability_modes preserves the native SDP format's declared
capabilities through custom encoder/decoder factories. Construct a recognized
mode with VideoScalabilityMode::parse() and add it using
with_scalability_mode(). Recognition is not codec support or selection: the
encoder must truthfully advertise it, and sender parameters select the mode.
Native SetParameters validates the capability list, so answering true in a
factory's support query alone does not enable a temporal profile. This format
API does not generate a GOP, dependency metadata or VLA.
VideoEncoderSettings reports the selected native scalability_mode and the
raw native h264_temporal_layers and per-stream simulcast_temporal_layers
counts independently at initialization. None is inferred from capability
declarations or submitted frames; an absent mode is preserved. The H.264 count
is absent for other codecs and may differ from the per-stream counts, which
also affect native H.264 initialization. An empty stream list preserves native
zero-stream configuration. Settings are Clone, not Copy.
Exposing these initialization facts alone does not select a temporal mode.
RtpReceiver::attach_encoded_sink() exclusively intercepts depacketized
encoded video access units before decoding. Its bounded four-frame/4 MiB queue
reports drops. Access units include optional RID, capture/receive timing, and
negotiated dependency-descriptor frame/layer/decode-target metadata; absent
metadata is not fabricated. For H.264, data is a depacketized Annex-B
access unit with start-code-delimited NAL units, not RTP payload fragments.
Closing the sink clears queued frames and resumes normal WebRTC decoding; this
pinned upstream receiver cannot safely be attached a second time, even after
close. This is not a raw RTP payload API.
RtpReceiver::attach_ordered_encoded_sink() instead forwards unchanged frames
through libwebrtc's native reference finder and frame buffer, observing the
selected encoded units through its recordable-frame callback. Exact native
SSRC/timestamp and payload identity associate metadata without custom ordering.
This mode requires a decoder accepting the negotiated codec and does not
suppress its decode calls. The encoded-only factory uses a no-output decoder;
built-in decoder factories still decode normally. Both sink modes share the
same receiver-lifetime attachment limit and bounded output queue. Ordered
observation also bounds pending metadata to 512 frames/4 MiB. Session encoded
video output uses this ordered mode, not raw interception.
For observations independent of polling retention, use
RtpReceiver::attach_ordered_encoded_sink_with_observer() or
Session::attach_raw_and_encoded_video_with_observers() for both encoded and
native-decoded callbacks on the same receiver. The encoded callback owns its
access unit and metadata, still bounded to 4 MiB per frame. Its Dropped event
reports unavailable native identity or pending metadata, not polling queue
eviction. Neither the decoder nor the existing polling queues change. Native
I420 can arrive before or after the encoded callback. Callbacks must not block,
await, or synchronously control the SDK. A panic retires the callback and sets
EncodedVideoSink::observer_failed() (or the session's
encoded_video_observer_failed()). Closing the sink retires its observer too.
RtpTransceiver::header_extensions_to_negotiate() returns the native complete
ordered extension snapshot. Change only each entry's direction, then apply
that full vector with set_header_extensions_to_negotiate() before the next
SDP negotiation. The engine validates the list and mandatory MID extension;
IDs and encryption remain native-owned. DD and VLA are disabled by default in
this pinned revision. negotiated_header_extensions() reports native negotiated
IDs and directions; stopped entries are not negotiated, including stopped
snapshots returned before negotiation. Negotiation does not prove that a frame
carried an extension. Libwebrtc computes and transmits VLA, but its pinned public
receiver, transformer and stats APIs do not expose received VLA allocations.
The encoded sink therefore makes no received-VLA claim. A test-only offline
native parser checks captured clear VLA/RID extensions against actual negotiated
IDs and native sender SSRCs after all controlled roots have been destroyed.
It proves sender wire generation, not authenticated received allocation; it
neither decrypts payload nor verifies SRTP authentication. The SDK still owns
metadata authentication. On Linux x86_64 with a matching core export:
python3 tests/build_native_vla_probe.py \
--kit "$PWD/.work/package/webrtc-core-linux-x86_64" \
--checkout-src "$PWD/.work/checkout/src" --output "$PWD/.work/native-probes/vla"
CARGO_HOME="$PWD/.work/cargo-home" \
PULSEBEAM_WEBRTC_SYS_ARTIFACT_DIR="$PWD/.work/package/webrtc-core-linux-x86_64" \
PULSEBEAM_NATIVE_VLA_PROBE="$PWD/.work/native-probes/vla" \
cargo test --offline --test controlled_media native_vla_wire_uses_provided_offline_parser \
-- --ignored --nocapture --test-threads=1The probe-dependent test is explicitly ignored in the default Rust suite; run it separately for wire qualification. It checks native full three-rung allocations, RID/index association, actual primary SSRC publication and the full captured UDP payload-size bound (leaving IPv4/UDP overhead within 1500 bytes). Sender-only three-rung runs also exercise fragmented synthetic opaque tails: the largest primary UDP payload observed was 1077 bytes, with full native VLA present on all three primary SSRCs. This does not establish simultaneous stock simulcast receipt or exhaustive MTU/retransmission behavior.
controlled_h264_opaque_tail_survives_fragmentation_without_decode separately
qualifies exact 4 KiB synthetic protected-tail reassembly for H264 without DD
and L1T3 with DD, on ordinary and loss/duplication/reordering links with exact
normalized replay. It retains clear Annex-B/NAL framing, valid SPS/PPS and the
slice fields consumed by the pinned native parser. Native may normalize the
unprotected SPS/VUI and framing, so only the protected VCL tail and its clear
slice prefix are compared. The SDK remains responsible for encryption,
authentication and syntax-safe protected framing; the fixture provides neither
cryptography nor a valid decodable-slice claim. Received keyframe, temporal,
dependency and SSRC metadata are checked independently. Native bitrate control
funds these large frames rather than overriding native allocation or pacing;
builtin H264 decode remains unavailable and encoded interception terminal.
For direct encoded sending, create EncodedVideoInput::new_for_format() with
an actual VP8, VP9, AV1 or H265 format (or EncodedH264Input::new() for the
constrained-baseline H.264 format), configure its encoder_factory() on the
peer factory builder, then call create_source() and create_track() on that
factory. Feed codec-matched EncodedVideoAccessUnit values with explicit
EncodedVideoMetadata via push_encoded(). Formats are advertised by the
supplied encoder factory; they are not claims that a bundled encoder exists.
A bare VP9, AV1 or H265 format uses the pinned upstream default fmtp; pass
explicit parameters when advertising another compatible profile or level.
The encoded bytes must match the negotiated format and the artifact must
include that codec's RTP packetizer. For H.264 the original push() method
accepts H264AccessUnit values with strictly increasing nonnegative
microsecond timestamps. Each value is
one Annex-B access unit (three- or four-byte start codes), with explicit
keyframe, dimensions and optional QP. Keyframes must contain SPS, PPS and IDR;
delta frames contain non-IDR slices. The advertised format is constrained
baseline, packetization mode 1, level 3.1; SPS must signal constrained
baseline profile and at most level 3.1. The native sender constructs a private
raw trigger solely to drive WebRTC's video stream scheduling. Caller-supplied
access-unit bytes bypass encoding, and WebRTC derives the RTP timestamp from
its capture clock. Each source has a stable stream_id; a bounded shared
16-capture-token/8 MiB pending queue evicts oldest complete captures under pressure, with
per-source dropped_frames() and pending_frames() observability.
take_encoder_error() retrieves and clears the latest source-keyed adapter
rejection or encoded callback failure; repeated errors coalesce and rejected
queued units also increment that source's drop count. This adapter
does not supply a decoder. Ordinary direct input does not support encoded
simulcast; spatial SVC and H.264 packetization mode 0 are unsupported.
The external producer owns encoded bitrate and pacing. The passthrough encoder
sets native has_trusted_rate_controller, preventing bitrate-based frame
optimization from discarding already-encoded references. This follows the
LiveKit passthrough contract,
not a process-wide field-trial override. Native scheduling, congestion handling
and transport remain active. Consume latest_rate_control() and keyframe
feedback; WebRTC cannot recompress a recording to meet a lower target bitrate
or synthesize a requested IDR. Select an appropriate recording/rendition and
resume from a genuine keyframe when recovery is requested.
Other native pre-encode drops and pending-queue eviction can still break an
encoded reference chain. The adapter tracks source-local submission order,
never gaps in factory-global presentation tokens. It withholds dependent
deltas, requests a keyframe, and reports those holds through native
OnFrameDropped, without fabricating encoder errors. A successful keyframe
callback restores continuity. Simulcast encodings recover independently;
inactive/unfunded encodings and failed callbacks do not establish continuity.
Temporal input conservatively requires a keyframe after any missing capture;
this does not infer compressed-frame dependencies from timestamps or metadata.
EncodedH264Input::new_simulcast() opts into three-rung H.264 sending through
native per-image callbacks. Create a sender with three ordered RIDs, then use
push_simulcast([unit0, unit1, unit2]) with matching capture timestamps, ascending
width/height and explicit simulcast indices 0..2. All three units are validated
and reserved as one bounded capture token; pending_frames() counts access
units, and eviction/drop accounting counts every unit. Images are published
separately, so a later native callback failure cannot retract an earlier image.
Native initialization must retain three ordered streams with matching active
rung geometry. Collapsed senders and ambiguous single-encoder fallback are
rejected asynchronously with InvalidConfiguration, including the native
single-active-rung fallback. Geometry is not substituted for encoding identity.
Inactive or unfunded rungs can be dropped observably. Native per-encoding
keyframe feedback is available via take_encoding_keyframe_requests(); this
has the same callback-only, non-idle guarantee. ProductionSession::new_simulcast
and push_video_simulcast expose the same bounded path in the actor.
PeerConnection::set_bitrate and actor set_peer_bitrate forward optional native
bandwidth-estimation constraints and native validation; they do not inject a
per-rung allocation. The initial native allocation can fund fewer than three
rungs. Stock libwebrtc does not receive all three simulcast rungs concurrently;
sender-only SFU-answer tests and outbound RID/SSRC statistics are not evidence
of simultaneous receipt or received DD/VLA on those rungs.
EncodedH264Input::new_l1t3([64, 128, 255]) opts into single-encoding H.264
three-temporal-layer input. The supplied nonzero, nondecreasing cumulative
fractions must end at 255 and are passed to native encoder capabilities, not
used to construct a GOP or VLA. Negotiate native DD on both peers, then select
L1T3 on the retained sender's parameters. Selection without negotiated DD
fails before native parameter mutation. Submit explicit temporal indices 0..=2
and H.264 base_layer_sync metadata; keyframes use index 0. Native initialization
must actually select L1T3 with one three-temporal-layer stream, or queued input
is rejected observably rather than emitted unlayered. Libwebrtc infers frame
dependencies from the supplied codec metadata. Its pinned H.264 fallback DD
structure has four decode targets, which are preserved, not rewritten to three.
Producers remain responsible for truthful compressed references and signaling.
ProductionSession::new_l1t3(config, fps) exposes the same profile in the movable
actor with owned extension snapshots and source-keyed encoder errors. take_keyframe_request() reports
and clears sender feedback after
WebRTC asks the adapter to encode a frame; a delta frame submitted during a
keyframe request is rejected. Idle sources have no keyframe-notification
promise: polling does not schedule Encode, synthesize frames or substitute
observed RTCP for native encoder feedback. latest_rate_control() exposes the last
native rate update for the encoder associated with the source's presentation
token. Later native rate callbacks update it without another submitted unit.
For ProductionSession, newly recorded keyframe/rate feedback and asynchronous
encoder errors notify actor readiness; unchanged rate and pending keyframe
snapshots coalesce. Notification does not dispatch engine work or create an
idle-source keyframe guarantee. Standalone sources retain polling access.
VideoRateControl::layer_bitrates_bps preserves the native five-by-four
spatial/simulcast and temporal allocation matrix: None is unset and Some(0)
is explicit zero; cells are per-layer, not cumulative. This is the effective
encoder allocation after native bitrate adjustment. target_bitrate_bps and
target_layer_bitrates_bps report the separate target from the actual native
SetRates callback, before that adjustment. Each allocation retains its own
bandwidth-limited flag. Neither is reconstructed from the caller's last request;
native simulcast adapters can slice allocations before product delivery. These
values are rate guidance, not received or independently declared VLA. The generic
EncodedImageCallback::emit_with_metadata() path also accepts codec-specific
VP8/VP9 packetization fields, native H.264 base_layer_sync, and explicit
simulcast, spatial and temporal indices for caller-provided encoders. These
fields are passed to libwebrtc, not used for binding-generated dependencies or
VLA; emit() retains the ordinary
single-layer behavior. Ordinary direct inputs reject layered units; the explicit
H.264 L1T3 profile allows its three temporal indices, and the explicit simulcast
profile admits exactly three RIDs and non-scalable rung encodings. Other SVC
modes and unsupported resolution changes are rejected before sender mutation.
RtpReceiver::request_keyframe() submits an RTCP keyframe request for a live
remote video receiver without guaranteeing that a remote sender honors it.
RtpSender::request_keyframe(&rids) submits a keyframe request for an
active sending video stream. An empty RID list targets all encodings; invalid
RIDs fail. Success means that the request was accepted, not that a keyframe
was emitted or that received video can be requested through this API.
PeerConnection::add_video_transceiver_with_rids configures explicit,
unique encoding RIDs before negotiation. Invalid RID identifiers and duplicate
RIDs fail before modifying the peer; upstream validation may reject additional
combinations. An empty list retains the default single encoding. Creation
configures identity, not a guarantee that the selected codec can emit every
encoding or that simulcast is negotiated with a remote peer.
A video transceiver's RtpSender::parameters() returns an owned encoding
snapshot. Edit encodings and pass the snapshot to set_parameters() on the
same sender handle. Supported fields are active state, maximum bitrate and
framerate, relative and absolute resolution scale, and scalability mode; RID
is inspectable but cannot change without renegotiation. Unsupported or invalid
values fail explicitly. A successful update consumes the snapshot transaction,
so obtain fresh parameters before another update. Absolute resolution scale
supersedes relative scale when both are set. Encoding order and count are
negotiated, not mutable here. This does not establish simulcast negotiation or
SVC interoperability.
The Linux artifact tests validate configuration, two-peer ICE restart through
renewed credentials and packet delivery, connected-peer stat relationships,
simulated IPv4 and IPv6 STUN Binding responses represented as server-reflexive
candidates in gathered SDP, authenticated TURN UDP/TCP relay through a local
hostname-resolved coturn fixture, authenticated TURN/TLS relay using an
additional fixture CA, rollback and pending/current SDP transitions, and
Rust-only linking. An explicitly invoked IPv6 coturn fixture also verifies
relay-only UDP connection over the host's global IPv6 interface, using gathered
SDP rather than individual candidate forwarding. This is host-dependent, so the
IPv6 test is ignored by the default suite and fails explicitly without a global
IPv6 interface. Run it inside the Linux runtime image with cargo test --test turn relay_only_ipv6_udp_with_gathered_sdp -- --ignored, setting the same artifact,
Cargo home, and target-directory environment as _runtime-test. This establishes
one real Linux IPv6 path, not qualification for all production networks.
The UDP fixture checks that bad TURN credentials produce no relay candidate and
report a candidate error. The TLS fixture rejects an untrusted CA and a
hostname mismatch. Terminating the TCP relay after connection exposes
transport loss to the peer. Trickle ICE and remote end-of-candidates are outside
this non-trickle signaling contract; no upstream patch is required.
This repository contains the low-level Rust bindings but no rendering code. The
intended pulsebeam-libwebrtc feature model uses core by default, lets an
additive native feature select the matching native artifact, and keeps
rendering independently selectable so core applications can render without
enabling platform devices.
wgpu is the intended common renderer for Android, iOS, macOS, Windows, and
Linux. The initial path uploads decoded I420 or NV12 planes and performs color
conversion, scaling, cropping, and rotation in shaders; the application owns
the window or view, surface lifetime, and UI-thread integration. Zero-copy
interop with CVPixelBuffer, AHardwareBuffer, Direct3D textures, and DMA-BUF
is deferred.
This repository owns the immutable source/tool pins, target matrix, GN arguments, private CXX definition, native adapters, low-level Rust package, artifact metadata, release workflow, consumer tests, and licenses needed to make releases trustworthy. Its own code is Apache-2.0 licensed. It must not vendor WebRTC, depot_tools, generated build trees, output archives, application or simulator code, production codec implementations, or a source patch stack beyond the documented immutable core-iOS closure delta. Build workspaces and release artifacts are disposable local/CI output.
Non-goals include:
- maintaining a WebRTC or BoringSSL fork;
- certifying H.264 patent clearance or licensing a consuming product;
- deterministic cryptographic output;
- exposing CXX or libwebrtc's C++ ABI as the public Rust API;
- implementing Rust, wgpu, or zero-copy rendering here;
- publishing debug artifacts or physical-device test infrastructure;
- promising byte-identical builds across runners; and
- depending on or adapting LiveKit
webrtc-sys.
For a routine WebRTC upgrade, rehearse the proposed 40-character commit in a
disposable checkout using tools/select_upgrade_pin.py, then refresh the CXX
bridge and compile/link the core Linux adapter locally. Once reviewed, change
only webrtc_commit near the top of the Justfile, run just check, and push
a new version tag to run the qualified release workflow.
Change depot_tools_commit only when WebRTC compatibility requires it. Change
build arguments or target selection only to repair an observed required-matrix
failure.
Upgrade CXX as one reviewable change:
- From the crates.io index and the matching upstream tag, update both the
cxxandcxxbridge-cmdpackage versions and checksums plus the repository tag and commit invendor/cxx/provenance.json. - Run
just refresh-cxx. Review the imported inventory and regenerated per-file digests; do not edit any imported file. - Pin the same exact version in the root
Cargo.toml, then updatecxxbridge-macroandCargo.lockwith Cargo. Change the overlay only for an intentional packaging requirement and describe that delta in provenance. - Run
just check, run the applicable native artifact build, then runjust refresh-cxxonce more and confirm that it produces no diff.
The pinned native SDK inventory and mapping defines the Linux x86_64 declaration scope independently of existing bindings. The focused capability-to-API/test matrix describes selected Rust surfaces, native consumer paths and their limits; it is not a complete SDK ledger. The table below describes repository jobs, not proof of every capability.
| Contract | Automated job or release check |
|---|---|
| Offline schemas, extraction, target/flavor substitution, link translation, source cleanliness, CXX inventory, and Rust-only dependency graph | CI and release / Source checks (just check) |
| Complete bridge and extracted-artifact C++/Rust compile/link for Linux x86_64 core and native | CI and release / Build plus audit_release.py |
| Deterministic execution/network, signaling, data channel, injected video path, and ordered teardown for both Linux flavors | Two Runtime matrix checks |
| Observer, callback, partial-construction, close/drop, and provider lifetime under instrumentation | Two ASan tests jobs |
| Fresh Git dependency, cold artifact and target caches, identity probe, host runtime smoke, and forbidden C/C++ compiler sentinels | CI and release / Released consumer |
| Mechanical CXX/generated-bridge refresh and pin changes | just refresh-cxx and Linux release qualification |
| Immutable URLs/checksums, complete external lock, licenses, notices, and attestations | Tag-triggered publish and released-consumer gates |