rave/src/core/stream.rs

520 lines
20 KiB
Rust
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.

//! 流实例 — 发布者 + 分发器 + GOP 缓存 + 订阅者列表
//!
/// Stream 是引擎的核心数据聚合单元,对应 lal 中的 "Group" 概念:
/// - 每个流路径(如 "live/test")对应一个 Stream 实例
/// - 一个 Stream 拥有恰好一个 Publisher
/// - 一个 Stream 可以有 N 个 Subscriber
/// - 内置 GOP 缓存,确保新订阅者能从最近关键帧开始播放
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use crate::core::dispatcher::Dispatcher;
use crate::core::publisher::Publisher;
use crate::core::subscriber::Subscriber;
use crate::sdk::types::{AudioCodec, AVFrame, CodecExtraInfo, StreamCodecMeta, StreamPath, StreamSummary, VideoCodec};
/// 流实例
///
/// 聚合了发布者、分发器、GOP 缓存等核心组件
pub struct Stream {
/// 流路径
path: StreamPath,
/// 发布者
publisher: Arc<Publisher>,
/// 分发器
dispatcher: Arc<Dispatcher>,
/// GOP 缓存:保存最近一个完整 GOP关键帧 + 后续 delta 帧)
///
/// 新订阅者连接时,先将缓存中的帧推送给它,
/// 确保播放端能立即从关键帧开始解码
gop_cache: Mutex<Vec<AVFrame>>,
/// 已检测到的视频编解码器
video_codec: std::sync::atomic::AtomicU8,
/// 已检测到的音频编解码器
audio_codec: std::sync::atomic::AtomicU8,
/// 最近视频帧时间戳(毫秒)
last_video_ts: AtomicU64,
/// 最近音频帧时间戳(毫秒);用 u64::MAX 表示"尚无"
last_audio_ts: AtomicU64,
/// 已接收总视频帧数
total_video_frames: AtomicU64,
/// 已接收总音频帧数
total_audio_frames: AtomicU64,
}
/// video_codec 原子存储编码
const VC_UNKNOWN: u8 = 0;
const VC_H264: u8 = 1;
const VC_H265: u8 = 2;
/// audio_codec 原子存储编码
const AC_UNKNOWN: u8 = 0;
const AC_AAC: u8 = 1;
const AC_G711A: u8 = 2;
const AC_G711U: u8 = 3;
const AC_OPUS: u8 = 4;
const NO_TS: u64 = u64::MAX;
impl Stream {
/// 创建流实例
///
/// 自动创建关联的 Publisher 和 Dispatcher
pub fn new(path: StreamPath) -> Self {
let publisher = Arc::new(Publisher::new(path.clone()));
let dispatcher = Arc::new(Dispatcher::new(publisher.clone()));
Self {
path,
publisher,
dispatcher,
gop_cache: Mutex::new(Vec::new()),
video_codec: std::sync::atomic::AtomicU8::new(VC_UNKNOWN),
audio_codec: std::sync::atomic::AtomicU8::new(AC_UNKNOWN),
last_video_ts: AtomicU64::new(NO_TS),
last_audio_ts: AtomicU64::new(NO_TS),
total_video_frames: AtomicU64::new(0),
total_audio_frames: AtomicU64::new(0),
}
}
/// 获取发布者引用
pub fn publisher(&self) -> Arc<Publisher> {
self.publisher.clone()
}
/// 创建订阅者并加入分发器
///
/// 订阅时会先将 GOP 缓存中的帧推送给新订阅者,
/// 然后将订阅者注册到 Dispatcher 以接收后续帧
pub fn subscribe(&self) -> Arc<Subscriber> {
let sub = Arc::new(Subscriber::new(self.path.clone()));
// 将 GOP 缓存中的帧推送给新订阅者
{
let cache = self.gop_cache.lock().unwrap();
for frame in cache.iter() {
sub.push_frame(frame.clone());
}
}
self.dispatcher.add_subscriber(sub.clone());
sub
}
/// 分发一帧到所有订阅者
///
/// 同时维护 GOP 缓存:
/// - 遇到新关键帧时清空旧缓存,开始新 GOP
/// - 非关键视频帧追加到当前 GOP
/// - 音频帧不纳入 GOP 缓存
pub fn dispatch_frame(&self, frame: AVFrame) {
// 更新统计信息
if frame.is_video() {
self.total_video_frames.fetch_add(1, Ordering::Relaxed);
self.last_video_ts.store(frame.timestamp_ms, Ordering::Relaxed);
let vc = match frame.video_codec {
VideoCodec::H264 => VC_H264,
VideoCodec::H265 => VC_H265,
_ => VC_UNKNOWN,
};
self.video_codec.store(vc, Ordering::Relaxed);
} else if frame.is_audio() {
self.total_audio_frames.fetch_add(1, Ordering::Relaxed);
self.last_audio_ts.store(frame.timestamp_ms, Ordering::Relaxed);
let ac = match frame.audio_codec {
AudioCodec::Aac => AC_AAC,
AudioCodec::G711A => AC_G711A,
AudioCodec::G711U => AC_G711U,
AudioCodec::Opus => AC_OPUS,
_ => AC_UNKNOWN,
};
self.audio_codec.store(ac, Ordering::Relaxed);
}
{
let mut cache = self.gop_cache.lock().unwrap();
let is_video_seq = frame.is_video() && frame.is_seq_header();
let is_audio_seq = frame.is_audio() && frame.is_seq_header();
let is_idr = frame.is_video() && frame.is_keyframe() && !frame.is_seq_header();
let is_pframe = frame.is_video() && !frame.is_keyframe();
if is_idr {
// 保留 seq header清空其余
let seq_headers: Vec<AVFrame> = cache.iter()
.filter(|f| f.is_seq_header())
.cloned()
.collect();
cache.clear();
cache.extend(seq_headers);
cache.push(frame.clone());
} else if is_pframe {
cache.push(frame.clone());
} else if is_video_seq {
// 替换已有的视频 seq header 或插入
let has_video_seq = cache.iter().any(|f| f.is_video() && f.is_seq_header());
if has_video_seq {
cache.retain(|f| !(f.is_video() && f.is_seq_header()));
}
cache.insert(0, frame.clone());
} else if is_audio_seq {
let has_audio_seq = cache.iter().any(|f| f.is_audio() && f.is_seq_header());
if has_audio_seq {
cache.retain(|f| !(f.is_audio() && f.is_seq_header()));
}
cache.push(frame.clone());
}
}
self.dispatcher.dispatch_frame(frame);
}
/// 获取当前订阅者数量
pub fn subscriber_count(&self) -> usize {
self.dispatcher.subscriber_count()
}
/// 获取流路径引用
pub fn path(&self) -> &StreamPath {
&self.path
}
/// 获取流的摘要信息快照
pub fn summary(&self) -> StreamSummary {
let vc = match self.video_codec.load(Ordering::Relaxed) {
VC_H264 => VideoCodec::H264,
VC_H265 => VideoCodec::H265,
_ => VideoCodec::Unknown,
};
let ac = match self.audio_codec.load(Ordering::Relaxed) {
AC_AAC => AudioCodec::Aac,
AC_G711A => AudioCodec::G711A,
AC_G711U => AudioCodec::G711U,
AC_OPUS => AudioCodec::Opus,
_ => AudioCodec::Unknown,
};
let vts = self.last_video_ts.load(Ordering::Relaxed);
let ats = self.last_audio_ts.load(Ordering::Relaxed);
StreamSummary {
path: self.path.clone(),
video_codec: vc,
audio_codec: ac,
subscriber_count: self.subscriber_count(),
last_video_timestamp_ms: if vts == NO_TS { None } else { Some(vts) },
last_audio_timestamp_ms: if ats == NO_TS { None } else { Some(ats) },
gop_cache_size: self.gop_cache.lock().unwrap().len(),
total_video_frames: self.total_video_frames.load(Ordering::Relaxed),
total_audio_frames: self.total_audio_frames.load(Ordering::Relaxed),
}
}
/// 获取流的编解码器元数据
///
/// 扫描 GOP 缓存中的 seq_header 帧,提取 H.264 SPS/PPS 和 AAC AudioSpecificConfig。
/// 用于 RTSP DESCRIBE 响应生成 SDP 描述。
///
/// AAC AudioSpecificConfig 2 字节解析:
/// - bits [4:0] byte0 + [7:5] byte1 → audioObjectType5 bits 从 byte0 高 5 位)
/// - bits [4:1] byte1 → samplingFrequencyIndex4 bits
/// - bit [0] byte1 + [7:5] byte2 → channelConfiguration4 bits
pub fn codec_metadata(&self) -> StreamCodecMeta {
let cache = self.gop_cache.lock().unwrap();
let mut meta = StreamCodecMeta::default();
for frame in cache.iter() {
if let Some(ref info) = frame.codec_info {
match info {
CodecExtraInfo::H264SeqHeader(sh) => {
meta.h264_sps = Some(sh.sps.clone());
meta.h264_pps = Some(sh.pps.clone());
}
CodecExtraInfo::AacSeqHeader(ah) => {
meta.aac_config = Some(ah.audio_specific_config.clone());
// 解析 2 字节 AudioSpecificConfig 提取采样率和通道数
let config = &ah.audio_specific_config;
if config.len() >= 2 {
// samplingFrequencyIndex: bits [7:3] of byte[1]
// 格式: audioObjectType(5)<<11 | samplingFreqIndex(4)<<7 | channelConfig(3)<<4 | ...
// 实际 layout: byte0[7:3]=audioObjectType(5), byte0[2:0]+byte1[7]=samplingFreqIndex(4)
// byte1[6:3]=channelConfiguration(4), ...
// 简化byte0 低 3 位 << 1 | byte1 高 1 位 = samplingFreqIndex
let sfi = (((config[0] & 0x07) as u32) << 1)
| (((config[1] >> 7) & 0x01) as u32);
// channelConfiguration: byte1 bits [6:3]
let ch = (config[1] >> 3) & 0x0F;
meta.audio_sample_rate = sampling_rate_from_index(sfi);
meta.audio_channels = ch;
}
}
}
}
}
meta
}
}
/// 根据 samplingFrequencyIndex 查表返回采样率
///
/// AAC AudioSpecificConfig 中的采样率索引表ISO 14496-3 Table 1.16
fn sampling_rate_from_index(index: u32) -> u32 {
match index {
0 => 96000,
1 => 88200,
2 => 64000,
3 => 48000,
4 => 44100,
5 => 32000,
6 => 24000,
7 => 22050,
8 => 16000,
9 => 12000,
10 => 11025,
11 => 8000,
12 => 7350,
_ => 44100, // 未知时默认 44100
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sdk::traits::{PublisherApi, SubscriberApi};
use crate::sdk::types::{AudioCodec, CodecExtraInfo, FrameType, VideoCodec, AacSeqHeader, H264SeqHeader};
use std::sync::Arc;
fn make_path() -> StreamPath {
StreamPath::new("live", "stream")
}
fn make_keyframe(ts: u64) -> AVFrame {
AVFrame::new_video(ts, Arc::new(vec![1]), VideoCodec::H264, FrameType::KeyFrame)
}
fn make_interframe(ts: u64) -> AVFrame {
AVFrame::new_video(ts, Arc::new(vec![2]), VideoCodec::H264, FrameType::InterFrame)
}
#[test]
fn test_stream_new_path_correct() {
let stream = Stream::new(make_path());
assert_eq!(stream.path().full_path(), "live/stream");
}
#[test]
fn test_stream_publisher_is_active() {
let stream = Stream::new(make_path());
assert!(stream.publisher().is_active());
}
#[test]
fn test_stream_subscribe_gets_empty_gop_cache() {
let stream = Stream::new(make_path());
let sub = stream.subscribe();
// 尚未写入任何帧GOP 缓存为空
assert!(sub.read_frame().is_none());
assert_eq!(stream.subscriber_count(), 1);
}
#[test]
fn test_stream_dispatch_to_subscriber() {
let stream = Stream::new(make_path());
let sub = stream.subscribe();
stream.dispatch_frame(make_keyframe(100));
let f = sub.read_frame().unwrap();
assert_eq!(f.timestamp_ms, 100);
assert!(f.is_keyframe());
}
#[test]
fn test_stream_gop_cache_new_subscriber_gets_cached_frames() {
let stream = Stream::new(make_path());
// 写入一个 GOP: 关键帧 + 2 个 delta 帧
stream.dispatch_frame(make_keyframe(100));
stream.dispatch_frame(make_interframe(200));
stream.dispatch_frame(make_interframe(300));
// 新订阅者应该收到缓存的 GOP
let sub = stream.subscribe();
let f1 = sub.read_frame().unwrap();
assert_eq!(f1.timestamp_ms, 100);
assert!(f1.is_keyframe());
assert_eq!(sub.read_frame().unwrap().timestamp_ms, 200);
assert_eq!(sub.read_frame().unwrap().timestamp_ms, 300);
}
#[test]
fn test_stream_gop_cache_cleared_on_new_keyframe() {
let stream = Stream::new(make_path());
// 第一个 GOP
stream.dispatch_frame(make_keyframe(100));
stream.dispatch_frame(make_interframe(200));
// 新关键帧清空旧缓存
stream.dispatch_frame(make_keyframe(300));
stream.dispatch_frame(make_interframe(400));
// 新订阅者只应收到第二个 GOP
let sub = stream.subscribe();
assert_eq!(sub.read_frame().unwrap().timestamp_ms, 300);
assert_eq!(sub.read_frame().unwrap().timestamp_ms, 400);
assert!(sub.read_frame().is_none());
}
#[test]
fn test_stream_multiple_subscribers_all_receive_frames() {
let stream = Stream::new(make_path());
let sub1 = stream.subscribe();
let sub2 = stream.subscribe();
stream.dispatch_frame(make_keyframe(50));
stream.dispatch_frame(make_interframe(60));
assert_eq!(sub1.read_frame().unwrap().timestamp_ms, 50);
assert_eq!(sub1.read_frame().unwrap().timestamp_ms, 60);
assert_eq!(sub2.read_frame().unwrap().timestamp_ms, 50);
assert_eq!(sub2.read_frame().unwrap().timestamp_ms, 60);
}
#[test]
fn test_stream_summary_initial_state() {
let stream = Stream::new(make_path());
let s = stream.summary();
assert_eq!(s.path.full_path(), "live/stream");
assert_eq!(s.video_codec, VideoCodec::Unknown);
assert_eq!(s.audio_codec, AudioCodec::Unknown);
assert_eq!(s.subscriber_count, 0);
assert!(s.last_video_timestamp_ms.is_none());
assert!(s.last_audio_timestamp_ms.is_none());
assert_eq!(s.total_video_frames, 0);
assert_eq!(s.total_audio_frames, 0);
}
#[test]
fn test_stream_summary_tracks_codecs_and_counts() {
let stream = Stream::new(make_path());
stream.dispatch_frame(make_keyframe(100));
stream.dispatch_frame(AVFrame::new_audio(100, Arc::new(vec![0xAF]), AudioCodec::Aac));
stream.dispatch_frame(make_interframe(133));
let s = stream.summary();
assert_eq!(s.video_codec, VideoCodec::H264);
assert_eq!(s.audio_codec, AudioCodec::Aac);
assert_eq!(s.total_video_frames, 2);
assert_eq!(s.total_audio_frames, 1);
assert_eq!(s.last_video_timestamp_ms, Some(133));
assert_eq!(s.last_audio_timestamp_ms, Some(100));
assert!(s.gop_cache_size > 0);
}
#[test]
fn test_stream_summary_subscriber_count() {
let stream = Stream::new(make_path());
assert_eq!(stream.summary().subscriber_count, 0);
let _sub1 = stream.subscribe();
assert_eq!(stream.summary().subscriber_count, 1);
let _sub2 = stream.subscribe();
assert_eq!(stream.summary().subscriber_count, 2);
}
/// 测试codec_metadata 初始状态返回空元数据
#[test]
fn test_stream_codec_metadata_initial_state_empty() {
let stream = Stream::new(make_path());
let meta = stream.codec_metadata();
assert!(meta.h264_sps.is_none(), "初始状态不应有 SPS");
assert!(meta.h264_pps.is_none(), "初始状态不应有 PPS");
assert!(meta.aac_config.is_none(), "初始状态不应有 AAC config");
assert_eq!(meta.audio_sample_rate, 0);
assert_eq!(meta.audio_channels, 0);
}
/// 测试codec_metadata 从 GOP 缓存中的 seq_header 帧提取 H.264 SPS/PPS
#[test]
fn test_stream_codec_metadata_extracts_h264_sps_pps() {
let stream = Stream::new(make_path());
let sps_data = Arc::new(vec![0x67, 0x64, 0x00, 0x29, 0xAC, 0xD9, 0x40, 0x78, 0x02]);
let pps_data = Arc::new(vec![0x68, 0xEE, 0x31, 0x12]);
let mut frame = AVFrame::new_video(0, Arc::new(vec![]), VideoCodec::H264, FrameType::KeyFrame);
frame.codec_info = Some(CodecExtraInfo::H264SeqHeader(H264SeqHeader {
sps: sps_data.clone(),
pps: pps_data.clone(),
}));
stream.dispatch_frame(frame);
let meta = stream.codec_metadata();
assert!(meta.h264_sps.is_some(), "应有 SPS");
assert!(meta.h264_pps.is_some(), "应有 PPS");
assert_eq!(&*meta.h264_sps.unwrap(), &*sps_data);
assert_eq!(&*meta.h264_pps.unwrap(), &*pps_data);
}
/// 测试codec_metadata 从 GOP 缓存中的 seq_header 帧提取 AAC config
#[test]
fn test_stream_codec_metadata_extracts_aac_config() {
let stream = Stream::new(make_path());
// AAC AudioSpecificConfig: audioObjectType=2(AAC-LC), samplingFreqIndex=3(48000), channelConfig=2
// byte0 = (audioObjectType=2) << 3 | (sfi=3) >> 1 = 00010_001 = 0x11
// byte1 = (sfi=3 & 1) << 7 | (channelConfig=2) << 3 = 1_0010_000 = 0x90
let aac_config = Arc::new(vec![0x11, 0x90]);
let mut frame = AVFrame::new_audio(0, Arc::new(vec![]), AudioCodec::Aac);
frame.codec_info = Some(CodecExtraInfo::AacSeqHeader(AacSeqHeader {
audio_specific_config: aac_config.clone(),
}));
stream.dispatch_frame(frame);
let meta = stream.codec_metadata();
assert!(meta.aac_config.is_some(), "应有 AAC config");
assert_eq!(&*meta.aac_config.unwrap(), &*aac_config);
assert_eq!(meta.audio_sample_rate, 48000, "采样率应为 48000");
assert_eq!(meta.audio_channels, 2, "通道数应为 2");
}
/// 测试codec_metadata 同时包含视频和音频元数据
#[test]
fn test_stream_codec_metadata_both_video_and_audio() {
let stream = Stream::new(make_path());
let sps_data = Arc::new(vec![0x67, 0x42, 0xC0, 0x1E, 0xD9]);
let pps_data = Arc::new(vec![0x68, 0xCE, 0x38, 0x80]);
// AAC AudioSpecificConfig: samplingFreqIndex=4(44100), channelConfig=2
// byte0 = (2<<3) | (4>>1) = 0x12, byte1 = (4<<7) | (2<<3) = 0x90... wait
// audioObjectType=2(5bits) = 00010
// samplingFreqIndex=4(4bits) = 0100
// channelConfiguration=2(4bits) = 0010
// layout: [00010][0100][0010][000]
// byte0 = 00010_010 = 0x12
// byte1 = 0_0010_000 = 0x10
let aac_config = Arc::new(vec![0x12, 0x10]);
let mut video_frame = AVFrame::new_video(0, Arc::new(vec![]), VideoCodec::H264, FrameType::KeyFrame);
video_frame.codec_info = Some(CodecExtraInfo::H264SeqHeader(H264SeqHeader {
sps: sps_data.clone(),
pps: pps_data.clone(),
}));
stream.dispatch_frame(video_frame);
let mut audio_frame = AVFrame::new_audio(0, Arc::new(vec![]), AudioCodec::Aac);
audio_frame.codec_info = Some(CodecExtraInfo::AacSeqHeader(AacSeqHeader {
audio_specific_config: aac_config.clone(),
}));
stream.dispatch_frame(audio_frame);
let meta = stream.codec_metadata();
assert!(meta.h264_sps.is_some());
assert!(meta.h264_pps.is_some());
assert!(meta.aac_config.is_some());
assert_eq!(meta.audio_sample_rate, 44100, "采样率应为 44100");
assert_eq!(meta.audio_channels, 2, "通道数应为 2");
}
/// 测试sampling_rate_from_index 查表正确
#[test]
fn test_sampling_rate_from_index_known_values() {
assert_eq!(super::sampling_rate_from_index(0), 96000);
assert_eq!(super::sampling_rate_from_index(3), 48000);
assert_eq!(super::sampling_rate_from_index(4), 44100);
assert_eq!(super::sampling_rate_from_index(11), 8000);
assert_eq!(super::sampling_rate_from_index(15), 44100); // 未知索引默认 44100
}
}