Skip to content

Commit 1996cdf

Browse files
authored
Encoder error handling improvements (#1305)
* more clippy stuff + handle encoder retries for enc-avfoundation * don't save pr caches * mostly fix windows * audio input NotReadyForMore * fix macos * hard error on write_trailer * fix lock error * beter enc-avfoundation error handling and windows fixes * macos * use double result for muxer partial failure * cleanup * better error reporting
1 parent 47a0e07 commit 1996cdf

File tree

43 files changed

+406
-325
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+406
-325
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ jobs:
103103
uses: swatinem/rust-cache@v2
104104
with:
105105
shared-key: ${{ matrix.settings.target }}
106+
save-if: ${{ github.ref == 'refs/heads/main' }}
106107

107108
- name: Create .env file in root
108109
run: |
@@ -169,6 +170,7 @@ jobs:
169170
uses: swatinem/rust-cache@v2
170171
with:
171172
shared-key: ${{ matrix.settings.target }}
173+
save-if: ${{ github.ref == 'refs/heads/main' }}
172174

173175
- uses: ./.github/actions/setup-js
174176

.github/workflows/publish.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ jobs:
173173
uses: swatinem/rust-cache@v2
174174
with:
175175
shared-key: ${{ matrix.settings.target }}
176+
save-if: ${{ github.ref == 'refs/heads/main' }}
176177

177178
- uses: ./.github/actions/setup-js
178179

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ percent-encoding = "2.3.1"
6464
[workspace.lints.rust]
6565
deprecated = "allow"
6666
unexpected_cfgs = "allow"
67+
unused_must_use = "deny"
6768

6869
[workspace.lints.clippy]
6970
dbg_macro = "deny"

apps/desktop/src-tauri/src/target_select_overlay.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,15 +196,15 @@ pub async fn focus_window(window_id: WindowId) -> Result<(), String> {
196196
if GetWindowPlacement(hwnd, &mut wp).is_ok() {
197197
// Restore using the previous placement to avoid resizing
198198
wp.showCmd = SW_RESTORE.0 as u32;
199-
SetWindowPlacement(hwnd, &wp);
199+
let _ = SetWindowPlacement(hwnd, &wp);
200200
} else {
201201
// Fallback to simple restore if placement fails
202-
ShowWindow(hwnd, SW_RESTORE);
202+
let _ = ShowWindow(hwnd, SW_RESTORE);
203203
}
204204
}
205205

206206
// Always try to bring to foreground
207-
SetForegroundWindow(hwnd);
207+
let _ = SetForegroundWindow(hwnd);
208208
}
209209
}
210210

crates/audio/src/latency.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ const WARMUP_GUARD_SAMPLES: u32 = 3;
230230
const WARMUP_SPIKE_RATIO: f64 = 50.0;
231231
#[cfg(not(target_os = "macos"))]
232232
const FALLBACK_WIRED_LATENCY_SECS: f64 = 0.03;
233+
#[cfg(target_os = "macos")]
233234
const WIRELESS_FALLBACK_LATENCY_SECS: f64 = 0.20;
234235
const WIRELESS_MIN_LATENCY_SECS: f64 = 0.12;
235236

@@ -615,6 +616,7 @@ mod macos {
615616
}
616617

617618
#[cfg(test)]
619+
#[allow(clippy::unchecked_duration_subtraction)]
618620
mod tests {
619621
use super::*;
620622
use std::time::Instant;

crates/camera-directshow/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::{
1010
ptr::{self, null, null_mut},
1111
time::{Duration, Instant},
1212
};
13-
use tracing::{trace, warn};
13+
use tracing::*;
1414
use windows::{
1515
Win32::{
1616
Foundation::*,

crates/camera-windows/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{
66
ffi::{OsStr, OsString},
77
fmt::{Debug, Display},
88
ops::Deref,
9-
time::{Duration, Instant},
9+
time::Duration,
1010
};
1111

1212
use windows::Win32::Media::{DirectShow::*, KernelStreaming::*, MediaFoundation::*};

crates/enc-avfoundation/src/mp4.rs

Lines changed: 71 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use cap_media_info::{AudioInfo, VideoInfo};
22
use cidre::{cm::SampleTimingInfo, objc::Obj, *};
33
use ffmpeg::frame;
44
use std::{ops::Sub, path::PathBuf, time::Duration};
5-
use tracing::{debug, error, info, trace};
5+
use tracing::*;
66

77
// before pausing at all, subtract 0.
88
// on pause, record last frame time.
@@ -48,23 +48,23 @@ pub enum InitError {
4848
}
4949

5050
#[derive(thiserror::Error, Debug)]
51-
pub enum QueueVideoFrameError {
51+
pub enum QueueFrameError {
5252
#[error("AppendError/{0}")]
5353
AppendError(arc::R<ns::Exception>),
5454
#[error("Failed")]
5555
Failed,
56+
#[error("Construct/{0}")]
57+
Construct(cidre::os::Error),
58+
#[error("NotReadyForMore")]
59+
NotReadyForMore,
5660
}
5761

5862
#[derive(thiserror::Error, Debug)]
59-
pub enum QueueAudioFrameError {
60-
#[error("No audio input")]
61-
NoAudioInput,
62-
#[error("Not ready")]
63-
NotReady,
64-
#[error("Setup/{0}")]
65-
Setup(cidre::os::Error),
66-
#[error("AppendError/{0}")]
67-
AppendError(&'static cidre::ns::Exception),
63+
pub enum FinishError {
64+
#[error("NotWriting")]
65+
NotWriting,
66+
#[error("NoFrames")]
67+
NoFrames,
6868
#[error("Failed")]
6969
Failed,
7070
}
@@ -219,11 +219,15 @@ impl MP4Encoder {
219219
&mut self,
220220
frame: arc::R<cm::SampleBuf>,
221221
timestamp: Duration,
222-
) -> Result<(), QueueVideoFrameError> {
223-
if self.is_paused || !self.video_input.is_ready_for_more_media_data() {
222+
) -> Result<(), QueueFrameError> {
223+
if self.is_paused {
224224
return Ok(());
225225
};
226226

227+
if !self.video_input.is_ready_for_more_media_data() {
228+
return Err(QueueFrameError::NotReadyForMore);
229+
}
230+
227231
if !self.is_writing {
228232
self.is_writing = true;
229233
self.asset_writer
@@ -270,10 +274,7 @@ impl MP4Encoder {
270274
timing.pts = cm::Time::new(pts_duration.as_millis() as i64, 1_000);
271275
let frame = frame.copy_with_new_timing(&[timing]).unwrap();
272276

273-
self.video_input
274-
.append_sample_buf(&frame)
275-
.map_err(|e| QueueVideoFrameError::AppendError(e.retained()))
276-
.and_then(|v| v.then_some(()).ok_or(QueueVideoFrameError::Failed))?;
277+
append_sample_buf(&mut self.video_input, &self.asset_writer, &frame)?;
277278

278279
self.video_frames_appended += 1;
279280
self.last_timestamp = Some(timestamp);
@@ -285,26 +286,26 @@ impl MP4Encoder {
285286
/// in the timebase of 1 / sample rate
286287
pub fn queue_audio_frame(
287288
&mut self,
288-
frame: frame::Audio,
289+
frame: &frame::Audio,
289290
timestamp: Duration,
290-
) -> Result<(), QueueAudioFrameError> {
291+
) -> Result<(), QueueFrameError> {
291292
if self.is_paused || !self.is_writing {
292293
return Ok(());
293294
}
294295

296+
let Some(audio_input) = &mut self.audio_input else {
297+
return Err(QueueFrameError::Failed);
298+
};
299+
295300
if let Some(pause_timestamp) = self.pause_timestamp
296301
&& let Some(gap) = timestamp.checked_sub(pause_timestamp)
297302
{
298303
self.timestamp_offset += gap;
299304
self.pause_timestamp = None;
300305
}
301306

302-
let Some(audio_input) = &mut self.audio_input else {
303-
return Err(QueueAudioFrameError::NoAudioInput);
304-
};
305-
306307
if !audio_input.is_ready_for_more_media_data() {
307-
return Ok(());
308+
return Err(QueueFrameError::NotReadyForMore);
308309
}
309310

310311
let audio_desc = cat::audio::StreamBasicDesc::common_f32(
@@ -316,11 +317,11 @@ impl MP4Encoder {
316317
let total_data = frame.samples() * frame.channels() as usize * frame.format().bytes();
317318

318319
let mut block_buf =
319-
cm::BlockBuf::with_mem_block(total_data, None).map_err(QueueAudioFrameError::Setup)?;
320+
cm::BlockBuf::with_mem_block(total_data, None).map_err(QueueFrameError::Construct)?;
320321

321322
let block_buf_slice = block_buf
322323
.as_mut_slice()
323-
.map_err(QueueAudioFrameError::Setup)?;
324+
.map_err(QueueFrameError::Construct)?;
324325

325326
if frame.is_planar() {
326327
let mut offset = 0;
@@ -335,7 +336,7 @@ impl MP4Encoder {
335336
}
336337

337338
let format_desc =
338-
cm::AudioFormatDesc::with_asbd(&audio_desc).map_err(QueueAudioFrameError::Setup)?;
339+
cm::AudioFormatDesc::with_asbd(&audio_desc).map_err(QueueFrameError::Construct)?;
339340

340341
let mut pts_duration = timestamp
341342
.checked_sub(self.timestamp_offset)
@@ -344,7 +345,7 @@ impl MP4Encoder {
344345
if let Some(last_pts) = self.last_audio_pts
345346
&& pts_duration <= last_pts
346347
{
347-
let frame_duration = Self::audio_frame_duration(&frame);
348+
let frame_duration = Self::audio_frame_duration(frame);
348349
let adjusted_pts = last_pts + frame_duration;
349350

350351
trace!(
@@ -383,12 +384,9 @@ impl MP4Encoder {
383384
}],
384385
&[],
385386
)
386-
.map_err(QueueAudioFrameError::Setup)?;
387+
.map_err(QueueFrameError::Construct)?;
387388

388-
audio_input
389-
.append_sample_buf(&buffer)
390-
.map_err(QueueAudioFrameError::AppendError)
391-
.and_then(|v| v.then_some(()).ok_or(QueueAudioFrameError::Failed))?;
389+
append_sample_buf(audio_input, &self.asset_writer, &buffer)?;
392390

393391
self.audio_frames_appended += 1;
394392
self.last_timestamp = Some(timestamp);
@@ -449,13 +447,14 @@ impl MP4Encoder {
449447
self.is_paused = false;
450448
}
451449

452-
pub fn finish(&mut self, timestamp: Option<Duration>) {
450+
pub fn finish(&mut self, timestamp: Option<Duration>) -> Result<(), FinishError> {
453451
if !self.is_writing {
454-
return;
452+
return Err(FinishError::NotWriting);
455453
}
456454

457455
let Some(mut most_recent_frame) = self.most_recent_frame.take() else {
458-
return;
456+
warn!("Encoder attempted to finish with no frame");
457+
return Err(FinishError::NoFrames);
459458
};
460459

461460
// We extend the video to the provided timestamp if possible
@@ -489,16 +488,17 @@ impl MP4Encoder {
489488
debug!("Appended {} video frames", self.video_frames_appended);
490489
debug!("Appended {} audio frames", self.audio_frames_appended);
491490

492-
// debug!("First video timestamp: {:?}", self.first_timestamp);
493-
// debug!("Last video timestamp: {:?}", self.last_pts);
491+
wait_for_writer_finished(&self.asset_writer).map_err(|_| FinishError::Failed)?;
494492

495493
info!("Finished writing");
494+
495+
Ok(())
496496
}
497497
}
498498

499499
impl Drop for MP4Encoder {
500500
fn drop(&mut self) {
501-
self.finish(None);
501+
let _ = self.finish(None);
502502
}
503503
}
504504

@@ -621,3 +621,35 @@ impl SampleBufExt for cm::SampleBuf {
621621
}
622622
}
623623
}
624+
625+
fn append_sample_buf(
626+
input: &mut av::AssetWriterInput,
627+
writer: &av::AssetWriter,
628+
frame: &cm::SampleBuf,
629+
) -> Result<(), QueueFrameError> {
630+
match input.append_sample_buf(frame) {
631+
Ok(true) => {}
632+
Ok(false) => {
633+
if writer.status() == av::asset::writer::Status::Failed {
634+
return Err(QueueFrameError::Failed);
635+
}
636+
if writer.status() == av::asset::writer::Status::Writing {
637+
return Err(QueueFrameError::NotReadyForMore);
638+
}
639+
}
640+
Err(e) => return Err(QueueFrameError::AppendError(e.retained())),
641+
}
642+
643+
Ok(())
644+
}
645+
646+
fn wait_for_writer_finished(writer: &av::AssetWriter) -> Result<(), ()> {
647+
use av::asset::writer::Status;
648+
loop {
649+
match writer.status() {
650+
Status::Completed | Status::Cancelled => return Ok(()),
651+
Status::Failed | Status::Unknown => return Err(()),
652+
Status::Writing => std::thread::sleep(Duration::from_millis(2)),
653+
}
654+
}
655+
}

crates/enc-ffmpeg/src/audio/aac.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ impl AACEncoder {
104104
self.base.send_frame(frame, timestamp, output)
105105
}
106106

107-
pub fn finish(&mut self, output: &mut format::context::Output) -> Result<(), ffmpeg::Error> {
108-
self.base.finish(output)
107+
pub fn flush(&mut self, output: &mut format::context::Output) -> Result<(), ffmpeg::Error> {
108+
self.base.flush(output)
109109
}
110110
}
111111

@@ -114,7 +114,7 @@ impl AudioEncoder for AACEncoder {
114114
let _ = self.send_frame(frame, Duration::MAX, output);
115115
}
116116

117-
fn finish(&mut self, output: &mut format::context::Output) {
118-
let _ = self.finish(output);
117+
fn flush(&mut self, output: &mut format::context::Output) -> Result<(), ffmpeg::Error> {
118+
self.flush(output)
119119
}
120120
}

0 commit comments

Comments
 (0)