mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-14 19:26:59 -04:00
M6.1: filmcraft-h264enc stage 2 — B 16x8/8x16 partitions with per-partition L0/L1/Bi
Motion vector predictors are re-derived in decoding order from the chosen per-partition directions. Stage 2 set: B-frames (1-3, spatial direct, B_Skip, concurrent B encoding), variance AQ, RD-lite (Quality). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
7103b58df9
commit
97ffb5acfb
8 files changed
+102
-3
No files matched your search
@@ -44,7 +44,7 @@ for p in enc.flush() { /* ... */ }
|
||||
| Parameter sets | SPS (level auto-selected from size/rate/DPB/bitrate, cropping, VUI: SAR, colour description + range, timing, bitstream restriction/reorder), PPS, AUD, user-data SEI, avcC |
|
||||
| Intra | 16x16 (4 modes, SATD), 4x4 (9 modes, SATD + mode-cost, sequential reconstruction), 8x8 (9 modes with reference filtering, High), chroma (4 modes) |
|
||||
| Inter P | hexagon integer search from predictors, half- then quarter-sample refinement (SATD) on precomputed 6-tap half-sample planes; 16x16/16x8/8x16/8x8 partitions (Balanced/Quality); P_Skip detection with the normative skip MV |
|
||||
| Inter B | 1–3 non-reference B-frames, spatial direct / B_Skip, L0/L1/Bi 16x16 with separate searches; all B-frames of a mini-GOP are encoded concurrently |
|
||||
| Inter B | 1–3 non-reference B-frames, spatial direct / B_Skip, L0/L1/Bi 16x16 and (Balanced/Quality) 16x8/8x16 with a per-partition direction; all B-frames of a mini-GOP are encoded concurrently |
|
||||
| Residual | 4x4 / 8x8 integer transforms, Intra16x16 and chroma DC Hadamards, deadzone quantisation (intra 1/3, inter 1/6), adaptive 4x4/8x8 inter transform choice, coefficient decimation for inter blocks |
|
||||
| Entropy | CABAC (all I and cabac_init_idc 0 context tables, verified against the spec text) and CAVLC (all coeff_token/total_zeros/run_before tables, level escapes) |
|
||||
| Loop filter | normative deblocking (bS 0–4 incl. bi-predicted rules, 8x8-transform edges, chroma QP mapping) in the reconstruction loop |
|
||||
@@ -77,7 +77,7 @@ ffmpeg and reports speed, bitrate, PSNR and any encoder/decoder mismatch (`MAP=1
|
||||
## Limitations / not yet implemented
|
||||
|
||||
- Single reference frame per list; no B-pyramid, no weighted prediction, no interlaced/MBAFF, no 4:2:2/4:4:4 or
|
||||
high bit depth, no custom scaling matrices, no sub-8x8 partitions, no B 16x8/8x16 partitions.
|
||||
high bit depth, no custom scaling matrices, no sub-8x8 partitions, no B_8x8.
|
||||
- CAVLC is used only for Baseline (High/Main always use CABAC); 8x8 transform is therefore CABAC-only.
|
||||
- No trellis quantisation, no psy-RD; no HRD/buffering-period SEI (VBV is enforced internally only).
|
||||
- Threading is slice-based only (no frame-level pipelining); with many slices, slice edges are not deblocked.
|
||||
+100
-1
@@ -1661,6 +1661,94 @@ impl<'a, 'b> SliceEnc<'a, 'b> {
|
||||
(out_ref, out_mv)
|
||||
}
|
||||
|
||||
/// B 16x8 / 8x16 candidate: search each partition in both lists, choose L0/L1/Bi per partition, then derive
|
||||
/// the motion vector predictors in decoding order from the chosen directions.
|
||||
fn b_partition(&self, mx: usize, my: usize, part: Part, src: &Src, qp: u8, lam: u32, seeds: [Mv; 2]) -> Option<Box<Cand>> {
|
||||
let f = self.f;
|
||||
let rp = [f.l0?, f.l1?];
|
||||
let (px, py) = (mx * 16, my * 16);
|
||||
let mut mvs = [[Mv::ZERO; 4]; 2];
|
||||
for l in 0..2 {
|
||||
mvs[l] = self.search_part(mx, my, rp[l], part, l, src, lam, seeds[l]).0;
|
||||
}
|
||||
let mut preds = [([0u8; 256], [0u8; 64], [0u8; 64]), ([0u8; 256], [0u8; 64], [0u8; 64])];
|
||||
for l in 0..2 {
|
||||
let (a, b, c) = &mut preds[l];
|
||||
for pi in 0..2 {
|
||||
self.pred_block(rp[l], px, py, part.rect(pi), mvs[l][pi], a, b, c);
|
||||
}
|
||||
}
|
||||
let mut bi = preds[0];
|
||||
avg_into(&mut bi.0, &preds[1].0);
|
||||
avg_into(&mut bi.1, &preds[1].1);
|
||||
avg_into(&mut bi.2, &preds[1].2);
|
||||
let srcs = [&preds[0], &preds[1], &bi];
|
||||
let (mut py_, mut pu, mut pv) = ([0u8; 256], [0u8; 64], [0u8; 64]);
|
||||
let mut dirs = [0u8; 4];
|
||||
let mut cost = ((lam * 8) >> 8) as u64;
|
||||
for pi in 0..2 {
|
||||
let (x, y, w, h) = part.rect(pi);
|
||||
let mut bestd = (u64::MAX, 0usize);
|
||||
for (d, s) in srcs.iter().enumerate() {
|
||||
let bits = if d == 2 { 14 } else { 7 };
|
||||
let c = satd(&src.y[y * 16 + x..], 16, &s.0[y * 16 + x..], 16, w, h) as u64 + ((lam * bits) >> 8) as u64;
|
||||
if c < bestd.0 {
|
||||
bestd = (c, d);
|
||||
}
|
||||
}
|
||||
cost += bestd.0;
|
||||
dirs[pi] = bestd.1 as u8;
|
||||
let s = srcs[bestd.1];
|
||||
for r in y..y + h {
|
||||
py_[r * 16 + x..r * 16 + x + w].copy_from_slice(&s.0[r * 16 + x..r * 16 + x + w]);
|
||||
}
|
||||
for r in y / 2..(y + h) / 2 {
|
||||
let o = r * 8 + x / 2;
|
||||
pu[o..o + w / 2].copy_from_slice(&s.1[o..o + w / 2]);
|
||||
pv[o..o + w / 2].copy_from_slice(&s.2[o..o + w / 2]);
|
||||
}
|
||||
}
|
||||
// predictors in decoding order with the actual per-list references
|
||||
let mut info = MbInfo::default();
|
||||
let mut refs = [[-1i8; 4]; 2];
|
||||
let mut fmvs = [[Mv::ZERO; 4]; 2];
|
||||
let mut mvds = [[Mv::ZERO; 4]; 2];
|
||||
for pi in 0..2 {
|
||||
let (x, y, w, h) = part.rect(pi);
|
||||
let mask = Self::mask_of(part, pi);
|
||||
for l in 0..2 {
|
||||
if dirs[pi] == 2 || dirs[pi] as usize == l {
|
||||
let mvp = self.mvp(&info, mask, mx, my, (x / 4) as i32, (y / 4) as i32, (w / 4) as i32, l, 0, Self::shape_of(part, pi));
|
||||
let mv = mvs[l][pi];
|
||||
mvds[l][pi] = Mv::new(mv.x as i32 - mvp.x as i32, mv.y as i32 - mvp.y as i32);
|
||||
refs[l][pi] = 0;
|
||||
fmvs[l][pi] = mv;
|
||||
}
|
||||
}
|
||||
for l in 0..2 {
|
||||
for by in y / 4..(y + h) / 4 {
|
||||
for bx in x / 4..(x + w) / 4 {
|
||||
info.mv[l][by * 4 + bx] = fmvs[l][pi];
|
||||
}
|
||||
}
|
||||
for by8 in y / 8..(y + h).div_ceil(8) {
|
||||
for bx8 in x / 8..(x + w).div_ceil(8) {
|
||||
info.ref_idx[l][by8 * 2 + bx8] = refs[l][pi];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut c = Cand::new();
|
||||
c.code.kind = MbKind::BInter;
|
||||
c.code.part = part;
|
||||
c.code.bdir = dirs;
|
||||
c.code.mvd = mvds;
|
||||
Self::cand_inter_info(&mut c, part, refs, fmvs, mvds);
|
||||
self.code_inter(src, &py_, &pu, &pv, qp, &mut c);
|
||||
c.cost = cost;
|
||||
Some(c)
|
||||
}
|
||||
|
||||
fn analyse_b(&self, mx: usize, my: usize, qp: u8, av: Avail, e: &MbEdges, src: &Src) -> Box<Cand> {
|
||||
let f = self.f;
|
||||
let (r0, r1) = (f.l0.unwrap(), f.l1.unwrap());
|
||||
@@ -1782,7 +1870,18 @@ impl<'a, 'b> SliceEnc<'a, 'b> {
|
||||
}
|
||||
});
|
||||
}
|
||||
let best = best.unwrap();
|
||||
let mut best = best.unwrap();
|
||||
// --- 16x8 / 8x16 partitions with a per-partition prediction direction
|
||||
if f.p.partitions && best.cost > 256 * 4 {
|
||||
for part in [Part::P16x8, Part::P8x16] {
|
||||
if let Some(c) = self.b_partition(mx, my, part, src, qp, lam, [res[0].0, res[1].0]) {
|
||||
let better = if f.p.rd { self.rd_cost(mx, my, &c, src, qp) < self.rd_cost(mx, my, &best, src, qp) } else { c.cost < best.cost };
|
||||
if better {
|
||||
best = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let need_intra = f.p.always_intra || best.cost > (256 * 3 + 16 * lam as u64 / 256) * 3;
|
||||
if need_intra {
|
||||
let ic = self.analyse_intra(mx, my, qp, av, e, src, best.cost);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user