|  | /* | 
|  | *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. | 
|  | * | 
|  | *  Use of this source code is governed by a BSD-style license | 
|  | *  that can be found in the LICENSE file in the root of the source | 
|  | *  tree. An additional intellectual property rights grant can be found | 
|  | *  in the file PATENTS.  All contributing project authors may | 
|  | *  be found in the AUTHORS file in the root of the source tree. | 
|  | */ | 
|  |  | 
|  | #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" | 
|  | #include "webrtc/modules/video_processing/video_denoiser.h" | 
|  |  | 
|  | namespace webrtc { | 
|  |  | 
|  | #if DISPLAY || DISPLAYNEON | 
|  | static void CopyMem8x8(const uint8_t* src, | 
|  | int src_stride, | 
|  | uint8_t* dst, | 
|  | int dst_stride) { | 
|  | for (int i = 0; i < 8; i++) { | 
|  | memcpy(dst, src, 8); | 
|  | src += src_stride; | 
|  | dst += dst_stride; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void 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, | 
|  | const uint8_t* v_src, | 
|  | uint8_t* u_dst, | 
|  | uint8_t* v_dst, | 
|  | int mb_rows_, | 
|  | int mb_cols_, | 
|  | int stride_u_, | 
|  | int stride_v_) { | 
|  | for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) { | 
|  | for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) { | 
|  | int mb_index = mb_row * mb_cols_ + mb_col; | 
|  | const uint8_t* mb_src_u = | 
|  | u_src + (mb_row << 3) * stride_u_ + (mb_col << 3); | 
|  | const uint8_t* mb_src_v = | 
|  | v_src + (mb_row << 3) * stride_v_ + (mb_col << 3); | 
|  | uint8_t* mb_dst_u = u_dst + (mb_row << 3) * stride_u_ + (mb_col << 3); | 
|  | uint8_t* mb_dst_v = v_dst + (mb_row << 3) * stride_v_ + (mb_col << 3); | 
|  | uint8_t uv_tmp[8 * 8]; | 
|  | memset(uv_tmp, 200, 8 * 8); | 
|  | if (d_status[mb_index] == 1) { | 
|  | // Paint to red. | 
|  | CopyMem8x8(mb_src_u, stride_u_, mb_dst_u, stride_u_); | 
|  | CopyMem8x8(uv_tmp, 8, mb_dst_v, stride_v_); | 
|  | } else if (moving_edge_red[mb_row * mb_cols_ + mb_col] && | 
|  | x_density[mb_col] * y_density[mb_row]) { | 
|  | // Paint to blue. | 
|  | CopyMem8x8(uv_tmp, 8, mb_dst_u, stride_u_); | 
|  | CopyMem8x8(mb_src_v, stride_v_, mb_dst_v, stride_v_); | 
|  | } else { | 
|  | CopyMem8x8(mb_src_u, stride_u_, mb_dst_u, stride_u_); | 
|  | CopyMem8x8(mb_src_v, stride_v_, mb_dst_v, stride_v_); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | VideoDenoiser::VideoDenoiser(bool runtime_cpu_detection) | 
|  | : width_(0), | 
|  | height_(0), | 
|  | filter_(DenoiserFilter::Create(runtime_cpu_detection, &cpu_type_)), | 
|  | ne_(new NoiseEstimation()) {} | 
|  |  | 
|  | void VideoDenoiser::DenoiserReset( | 
|  | const rtc::scoped_refptr<VideoFrameBuffer>& frame, | 
|  | rtc::scoped_refptr<I420Buffer>* denoised_frame, | 
|  | rtc::scoped_refptr<I420Buffer>* denoised_frame_prev) { | 
|  | width_ = frame->width(); | 
|  | height_ = frame->height(); | 
|  | mb_cols_ = width_ >> 4; | 
|  | mb_rows_ = height_ >> 4; | 
|  | stride_y_ = frame->StrideY(); | 
|  | stride_u_ = frame->StrideU(); | 
|  | stride_v_ = frame->StrideV(); | 
|  |  | 
|  | // Allocate an empty buffer for denoised_frame_prev. | 
|  | *denoised_frame_prev = I420Buffer::Create( | 
|  | width_, height_, stride_y_, stride_u_, stride_v_); | 
|  | // Allocate and initialize denoised_frame with key frame. | 
|  | *denoised_frame = I420Buffer::CopyKeepStride(frame); | 
|  |  | 
|  | // Init noise estimator and allocate buffers. | 
|  | ne_->Init(width_, height_, cpu_type_); | 
|  | moving_edge_.reset(new uint8_t[mb_cols_ * mb_rows_]); | 
|  | mb_filter_decision_.reset(new DenoiserDecision[mb_cols_ * mb_rows_]); | 
|  | x_density_.reset(new uint8_t[mb_cols_]); | 
|  | y_density_.reset(new uint8_t[mb_rows_]); | 
|  | moving_object_.reset(new uint8_t[mb_cols_ * mb_rows_]); | 
|  | } | 
|  |  | 
|  | int VideoDenoiser::PositionCheck(int mb_row, int mb_col, int noise_level) { | 
|  | if (noise_level == 0) | 
|  | return 1; | 
|  | if ((mb_row <= (mb_rows_ >> 4)) || (mb_col <= (mb_cols_ >> 4)) || | 
|  | (mb_col >= (15 * mb_cols_ >> 4))) | 
|  | return 3; | 
|  | else if ((mb_row <= (mb_rows_ >> 3)) || (mb_col <= (mb_cols_ >> 3)) || | 
|  | (mb_col >= (7 * mb_cols_ >> 3))) | 
|  | return 2; | 
|  | else | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | void VideoDenoiser::ReduceFalseDetection( | 
|  | const std::unique_ptr<uint8_t[]>& d_status, | 
|  | std::unique_ptr<uint8_t[]>* moving_edge_red, | 
|  | int noise_level) { | 
|  | // From up left corner. | 
|  | int mb_col_stop = mb_cols_ - 1; | 
|  | for (int mb_row = 0; mb_row <= mb_rows_ - 1; ++mb_row) { | 
|  | for (int mb_col = 0; mb_col <= mb_col_stop; ++mb_col) { | 
|  | if (d_status[mb_row * mb_cols_ + mb_col]) { | 
|  | mb_col_stop = mb_col - 1; | 
|  | break; | 
|  | } | 
|  | (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0; | 
|  | } | 
|  | } | 
|  | // From bottom left corner. | 
|  | mb_col_stop = mb_cols_ - 1; | 
|  | for (int mb_row = mb_rows_ - 1; mb_row >= 0; --mb_row) { | 
|  | for (int mb_col = 0; mb_col <= mb_col_stop; ++mb_col) { | 
|  | if (d_status[mb_row * mb_cols_ + mb_col]) { | 
|  | mb_col_stop = mb_col - 1; | 
|  | break; | 
|  | } | 
|  | (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0; | 
|  | } | 
|  | } | 
|  | // From up right corner. | 
|  | mb_col_stop = 0; | 
|  | for (int mb_row = 0; mb_row <= mb_rows_ - 1; ++mb_row) { | 
|  | for (int mb_col = mb_cols_ - 1; mb_col >= mb_col_stop; --mb_col) { | 
|  | if (d_status[mb_row * mb_cols_ + mb_col]) { | 
|  | mb_col_stop = mb_col + 1; | 
|  | break; | 
|  | } | 
|  | (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0; | 
|  | } | 
|  | } | 
|  | // From bottom right corner. | 
|  | mb_col_stop = 0; | 
|  | for (int mb_row = mb_rows_ - 1; mb_row >= 0; --mb_row) { | 
|  | for (int mb_col = mb_cols_ - 1; mb_col >= mb_col_stop; --mb_col) { | 
|  | if (d_status[mb_row * mb_cols_ + mb_col]) { | 
|  | mb_col_stop = mb_col + 1; | 
|  | break; | 
|  | } | 
|  | (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | bool VideoDenoiser::IsTrailingBlock(const std::unique_ptr<uint8_t[]>& d_status, | 
|  | int mb_row, | 
|  | int mb_col) { | 
|  | bool ret = false; | 
|  | int mb_index = mb_row * mb_cols_ + mb_col; | 
|  | if (!mb_row || !mb_col || mb_row == mb_rows_ - 1 || mb_col == mb_cols_ - 1) | 
|  | ret = false; | 
|  | else | 
|  | ret = d_status[mb_index + 1] || d_status[mb_index - 1] || | 
|  | d_status[mb_index + mb_cols_] || d_status[mb_index - mb_cols_]; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void VideoDenoiser::CopySrcOnMOB(const uint8_t* y_src, uint8_t* y_dst) { | 
|  | // Loop over to copy src block if the block is marked as moving object block | 
|  | // or if the block may cause trailing artifacts. | 
|  | for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) { | 
|  | const int mb_index_base = mb_row * mb_cols_; | 
|  | const int offset_base = (mb_row << 4) * stride_y_; | 
|  | const uint8_t* mb_src_base = y_src + offset_base; | 
|  | uint8_t* mb_dst_base = y_dst + offset_base; | 
|  | for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) { | 
|  | const int mb_index = mb_index_base + mb_col; | 
|  | const uint32_t offset_col = mb_col << 4; | 
|  | const uint8_t* mb_src = mb_src_base + offset_col; | 
|  | uint8_t* mb_dst = mb_dst_base + offset_col; | 
|  | // Check if the block is a moving object block or may cause a trailing | 
|  | // artifacts. | 
|  | if (mb_filter_decision_[mb_index] != FILTER_BLOCK || | 
|  | IsTrailingBlock(moving_edge_, mb_row, mb_col) || | 
|  | (x_density_[mb_col] * y_density_[mb_row] && | 
|  | moving_object_[mb_row * mb_cols_ + mb_col])) { | 
|  | // Copy y source. | 
|  | filter_->CopyMem16x16(mb_src, stride_y_, mb_dst, stride_y_); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void VideoDenoiser::CopyLumaOnMargin(const uint8_t* y_src, uint8_t* y_dst) { | 
|  | if ((mb_rows_ << 4) != height_) { | 
|  | const uint8_t* margin_y_src = y_src + (mb_rows_ << 4) * stride_y_; | 
|  | uint8_t* margin_y_dst = y_dst + (mb_rows_ << 4) * stride_y_; | 
|  | memcpy(margin_y_dst, margin_y_src, (height_ - (mb_rows_ << 4)) * stride_y_); | 
|  | } | 
|  | if ((mb_cols_ << 4) != width_) { | 
|  | const uint8_t* margin_y_src = y_src + (mb_cols_ << 4); | 
|  | uint8_t* margin_y_dst = y_dst + (mb_cols_ << 4); | 
|  | for (int i = 0; i < height_; ++i) { | 
|  | for (int j = mb_cols_ << 4; j < width_; ++j) { | 
|  | margin_y_dst[i * stride_y_ + j] = margin_y_src[i * stride_y_ + j]; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void VideoDenoiser::DenoiseFrame( | 
|  | const rtc::scoped_refptr<VideoFrameBuffer>& frame, | 
|  | rtc::scoped_refptr<I420Buffer>* denoised_frame, | 
|  | rtc::scoped_refptr<I420Buffer>* denoised_frame_prev, | 
|  | bool noise_estimation_enabled) { | 
|  | // If previous width and height are different from current frame's, need to | 
|  | // reallocate the buffers and no denoising for the current frame. | 
|  | if (width_ != frame->width() || height_ != frame->height()) { | 
|  | DenoiserReset(frame, denoised_frame, denoised_frame_prev); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Set buffer pointers. | 
|  | const uint8_t* y_src = frame->DataY(); | 
|  | const uint8_t* u_src = frame->DataU(); | 
|  | const uint8_t* v_src = frame->DataV(); | 
|  | uint8_t* y_dst = (*denoised_frame)->MutableDataY(); | 
|  | uint8_t* u_dst = (*denoised_frame)->MutableDataU(); | 
|  | uint8_t* v_dst = (*denoised_frame)->MutableDataV(); | 
|  | uint8_t* y_dst_prev = (*denoised_frame_prev)->MutableDataY(); | 
|  | memset(x_density_.get(), 0, mb_cols_); | 
|  | memset(y_density_.get(), 0, mb_rows_); | 
|  | memset(moving_object_.get(), 1, mb_cols_ * mb_rows_); | 
|  |  | 
|  | uint8_t noise_level = noise_estimation_enabled ? ne_->GetNoiseLevel() : 0; | 
|  | int thr_var_base = 16 * 16 * 2; | 
|  | // Loop over blocks to accumulate/extract noise level and update x/y_density | 
|  | // factors for moving object detection. | 
|  | for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) { | 
|  | const int mb_index_base = mb_row * mb_cols_; | 
|  | const int offset_base = (mb_row << 4) * stride_y_; | 
|  | const uint8_t* mb_src_base = y_src + offset_base; | 
|  | uint8_t* mb_dst_base = y_dst + offset_base; | 
|  | uint8_t* mb_dst_prev_base = y_dst_prev + offset_base; | 
|  | for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) { | 
|  | const int mb_index = mb_index_base + mb_col; | 
|  | const bool ne_enable = (mb_index % NOISE_SUBSAMPLE_INTERVAL == 0); | 
|  | const int pos_factor = PositionCheck(mb_row, mb_col, noise_level); | 
|  | const uint32_t thr_var_adp = thr_var_base * pos_factor; | 
|  | const uint32_t offset_col = mb_col << 4; | 
|  | const uint8_t* mb_src = mb_src_base + offset_col; | 
|  | uint8_t* mb_dst = mb_dst_base + offset_col; | 
|  | uint8_t* mb_dst_prev = mb_dst_prev_base + offset_col; | 
|  |  | 
|  | // TODO(jackychen): Need SSE2/NEON opt. | 
|  | int luma = 0; | 
|  | if (ne_enable) { | 
|  | for (int i = 4; i < 12; ++i) { | 
|  | for (int j = 4; j < 12; ++j) { | 
|  | luma += mb_src[i * stride_y_ + j]; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Get the filtered block and filter_decision. | 
|  | mb_filter_decision_[mb_index] = | 
|  | filter_->MbDenoise(mb_dst_prev, stride_y_, mb_dst, stride_y_, mb_src, | 
|  | stride_y_, 0, noise_level); | 
|  |  | 
|  | // If filter decision is FILTER_BLOCK, no need to check moving edge. | 
|  | // It is unlikely for a moving edge block to be filtered in current | 
|  | // setting. | 
|  | if (mb_filter_decision_[mb_index] == FILTER_BLOCK) { | 
|  | uint32_t sse_t = 0; | 
|  | if (ne_enable) { | 
|  | // The variance used in noise estimation is based on the src block in | 
|  | // time t (mb_src) and filtered block in time t-1 (mb_dist_prev). | 
|  | uint32_t noise_var = filter_->Variance16x8(mb_dst_prev, stride_y_, | 
|  | mb_src, stride_y_, &sse_t); | 
|  | ne_->GetNoise(mb_index, noise_var, luma); | 
|  | } | 
|  | moving_edge_[mb_index] = 0;  // Not a moving edge block. | 
|  | } else { | 
|  | uint32_t sse_t = 0; | 
|  | // The variance used in MOD is based on the filtered blocks in time | 
|  | // T (mb_dst) and T-1 (mb_dst_prev). | 
|  | uint32_t noise_var = filter_->Variance16x8(mb_dst_prev, stride_y_, | 
|  | mb_dst, stride_y_, &sse_t); | 
|  | if (noise_var > thr_var_adp) {  // Moving edge checking. | 
|  | if (ne_enable) { | 
|  | ne_->ResetConsecLowVar(mb_index); | 
|  | } | 
|  | moving_edge_[mb_index] = 1;  // Mark as moving edge block. | 
|  | x_density_[mb_col] += (pos_factor < 3); | 
|  | y_density_[mb_row] += (pos_factor < 3); | 
|  | } else { | 
|  | moving_edge_[mb_index] = 0; | 
|  | if (ne_enable) { | 
|  | // The variance used in noise estimation is based on the src block | 
|  | // in time t (mb_src) and filtered block in time t-1 (mb_dist_prev). | 
|  | uint32_t noise_var = filter_->Variance16x8( | 
|  | mb_dst_prev, stride_y_, mb_src, stride_y_, &sse_t); | 
|  | ne_->GetNoise(mb_index, noise_var, luma); | 
|  | } | 
|  | } | 
|  | } | 
|  | }  // End of for loop | 
|  | }    // End of for loop | 
|  |  | 
|  | ReduceFalseDetection(moving_edge_, &moving_object_, noise_level); | 
|  |  | 
|  | CopySrcOnMOB(y_src, y_dst); | 
|  |  | 
|  | // When frame width/height not divisible by 16, copy the margin to | 
|  | // denoised_frame. | 
|  | if ((mb_rows_ << 4) != height_ || (mb_cols_ << 4) != width_) | 
|  | CopyLumaOnMargin(y_src, y_dst); | 
|  |  | 
|  | // TODO(jackychen): Need SSE2/NEON opt. | 
|  | // Copy u/v planes. | 
|  | memcpy(u_dst, u_src, (height_ >> 1) * stride_u_); | 
|  | memcpy(v_dst, v_src, (height_ >> 1) * stride_v_); | 
|  |  | 
|  | #if DISPLAY || DISPLAYNEON | 
|  | // Show rectangular region | 
|  | ShowRect(filter_, moving_edge_, moving_object_, x_density_, y_density_, u_src, | 
|  | v_src, u_dst, v_dst, mb_rows_, mb_cols_, stride_u_, stride_v_); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | }  // namespace webrtc |