rave/AGENTS.md

300 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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<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`
```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<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`:
```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 <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.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/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
```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 <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