//! 流实例 — 发布者 + 分发器 + 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, /// 分发器 dispatcher: Arc, /// GOP 缓存:保存最近一个完整 GOP(关键帧 + 后续 delta 帧) /// /// 新订阅者连接时,先将缓存中的帧推送给它, /// 确保播放端能立即从关键帧开始解码 gop_cache: Mutex>, /// 已检测到的视频编解码器 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 { self.publisher.clone() } /// 创建订阅者并加入分发器 /// /// 订阅时会先将 GOP 缓存中的帧推送给新订阅者, /// 然后将订阅者注册到 Dispatcher 以接收后续帧 pub fn subscribe(&self) -> Arc { 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 = 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 → audioObjectType(5 bits 从 byte0 高 5 位) /// - bits [4:1] byte1 → samplingFrequencyIndex(4 bits) /// - bit [0] byte1 + [7:5] byte2 → channelConfiguration(4 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 } }