Introduction
Mediaway is a Rust media stack for encode, decode, mux/demux, and device capture (camera, mic, screen) — built as high-level pipelines composed from first-class low-level surfaces, not a monolith that hides them.
Three ideas run through the whole stack:
- Zero-Copy paths. Video moves through GPU handles (
GpuBufferHandle) or shared CPU buffers wherever the platform allows it. When a copy, upload, or readback is unavoidable, the API names it — never a silent slow default. - Sans-IO cores. Muxers, demuxers, and bitstream/timebase logic are pure state machines: you push bytes or packets in, poll bytes or packets out. No file handles or sockets inside the core, so the same logic runs unchanged on native hosts and in WASM.
- Low-level APIs stay public.
VideoEncoder,VideoDecoder,Muxer,Demuxer, and friends are the real surface.mediaway’sEncodeSessionandplatform::Auto*helpers are convenience wrappers over them, not a gate you have to go around.
Is Mediaway ready for my project?
Not for production yet. Mediaway is early development (0.x), pre-1.0:
public APIs, crate layout, and backend behavior can change without a
deprecation cycle. See Status & Stability for what that
means concretely and when to reconsider.
It’s a good fit today for experimentation, integration spikes that can tolerate breakage, and contributing to a platform still taking shape.
Where to go next
- New to the crates? Start with Installation and Quick Start.
- Want a worked example for a specific task? See Guides.
- Need to know exactly what’s implemented on your platform/codec/GPU combo? See Reference — those tables are pulled directly from the project README, so they stay in sync automatically.
Design rationale beyond what this book covers lives in the repository’s
docs/spec/ —
this book is the user-facing guide, docs/spec/ is the engineering SSOT.
Installation
Mediaway is not published on crates.io yet (Status & Stability).
Depend on it via git in your Cargo.toml, pinning a revision so pulls don’t
surprise you with breakage:
[dependencies]
mediaway-common = { git = "https://github.com/nyxways/mediaway", rev = "<commit-sha>" }
mediaway-container = { git = "https://github.com/nyxways/mediaway", rev = "<commit-sha>" }
# add mediaway-encoder / mediaway-decoder / mediaway-device / mediaway as needed
Pin the same rev across every mediaway-* crate you depend on — the
workspace evolves as one unit pre-1.0.
Toolchain
Mediaway targets stable Rust (pinned in rust-toolchain.toml).
rustup picks up the pin automatically once your project is inside (or
depends on) the Mediaway workspace tree.
Platform notes
Not every backend is available everywhere yet — check Codec Support and Device for the current matrix before depending on a specific codec/platform combination.
- Windows — WMF/DX11 encode+decode, DXGI/WGC/WASAPI capture. No extra setup beyond the Rust toolchain.
- Web (wasm32) — add the
wasm32-unknown-unknowntarget (rustup target add wasm32-unknown-unknown) for the*-web/*-wasmcrates. WebCodecs/getUserMedia/getDisplayMediabackends run inside a real browser, not in a headless test runner. - Linux — VA-API (
mediaway-*-linux) needs/dev/driand a workinglibvadriver on the host; portal/PipeWire capture needsxdg-desktop-portaland a PipeWire session (typical on a desktop session, often absent in containers/CI/WSL2). - Apple / Android — not implemented yet (see the reference tables).
Optional: system FFmpeg
Mediaway never links FFmpeg/libav* in shipped crates (MIT OR Apache-2.0
only). A system ffmpeg/ffprobe on PATH is only ever used as an optional
test/dev oracle in this repository’s own test suite — it is never required to
build or run your application.
Quick Start
The fastest way to see Mediaway work is the mux/demux roundtrip: register
tracks, push packets, pull bytes out as fragmented MP4, then demux the same
bytes back. It’s pure Rust — no OS codec, no unsafe, runs on every platform.
cargo run --example mux_roundtrip
Expected output looks like:
mux_roundtrip: 90 frames → NNNN bytes of fMP4
mux_roundtrip: demuxer sees 2 stream(s)
stream 0 — H264 1920×1080
stream 1 — Aac (no geometry)
mux_roundtrip: recovered 90 video + 90 audio packets
mux_roundtrip: OK
Full source and a line-by-line walkthrough: Mux + Demux Roundtrip.
Next steps
- Want to actually encode video (not just mux pre-made bytes)? See Encode to MP4.
- Want to capture the screen and encode it live? See Screen Recording.
- Want to edit — trim and splice clips using the low-level decoder/encoder traits directly? See Decode, Trim & Splice.
- Need the exact platform/codec support matrix? See Codec Support.
Container: Mux + Demux
mediaway-container wraps eight freestanding, sans-io container cores
(iso-bmff, ebml-webm, riff-wave-core, adts-core, mpeg-audio, ogg, flv,
mpeg-ts-core) behind one shape: register tracks, push packets, poll bytes out —
and the mirror for demux. None of it touches a file handle or socket; I/O is
entirely the caller’s job.
This guide walks the MP4 (mp4::Muxer/mp4::Demuxer) shape, which every
other format variant follows closely.
Muxing is a typestate
Muxer::new() starts in an Open state, where you register every track
you’re going to write:
let mut muxer = mp4::Muxer::new();
let video_track = muxer.add_track(StreamInfo::Video {
id: 0,
codec: CodecKind::H264,
time_base: Rational::new(1, 30),
geometry: VideoGeometry { width: 1920, height: 1080 },
extra_data: Bytes::new(),
})?;
muxer.begin() transitions Open → Live. That’s a real type change, not a
runtime flag — once you call it, add_track is no longer callable on the
result. The compiler enforces “register tracks, then stream packets,” not a
convention you have to remember.
let mut muxer = muxer.begin();
Streaming packets, streaming bytes
Each Packet carries pts/dts/duration in the track’s own time base, an
is_keyframe flag, and the payload bytes:
muxer.push_packet(&Packet {
stream_id: video_track,
pts: 0,
dts: 0,
duration: 1,
is_keyframe: true,
is_discard: false,
payload: encoded_bytes,
})?;
muxer.flush();
let mut mp4_bytes = Vec::new();
muxer.poll_bytes(&mut mp4_bytes);
poll_bytes drains whatever the muxer has ready into a buffer you own —
write it to a file, stream it over a socket, hand it to another crate. The
muxer never makes that choice for you.
Demuxing is the mirror
let mut demux = mp4::Demuxer::new();
demux.push_bytes(&mp4_bytes);
for stream in demux.streams() {
println!("stream {} — {:?}", stream.id(), stream.codec());
}
while let Some(packet) = demux.poll_packet() {
// route by packet.stream_id
}
push_bytes can be called incrementally as bytes arrive — over a network,
say — not just once with a whole buffer like the snippet above.
Extra data (avcC) for H.264
Notice the extra_data: Bytes::new() above — for H.264 tracks you can leave
it empty. The muxer derives a proper avcC record from the first packet’s
in-band SPS/PPS, rather than requiring the caller to pre-assemble one.
Try it
cargo run --example mux_demux_mp4
examples/container/mux_demux_mp4.rs
is the full, compiling version of everything above, including audio (AAC)
as a second track — see it run.
Other formats
Same marks, same shape, different framing quirks — WAV needs a known-upfront
size, MP3 needs an explicit padding bit, MPEG-TS uses a fixed 90 kHz clock.
See Container Support for the full
format matrix, and mediaway-container/adr/0002 for why a few formats don’t
fit the shared Mux/Demux trait shape exactly.
Encode
The low-level surface is the VideoEncoder trait: push frames in, poll
packets out. mediaway’s platform::AutoEncoder picks the best
backend available on the current platform (Windows WMF today; VA-API on
Linux) and hands you back something that implements it.
Push / poll, not call-and-block
let config = AutoVideoEncodeConfig {
bitrate_bps: 1_000_000,
..AutoVideoEncodeConfig::new(CodecKind::H264, 320, 240, Rational::new(1, 30))
};
let mut encoder = platform::AutoEncoder::open(&config)?;
encoder.push_frame(&frame)?;
while let Some(packet) = encoder.poll_packet()? {
// packet.payload is compressed bitstream data
}
An encoder may buffer internally (B-frame reordering, rate control lookahead)
— push_frame doesn’t promise a packet back immediately, which is why the
poll loop runs after every push, not just once at the end.
Flushing
When you’re done pushing frames, flush and drain whatever’s still buffered:
encoder.flush()?;
while let Some(packet) = encoder.poll_packet()? {
// final packets
}
Zero-Copy vs CPU upload
AutoVideoEncodeConfig’s max_path_class controls how far up the cost
ladder the encoder is allowed to go: ZeroCopy (GPU handle straight into the
hardware encoder, no payload memcpy) down through CpuUpload (a CPU
buffer gets uploaded to the GPU encoder session) — never a silent slow
default. Passing a gpu_device (e.g.
GpuDeviceHandle::DirectX11(handle)) is what makes the Zero-Copy path
reachable; without one, CpuUpload is as far as it goes.
Try it
cargo run --example encode_h264
examples/encode/encode_h264.rs
is the complete, compiling version — encoder in isolation, no muxing, no
capture, so you can see exactly what push/poll/flush produces on its own.
For turning that packet stream into a playable file, see Pipelines.
Decode
The mirror of encode: VideoDecoder’s push_packet / poll_frame / flush
shape, dispatched cross-platform through platform::AutoDecoder.
Push / poll
let config = VideoDecoderConfig {
extra_data,
output: VideoOutputPreference::CpuFramesOk,
..VideoDecoderConfig::h264(320, 240, Rational::new(1, 30))
};
let mut decoder = platform::AutoDecoder::open(&config)?;
decoder.push_packet(&packet)?;
while let Some(frame) = decoder.poll_frame()? {
// decoded pixel data
}
Same reasoning as encode: a decoder may hold packets internally waiting for a full frame’s worth of data (B-frame reordering again), so a packet going in doesn’t promise a frame coming out on that same call.
extra_data matters
extra_data carries the codec’s out-of-band configuration — SPS/PPS for
H.264, wrapped as avcC. Get this from wherever the bitstream came from
(a demuxed StreamInfo::Video::extra_data, or an encoder’s own
stream_info() if you’re decoding what you just encoded, as the example
below does).
Zero-Copy output
Like encode, VideoDecoderConfig::output controls the cost ladder:
ZeroCopyGpu keeps decoded frames GPU-resident (paired with a gpu_device);
CpuFramesOk accepts a CPU readback when there’s no GPU path, or none is
needed.
Try it
cargo run --example decode_h264
examples/decode/decode_h264.rs
is the complete, compiling version. It encodes a few frames first purely to
have real H.264 bytes to feed the decoder — that setup step isn’t the point
of the example, decode is.
For a full edit built from decode + encode together, see Pipelines.
Device
Four capture sources share two traits — VideoCapture (screen, window,
camera) and AudioCapture (microphone) — both a poll loop over frames, no
encoding involved. What differs per source is how much of it is wired into
mediaway_pipeline::platform’s cross-platform dispatch versus needing a
platform-specific type directly.
Screen — fully dispatched
let config = VideoCaptureConfig::screen(Select::Default, Rational::new(1, 30));
let mut capture = platform::ScreenCapture::open(&config)?;
while let Some(frame) = capture.poll_frame()? {
// consume frame …
capture.release_frame()?;
}
capture.close()?;
release_frame matters for GPU-backed sources: it frees the backend’s hold
on that frame (e.g. DXGI’s ReleaseFrame) before the next poll_frame can
acquire again.
Try it: cargo run --example capture_screen —
examples/device/capture_screen.rs.
Microphone — fully dispatched
let config = AudioCaptureConfig::microphone(Rational::new(1, 48_000));
let mut mic = platform::Microphone::open(&config)?;
while let Some(frame) = mic.poll_frame()? {
// frame.data is interleaved PCM
}
Try it: cargo run --example capture_microphone —
examples/device/capture_microphone.rs.
Camera — platform type directly
Camera capture isn’t wired into platform yet, so reach for the Windows
backend directly — it compiles on every platform (a stub returns
CaptureError::Unsupported off Windows, same failure shape as a missing
camera on Windows itself):
let config = VideoCaptureConfig {
source: CaptureSource::Camera { select: Select::Default },
time_base: Rational::new(1, 30),
// Media Foundation's camera backend is CPU-frames-only today.
output: CaptureOutputPreference::CpuFramesOk,
gpu_device: None,
};
let mut camera = mediaway_device_windows::WindowsCameraCapture::open(&config)?;
Try it: cargo run --example capture_camera —
examples/device/capture_camera.rs.
Window — needs a caller-owned GPU device
Window capture (WinRT Graphics Capture) is the one source with no
CPU-only path — open() requires both a live HWND and a caller-owned
ID3D11Device handed in as gpu_device: Some(GpuDeviceHandle::DirectX11(handle)).
Obtaining either means calling raw Win32/WinRT FFI, which is unsafe — out
of scope for a plain example, so
examples/device/capture_window.rs
only shows the config shape. For a complete, hardware-tested version with
the unsafe fully contained and documented, see
crates/mediaway-device-windows/src/lib_tests.rs’s
open_window_capture_foreground_or_skip test in the repository.
What’s available where
See Device for the full platform/source support matrix.
Pipelines: Composing It All
The guides so far cover one capability at a time. Real applications compose
several — capture into encode, encode into mux, decode into edit into
re-encode. mediaway supplies EncodeSession for the common
encode→mux case; everything past that is the low-level traits from the
earlier guides, wired together by your own code, exactly like the examples
below do.
Encode to MP4
EncodeSession wraps one VideoEncoder + a single-track mp4::Muxer,
draining poll_packet into the muxer on every write_frame call so you
don’t hand-write that loop:
let encoder = platform::AutoEncoder::open(&config)?;
let mut session = EncodeSession::open(encoder)?;
session.write_frame(&frame)?;
let mp4_bytes = session.finish()?; // flush + mux flush + poll_bytes
EncodeSession is generic over the encoder type — no Box/dyn overhead
beyond whatever platform::AutoEncoder::open itself returns. It’s a
convenience layer, not a gate: the manual push/poll/mux loop from the
Container and Encode guides stays fully
usable if you need something EncodeSession doesn’t do (e.g. a second
track — see below).
Try it: cargo run --example encode_to_mp4 —
examples/pipeline/encode_to_mp4.rs.
Screen Recording — video + audio
EncodeSession is deliberately video-only, single-track — adding a second
(audio) track means composing it yourself against a shared mp4::Muxer,
the same pattern the workspace’s own hardware-verified integration test
uses:
let mut open = Muxer::with_fragment_batch(2);
let video_track = open.add_track(video_encoder.stream_info().clone())?;
let audio_track = open.add_track(audio_encoder.stream_info().clone())?;
let mut mux = open.begin();
// … capture screen + mic, push into their respective encoders, poll packets,
// mux.push_packet each with the right stream_id …
mux.flush();
let mut bytes = Vec::new();
mux.poll_bytes(&mut bytes);
Screen and microphone capture come from platform::ScreenCapture /
platform::Microphone; audio encode has no cross-platform dispatcher yet,
so the example reaches for mediaway_encoder_windows::WindowsAudioEncoder
directly (it compiles everywhere, degrading gracefully off Windows — see
Device for the same pattern applied to camera).
Try it: cargo run --example screen_record —
examples/pipeline/screen_record.rs
produces out_screen.mp4 with real captured audio muxed as a second track.
(Video frames are still a synthetic placeholder — the example’s doc comment
explains why BGRA→NV12 conversion is a separately-tracked gap, not silently
skipped.)
Trim & Splice
A non-linear edit built entirely from the low-level VideoDecoder/
VideoEncoder traits plus container mux/demux — no new DecodeSession or
EditTimeline abstraction. The shape:
- Encode two short clips, mux each to fMP4.
- Demux + decode each clip back to
Vec<VideoFrame>. - Trim — slice the decoded frames by index/PTS; no new type needed.
- Splice —
Iterator::chainthe trimmed segments, then renumberpts/durationcontiguously (encoded timestamps must be monotonic). - Re-encode the spliced frames and mux the result.
let trimmed_1 = &decoded_1[1..decoded_1.len() - 1];
let trimmed_2 = &decoded_2[1..decoded_2.len() - 1];
let spliced: Vec<VideoFrame> = trimmed_1.iter().chain(trimmed_2.iter())
.enumerate()
.map(|(i, f)| VideoFrame { pts: i as i64, duration: 1, ..f.clone() })
.collect();
Try it: cargo run --example trim_and_splice —
examples/pipeline/trim_and_splice.rs.
Detail on what this composition surfaced (an AVCC-vs-Annex-B extradata bug):
docs/ai/wiki/pipeline/trim-and-splice.md
in the repository.
Examples
Every guide in this book is paired with runnable examples in the repository. The Rust
workspace holds the canonical set under examples/,
and the bindings directories mirror them for each supported host language:
| Language | Interop path | Examples |
|---|---|---|
| Rust | Native crates | examples/ |
| C | Direct C ABI (mediaway-ffi) | bindings/c/examples/ |
| C++ | Thin RAII C ABI | bindings/cpp/examples/ |
| C# | P/Invoke | bindings/csharp/examples/ |
| Python | ctypes / cffi | bindings/python/examples/ |
| Node.js | Native Addon / N-API (koffi FFI today) | bindings/nodejs/examples/ |
| Browser | WASM + WebCodecs (no C FFI) | bindings/browser/examples/ |
Examples are grouped by capability, mirroring the guides:
container/— mux + demux only (no codec, no capture)encode//decode/— one codec direction, no containerdevice/— one capture source, no encodepipeline/— composed end-to-end flows (capture → encode → mux, or decode → trim → re-encode)
The language pages below list each language’s example files and how to run them.
Rust
The primary, always-first-class API. Every capability is reachable from native crates on
crates.io (mediaway, mediaway-encoder, mediaway-decoder, mediaway-device,
mediaway-container, the *-core format crates, …).
Install
cargo add mediaway # umbrella: pipeline + platform dispatch + re-exports
# or a specific capability / freestanding core:
cargo add mediaway-encoder mediaway-container iso-bmff ogg-core
Runnable examples live in the workspace examples/
directory, grouped by capability:
| Capability | Example | Run |
|---|---|---|
| Container | container/mux_demux_mp4.rs | cargo run --example mux_demux_mp4 |
| Encode | encode/encode_h264.rs | cargo run --example encode_h264 |
| Decode | decode/decode_h264.rs | cargo run --example decode_h264 |
| Device | device/capture_camera.rs · capture_microphone.rs · capture_screen.rs · capture_window.rs | cargo run --example capture_screen |
| Pipeline | pipeline/encode_to_mp4.rs · screen_record.rs · trim_and_splice.rs | cargo run --example screen_record |
The guides in this book walk through the same flows with annotated code:
API reference: Crate Docs (docs.rs).
C
C hosts link directly against the mediaway-ffi
C ABI facade — one shared library, opaque handles, integer status codes, and
hand-written headers under include/mediaway/. Status: ✅ verified (real binding source
built and run against the native libraries).
Build and minimal usage
# Windows example from the examples (link against the built mediaway_ffi)
gcc -Icrates/mediaway-ffi/include bindings/c/examples/container/mux_roundtrip.c \
-Ltarget/x86_64-pc-windows-gnu/debug -lmediaway_ffi -o mux_roundtrip.exe
#include <mediaway/container.h>
mediaway_muxer_t *muxer = mediaway_muxer_create();
mediaway_video_track_info_t track = {
.id = 0, .codec = MEDIAWAY_CODEC_H264,
.time_base = { 1, 30 }, .width = 1920, .height = 1080,
};
mediaway_muxer_add_video_track(muxer, &track);
mediaway_muxer_begin(muxer);
/* push packets, flush, drain with mediaway_muxer_poll_bytes */
mediaway_muxer_close(muxer);
Examples live in bindings/c/examples/:
| Capability | Example files |
|---|---|
| Container | container/mux_roundtrip.c |
| Device | device/camera_record.c · capture_microphone.c · capture_screen.c |
| Pipeline | pipeline/encode_audio.c · encode_to_mp4.c · screen_record.c |
Build and run instructions: bindings/c/README.md.
C++
A thin RAII wrapper over the mediaway-ffi
C ABI — for native desktop apps, custom engines, and rendering pipelines. Status:
✅ verified.
Note: the C++ RAII wrapper surface is at design stage — the examples target it (nothing compiles yet). The underlying
mediaway-ffiC ABI is real and link-run verified; C++ apps can call it directly today. Build with CMake perbindings/cpp/README.md.
Examples live in bindings/cpp/examples/:
| Capability | Example files |
|---|---|
| Container | container/mux_roundtrip.cpp |
| Device | device/camera_record.cpp · capture_microphone.cpp · capture_screen.cpp |
| Pipeline | pipeline/encode_audio.cpp · encode_to_mp4.cpp · decode_roundtrip.cpp · screen_record.cpp |
Build and run instructions (CMake): bindings/cpp/README.md.
C# (.NET)
Windows desktop apps (WPF/WinUI) and Unity native plugins call Mediaway through P/Invoke
against the mediaway-ffi
C ABI. Status: ✅ verified.
Install
dotnet add package Mediaway.Container # container mux/demux (pulls Mediaway.Common)
dotnet add package Mediaway.Device # capture/playback; Mediaway.Device.{Camera,Desktop,Audio,Hotplug} subsets
dotnet add package Mediaway.Pipeline # encode pipeline
using Mediaway.Common;
using Mediaway.Container;
using var muxer = new Muxer();
muxer.AddTrack(new VideoTrackInfo
{
Id = 0,
Codec = CodecKind.H264,
TimeBase = new Rational(1, 30),
Width = 1920,
Height = 1080,
});
using MuxerSession session = muxer.Begin(); // Open -> Live
/* session.PushPacket(...); session.Flush(); drain with PollBytes() */
Examples live in bindings/csharp/examples/,
mirroring the Rust examples/ sector layout (Container/, Device/, Pipeline/):
| Capability | Example files |
|---|---|
| Container | Container/MuxRoundtrip.cs |
| Device | Device/CameraRecord.cs · CaptureMicrophone.cs |
| Pipeline | Pipeline/EncodeAudio.cs · EncodeToMp4.cs · ScreenRecord.cs |
Mediaway.Pipeline.AudioEncoder (AAC, ABI v2 — crates/mediaway-ffi/adr/pipeline/0003-auto-audio-encode-c-abi.md)
is the session-is-the-encoder counterpart of AutoVideoEncoder/EncodeSession: no
intermediate handle, no consumption trap. EncodeAudio.cs is hardware-verified: 96 frames of
a synthetic 440 Hz sine → 96 AAC packets → an audio-only fragmented MP4.
Build and run instructions: bindings/csharp/README.md.
Python
Data-processing pipelines and ML input/output streams call Mediaway via ctypes/cffi
over the mediaway-ffi
C ABI. Status: ✅ verified.
Install
pip install mediaway
from mediaway import Codec, Muxer, Packet, Rational, VideoStreamInfo
with Muxer() as muxer:
video_id = muxer.add_video_track(VideoStreamInfo(
codec=Codec.H264, width=1920, height=1080,
frame_rate=Rational(1, 30),
))
with muxer.begin() as live: # Open -> Live (handle moves; registration impossible)
live.push_packet(Packet(
stream_index=video_id, pts=Rational(0, 30),
payload=b"\x00\x00\x00\x01", key=True,
))
live.flush()
chunks = []
while True:
chunk = live.poll_bytes() # caller owns byte I/O (sans-io)
if chunk is None:
break
chunks.append(chunk)
mp4 = b"".join(chunks)
Examples live in bindings/python/examples/:
| Capability | Example files |
|---|---|
| Container | container/mux_roundtrip.py |
| Device | device/camera_record.py · capture_microphone.py · capture_screen.py |
| Pipeline | pipeline/encode_audio.py · encode_to_mp4.py · screen_record.py |
Build and run instructions: bindings/python/README.md.
Node.js (TypeScript)
Server-side video processing and CLI tools call Mediaway through a native addon / N-API
(koffi FFI today) over the mediaway-ffi
C ABI. Status: ✅ verified.
Install
npm install @mediaway/container # also @mediaway/device, @mediaway/encoder, @mediaway/ffi
import { Muxer } from "@mediaway/container";
const muxer = new Muxer();
const videoTrack = muxer.addVideoTrack({
codec: "h264", width: 1920, height: 1080,
pixelFormat: "nv12", timeBase: { num: 1, den: 30 },
});
const chunks: Buffer[] = [muxer.begin()]; // init segment (ftyp + moov), write once
muxer.push({
trackIndex: videoTrack,
data: Buffer.from([0, 0, 0, 1]),
pts: 0, duration: 1, key: true,
});
muxer.flush();
for (let chunk = muxer.pollBytes(); chunk.length > 0; chunk = muxer.pollBytes()) {
chunks.push(chunk);
}
Examples live in bindings/nodejs/examples/:
| Capability | Example files |
|---|---|
| Container | container/mux-roundtrip.ts |
| Device | device/camera-record.ts · capture-microphone.ts · capture-screen.ts |
| Pipeline | pipeline/encode-audio.ts · encode-to-mp4.ts · screen-record.ts |
Build and run instructions: bindings/nodejs/README.md.
Browser (WASM + WebCodecs)
Browser apps run Mediaway natively in the page — WASM (wasm-bindgen) wired into
WebCodecs and WebGPU, bypassing the C ABI entirely for zero-overhead browser execution.
Status: ✅ verified.
Install
npm install @mediaway/browser
import { init, Muxer } from "@mediaway/browser";
await init(); // fetches + instantiates the WASM module
const muxer = new Muxer(); // one sample per fragment
muxer.addTrack({
id: 0, codec: "h264",
timeBase: { num: 1, den: 30 }, width: 1920, height: 1080,
extraData: new Uint8Array(/* avcC … */),
});
muxer.begin(); // live: packets may now be pushed
muxer.pushPacket({
streamId: 0, pts: 0, dts: 0, duration: 1,
isKeyframe: true, isDiscard: false,
payload: new Uint8Array([0, 2, 0x65, 0x88]),
});
muxer.flush();
const mp4 = muxer.pollBytes(); // fresh copy into JS-owned memory
muxer.free(); // WASM handle — JS GC cannot see into WASM memory
Examples live in bindings/browser/examples/:
| Capability | Example files |
|---|---|
| Container | container/mux-roundtrip.ts |
| Device | device/camera-record.ts · capture-microphone.ts · capture-screen.ts · list-and-watch-devices.ts |
| Pipeline | pipeline/encode-audio.ts · encode-to-mp4.ts · screen-record.ts |
Playback (decode) uses DecodeSession — the mirror image of EncodeSession, feeding
the browser’s native VideoDecoder/AudioDecoder from the WASM Demuxer:
import { Demuxer, DecodeSession } from "@mediaway/browser";
const demuxer = new Demuxer();
demuxer.pushBytes(mp4);
const decode = new DecodeSession(demuxer, {
resolveCodec: () => "avc1.42E01E", // the exact WebCodecs string you encoded with
});
decode.onVideoFrame((frame) => { /* draw frame, then */ frame.close(); });
await decode.start();
for (let s = demuxer.pollPacket(); s !== null; s = demuxer.pollPacket()) decode.pushPacket(s);
await decode.finish();
These are exercised end-to-end by Playwright against iso-bmff-wasm and the WebCodecs
backends. Build and run instructions: bindings/browser/README.md.
Codec Support
Pulled directly from the project README, so this stays in sync automatically — no separate table to fall out of date.
| Mark | Meaning |
|---|---|
| ✅ | First-class (tests for claimed scope) |
| ⚡ | Zero-Copy path — no payload memcpy (GPU handle or shared CPU buffer; implies ✅) |
| 🆗 | Best-effort / prototype |
| 🛠️ | Planned |
| ❌ | Attempted and genuinely blocked — no upstream API to build on, a hard version/license conflict, or a real query returned “unsupported.” Not a “ran out of time” 🛠️. |
| 👻 | Not exercisable yet — license/patent blocked, no target hardware, out of scope, or no device/daemon/session available to run otherwise-tested code against |
Cell = encode/decode. One mark means both; A/B means encode / decode.
For now only Windows · Web · Linux are planned; Apple / Android (and Metal / Apple / Qualcomm) are 👻.
Zero-Copy honesty: ⚡ means no payload copy — a GPU handle or a shared CPU buffer, not “GPU only.” Allocating a new Vec and copying into it is 🆗, not ⚡.
OS · CPU
OS codec APIs (WMF, WebCodecs, VA-API, …) fed with CPU buffers (upload may apply); ⚡ here means a shared/borrowed buffer, not software encode.
| Codec | Windows | Web | Linux | Apple | Android |
|---|---|---|---|---|---|
| H.264 / AVC | 🆗 / 🆗 | 🆗 / 🆗 | 👻 | 👻 | 👻 |
| HEVC / H.265 | 🆗 / 🆗 | ❌ / 🆗 | 🛠️ | 👻 | 👻 |
| AV1 | 🛠️ / 🛠️ | 🆗 | 🛠️ | 👻 | 👻 |
| VP9 | 🆗 / 🆗 | 🆗 | 🛠️ | 👻 | 👻 |
| ProRes | 👻 | 👻 | 👻 | 👻 | 👻 |
| AAC | 🆗 | 🆗 | 🛠️ | 👻 | 👻 |
| Opus | 🆗 / 🆗 | 🛠️ | 🛠️ | 👻 | 👻 |
Windows Opus: encode runs through
mediaway-sw(no inbox encoder MFT exists — verified viaMFTEnumEx), wired intoWindowsAudioEncoder; decode uses the inbox decoder MFT session (CMSOpusDecMFT), public asmediaway_decoder::windows::WmfOpusDecoder— both verified end-to-end (encode→Ogg→ffprobe 2.000 s + mpv; decode of ffmpeg-produced Opus → exact PCM).
Detail: backends live as #[cfg]-gated modules — mediaway-decoder::{windows, web, linux}, mediaway-encoder::{windows, web, linux}.
OS · GPU
Same OS APIs with GPU surfaces (GpuBufferHandle, DXGI, …). Video only — audio Zero-Copy lives under OS · CPU.
| Codec | Windows | Web | Linux | Apple | Android |
|---|---|---|---|---|---|
| H.264 / AVC | ⚡ / 🆗 | 🆗 | 🛠️ | 👻 | 👻 |
| HEVC / H.265 | 🆗 / 🆗 | 🛠️ | 🛠️ | 👻 | 👻 |
| AV1 | 🆗 / 🆗 | 🛠️ | 🛠️ | 👻 | 👻 |
| VP9 | 🆗 / 🆗 | 🛠️ | 🛠️ | 👻 | 👻 |
| ProRes | 👻 | 👻 | 👻 | 👻 | 👻 |
Detail: mediaway-encoder::{windows, web} (windows = WMF + DX11 Zero-Copy; web = WebCodecs + WebGPU).
GPU — by API
Graphics interop (D3D11, Vulkan, Metal, …) — which API your textures use. Orthogonal to OS · CPU/GPU. Video only.
Adapters: mediaway wgpu module 🆗 (DX12 ↔ WMF GpuCopy bridges).
| Codec | D3D11 | D3D12 | Vulkan | Metal |
|---|---|---|---|---|
| H.264 / AVC | ⚡ / 🆗 | 🆗 / 🛠️ | 🆗 / 🆗 | 👻 |
| HEVC / H.265 | 🆗 / 🆗 | 🆗 / 🛠️ | 🆗 / 🆗 | 👻 |
| AV1 | 🆗 / 🆗 | 🛠️ | ❌ / 🛠️ | 👻 |
| VP9 | 🆗 / 🆗 | 👻 | 👻 | 👻 |
Detail: mediaway wgpu module · mediaway-encoder::{windows, vulkan} · mediaway-decoder::{windows, vulkan}.
GPU — by vendor
Vendor SDKs (NVENC, AMF, …) — separate from OS + graphics interop. Not the default Auto path. Video only.
- NVIDIA —
mediaway-encoder::nvenc(mediaway-encoder), hardware-verified H.264/HEVC/AV1 CPU-upload encode. - Intel —
mediaway-encoder::quicksync(mediaway-encoder), hardware-verified H.264/HEVC encode; AV1 is ❌ (no hardware support on this iGPU generation). - AMD — AMF backend 🛠️ deferred (binding/dependency blockers, not an AMD capability gap).
| Codec | NVIDIA | AMD | Intel | Apple | Qualcomm |
|---|---|---|---|---|---|
| H.264 / AVC | 🆗 | 🛠️ | 🆗 | 👻 | 👻 |
| HEVC / H.265 | 🆗 | 🛠️ | 🆗 | 👻 | 👻 |
| AV1 | 🆗 | 🛠️ | ❌ | 👻 | 👻 |
| VP9 | 👻 | 👻 | 👻 | 👻 | 👻 |
CPU / SW
Pure Rust sans-io software codecs — no C codec FFI (OpenH264, libvpx, …). Opt-in only; never a silent HW fallback. Detail: mediaway-sw (H.264 decode, AV1/Opus encode, PCM, audio processing).
| Codec | Status |
|---|---|
| H.264 / AVC | 🆗 |
| HEVC / H.265 | 👻 |
| AV1 | 🆗 |
| VP9 | 👻 |
| AAC | 👻 |
| Opus | 🆗 |
| PCM / raw | 🆗 |
Container Support
Pulled directly from the project README.
Freestanding mux/demux cores plus the mediaway-container facade (wraps all eight as Mediaway-typed StreamInfo/Packet modules: mp4, webm, wav, adts, mp3, ogg, flv, ts). Same marks as § Codec support — not every 🆗-mux implements the shared Mux trait; see mediaway-container for which.
| Format | Mux | Demux |
|---|---|---|
| MP4 / fMP4 | 🆗 | 🆗 |
| WebM | 🆗 | 🆗 |
| WAV / RIFF (PCM) | 🆗 | 🆗 |
| ADTS (raw AAC) | 🆗 | 🆗 |
| MP3 (MPEG Layer III) | 🆗 | 🆗 |
| Ogg | 🆗 | 🆗 |
| FLV | 🆗 | 🆗 |
| MPEG-TS | 🆗 | 🆗 |
Device
Pulled directly from the project README.
What mediaway-device backends target (camera, mic, screen, window). Same marks as § Codec support. ⚡ = Zero-Copy out (GPU GpuBufferHandle or shared CPU PCM without payload copy); cell = CPU capture / GPU surface where both apply (🆗 / ⚡).
| Source | Windows | Web | Linux | Apple | Android |
|---|---|---|---|---|---|
| Camera (video) | 🆗 | 🆗 | 👻 | 👻 | 👻 |
| Microphone | 🆗 | 🆗 | 👻 | 👻 | 👻 |
| Screen / display | ⚡ | 🆗 | 👻 | 👻 | 👻 |
| Window | 🆗 | 🆗 | 👻 | 👻 | 👻 |
Crate Map
Pulled directly from the project README.
| Crate | Role |
|---|---|
mediaway-common | Shared types (Rational, formats, GpuBufferHandle, packets/frames) |
iso-bmff | MP4 / ISOBMFF mux + demux core (fMP4, ClearKey CENC) |
iso-cenc | ClearKey CENC sample crypto (AES-128-CTR) |
ebml-webm | EBML / WebM mux + demux core |
riff-wave-core | WAV / RIFF PCM mux + demux core |
adts-core | ADTS (raw AAC) mux + demux core |
mpeg-audio | MP3 (MPEG Layer III) mux + demux core |
ogg-core | Ogg page/packet mux + demux core |
flv-core | FLV tag mux + demux core |
mpeg-ts-core | MPEG-2 Transport Stream mux + demux core |
rtp-core | RTP payloadization for H.264/HEVC (RFC 3550/6184/7798) |
rtmp | RTMP publish-client handshake + chunk stream + AMF0 command mux |
mediaway-container | Container facade: shared traits + typed mp4/webm/wav/adts/mp3/ogg/flv/ts |
mediaway-encoder | Encode traits + auto selection; Windows WMF / NVENC / QuickSync / Vulkan / WebCodecs / VA-API backends |
mediaway-decoder | Decode traits; Windows WMF (HW, DX11 Zero-Copy) / Vulkan / WebCodecs backends |
mediaway-device | Capture + playback traits; Windows DXGI/WGC/WASAPI, Linux portal+PipeWire+V4L2, Web getUserMedia/getDisplayMedia |
mediaway | Convenience pipeline (EncodeSession + platform auto-dispatch + wgpu interop) |
mediaway-sw | Pure Rust sans-io software codecs (H.264 decode, AV1/Opus encode, PCM, audio processing) |
vpl-sys | oneVPL FFI bindings (runtime-loaded; no build-time link) |
iso-bmff-wasm | WASM bindings for iso-bmff (browser) |
mediaway-ffi | Single C ABI facade (container / device / pipeline) |
mediaway-test-media | Rust-generated test fixtures (local cache only) |
mediaway-avcli | AV CLI (mux; not affiliated with FFmpeg) |
mediaway-avprobe | Media probe CLI (not affiliated with FFmpeg) |
OS backends live as #[cfg]-gated modules inside the facade crates
(mediaway-encoder, mediaway-decoder, mediaway-device); per-crate API docs are on
Crate Docs. Platform order and stages: docs/roadmap.md
in the repository.
Crate Docs (docs.rs)
Per-crate API reference lives on docs.rs. Every published Mediaway crate links there from its crates.io page; this page is the index.
Published
| Crate | docs.rs | What it is |
|---|---|---|
mediaway-common | docs.rs | Shared types (Rational, formats, GpuBufferHandle, packets/frames) |
iso-bmff | docs.rs | MP4 / ISOBMFF mux + demux core |
iso-cenc | docs.rs | ClearKey CENC sample crypto |
ebml-webm | docs.rs | EBML / WebM mux + demux core |
riff-wave-core | docs.rs | WAV / RIFF PCM core |
adts-core | docs.rs | ADTS (raw AAC) core |
mpeg-audio | docs.rs | MP3 (Layer III) core |
ogg-core | docs.rs | Ogg page/packet core |
flv-core | docs.rs | FLV tag core |
mpeg-ts-core | docs.rs | MPEG-2 Transport Stream core |
mediaway-container | docs.rs | Container facade (typed mp4/webm/wav/adts/mp3/ogg/flv/ts) |
mediaway-encoder | docs.rs | Encode traits + backends |
mediaway-decoder | docs.rs | Decode traits + backends |
mediaway-device | docs.rs | Capture/playback traits + backends |
mediaway | docs.rs | Convenience pipeline (EncodeSession, platform) |
mediaway-sw | docs.rs | Pure Rust software codecs |
vpl-sys | docs.rs | oneVPL FFI bindings |
mediaway-test-media | docs.rs | Generated test fixtures (test-only) |
mediaway-avcli | docs.rs | AV CLI (mux) |
mediaway-avprobe | docs.rs | Media probe CLI |
Not yet published
These crates are not on crates.io (their publish flag is off while they stabilize) —
use the repository copies:
| Crate | Repo README |
|---|---|
rtmp | crates/rtmp/README.md |
mediaway-ffi | crates/mediaway-ffi/README.md |
iso-bmff-wasm | crates/iso-bmff-wasm/README.md |
How the support matrices stay in sync
The support-matrix pages (Codec Support, Container
Support, Device) and the Crate
Map are {{#include}}d from the root README.md
anchor blocks (<!-- ANCHOR: … -->). To update a matrix, edit the README — never the
book page. See Extending the Book.
Status & Stability
Mediaway is early development (pre-1.0). Production use is not recommended.
No stable API promise yet
Until 1.0:
- Public types, traits, module layout, and function signatures may change without a deprecation cycle.
- Crate boundaries and feature flags may be split, renamed, or removed.
- Backend behavior and encode/decode path selection may change as platforms
and
Auto/preference APIs land.
If you depend on Mediaway today, pin a git revision and expect breakage on
pull — publish = false on crates.io until this changes.
| Signal | Meaning |
|---|---|
| Version | 0.x — breaking changes without a stable promise |
| Completeness | Scaffolds and design docs first; many crates have no public API yet |
| Support | Best-effort while the stack is being built |
Suitable today
Experimentation, design feedback, contributing, early integration spikes that can tolerate breakage.
Not suitable yet
Shipping end-user products that depend on Mediaway for encode/decode/mux in production.
Checking what’s actually implemented
Status tables (this book’s Reference section, and the project README) distinguish first-class (✅), Zero-Copy (⚡), best-effort/prototype (🆗), planned (🛠️), genuinely blocked (❌), and out-of-scope-here (👻) per cell — read the legend, not just the marks, since “planned” and “blocked” mean very different things for whether to wait or look elsewhere.
Full detail: docs/spec/status.md ·
docs/spec/maturity-bar.md
(what it takes to earn correctness/stability/performance trust for a given
scope) in the repository.
Contributing
Mediaway takes contributions — code, design feedback, and bug reports.
- Human contributor guide:
CONTRIBUTING.md - Getting-started, docs map, PR process:
docs/contributing/ - PR author checklist (doc sync, quality gates):
docs/contributing/pull-requests.md - Adding a new crate, per-language example, or reference page: Extending the Book
- Bug/crash/docs issues use the issue tracker; feature ideas start as
GitHub Discussions —
see
docs/contributing/issues.mdfor the split.
Dev setup
# Toolchain (rust-toolchain.toml pins stable)
rustup show
# Hooks
cargo install lefthook cargo-deny
lefthook install
# Optional — some suites use these as test/dev oracles or accelerate nextest
cargo install cargo-nextest gitleaks
cargo nextest run --workspace # preferred when installed
cargo test --workspace # fallback
If you’re bringing an AI coding assistant
Start with
docs/contributing/for-agents.md,
then the project’s AGENTS.md (single source of truth for repo conventions
— license/safety rules, architecture rules, commit format, and more).
Extending the Book
This page is the checklist for adding a new package (crate) to the Mediaway mdBook,
and for adding per-language examples and reference entries. The book is the
human-facing narrative layer; the README, docs/spec/, crate docs/roadmap.md, and
adr/ remain the source of truth for facts.
Adding a new crate
- Scaffold the crate per the workspace layout:
crates/<name>/withREADME.md(see thecrate-readme-template),docs/roadmap.md, andadr/(crate-local ADRs). - Cargo manifest: add the crate to
[workspace].membersand[workspace.dependencies](with its version). For publishable crates setpublish = true, a user-facingdescription(no internal jargon, no ADR references), and an independent version if the crate is a freestanding core. - Root README: add a row to the Crates table between the
<!-- ANCHOR: crates -->markers, and update any support-matrix cell the crate touches (codec / container / device tables). The book’s Crate Map and matrix pages include these anchors — this one edit syncs them all. - Workspace roadmap: add the crate to the crate-roadmaps index table in
docs/roadmap.md. - Book SUMMARY: add pages under the matching section — a guide for a new capability, a reference entry, and examples pages.
- Reference: add the crate to Crate Docs once it is published (docs.rs link).
- Guides: extend an existing
guides/<capability>.mdor add a new one. Guides are hand-written and teaching-focused — do not{{#include}}code fromexamples/; the runnable examples stay the compiling source of truth and the guide links to them. - Examples: add a Rust example under
examples/(workspace member, one capability per file) and mirror it per language underbindings/<lang>/examples/<capability>/when the crate has user-facing APIs worth demoing. Register the files in the language’s Examples page. - Wiki: add or update a
docs/ai/wiki/page (agent knowledge — Rule 0 upkeep).
Adding a per-language example
- Mirror the Rust example’s capability and file name (
mux_roundtrip,screen_record, …) inbindings/<lang>/examples/<capability>/. - Keep it runnable and verified; the binding’s README documents the build/run steps.
- Add the file to the language page under Examples and the root README’s binding table if it demonstrates a new capability.
Reference support rules
- Support-matrix pages are
{{#include}}d from root README anchors — edit the README, not the book page, and keep the<!-- ANCHOR: … -->comments in sync. - Per-crate API docs live on docs.rs;
Crate Docsonly links them. - Facts (status, versions, platform support) belong in README /
docs/spec// crate roadmaps — the book narrates, it does not duplicate.
License & Dependencies
Mediaway is dual-licensed:
The license/dependency boundary is a hard rule, not a preference
- No GPL / LGPL / AGPL / SSPL / BUSL dependencies — including FFmpeg
crates or linking
libav*, x264, x265. Enforced bycargo denyin CI. - No FFmpeg /
libav*linked or vendored in any shipped Mediaway crate. - Software codecs, when used, are pure Rust sans-io only
(
mediaway-swand codec cores) — explicit opt-in, never a silent fallback. - A system
ffmpeg/ffprobeonPATHmay be used as an optional test/dev oracle — comparing Mediaway’s own output against a well-known reference during testing — but it is never required to build or run shipped Mediaway. See ADR-0002.
Mediaway is not affiliated with the FFmpeg project. The product CLIs
(mediaway-avcli, mediaway-avprobe) are independent tools.
Full detail: docs/spec/vision.md
§ License & dependency boundary,
docs/conventions/deps-policy.md.