rave/AGENTS.md

16 KiB
Raw Blame History

AGENTS.md — Rave (Rust 全协议流媒体引擎)

强制规范 (Mandatory Rules)

注释要求

  • 所有代码必须包含中文注释:每个 structenumtraitfnimpl 块都要有文档注释
  • 模块级注释 (//!) 说明该模块的职责和设计思路
  • 公开 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) and Monibuca v6 (Rust) — 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<u8>, 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

// 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<EngineContext>, cfg: &dyn ConfigProvider) -> Result<(), String> { /* ... */ }
    fn start(&mut self, ctx: Arc<EngineContext>) -> 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<Vec<u8>>)
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<dyn StreamManagerApi> for stream operations
  • register_service::<T>() / get_service::<T>() — cross-plugin service sharing via TypeId

PluginRegistry

Manages all registered plugins. Called from main.rs:

// 内置插件:通过 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 <name>.enabled
registry.start_all()?;         // calls start() on each (spawns async listeners)
// ... server runs ...
registry.stop_all()?;          // reverse-order stop

内置插件自动注册

内置协议插件通过 protocol::all_plugins() 清单函数统一注册:

  • 新增内置协议:在 protocol/<name>/plugin.rs 实现插件 → 在 protocol/mod.rsall_plugins() 加一行
  • 外部插件:在 main.rs 中手动 registry.register()

配置级插件开关

通过 <插件名>.enabled = false 在配置中禁用指定插件:

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/S0C2/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/S0C2/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

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 <test_name>   # 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<Vec<u8>>; 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<u8> 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 <crate_name>:: 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