WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions crates/camera-directshow/examples/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,11 @@ mod windows {
.start_capturing(
&selected_format.media_type,
Box::new(|frame| {
unsafe { dbg!(frame.sample.GetActualDataLength()) };
// dbg!(frame.media_type.subtype_str());
// dbg!(frame.reference_time);
dbg!(frame.timestamp);
let data_length = unsafe { frame.sample.GetActualDataLength() };
println!(
"Frame: data_length={data_length:?}, timestamp={:?}",
frame.timestamp
);
}),
)
.unwrap();
Expand Down
2 changes: 1 addition & 1 deletion crates/camera-mediafoundation/examples/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ mod windows {

let device_sources = DeviceSourcesIterator::new().unwrap();

if device_sources.len() == 0 {
if device_sources.is_empty() {
warn!("No devices found");
return;
}
Expand Down
3 changes: 2 additions & 1 deletion crates/camera-windows/examples/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ mod windows {
let Ok(bytes) = frame.bytes() else {
return;
};
dbg!(
println!(
"Frame: len={}, pixel_format={:?}, timestamp={:?}, perf_counter={:?}",
bytes.len(),
frame.pixel_format,
frame.timestamp,
Expand Down
162 changes: 137 additions & 25 deletions crates/enc-avfoundation/src/segmented.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,37 @@ use cap_media_info::{AudioInfo, VideoInfo};
use cidre::arc;
use ffmpeg::frame;
use serde::Serialize;
use std::{path::PathBuf, time::Duration};
use std::{
io::Write,
path::{Path, PathBuf},
time::Duration,
};

fn atomic_write_json<T: Serialize>(path: &Path, data: &T) -> std::io::Result<()> {
let temp_path = path.with_extension("json.tmp");
let json = serde_json::to_string_pretty(data)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;

let mut file = std::fs::File::create(&temp_path)?;
file.write_all(json.as_bytes())?;
file.sync_all()?;

std::fs::rename(&temp_path, path)?;

if let Some(parent) = path.parent()
&& let Ok(dir) = std::fs::File::open(parent)
{
let _ = dir.sync_all();
}

Ok(())
}

fn sync_file(path: &Path) {
if let Ok(file) = std::fs::File::open(path) {
let _ = file.sync_all();
}
}

pub struct SegmentedMP4Encoder {
base_path: PathBuf,
Expand All @@ -24,6 +54,7 @@ pub struct SegmentInfo {
pub path: PathBuf,
pub index: u32,
pub duration: Duration,
pub file_size: Option<u64>,
}

#[derive(Serialize)]
Expand All @@ -32,10 +63,15 @@ struct FragmentEntry {
index: u32,
duration: f64,
is_complete: bool,
#[serde(skip_serializing_if = "Option::is_none")]
file_size: Option<u64>,
}

const MANIFEST_VERSION: u32 = 2;

#[derive(Serialize)]
struct Manifest {
version: u32,
fragments: Vec<FragmentEntry>,
#[serde(skip_serializing_if = "Option::is_none")]
total_duration: Option<f64>,
Expand All @@ -55,7 +91,7 @@ impl SegmentedMP4Encoder {
let segment_path = base_path.join("fragment_000.mp4");
let encoder = MP4Encoder::init(segment_path, video_config, audio_config, output_height)?;

Ok(Self {
let instance = Self {
base_path,
video_config,
audio_config,
Expand All @@ -65,7 +101,11 @@ impl SegmentedMP4Encoder {
segment_duration,
segment_start_time: None,
completed_segments: Vec::new(),
})
};

instance.write_in_progress_manifest();

Ok(instance)
}

pub fn queue_video_frame(
Expand Down Expand Up @@ -106,15 +146,27 @@ impl SegmentedMP4Encoder {
fn rotate_segment(&mut self, timestamp: Duration) -> Result<(), QueueFrameError> {
let segment_start = self.segment_start_time.unwrap_or(Duration::ZERO);
let segment_duration = timestamp.saturating_sub(segment_start);
let completed_segment_path = self.current_segment_path();

if let Some(mut encoder) = self.current_encoder.take() {
let _ = encoder.finish(Some(timestamp));
if let Err(e) = encoder.finish(Some(timestamp)) {
tracing::warn!("Failed to finish encoder during rotation: {e}");
}

sync_file(&completed_segment_path);

let file_size = std::fs::metadata(&completed_segment_path)
.ok()
.map(|m| m.len());

self.completed_segments.push(SegmentInfo {
path: self.current_segment_path(),
path: completed_segment_path,
index: self.current_index,
duration: segment_duration,
file_size,
});

self.write_manifest();
}

self.current_index += 1;
Expand All @@ -131,7 +183,7 @@ impl SegmentedMP4Encoder {
.map_err(|_| QueueFrameError::Failed)?,
);

self.write_manifest();
self.write_in_progress_manifest();

Ok(())
}
Expand All @@ -143,6 +195,7 @@ impl SegmentedMP4Encoder {

fn write_manifest(&self) {
let manifest = Manifest {
version: MANIFEST_VERSION,
fragments: self
.completed_segments
.iter()
Expand All @@ -156,17 +209,67 @@ impl SegmentedMP4Encoder {
index: s.index,
duration: s.duration.as_secs_f64(),
is_complete: true,
file_size: s.file_size,
})
.collect(),
total_duration: None,
is_complete: false,
};

let manifest_path = self.base_path.join("manifest.json");
let _ = std::fs::write(
manifest_path,
serde_json::to_string_pretty(&manifest).unwrap_or_default(),
);
if let Err(e) = atomic_write_json(&manifest_path, &manifest) {
tracing::warn!(
"Failed to write manifest to {}: {e}",
manifest_path.display()
);
}
}

fn write_in_progress_manifest(&self) {
let mut fragments: Vec<FragmentEntry> = self
.completed_segments
.iter()
.map(|s| FragmentEntry {
path: s
.path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned(),
index: s.index,
duration: s.duration.as_secs_f64(),
is_complete: true,
file_size: s.file_size,
})
.collect();

fragments.push(FragmentEntry {
path: self
.current_segment_path()
.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned(),
index: self.current_index,
duration: 0.0,
is_complete: false,
file_size: None,
});

let manifest = Manifest {
version: MANIFEST_VERSION,
fragments,
total_duration: None,
is_complete: false,
};

let manifest_path = self.base_path.join("manifest.json");
if let Err(e) = atomic_write_json(&manifest_path, &manifest) {
tracing::warn!(
"Failed to write in-progress manifest to {}: {e}",
manifest_path.display()
);
}
}

pub fn pause(&mut self) {
Expand All @@ -182,20 +285,25 @@ impl SegmentedMP4Encoder {
}

pub fn finish(&mut self, timestamp: Option<Duration>) -> Result<(), FinishError> {
if let Some(segment_start) = self.segment_start_time {
let final_duration = timestamp
.unwrap_or(segment_start)
.saturating_sub(segment_start);

self.completed_segments.push(SegmentInfo {
path: self.current_segment_path(),
index: self.current_index,
duration: final_duration,
});
}
let segment_path = self.current_segment_path();
let segment_start = self.segment_start_time;

if let Some(mut encoder) = self.current_encoder.take() {
encoder.finish(timestamp)?;

sync_file(&segment_path);

if let Some(start) = segment_start {
let final_duration = timestamp.unwrap_or(start).saturating_sub(start);
let file_size = std::fs::metadata(&segment_path).ok().map(|m| m.len());

self.completed_segments.push(SegmentInfo {
path: segment_path,
index: self.current_index,
duration: final_duration,
file_size,
});
}
}

self.finalize_manifest();
Expand All @@ -207,6 +315,7 @@ impl SegmentedMP4Encoder {
let total_duration: Duration = self.completed_segments.iter().map(|s| s.duration).sum();

let manifest = Manifest {
version: MANIFEST_VERSION,
fragments: self
.completed_segments
.iter()
Expand All @@ -220,17 +329,20 @@ impl SegmentedMP4Encoder {
index: s.index,
duration: s.duration.as_secs_f64(),
is_complete: true,
file_size: s.file_size,
})
.collect(),
total_duration: Some(total_duration.as_secs_f64()),
is_complete: true,
};

let manifest_path = self.base_path.join("manifest.json");
let _ = std::fs::write(
manifest_path,
serde_json::to_string_pretty(&manifest).unwrap_or_default(),
);
if let Err(e) = atomic_write_json(&manifest_path, &manifest) {
tracing::warn!(
"Failed to write final manifest to {}: {e}",
manifest_path.display()
);
}
}

pub fn completed_segments(&self) -> &[SegmentInfo] {
Expand Down
Loading