diff --git a/crates/h264enc/README.md b/crates/h264enc/README.md index dca290a6b..69622e856 100644 --- a/crates/h264enc/README.md +++ b/crates/h264enc/README.md @@ -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. diff --git a/crates/h264enc/src/slice.rs b/crates/h264enc/src/slice.rs index 294991c09..39d40e8d3 100644 --- a/crates/h264enc/src/slice.rs +++ b/crates/h264enc/src/slice.rs @@ -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> { + 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 { 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); diff --git a/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/bframes.h264 b/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/bframes.h264 index 8c697d93f..ef283762a 100644 Binary files a/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/bframes.h264 and b/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/bframes.h264 differ diff --git a/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/high_crf_b2.h264 b/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/high_crf_b2.h264 index 9230f0e91..07aa01715 100644 Binary files a/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/high_crf_b2.h264 and b/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/high_crf_b2.h264 differ diff --git a/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/high_quality_b3_slices.h264 b/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/high_quality_b3_slices.h264 index af2c68d0b..a7a7ca72c 100644 Binary files a/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/high_quality_b3_slices.h264 and b/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/high_quality_b3_slices.h264 differ diff --git a/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/main_b2.h264 b/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/main_b2.h264 index 12ac5aa55..2cacf7b86 100644 Binary files a/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/main_b2.h264 and b/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/main_b2.h264 differ diff --git a/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/mandelbrot.h264 b/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/mandelbrot.h264 index b781fb5aa..cc9201a00 100644 Binary files a/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/mandelbrot.h264 and b/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/mandelbrot.h264 differ diff --git a/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/testsrc2.h264 b/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/testsrc2.h264 index e0b8fd559..8fecc11a2 100644 Binary files a/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/testsrc2.h264 and b/crates/h264enc/target/agent-h264enc/fixtures/h264enc/oracle/testsrc2.h264 differ