This project captures live webcam video with OpenCV, then uses a custom C++ codec (standard library only for compression logic) to encode/decode a very low-bitrate grayscale stream focused on scene recognizability. The codec engine runs in-process, while a built-in web UI provides camera selection, controls, and live visualization.
- Webcam capture with runtime camera selection
- Browser dashboard (no OpenCV text overlays on frames)
- Role-based dashboard tabs:
Link,Send,Receive,Status,Advanced - Triple live feed dashboard:
RAW INPUTSENT (DITHERED)RECEIVED (INTERP+ENH)
- Internal codec profile tuned for unknown emergency-scene content
- Grayscale-only 4-bit luma internal coding
- Periodic keyframes + interframe temporal prediction
- Block-level sparse updates with motion-saliency capping
- Face-aware ROI updates (detected faces are prioritized for inter updates)
- Motion-compensated interpolation on the receiver path for smoother low-fps playback
- Receiver-side deblock + edge-aware enhancement
- Acoustic link transport with custom packet framing:
local_loopbackacoustic_txacoustic_rx_liveacoustic_rx_mediaacoustic_duplex_arq(half-duplex TDMA ARQ)
- Miniaudio capture/playback path for live mic/speaker transport
- Media-file receive path (demod from decoded audio track)
- Config beacons + config hash/version checks before payload decode
- Per-frame CRC + repetition/interleave FEC
- ACK/retransmit path in duplex ARQ mode
- Custom bitstream with custom bit writer/reader
- Live metrics: bytes/frame, live/smoothed/average bitrate, compression ratios, encoded fps, keyframe ratio, changed blocks, PSNR
- Live bandwidth bar visualization
- Web controls are fully wired (no inert fields): transport, link role, serial settings, relay paths, text send, codec/display toggles, and link start/stop
Requirements:
- CMake >= 3.16
- C++17 compiler
- OpenCV modules:
core,imgproc,highgui,videoio,objdetect pkg-config- FFmpeg development libraries:
libavformat,libavcodec,libavutil,libswresample
Build steps:
cmake -S . -B build
cmake --build build -jPortable package (same CMake workflow intended for macOS/Windows/Linux):
cmake --build build --target portableThis creates a self-contained folder at:
build/portable
Run from there:
./build/portable/emergency_videoPortable notes:
- This is a portable folder, not a single self-contained binary.
- Move the whole
build/portabledirectory to another machine with the same OS/arch. - The packaging flow is the same across macOS/Linux/Windows (
cmake --build build --target portable), while copied runtime library types differ by platform (.dylib/.so/.dll). - For the most aggressive dependency bundling, configure with:
-DEV_PORTABLE_BUNDLE_SYSTEM_LIBS=ON
If OpenCV is not installed locally, CMake will automatically fetch/build OpenCV (tag 4.10.0) using FetchContent.
This can make the first configure/build significantly longer.
System OpenCV only (disable fetch fallback):
cmake -S . -B build -DEV_FETCH_OPENCV=OFF -DOpenCV_DIR=/path/to/OpenCVConfig.cmake
cmake --build build -jRun:
./build/emergency_videoThen open:
http://localhost:8080
Optional legacy OpenCV window UI build:
cmake -S . -B build -DEV_BUILD_LEGACY_OPENCV_UI=ON
cmake --build build -j --target emergency_video_legacy
./build/emergency_video_legacyRun tests locally:
cmake -S . -B build -DEV_BUILD_TESTS=ON
cmake --build build -j
ctest --test-dir build --output-on-failure --verboseUnit-test coverage includes:
- bitstream read/write round-trip and header round-trip
- codec encode/decode round-trip in safer and aggressive modes
- corruption handling (invalid sync, truncation, trailing garbage)
- decoder state handling (interframe without reference, reset behavior)
- malformed change-map rejection
- keyframe interval behavior and frame-index progression
- deterministic encoding checks (same input/state -> same bytes)
- communicator envelope fragmentation/reassembly checks
- communicator envelope corruption rejection and queue dedup checks
GitHub Actions workflow:
.github/workflows/portable-artifacts.yml
What it does on each push/PR/manual run:
- Builds on
ubuntu-latest,macos-latest, andwindows-latest(MSYS2). - Runs CTest codec/unit tests.
- Builds the
portablepackage. - Validates packaged runtime dependencies (
ldd/otoolchecks). - Uploads downloadable portable artifacts per platform.
Linktab: essential setup for non-technical use (alias, camera, transport, role, quality, resolution, fps, start/stop).Sendtab: reliable text channel + quick emergency phrases + timeline.Receivetab: relay import/export actions.Statustab: queue/link/codec diagnostics.Advancedtab: acoustic/serial specifics and codec/display toggles.- Transport role selector uses
send/receive/duplex. - Start camera preference can be set with
EV_CAMERA_INDEX(default1)
HTTP API (v2 only):
GET /api/v2/statePOST /api/v2/controlPOST /api/v2/link/startPOST /api/v2/link/stopPOST /api/v2/messages/sendGET /api/v2/frame/{raw|sent|received}.jpg
Face detector note:
- Set
EV_FACE_CASCADEto your Haar cascade path if auto-discovery fails. - Typical filename:
haarcascade_frontalface_default.xml.
Camera note:
- The app now explicitly prefers camera index
1by default. - If your internal camera is on another index, run for example:
EV_CAMERA_INDEX=1 ./build/emergency_video
- Capture full-color webcam frame via OpenCV.
- Resize to internal encoded resolution.
- Convert to grayscale.
- Quantize each pixel to 4-bit luma (
0..15). - Encode frame as keyframe or interframe.
- Decode immediately from produced bitstream.
- Optional enhancement/dithering for display.
safer(default):128x96, target~2.5 fps, keyframe interval12aggressive:96x72, target~2.0 fps, keyframe interval18
- Block size:
8x8 - Keyframe blocks:
- absolute
4x4cell representation (16 values, 4 bits each), or - raw
8x84-bit samples for difficult blocks
- absolute
- Interframe blocks:
- unchanged blocks skipped via change map
- changed blocks use one of:
- residual
4x4cells vs previous reconstructed frame - absolute
4x4cells - raw
8x84-bit samples
- residual
The encoder always predicts from the previous reconstructed frame.
- Block change detection uses MAE and max-difference thresholds.
- Candidate changed blocks are ranked by motion saliency score.
- A per-profile cap (
maxChangedFraction) keeps only the strongest changed blocks. - Non-selected blocks are copied from previous reconstruction.
This acts as a low-bitrate safety floor when scene motion spikes.
Each frame bitstream includes:
- frame sync + version
- frame type (key/inter)
- codec mode
- dimensions and block size
- residual step
- frame index
- keyframe interval
- total block count
- changed block count
- payload:
- keyframe block payloads, or
- interframe RLE change map + changed block payloads
Change map uses run-length coding over block states (unchanged/changed), starting from unchanged.
- Always keeps global context through periodic keyframes.
- Preserves coarse scene structure with low-resolution cell coding.
- Prioritizes moving/high-change regions under bitrate pressure.
- Core codec remains deterministic and block-based; optional face-ROI prioritization is a lightweight assist.
- Uses deterministic, transparent logic suitable for on-device fallback pipelines.
- Lower internal resolution (
128x96or96x72) - Grayscale-only coding
- 4-bit luma quantization
- Low encoded fps (
~2-3) - Sparse interframe updates (skip unchanged blocks)
- Saliency-based cap on changed block count
- Coarse
4x4block payload modes instead of always raw8x8 - Run-length coding for block change map
- Switch to
aggressivemode in the UI. - Increase keyframe interval (edit
makeCodecParamsvalues). - Raise
skipAvgThreshold/skipMaxThreshold. - Lower
maxChangedFraction. - Increase
residualStep. - Lower
targetFps. - Use
96x72or lower encoded resolution.
- Use
safermode. - Reduce keyframe interval (use
defaultinstead ofshort). - Lower skip thresholds to update more blocks.
- Increase
maxChangedFraction. - Lower
residualStep. - Keep receiver enhancement enabled.
- Keep dithering enabled if banding is distracting.
- Not a standards-compliant media codec.
- Acoustic transport is best-effort and environment dependent (speaker/mic quality, room echo, noise, AGC).
- Current implementation uses simple repetition/interleave FEC rather than full Reed-Solomon.
- FFmpeg is linked as a library dependency at build/runtime (single-binary static packaging is not completed here).
- No global motion compensation or advanced entropy coding.
- Severe motion or camera shake can still force visible staleness or blocking.
- PSNR is computed in the internal 4-bit domain (useful for trend tracking, not perceptual truth).