VP7 encoder
[nihav.git] / nihav-duck / src / codecs / vp7enc / frame_coder.rs
CommitLineData
c5d5793c
KS
1use nihav_core::codecs::*;
2use nihav_codec_support::codecs::ZERO_MV;
3use super::super::vp78::PredMode;
4use super::super::vp78dsp::*;
5use super::super::vp7dsp::*;
6use super::blocks::*;
7use super::coder::*;
8use super::mb_coding::*;
9use super::models::*;
10use super::motion_est::*;
11use super::rdo::*;
12
13const MBT_Q_OFFSET: usize = 3;
14
15pub struct LoopParams {
16 pub loop_sharpness: u8,
17 pub loop_filter_level: u8,
18 pub lf_simple: bool,
19}
20
21pub struct FrameEncoder {
22 mb_w: usize,
23 mb_h: usize,
24 pub loop_params: LoopParams,
25
26 sblocks: Vec<SrcBlock>,
27 res: Vec<Residue>,
28 mbtypes: Vec<MBType>,
29 recon: Vec<SrcBlock>,
30 features: Vec<u8>,
31 has_features: bool,
32
33 pctx: PredContext,
34
35 me_mode: MVSearchMode,
36 me_range: i16,
37 mc_buf1: NAVideoBufferRef<u8>,
38 mc_buf2: NAVideoBufferRef<u8>,
39 mv_search_last: Box<dyn MVSearch + Send>,
40 mv_search_gold: Box<dyn MVSearch + Send>,
41}
42
43impl FrameEncoder {
44 pub fn new(mc_buf1: NAVideoBufferRef<u8>, mc_buf2: NAVideoBufferRef<u8>) -> Self {
45 let me_mode = MVSearchMode::default();
46
47 Self {
48 mb_w: 0,
49 mb_h: 0,
50
51 sblocks: Vec::new(),
52 res: Vec::new(),
53 mbtypes: Vec::new(),
54 recon: Vec::new(),
55 features: Vec::new(),
56 has_features: false,
57
58 pctx: PredContext::new(),
59
60 loop_params: LoopParams {
61 loop_filter_level: 0,
62 loop_sharpness: 0,
63 lf_simple: true,
64 },
65 me_mode,
66 me_range: 0,
67 mv_search_last: me_mode.create_search(),
68 mv_search_gold: me_mode.create_search(),
69 mc_buf1, mc_buf2,
70 }
71 }
72 pub fn resize(&mut self, mb_w: usize, mb_h: usize) {
73 self.mb_w = mb_w;
74 self.mb_h = mb_h;
75
76 self.pctx.resize(mb_w, mb_h);
77
78 self.sblocks.clear();
79 self.sblocks.reserve(mb_w * mb_h);
80 self.res.clear();
81 self.res.reserve(mb_w * mb_h);
82 self.mbtypes.clear();
83 self.mbtypes.reserve(mb_w * mb_h);
84 self.recon.clear();
85 self.recon.reserve(mb_w * mb_h);
86 self.features.clear();
87 self.features.reserve(mb_w * mb_h);
88 }
89 pub fn set_me_params(&mut self, me_mode: MVSearchMode, me_range: i16, version: u8) {
90 self.me_range = me_range;
91 if self.me_mode != me_mode {
92 self.me_mode = me_mode;
93 self.mv_search_last = me_mode.create_search();
94 self.mv_search_gold = me_mode.create_search();
95 }
96 self.pctx.version = version;
97 }
98 pub fn load_frame(&mut self, vbuf: &NAVideoBuffer<u8>) {
99 load_blocks(vbuf, &mut self.sblocks);
100 }
101
102 pub fn mb_tree_search(&mut self, ref_frm: NAVideoBufferRef<u8>, mb_map: &[usize], new_mb_map: &mut [usize], mb_weights: &mut [usize]) {
103 let mut mv_est = MVEstimator::new(ref_frm, self.mc_buf1.clone(), self.me_range);
104 self.mv_search_last.preinit(&mv_est);
105 let mut mb_idx = 0;
106 new_mb_map.copy_from_slice(mb_map);
107 for (mb_y, mb_row) in self.sblocks.chunks(self.mb_w).enumerate() {
108 for (mb_x, blk) in mb_row.iter().enumerate() {
109 let (mv, _) = self.mv_search_last.search_mb(&mut mv_est, blk, mb_x, mb_y);
110
111 if mv != ZERO_MV {
112 let new_x = ((((mb_x as isize) * 64 + (mv.x as isize) + 32) >> 6).max(0) as usize).min(self.mb_w - 1);
113 let new_y = ((((mb_y as isize) * 64 + (mv.y as isize) + 32) >> 6).max(0) as usize).min(self.mb_h - 1);
114 let nidx = new_x + new_y * self.mb_w;
115 new_mb_map[mb_idx] = mb_map[nidx];
116 }
117 mb_weights[new_mb_map[mb_idx]] += 1;
118 mb_idx += 1;
119 }
120 }
121 }
122
123 pub fn intra_blocks(&mut self, base_q: usize, metric: &RateDistMetric, models: &VP7Models, mbt_map: Option<&[usize]>) {
124 self.mbtypes.clear();
125 self.pctx.reset();
126 self.pctx.reset_intra();
127 self.res.clear();
128 self.recon.clear();
129 self.features.clear();
130
131 self.has_features = false;
132 if base_q > MBT_Q_OFFSET {
133 if let Some(map) = mbt_map {
134 let sum: usize = map.iter().sum();
135 let size = map.len();
136 let avg = (sum + size / 2) / size;
137 for &val in map.iter() {
138 if val > avg {
139 self.features.push(1);
140 self.has_features = true;
141 } else {
142 self.features.push(0);
143 }
144 }
145 } else {
146 for _ in 0..(self.mb_w * self.mb_h) {
147 self.features.push(0);
148 }
149 }
150 }
151
152 let mut imctx = IntraModePredCtx {
153 metric,
154 models,
155 tr: [0; 4],
156 q: base_q,
157 ipred_y: IPredContext::default(),
158 ipred_u: IPredContext::default(),
159 ipred_v: IPredContext::default(),
160 pctx: BlockPCtx::default(),
161 };
162
163 for (mb_y, mb_row) in self.sblocks.chunks_mut(self.mb_w).enumerate() {
164 imctx.ipred_y.has_top = mb_y != 0;
165 imctx.ipred_u.has_top = mb_y != 0;
166 imctx.ipred_v.has_top = mb_y != 0;
167
168 for (mb_x, sblk) in mb_row.iter().enumerate() {
169 self.pctx.fill_ipred(0, mb_x, &mut imctx.ipred_y);
170 self.pctx.fill_ipred(1, mb_x, &mut imctx.ipred_u);
171 self.pctx.fill_ipred(2, mb_x, &mut imctx.ipred_v);
172 self.pctx.fill_pctx(mb_x, &mut imctx.pctx);
173 if self.has_features {
174 imctx.q = if self.features[mb_x + mb_y * self.mb_w] != 0 {
175 base_q - MBT_Q_OFFSET
176 } else {
177 base_q
178 };
179 }
180
181 let mut res = Residue::new();
182 let mut newblk = SrcBlock::default();
183
184 imctx.tr = self.pctx.get_ipred_tr(mb_x);
185 let mut mb_type = select_intra_mode(sblk, &mut newblk, &mut res, &imctx, MAX_DIST, MBType::InterNoMV(false, [0;4]));
186
187 let use_i4 = match mb_type {
188 MBType::Intra(best_ymode, best_cmode) => {
189 sblk.apply_ipred_luma(best_ymode, &imctx.ipred_y, &mut res);
190 newblk.fill_ipred_luma(best_ymode, &imctx.ipred_y);
191 sblk.apply_ipred_chroma(best_cmode, &imctx.ipred_u, &imctx.ipred_v, &mut res);
192 newblk.fill_ipred_chroma(best_cmode, &imctx.ipred_u, &imctx.ipred_v);
193 res.fdct();
194 res.fdct_dc_block();
195
196 self.pctx.ymodes.set_mode(mb_x, best_ymode);
197
198 false
199 },
200 MBType::Intra4x4(ref i4_modes, ref mut i4ctx, best_cmode) => {
201 sblk.apply_ipred_chroma(best_cmode, &imctx.ipred_u, &imctx.ipred_v, &mut res);
202 newblk.fill_ipred_chroma(best_cmode, &imctx.ipred_u, &imctx.ipred_v);
203 res.fdct();
204
205 self.pctx.ymodes.set_modes4x4(mb_x, i4_modes, i4ctx);
206
207 true
208 },
209 _ => unreachable!(),
210 };
211
212 res.quant(imctx.q);
213 self.pctx.set_nz(mb_x, &res);
214 let mut recon = res.clone();
215 self.res.push(res);
216 self.mbtypes.push(mb_type);
217
218 if !use_i4 {
219 recon.add_residue(&mut newblk);
220 } else {
221 recon.add_residue_chroma(&mut newblk);
222 }
223
224 self.pctx.update_mb(&newblk, mb_x);
225 self.recon.push(newblk);
226 }
227 self.pctx.update_mb_row();
228 }
229 }
230 pub fn inter_blocks(&mut self, q: usize, metric: &RateDistMetric, models: &VP7Models, last_frame: &NABufferType, gold_frame: &NABufferType) {
231 self.has_features = false;
232
233 let mut mv_est_last = MVEstimator::new(last_frame.get_vbuf().unwrap(), self.mc_buf1.clone(), self.me_range);
234 self.mv_search_last.preinit(&mv_est_last);
235 let mut mv_est_gold = if let Some(gbuf) = gold_frame.get_vbuf() {
236 let mv_est = MVEstimator::new(gbuf, self.mc_buf2.clone(), self.me_range);
237 self.mv_search_gold.preinit(&mv_est);
238 Some(mv_est)
239 } else {
240 None
241 };
242
243 self.mbtypes.clear();
244 self.pctx.reset();
245 self.pctx.save_dc_pred();
246 self.res.clear();
247 self.recon.clear();
248 self.features.clear();
249
250 let mut imctx = IntraModePredCtx {
251 metric,
252 models,
253 tr: [0; 4],
254 q,
255 ipred_y: IPredContext::default(),
256 ipred_u: IPredContext::default(),
257 ipred_v: IPredContext::default(),
258 pctx: BlockPCtx::default(),
259 };
260
261 for (mb_y, mb_row) in self.sblocks.chunks_mut(self.mb_w).enumerate() {
262 imctx.ipred_y.has_top = mb_y != 0;
263 imctx.ipred_u.has_top = mb_y != 0;
264 imctx.ipred_v.has_top = mb_y != 0;
265
266 for (mb_x, sblk) in mb_row.iter().enumerate() {
267 self.pctx.fill_ipred(0, mb_x, &mut imctx.ipred_y);
268 self.pctx.fill_ipred(1, mb_x, &mut imctx.ipred_u);
269 self.pctx.fill_ipred(2, mb_x, &mut imctx.ipred_v);
270 self.pctx.fill_pctx(mb_x, &mut imctx.pctx);
271
272 let mut res = Residue::new();
273 let mut newblk = SrcBlock::default();
274
275 let (mvprobs, nearest_mv, near_mv, pred_mv) = self.pctx.find_mv_pred(mb_x, mb_y);
276
277 let (mv, _dist) = self.mv_search_last.search_mb(&mut mv_est_last, sblk, mb_x, mb_y);
278
279 mv_est_last.get_mb(&mut newblk, mb_x, mb_y, mv);
280 let mv_nits_dist = metric.calc_metric(0, inter_mv_nits(mv, &mvprobs, nearest_mv, near_mv, pred_mv, models));
281 let last_dist = calc_inter_mb_dist(sblk, &newblk, &mut res, &imctx, self.pctx.get_y2_dc_pred(true)) + mv_nits_dist;
282
283 let (gmv, gold_dist) = if last_dist > SMALL_DIST {
284 if let Some(ref mut mv_est) = &mut mv_est_gold {
285 let (gmv, _gdist) = self.mv_search_gold.search_mb(mv_est, sblk, mb_x, mb_y);
286 mv_est.get_mb(&mut newblk, mb_x, mb_y, gmv);
287 let mv_nits_dist = metric.calc_metric(0, inter_mv_nits(gmv, &mvprobs, nearest_mv, near_mv, pred_mv, models));
288 let gdist = calc_inter_mb_dist(sblk, &newblk, &mut res, &imctx, self.pctx.get_y2_dc_pred(false)) + mv_nits_dist;
289 (gmv, gdist)
290 } else {
291 (ZERO_MV, MAX_DIST)
292 }
293 } else {
294 (ZERO_MV, MAX_DIST)
295 };
296
297 let (last, mut inter_dist, mv, mv_est) = if last_dist < gold_dist {
298 (true, last_dist, mv, &mut mv_est_last)
299 } else if let Some (ref mut mv_est) = &mut mv_est_gold {
300 (false, gold_dist, gmv, mv_est)
301 } else {
302 unreachable!()
303 };
304
305 let mut mb_type = if mv == ZERO_MV {
306 MBType::InterNoMV(last, mvprobs)
307 } else if mv == nearest_mv {
308 MBType::InterNearest(last, mvprobs)
309 } else if mv == near_mv {
310 MBType::InterNear(last, mvprobs)
311 } else {
312 MBType::InterMV(last, mvprobs, mv - pred_mv)
313 };
314 if inter_dist > SMALL_DIST {
315 if let MBType::InterMV(_, _, _) = mb_type { // xxx: maybe do it for all types?
316 let mv_search = if last { &mut self.mv_search_last } else { &mut self.mv_search_gold };
317 if let Some((mbt, dist)) = try_inter_split(sblk, &mut newblk, &mut res, mvprobs, nearest_mv, near_mv, pred_mv, last, mb_x, mb_y, mv_search, mv_est, &mut self.pctx, &imctx, inter_dist) {
318 mb_type = mbt;
319 inter_dist = dist;
320 }
321 }
322 }
323
324 if inter_dist > SMALL_DIST {
325 imctx.tr = self.pctx.get_ipred_tr(mb_x);
326 mb_type = select_intra_mode(sblk, &mut newblk, &mut res, &imctx, inter_dist, mb_type);
327 }
328
329 self.mbtypes.push(mb_type);
330 res.reset();
331 match mb_type {
332 MBType::Intra(ymode, cmode) => {
333 newblk.fill_ipred_luma(ymode, &imctx.ipred_y);
334 newblk.fill_ipred_chroma(cmode, &imctx.ipred_u, &imctx.ipred_v);
335 self.pctx.ymodes.set_mode(mb_x, ymode);
336 self.pctx.fill_mv(mb_x, mb_y, ZERO_MV);
337 },
338 MBType::Intra4x4(ref i4_modes, ref mut i4ctx, cmode) => {
339 newblk.fill_ipred_chroma(cmode, &imctx.ipred_u, &imctx.ipred_v);
340 self.pctx.ymodes.set_modes4x4(mb_x, i4_modes, i4ctx);
341 self.pctx.fill_mv(mb_x, mb_y, ZERO_MV);
342 },
343 MBType::InterNoMV(_, _) |
344 MBType::InterNearest(_, _) |
345 MBType::InterNear(_, _) |
346 MBType::InterMV(_, _, _) => {
347 mv_est.get_mb(&mut newblk, mb_x, mb_y, mv);
348 self.pctx.fill_mv(mb_x, mb_y, mv);
349 self.pctx.ymodes.set_mode(mb_x, PredMode::Inter);
350 },
351 MBType::InterSplitMV(_, _, _, _, _) => {
352 self.pctx.ymodes.set_mode(mb_x, PredMode::Inter);
353 recon_split_mb(&mut newblk, mb_x, mb_y, &self.pctx.mvs, self.pctx.mv_stride, mv_est);
354 },
355 };
356 if let MBType::Intra4x4(_, _, _) = mb_type {
357 res.set_chroma_from_diff(&sblk.chroma, &newblk.chroma);
358 res.fdct();
359 } else {
360 res.set_luma_from_diff(&sblk.luma, &newblk.luma);
361 res.set_chroma_from_diff(&sblk.chroma, &newblk.chroma);
362 res.fdct();
363 res.fdct_dc_block();
364 if !mb_type.is_intra() {
365 requant_y2_dc(&mut res.dcs[0], q);
366 self.pctx.predict_y2_dc(&mut res.dcs[0], last);
367 }
368 }
369
370 res.quant(q);
371 self.pctx.set_nz(mb_x, &res);
372 let mut recon = res.clone();
373 self.res.push(res);
374 self.features.push(0);
375 if let MBType::Intra4x4(_, _, _) = mb_type {
376 recon.add_residue_chroma(&mut newblk);
377 } else {
378 recon.add_residue(&mut newblk);
379 }
380 self.pctx.update_mb(&newblk, mb_x);
381 self.recon.push(newblk);
382 }
383 self.pctx.update_mb_row();
384 }
385 }
386 pub fn encode_features(&self, bc: &mut BoolEncoder, q: usize, models: &VP7Models) -> EncoderResult<()> {
387 if self.has_features {
388 // first feature - quantiser
389 bc.put_bool(true, 128)?;
390 bc.put_byte(models.feature_present[0])?;
391 for &prob in models.feature_tree_probs[0].iter() {
392 bc.put_bool(prob != 255, 128)?;
393 if prob != 255 {
394 bc.put_byte(prob)?;
395 }
396 }
397 bc.put_bool(true, 128)?;
398 bc.put_bits((q - MBT_Q_OFFSET) as u32, 7)?;
399 for _ in 1..4 {
400 bc.put_bool(false, 128)?; // other quants
401 }
402
403 // other features (
404 for _ in 1..4 {
405 bc.put_bool(false, 128)?;
406 }
407 } else {
408 for _ in 0..4 {
409 bc.put_bool(false, 128)?;
410 }
411 }
412 Ok(())
413 }
414 pub fn encode_mb_types(&self, bc: &mut BoolEncoder, is_intra: bool, models: &VP7Models) -> EncoderResult<()> {
415 for (mb_type, &feature) in self.mbtypes.iter().zip(self.features.iter()) {
416 if self.has_features {
417 bc.encode_feature(0, if feature == 0 { None } else { Some(0) }, models)?;
418 }
419 bc.encode_mb_type(is_intra, mb_type, models)?;
420 }
421 Ok(())
422 }
423 pub fn encode_residues(&mut self, bc: &mut BoolEncoder, models: &VP7Models) -> EncoderResult<()> {
424 self.pctx.reset();
425 //self.pctx.restore_dc_pred();
426 for (_mb_y, mb_row) in self.res.chunks(self.mb_w).enumerate() {
427 for (mb_x, blk) in mb_row.iter().enumerate() {
428 if blk.has_dc {
429 let pctx = (self.pctx.nz_y2_left as u8) + (self.pctx.nz_y2_top[mb_x] as u8);
430 bc.encode_subblock(&blk.dcs, 1, pctx, models)?;
431 let has_nz = blk.dcs.has_nz();
432 self.pctx.nz_y2_left = has_nz;
433 self.pctx.nz_y2_top[mb_x] = has_nz;
434 }
435 let ytype = if blk.has_dc { 0 } else { 3 };
436 for (y, blk_row) in blk.luma.chunks(4).enumerate() {
437 for (x, blk) in blk_row.iter().enumerate() {
438 let pctx = (self.pctx.nz_y_left[y] as u8) + (self.pctx.nz_y_top[mb_x * 4 + x] as u8);
439 bc.encode_subblock(blk, ytype, pctx, models)?;
440 let has_nz = blk.has_nz();
441 self.pctx.nz_y_left[y] = has_nz;
442 self.pctx.nz_y_top[mb_x * 4 + x] = has_nz;
443 }
444 }
445
446 for (c, chroma) in blk.chroma.iter().enumerate() {
447 for (y, blk_row) in chroma.chunks(2).enumerate() {
448 for (x, blk) in blk_row.iter().enumerate() {
449 let pctx = (self.pctx.nz_c_left[c][y] as u8) + (self.pctx.nz_c_top[c][mb_x * 2 + x] as u8);
450 bc.encode_subblock(blk, 2, pctx, models)?;
451 let has_nz = blk.has_nz();
452 self.pctx.nz_c_left[c][y] = has_nz;
453 self.pctx.nz_c_top[c][mb_x * 2 + x] = has_nz;
454 }
455 }
456 }
457 }
458 self.pctx.update_mb_row();
459 }
460 Ok(())
461 }
462 pub fn generate_models(&mut self, is_intra: bool, stats: &mut VP7ModelsStat) {
463 stats.reset();
464 let est = Estimator::new();
465 self.pctx.reset();
466 if self.has_features {
467 for &feat in self.features.iter() {
468 est.estimate_feature(0, if feat == 0 { None } else { Some(0) }, stats);
469 }
470 }
471 for (mbt_row, mb_row) in self.mbtypes.chunks(self.mb_w).zip(self.res.chunks(self.mb_w)) {
472 for (mb_x, (mbtype, blk)) in mbt_row.iter().zip(mb_row.iter()).enumerate() {
473 est.estimate_mb_type(is_intra, mbtype, stats);
474 if blk.has_dc {
475 let pctx = (self.pctx.nz_y2_left as u8) + (self.pctx.nz_y2_top[mb_x] as u8);
476 est.estimate_subblock(&blk.dcs, 1, pctx, stats);
477 let has_nz = blk.dcs.has_nz();
478 self.pctx.nz_y2_left = has_nz;
479 self.pctx.nz_y2_top[mb_x] = has_nz;
480 }
481 let ytype = if blk.has_dc { 0 } else { 3 };
482 for (y, blk_row) in blk.luma.chunks(4).enumerate() {
483 for (x, blk) in blk_row.iter().enumerate() {
484 let pctx = (self.pctx.nz_y_left[y] as u8) + (self.pctx.nz_y_top[mb_x * 4 + x] as u8);
485 est.estimate_subblock(blk, ytype, pctx, stats);
486 let has_nz = blk.has_nz();
487 self.pctx.nz_y_left[y] = has_nz;
488 self.pctx.nz_y_top[mb_x * 4 + x] = has_nz;
489 }
490 }
491
492 for (c, chroma) in blk.chroma.iter().enumerate() {
493 for (y, blk_row) in chroma.chunks(2).enumerate() {
494 for (x, blk) in blk_row.iter().enumerate() {
495 let pctx = (self.pctx.nz_c_left[c][y] as u8) + (self.pctx.nz_c_top[c][mb_x * 2 + x] as u8);
496 est.estimate_subblock(blk, 2, pctx, stats);
497 let has_nz = blk.has_nz();
498 self.pctx.nz_c_left[c][y] = has_nz;
499 self.pctx.nz_c_top[c][mb_x * 2 + x] = has_nz;
500 }
501 }
502 }
503 }
504 self.pctx.update_mb_row();
505 }
506 }
507 pub fn reconstruct_frame(&mut self, frm: &mut NASimpleVideoFrame<u8>, is_intra: bool) {
508 let mut yidx = frm.offset[0];
509 let mut uidx = frm.offset[1];
510 let mut vidx = frm.offset[2];
511 let ystride = frm.stride[0];
512 let ustride = frm.stride[1];
513 let vstride = frm.stride[2];
514
515 for (mb_y, (f_row, mb_row)) in self.features.chunks(self.mb_w).zip(self.recon.chunks(self.mb_w)).enumerate() {
516 for (mb_x, (&feature, sblk)) in f_row.iter().zip(mb_row.iter()).enumerate() {
517 let dst = &mut frm.data[yidx + mb_x * 16..];
518 for (dst, src) in dst.chunks_mut(ystride).zip(sblk.luma.chunks(16)) {
519 dst[..16].copy_from_slice(src);
520 }
521 let dst = &mut frm.data[uidx + mb_x * 8..];
522 for (dst, src) in dst.chunks_mut(ustride).zip(sblk.chroma[0].chunks(8)) {
523 dst[..8].copy_from_slice(src);
524 }
525 let dst = &mut frm.data[vidx + mb_x * 8..];
526 for (dst, src) in dst.chunks_mut(vstride).zip(sblk.chroma[1].chunks(8)) {
527 dst[..8].copy_from_slice(src);
528 }
529
530 let loop_str = if feature != 2 {
531 self.loop_params.loop_filter_level
532 } else { 0 }; //todo
533 loop_filter_mb(frm, mb_x, mb_y, loop_str, &self.loop_params, is_intra);
534 }
535 yidx += ystride * 16;
536 uidx += ustride * 8;
537 vidx += vstride * 8;
538 }
539 }
540}
541
542fn loop_filter_mb(dframe: &mut NASimpleVideoFrame<u8>, mb_x: usize, mb_y: usize, loop_str: u8, loop_params: &LoopParams, is_intra: bool) {
543 const HIGH_EDGE_VAR_THR: [[u8; 64]; 2] = [
544 [
545 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
546 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
547 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
548 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3
549 ], [
550 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
551 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
552 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2,
553 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
554 ]];
555
556 let edge_thr = i16::from(loop_str) + 2;
557 let luma_thr = i16::from(loop_str);
558 let chroma_thr = i16::from(loop_str) * 2;
559 let inner_thr = if loop_params.loop_sharpness == 0 {
560 i16::from(loop_str)
561 } else {
562 let bound1 = i16::from(9 - loop_params.loop_sharpness);
563 let shift = (loop_params.loop_sharpness + 3) >> 2;
564 (i16::from(loop_str) >> shift).min(bound1)
565 };
566 let hev_thr = i16::from(HIGH_EDGE_VAR_THR[if is_intra { 1 } else { 0 }][loop_str as usize]);
567
568 let ystride = dframe.stride[0];
569 let ustride = dframe.stride[1];
570 let vstride = dframe.stride[2];
571 let ypos = dframe.offset[0] + mb_x * 16 + mb_y * 16 * ystride;
572 let upos = dframe.offset[1] + mb_x * 8 + mb_y * 8 * ustride;
573 let vpos = dframe.offset[2] + mb_x * 8 + mb_y * 8 * vstride;
574
575 let (loop_edge, loop_inner) = if loop_params.lf_simple {
576 (simple_loop_filter as LoopFilterFunc, simple_loop_filter as LoopFilterFunc)
577 } else {
578 (normal_loop_filter_edge as LoopFilterFunc, normal_loop_filter_inner as LoopFilterFunc)
579 };
580
581 if mb_x > 0 {
582 loop_edge(dframe.data, ypos, 1, ystride, 16, edge_thr, inner_thr, hev_thr);
583 loop_edge(dframe.data, upos, 1, ustride, 8, edge_thr, inner_thr, hev_thr);
584 loop_edge(dframe.data, vpos, 1, vstride, 8, edge_thr, inner_thr, hev_thr);
585 }
586 if mb_y > 0 {
587 loop_edge(dframe.data, ypos, ystride, 1, 16, edge_thr, inner_thr, hev_thr);
588 loop_edge(dframe.data, upos, ustride, 1, 8, edge_thr, inner_thr, hev_thr);
589 loop_edge(dframe.data, vpos, vstride, 1, 8, edge_thr, inner_thr, hev_thr);
590 }
591
592 for y in 1..4 {
593 loop_inner(dframe.data, ypos + y * 4 * ystride, ystride, 1, 16, luma_thr, inner_thr, hev_thr);
594 }
595 loop_inner(dframe.data, upos + 4 * ustride, ustride, 1, 8, chroma_thr, inner_thr, hev_thr);
596 loop_inner(dframe.data, vpos + 4 * vstride, vstride, 1, 8, chroma_thr, inner_thr, hev_thr);
597
598 for x in 1..4 {
599 loop_inner(dframe.data, ypos + x * 4, 1, ystride, 16, luma_thr, inner_thr, hev_thr);
600 }
601 loop_inner(dframe.data, upos + 4, 1, ustride, 8, chroma_thr, inner_thr, hev_thr);
602 loop_inner(dframe.data, vpos + 4, 1, vstride, 8, chroma_thr, inner_thr, hev_thr);
603}