#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DemuxLevel(pub u8);
impl DemuxLevel {
pub const FRAME: DemuxLevel = DemuxLevel(1);
pub const CONTAINER: DemuxLevel = DemuxLevel(2);
pub const ELEMENTARY: DemuxLevel = DemuxLevel(4);
pub const ANCILLARY: DemuxLevel = DemuxLevel(8);
pub fn contains(self, level: DemuxLevel) -> bool {
self.0 & level.0 != 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContentType {
MainStream,
SubStream,
Header,
Synchro,
}
#[derive(Debug, Clone)]
pub struct DemuxEvent {
pub content_type: ContentType,
pub stream_ids: Vec<u16>,
pub pts: Option<u64>,
pub dts: Option<u64>,
pub duration: Option<u64>,
pub offset_stream: u64,
pub offset_content: u64,
pub random_access: bool,
}
pub struct DemuxState {
pub active_level: DemuxLevel,
pub events: Vec<DemuxEvent>,
pub frame_number: u64,
pub field_count: u64,
pub total_bytes: u64,
pub first_dts: Option<u64>,
pub unpacketize: bool,
}
impl DemuxState {
pub fn new(level: DemuxLevel) -> Self {
DemuxState {
active_level: level,
events: Vec::new(),
frame_number: 0,
field_count: 0,
total_bytes: 0,
first_dts: None,
unpacketize: false,
}
}
pub fn emit(&mut self, event: DemuxEvent) {
if self.first_dts.is_none() {
self.first_dts = event.dts;
}
self.total_bytes += event.offset_content;
self.frame_number += 1;
self.events.push(event);
}
pub fn event_count(&self) -> usize {
self.events.len()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TraceFormat {
Tree,
Csv,
Xml,
MicroXml,
}
#[derive(Debug, Clone)]
pub struct TraceNode {
pub name: String,
pub file_offset: u64,
pub size: u64,
pub value: Option<String>,
pub infos: Vec<(String, String)>,
pub children: Vec<TraceNode>,
}
impl TraceNode {
pub fn new(name: &str, file_offset: u64) -> Self {
TraceNode {
name: name.to_string(),
file_offset,
size: 0,
value: None,
infos: Vec::new(),
children: Vec::new(),
}
}
pub fn set_value(&mut self, val: &str) {
self.value = Some(val.to_string());
}
pub fn set_size(&mut self, size: u64) {
self.size = size;
}
pub fn add_info(&mut self, key: &str, val: &str) {
self.infos.push((key.to_string(), val.to_string()));
}
pub fn add_child(&mut self, child: TraceNode) {
self.children.push(child);
}
pub fn render(&self, format: TraceFormat, depth: usize) -> String {
match format {
TraceFormat::Tree => self.render_tree(depth),
TraceFormat::Csv => self.render_csv(depth),
TraceFormat::Xml => self.render_xml(depth),
TraceFormat::MicroXml => self.render_micro_xml(),
}
}
fn render_tree(&self, depth: usize) -> String {
let indent = " ".repeat(depth);
let mut s = format!("{}{}", indent, self.name);
if let Some(ref v) = self.value {
s.push_str(&format!(": {}", v));
}
for (k, v) in &self.infos {
s.push_str(&format!(" [{}={}]", k, v));
}
s.push('\n');
for child in &self.children {
s.push_str(&child.render_tree(depth + 1));
}
s
}
fn render_csv(&self, depth: usize) -> String {
let val = self.value.as_deref().unwrap_or("");
let mut s = format!("{},{},{},{},{}\n", depth, self.file_offset, self.name, self.size, val);
for (k, v) in &self.infos {
s.push_str(&format!(" {},\"info\",{},{}\n", depth, k, v));
}
for child in &self.children {
s.push_str(&child.render_csv(depth + 1));
}
s
}
fn render_xml(&self, depth: usize) -> String {
let indent = " ".repeat(depth);
let val = self.value.as_deref().unwrap_or("");
if val.is_empty() && self.children.is_empty() {
format!(
"{}<{} offset=\"{}\" size=\"{}\"/>\n",
indent, self.name, self.file_offset, self.size
)
} else if val.is_empty() {
let children: String = self.children.iter().map(|c| c.render_xml(depth + 1)).collect();
format!(
"{}<{} offset=\"{}\" size=\"{}\">\n{}{}</{}>\n",
indent, self.name, self.file_offset, self.size, children, indent, self.name
)
} else {
let children: String = self.children.iter().map(|c| c.render_xml(depth + 1)).collect();
format!(
"{}<{} offset=\"{}\" size=\"{}\">\n{} {}\n{}{}</{}>\n",
indent,
self.name,
self.file_offset,
self.size,
indent,
val,
children,
indent,
self.name
)
}
}
fn render_micro_xml(&self) -> String {
let val = self.value.as_deref().unwrap_or("");
if self.children.is_empty() {
format!(
"<{} o=\"{}\" s=\"{}\" v=\"{}\"/>\n",
self.name, self.file_offset, self.size, val
)
} else {
let children: String = self.children.iter().map(|c| c.render_micro_xml()).collect();
format!(
"<{} o=\"{}\" s=\"{}\">\n{}</{}>\n",
self.name, self.file_offset, self.size, children, self.name
)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_demux_level() {
let full = DemuxLevel(0x0F);
assert!(full.contains(DemuxLevel::FRAME));
assert!(full.contains(DemuxLevel::CONTAINER));
assert!(full.contains(DemuxLevel::ELEMENTARY));
assert!(full.contains(DemuxLevel::ANCILLARY));
}
#[test]
fn test_demux_state() {
let mut state = DemuxState::new(DemuxLevel::CONTAINER);
state.emit(DemuxEvent {
content_type: ContentType::MainStream,
stream_ids: vec![0x1011],
pts: Some(0),
dts: Some(0),
duration: Some(40),
offset_stream: 0,
offset_content: 1024,
random_access: true,
});
assert_eq!(state.event_count(), 1);
assert_eq!(state.frame_number, 1);
}
#[test]
fn test_trace_tree() {
let mut root = TraceNode::new("mp4", 0);
root.set_size(1024);
let mut child = TraceNode::new("moov", 32);
child.set_size(512);
child.add_info("timescale", "1000");
root.add_child(child);
let tree = root.render(TraceFormat::Tree, 0);
assert!(tree.contains("mp4"));
assert!(tree.contains("moov"));
assert!(tree.contains("timescale=1000"));
}
#[test]
fn test_trace_micro_xml() {
let mut root = TraceNode::new("ftyp", 0);
root.set_value("mp42");
root.set_size(28);
let xml = root.render(TraceFormat::MicroXml, 0);
assert!(xml.contains("ftyp"));
assert!(xml.contains("mp42"));
}
}