# AGENTS.md — Rave (Rust 全协议流媒体引擎) ## 强制规范 (Mandatory Rules) ### 注释要求 - **所有代码必须包含中文注释**:每个 `struct`、`enum`、`trait`、`fn`、`impl` 块都要有文档注释 - 模块级注释 (`//!`) 说明该模块的职责和设计思路 - 公开 API 必须有 `///` 文档注释,说明参数、返回值、用途 - 内部实现 (`//`) 注释解释"为什么"而非"做了什么" - `unsafe` 块必须附带 Safety invariant 说明 - 注释语言:中文为主,技术术语保留英文原文 ### TDD 测试驱动开发 - **先写测试,再写实现**:每个公开函数/API 必须有对应测试 - 测试放置位置:与源文件同目录,使用 `#[cfg(test)] mod tests { ... }` - 测试命名规范:`test_<功能>_<场景>_<预期结果>`,例如 `test_buffer_write_wrap_around_succeeds` - 必须覆盖的测试类别: - **单元测试**:每个 pub fn / pub struct 的基本行为 - **边界测试**:空输入、满队列、环绕 (wrap-around)、并发 - **流媒体推拉流测试**:模拟 Publisher 推流 → Subscriber 拉流 的完整数据通路 - **GOP 缓存测试**:验证新订阅者能收到缓存的最近关键帧 - **背压测试**:验证慢订阅者丢帧而不阻塞发布者 - **协议编解码测试**:用手工构造的字节 fixture 测试解析器 (RTMP/FLV/TS) - 运行:`cargo test` 必须全部通过才能视为完成 ## Project Identity **Rave** is a full-protocol streaming media server engine written in **pure Rust, zero third-party crates** (except `tokio` for async runtime). It draws architectural inspiration from [lal (Go)](https://github.com/q191201771/lal) and [Monibuca v6 (Rust)](https://monibuica.com/) — but must not depend on any external crate beyond `tokio`. ## Hard Constraint: Minimal Third-Party Dependencies ``` # Cargo.toml [dependencies] — only tokio is allowed [dependencies] tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "io-util", "time", "sync", "signal"] } ``` Everything beyond `tokio` async runtime — networking, concurrency primitives, codec parsers — must be hand-written using only `std` and `core`. This is a deliberate design choice, not a temporary state. Consequences an agent must remember: - No `bytes` — use `Vec`, slices, or custom `Bytes`-like arena - No `parking_lot`, `dashmap`, `crossbeam` — use `std::sync::{Mutex, RwLock, Arc}` and `atomic` types - No `serde` — hand-parse/serialize config (TOML/JSON/YAML) - No `log` / `tracing` — build a minimal logger on `std::io::stderr` or `stdout` - No `tonic` / `prost` — gRPC/protobuf must be hand-implemented if needed - No `rustls` / `native-tls` — TLS must be hand-implemented or omitted - No `clap` / `structopt` — parse CLI args from `std::env::args` ## SDK 契约层架构 (Plugin Contract Layer) Inspired by Monibuca v6's `monibuca-sdk` pattern — plugins depend only on trait contracts, never on engine internals. The SDK layer is the **sole interface** between protocol plugins and the engine core. ### Dependency Direction (strict) ``` sdk/types.rs ◀── shared types only (AVFrame, codecs, StreamPath) ▲ sdk/traits.rs ◀── trait contracts (PublisherApi, SubscriberApi, StreamManagerApi) ▲ sdk/plugin.rs ◀── Plugin trait, lifecycle, ProtocolPlugin trait ▲ sdk/context.rs ◀── EngineContext (IoC-like service locator) sdk/registry.rs◀── PluginRegistry (init/start/stop lifecycle manager) ▲ core/ ◀── Engine internals (RingBuffer, Dispatcher, Stream, StreamManager) (implements the sdk traits, but plugins NEVER import from core/) ▲ protocol/ ◀── Protocol handlers (rtmp/, rtsp/, httpflv/, hls/, wsflv/) (depend ONLY on sdk:: traits + types, never on core:: directly) ``` ### Plugin Lifecycle Every plugin follows: `Created → init() → start() → [running] → stop() → Stopped` ```rust // To create a new protocol plugin, implement the Plugin trait: use rave::sdk::plugin::{Plugin, PluginMeta, PluginState, ProtocolPlugin}; use rave::sdk::context::EngineContext; use rave::sdk::traits::{ConfigProvider, StreamManagerApi}; use std::sync::Arc; pub struct RtmpPlugin { /* ... */ } impl Plugin for RtmpPlugin { fn meta(&self) -> &PluginMeta { /* ... */ } fn init(&mut self, ctx: Arc, cfg: &dyn ConfigProvider) -> Result<(), String> { /* ... */ } fn start(&mut self, ctx: Arc) -> Result<(), String> { /* ... */ } fn stop(&mut self) -> Result<(), String> { /* ... */ } fn state(&self) -> PluginState { /* ... */ } fn as_any(&self) -> &dyn std::any::Any { self } fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self } } impl ProtocolPlugin for RtmpPlugin { fn protocol_name(&self) -> &str { "rtmp" } fn default_port(&self) -> u16 { 1935 } } ``` ### Key SDK Traits (what plugins use) | Trait | Location | Purpose | |-------|----------|---------| | `PublisherApi` | `sdk/traits.rs` | Write frames into a stream, add tracks | | `SubscriberApi` | `sdk/traits.rs` | Read frames from a stream | | `StreamManagerApi` | `sdk/traits.rs` | Create/remove/subscribe to streams | | `EventHandler` | `sdk/traits.rs` | Receive stream lifecycle events (PublishStart, etc.) | | `ConfigProvider` | `sdk/traits.rs` | Read plugin-specific config | | `Plugin` | `sdk/plugin.rs` | Base plugin lifecycle trait | | `ProtocolPlugin` | `sdk/plugin.rs` | Extends Plugin with protocol_name + default_port | ### Key SDK Types (shared across all layers) | Type | Location | Purpose | |------|----------|---------| | `AVFrame` | `sdk/types.rs` | Universal audio/video frame (timestamp, codec, data as `Arc>`) | | `VideoCodec` | `sdk/types.rs` | H264, H265, Unknown | | `AudioCodec` | `sdk/types.rs` | Aac, G711A, G711U, Opus, Unknown | | `FrameType` | `sdk/types.rs` | KeyFrame, InterFrame, etc. | | `StreamPath` | `sdk/types.rs` | app_name + stream_name (e.g., "live/test") | | `TrackInfo` / `TrackId` | `sdk/types.rs` | Track metadata and identifier | | `StreamEvent` | `sdk/traits.rs` | PublishStart/Stop, SubscribeStart/Stop, FrameReceived | | `EngineContext` | `sdk/context.rs` | IoC container: access StreamManager + registered services | ### EngineContext (Service Locator) Plugins receive `EngineContext` during `init()` and `start()`. It provides: - `stream_manager()` — the `Arc` for stream operations - `register_service::()` / `get_service::()` — cross-plugin service sharing via TypeId ### PluginRegistry Manages all registered plugins. Called from `main.rs`: ```rust // 内置插件:通过 all_plugins() 自动注册 for plugin in protocol::all_plugins() { registry.register(plugin); } // 外部插件:手动注册 // registry.register(Box::new(my_external_plugin::XxxPlugin::new())); registry.init_all(&config)?; // calls init() on each, checks .enabled registry.start_all()?; // calls start() on each (spawns async listeners) // ... server runs ... registry.stop_all()?; // reverse-order stop ``` ### 内置插件自动注册 内置协议插件通过 `protocol::all_plugins()` 清单函数统一注册: - 新增内置协议:在 `protocol//plugin.rs` 实现插件 → 在 `protocol/mod.rs` 的 `all_plugins()` 加一行 - 外部插件:在 `main.rs` 中手动 `registry.register()` ### 配置级插件开关 通过 `<插件名>.enabled = false` 在配置中禁用指定插件: ```toml rtmp.enabled = false # 禁用 RTMP 插件 rtsp.enabled = false # 禁用 RTSP 插件 ``` ## Target Protocol Support ### Phase 1 — Core Protocols | Protocol | Direction | Key Spec Elements | |----------|-----------|-------------------| | **RTMP** | Pub + Sub | Handshake (C0/S0–C2/S2), Chunk protocol, AMF0/AMF3, message types | | **RTSP/RTP/RTCP** | Pub + Sub | DESCRIBE/SETUP/PLAY/RECORD, SDP parse, RTP packetization, interleaved+UDP | | **HTTP-FLV** | Sub only | HTTP long-poll, FLV tag header + body | | **HLS** | Sub only | M3U8 master/media playlist, MPEG-TS segments (.ts), optional fMP4 (CMAF) | | **WebSocket-FLV** | Sub only | WS handshake (RFC 6455, hand-written), then FLV tag frames | ### Phase 2 — Extended Protocols | Protocol | Direction | Notes | |----------|-----------|-------| | **WebRTC** | Pub + Sub | WHIP/WHEP, DTLS-SRTP, ICE, SDP — extremely complex without libs | | **SRT** | Pub + Sub | URIPacket-based, ARQ, encryption | | **GB28181** | Pub only | SIP signaling + PS payload demux | | **WebTransport** | Pub + Sub | QUIC-based; requires hand-written QUIC | ### Codec Support Required - Video: H.264 (AnnexB + AVCC), H.265/HEVC - Audio: AAC (ADTS + raw), G.711 (A-law / μ-law), Opus (for WebRTC) - Container parsing: FLV, MPEG-TS, fMP4/CMAF ## Architecture (Current Implementation) ``` rave/ ├── Cargo.toml # [dependencies] = tokio only, edition = "2024" ├── src/ │ ├── main.rs # CLI entry, config load, PluginRegistry bootstrap │ ├── lib.rs # Module root: core, sdk, config, logger │ ├── core/ │ │ ├── mod.rs │ │ ├── buffer.rs # Lock-free SPMC ring buffer (atomic write cursor) │ │ ├── frame.rs # Re-exports AVFrame from sdk/types │ │ ├── track.rs # VideoTrack / AudioTrack — owns a Buffer │ │ ├── publisher.rs # Publisher — implements PublisherApi, writes into tracks │ │ ├── subscriber.rs # Subscriber — implements SubscriberApi, bounded queue │ │ ├── dispatcher.rs # Single-read-broadcast: reads once, pushes to all subs │ │ ├── stream.rs # Stream: publisher + dispatcher + GOP cache + subscriber list │ │ └── group.rs # StreamManager: implements StreamManagerApi, HashMap registry │ ├── sdk/ │ │ ├── mod.rs # Re-exports all public SDK items │ │ ├── types.rs # AVFrame, VideoCodec, AudioCodec, FrameType, StreamPath, TrackId │ │ ├── traits.rs # PublisherApi, SubscriberApi, StreamManagerApi, EventHandler │ │ ├── plugin.rs # Plugin trait, ProtocolPlugin trait, PluginMeta, PluginState │ │ ├── context.rs # EngineContext — IoC service locator │ │ └── registry.rs # PluginRegistry — lifecycle management, config enable/disable │ ├── codec/ │ │ ├── h264.rs # H.264 NALU 解析、AVCC 编码 │ │ ├── hevc.rs # H.265 NALU 类型 │ │ ├── aac.rs # AAC ADTS 解析 │ │ ├── flv.rs # FLV tag 编解码 │ │ └── ts.rs # MPEG-TS 打包 │ ├── protocol/ │ │ ├── mod.rs # all_plugins() 内置插件清单函数 │ │ ├── rtmp/ │ │ │ ├── plugin.rs # RtmpPlugin (Plugin + ProtocolPlugin) │ │ │ ├── server.rs # RtmpServer TCP 监听 │ │ │ ├── session.rs # RTMP 会话状态机 │ │ │ ├── handshake.rs# RTMP 握手 (C0/S0–C2/S2) │ │ │ ├── chunk.rs # Chunk 协议解析 │ │ │ ├── message.rs # RTMP 消息类型 │ │ │ └── amf0.rs # AMF0 编解码 │ │ ├── rtsp/ │ │ │ ├── plugin.rs # RtspPlugin (Plugin + ProtocolPlugin) │ │ │ ├── server.rs # RtspServer TCP 监听 │ │ │ ├── session.rs # RTSP 会话(DESCRIBE/SETUP/PLAY/RECORD) │ │ │ ├── rtp.rs # RTP 封装/解包 │ │ │ ├── rtcp.rs # RTCP Sender Report │ │ │ ├── depacketizer.rs # RTP→AVFrame 解包器(H.264 FU-A、AAC) │ │ │ ├── sdps.rs # SDP 生成 │ │ │ ├── request.rs # RTSP 请求解析 │ │ │ └── response.rs # RTSP 响应构造 │ │ ├── httpflv.rs # HTTP-FLV 请求解析、FLV 流式输出 │ │ ├── httpflv_server.rs # HTTP-FLV TCP 服务器 │ │ ├── httpflv_server_plugin.rs # HttpFlvPlugin (Plugin + ProtocolPlugin) │ │ ├── hls.rs # HLS 框架 │ │ └── wsflv.rs # WebSocket-FLV 框架 │ ├── remux/ │ │ ├── rtmp2flv.rs # RTMP ↔ FLV │ │ └── flv2ts.rs # FLV ↔ MPEG-TS │ ├── config.rs # Hand-written TOML parser, implements ConfigProvider │ ├── logger.rs # Minimal stderr logger with level filtering, #[macro_export] macros │ └── stats.rs # ServerStats — bandwidth, fps, connection counters ``` ## Build & Run ```bash cargo build # debug build cargo build --release # optimized build cargo run # start server (optionally: cargo run -- path/to/rave.conf) cargo test # run all tests cargo test # run a single test ``` Edition is `2024` — use latest Rust idioms (`let` chains, gen blocks when stable, etc.). Logger macros are `#[macro_export]` — use `rave::log_info!(...)` from the binary crate, or `log_info!(...)` from within the library crate. ## Performance Design Principles (from Monibuca v6 reference) 1. **Lock-free SPMC ring buffer**: Publisher writes via `fetch_add` atomic slot index; subscribers read via per-subscriber cursor 2. **Zero-copy**: Frame data shared via `Arc>`; never clone media payloads between subscribers 3. **Single-read-broadcast**: Dispatcher reads each frame exactly once from the ring, then distributes to all subscriber queues 4. **Backpressure**: Bounded per-subscriber channel (1024 frames); slow subscribers drop oldest frames rather than blocking the publisher or other subscribers 5. **Object pool**: Reuse `Vec` buffers and frame structs on hot paths; avoid per-frame allocation ## Key Reference Architecture Facts (from lal + Monibuca) - **Group pattern** (lal): Each stream path maps to a "group" that owns one publisher and N subscribers. Protocols interoperate at the group level via a common `AVFrame` type. Implemented as `Stream` + `StreamManager`. - **GOP cache**: `Stream` caches the last complete GOP (keyframe + following delta frames) so new HTTP-FLV/HLS subscribers can start with a keyframe. Cleared on each new keyframe. - **Remux layer**: Protocol conversion happens through a remux layer that transforms between `AVFrame` ↔ protocol-specific formats (FLV tags, RTP packets, MPEG-TS packets). Each protocol plugin handles its own remux. - **SDK decoupling** (Monibuca v6 pattern): Plugins depend only on `sdk::` trait contracts. Engine core (`core/`) implements those traits. Protocol handlers never import from `core/` directly — they go through `PublisherApi`/`SubscriberApi`/`StreamManagerApi`. This enables future static/dynamic/WASM plugin loading without changing plugin code. ## Coding Conventions - Only `tokio` from crates.io; all other code uses `std` and `core` only. If you catch yourself writing `use ::` for a non-std crate (other than tokio), stop. - **Plugin isolation**: Protocol handlers in `protocol/` must only import from `sdk/`, never from `core/`. The `core/` module is engine-private. - RTMP chunk size defaults to 128 bytes; handle chunk size negotiation (SetChunkSize message). - Timestamps are in milliseconds (RTMP) or 90kHz clock (RTP/RTSP) — always normalize to a common clock internally. - Test protocol parsers with hand-crafted byte fixtures, not external libraries. ## What NOT To Do - Do not add any entry to `[dependencies]` in `Cargo.toml` - Do not use `unsafe` unless absolutely necessary for performance-critical paths (ring buffer, zero-copy) — document every `unsafe` block with a safety invariant - Do not reference Monibuca v6's crate dependency graph — it uses many third-party crates; Rave must not - Do not copy Go idioms from lal (goroutines, channels, `select`) — translate to Rust idioms (tasks, `select!`, channels from `std::sync::mpsc`) - Do not let protocol plugins import from `core/` — they must use only `sdk/` traits and types