feat(video-streamer): add codec-aware VP9 keyframe detection for session shadowing#1705
feat(video-streamer): add codec-aware VP9 keyframe detection for session shadowing#1705irvingouj@Devolutions (irvingoujAtDevolution) wants to merge 4 commits intomasterfrom
Conversation
…ion shadowing The existing keyframe detection only handled VP8 frames. This adds proper VP9 keyframe detection based on the VP9 bitstream specification, supporting all profiles (0-3). The codec type is now threaded through the iterator and block tag layers so keyframe checks use the correct codec-specific logic. Also sets VpxEncoderPreset::BestPerformance for improved re-encoding throughput. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Send Error (not End) to client when shutdown is caused by a stream error - Use AbortOnDrop guard for bridge task to handle early returns/bails - Remove dead UserFriendlyError::UnexpectedEOF variant Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Let maintainers know that an action is required on their side
|
There was a problem hiding this comment.
Pull request overview
Adds periodic, interval-based keyframe forcing during re-encoding so the outgoing WebM stream remains seekable over time (and starts new clusters on forced keyframes), and improves EOF-wait robustness by warning when the when_eof oneshot sender is dropped.
Changes:
- Track last forced keyframe time and force a new keyframe every
KEYFRAME_INTERVAL_MS, starting a new cluster for that keyframe. - Log a warning and treat EOF-wait as shutdown if the
when_eofoneshot sender is unexpectedly dropped.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| crates/video-streamer/src/streamer/tag_writers.rs | Adds interval-based forced keyframes and cluster splitting to improve seekability/resilience. |
| crates/video-streamer/src/streamer/mod.rs | Adds a warning log when the when_eof oneshot sender is dropped unexpectedly. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| /// Interval in milliseconds at which keyframes are forced in the outgoing stream. | ||
| /// Periodic keyframes improve seekability and resilience to packet loss. | ||
| const KEYFRAME_INTERVAL_MS: u64 = 10_000; | ||
|
|
There was a problem hiding this comment.
This PR description mentions adding “codec-aware VP9 keyframe detection in tag_writers.rs”, but the change here introduces periodic forced keyframes (interval-based) rather than keyframe detection. If VP9 detection work is happening elsewhere, consider updating the PR description; otherwise, ensure the intended VP9 keyframe-detection fix is included.
| @@ -524,10 +540,25 @@ where | |||
| let frame_size = frame.len(); | |||
| perf_trace!(block_timestamp, frame_size, "Frame available from encoder"); | |||
|
|
|||
| let timestamp = self.compute_met_timestamp(cut_block_absolute_time, abs_time)?; | |||
| let block = SimpleBlock::new_uncheked(&frame, 1, timestamp, false, None, false, false); | |||
| perf_trace!(block_timestamp, "Writing block to output"); | |||
| self.write_block(block)?; | |||
| if force_keyframe { | |||
| // Start a new cluster for the forced keyframe so the stream remains seekable. | |||
| let cluster_rel = abs_time - cut_block_absolute_time; | |||
| self.maybe_report_realtime_ratio(abs_time, cluster_rel); | |||
| self.start_new_cluster(cluster_rel)?; | |||
| self.cut_block_state = CutBlockState::Met { | |||
| cut_block_absolute_time, | |||
| last_cluster_relative_time: cluster_rel, | |||
| }; | |||
| let block = SimpleBlock::new_uncheked(&frame, 1, 0, false, None, false, true); | |||
| perf_trace!(block_timestamp, "Writing forced keyframe block to output"); | |||
| self.write_block(block)?; | |||
| self.last_keyframe_abs_time = Some(abs_time); | |||
| } else { | |||
There was a problem hiding this comment.
The new periodic keyframe forcing / cluster-splitting behavior doesn’t appear to be covered by tests. Since this is important for session shadowing correctness, consider adding an integration test (similar to existing webm_stream_* tests) that runs long enough to cross KEYFRAME_INTERVAL_MS and asserts that a new cluster starts and the corresponding SimpleBlock is marked as a keyframe.
| self.start_new_cluster(cluster_rel)?; | ||
| self.cut_block_state = CutBlockState::Met { | ||
| cut_block_absolute_time, | ||
| last_cluster_relative_time: cluster_rel, | ||
| }; |
There was a problem hiding this comment.
In the forced-keyframe path, cut_block_state is updated to Met { .. last_cluster_relative_time: cluster_rel } before start_new_cluster(cluster_rel)? runs. If start_new_cluster fails (e.g., write error), the writer state is mutated even though the new cluster wasn't actually emitted. Consider calling start_new_cluster first and only updating cut_block_state after it succeeds (mirrors the ordering used in compute_met_timestamp).
Summary
tag_writers.rsso the streamer correctly identifies keyframes for both VP8 and VP9 codecs during session shadowingAbortOnDropbridge guard and shutdown-reason-aware final messages (Error vs End)when_eofoneshot sender is dropped unexpectedlyTest plan
🤖 Generated with Claude Code