Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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’s EncodeSession and platform::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-unknown target (rustup target add wasm32-unknown-unknown) for the *-web/*-wasm crates. WebCodecs/getUserMedia/getDisplayMedia backends run inside a real browser, not in a headless test runner.
  • Linux — VA-API (mediaway-*-linux) needs /dev/dri and a working libva driver on the host; portal/PipeWire capture needs xdg-desktop-portal and 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 OpenLive. 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_screenexamples/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_microphoneexamples/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_cameraexamples/device/capture_camera.rs.

Window — needs a caller-owned GPU device

Window capture (WinRT Graphics Capture) is the one source with no CPU-only pathopen() 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_mp4examples/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_recordexamples/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:

  1. Encode two short clips, mux each to fMP4.
  2. Demux + decode each clip back to Vec<VideoFrame>.
  3. Trim — slice the decoded frames by index/PTS; no new type needed.
  4. SpliceIterator::chain the trimmed segments, then renumber pts/duration contiguously (encoded timestamps must be monotonic).
  5. 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_spliceexamples/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:

LanguageInterop pathExamples
RustNative cratesexamples/
CDirect C ABI (mediaway-ffi)bindings/c/examples/
C++Thin RAII C ABIbindings/cpp/examples/
C#P/Invokebindings/csharp/examples/
Pythonctypes / cffibindings/python/examples/
Node.jsNative Addon / N-API (koffi FFI today)bindings/nodejs/examples/
BrowserWASM + 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 container
  • device/ — one capture source, no encode
  • pipeline/ — 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:

CapabilityExampleRun
Containercontainer/mux_demux_mp4.rscargo run --example mux_demux_mp4
Encodeencode/encode_h264.rscargo run --example encode_h264
Decodedecode/decode_h264.rscargo run --example decode_h264
Devicedevice/capture_camera.rs · capture_microphone.rs · capture_screen.rs · capture_window.rscargo run --example capture_screen
Pipelinepipeline/encode_to_mp4.rs · screen_record.rs · trim_and_splice.rscargo 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/:

CapabilityExample files
Containercontainer/mux_roundtrip.c
Devicedevice/camera_record.c · capture_microphone.c · capture_screen.c
Pipelinepipeline/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-ffi C ABI is real and link-run verified; C++ apps can call it directly today. Build with CMake per bindings/cpp/README.md.

Examples live in bindings/cpp/examples/:

CapabilityExample files
Containercontainer/mux_roundtrip.cpp
Devicedevice/camera_record.cpp · capture_microphone.cpp · capture_screen.cpp
Pipelinepipeline/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/):

CapabilityExample files
ContainerContainer/MuxRoundtrip.cs
DeviceDevice/CameraRecord.cs · CaptureMicrophone.cs
PipelinePipeline/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/:

CapabilityExample files
Containercontainer/mux_roundtrip.py
Devicedevice/camera_record.py · capture_microphone.py · capture_screen.py
Pipelinepipeline/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/:

CapabilityExample files
Containercontainer/mux-roundtrip.ts
Devicedevice/camera-record.ts · capture-microphone.ts · capture-screen.ts
Pipelinepipeline/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/:

CapabilityExample files
Containercontainer/mux-roundtrip.ts
Devicedevice/camera-record.ts · capture-microphone.ts · capture-screen.ts · list-and-watch-devices.ts
Pipelinepipeline/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.

MarkMeaning
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.

CodecWindowsWebLinuxAppleAndroid
H.264 / AVC🆗 / 🆗🆗 / 🆗👻👻👻
HEVC / H.265🆗 / 🆗❌ / 🆗🛠️👻👻
AV1🛠️ / 🛠️🆗🛠️👻👻
VP9🆗 / 🆗🆗🛠️👻👻
ProRes👻👻👻👻👻
AAC🆗🆗🛠️👻👻
Opus🆗 / 🆗🛠️🛠️👻👻

Windows Opus: encode runs through mediaway-sw (no inbox encoder MFT exists — verified via MFTEnumEx), wired into WindowsAudioEncoder; decode uses the inbox decoder MFT session (CMSOpusDecMFT), public as mediaway_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.

CodecWindowsWebLinuxAppleAndroid
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).

CodecD3D11D3D12VulkanMetal
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.

  • NVIDIAmediaway-encoder::nvenc (mediaway-encoder), hardware-verified H.264/HEVC/AV1 CPU-upload encode.
  • Intelmediaway-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).
CodecNVIDIAAMDIntelAppleQualcomm
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).

CodecStatus
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.

FormatMuxDemux
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 (🆗 / ⚡).

SourceWindowsWebLinuxAppleAndroid
Camera (video)🆗🆗👻👻👻
Microphone🆗🆗👻👻👻
Screen / display🆗👻👻👻
Window🆗🆗👻👻👻

Crate Map

Pulled directly from the project README.

CrateRole
mediaway-commonShared types (Rational, formats, GpuBufferHandle, packets/frames)
iso-bmffMP4 / ISOBMFF mux + demux core (fMP4, ClearKey CENC)
iso-cencClearKey CENC sample crypto (AES-128-CTR)
ebml-webmEBML / WebM mux + demux core
riff-wave-coreWAV / RIFF PCM mux + demux core
adts-coreADTS (raw AAC) mux + demux core
mpeg-audioMP3 (MPEG Layer III) mux + demux core
ogg-coreOgg page/packet mux + demux core
flv-coreFLV tag mux + demux core
mpeg-ts-coreMPEG-2 Transport Stream mux + demux core
rtp-coreRTP payloadization for H.264/HEVC (RFC 3550/6184/7798)
rtmpRTMP publish-client handshake + chunk stream + AMF0 command mux
mediaway-containerContainer facade: shared traits + typed mp4/webm/wav/adts/mp3/ogg/flv/ts
mediaway-encoderEncode traits + auto selection; Windows WMF / NVENC / QuickSync / Vulkan / WebCodecs / VA-API backends
mediaway-decoderDecode traits; Windows WMF (HW, DX11 Zero-Copy) / Vulkan / WebCodecs backends
mediaway-deviceCapture + playback traits; Windows DXGI/WGC/WASAPI, Linux portal+PipeWire+V4L2, Web getUserMedia/getDisplayMedia
mediawayConvenience pipeline (EncodeSession + platform auto-dispatch + wgpu interop)
mediaway-swPure Rust sans-io software codecs (H.264 decode, AV1/Opus encode, PCM, audio processing)
vpl-sysoneVPL FFI bindings (runtime-loaded; no build-time link)
iso-bmff-wasmWASM bindings for iso-bmff (browser)
mediaway-ffiSingle C ABI facade (container / device / pipeline)
mediaway-test-mediaRust-generated test fixtures (local cache only)
mediaway-avcliAV CLI (mux; not affiliated with FFmpeg)
mediaway-avprobeMedia 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

Cratedocs.rsWhat it is
mediaway-commondocs.rsShared types (Rational, formats, GpuBufferHandle, packets/frames)
iso-bmffdocs.rsMP4 / ISOBMFF mux + demux core
iso-cencdocs.rsClearKey CENC sample crypto
ebml-webmdocs.rsEBML / WebM mux + demux core
riff-wave-coredocs.rsWAV / RIFF PCM core
adts-coredocs.rsADTS (raw AAC) core
mpeg-audiodocs.rsMP3 (Layer III) core
ogg-coredocs.rsOgg page/packet core
flv-coredocs.rsFLV tag core
mpeg-ts-coredocs.rsMPEG-2 Transport Stream core
mediaway-containerdocs.rsContainer facade (typed mp4/webm/wav/adts/mp3/ogg/flv/ts)
mediaway-encoderdocs.rsEncode traits + backends
mediaway-decoderdocs.rsDecode traits + backends
mediaway-devicedocs.rsCapture/playback traits + backends
mediawaydocs.rsConvenience pipeline (EncodeSession, platform)
mediaway-swdocs.rsPure Rust software codecs
vpl-sysdocs.rsoneVPL FFI bindings
mediaway-test-mediadocs.rsGenerated test fixtures (test-only)
mediaway-avclidocs.rsAV CLI (mux)
mediaway-avprobedocs.rsMedia probe CLI

Not yet published

These crates are not on crates.io (their publish flag is off while they stabilize) — use the repository copies:

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.

SignalMeaning
Version0.x — breaking changes without a stable promise
CompletenessScaffolds and design docs first; many crates have no public API yet
SupportBest-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.

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

  1. Scaffold the crate per the workspace layout: crates/<name>/ with README.md (see the crate-readme-template), docs/roadmap.md, and adr/ (crate-local ADRs).
  2. Cargo manifest: add the crate to [workspace].members and [workspace.dependencies] (with its version). For publishable crates set publish = true, a user-facing description (no internal jargon, no ADR references), and an independent version if the crate is a freestanding core.
  3. 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.
  4. Workspace roadmap: add the crate to the crate-roadmaps index table in docs/roadmap.md.
  5. Book SUMMARY: add pages under the matching section — a guide for a new capability, a reference entry, and examples pages.
  6. Reference: add the crate to Crate Docs once it is published (docs.rs link).
  7. Guides: extend an existing guides/<capability>.md or add a new one. Guides are hand-written and teaching-focused — do not {{#include}} code from examples/; the runnable examples stay the compiling source of truth and the guide links to them.
  8. Examples: add a Rust example under examples/ (workspace member, one capability per file) and mirror it per language under bindings/<lang>/examples/<capability>/ when the crate has user-facing APIs worth demoing. Register the files in the language’s Examples page.
  9. 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, …) in bindings/<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 Docs only 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 by cargo deny in CI.
  • No FFmpeg / libav* linked or vendored in any shipped Mediaway crate.
  • Software codecs, when used, are pure Rust sans-io only (mediaway-sw and codec cores) — explicit opt-in, never a silent fallback.
  • A system ffmpeg / ffprobe on PATH may 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.