mod entry;
mod events;
mod fanout;
mod join;
#[cfg(test)]
mod tests;
use crate::flv::flv_tag_body::{is_audio_sequence_header, is_video_sequence_header};
use crate::rtmp::gop::Gops;
use bytes::Bytes;
use rml_rtmp::chunk_io::{ChunkSerializationError, ChunkSerializer, Packet};
use rml_rtmp::messages::{MessageSerializationError, RtmpMessage};
use rml_rtmp::sessions::StreamMetadata;
use rml_rtmp::sessions::{ServerSession, ServerSessionError, ServerSessionEvent};
use rml_rtmp::time::RtmpTimestamp;
use slab::Slab;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use thiserror::Error;
#[derive(Error, Debug)]
pub(super) enum SchedulerError {
#[error("RTMP session error: {0}")]
Session(#[from] ServerSessionError),
#[error("oversized sequence header: {size} bytes exceeds the {limit}-byte cacheable limit")]
OversizedSequenceHeader { size: usize, limit: usize },
}
const OUTBOUND_CHUNK_SIZE: usize = 4096;
const MAX_CACHEABLE_SEQUENCE_HEADER_BYTES: usize = 256 * 1024;
const WATCHER_SET_SHRINK_MIN_CAPACITY: usize = 64;
enum ClientAction {
Waiting,
Publishing {
stream_key: Rc<str>,
channel: ChannelHandle,
},
Watching {
stream_key: String,
stream_id: u32,
channel: ChannelHandle,
},
}
#[derive(Clone, Copy)]
enum ReceivedDataType {
Audio,
Video,
}
struct Client {
session: ServerSession,
current_action: ClientAction,
connection_id: usize,
has_received_video_keyframe: bool,
}
impl Client {
fn get_active_stream_id(&self) -> Option<u32> {
match self.current_action {
ClientAction::Waiting => None,
ClientAction::Publishing { .. } => None,
ClientAction::Watching { stream_id, .. } => Some(stream_id),
}
}
}
struct MediaChannel {
publishing_client_id: Option<usize>,
watching_client_ids: HashSet<usize>,
metadata: Option<Rc<StreamMetadata>>,
video_sequence_header: Option<Bytes>,
video_timestamp: RtmpTimestamp,
audio_sequence_header: Option<Bytes>,
audio_timestamp: RtmpTimestamp,
gops: Gops,
fanout_serializer: ChunkSerializer,
}
impl MediaChannel {
fn new(gop_limit: usize) -> MediaChannel {
Self {
publishing_client_id: None,
watching_client_ids: Default::default(),
metadata: None,
video_sequence_header: None,
video_timestamp: RtmpTimestamp { value: 0 },
audio_sequence_header: None,
audio_timestamp: RtmpTimestamp { value: 0 },
gops: Gops::new(gop_limit),
fanout_serializer: new_media_serializer(),
}
}
fn should_remove(&self) -> bool {
self.publishing_client_id.is_none() && self.watching_client_ids.is_empty()
}
fn shrink_watchers_if_sparse(&mut self) {
let watchers = &mut self.watching_client_ids;
if watchers.capacity() > WATCHER_SET_SHRINK_MIN_CAPACITY
&& watchers.len() < watchers.capacity() / 4
{
watchers.shrink_to(watchers.len() * 2);
}
}
}
#[derive(Debug)]
pub(super) enum ServerResult {
DisconnectConnection {
connection_id: usize,
},
OutboundPacket {
target_connection_id: usize,
bytes: Bytes,
can_be_dropped: bool,
is_keyframe: bool,
is_sequence_header: bool,
is_video: bool,
},
}
impl ServerResult {
fn outbound(
target_connection_id: usize,
packet: Packet,
is_keyframe: bool,
is_sequence_header: bool,
is_video: bool,
) -> ServerResult {
ServerResult::OutboundPacket {
target_connection_id,
bytes: Bytes::from(packet.bytes),
can_be_dropped: packet.can_be_dropped,
is_keyframe,
is_sequence_header,
is_video,
}
}
}
fn new_media_serializer() -> ChunkSerializer {
let mut serializer = ChunkSerializer::new();
serializer
.set_max_chunk_size(OUTBOUND_CHUNK_SIZE as u32, RtmpTimestamp { value: 0 })
.expect("4096 is a valid outbound chunk size");
serializer
}
#[derive(Debug)]
enum MediaSerializeError {
Message(#[allow(dead_code)] MessageSerializationError),
Chunk(#[allow(dead_code)] ChunkSerializationError),
}
fn serialize_media(
serializer: &mut ChunkSerializer,
data_type: ReceivedDataType,
stream_id: u32,
data: Bytes,
timestamp: RtmpTimestamp,
can_be_dropped: bool,
) -> Result<Packet, MediaSerializeError> {
let message = match data_type {
ReceivedDataType::Audio => RtmpMessage::AudioData { data },
ReceivedDataType::Video => RtmpMessage::VideoData { data },
};
let payload = message
.into_message_payload(timestamp, stream_id)
.map_err(MediaSerializeError::Message)?;
serializer
.serialize(&payload, false, can_be_dropped)
.map_err(MediaSerializeError::Chunk)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct ChannelHandle {
index: usize,
generation: u64,
}
struct ChannelSlot {
generation: u64,
stream_key: Rc<str>,
channel: MediaChannel,
}
struct ChannelTable {
slots: Slab<ChannelSlot>,
by_key: HashMap<Rc<str>, usize>,
next_generation: u64,
}
impl ChannelTable {
fn new() -> ChannelTable {
ChannelTable {
slots: Slab::new(),
by_key: HashMap::new(),
next_generation: 1,
}
}
fn get(&self, stream_key: &str) -> Option<&MediaChannel> {
let &index = self.by_key.get(stream_key)?;
self.slots.get(index).map(|slot| &slot.channel)
}
fn get_mut(&mut self, stream_key: &str) -> Option<&mut MediaChannel> {
let &index = self.by_key.get(stream_key)?;
self.slots.get_mut(index).map(|slot| &mut slot.channel)
}
#[cfg_attr(not(test), allow(dead_code))]
fn contains_key(&self, stream_key: &str) -> bool {
self.by_key.contains_key(stream_key)
}
fn handle_by_key(&self, stream_key: &str) -> Option<ChannelHandle> {
let &index = self.by_key.get(stream_key)?;
let slot = self.slots.get(index)?;
Some(ChannelHandle {
index,
generation: slot.generation,
})
}
fn slot_by_handle_mut(&mut self, handle: ChannelHandle) -> Option<&mut ChannelSlot> {
self.slots
.get_mut(handle.index)
.filter(|slot| slot.generation == handle.generation)
}
fn get_or_create(
&mut self,
stream_key: &str,
gop_limit: usize,
) -> (ChannelHandle, &mut ChannelSlot) {
let index = match self.by_key.get(stream_key).copied() {
Some(index) => index,
None => {
let generation = self.next_generation;
self.next_generation = self
.next_generation
.checked_add(1)
.expect("channel generation counter exhausted");
let key: Rc<str> = Rc::from(stream_key);
let index = self.slots.insert(ChannelSlot {
generation,
stream_key: key.clone(),
channel: MediaChannel::new(gop_limit),
});
self.by_key.insert(key, index);
index
}
};
let slot = self
.slots
.get_mut(index)
.expect("channel table invariant: by_key always points at an occupied slot");
(
ChannelHandle {
index,
generation: slot.generation,
},
slot,
)
}
fn remove(&mut self, stream_key: &str) {
if let Some(index) = self.by_key.remove(stream_key) {
self.slots.try_remove(index);
}
}
}
pub(super) struct RtmpScheduler {
clients: Slab<Client>,
connection_to_client_map: HashMap<usize, usize>,
publisher_to_client_map: HashMap<usize, usize>,
channels: ChannelTable,
gop_limit: usize,
serving_connection_backlog_bytes: usize,
serving_prefix_scan_pos: usize,
serving_prefix_bytes: usize,
}
fn is_oversized_sequence_header(data: &Bytes, data_type: ReceivedDataType) -> bool {
let is_sequence_header = match data_type {
ReceivedDataType::Video => is_video_sequence_header(data),
ReceivedDataType::Audio => is_audio_sequence_header(data),
};
is_sequence_header && data.len() > MAX_CACHEABLE_SEQUENCE_HEADER_BYTES
}
fn oversized_sequence_header_error(event: &ServerSessionEvent) -> Option<SchedulerError> {
let (data, data_type) = match event {
ServerSessionEvent::VideoDataReceived { data, .. } => (data, ReceivedDataType::Video),
ServerSessionEvent::AudioDataReceived { data, .. } => (data, ReceivedDataType::Audio),
_ => return None,
};
if is_oversized_sequence_header(data, data_type) {
Some(SchedulerError::OversizedSequenceHeader {
size: data.len(),
limit: MAX_CACHEABLE_SEQUENCE_HEADER_BYTES,
})
} else {
None
}
}