Add file sink#601
Draft
joaoantoniocardoso wants to merge 39 commits into
Draft
Conversation
Merged
… and to_nanos helper Add `Deserialize` derive to `Timestamp` and `CompressedVideo` so the new `stream-clients` crate (added in a later commit) can deserialize Zenoh messages received over the wire. The zenoh_client includes this file via `#[path]` and needs both derives for CDR deserialization. Add `Timestamp::to_nanos()` to convert a timestamp to nanoseconds, used by the stream-latency measurement example. Add `#[allow(dead_code)]` on `impl Timestamp` since some methods are only used when the file is included from the clients crate.
Removes the in-process WebRTC thread leak test that was gated behind the webrtc-test cargo feature: - Deletes src/lib/helper/develop.rs - Removes --enable-webrtc-task-test CLI argument from manager.rs - Removes webrtc-test feature gate from helper/mod.rs and main.rs Thread leak detection is now a standard integration test (thread_leak::test_webrtc_thread_leak) that inspects the MCM child process externally via sysinfo, requiring no special feature flags or CLI arguments.
New workspace member `stream-clients` (at clients/) providing GStreamer-based stream consumer clients for integration testing and latency measurement: - RTSP, UDP, WebRTC, Zenoh, and thumbnail HTTP clients - Optional `webrtc-test` feature gates the Selenium-based browser_webrtc_client (depends on thirtyfour) Dependencies include gstreamer 0.25, async-tungstenite, zenoh 1.5.1, reqwest, and serde/serde_json for signalling protocol handling.
…ncies Register the new `stream-clients` crate (clients/) as a workspace member. Set `autobenches = false` and `autotests = false` to disable automatic test/bench discovery, since the integration tests use an explicit `[[test]]` entry pointing at `tests/integration/main.rs`. Extend the `webrtc-test` feature to forward `stream-clients/webrtc-test` so the Selenium-based browser WebRTC client is available when the feature is enabled. Add dev-dependencies required by the integration test suite: reqwest, tokio-tungstenite, anyhow, futures, tempfile, thirtyfour, zenoh, stream-clients, and gst-rtsp-server. Add `[[example]]` entry for the `stream_latency` measurement tool.
Core abstractions for the stream-clients crate, shared by all stream consumer clients. Public API: - `StreamClient` async trait with `frames()`, `pipeline()`, `wait_for_frames(min, timeout)`, and `wait_for_continuous_frames(duration, check_interval)` for frame-count polling with timeout and stall detection. - `Codec` enum: H264, H265, Mjpg, Yuyv, Rgb. - `FrameSample` struct: content_hash, relative_pts_ms, arrival instant, and buffer_size for per-frame metadata. - `SampleSender` type alias for `mpsc::UnboundedSender<FrameSample>`. - `hash_vcl_nals(data)`: hashes only H.264/H.265 VCL NAL units, producing a stable hash across different pipeline processing chains (SPS/PPS injection, stream-format conversion, etc.). - `attach_frame_probe(pad, name, sender)`: installs a GStreamer pad probe that sends a `FrameSample` for each buffer, enabling hash-based frame correlation across transports.
Mirror of the main application's WebRTC signalling types, without the ts-rs dependency. Used by `webrtc_client` and integration tests to communicate over the WebSocket signalling channel. Defines the JSON message envelope (`Message`, `Question`, `Answer`, `Negotiation`), session/peer ID types (`SessionId`, `PeerId` as UUID aliases), stream metadata (`Stream`, `MediaNegotiation`), and SDP/ICE exchange types (`RTCSessionDescription`, `RTCIceCandidateInit`, `BindOffer`, `BindAnswer`). Includes `From` impls to wrap each variant into the `Protocol` enum for ergonomic message construction.
HTTP client for MCM's `/thumbnail?source=` endpoint, used by integration tests to verify thumbnail generation. Public API: - `ThumbnailClient::new(base_url)`: creates a client targeting the given MCM REST base URL. - `thumbnail(source)`: single GET request, returns the raw JPEG bytes or an error if the response is not 200. - `thumbnail_with_retry(source, retries, delay)`: retries up to N times with a fixed delay between attempts. - `cold_thumbnail(source, timeout)`: polls until a non-empty thumbnail is returned, used for cold-start scenarios where the pipeline may not yet have produced a frame. - `ensure_data_flowing(source, count, interval)`: fetches `count` thumbnails at the given interval, asserting each is non-empty, to verify continuous data flow.
GStreamer pipeline client that pulls from an RTSP URL, depayloads by codec (H264/H265/MJPEG), parses where applicable, and ends in fakesink. Waits for TCP reachability on the RTSP host:port before building the pipeline, avoiding spurious failures when MCM's RTSP server is still starting up. Optionally accepts a `SampleSender` to install a frame probe via `attach_frame_probe` for hash-based frame correlation in tests. Public API: - `RtspClient::new(url, codec, sender)`: builds and starts the pipeline after confirming TCP reachability. - Implements `StreamClient` (frames(), pipeline()). - `Drop` sets the pipeline to Null.
GStreamer udpsrc-based client for receiving RTP video streams over UDP. Supports two construction modes: - `new(port, codec, sender)`: for coded streams (H264/H265/MJPEG), builds the appropriate RTP depayloader and parser chain. - `new_raw(port, codec, width, height, sender)`: for raw streams (YUYV/RGB) with explicit dimensions passed via capsfilter. Both modes end in fakesink with frame probe attached via `attach_frame_probe` for hash-based flow verification in tests. Implements `StreamClient` (frames(), pipeline()). Drop sets the pipeline to Null.
Subscribes to a Zenoh topic and deserializes CompressedVideo messages (CDR-encoded) into GStreamer buffers for frame counting and hashing. The CompressedVideo type is imported via `#[path]` from the main crate's `zenoh_message.rs` (commit 1 added the required Deserialize derives). Pipeline: appsrc -> h264parse/h265parse -> capsfilter -> fakesink, with frame probe attached for hash-based flow verification. Supports H264 and H265 codecs. The Zenoh subscriber runs in a background tokio task that pushes buffers into appsrc. Public API: - `ZenohClient::new(topic, codec, sender, zenoh_config)`: opens a Zenoh session, subscribes to the topic, and starts the pipeline. - Implements `StreamClient` (frames(), pipeline()). - `Drop` sets the pipeline to Null and aborts the subscriber task.
Full GStreamer webrtcbin-based consumer client that handles the complete WebRTC session lifecycle over WebSocket signalling. Signalling flow: 1. Connect to the MCM signalling WebSocket endpoint 2. Obtain a peer ID 3. List available streams and select one (optionally by producer ID) 4. Start a session and receive the SDP offer 5. Create an SDP answer via webrtcbin and send it back 6. Exchange ICE candidates bidirectionally Media handling: - Dynamic pad callback detects codec from caps (H264/H265) - Builds depay -> parse -> decode pipeline per pad - Frame counting on both parsed (pre-decode) and decoded paths - Optional SampleSender probe on the parsed path for hash-based frame correlation Public API: - `WebrtcClient::connect(ws_url, producer, codec, sender)`: async constructor that completes the signalling handshake and returns a connected client. - `decoded_frames()`: frame count from the decoded path. - `wait_for_decoded_frames(min, timeout)`: polls decoded frame count. - Implements `StreamClient` (frames(), pipeline()) on the parsed path. - `Drop` aborts the signalling task and sets pipeline to Null.
Moves thumbnail_client below the other protocol clients for consistent alphabetical ordering in lib.rs.
Introduce the integration test binary at tests/integration/main.rs and the shared common module used by all test categories. The common module provides three submodules: - types: Serde models mirroring MCM's REST and signalling API (StreamInformation, PostStream, StreamStatus, StreamStatusState, ExtendedConfiguration, SignallingProtocol/Message/Question/Answer, BindOffer, BindAnswer, etc.). These allow tests to create streams, check status, and communicate over WebSocket without depending on the main crate's types. - mcm: McmProcess helper that spawns the mavlink-camera-manager binary as a child process with ephemeral ports (REST, signalling, RTSP) and a temporary settings directory. Includes automatic port-conflict retry, allocate_ports() and allocate_udp_ports() helpers, and optional MAVLink/Zenoh endpoint configuration. - api: McmClient HTTP client wrapping reqwest for REST API operations (list/create/delete streams, start/end WebRTC sessions, get thumbnails, check extended configuration). Also provides StateMonitor which polls stream status in a background task and records state transitions for assertions in lifecycle tests.
End-to-end data flow verification for each sink type. Tests create a stream via the REST API, connect a stream-clients consumer, and verify frames arrive continuously over a measurement window. mod.rs provides shared helpers: - NON_LAZY ExtendedConfiguration (disables lazy lifecycle for deterministic testing) - drain/collect_frames/wait_first_frame for frame sample analysis - verify_data_flow: asserts continuous frame arrival over the measurement window with gap detection - RTSP and TCP readiness polling helpers - create_fake_rtsp / create_redirect_stream setup functions Test submodules: - rtsp: H264 fake RTSP stream via RTSP client - udp: H264 fake RTSP stream via UDP sink client - webrtc: H264 fake RTSP stream via WebRTC client - thumbnail: thumbnail endpoint data flow check - qr: QR-code overlay verification (encodes stream name in video, decodes from received frames to verify correct routing) - redirect: redirect pipeline tests for H264, H265, and MJPEG with both UDP and WebRTC consumers - zenoh: Zenoh topic subscription tests for H264 and H265, including Zenoh-specific configuration and subscriber setup
Tests for the lazy stream lifecycle state machine, verifying that streams correctly transition through Idle, Running, Draining, and back to Idle based on consumer activity. mod.rs provides shared helpers: - IDLE_GRACE (5s) and IDLE_WAIT (8s) constants matching the watcher's idle grace period plus slack for the 100ms tick loop - setup_fake_rtsp: creates an MCM instance with a fake RTSP stream - wait_for_idle: polls until StreamStatusState::Idle is observed - rtsp_options_ok: RTSP OPTIONS probe to check server readiness Test submodules: - state_transitions: verifies the full Idle -> Running -> Draining -> Idle cycle using StateMonitor transition recording - rtsp: RTSP consumer triggers Running, disconnection triggers Idle - webrtc: WebRTC session start/end drives lifecycle transitions - thumbnail: thumbnail requests keep the stream alive - recovery: stream recovers from Idle when a new consumer connects - mixed: concurrent RTSP + WebRTC consumers, stream stays Running until all consumers disconnect
…nce test Verifies that MAVLink HEARTBEAT messages with MAV_TYPE_CAMERA continue to be emitted even after a lazy stream transitions to Idle. Test procedure: 1. Spawn MCM with a MAVLink UDP endpoint (udpout:127.0.0.1:<port>) 2. Create a fake RTSP stream with MAVLink enabled and lazy lifecycle 3. Wait for the stream to go through Running -> Draining -> Idle 4. Listen for heartbeats over ~5 seconds on the UDP port 5. Assert at least 3 heartbeats were received (1 Hz expected rate) Uses a background std::thread with the mavlink crate for UDP reception, since the mavlink crate's blocking recv is simpler than async alternatives for this use case.
…egration tests Tests for redirect pipelines (where an external source sends RTP packets to MCM) combined with WebRTC, thumbnail, and Zenoh consumers. mod.rs provides: - spawn_h264_udp_sender: launches an external gst-launch-1.0 process that sends H264 RTP to a given host:port, simulating a real camera or external encoder - Shared constants and imports for the submodules Test submodules: - webrtc: verifies that a redirect stream (UDP H264 input) can be consumed via WebRTC signalling, testing both the producer-specific session start and frame reception - thumbnail: verifies thumbnail generation from redirect streams - zenoh: verifies Zenoh topic publication from redirect streams, including subscriber setup and frame reception for H264
… tests Heavier integration scenarios supporting both fake and real camera sources. Designed for CI with fake sources and manual runs against real cameras via the TEST_SOURCE_MODE=camera environment variable. mod.rs provides: - TestEnv: auto-selects between spawning a local MCM with fake RTSP source or connecting to a pre-running instance (camera mode reads MCM_REST_URL, MCM_SIGNALLING_PORT, MCM_RTSP_URL, STREAM_SOURCE, ICE_FILTER_REGEX env vars) - mcm_pid() for external thread inspection via sysinfo - SourceMode/SourceTag enums for test filtering - Shared setup helpers (wait_for_stream_running, verify_frames, create_stream_and_wait) that handle both fake and camera modes - STATE_POLL and SETUP_TIMEOUT constants Test submodules: - rtsp: RTSP consumption with frame counting and continuous flow - webrtc: WebRTC session lifecycle with frame verification - thumb: thumbnail endpoint verification with cold-start and continuous data flow checks - zenoh: Zenoh topic subscription (camera mode only, skipped in fake) - mixed: concurrent multi-consumer scenarios (RTSP + WebRTC + thumbnail simultaneously) - thread_leak: WebRTC thread leak detection using sysinfo to monitor the MCM process for leaked threads after repeated WebRTC session cycles. Uses a deny-list of known infrastructure thread prefixes and a warmup cycle with dynamic stabilization for robust baselines.
CLI tool that receives the same video stream via multiple transports
(RTSP, WebRTC, UDP) simultaneously and measures pairwise latency
differences by correlating frames across transports using VCL NAL
content hashing.
Usage:
cargo run --example stream_latency -- \
--rtsp rtsp://host/path --webrtc ws://host:port [--udp 0.0.0.0:5600]
Features:
- Configurable warmup period to exclude startup transients
- Periodic progress reports during measurement
- CSV export (--csv output.csv) of raw per-frame data for
post-processing
- Per-transport statistics: frame count, bitrate, inter-frame jitter
- Pairwise latency: mean, median, stddev, min, max for each
transport pair that shares matching frames
Includes a companion Python script (plot_results.py) that loads one
or more CSV files and plots latency comparisons across runs (e.g.
different bitrates) and across transport pairs using matplotlib.
Switch CI from cargo test to cargo nextest run for parallel test execution with per-test timeout enforcement. Changes to .github/workflows/rust.yml: - Add gstreamer1.0-plugins-bad to apt dependencies (required by some integration test pipelines that use elements from plugins-bad) - Install nextest via taiki-e/install-action@nextest - Replace `cargo test --verbose --locked` with `cargo nextest run --verbose --no-fail-fast --locked` - Set GST_DEBUG=2 and GST_DEBUG_NO_COLOR=1 env vars for GStreamer traceability in CI logs - Add --success-output final --failure-output final for cleaner nextest output Add .config/nextest.toml with: - 120s slow-timeout with terminate-after=2 (kills hung tests) - A `camera` profile with threads-required=1 override for remote_integration:: tests, which share a single MCM instance on the remote host and must run sequentially (usage: cargo nextest run --profile camera)
- Add main AGENTS.md with Must Read section, task navigation, and module overviews - Add agents/ directory with detailed module documentation: - stream.md: Stream management architecture - pipeline.md: Pipeline implementations - sink.md: Sink implementations - video.md: Video source abstractions - mavlink.md: MAVLink integration - server.md: REST API server - settings.md: Settings/configuration management - preferences.md: User preferences template - knowledge.md: Knowledge base template This is a work in progress - system will be tested next.
`sysinfo::DiskExt::{total,available}_space` return bytes, but the code
divided by 2^10 with a stale comment claiming "Both uses KB". MAVLink
STORAGE_INFORMATION and CAMERA_CAPTURE_STATUS both declare units="MiB"
in common.xml, so the correct divisor is 2^20. The bug produced values
1024x too large (e.g. QGC displaying "87.48 TB" for an ~85 GiB free
partition).
`--recording-path` defaults to `./recordings`, which is created on first recording and would otherwise appear as untracked noise for developers running MCM from the repo root.
Add a MAVLink client that drives MCM's recording feature via MAV_CMD_VIDEO_START_CAPTURE / MAV_CMD_VIDEO_STOP_CAPTURE and consumes CAMERA_CAPTURE_STATUS / STORAGE_INFORMATION / STATUSTEXT responses. Used by the recording integration tests to exercise the end-to-end path without depending on QGroundControl.
Introduce two CLI arguments that drive the recording feature: --recording-path <PATH> (default: ./recordings) --min-free-disk-percent <PERCENT> (default: 10, 0 disables) Plus a small unrelated cleanup: expose an accessor for the existing `enable_webrtc_task_test` field so `webrtc-test`-feature code can read it consistently with the other CLI flags.
Add `create_fake_h264_stream_info`, `create_fake_h265_stream_info`, `temp_recording_path` and `cleanup_recording_path` helpers used by the recording tests to synthesise encoded sources via the GStreamer `ball` test pattern without needing a real camera. Widen `custom::test` visibility so tests outside the module can reach them.
Add the per-stream opt-out flag that the upcoming file_sink wiring and MAVLink VIDEO_START_CAPTURE handler will consult. The shared test `ExtendedConfiguration` mirror and the `NON_LAZY` constant used by the data_flow tests are updated in lockstep so the struct stays a drop-in replica of the REST API's shape.
Add a new FileSink that records the raw elementary stream (H.264 or H.265) into a fragmented MP4 on disk via a proxysink/proxysrc-isolated sub-pipeline. The sub-pipeline uses `mp4mux fragment-mode=dash-or-mss fragment-duration=1000` so each 1 s fragment is a self-contained moof+mdat pair: after SIGKILL / OOM / power loss the file is playable up to the last committed fragment (worst-case loss is one fragment), and recording duration is unbounded because no moov slot is pre-allocated. FileSink dynamically attaches to the source pipeline's video_tee via the existing link_sink_to_tee path; the tee variant match in pipeline::PipelineState::add_sink is widened to route `Sink::File(_)` through the video tee alongside Image/Zenoh/Rtsp. To keep the recording decodable from the first frame, `start()` dispatches an UpstreamForceKeyUnitEvent on the tee src pad so the encoder emits an immediate IDR, and a buffer probe on the proxysrc src pad drops leading DELTA_UNIT buffers until that IDR arrives. Without this, dynamic attach latches onto the tee mid-GOP and the resulting file begins with P-frames that reference an I-frame we never wrote: the container parses but every decoder produces garbage (mpv refuses, VLC shows corrupted frames). The same idiom is used by rtsp_sink on first client connect and by webrtc_sink on session connect.
`PipelineState::add_sink` previously inserted the Sink into the map without calling `start()` on it. The static start loop in `Stream::try_new` compensates for that on startup, but sinks added at runtime (recording via MAVLink, new WebRTC sessions) never had their proxy-isolated sub-pipeline flipped to Playing. Call `sink.start()` immediately after insert so dynamic adds are symmetric with the static boot path; `Rtsp` and `WebRTC` sinks remain no-ops as before.
… reason
Introduce `stream::recording::Manager`, a singleton that owns per-
stream `RecordingSession` state and enforces `--min-free-disk-percent`
before allowing a recording to start. Expose a thin lifecycle API on
`stream::manager::Manager`:
- start_recording[_by_name]: build a FileSink via create_file_sink,
attach it to the running Pipeline, and track the session.
- stop_recording[_by_name]: remove the sink from the Pipeline and
clear the session.
- rotate_recording: tear down the current sink and immediately
install a fresh one under a new timestamped filename. Called by
the deletion watcher when the output file disappears mid-record,
and uses `stop_recording_for_rotation` on the recording manager
so the rotation-cap history cumulates across consecutive rotations.
- take_recording_stop_reason: pop any error reason set by the
rotation path so the MAVLink layer can surface it as
`REC ERROR <reason>`.
- is_recording, get_recording_duration_ms,
is_recording_available_by_name: status queries consumed by the
MAVLink layer for CAMERA_INFORMATION flags and
CAMERA_CAPTURE_STATUS replies.
Each active recording spawns a detached watcher task that polls
`(device, inode)` of the output file every second. If the path goes
away (ENOENT) or is atomically replaced (different dev/ino), the
watcher triggers rotation, giving the user a seamless recover-on-`rm`
experience without touching the pipeline. A sliding window caps
rotations at 5 per 10 seconds; past the cap the watcher stops
recording and stashes a stop reason for the MAVLink layer to surface
as `REC ERROR <reason>`. Rotation dispatch is deliberately
fire-and-forget (a plain `fn` that spawns the rotation body) to
break an `async fn` type recursion between the watcher future and
`try_start`, which would otherwise prevent the compiler from
resolving `Send` for `tokio::spawn`.
`StreamStatus` gains `recording_unavailable_reason: Option<String>`,
populated here with human-readable causes (feature disabled, MJPEG /
raw encoding not supported, unknown codec) so REST consumers can
surface the reason without re-deriving the policy.
Wire the MAVLink camera protocol to the recording lifecycle manager:
- MAV_CMD_VIDEO_START_CAPTURE calls
`stream::manager::Manager::start_recording_by_name`, ACKs with the
appropriate MavResult, emits a `REC START <filename>` STATUSTEXT
on success (`REC ERROR <reason>` on failure), and spawns a
per-component streaming task that pushes CAMERA_CAPTURE_STATUS at
the requested status-frequency until recording stops.
- MAV_CMD_VIDEO_STOP_CAPTURE stops the session, emits `REC STOP
user_request`, and the capture-status task exits on the next tick.
- CAMERA_INFORMATION now sets CAMERA_CAP_FLAGS_CAPTURE_VIDEO only
when `is_recording_available_by_name` returns true, so QGC greys
out the record button for streams that cannot be recorded.
- MAV_CMD_REQUEST_CAMERA_CAPTURE_STATUS reports real
`video_status` / `recording_time_ms` from the recording manager
instead of the previous hard-coded zeros.
The capture-status streaming task tolerates a single transient
`is_recording == false` read before exiting: rotation-driven stops
(see `stream::recording`) briefly clear the active recording between
the internal stop and the subsequent start, and without this grace
the status stream would be torn down on every rotation. On a genuine
error-driven stop (e.g. the rotation cap tripping), the task consumes
any pending stop reason via
`stream::manager::Manager::take_recording_stop_reason` and emits a
final `REC ERROR <reason>` STATUSTEXT before terminating.
Adds the `capture_status_task: RwLock<Option<JoinHandle<()>>>` field
on `MavlinkCameraInner` and an async `capture_status_streaming_task`
that self-terminates when recording stops. No new runtime thread is
created; the task runs on the existing Tokio runtime.
Extend the McmProcess test harness to support recording tests:
- `start_with_recording(mavlink_port, recording_path)`: spawns MCM
with MAVLink on a TCP endpoint and `--recording-path` pointing at
a caller-owned directory (typically a tempfile::TempDir).
- `kill9` / `sigstop` / `sigcont`: libc-based signal helpers for
the robustness suite (SIGKILL to simulate hard process death;
SIGSTOP/SIGCONT to simulate source freezes and bursts).
- `mavlink_port: Option<u16>` so tests can discover the endpoint
MCM is listening on without re-plumbing allocate_ports.
Internal `try_start_inner` takes a new optional `recording_path`
parameter; existing call sites pass `None` and are otherwise
unchanged.
Five black-box tests that drive MCM's recording feature end-to-end via MAVLink (no REST shortcuts): H264 and H265 happy paths (start, wait, stop, probe the produced fMP4), CAMERA_INFORMATION capability flag assertion, CAMERA_CAPTURE_STATUS during an active recording, and STORAGE_INFORMATION cross-checked against `sysinfo` for the host's `/` mount within a +-10% tolerance so a future unit regression in `mavlink::sys_info` (e.g. KiB/GiB swap) fails this test loudly. Introduces `setup_recording_test`, `count_mp4_files`, and `walkdir` helpers shared with the robustness suite. `count_mp4_files` walks recursively so it remains robust to future subdirectory layouts.
…ests
Seven scenarios that stress the recording pipeline beyond the happy
path:
- test_recording_survives_sigkill: SIGKILL MCM mid-recording; assert
the resulting fMP4 parses, decodes cleanly, and contains at least
3 committed 1 s fragments (>=75 frames) out of the 6 s run. Proves
`mp4mux fragment-mode=dash-or-mss` keeps the file playable up to
the last flushed fragment.
- test_recording_smooth_against_stutters: 6 cycles of
{SIGSTOP 150 ms, SIGCONT 1200 ms}; assert monotonic PTS, clean
decode, and packet/decoded-frame count agreement.
- test_recording_smooth_against_bursts: 3 cycles of
{SIGSTOP 1500 ms, SIGCONT 2000 ms}; same assertions under longer
freezes.
- test_recording_rotates_on_file_deletion: `rm` the active mp4
mid-record; assert exactly one post-rotation mp4 exists under a
fresh timestamped filename and it decodes cleanly. Exercises the
deletion-watcher -> rotate_recording path end-to-end.
- test_recording_rotation_cap_triggers_stop: tight `rm` loop at
roughly watcher-tick cadence; assert the rotation cap trips,
stops recording, and no further files appear. Protects against
a rotate-on-delete storm.
- test_sequential_recordings_do_not_overlap: 3 back-to-back
recordings; assert distinct files, strict PTS ordering, and each
file decodes cleanly.
- test_rapid_start_stop_cycles: 10 rapid start/stop cycles via
MAVLink; assert every ACK succeeds, every file parses and decodes,
and the per-file/total frame counts clear a conservative floor.
Plus two `#[ignore]`d manual-inspection tests (`inspectable_recording`,
`inspectable_burst_recording`) that leave an MCM-produced fMP4 under
/tmp for mpv/VLC eyeballing.
The decode-correctness assertion uses `ffmpeg -f null -` paired with
`ffprobe -count_frames`: a file that parses but is undecodeable (e.g.
a missing leading keyframe) is detected here rather than silently
shipping unplayable recordings.
11e7588 to
e9a2a5d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merge after #594
TODO: