Skip to main content

strand_cam_remote_control/
lib.rs

1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Types for [Strand Camera](https://strawlab.org/strand-cam) remote control and configuration
5
6extern crate serde;
7extern crate strand_cam_bui_types;
8extern crate strand_cam_enum_iter;
9extern crate strand_cam_types;
10
11use serde::{Deserialize, Serialize};
12use strand_cam_bui_types::ClockModel;
13use strand_cam_enum_iter::EnumIter;
14
15/// Frame rate options for video recording.
16#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
17pub enum RecordingFrameRate {
18    /// 1 frame per second
19    Fps1,
20    /// 2 frames per second
21    Fps2,
22    /// 5 frames per second
23    Fps5,
24    /// 10 frames per second
25    Fps10,
26    /// 20 frames per second
27    Fps20,
28    /// 25 frames per second
29    Fps25,
30    /// 30 frames per second
31    Fps30,
32    /// 40 frames per second
33    Fps40,
34    /// 50 frames per second
35    Fps50,
36    /// 60 frames per second
37    Fps60,
38    /// 100 frames per second
39    Fps100,
40    /// No frame rate limit
41    #[default]
42    Unlimited,
43}
44
45impl RecordingFrameRate {
46    /// Returns the duration between frames for this frame rate.
47    pub fn interval(&self) -> std::time::Duration {
48        use RecordingFrameRate::*;
49        use std::time::Duration;
50        match self {
51            Fps1 => Duration::from_millis(1000),
52            Fps2 => Duration::from_millis(500),
53            Fps5 => Duration::from_millis(200),
54            Fps10 => Duration::from_millis(100),
55            Fps20 => Duration::from_millis(50),
56            Fps25 => Duration::from_millis(40),
57            Fps30 => Duration::from_nanos(33333333),
58            Fps40 => Duration::from_millis(25),
59            Fps50 => Duration::from_millis(20),
60            Fps60 => Duration::from_nanos(16666667),
61            Fps100 => Duration::from_millis(10),
62            Unlimited => Duration::from_millis(0),
63        }
64    }
65
66    /// Returns frame rate as numerator/denominator, or None for unlimited.
67    pub fn as_numerator_denominator(&self) -> Option<(u32, u32)> {
68        use RecordingFrameRate::*;
69        Some(match self {
70            Fps1 => (1, 1),
71            Fps2 => (2, 1),
72            Fps5 => (5, 1),
73            Fps10 => (10, 1),
74            Fps20 => (20, 1),
75            Fps25 => (25, 1),
76            Fps30 => (30, 1),
77            Fps40 => (40, 1),
78            Fps50 => (50, 1),
79            Fps60 => (60, 1),
80            Fps100 => (100, 1),
81            Unlimited => {
82                return None;
83            }
84        })
85    }
86}
87
88impl std::fmt::Display for RecordingFrameRate {
89    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
90        use RecordingFrameRate::*;
91        let s = match self {
92            Fps1 => "1 fps",
93            Fps2 => "2 fps",
94            Fps5 => "5 fps",
95            Fps10 => "10 fps",
96            Fps20 => "20 fps",
97            Fps25 => "25 fps",
98            Fps30 => "30 fps",
99            Fps40 => "40 fps",
100            Fps50 => "50 fps",
101            Fps60 => "60 fps",
102            Fps100 => "100 fps",
103            Unlimited => "unlimited",
104        };
105        write!(fmt, "{s}")
106    }
107}
108
109impl EnumIter for RecordingFrameRate {
110    fn variants() -> Vec<Self> {
111        use RecordingFrameRate::*;
112        vec![
113            Fps1, Fps2, Fps5, Fps10, Fps20, Fps25, Fps30, Fps40, Fps50, Fps60, Fps100, Unlimited,
114        ]
115    }
116}
117
118/// H.264 codec options for MP4 encoding.
119#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
120pub enum Mp4Codec {
121    /// Encode data with Nvidia's NVENC.
122    H264NvEnc(NvidiaH264Options),
123    /// Encode data with OpenH264.
124    H264OpenH264(OpenH264Options),
125    /// Encode data with LessAVC.
126    H264LessAvc,
127    /// Data is already encoded as a raw H264 stream.
128    H264RawStream,
129}
130
131/// Options for OpenH264 encoder.
132#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
133pub struct OpenH264Options {
134    /// Enable OpenH264 debug messages
135    pub debug: bool,
136    /// Encoding preset configuration
137    pub preset: OpenH264Preset,
138}
139
140impl OpenH264Options {
141    /// Returns debug flag.
142    pub fn debug(&self) -> bool {
143        self.debug
144    }
145    /// Returns whether frame skipping should be enabled.
146    pub fn enable_skip_frame(&self) -> bool {
147        match self.preset {
148            OpenH264Preset::AllFrames => false,
149            OpenH264Preset::SkipFramesBitrate(_) => true,
150        }
151    }
152    /// Returns the rate control mode to use.
153    pub fn rate_control_mode(&self) -> OpenH264RateControlMode {
154        match self.preset {
155            OpenH264Preset::AllFrames => OpenH264RateControlMode::Off,
156            OpenH264Preset::SkipFramesBitrate(_) => OpenH264RateControlMode::Bitrate,
157        }
158    }
159    /// Returns the target bitrate in bits per second.
160    pub fn bitrate_bps(&self) -> u32 {
161        match self.preset {
162            OpenH264Preset::AllFrames => 0,
163            OpenH264Preset::SkipFramesBitrate(bitrate) => bitrate,
164        }
165    }
166}
167
168/// Encoding presets for OpenH264.
169#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
170pub enum OpenH264Preset {
171    /// Encode all frames without skipping
172    AllFrames,
173    /// Skip frames to achieve target bitrate
174    SkipFramesBitrate(u32),
175}
176
177impl Default for OpenH264Preset {
178    fn default() -> Self {
179        Self::SkipFramesBitrate(5000)
180    }
181}
182
183/// Rate control modes for OpenH264 encoder.
184#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Copy)]
185pub enum OpenH264RateControlMode {
186    /// Quality mode.
187    Quality,
188    /// Bitrate mode.
189    Bitrate,
190    /// No bitrate control, only using buffer status, adjust the video quality.
191    Bufferbased,
192    /// Rate control based timestamp.
193    Timestamp,
194    /// Rate control off mode.
195    Off,
196}
197
198/// Options for NVIDIA H.264 encoder.
199#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
200pub struct NvidiaH264Options {
201    /// The bitrate (used in association with the framerate).
202    pub bitrate: Option<u32>,
203    /// The device number of the CUDA device to use.
204    pub cuda_device: i32,
205}
206
207/// Configuration for MP4 recording
208#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
209pub struct Mp4RecordingConfig {
210    pub codec: Mp4Codec,
211    /// Limits the recording to a maximum frame rate.
212    pub max_framerate: RecordingFrameRate,
213    pub h264_metadata: Option<H264Metadata>,
214}
215
216/// Configuration for an ffmpeg-based recording
217#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
218pub struct FfmpegRecordingConfig {
219    pub codec_args: FfmpegCodecArgs,
220    /// Limits the recording to a maximum frame rate.
221    pub max_framerate: RecordingFrameRate,
222    pub h264_metadata: Option<H264Metadata>,
223}
224
225/// Specify recording method and configuration
226#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
227pub enum RecordingConfig {
228    /// Record using MP4 writer
229    Mp4(Mp4RecordingConfig),
230    /// Record via y4m pipe to ffmpeg
231    Ffmpeg(FfmpegRecordingConfig),
232}
233
234impl Default for RecordingConfig {
235    fn default() -> Self {
236        Self::Ffmpeg(Default::default())
237    }
238}
239
240impl RecordingConfig {
241    /// Returns the maximum frame rate for this recording configuration.
242    pub fn max_framerate(&self) -> &RecordingFrameRate {
243        use RecordingConfig::*;
244        match self {
245            Mp4(c) => &c.max_framerate,
246            Ffmpeg(c) => &c.max_framerate,
247        }
248    }
249}
250
251/// Universal identifier for our H264 metadata.
252///
253/// Generated with `uuid -v3 ns:URL https://strawlab.org/h264-metadata/`
254pub const H264_METADATA_UUID: [u8; 16] = [
255    // 0ba99cc7-f607-3851-b35e-8c7d8c04da0a
256    0x0B, 0xA9, 0x9C, 0xC7, 0xF6, 0x07, 0x08, 0x51, 0x33, 0x5E, 0x8C, 0x7D, 0x8C, 0x04, 0xDA, 0x0A,
257];
258/// Version for our H264 metadata.
259pub const H264_METADATA_VERSION: &str = "https://strawlab.org/h264-metadata/v1/";
260
261/// Metadata to embed in H.264 streams.
262#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
263pub struct H264Metadata {
264    /// Version of this structure
265    ///
266    /// Should be equal to H264_METADATA_VERSION.
267    /// This field must always be serialized first.
268    pub version: String,
269
270    /// Application name that created the stream
271    pub writing_app: String,
272
273    /// Stream creation timestamp
274    pub creation_time: chrono::DateTime<chrono::FixedOffset>,
275
276    /// Optional camera name
277    #[serde(default, skip_serializing_if = "Option::is_none")]
278    pub camera_name: Option<String>,
279
280    /// Optional gamma correction value
281    #[serde(default, skip_serializing_if = "Option::is_none")]
282    pub gamma: Option<f32>,
283}
284
285impl H264Metadata {
286    pub fn new(writing_app: &str, creation_time: chrono::DateTime<chrono::FixedOffset>) -> Self {
287        Self {
288            version: H264_METADATA_VERSION.to_string(),
289            writing_app: writing_app.to_string(),
290            creation_time,
291            camera_name: None,
292            gamma: None,
293        }
294    }
295}
296
297/// CSV recording configuration.
298#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
299pub enum CsvSaveConfig {
300    /// Do not save CSV
301    NotSaving,
302    /// Save CSV with optional framerate limit
303    Saving(Option<f32>),
304}
305
306/// AprilTag family types for detection.
307#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
308pub enum TagFamily {
309    /// 36h11 tag family (default)
310    #[default]
311    Family36h11,
312    /// Standard 41h12 tag family
313    FamilyStandard41h12,
314    /// 16h5 tag family
315    Family16h5,
316    /// 25h9 tag family
317    Family25h9,
318    /// Circle 21h7 tag family
319    FamilyCircle21h7,
320    /// Circle 49h12 tag family
321    FamilyCircle49h12,
322    /// Custom 48h12 tag family
323    FamilyCustom48h12,
324    /// Standard 52h13 tag family
325    FamilyStandard52h13,
326}
327
328impl EnumIter for TagFamily {
329    fn variants() -> Vec<Self> {
330        use TagFamily::*;
331        vec![
332            Family36h11,
333            FamilyStandard41h12,
334            Family16h5,
335            Family25h9,
336            FamilyCircle21h7,
337            FamilyCircle49h12,
338            FamilyCustom48h12,
339            FamilyStandard52h13,
340        ]
341    }
342}
343
344impl std::fmt::Display for TagFamily {
345    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
346        use TagFamily::*;
347        let fam = match self {
348            Family36h11 => "36h11".to_string(),
349            FamilyStandard41h12 => "standard-41h12".to_string(),
350            Family16h5 => "16h5".to_string(),
351            Family25h9 => "25h9".to_string(),
352            FamilyCircle21h7 => "circle-21h7".to_string(),
353            FamilyCircle49h12 => "circle-49h12".to_string(),
354            FamilyCustom48h12 => "custom-48h12".to_string(),
355            FamilyStandard52h13 => "standard-52h13".to_string(),
356        };
357
358        write!(f, "{fam}")
359    }
360}
361
362/// Bitrate selection options for video encoding.
363#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
364pub enum BitrateSelection {
365    /// Bitrate 500
366    Bitrate500,
367    /// Bitrate 1000 (default)
368    #[default]
369    Bitrate1000,
370    /// Bitrate 2000
371    Bitrate2000,
372    /// Bitrate 3000
373    Bitrate3000,
374    /// Bitrate 4000
375    Bitrate4000,
376    /// Bitrate 5000
377    Bitrate5000,
378    /// Bitrate 10000
379    Bitrate10000,
380    /// No bitrate limit
381    BitrateUnlimited,
382}
383
384impl std::fmt::Display for BitrateSelection {
385    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
386        use BitrateSelection::*;
387        match self {
388            Bitrate500 => write!(f, "500"),
389            Bitrate1000 => write!(f, "1000"),
390            Bitrate2000 => write!(f, "2000"),
391            Bitrate3000 => write!(f, "3000"),
392            Bitrate4000 => write!(f, "4000"),
393            Bitrate5000 => write!(f, "5000"),
394            Bitrate10000 => write!(f, "10000"),
395            BitrateUnlimited => write!(f, "Unlimited"),
396        }
397    }
398}
399
400impl strand_cam_enum_iter::EnumIter for BitrateSelection {
401    fn variants() -> Vec<Self> {
402        vec![
403            BitrateSelection::Bitrate500,
404            BitrateSelection::Bitrate1000,
405            BitrateSelection::Bitrate2000,
406            BitrateSelection::Bitrate3000,
407            BitrateSelection::Bitrate4000,
408            BitrateSelection::Bitrate5000,
409            BitrateSelection::Bitrate10000,
410            BitrateSelection::BitrateUnlimited,
411        ]
412    }
413}
414
415/// Type alias for optional ffmpeg codec argument lists.
416type FfmpegCodecArgList = Option<Vec<(String, String)>>;
417
418/// The default output pixel format. 4:2:0 chroma subsampling is the most widely
419/// decodable choice; in particular the built-in OpenH264 decoder only handles
420/// 4:2:0, so anything we might want to decode later must be encoded this way.
421/// Without forcing this, encoders like libx264 pick a format matching the input
422/// (e.g. `yuv444p` for RGB input), which OpenH264 cannot decode.
423const DEFAULT_OUTPUT_PIXFMT: &str = "yuv420p";
424
425fn default_output_pixfmt() -> Option<String> {
426    Some(DEFAULT_OUTPUT_PIXFMT.to_string())
427}
428
429/// Codec-specific arguments for ffmpeg.
430#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
431pub struct FfmpegCodecArgs {
432    /// Device-specific arguments
433    pub device_args: FfmpegCodecArgList,
434    /// Arguments before codec specification
435    pub pre_codec_args: FfmpegCodecArgList,
436    /// Codec name
437    pub codec: Option<String>,
438    /// Arguments after codec specification
439    pub post_codec_args: FfmpegCodecArgList,
440    /// Output pixel format passed to ffmpeg as `-pix_fmt`. Defaults to
441    /// [`DEFAULT_OUTPUT_PIXFMT`] (`yuv420p`) so encoded video is decodable by
442    /// OpenH264. Set to `None` to let ffmpeg (or a `-vf`/`-pix_fmt` in the other
443    /// arg lists) decide, e.g. for hardware encoders whose filter chain already
444    /// fixes the format.
445    #[serde(default = "default_output_pixfmt")]
446    pub pixfmt: Option<String>,
447    /// Maximum number of B-frames passed to ffmpeg as `-bf`. `None`, the
448    /// default, lets the encoder (or a `-bf` in the other arg lists) decide.
449    pub max_bframes: Option<u32>,
450}
451
452impl Default for FfmpegCodecArgs {
453    fn default() -> Self {
454        Self {
455            device_args: None,
456            pre_codec_args: None,
457            codec: None,
458            post_codec_args: None,
459            pixfmt: default_output_pixfmt(),
460            max_bframes: None,
461        }
462    }
463}
464
465impl std::fmt::Display for FfmpegCodecArgs {
466    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
467        fn arg_fmt(args: Option<&Vec<(String, String)>>) -> String {
468            if let Some(args) = args {
469                args.iter()
470                    .map(|(a1, a2)| format!("{a1} {a2}"))
471                    .collect::<Vec<_>>()
472                    .join(" ")
473            } else {
474                "".into()
475            }
476        }
477        let pre = arg_fmt(self.pre_codec_args.as_ref());
478        let codec = self
479            .codec
480            .as_ref()
481            .map(|c| format!("-c:v {c}"))
482            .unwrap_or_default();
483        let pixfmt = self
484            .pixfmt
485            .as_ref()
486            .map(|p| format!("-pix_fmt {p}"))
487            .unwrap_or_default();
488        let bframes = self
489            .max_bframes
490            .map(|n| format!("-bf {n}"))
491            .unwrap_or_default();
492        let post = arg_fmt(self.post_codec_args.as_ref());
493        write!(f, "ffmpeg {pre} {codec} {pixfmt} {bframes} {post}")
494    }
495}
496
497/// Codec selection for video encoding.
498#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
499pub enum CodecSelection {
500    /// H.264 NVENC hardware encoder
501    H264Nvenc,
502    /// OpenH264 software encoder
503    H264OpenH264,
504    /// Custom ffmpeg codec configuration
505    Ffmpeg(FfmpegCodecArgs),
506}
507
508impl CodecSelection {
509    /// Checks if this codec selection requires a specific feature.
510    pub fn requires(&self, what: &str) -> bool {
511        use CodecSelection::*;
512        match self {
513            H264Nvenc => what == "nvenc",
514            H264OpenH264 => false,
515            Ffmpeg(args) => {
516                if let Some(codec) = &args.codec {
517                    codec.contains(what)
518                } else {
519                    false
520                }
521            }
522        }
523    }
524}
525
526impl std::fmt::Display for CodecSelection {
527    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
528        use CodecSelection::*;
529        let x = match self {
530            H264Nvenc => "H264 NVENC",
531            H264OpenH264 => "OpenH264",
532            Ffmpeg(args) => {
533                return std::fmt::Display::fmt(args, f);
534            }
535        };
536        write!(f, "{x}")
537    }
538}
539
540impl strand_cam_enum_iter::EnumIter for CodecSelection {
541    fn variants() -> Vec<Self> {
542        use CodecSelection::*;
543        vec![
544            H264Nvenc,
545            H264OpenH264,
546            // Don't give bare option as it seems less useful than specifying a codec.
547            // Keep these in sync with the list in ffmpeg-writer.
548            Ffmpeg(FfmpegCodecArgs {
549                codec: Some("h264_videotoolbox".to_string()),
550                ..Default::default()
551            }),
552            Ffmpeg(FfmpegCodecArgs {
553                codec: Some("h264_nvenc".to_string()),
554                ..Default::default()
555            }),
556            Ffmpeg(FfmpegCodecArgs {
557                codec: Some("h264_nvmpi".to_string()),
558                ..Default::default()
559            }),
560            Ffmpeg(FfmpegCodecArgs {
561                device_args: Some(vec![("-vaapi_device".into(), "/dev/dri/renderD128".into())]),
562                pre_codec_args: Some(vec![("-vf".into(), "format=nv12,hwupload".into())]),
563                codec: Some("h264_vaapi".to_string()),
564                post_codec_args: Some(vec![("-color_range".into(), "pc".into())]),
565                // The `format=nv12,hwupload` filter chain already fixes the
566                // format and the encoder works on hardware surfaces; forcing an
567                // output `-pix_fmt` here would conflict.
568                pixfmt: None,
569                ..Default::default()
570            }),
571            // x264 with defaults
572            Ffmpeg(FfmpegCodecArgs {
573                codec: Some("libx264".to_string()),
574                ..Default::default()
575            }),
576            // x264 with -crf and -preset
577            Ffmpeg(FfmpegCodecArgs {
578                codec: Some("libx264".to_string()),
579                post_codec_args: Some(vec![
580                    ("-crf".into(), "22".into()),
581                    ("-preset".into(), "medium".into()),
582                ]),
583                ..Default::default()
584            }),
585        ]
586    }
587}
588
589/// Camera control commands for remote operation.
590#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
591pub enum CamArg {
592    /// Ignore future frame processing errors for this duration of seconds from current time.
593    ///
594    /// If seconds are not given, ignore forever.
595    SetIngoreFutureFrameProcessingErrors(Option<i64>),
596
597    SetExposureTime(f64),
598    SetExposureAuto(strand_cam_types::AutoMode),
599    SetFrameRateLimitEnabled(bool),
600    SetFrameRateLimit(f64),
601    SetGain(f64),
602    SetGainAuto(strand_cam_types::AutoMode),
603    SetRecordingFps(RecordingFrameRate),
604    SetMp4Bitrate(BitrateSelection),
605    SetMp4Codec(CodecSelection),
606    SetMp4CudaDevice(String),
607    SetMp4MaxFramerate(RecordingFrameRate),
608    SetIsRecordingMp4(bool),
609    SetIsRecordingFmf(bool),
610    /// used only with image-tracker crate
611    SetIsRecordingUfmf(bool),
612    /// used only with image-tracker crate
613    SetIsDoingObjDetection(bool),
614    /// used only with image-tracker crate
615    SetIsSavingObjDetectionCsv(CsvSaveConfig),
616    /// used only with image-tracker crate
617    SetObjDetectionConfig(String),
618    CamArgSetKalmanTrackingConfig(String),
619    CamArgSetLedProgramConfig(String),
620    SetFrameOffset(u64),
621    SetTriggerboxClockModel(Option<ClockModel>),
622    SetFormatStr(String),
623    ToggleCheckerboardDetection(bool),
624    ToggleCheckerboardDebug(bool),
625    SetCheckerboardWidth(u32),
626    SetCheckerboardHeight(u32),
627    ClearCheckerboards,
628    PerformCheckerboardCalibration,
629    DoQuit,
630    PostTrigger,
631    SetPostTriggerBufferSize(usize),
632    ToggleAprilTagFamily(TagFamily),
633    ToggleAprilTagDetection(bool),
634    SetIsRecordingAprilTagCsv(bool),
635    ToggleImOpsDetection(bool),
636    SetImOpsDestination(std::net::SocketAddr),
637    SetImOpsSource(std::net::IpAddr),
638    SetImOpsCenterX(u32),
639    SetImOpsCenterY(u32),
640    SetImOpsThreshold(u8),
641}