14 KiB
AV quality enhancement — Q&A session notes
Research and recommendations for movie cast vs video cast quality enhancement in the Android Cast project. Combines user requirements, comparison tables, algorithm choices, placement (sender vs receiver), and fit with the current codebase.
Status: Planning / research only — not implemented.
Related code (today): sender ScreenCastService → VideoEncoder / AudioEncoder → network; receiver network → VideoDecoder / LibvpxCapableVideoDecoder / AudioDecoder → TextureView / AudioTrack.
1. User requirements (preliminary conditions)
- Content is either movie cast or video cast (live screen/camera); both must be supported with different tuning.
- Both video and audio need quality enhancement.
- Two processing shapes:
- 2.1 Temporal (N=2 frames/windows): small gap between sequential video frames → predict/enhance using deltas between frames; same idea for audio (overlapping buffers).
- 2.2 Single frame/chunk: static image from sink/source; one video frame; one audio PCM chunk.
- 2.3 Reassembly: output stream must keep original PTS/DTS (or fixed delay with uniform compensation); avoid changing frame/sample count on live paths unless explicitly designed.
Comparison dimensions requested: implementation details, difficulty, realtime on modern ARM (GPU Android/Linux where applicable), input vs output quality, single vs 2-frame vs stream (with delay), multithreading.
2. Mode overview
| Mode | Video input | Audio input | Typical delay | Best when |
|---|---|---|---|---|
| 2.2 Single | One buffer / frame | 10–40 ms PCM chunk | 0–1 frame (~0–33 ms @ 30 fps) | Live video cast, UI, games |
| 2.1 Two-frame (N=2) | Pair with small temporal gap | Overlapping windows | ~1 frame (+ overlap) | Motion; mild temporal help |
| Stream (3–8) | Short FIFO | Ring + STFT state | 100–400 ms+ | Movie cast, VOD-like |
| Heavy stream ML | Long context | Streaming neural | 200 ms–2 s+ | Offline / non-live only |
Quality (typical): stream/temporal > 2-frame > single frame
Latency (best first): single frame > 2-frame > stream
Policy split:
| Cast type | Video | Audio |
|---|---|---|
| Video cast (live) | 2.2 default; optional light 2.1 (+1 frame max) | 2.2 (RNNoise + limiter) |
| Movie cast | 2.1 or 3–4 frame TNR / MCTF-lite | 2-window spectral or DeepFilterNet2 streaming |
3. Video — method comparison
| Method | Implementation | Difficulty | ARM @ 720p30 | ARM @ 1080p30 | GPU (Android/Linux) | Quality vs input | PTS/DTS | Multithread |
|---|---|---|---|---|---|---|---|---|
| Single: bilateral / guided filter denoise | GLES/Vulkan on YUV; NEON fallback | Low | Yes | Yes (tuned) | High | Small–medium | Trivial | Yes (tiles) |
| Single: tone / CLAHE-lite | LUT + luma histogram tiles | Low–med | Yes | Yes | Medium | Medium in flat scenes | Trivial | Yes |
| Single: light SR (ESPCN-class tiny CNN) | TFLite/NCNN → NNAPI/GPU | Medium | Maybe | Tight | High | Medium (edges/text) | Trivial | Yes (async infer) |
| Single: heavy SR (Real-ESRGAN, SwinIR) | Large CNN | High | No | No | BW-bound | High but slow | Trivial | Limited |
| 2-frame: DIS / Farneback flow + warp blend | Flow on downscaled luma → warp prev | Medium | Yes | Maybe | Med–high | Medium | +1 frame | Yes |
| 2-frame: residual / delta enhance | enhanced = f(curr) + α·(curr − warped_prev) |
Medium | Yes | Maybe | Medium | Med–high on repetitive motion | +1 frame | Yes |
| 2-frame: RIFE-lite / FILM-small | Interpolate between frames | Med–high | Borderline 720p | Unlikely 1080p30 | High if custom | High for large gaps | Timing care | Yes |
| Stream: IIR TNR / motion-mask accumulate | 3–4 frame buffer | Medium | Yes | Maybe | High | High static; trail risk | +2–3 frames | Yes |
| Stream: ML temporal (VRT/RVRT-class) | Stateful temporal CNN | Very high | No live | No | Medium | Very high | Buffer delay | Some |
4. Audio — method comparison
| Method | Implementation | Difficulty | ARM realtime 48 kHz | GPU | Quality vs input | PTS/DTS | Multithread |
|---|---|---|---|---|---|---|---|
| Single: DC block + EQ + limiter | Biquads per chunk | Low | Yes | Low | Small | Preserve samples | Yes |
| Single: RNNoise / DeepFilterNet (10 ms) | TFLite/ONNX streaming | Medium | Yes mono; maybe stereo | NNAPI | High for noise | +10–20 ms | Yes (infer thread) |
| 2-window: Wiener / min-stats STFT | 20 ms, 50% hop | Medium | Yes | Low | Med–high | +1 hop (~10 ms) | Yes |
| 2-window: PLC | Predict from last 2 frames | Low–med | Yes | Low | High only on loss | Keep clock | Yes |
| Stream: multiband + AGC | Ring buffer envelopes | Low–med | Yes | Low | Medium intelligibility | 5–30 ms | Yes |
| Stream: DeepFilterNet2 | Stateful STFT+GRU | Med–high | Yes modern ARM | NNAPI | High | 20–40 ms | Yes |
5. Single vs 2-frame vs stream — decision matrix
| Criterion | Single (2.2) | 2-frame (2.1) | Stream (3–8) |
|---|---|---|---|
| Implementation complexity | Lowest | Medium | Highest |
| Encoder integration | Easiest | +1 frame pipeline | Delay queue |
| ARM realtime 720p30 | Best | Good | Good (TNR); poor heavy ML |
| ARM realtime 1080p30 | Good | Marginal | Marginal |
| GPU fit | Best | Good (warp) | Good (TNR) |
| Quality gain (video) | Low–medium | Medium + motion | High static / medium action |
| Quality gain (audio) | Medium | Med–high | High steady noise |
| Artifact risk | Banding, oversharpen | Ghosting, warp error | Trails, smear |
| PTS/DTS | Trivial | Fixed +1 frame delay | Uniform delay all outputs |
| Multithread | Capture ∥ GPU ∥ encode | Flow ∥ enhance | Buffer thread + pool |
| Movie cast | OK quick polish | Recommended | Recommended if delay OK |
| Video cast | Recommended | Optional 1-frame | Not recommended live |
6. PTS/DTS reassembly (2.3)
| Approach | Description | Modes |
|---|---|---|
| In-place payload replace | Same PTS/DTS, duration; swap enhanced payload | Single |
| Delay compensation | Queue enhanced frames; release with original PTS | 2-frame, stream |
| Composition timestamp | presentationTimeUs from source → enhanced buffer (MediaCodec) |
All on Android |
| Audio | Keep sample count constant → PTS unchanged | Prefer for live |
Rule: Do not change frame/sample count on live cast unless explicit insert/drop policy exists.
7. Pipeline placement: sender (2.1) vs receiver (2.2)
User question
- 2.1 Sender: enhancement between capture (camera/screen) and encoder.
- 2.2 Receiver: enhancement between decoded stream and renderer.
- User intuition: 2.1 has limited value because encoder lossy output destroys pre-enhance detail; increases sender load.
Assessment
| Claim | Verdict |
|---|---|
| Encoder “kills” sharpen/SR/local contrast pushed pre-encode | True |
| 2.1 is pointless | False for denoise-before-encode (helps bitrate/artifacts at same rate) |
| 2.1 burns sender CPU for little gain | Often true on thermally limited phones |
Preference (consensus from session)
| Placement | Role |
|---|---|
| Receiver (2.2) | Primary perceived quality — deblock, denoise, mild sharpen; optional 2-frame for movie |
| Sender (2.1) | Optional compression prep only (bilateral/NLMeans denoise), not main beauty pipeline |
| Both | Receiver polishes; sender light or off on live video cast |
Current app hooks:
| Pipeline | Integration point | Difficulty (1=easy, 5=hard) |
|---|---|---|
| 2.2 Video receiver | After VideoDecoder / LibvpxCapableVideoDecoder, before TextureView |
3–4 (new GLES: decode → FBO → display) |
| 2.2 Audio receiver | AudioDecoder PCM before AudioTrack |
2 |
| 2.1 Video sender | Between capture and VideoEncoder.prepare() Surface |
4–5 (intermediate EGL Surface, rotation) |
| 2.1 Audio sender | Before AudioEncoder |
2–3 |
| 2-frame video | Ring buffer +1 frame delay | +1 on above |
Recommended implementation fork: decoded-buffer → GLES → Surface (consistent for MediaCodec and libvpx VP9), not only TextureView overlay.
8. Named algorithm pipelines (best practices)
A. Receiver — live video cast (default)
Video (2.2, GPU-first):
- Post-decode deblocking filter (lite H.264-style / mild dering; VP9 loop filter already in decode)
- Guided filter or bilateral filter (edge-preserving denoise) on luma
- Mild unsharp mask (limit on blocky regions)
- Optional: gamma / BT.1886 + CLAHE-lite
Audio (2.2):
- RNNoise (10 ms frames)
- Limiter + high-pass (DC removal)
- Optional: SpeexDSP
preprocess(AGC, mild denoise)
B. Receiver — movie cast (+1 frame delay)
Video (2.1):
- DIS or Farneback optical flow (downscaled)
- MCTF-lite or IIR temporal denoise on stable regions
- Spatial chain from A (guided/bilateral + mild unsharp)
Audio (2.1):
- STFT overlap-add + Wiener filter, or DeepFilterNet2 (streaming)
- Multiband compressor
C. Sender — optional compression prep only
Video: Bilateral or fast NLMeans only when bitrate-starved — no Real-ESRGAN / strong sharpen pre-encode.
Audio: SpeexDSP preprocess or light RNNoise before AAC.
D. Avoid for realtime in this project
| Algorithm | Reason |
|---|---|
| Real-ESRGAN, SwinIR, BasicVSR++ | Too heavy ARM @ 720p–1080p30 live |
| RIFE / FILM @ 1080p30 full rate | Borderline; movie-only, lower rate |
| BM3D, VRT/RVRT | Offline-tier |
| Heavy 2.1 sender SR before VP9 | Encoder erases; thermals |
9. Ranked preferences (quality benefit vs implementation in android cast)
Descending order — build in this sequence:
| Rank | Choice | Placement | Core algorithms | Quality benefit | Impl difficulty (this app) |
|---|---|---|---|---|---|
| 1 | Receiver video 2.2 (GPU) | 2.2 | Guided/bilateral + deblock + mild unsharp | High vs blocky stream | 3–4 |
| 2 | Receiver audio 2.2 | 2.2 | RNNoise + limiter (+ SpeexDSP) | High for noise | 2 |
| 3 | Receiver video 2.1 (movie) | 2.2 + 1f delay | DIS/Farneback + MCTF-lite / IIR TNR + spatial | Higher static/grain | 4 |
| 4 | Receiver audio 2.1 (movie) | 2.2 | DeepFilterNet2 or Wiener STFT | Medium–high | 3 |
| 5 | Sender audio light prep | 2.1 | SpeexDSP / RNNoise → AAC | Low–medium | 2–3 |
| 6 | Sender video denoise only | 2.1 | Bilateral / fast NLMeans (not SR) | Low–medium (bitrate) | 4–5 |
| 7 | Sender video 2.1 beauty | 2.1 | Flow + enhance pre-encode | Low (washed by codec) | 5 |
| 8 | Heavy SR either side | either | Real-ESRGAN class | High offline only | 5, not realtime |
10. Multithreading pipeline (target architecture)
[Capture / Network decode] → queue → [Enhance worker(s)] → queue → [Encode / Render]
↑
N=2: previous frame
Stream: ring buffer
| Stage | Threads | Notes |
|---|---|---|
| Capture / decode callback | 1 | Non-blocking |
| Enhance (GPU) | 1–2 | Double-buffered FBO / AHardwareBuffer |
| CPU fallback (NEON) | 2–4 | Tile-based |
| ML audio | 1 | Async from video |
| Encode / display | 1 | MediaCodec async; TextureView main |
Golden rule: max one frame enhancement queue for video cast; 2–4 frames for movie cast.
Receiver already has videoDecodeThread in ReceiverCastService — natural host for GL enhance stage.
11. Short answers to numbered conditions
| # | Requirement | Preference |
|---|---|---|
| 1 | Movie or video cast | Different latency policies; same codebase, different presets |
| 2 | Process both A+V | Shared audio chain; video policy switches by cast mode |
| 2.1.1 | 2-frame video deltas | DIS/Farneback + residual — not full RIFE on ARM live |
| 2.1.2 | 2-frame audio | Overlap-add STFT or streaming DeepFilterNet |
| 2.2.1 | Single image video | GPU bilateral/guided + tone; tiny SR @ 720p only if ever |
| 2.2.2 | Single chunk audio | RNNoise + limiter |
| 2.3 | PTS/DTS reassembly | In-place or fixed-delay queue; no timeline shift on live |
12. Bottom line
| Goal | Best option |
|---|---|
| Lowest latency + ARM-safe | Single frame receiver (2.2) + GPU shaders |
| Best quality per ms delay | 2-frame receiver (2.1) + 2-window/stream audio |
| Best absolute quality (movie) | Small stream buffer (3–4) video TNR + streaming audio ML |
| Avoid realtime cast | Heavy per-frame SR, RIFE @ 1080p30, deep temporal video CNN |
| Placement | Receiver-first; sender denoise-only at most |
User’s encoder observation is correct for “beauty” enhancement pre-encode; denoise-before-encode remains the only strong sender-side exception.
13. Other work in same project session (context only)
Unrelated to AV enhancement but done in parallel on this branch:
- OTA optional
.otabundle.zip, crash reporter (:crashwatcher), HEADER.txt automation, PHP crash backend — seedocs/OTA.md,docs/CRASH_REPORTER.md,examples/crash_reporter/.
14. Open decisions when implementation starts
- Enhance on display path (
TextureView) vs decode buffer → GLES → Surface (recommended: latter). - User-toggle vs automatic preset for movie vs video cast.
- Whether sender denoise-only preset is worth thermals on low-end devices.
- GPU API: OpenGL ES 3.x compute vs Vulkan (ES3 often faster to integrate in Android media apps).
Document generated from AV quality Q&A session. Update as prototypes prove FPS/latency on target devices.