1#[cfg(feature = "nv-encode")]
18use std::rc::Rc;
19
20#[cfg(feature = "nv-encode")]
21use machine_vision_formats::image_ref::ImageRefMut;
22use strand_cam_remote_control::{H264_METADATA_UUID, H264Metadata, Mp4RecordingConfig};
23#[cfg(feature = "nv-encode")]
24use tracing::info;
25use tracing::{debug, trace};
26
27use strand_dynamic_frame::DynamicFrame;
28
29#[cfg(feature = "nv-encode")]
30use machine_vision_formats::pixel_format;
31
32#[cfg(feature = "nv-encode")]
33use nvenc::{InputBuffer, OutputBuffer, RateControlMode};
34
35use thiserror::Error;
36
37mod h264_annexb_split;
38use h264_annexb_split::h264_annexb_split;
39
40const MOVIE_TIMESCALE: u32 = 90_000;
42const TRACK_ID: u32 = 1;
43
44#[derive(Error, Debug)]
45pub enum Error {
46 #[error("{source}")]
47 Mp4Error {
48 #[from]
49 source: mp4::Error,
50 },
51 #[error("IO error: {source}")]
52 IoError {
53 #[from]
54 source: std::io::Error,
55 },
56 #[error("required h264 data (SPS or PPS) not found")]
57 RequiredH264DataNotFound {},
58 #[error("file already closed")]
59 FileAlreadyClosed {},
60 #[error("cannot encode frame when copying h264 stream")]
61 RawH264CopyCannotEncodeFrame {},
62 #[error("bad input data")]
63 BadInputData {},
64 #[error("inconsistent state")]
65 InconsistentState {},
66 #[error("timestamp too large")]
67 TimestampTooLarge {},
68 #[error("convert image error")]
69 ConvertImageError(#[from] convert_image::Error),
70 #[cfg(feature = "openh264")]
71 #[error("openhs264 error {}", inner)]
72 OpenH264Error {
73 #[from]
74 inner: openh264::Error,
75 },
76 #[cfg(not(feature = "nv-encode"))]
77 #[error("no nvenc at compile time")]
78 NoNvencCompiledError,
79 #[cfg(feature = "nv-encode")]
80 #[error("nvenc error")]
81 NvencError(#[from] nvenc::NvEncError),
82 #[error("nvenc libraries not loaded")]
83 NvencLibsNotLoaded,
84 #[error("less-avc error {}", inner)]
85 LessAvcWrapperError {
86 #[from]
87 inner: less_avc_wrapper::Error,
88 },
89 #[error("y4m-writer error {0}")]
90 Y4mWriterError(#[from] y4m_writer::Error),
91}
92
93#[cfg(feature = "nv-encode")]
94impl From<dynlink_nvidia_encode::NvencError> for Error {
95 fn from(orig: dynlink_nvidia_encode::NvencError) -> Self {
96 Error::NvencError(orig.into())
97 }
98}
99
100#[cfg(feature = "nv-encode")]
101impl From<dynlink_cuda::CudaError> for Error {
102 fn from(orig: dynlink_cuda::CudaError) -> Self {
103 Error::NvencError(orig.into())
104 }
105}
106
107type Result<T> = std::result::Result<T, Error>;
108
109enum MyEncoder<'lib> {
110 CopyRawH264 {
111 h264_parser: H264Parser,
112 },
113 #[cfg(feature = "nv-encode")]
114 Nvidia(NvEncoder<'lib>),
115 #[cfg(not(feature = "nv-encode"))]
116 #[expect(dead_code)]
117 NoNvidia(std::marker::PhantomData<&'lib u8>),
118 #[cfg(feature = "openh264")]
119 OpenH264(Box<OpenH264Encoder>),
120 LessH264(LessEncoderWrapper),
121}
122
123pub struct Mp4Writer<'lib, T>
124where
125 T: std::io::Write + std::io::Seek,
126{
127 inner: Option<WriteState<'lib, T>>,
128 #[cfg(feature = "nv-encode")]
129 nv_enc: Option<nvenc::NvEnc<'lib>>,
130 first_sps: Option<Vec<u8>>,
131 first_pps: Option<Vec<u8>>,
132}
133
134impl<'lib, T> Mp4Writer<'lib, T>
135where
136 T: std::io::Write + std::io::Seek,
137{
138 pub fn new(
139 fd: T,
140 config: Mp4RecordingConfig,
141 #[cfg(feature = "nv-encode")] nv_enc: Option<nvenc::NvEnc<'lib>>,
142 ) -> Result<Self> {
143 let h264_parser = H264Parser::new(config.h264_metadata.clone());
144 Ok(Self {
145 inner: Some(WriteState::Configured(Box::new((fd, config, h264_parser)))),
146 #[cfg(feature = "nv-encode")]
147 nv_enc,
148 first_sps: None,
149 first_pps: None,
150 })
151 }
152
153 pub fn set_first_sps_pps(&mut self, first_sps: Option<Vec<u8>>, first_pps: Option<Vec<u8>>) {
154 self.first_sps = first_sps;
155 self.first_pps = first_pps;
156 }
157
158 pub fn write_h264_buf<TS1, TS2>(
162 &mut self,
163 data: &frame_source::H264EncodingVariant,
164 width: u32,
165 height: u32,
166 timestamp: TS1,
167 frame0_time: TS2,
168 insert_precision_timestamp: bool,
169 ) -> Result<()>
170 where
171 TS1: Into<chrono::DateTime<chrono::Local>>,
172 TS2: Into<chrono::DateTime<chrono::Local>>,
173 {
174 let timestamp: chrono::DateTime<chrono::Local> = timestamp.into();
175 let frame0_time: chrono::DateTime<chrono::Local> = frame0_time.into();
176
177 let pts = timestamp - frame0_time;
178 let mp4_sample_start_time = dur2raw(&pts.to_std().unwrap());
179 let precision_timestamp = insert_precision_timestamp.then_some(timestamp);
180 self.write_h264_sample(
184 data,
185 width,
186 height,
187 mp4_sample_start_time,
188 pts,
189 None,
190 0,
191 precision_timestamp,
192 )
193 }
194
195 #[allow(clippy::too_many_arguments)]
205 pub fn write_h264_buf_passthrough<TS>(
206 &mut self,
207 data: &frame_source::H264EncodingVariant,
208 width: u32,
209 height: u32,
210 decode_duration: std::time::Duration,
211 composition_offset: chrono::Duration,
212 sei_timestamp: TS,
213 insert_precision_timestamp: bool,
214 ) -> Result<()>
215 where
216 TS: Into<chrono::DateTime<chrono::Local>>,
217 {
218 let sei_timestamp: chrono::DateTime<chrono::Local> = sei_timestamp.into();
219 let explicit_duration = dur2raw(&decode_duration);
220 let rendering_offset = composition_offset_to_raw(composition_offset);
221 let precision_timestamp = insert_precision_timestamp.then_some(sei_timestamp);
222 self.write_h264_sample(
225 data,
226 width,
227 height,
228 0,
229 chrono::Duration::zero(),
230 Some(explicit_duration),
231 rendering_offset,
232 precision_timestamp,
233 )
234 }
235
236 #[allow(clippy::too_many_arguments)]
237 fn write_h264_sample(
238 &mut self,
239 data: &frame_source::H264EncodingVariant,
240 width: u32,
241 height: u32,
242 mp4_sample_start_time: u64,
243 pts: chrono::Duration,
244 explicit_duration: Option<u64>,
245 rendering_offset: i32,
246 precision_timestamp: Option<chrono::DateTime<chrono::Local>>,
247 ) -> Result<()> {
248 let is_keyframe = parse_h264_is_idr_frame(data)?;
249
250 let sample = match &data {
251 frame_source::H264EncodingVariant::AnnexB(buf) => {
252 let nals = h264_annexb_split(&buf[..]).collect();
253
254 EbspNals {
255 pts,
256 mp4_sample_start_time,
257 is_keyframe,
258 nals,
259 }
260 }
261 frame_source::H264EncodingVariant::Avcc(bufs) => {
262 let nal_iter = iter_avcc_bufs(bufs);
263 let mut nals = Vec::new();
264 for nal_ebsp_bytes in nal_iter {
265 let nal_ebsp_bytes = nal_ebsp_bytes?;
266 nals.push(nal_ebsp_bytes.to_vec());
267 }
268 EbspNals {
269 pts,
270 mp4_sample_start_time,
271 is_keyframe,
272 nals,
273 }
274 }
275 frame_source::H264EncodingVariant::RawEbsp(nals) => EbspNals {
276 pts,
277 mp4_sample_start_time,
278 is_keyframe,
279 nals: nals.clone(),
280 },
281 };
282
283 let inner = self.inner.take();
284
285 let mut state = match inner {
286 Some(WriteState::Configured(mut mybox)) => {
287 let (fd, _cfg, ref mut h264_parser) = *mybox;
288 h264_parser.push_nals(
289 sample,
290 precision_timestamp,
291 explicit_duration,
292 rendering_offset,
293 );
294
295 let sps = if let Some(sps) = self.first_sps.as_ref() {
296 sps
297 } else {
298 h264_parser
299 .sps
300 .as_ref()
301 .ok_or(Error::RequiredH264DataNotFound {})?
302 };
303
304 let pps = if let Some(pps) = self.first_pps.as_ref() {
305 pps
306 } else {
307 h264_parser
308 .pps
309 .as_ref()
310 .ok_or(Error::RequiredH264DataNotFound {})?
311 };
312
313 let mp4_writer = start_mp4_writer(fd, sps, pps, width, height)?;
314 let mp4_segment = MaybeMp4Writer::Mp4Writer(mp4_writer);
315 let my_encoder = MyEncoder::CopyRawH264 {
316 h264_parser: h264_parser.clone(),
317 };
318 Box::new(RecordingState {
319 mp4_segment,
320 my_encoder,
321 inner: None,
322 })
323 }
324 Some(WriteState::Recording(mut state)) => {
325 match &mut state.my_encoder {
326 &mut MyEncoder::CopyRawH264 {
327 ref mut h264_parser,
328 } => {
329 h264_parser.push_nals(
330 sample,
331 precision_timestamp,
332 explicit_duration,
333 rendering_offset,
334 );
335 }
336 _ => {
337 panic!();
338 }
339 }
340 state
341 }
342 None | Some(WriteState::Finished) => {
343 return inconsistent_state_err();
344 }
345 };
346
347 if state.inner.is_some() {
348 return inconsistent_state_err();
349 }
350
351 let sample = match &mut state.my_encoder {
352 &mut MyEncoder::CopyRawH264 {
353 ref mut h264_parser,
354 } => h264_parser.avcc_sample().unwrap(),
355 _ => {
356 panic!();
357 }
358 };
359
360 let write_result = match &mut state.mp4_segment {
361 MaybeMp4Writer::Mp4Writer(mp4_writer) => mp4_writer
362 .write_sample(TRACK_ID, &sample)
363 .map_err(Error::from),
364 _ => {
365 return inconsistent_state_err();
366 }
367 };
368 self.inner = Some(WriteState::Recording(state));
369 write_result?;
370
371 Ok(())
372 }
373
374 pub fn write_dynamic<TS>(&mut self, frame: &DynamicFrame, timestamp: TS) -> Result<()>
375 where
376 TS: Into<chrono::DateTime<chrono::Local>>,
377 {
378 let timestamp: chrono::DateTime<chrono::Local> = timestamp.into();
379 let inner = self.inner.take();
380
381 match inner {
382 Some(WriteState::Configured(mybox)) => {
383 let (fd, cfg, h264_parser) = *mybox;
384
385 let width = frame.width();
386 let height = frame.height();
387
388 #[cfg(feature = "nv-encode")]
389 let mut opt_nv_h264_encoder = None;
390
391 match &cfg.codec {
392 strand_cam_remote_control::Mp4Codec::H264RawStream => {}
393 strand_cam_remote_control::Mp4Codec::H264LessAvc => {}
394 strand_cam_remote_control::Mp4Codec::H264OpenH264(_) => {}
395 #[cfg(not(feature = "nv-encode"))]
396 strand_cam_remote_control::Mp4Codec::H264NvEnc(_) => {
397 return Err(Error::NoNvencCompiledError);
398 }
399 #[cfg(feature = "nv-encode")]
400 strand_cam_remote_control::Mp4Codec::H264NvEnc(opts) => {
401 match &self.nv_enc {
403 Some(nv_enc) => {
404 debug!("Using codec H264 in mp4 file.");
405
406 let cuda_version = nv_enc.cuda_version()?;
408 info!("CUDA version {}", cuda_version);
409
410 let nvenc_version = nv_enc
411 .libnvenc
412 .api_get_max_supported_version()
413 .map_err(nvenc::NvEncError::from)?;
414 info!(
415 "NV_ENC version {}.{}",
416 nvenc_version.major, nvenc_version.minor
417 );
418
419 let num_bufs = 60;
423
424 let dev = nv_enc.libcuda.new_device(opts.cuda_device)?;
425
426 info!("CUDA device: {}, name: {}", opts.cuda_device, dev.name()?);
427 let ctx = dev.into_context()?;
428 let encoder: Rc<nvenc::Encoder<'lib>> =
429 nv_enc.functions.new_encoder(ctx)?;
430
431 let encode = nvenc::NV_ENC_CODEC_H264_GUID;
432 let preset = nvenc::NV_ENC_PRESET_HP_GUID;
434 let format = nvenc::BufferFormat::NV12;
436
437 let param_builder =
438 nvenc::InitParamsBuilder::new(encode, width, height)
439 .preset_guid(preset);
441
442 let param_builder =
443 match cfg.max_framerate.as_numerator_denominator() {
444 Some((num, den)) => param_builder.set_framerate(num, den),
445 None => param_builder,
446 };
447
448 let mut encoder_config =
449 encoder.get_encode_preset_config(encode, preset)?;
450 encoder_config.set_rate_control_mode(RateControlMode::Vbr);
451 if let Some(bitrate) = opts.bitrate.as_ref() {
452 encoder_config.set_average_bit_rate(bitrate * 1000);
453 encoder_config.set_max_bit_rate(bitrate * 1000);
454 }
455
456 let params =
457 param_builder.set_encode_config(encoder_config).build()?;
458
459 match encoder.initialize(¶ms) {
460 Ok(()) => Ok(()),
461 Err(e) => {
462 tracing::error!(
463 "failed initializing nvenc with params: {:?}",
464 params
465 );
466 Err(e)
467 }
468 }?;
469
470 let input_buffers: Vec<InputBuffer<'lib>> = (0..num_bufs)
471 .map(|_| {
472 nvenc::Encoder::alloc_input_buffer(
473 &encoder, width, height, format,
474 )
475 })
476 .collect::<std::result::Result<Vec<_>, _>>()?;
477
478 let output_buffers: Vec<_> = (0..num_bufs)
479 .map(|_| nvenc::Encoder::alloc_output_buffer(&encoder))
480 .collect::<std::result::Result<Vec<_>, _>>()?;
481
482 let vram_buffers: Vec<IOBuffer<_, _>> = input_buffers
483 .into_iter()
484 .zip(output_buffers)
485 .map(|(i, o)| IOBuffer {
486 in_buf: i,
487 out_buf: o,
488 })
489 .collect();
490
491 let vram_queue = nvenc::Queue::new(vram_buffers);
492
493 opt_nv_h264_encoder = Some(NvEncoder {
494 encoder,
495 h264_parser: h264_parser.clone(),
496 vram_queue,
498 first_timestamp: timestamp,
499 });
500 }
501 None => return Err(Error::NvencLibsNotLoaded),
502 }
503 }
504 };
505
506 let my_encoder = match cfg.codec {
507 strand_cam_remote_control::Mp4Codec::H264RawStream => MyEncoder::CopyRawH264 {
508 h264_parser,
510 },
511 strand_cam_remote_control::Mp4Codec::H264LessAvc => {
512 MyEncoder::LessH264(LessEncoderWrapper {
513 encoder: Default::default(),
514 h264_parser,
515 first_timestamp: timestamp,
516 })
517 }
518 #[cfg_attr(not(feature = "openh264"), expect(unused_variables))]
519 strand_cam_remote_control::Mp4Codec::H264OpenH264(opts) => {
520 #[cfg(feature = "openh264")]
521 {
522 let cfg = openh264::encoder::EncoderConfig::new()
523 .debug(opts.debug())
524 .skip_frames(opts.enable_skip_frame())
525 .rate_control_mode(convert_openh264_rc_mode(
526 opts.rate_control_mode(),
527 ))
528 .bitrate(openh264::encoder::BitRate::from_bps(opts.bitrate_bps()));
529
530 MyEncoder::OpenH264(Box::new(OpenH264Encoder {
531 encoder: openh264::encoder::Encoder::with_api_config(
532 openh264::OpenH264API::from_source(),
533 cfg,
534 )?,
535 h264_parser,
536 first_timestamp: timestamp,
537 }))
538 }
539 #[cfg(not(feature = "openh264"))]
540 {
541 panic!("No Open H264 support at compilation time.");
543 }
544 }
545 #[cfg(feature = "nv-encode")]
546 strand_cam_remote_control::Mp4Codec::H264NvEnc(_) => {
547 let enc = opt_nv_h264_encoder.unwrap();
548 MyEncoder::Nvidia(enc)
549 }
550 #[cfg(not(feature = "nv-encode"))]
551 strand_cam_remote_control::Mp4Codec::H264NvEnc(_) => {
552 return Err(Error::NoNvencCompiledError);
553 }
554 };
555
556 let inner = RecordingStateInner {
557 first_timestamp: timestamp,
558 previous_timestamp: timestamp,
559 interval_for_limiting_fps: chrono::Duration::from_std(
560 cfg.max_framerate.interval(),
561 )
562 .unwrap(),
563 trim_width: width,
564 trim_height: height,
565 };
566
567 let mut state = RecordingState {
568 mp4_segment: MaybeMp4Writer::Starting(fd),
569 my_encoder,
570 inner: Some(inner),
571 };
572
573 write_dynamic_frame(&mut state, frame, timestamp)?;
574
575 self.inner = Some(WriteState::Recording(Box::new(state)));
576
577 Ok(())
578 }
579 Some(WriteState::Recording(mut state)) => {
580 let frame = if let Some(state_inner) = &mut state.inner {
581 let interval = timestamp.signed_duration_since(state_inner.previous_timestamp);
582 if interval >= state_inner.interval_for_limiting_fps {
583 let frame = frame
584 .roi(0, 0, state_inner.trim_width, state_inner.trim_height)
585 .unwrap();
586 debug!("Saving frame at {}: interval {}", timestamp, interval);
587
588 state_inner.previous_timestamp = timestamp;
589 Some(frame)
590 } else {
591 debug!(
592 "Not saving frame at {}: interval {} too small",
593 timestamp, interval
594 );
595 None
596 }
597 } else {
598 return inconsistent_state_err();
599 };
600 if let Some(frame) = frame {
601 write_dynamic_frame(&mut state, &frame, timestamp)?;
602 }
603 self.inner = Some(WriteState::Recording(state));
604
605 Ok(())
606 }
607 Some(WriteState::Finished) => {
608 self.inner = Some(WriteState::Finished);
609 Err(Error::FileAlreadyClosed {})
610 }
611
612 None => Err(Error::InconsistentState {}),
613 }
614 }
615
616 pub fn finish(&mut self) -> Result<()> {
622 let inner = self.inner.take();
623 match inner {
624 Some(WriteState::Configured(_)) => {
625 self.inner = Some(WriteState::Finished);
627 Ok(())
628 }
629 #[cfg_attr(not(feature = "nv-encode"), expect(unused_mut))]
630 Some(WriteState::Recording(mut state)) => {
631 match state.my_encoder {
632 MyEncoder::CopyRawH264 { h264_parser: _ } | MyEncoder::LessH264(_) => { }
634 #[cfg(feature = "openh264")]
635 MyEncoder::OpenH264(_encoder) => { }
636 #[cfg(not(feature = "nv-encode"))]
637 MyEncoder::NoNvidia(_) => {
638 return Err(Error::NoNvencCompiledError);
639 }
640 #[cfg(feature = "nv-encode")]
641 MyEncoder::Nvidia(ref mut nv_encoder) => {
642 nv_encoder.encoder.end_stream()?;
643 loop {
645 let sample = match nv_encoder.vram_queue.get_pending() {
646 None => break,
647 Some(iobuf) => {
648 let outbuf = iobuf.out_buf.lock()?;
650 nv_outbuf_to_sample(outbuf)
651 }
652 };
653 if let Some(state_inner) = state.inner.as_ref() {
654 nv_encoder.inner_save_data(
655 &mut state.mp4_segment,
656 sample,
657 state_inner.trim_width,
658 state_inner.trim_height,
659 )?;
660 } else {
661 return Err(Error::InconsistentState {});
662 }
663 }
664 }
665 }
666
667 if let MaybeMp4Writer::Mp4Writer(mut mp4_writer) = state.mp4_segment {
668 mp4_writer.write_end()?;
669 }
670
671 trace!("Finalized video.");
672 self.inner = Some(WriteState::Finished);
673 Ok(())
674 }
675 Some(WriteState::Finished) => {
676 self.inner = Some(WriteState::Finished);
677 Err(Error::FileAlreadyClosed {})
678 }
679 None => Err(Error::InconsistentState {}),
680 }
681 }
682}
683
684#[cfg(feature = "nv-encode")]
685fn nv_outbuf_to_sample(outbuf: dynlink_nvidia_encode::api::LockedOutputBuffer) -> EbspNals {
686 let nals = h264_annexb_split(outbuf.mem()).collect();
687
688 EbspNals {
689 pts: chrono::Duration::from_std(*outbuf.pts()).unwrap(),
690 mp4_sample_start_time: outbuf.output_time_stamp(),
691 is_keyframe: outbuf.is_keyframe(),
692 nals,
693 }
694}
695
696impl<T> Drop for Mp4Writer<'_, T>
697where
698 T: std::io::Write + std::io::Seek,
699{
700 fn drop(&mut self) {
701 match &self.inner {
702 Some(WriteState::Finished) => {}
704 None => {}
706 Some(_) if !std::thread::panicking() => {
708 self.finish().unwrap()
710 }
711 Some(_) => {}
714 }
715 }
716}
717
718fn write_dynamic_frame<T>(
719 state: &mut RecordingState<'_, T>,
720 raw_frame: &DynamicFrame,
721 timestamp: chrono::DateTime<chrono::Local>,
722) -> Result<()>
723where
724 T: std::io::Write + std::io::Seek,
725{
726 match (&mut state.my_encoder, &state.inner) {
727 (MyEncoder::CopyRawH264 { h264_parser: _ }, _) => {
728 return Err(Error::RawH264CopyCannotEncodeFrame {});
729 }
730 (MyEncoder::LessH264(encoder), Some(state_inner)) => {
731 let nals = encoder.encoder.encode_dynamic_to_nal_units(raw_frame)?;
732
733 let is_keyframe = true;
734
735 let pts = timestamp - encoder.first_timestamp;
736 let mp4_sample_start_time = dur2raw(&pts.to_std().unwrap());
737
738 let sample = EbspNals {
739 pts,
740 mp4_sample_start_time,
741 is_keyframe,
742 nals,
743 };
744
745 encoder.inner_save_data(
746 &mut state.mp4_segment,
747 sample,
748 state_inner.trim_width,
749 state_inner.trim_height,
750 )?;
751 }
752 #[cfg(feature = "openh264")]
753 (MyEncoder::OpenH264(encoder), Some(state_inner)) => {
754 let y4m =
757 y4m_writer::encode_y4m_dynamic_frame(raw_frame, y4m::Colorspace::C420paldv, None)?;
758
759 let encoded = encoder.encoder.encode(&YUVData::from(y4m)).unwrap();
760
761 use openh264::encoder::FrameType;
762 let is_keyframe =
763 (encoded.frame_type() == FrameType::IDR) | (encoded.frame_type() == FrameType::I);
764
765 let annex_b_data = encoded.to_vec();
767
768 let nals = h264_annexb_split(&annex_b_data).collect();
769
770 let pts = timestamp - encoder.first_timestamp;
771 let mp4_sample_start_time = dur2raw(&pts.to_std().unwrap());
772
773 let sample = EbspNals {
774 pts,
775 mp4_sample_start_time,
776 is_keyframe,
777 nals,
778 };
779
780 encoder.inner_save_data(
781 &mut state.mp4_segment,
782 sample,
783 state_inner.trim_width,
784 state_inner.trim_height,
785 )?;
786 }
787 #[cfg(not(feature = "nv-encode"))]
788 (MyEncoder::NoNvidia(_), Some(_)) => {
789 return Err(Error::NoNvencCompiledError);
790 }
791 #[cfg(feature = "nv-encode")]
792 (MyEncoder::Nvidia(nv_encoder), Some(state_inner)) => {
793 let vram_buf: &mut IOBuffer<_, _> = match nv_encoder.vram_queue.get_available() {
794 Some(iobuf) => iobuf,
795 None => {
796 let sample = {
797 let iobuf = nv_encoder.vram_queue.get_pending().expect("get pending");
798 let outbuf = iobuf.out_buf.lock()?;
800 nv_outbuf_to_sample(outbuf)
801 };
802 nv_encoder.inner_save_data(
803 &mut state.mp4_segment,
804 sample,
805 state_inner.trim_width,
806 state_inner.trim_height,
807 )?;
808 nv_encoder
809 .vram_queue
810 .get_available()
811 .expect("get available")
812 }
813 };
814
815 let pitch = {
818 let mut inbuf = vram_buf.in_buf.lock()?;
820 let dest_stride = inbuf.pitch();
821
822 let mut dest = ImageRefMut::<pixel_format::NV12>::new(
823 raw_frame.width(),
824 raw_frame.height(),
825 dest_stride,
826 inbuf.mem_mut(),
827 )
828 .unwrap();
829
830 raw_frame.into_pixel_format_dest(&mut dest)?;
831 dest_stride
833 };
834
835 let elapsed = timestamp.signed_duration_since(state_inner.first_timestamp);
836 let pts = elapsed.to_std().unwrap();
837
838 nv_encoder
839 .encoder
840 .encode_picture(&vram_buf.in_buf, &vram_buf.out_buf, pitch, pts)?;
841 }
842 (_encoder, None) => {
843 return inconsistent_state_err();
844 }
845 }
846 Ok(())
847}
848
849enum WriteState<'lib, T>
850where
851 T: std::io::Write + std::io::Seek,
852{
853 Configured(Box<(T, Mp4RecordingConfig, H264Parser)>),
854 Recording(Box<RecordingState<'lib, T>>),
855 Finished,
856}
857
858struct RecordingState<'lib, T>
859where
860 T: std::io::Write + std::io::Seek,
861{
862 mp4_segment: MaybeMp4Writer<T>,
863 my_encoder: MyEncoder<'lib>,
864 inner: Option<RecordingStateInner>,
865}
866
867struct RecordingStateInner {
868 #[cfg_attr(not(feature = "nv-encode"), expect(unused))]
869 first_timestamp: chrono::DateTime<chrono::Local>,
870 previous_timestamp: chrono::DateTime<chrono::Local>,
871 interval_for_limiting_fps: chrono::Duration,
873 trim_width: u32,
874 trim_height: u32,
875}
876
877struct LessEncoderWrapper {
878 encoder: less_avc_wrapper::WrappedLessEncoder,
879 h264_parser: H264Parser,
880 first_timestamp: chrono::DateTime<chrono::Local>,
881}
882
883impl LessEncoderWrapper {
884 fn compute_local_timestamp(&self, sample: &EbspNals) -> chrono::DateTime<chrono::Local> {
885 self.first_timestamp + sample.pts
886 }
887 fn inner_save_data<T>(
888 &mut self,
889 mp4_segment: &mut MaybeMp4Writer<T>,
890 sample: EbspNals,
891 trim_width: u32,
892 trim_height: u32,
893 ) -> Result<()>
894 where
895 T: std::io::Write + std::io::Seek,
896 {
897 let local_timestamp = self.compute_local_timestamp(&sample);
898 self.h264_parser
899 .push_nals(sample, Some(local_timestamp), None, 0);
900 let sps = self.h264_parser.sps().unwrap();
901 let pps = self.h264_parser.pps().unwrap();
902
903 let mut mp4_writer = match std::mem::replace(mp4_segment, MaybeMp4Writer::Nothing) {
904 MaybeMp4Writer::Mp4Writer(mp4_writer) => mp4_writer,
905 MaybeMp4Writer::Starting(fd) => {
906 start_mp4_writer(fd, sps, pps, trim_width, trim_height)?
907 }
908 MaybeMp4Writer::Nothing => {
909 panic!("inconsistent state");
910 }
911 };
912
913 let avcc_sample = self.h264_parser.avcc_sample().unwrap();
914 mp4_writer.write_sample(TRACK_ID, &avcc_sample)?;
915
916 *mp4_segment = MaybeMp4Writer::Mp4Writer(mp4_writer);
917
918 Ok(())
919 }
920}
921
922#[cfg(feature = "nv-encode")]
923struct NvEncoder<'lib> {
924 encoder: Rc<nvenc::Encoder<'lib>>,
925 h264_parser: H264Parser,
926 vram_queue: nvenc::Queue<IOBuffer<InputBuffer<'lib>, OutputBuffer<'lib>>>,
927 first_timestamp: chrono::DateTime<chrono::Local>,
928}
929
930#[cfg(feature = "nv-encode")]
931impl NvEncoder<'_> {
932 fn compute_local_timestamp(&self, sample: &EbspNals) -> chrono::DateTime<chrono::Local> {
933 self.first_timestamp + sample.pts
934 }
935 fn inner_save_data<T>(
936 &mut self,
937 mp4_segment: &mut MaybeMp4Writer<T>,
938 sample: EbspNals,
939 trim_width: u32,
940 trim_height: u32,
941 ) -> Result<()>
942 where
943 T: std::io::Write + std::io::Seek,
944 {
945 let local_timestamp = self.compute_local_timestamp(&sample);
946 self.h264_parser
947 .push_nals(sample, Some(local_timestamp), None, 0);
948 let mut mp4_writer = match std::mem::replace(mp4_segment, MaybeMp4Writer::Nothing) {
949 MaybeMp4Writer::Mp4Writer(mp4_writer) => mp4_writer,
950 MaybeMp4Writer::Starting(fd) => {
951 let sps = self.h264_parser.sps().unwrap();
952 let pps = self.h264_parser.pps().unwrap();
953 start_mp4_writer(fd, sps, pps, trim_width, trim_height)?
954 }
955 MaybeMp4Writer::Nothing => {
956 panic!("inconsistent state");
957 }
958 };
959
960 let avcc_sample = self.h264_parser.avcc_sample().unwrap();
961 mp4_writer.write_sample(TRACK_ID, &avcc_sample)?;
962
963 *mp4_segment = MaybeMp4Writer::Mp4Writer(mp4_writer);
964
965 Ok(())
966 }
967}
968
969fn start_mp4_writer<T>(
970 fd: T,
971 sps: &[u8],
972 pps: &[u8],
973 trim_width: u32,
974 trim_height: u32,
975) -> Result<mp4::Mp4Writer<T>>
976where
977 T: std::io::Write + std::io::Seek,
978{
979 let mp4_config = mp4::Mp4Config {
980 major_brand: str::parse("isom").unwrap(),
981 minor_version: 512,
982 compatible_brands: vec![str::parse("isom").unwrap()],
983 timescale: MOVIE_TIMESCALE,
986 };
987
988 let mut mp4_writer = mp4::Mp4Writer::write_start(fd, &mp4_config)?;
989
990 let media_conf = mp4::MediaConfig::AvcConfig(mp4::AvcConfig {
991 width: trim_width.try_into().unwrap(),
992 height: trim_height.try_into().unwrap(),
993 seq_param_set: sps.to_vec(),
994 pic_param_set: pps.to_vec(),
995 });
996
997 let track_conf = mp4::TrackConfig {
998 track_type: mp4::TrackType::Video,
999 timescale: MOVIE_TIMESCALE,
1000 language: String::from("eng"),
1001 media_conf,
1002 };
1003
1004 mp4_writer.add_track(&track_conf)?;
1005 Ok(mp4_writer)
1006}
1007
1008#[cfg(feature = "openh264")]
1009struct OpenH264Encoder {
1010 encoder: openh264::encoder::Encoder,
1011 h264_parser: H264Parser,
1012 first_timestamp: chrono::DateTime<chrono::Local>,
1013}
1014
1015#[cfg(feature = "openh264")]
1016impl OpenH264Encoder {
1017 fn compute_local_timestamp(&self, sample: &EbspNals) -> chrono::DateTime<chrono::Local> {
1018 self.first_timestamp + sample.pts
1019 }
1020 fn inner_save_data<T>(
1021 &mut self,
1022 mp4_segment: &mut MaybeMp4Writer<T>,
1023 sample: EbspNals,
1024 trim_width: u32,
1025 trim_height: u32,
1026 ) -> Result<()>
1027 where
1028 T: std::io::Write + std::io::Seek,
1029 {
1030 let local_timestamp = self.compute_local_timestamp(&sample);
1031 self.h264_parser
1032 .push_nals(sample, Some(local_timestamp), None, 0);
1033 let sps = self.h264_parser.sps().unwrap();
1034 let pps = self.h264_parser.pps().unwrap();
1035
1036 let mut mp4_writer = match std::mem::replace(mp4_segment, MaybeMp4Writer::Nothing) {
1037 MaybeMp4Writer::Mp4Writer(mp4_writer) => mp4_writer,
1038 MaybeMp4Writer::Starting(fd) => {
1039 start_mp4_writer(fd, sps, pps, trim_width, trim_height)?
1040 }
1041 MaybeMp4Writer::Nothing => {
1042 panic!("inconsistent state");
1043 }
1044 };
1045
1046 let avcc_sample = self.h264_parser.avcc_sample().unwrap();
1047 mp4_writer.write_sample(TRACK_ID, &avcc_sample)?;
1048
1049 *mp4_segment = MaybeMp4Writer::Mp4Writer(mp4_writer);
1050
1051 Ok(())
1052 }
1053}
1054
1055pub struct IOBuffer<I, O> {
1056 pub in_buf: I,
1057 pub out_buf: O,
1058}
1059
1060enum MaybeMp4Writer<T>
1061where
1062 T: std::io::Write + std::io::Seek,
1063{
1064 Nothing,
1065 Starting(T),
1066 Mp4Writer(mp4::Mp4Writer<T>),
1067}
1068
1069#[derive(Clone)]
1070struct H264Parser {
1071 sps: Option<Vec<u8>>,
1072 pps: Option<Vec<u8>>,
1073 previous_stamp: Option<u64>,
1074 last_sample: Option<ParsedH264Frame>,
1076 first_frame_done: bool,
1077 h264_metadata: Option<H264Metadata>,
1078}
1079
1080impl H264Parser {
1081 fn new(h264_metadata: Option<H264Metadata>) -> Self {
1083 Self {
1084 sps: None,
1085 pps: None,
1086 previous_stamp: None,
1087 last_sample: None,
1088 first_frame_done: false,
1089 h264_metadata,
1090 }
1091 }
1092 fn sps(&self) -> Option<&[u8]> {
1093 self.sps.as_deref()
1094 }
1095 fn pps(&self) -> Option<&[u8]> {
1096 self.pps.as_deref()
1097 }
1098
1099 fn push_nals(
1100 &mut self,
1101 nals: EbspNals,
1102 mut precision_timestamp: Option<chrono::DateTime<chrono::Local>>,
1103 explicit_duration: Option<u64>,
1104 rendering_offset: i32,
1105 ) {
1106 let mut all_avcc_nal_units: Vec<u8> = Vec::with_capacity(nals.annex_b_size() + 32);
1113
1114 if !self.first_frame_done {
1115 use less_avc::{nal_unit::*, sei::UserDataUnregistered};
1116
1117 if let Some(h264_metadata) = &self.h264_metadata {
1118 let h264_metadata_updated = if let Some(ts) = precision_timestamp {
1121 let creation_time = ts.into();
1122 H264Metadata {
1123 creation_time,
1124 ..h264_metadata.clone()
1125 }
1126 } else {
1127 h264_metadata.clone()
1128 };
1129
1130 let msg = serde_json::to_vec(&h264_metadata_updated).unwrap();
1131
1132 let payload = UserDataUnregistered::new(H264_METADATA_UUID, msg);
1133
1134 use less_avc::sei::SupplementalEnhancementInformation;
1135 let rbsp_data =
1136 SupplementalEnhancementInformation::UserDataUnregistered(payload).to_rbsp();
1137 let annex_b_data = NalUnit::new(
1138 less_avc::nal_unit::NalRefIdc::Zero,
1139 less_avc::nal_unit::NalUnitType::SupplementalEnhancementInformation,
1140 rbsp_data,
1141 )
1142 .to_annex_b_data();
1143
1144 const ANNEX_B_START: &[u8] = &[0x00, 0x00, 0x00, 0x01];
1145 debug_assert_eq!(&annex_b_data[..4], ANNEX_B_START);
1146
1147 all_avcc_nal_units.extend(buf_to_avcc(&annex_b_data[4..]));
1150 }
1151
1152 self.first_frame_done = true;
1153 }
1154
1155 for ebsp_msg in nals.nals.iter() {
1157 let mut is_this_sps_or_pps = false;
1158 if !ebsp_msg.is_empty() {
1159 let code = ebsp_msg[0];
1160 match code {
1161 0x67 => {
1162 self.sps = Some(ebsp_msg[..].to_vec());
1163 is_this_sps_or_pps = true;
1164 }
1165 0x68 => {
1166 self.pps = Some(ebsp_msg[..].to_vec());
1167 is_this_sps_or_pps = true;
1168 }
1169 _ => {}
1170 }
1171 if !is_this_sps_or_pps {
1172 if let Some(ts) = precision_timestamp.take() {
1176 let mut rbsp_msg = [0u8; 32];
1177 rbsp_msg[0] = 0x06; rbsp_msg[1] = 0x05; rbsp_msg[2] = 28; timestamp_to_sei_payload(ts.into(), &mut rbsp_msg[3..31]);
1181 rbsp_msg[31] = 0x80; let ebsp_msg = rbsp_msg;
1190 all_avcc_nal_units.extend(buf_to_avcc(&ebsp_msg[..]));
1191 }
1192 }
1193 all_avcc_nal_units.extend(buf_to_avcc(ebsp_msg));
1194 }
1195 }
1196
1197 if self
1198 .last_sample
1199 .replace(ParsedH264Frame {
1200 mp4_sample_start_time: nals.mp4_sample_start_time,
1201 is_keyframe: nals.is_keyframe,
1202 avcc_buf: all_avcc_nal_units,
1203 explicit_duration,
1204 rendering_offset,
1205 })
1206 .is_some()
1207 {
1208 eprintln!("unused NAL unit");
1209 };
1210 }
1211
1212 fn avcc_sample(&mut self) -> Option<mp4::Mp4Sample> {
1213 let explicit_duration = self.last_sample.as_ref().and_then(|f| f.explicit_duration);
1214 let mut sample = self.last_sample.take().map(parsed_to_mp4_sample);
1215 if let Some(ref mut s) = sample {
1216 match explicit_duration {
1217 Some(dur) => {
1221 s.duration = dur.try_into().unwrap();
1222 }
1223 None => {
1227 if let Some(prev) = self.previous_stamp {
1228 let dur = s.start_time - prev;
1232 s.duration = dur.try_into().unwrap();
1233 }
1234 self.previous_stamp = Some(s.start_time);
1235 }
1236 }
1237 }
1238 sample
1242 }
1243}
1244
1245fn parsed_to_mp4_sample(orig: ParsedH264Frame) -> mp4::Mp4Sample {
1246 let bytes = orig.avcc_buf.into();
1247
1248 mp4::Mp4Sample {
1249 start_time: orig.mp4_sample_start_time,
1250 duration: 0,
1251 rendering_offset: orig.rendering_offset,
1252 is_sync: orig.is_keyframe,
1253 bytes,
1254 }
1255}
1256
1257struct EbspNals {
1262 pts: chrono::Duration,
1263 mp4_sample_start_time: u64,
1265 is_keyframe: bool,
1266 nals: Vec<Vec<u8>>,
1267}
1268
1269impl EbspNals {
1270 fn annex_b_size(&self) -> usize {
1271 let raw_sz: usize = self.nals.iter().map(|x| x.len()).sum();
1272 raw_sz + 4 * self.nals.len()
1273 }
1274}
1275
1276#[derive(Clone)]
1277struct ParsedH264Frame {
1278 mp4_sample_start_time: u64,
1280 is_keyframe: bool,
1281 avcc_buf: Vec<u8>,
1282 explicit_duration: Option<u64>,
1286 rendering_offset: i32,
1289}
1290
1291fn buf_to_avcc(nal: &[u8]) -> Vec<u8> {
1292 let sz: u32 = nal.len().try_into().unwrap();
1293 let mut result = vec![0u8; nal.len() + 4];
1294 result[0..4].copy_from_slice(&sz.to_be_bytes());
1295 result[4..].copy_from_slice(nal);
1296 result
1297}
1298
1299#[cfg(feature = "openh264")]
1300struct YUVData {
1301 width: usize,
1302 height: usize,
1303 data: Vec<u8>,
1304 y_stride: usize,
1305 u_stride: usize,
1306 v_stride: usize,
1307}
1308
1309#[cfg(feature = "openh264")]
1346impl From<y4m_writer::Y4MFrame> for YUVData {
1347 fn from(orig: y4m_writer::Y4MFrame) -> YUVData {
1348 let width = orig.width.try_into().unwrap();
1349 let height = orig.height.try_into().unwrap();
1350 let y_stride = orig.y_stride.try_into().unwrap();
1351 let u_stride = orig.u_stride();
1352 let v_stride = orig.v_stride();
1353 Self {
1354 width,
1355 height,
1356 data: orig.into_data(),
1357 y_stride,
1358 u_stride,
1359 v_stride,
1360 }
1361 }
1362}
1363
1364#[cfg(feature = "openh264")]
1365impl YUVData {
1366 #[inline]
1367 fn u_start(&self) -> usize {
1368 self.height * self.y_stride
1369 }
1370 #[inline]
1371 fn v_start(&self) -> usize {
1372 self.u_start() + self.height / 2 * self.u_stride
1373 }
1374 #[inline]
1375 fn v_end(&self) -> usize {
1376 self.v_start() + self.height / 2 * self.u_stride
1377 }
1378}
1379
1380#[cfg(feature = "openh264")]
1381impl openh264::formats::YUVSource for YUVData {
1382 fn dimensions(&self) -> (usize, usize) {
1383 (self.width, self.height)
1384 }
1385 fn y(&self) -> &[u8] {
1386 &self.data[0..self.u_start()]
1387 }
1388 fn u(&self) -> &[u8] {
1389 &self.data[self.u_start()..self.v_start()]
1390 }
1391 fn v(&self) -> &[u8] {
1392 &self.data[self.v_start()..self.v_end()]
1393 }
1394 fn strides(&self) -> (usize, usize, usize) {
1395 (self.y_stride, self.u_stride, self.v_stride)
1396 }
1397}
1398
1399fn dur2raw(dur: &std::time::Duration) -> u64 {
1400 (dur.as_secs_f64() * MOVIE_TIMESCALE as f64).round() as u64
1401}
1402
1403fn composition_offset_to_raw(offset: chrono::Duration) -> i32 {
1406 let nanos = offset.num_nanoseconds().unwrap_or(0) as i128;
1407 ((nanos * MOVIE_TIMESCALE as i128) / 1_000_000_000i128) as i32
1408}
1409
1410fn timestamp_to_sei_payload(timestamp: chrono::DateTime<chrono::Utc>, payload: &mut [u8]) {
1411 assert_eq!(payload.len(), 28);
1412 let precision_time_stamp = timestamp.timestamp_micros();
1413
1414 let precision_time_stamp_bytes: [u8; 8] = precision_time_stamp.to_be_bytes();
1415
1416 payload[0..16].copy_from_slice(b"MISPmicrosectime"); payload[16] = 0x1F; payload[17..19].copy_from_slice(&precision_time_stamp_bytes[0..2]);
1424 payload[19] = 0xff;
1425 payload[20..22].copy_from_slice(&precision_time_stamp_bytes[2..4]);
1426 payload[22] = 0xff;
1427 payload[23..25].copy_from_slice(&precision_time_stamp_bytes[4..6]);
1428 payload[25] = 0xff;
1429 payload[26..28].copy_from_slice(&precision_time_stamp_bytes[6..8]);
1430}
1431
1432#[cfg(feature = "openh264")]
1433fn convert_openh264_rc_mode(
1434 orig: strand_cam_remote_control::OpenH264RateControlMode,
1435) -> openh264::encoder::RateControlMode {
1436 use openh264::encoder::RateControlMode::*;
1437 use strand_cam_remote_control::OpenH264RateControlMode as mode;
1438 match orig {
1439 mode::Quality => Quality,
1440 mode::Bitrate => Bitrate,
1441 mode::Bufferbased => Bufferbased,
1442 mode::Timestamp => Timestamp,
1443 mode::Off => Off,
1444 }
1445}
1446
1447struct NalAvccBufIter<'a> {
1448 cur_buf: &'a [u8],
1449}
1450
1451impl<'a> Iterator for NalAvccBufIter<'a> {
1452 type Item = Result<&'a [u8]>;
1453 fn next(&mut self) -> Option<Self::Item> {
1454 if self.cur_buf.is_empty() {
1455 return None;
1456 }
1457 if self.cur_buf.len() < 4 {
1458 return Some(Err(Error::BadInputData {}));
1459 }
1460 let bytes: [u8; 4] = self.cur_buf[0..4].try_into().unwrap();
1461 let nal_unit_payload_len = usize::try_from(u32::from_be_bytes(bytes)).unwrap();
1462 let nal_ebsp_bytes = &self.cur_buf[4..4 + nal_unit_payload_len];
1463 if nal_ebsp_bytes.len() != nal_unit_payload_len {
1464 return Some(Err(Error::BadInputData {}));
1465 }
1466 self.cur_buf = &self.cur_buf[4 + nal_unit_payload_len..];
1467 Some(Ok(nal_ebsp_bytes))
1468 }
1469}
1470
1471fn iter_avcc_bufs(buf: &[u8]) -> NalAvccBufIter<'_> {
1472 NalAvccBufIter { cur_buf: buf }
1473}
1474
1475fn parse_h264_is_idr_frame(data: &frame_source::H264EncodingVariant) -> Result<bool> {
1477 use h264_reader::nal::{Nal, RefNal, UnitType};
1478 use h264_reader::push::NalInterest;
1479 let mut calls = Vec::new();
1480 match data {
1481 frame_source::H264EncodingVariant::Avcc(buf) => {
1482 let nal_iter = iter_avcc_bufs(buf);
1483 for nal_ebsp_bytes in nal_iter {
1484 let nal_ebsp_bytes = nal_ebsp_bytes?;
1485 let nal = RefNal::new(nal_ebsp_bytes, &[], true);
1486 let nal_unit_type = nal.header().unwrap().nal_unit_type();
1487 calls.push(nal_unit_type);
1488 }
1489 }
1490 frame_source::H264EncodingVariant::AnnexB(buf) => {
1491 use h264_reader::annexb::AnnexBReader;
1492 let mut reader = AnnexBReader::accumulate(|nal: RefNal<'_>| {
1493 let nal_unit_type = nal.header().unwrap().nal_unit_type();
1494 calls.push(nal_unit_type);
1495 match nal_unit_type {
1496 UnitType::SeqParameterSet => NalInterest::Buffer,
1497 _ => NalInterest::Ignore,
1498 }
1499 });
1500 reader.push(&buf[..]);
1501 }
1502 frame_source::H264EncodingVariant::RawEbsp(nals) => {
1503 for nal_ebsp_bytes in nals.iter() {
1504 let nal = RefNal::new(nal_ebsp_bytes, &[], true);
1505 let nal_unit_type = nal.header().unwrap().nal_unit_type();
1506 calls.push(nal_unit_type);
1507 }
1508 }
1509 }
1510 let mut is_keyframe = None;
1511 for nal_unit_type in calls.into_iter() {
1512 match nal_unit_type {
1513 UnitType::SliceLayerWithoutPartitioningIdr => {
1514 if is_keyframe.is_some() {
1515 return Err(Error::BadInputData {});
1517 };
1518 is_keyframe = Some(true);
1519 }
1520 UnitType::SliceLayerWithoutPartitioningNonIdr => {
1521 if is_keyframe.is_some() {
1522 return Err(Error::BadInputData {});
1524 };
1525 is_keyframe = Some(false);
1526 }
1527 _ => {}
1528 }
1529 }
1530 is_keyframe.ok_or_else(|| Error::BadInputData {})
1531}
1532
1533fn inconsistent_state_err<T>() -> Result<T> {
1534 Err(Error::InconsistentState {})
1535}
1536
1537#[cfg(test)]
1538mod tests {
1539 use std::cell::{Cell, RefCell};
1540 use std::io::{Cursor, Seek, SeekFrom, Write};
1541 use std::rc::Rc;
1542
1543 use chrono::TimeDelta;
1544 use frame_source::H264EncodingVariant;
1545 use strand_cam_remote_control::{Mp4Codec, Mp4RecordingConfig};
1546
1547 use super::Mp4Writer;
1548
1549 #[derive(Clone, Default)]
1550 struct FailOnceWriter {
1551 cursor: Rc<RefCell<Cursor<Vec<u8>>>>,
1552 fail_next_write: Rc<Cell<bool>>,
1553 }
1554
1555 impl Write for FailOnceWriter {
1556 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1557 if self.fail_next_write.replace(false) {
1558 return Err(std::io::Error::other("injected write failure"));
1559 }
1560 self.cursor.borrow_mut().write(buf)
1561 }
1562
1563 fn flush(&mut self) -> std::io::Result<()> {
1564 self.cursor.borrow_mut().flush()
1565 }
1566 }
1567
1568 impl Seek for FailOnceWriter {
1569 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
1570 self.cursor.borrow_mut().seek(pos)
1571 }
1572 }
1573
1574 #[test]
1575 fn finish_recovers_after_h264_sample_write_error() {
1576 let output = FailOnceWriter::default();
1577 let fail_next_write = output.fail_next_write.clone();
1578 let output_cursor = output.cursor.clone();
1579 let config = Mp4RecordingConfig {
1580 codec: Mp4Codec::H264RawStream,
1581 max_framerate: Default::default(),
1582 h264_metadata: None,
1583 };
1584 let mut writer = Mp4Writer::new(
1585 output,
1586 config,
1587 #[cfg(feature = "nv-encode")]
1588 None,
1589 )
1590 .unwrap();
1591 writer.set_first_sps_pps(
1592 Some(vec![0x67, 0x42, 0xc0, 0x0a]),
1593 Some(vec![0x68, 0xce, 0x0f, 0xc8]),
1594 );
1595
1596 let start = chrono::DateTime::<chrono::Local>::from(std::time::SystemTime::UNIX_EPOCH);
1597 writer
1598 .write_h264_buf(
1599 &H264EncodingVariant::RawEbsp(vec![vec![0x65, 0x88]]),
1600 16,
1601 16,
1602 start,
1603 start,
1604 false,
1605 )
1606 .unwrap();
1607
1608 fail_next_write.set(true);
1609 let write_error = writer
1610 .write_h264_buf(
1611 &H264EncodingVariant::RawEbsp(vec![vec![0x41, 0x9a]]),
1612 16,
1613 16,
1614 start + TimeDelta::seconds(1),
1615 start,
1616 false,
1617 )
1618 .unwrap_err();
1619 assert!(write_error.to_string().contains("injected write failure"));
1620
1621 writer.finish().unwrap();
1622 drop(writer);
1623
1624 let output = output_cursor.borrow().get_ref().clone();
1625 let output_len = output.len() as u64;
1626 let reader = mp4::Mp4Reader::read_header(Cursor::new(output), output_len).unwrap();
1627 assert_eq!(reader.sample_count(super::TRACK_ID).unwrap(), 2);
1628 }
1629}