feat: migrate video capture/encoding from OpenCV to PyAV - #6
Merged
Conversation
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Replace cv2.VideoCapture with open_uvc_input() platform dispatch - Replace cv2.VideoWriter with shared VideoEncoder - Default resolution 1280x720@30 (enables request at UVC level) - Timestamp strategy unchanged (time.monotonic_ns after decode) - Discover helpers preserved verbatim - Existing UVC tests now fail; Task 5 migrates them Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…cording Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Remove cv2 import block - Use shared VideoEncoder.open() instead of cv2.VideoWriter - Reset per-recording counters on start_recording (same as UVC) - .release() -> .close() in _release_writers - DepthAI capture path and depth .bin write unchanged - OAK tests now fail on cv2 mock; Task 7 migrates them Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Prevents two issues: - Preview-phase intervals no longer poison p95/p99 on short recordings - Capture thread no longer mutates _intervals_ns while stop_recording is iterating it via sorted() in compute_jitter_percentiles Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
styu12
force-pushed
the
feat/pyav-migration
branch
from
April 14, 2026 01:08
b249ac1 to
94a33a2
Compare
Contributor
Author
|
Rebased onto latest Conflict resolution:
Post-rebase verification: migration-scope tests all pass — 240 tests across The 18 migration commits were all rebased successfully with no logic conflicts; only the uv.lock needed regeneration. |
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.
Summary
Migrates syncfield-python's video capture, encoding, and MJPEG serving paths from OpenCV (
cv2) to PyAV (av), while preserving the Stream SPI contract exactly.cv2.VideoCapture→ PyAV platform dispatch (avfoundation/v4l2/dshow);cv2.VideoWriter→ sharedVideoEncoder(H.264 via VideoToolbox on Apple Silicon, libx264 fallback). Defaults to 1280×720@30 — fixes a silent-ignore bug on main wherecv2.set(CAP_PROP_FRAME_WIDTH)was never called.VideoEncoder. Depth.binwrite path untouched.cv2.imencode→ Pillow for MJPEG endpoint.FinalizationReportgains optionaljitter_p95_ns/jitter_p99_nsfields, populated from inter-frame intervals during the recording window (≥20 sample threshold).Timestamp strategy intentionally unchanged:
time.monotonic_ns()taken immediately after each decoded frame. PTS NOT adopted — design rationale in the plan doc.Architecture
src/syncfield/adapters/_video_encoder.py— shared PyAV MP4 writer +open_uvc_input()platform dispatch +compute_jitter_percentiles()helper.StreamSPI (constructor, 4-phase lifecycle, legacystart/stop,latest_frame,FinalizationReportshape) preserved verbatim.pyproject.toml: removedopencv-python; addedav>=12.0.0andPillow>=10.0.0to extras.mock_avfixture hoisted totests/unit/adapters/conftest.py; UVC and OAK tests migrated;PytestUnhandledThreadExceptionWarningleaks cleaned.Also hardened on the way:
_capture_loopemits aHealthEvent(kind=ERROR)on decode error (was silent on main).start_recordingso back-to-back recordings in one session produce clean reports.VideoEncoder.close()idempotent even when flush raises._recordingflag — no race withstop_recording, no preview-phase pollution.Files changed
src/syncfield/adapters/_video_encoder.py(new, ~180 lines)src/syncfield/adapters/uvc_webcam.py(rewritten internals)src/syncfield/adapters/oak_camera.py(writer swapped)src/syncfield/viewer/server.py(Pillow JPEG)src/syncfield/types.py(+ jitter fields)src/syncfield/adapters/__init__.py,src/syncfield/viewer/__init__.py,src/syncfield/discovery/types.py(docstring cleanup)pyproject.toml+uv.locktests/unit/adapters/conftest.py(new)tests/unit/adapters/test_video_encoder.py(new, 10 tests)tests/unit/adapters/test_uvc_webcam.py,test_oak_camera.py,test_discover_*(migrated)tests/unit/viewer/test_cluster_endpoints.py(cv2 stub removed)tests/unit/test_finalization_jitter.py(new, 2 tests)examples/*/README.md(doc updates)docs/plans/2026-04-13-opencv-to-pyav-migration.md(plan)docs/plans/2026-04-13-opencv-to-pyav-migration-hw-smoketest.md(HW smoke-test checklist)Test plan
main)cv2/opencvreferences insrc/ortests/PytestUnhandledThreadExceptionWarningdocs/plans/2026-04-13-opencv-to-pyav-migration-hw-smoketest.md— requires real M3/M4 + cameras, to be run by reviewerNotes for reviewer
mainas well (verified viagit stash); they are intest_orchestrator.pyandtest_writer.pyand appear to be a version-string and manifest-path issue unrelated to this migration.pace_secondsparameter in the test fixtures is a pragmatic wall-clock workaround noted asTODO(test-harness)— a deterministic event-driven pump would be cleaner but the current pacing is stable. Revisit if CI flakes appear.🤖 Generated with Claude Code