1 /*
2  *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "modules/video_processing/video_denoiser.h"
12 
13 #include <stdint.h>
14 #include <string.h>
15 
16 #include "api/video/i420_buffer.h"
17 #include "third_party/libyuv/include/libyuv/planar_functions.h"
18 
19 namespace webrtc {
20 
21 #if DISPLAY || DISPLAYNEON
CopyMem8x8(const uint8_t * src,int src_stride,uint8_t * dst,int dst_stride)22 static void CopyMem8x8(const uint8_t* src,
23                        int src_stride,
24                        uint8_t* dst,
25                        int dst_stride) {
26   for (int i = 0; i < 8; i++) {
27     memcpy(dst, src, 8);
28     src += src_stride;
29     dst += dst_stride;
30   }
31 }
32 
ShowRect(const std::unique_ptr<DenoiserFilter> & filter,const std::unique_ptr<uint8_t[]> & d_status,const std::unique_ptr<uint8_t[]> & moving_edge_red,const std::unique_ptr<uint8_t[]> & x_density,const std::unique_ptr<uint8_t[]> & y_density,const uint8_t * u_src,int stride_u_src,const uint8_t * v_src,int stride_v_src,uint8_t * u_dst,int stride_u_dst,uint8_t * v_dst,int stride_v_dst,int mb_rows_,int mb_cols_)33 static void ShowRect(const std::unique_ptr<DenoiserFilter>& filter,
34                      const std::unique_ptr<uint8_t[]>& d_status,
35                      const std::unique_ptr<uint8_t[]>& moving_edge_red,
36                      const std::unique_ptr<uint8_t[]>& x_density,
37                      const std::unique_ptr<uint8_t[]>& y_density,
38                      const uint8_t* u_src,
39                      int stride_u_src,
40                      const uint8_t* v_src,
41                      int stride_v_src,
42                      uint8_t* u_dst,
43                      int stride_u_dst,
44                      uint8_t* v_dst,
45                      int stride_v_dst,
46                      int mb_rows_,
47                      int mb_cols_) {
48   for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) {
49     for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
50       int mb_index = mb_row * mb_cols_ + mb_col;
51       const uint8_t* mb_src_u =
52           u_src + (mb_row << 3) * stride_u_src + (mb_col << 3);
53       const uint8_t* mb_src_v =
54           v_src + (mb_row << 3) * stride_v_src + (mb_col << 3);
55       uint8_t* mb_dst_u = u_dst + (mb_row << 3) * stride_u_dst + (mb_col << 3);
56       uint8_t* mb_dst_v = v_dst + (mb_row << 3) * stride_v_dst + (mb_col << 3);
57       uint8_t uv_tmp[8 * 8];
58       memset(uv_tmp, 200, 8 * 8);
59       if (d_status[mb_index] == 1) {
60         // Paint to red.
61         CopyMem8x8(mb_src_u, stride_u_src, mb_dst_u, stride_u_dst);
62         CopyMem8x8(uv_tmp, 8, mb_dst_v, stride_v_dst);
63       } else if (moving_edge_red[mb_row * mb_cols_ + mb_col] &&
64                  x_density[mb_col] * y_density[mb_row]) {
65         // Paint to blue.
66         CopyMem8x8(uv_tmp, 8, mb_dst_u, stride_u_dst);
67         CopyMem8x8(mb_src_v, stride_v_src, mb_dst_v, stride_v_dst);
68       } else {
69         CopyMem8x8(mb_src_u, stride_u_src, mb_dst_u, stride_u_dst);
70         CopyMem8x8(mb_src_v, stride_v_src, mb_dst_v, stride_v_dst);
71       }
72     }
73   }
74 }
75 #endif
76 
VideoDenoiser(bool runtime_cpu_detection)77 VideoDenoiser::VideoDenoiser(bool runtime_cpu_detection)
78     : width_(0),
79       height_(0),
80       filter_(DenoiserFilter::Create(runtime_cpu_detection, &cpu_type_)),
81       ne_(new NoiseEstimation()) {}
82 
DenoiserReset(rtc::scoped_refptr<I420BufferInterface> frame)83 void VideoDenoiser::DenoiserReset(
84     rtc::scoped_refptr<I420BufferInterface> frame) {
85   width_ = frame->width();
86   height_ = frame->height();
87   mb_cols_ = width_ >> 4;
88   mb_rows_ = height_ >> 4;
89 
90   // Init noise estimator and allocate buffers.
91   ne_->Init(width_, height_, cpu_type_);
92   moving_edge_.reset(new uint8_t[mb_cols_ * mb_rows_]);
93   mb_filter_decision_.reset(new DenoiserDecision[mb_cols_ * mb_rows_]);
94   x_density_.reset(new uint8_t[mb_cols_]);
95   y_density_.reset(new uint8_t[mb_rows_]);
96   moving_object_.reset(new uint8_t[mb_cols_ * mb_rows_]);
97 }
98 
PositionCheck(int mb_row,int mb_col,int noise_level)99 int VideoDenoiser::PositionCheck(int mb_row, int mb_col, int noise_level) {
100   if (noise_level == 0)
101     return 1;
102   if ((mb_row <= (mb_rows_ >> 4)) || (mb_col <= (mb_cols_ >> 4)) ||
103       (mb_col >= (15 * mb_cols_ >> 4)))
104     return 3;
105   else if ((mb_row <= (mb_rows_ >> 3)) || (mb_col <= (mb_cols_ >> 3)) ||
106            (mb_col >= (7 * mb_cols_ >> 3)))
107     return 2;
108   else
109     return 1;
110 }
111 
ReduceFalseDetection(const std::unique_ptr<uint8_t[]> & d_status,std::unique_ptr<uint8_t[]> * moving_edge_red,int noise_level)112 void VideoDenoiser::ReduceFalseDetection(
113     const std::unique_ptr<uint8_t[]>& d_status,
114     std::unique_ptr<uint8_t[]>* moving_edge_red,
115     int noise_level) {
116   // From up left corner.
117   int mb_col_stop = mb_cols_ - 1;
118   for (int mb_row = 0; mb_row <= mb_rows_ - 1; ++mb_row) {
119     for (int mb_col = 0; mb_col <= mb_col_stop; ++mb_col) {
120       if (d_status[mb_row * mb_cols_ + mb_col]) {
121         mb_col_stop = mb_col - 1;
122         break;
123       }
124       (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
125     }
126   }
127   // From bottom left corner.
128   mb_col_stop = mb_cols_ - 1;
129   for (int mb_row = mb_rows_ - 1; mb_row >= 0; --mb_row) {
130     for (int mb_col = 0; mb_col <= mb_col_stop; ++mb_col) {
131       if (d_status[mb_row * mb_cols_ + mb_col]) {
132         mb_col_stop = mb_col - 1;
133         break;
134       }
135       (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
136     }
137   }
138   // From up right corner.
139   mb_col_stop = 0;
140   for (int mb_row = 0; mb_row <= mb_rows_ - 1; ++mb_row) {
141     for (int mb_col = mb_cols_ - 1; mb_col >= mb_col_stop; --mb_col) {
142       if (d_status[mb_row * mb_cols_ + mb_col]) {
143         mb_col_stop = mb_col + 1;
144         break;
145       }
146       (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
147     }
148   }
149   // From bottom right corner.
150   mb_col_stop = 0;
151   for (int mb_row = mb_rows_ - 1; mb_row >= 0; --mb_row) {
152     for (int mb_col = mb_cols_ - 1; mb_col >= mb_col_stop; --mb_col) {
153       if (d_status[mb_row * mb_cols_ + mb_col]) {
154         mb_col_stop = mb_col + 1;
155         break;
156       }
157       (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
158     }
159   }
160 }
161 
IsTrailingBlock(const std::unique_ptr<uint8_t[]> & d_status,int mb_row,int mb_col)162 bool VideoDenoiser::IsTrailingBlock(const std::unique_ptr<uint8_t[]>& d_status,
163                                     int mb_row,
164                                     int mb_col) {
165   bool ret = false;
166   int mb_index = mb_row * mb_cols_ + mb_col;
167   if (!mb_row || !mb_col || mb_row == mb_rows_ - 1 || mb_col == mb_cols_ - 1)
168     ret = false;
169   else
170     ret = d_status[mb_index + 1] || d_status[mb_index - 1] ||
171           d_status[mb_index + mb_cols_] || d_status[mb_index - mb_cols_];
172   return ret;
173 }
174 
CopySrcOnMOB(const uint8_t * y_src,int stride_src,uint8_t * y_dst,int stride_dst)175 void VideoDenoiser::CopySrcOnMOB(const uint8_t* y_src,
176                                  int stride_src,
177                                  uint8_t* y_dst,
178                                  int stride_dst) {
179   // Loop over to copy src block if the block is marked as moving object block
180   // or if the block may cause trailing artifacts.
181   for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) {
182     const int mb_index_base = mb_row * mb_cols_;
183     const uint8_t* mb_src_base = y_src + (mb_row << 4) * stride_src;
184     uint8_t* mb_dst_base = y_dst + (mb_row << 4) * stride_dst;
185     for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
186       const int mb_index = mb_index_base + mb_col;
187       const uint32_t offset_col = mb_col << 4;
188       const uint8_t* mb_src = mb_src_base + offset_col;
189       uint8_t* mb_dst = mb_dst_base + offset_col;
190       // Check if the block is a moving object block or may cause a trailing
191       // artifacts.
192       if (mb_filter_decision_[mb_index] != FILTER_BLOCK ||
193           IsTrailingBlock(moving_edge_, mb_row, mb_col) ||
194           (x_density_[mb_col] * y_density_[mb_row] &&
195            moving_object_[mb_row * mb_cols_ + mb_col])) {
196         // Copy y source.
197         filter_->CopyMem16x16(mb_src, stride_src, mb_dst, stride_dst);
198       }
199     }
200   }
201 }
202 
CopyLumaOnMargin(const uint8_t * y_src,int stride_src,uint8_t * y_dst,int stride_dst)203 void VideoDenoiser::CopyLumaOnMargin(const uint8_t* y_src,
204                                      int stride_src,
205                                      uint8_t* y_dst,
206                                      int stride_dst) {
207   int height_margin = height_ - (mb_rows_ << 4);
208   if (height_margin > 0) {
209     const uint8_t* margin_y_src = y_src + (mb_rows_ << 4) * stride_src;
210     uint8_t* margin_y_dst = y_dst + (mb_rows_ << 4) * stride_dst;
211     libyuv::CopyPlane(margin_y_src, stride_src, margin_y_dst, stride_dst,
212                       width_, height_margin);
213   }
214   int width_margin = width_ - (mb_cols_ << 4);
215   if (width_margin > 0) {
216     const uint8_t* margin_y_src = y_src + (mb_cols_ << 4);
217     uint8_t* margin_y_dst = y_dst + (mb_cols_ << 4);
218     libyuv::CopyPlane(margin_y_src, stride_src, margin_y_dst, stride_dst,
219                       width_ - (mb_cols_ << 4), mb_rows_ << 4);
220   }
221 }
222 
DenoiseFrame(rtc::scoped_refptr<I420BufferInterface> frame,bool noise_estimation_enabled)223 rtc::scoped_refptr<I420BufferInterface> VideoDenoiser::DenoiseFrame(
224     rtc::scoped_refptr<I420BufferInterface> frame,
225     bool noise_estimation_enabled) {
226   // If previous width and height are different from current frame's, need to
227   // reallocate the buffers and no denoising for the current frame.
228   if (!prev_buffer_ || width_ != frame->width() || height_ != frame->height()) {
229     DenoiserReset(frame);
230     prev_buffer_ = frame;
231     return frame;
232   }
233 
234   // Set buffer pointers.
235   const uint8_t* y_src = frame->DataY();
236   int stride_y_src = frame->StrideY();
237   rtc::scoped_refptr<I420Buffer> dst =
238       buffer_pool_.CreateI420Buffer(width_, height_);
239 
240   uint8_t* y_dst = dst->MutableDataY();
241   int stride_y_dst = dst->StrideY();
242 
243   const uint8_t* y_dst_prev = prev_buffer_->DataY();
244   int stride_prev = prev_buffer_->StrideY();
245 
246   memset(x_density_.get(), 0, mb_cols_);
247   memset(y_density_.get(), 0, mb_rows_);
248   memset(moving_object_.get(), 1, mb_cols_ * mb_rows_);
249 
250   uint8_t noise_level = noise_estimation_enabled ? ne_->GetNoiseLevel() : 0;
251   int thr_var_base = 16 * 16 * 2;
252   // Loop over blocks to accumulate/extract noise level and update x/y_density
253   // factors for moving object detection.
254   for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) {
255     const int mb_index_base = mb_row * mb_cols_;
256     const uint8_t* mb_src_base = y_src + (mb_row << 4) * stride_y_src;
257     uint8_t* mb_dst_base = y_dst + (mb_row << 4) * stride_y_dst;
258     const uint8_t* mb_dst_prev_base = y_dst_prev + (mb_row << 4) * stride_prev;
259     for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
260       const int mb_index = mb_index_base + mb_col;
261       const bool ne_enable = (mb_index % NOISE_SUBSAMPLE_INTERVAL == 0);
262       const int pos_factor = PositionCheck(mb_row, mb_col, noise_level);
263       const uint32_t thr_var_adp = thr_var_base * pos_factor;
264       const uint32_t offset_col = mb_col << 4;
265       const uint8_t* mb_src = mb_src_base + offset_col;
266       uint8_t* mb_dst = mb_dst_base + offset_col;
267       const uint8_t* mb_dst_prev = mb_dst_prev_base + offset_col;
268 
269       // TODO(jackychen): Need SSE2/NEON opt.
270       int luma = 0;
271       if (ne_enable) {
272         for (int i = 4; i < 12; ++i) {
273           for (int j = 4; j < 12; ++j) {
274             luma += mb_src[i * stride_y_src + j];
275           }
276         }
277       }
278 
279       // Get the filtered block and filter_decision.
280       mb_filter_decision_[mb_index] =
281           filter_->MbDenoise(mb_dst_prev, stride_prev, mb_dst, stride_y_dst,
282                              mb_src, stride_y_src, 0, noise_level);
283 
284       // If filter decision is FILTER_BLOCK, no need to check moving edge.
285       // It is unlikely for a moving edge block to be filtered in current
286       // setting.
287       if (mb_filter_decision_[mb_index] == FILTER_BLOCK) {
288         uint32_t sse_t = 0;
289         if (ne_enable) {
290           // The variance used in noise estimation is based on the src block in
291           // time t (mb_src) and filtered block in time t-1 (mb_dist_prev).
292           uint32_t noise_var = filter_->Variance16x8(
293               mb_dst_prev, stride_y_dst, mb_src, stride_y_src, &sse_t);
294           ne_->GetNoise(mb_index, noise_var, luma);
295         }
296         moving_edge_[mb_index] = 0;  // Not a moving edge block.
297       } else {
298         uint32_t sse_t = 0;
299         // The variance used in MOD is based on the filtered blocks in time
300         // T (mb_dst) and T-1 (mb_dst_prev).
301         uint32_t noise_var = filter_->Variance16x8(
302             mb_dst_prev, stride_prev, mb_dst, stride_y_dst, &sse_t);
303         if (noise_var > thr_var_adp) {  // Moving edge checking.
304           if (ne_enable) {
305             ne_->ResetConsecLowVar(mb_index);
306           }
307           moving_edge_[mb_index] = 1;  // Mark as moving edge block.
308           x_density_[mb_col] += (pos_factor < 3);
309           y_density_[mb_row] += (pos_factor < 3);
310         } else {
311           moving_edge_[mb_index] = 0;
312           if (ne_enable) {
313             // The variance used in noise estimation is based on the src block
314             // in time t (mb_src) and filtered block in time t-1 (mb_dist_prev).
315             uint32_t noise_var = filter_->Variance16x8(
316                 mb_dst_prev, stride_prev, mb_src, stride_y_src, &sse_t);
317             ne_->GetNoise(mb_index, noise_var, luma);
318           }
319         }
320       }
321     }  // End of for loop
322   }    // End of for loop
323 
324   ReduceFalseDetection(moving_edge_, &moving_object_, noise_level);
325 
326   CopySrcOnMOB(y_src, stride_y_src, y_dst, stride_y_dst);
327 
328   // When frame width/height not divisible by 16, copy the margin to
329   // denoised_frame.
330   if ((mb_rows_ << 4) != height_ || (mb_cols_ << 4) != width_)
331     CopyLumaOnMargin(y_src, stride_y_src, y_dst, stride_y_dst);
332 
333   // Copy u/v planes.
334   libyuv::CopyPlane(frame->DataU(), frame->StrideU(), dst->MutableDataU(),
335                     dst->StrideU(), (width_ + 1) >> 1, (height_ + 1) >> 1);
336   libyuv::CopyPlane(frame->DataV(), frame->StrideV(), dst->MutableDataV(),
337                     dst->StrideV(), (width_ + 1) >> 1, (height_ + 1) >> 1);
338 
339 #if DISPLAY || DISPLAYNEON
340   // Show rectangular region
341   ShowRect(filter_, moving_edge_, moving_object_, x_density_, y_density_,
342            frame->DataU(), frame->StrideU(), frame->DataV(), frame->StrideV(),
343            dst->MutableDataU(), dst->StrideU(), dst->MutableDataV(),
344            dst->StrideV(), mb_rows_, mb_cols_);
345 #endif
346   prev_buffer_ = dst;
347   return dst;
348 }
349 
350 }  // namespace webrtc
351