/work/vvenc/source/Lib/EncoderLib/CuTree.cpp
Line | Count | Source |
1 | | /* ----------------------------------------------------------------------------- |
2 | | The copyright in this software is being made available under the Clear BSD |
3 | | License, included below. No patent rights, trademark rights and/or |
4 | | other Intellectual Property Rights other than the copyrights concerning |
5 | | the Software are granted under this license. |
6 | | |
7 | | The Clear BSD License |
8 | | |
9 | | Copyright (c) 2019-2026, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. & The VVenC Authors. |
10 | | All rights reserved. |
11 | | |
12 | | Redistribution and use in source and binary forms, with or without modification, |
13 | | are permitted (subject to the limitations in the disclaimer below) provided that |
14 | | the following conditions are met: |
15 | | |
16 | | * Redistributions of source code must retain the above copyright notice, |
17 | | this list of conditions and the following disclaimer. |
18 | | |
19 | | * Redistributions in binary form must reproduce the above copyright |
20 | | notice, this list of conditions and the following disclaimer in the |
21 | | documentation and/or other materials provided with the distribution. |
22 | | |
23 | | * Neither the name of the copyright holder nor the names of its |
24 | | contributors may be used to endorse or promote products derived from this |
25 | | software without specific prior written permission. |
26 | | |
27 | | NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY |
28 | | THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
29 | | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
30 | | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A |
31 | | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR |
32 | | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
33 | | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
34 | | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
35 | | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER |
36 | | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
37 | | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
38 | | POSSIBILITY OF SUCH DAMAGE. |
39 | | |
40 | | |
41 | | ------------------------------------------------------------------------------------------- */ |
42 | | |
43 | | |
44 | | /** \file CuTree.cpp |
45 | | \brief look-ahead stage for CU-tree adaptive quantization |
46 | | |
47 | | Implements the temporal dependency model (TPL) of |
48 | | J. Han, P. Wilkins, Y. Xu, J. Bankoski, "A quantitative approach to the temporal dependency in video coding", |
49 | | arXiv:2108.11586, 2021 (https://arxiv.org/abs/2108.11586). The equation numbers in the comments refer to it. |
50 | | */ |
51 | | |
52 | | #include "CuTree.h" |
53 | | #include "EncSlice.h" |
54 | | #include "CommonLib/Picture.h" |
55 | | #include "Utilities/NoMallocThreadPool.h" |
56 | | |
57 | | #include <algorithm> |
58 | | #include <atomic> |
59 | | #include <cmath> |
60 | | #include <condition_variable> |
61 | | #include <memory> |
62 | | #include <mutex> |
63 | | #include <vector> |
64 | | |
65 | | //! \ingroup EncoderLib |
66 | | //! \{ |
67 | | |
68 | | namespace vvenc { |
69 | | |
70 | | static constexpr int MAX_BLK_SIZE = tplBlkSide( NUM_TPL_BLK_SIZES - 1 ); // largest analysis block, half resolution |
71 | | static constexpr int MV_PREC = 16; // motion vectors are in 1/16 sample, as in MCTF |
72 | | static constexpr int NUM_LEVELS = 3; // half, quarter and eighth resolution |
73 | | static constexpr int NO_REF = MAX_INT; |
74 | | // Border of the half-resolution pictures. Motion search and prediction clamp reads that go beyond it. |
75 | | static constexpr int LOWRES_PAD = 32; |
76 | | |
77 | | enum CuTreePred : uint8_t |
78 | | { |
79 | | CTP_INTRA = 0, |
80 | | CTP_L0 = 1, |
81 | | CTP_L1 = 2, |
82 | | CTP_BI = 3 |
83 | | }; |
84 | | |
85 | | // Analysis data of one picture. Distortions are squared errors of full-resolution samples. |
86 | | struct CuTree::Frame |
87 | | { |
88 | | int poc = 0; |
89 | | int codingNum = 0; |
90 | | int qp = 0; // the picture's QP from the temporal-layer cascade |
91 | | bool isIntra = false; |
92 | | bool analysed = false; |
93 | | bool hasImages = true; // lowRes and recon are kept while other pictures can reference them |
94 | | int refPoc[2] = { NO_REF, NO_REF }; |
95 | | int picQpOffset = 0; // BIM 2's picture-level QP offset, see xSetPicQpOffsets |
96 | | PelStorage lowRes[NUM_LEVELS]; |
97 | | PelStorage recon; // half-resolution reconstruction at the picture's QP |
98 | | Array2D<MotionVector> mvs[2]; // half-resolution motion towards refPoc[0] and refPoc[1] |
99 | | std::vector<uint8_t> predMode; |
100 | | std::vector<float> distRec; // distortion of the coded block, predicted from reconstructed references |
101 | | std::vector<float> distSrc[2]; // the same with reference l replaced by its original |
102 | | std::vector<float> deltaDist[2]; // extra distortion and rate caused by the coding error of reference l |
103 | | std::vector<float> deltaRate[2]; |
104 | | std::vector<double> propDist; // distortion and rate that later pictures owe to each block |
105 | | std::vector<double> propRate; |
106 | | }; |
107 | | |
108 | | // ==================================================================================================================== |
109 | | // Block helpers |
110 | | // ==================================================================================================================== |
111 | | |
112 | | // The encoder's own (SIMD) Hadamard SATD of the difference between two analysis blocks. |
113 | | struct BlockSatd |
114 | | { |
115 | | FpDistFunc func; |
116 | | int bitDepth; |
117 | | int size; |
118 | | |
119 | | int operator()( const Pel* org, ptrdiff_t orgStride, const Pel* pred, ptrdiff_t predStride ) const |
120 | 0 | { |
121 | 0 | const DistParam dp( CPelBuf( org, ( int ) orgStride, size, size ), CPelBuf( pred, ( int ) predStride, size, size ), |
122 | 0 | func, bitDepth, 0, COMP_Y ); |
123 | 0 | return ( int ) func( dp ); |
124 | 0 | } |
125 | | }; |
126 | | |
127 | | template<int N> |
128 | | void tplPredBlockCore( const Pel* src, ptrdiff_t srcStride, int fx, int fy, Pel* dst ) |
129 | 0 | { |
130 | 0 | for( int j = 0; j < N; j++ ) |
131 | 0 | { |
132 | 0 | const Pel* row0 = src + j * srcStride; |
133 | 0 | const Pel* row1 = row0 + srcStride; |
134 | 0 | for( int i = 0; i < N; i++ ) |
135 | 0 | { |
136 | 0 | const int top = row0[i] * ( MV_PREC - fx ) + row0[i + 1] * fx; |
137 | 0 | const int bottom = row1[i] * ( MV_PREC - fx ) + row1[i + 1] * fx; |
138 | 0 | dst[j * N + i] = Pel( ( top * ( MV_PREC - fy ) + bottom * fy + 128 ) >> 8 ); |
139 | 0 | } |
140 | 0 | } |
141 | 0 | } Unexecuted instantiation: void vvenc::tplPredBlockCore<8>(short const*, long, int, int, short*) Unexecuted instantiation: void vvenc::tplPredBlockCore<16>(short const*, long, int, int, short*) |
142 | | |
143 | | template void tplPredBlockCore<8> ( const Pel* src, ptrdiff_t srcStride, int fx, int fy, Pel* dst ); |
144 | | template void tplPredBlockCore<16>( const Pel* src, ptrdiff_t srcStride, int fx, int fy, Pel* dst ); |
145 | | |
146 | | void tplDownsampleRowCore( const Pel* src, ptrdiff_t srcStride, Pel* dst, int dstWidth ) |
147 | 0 | { |
148 | 0 | const Pel* below = src + srcStride; |
149 | 0 | for( int x = 0; x < dstWidth; x++ ) |
150 | 0 | { |
151 | 0 | dst[x] = Pel( ( src[2 * x] + src[2 * x + 1] + below[2 * x] + below[2 * x + 1] + 2 ) >> 2 ); |
152 | 0 | } |
153 | 0 | } |
154 | | |
155 | | // Bilinear prediction of the n x n block at (x, y) displaced by mv (1/16 sample). Positions outside the padded |
156 | | // reference repeat its edge, as the motion search does. |
157 | | static void predictBlock( TplPredBlockFn predBlock, int n, const CPelBuf& ref, int x, int y, const MotionVector& mv, Pel* dst ) |
158 | 0 | { |
159 | 0 | const int fx = mv.x & ( MV_PREC - 1 ); |
160 | 0 | const int fy = mv.y & ( MV_PREC - 1 ); |
161 | 0 | const int x0 = x + ( mv.x >> 4 ); |
162 | 0 | const int y0 = y + ( mv.y >> 4 ); |
163 | 0 | const int minX = -LOWRES_PAD, maxX = ( int ) ref.width + LOWRES_PAD - 1; |
164 | 0 | const int minY = -LOWRES_PAD, maxY = ( int ) ref.height + LOWRES_PAD - 1; |
165 | |
|
166 | 0 | if( x0 >= minX && x0 + n <= maxX && y0 >= minY && y0 + n <= maxY ) |
167 | 0 | { |
168 | 0 | predBlock( ref.buf + y0 * ref.stride + x0, ref.stride, fx, fy, dst ); |
169 | 0 | return; |
170 | 0 | } |
171 | | |
172 | 0 | for( int j = 0; j < n; j++ ) |
173 | 0 | { |
174 | 0 | const Pel* row0 = ref.buf + Clip3( minY, maxY, y0 + j ) * ref.stride; |
175 | 0 | const Pel* row1 = ref.buf + Clip3( minY, maxY, y0 + j + 1 ) * ref.stride; |
176 | 0 | for( int i = 0; i < n; i++ ) |
177 | 0 | { |
178 | 0 | const int c0 = Clip3( minX, maxX, x0 + i ); |
179 | 0 | const int c1 = Clip3( minX, maxX, x0 + i + 1 ); |
180 | 0 | const int top = row0[c0] * ( MV_PREC - fx ) + row0[c1] * fx; |
181 | 0 | const int bottom = row1[c0] * ( MV_PREC - fx ) + row1[c1] * fx; |
182 | 0 | dst[j * n + i] = Pel( ( top * ( MV_PREC - fy ) + bottom * fy + 128 ) >> 8 ); |
183 | 0 | } |
184 | 0 | } |
185 | 0 | } |
186 | | |
187 | | // The best of DC, vertical, horizontal and planar prediction of an N x N block from the neighbouring source samples, |
188 | | // by SATD. The size is a template parameter so that the loops over the block keep a constant trip count. |
189 | | template<int N> |
190 | | static void predictIntra( const CPelBuf& pic, int x, int y, int bitDepth, const BlockSatd& satd, Pel* bestPred ) |
191 | 0 | { |
192 | 0 | static constexpr int n = N, log2n = N == 16 ? 4 : 3; |
193 | 0 | const Pel* org = pic.buf + y * pic.stride + x; |
194 | 0 | const bool hasTop = y > 0; |
195 | 0 | const bool hasLeft = x > 0; |
196 | 0 | Pel top[n], left[n]; |
197 | 0 | Pel pred[n * n]; |
198 | |
|
199 | 0 | int sum = 0; |
200 | 0 | for( int i = 0; i < n; i++ ) |
201 | 0 | { |
202 | 0 | top[i] = hasTop ? org[i - pic.stride] : 0; |
203 | 0 | left[i] = hasLeft ? org[i * pic.stride - 1] : 0; |
204 | 0 | sum += ( hasTop ? top[i] : 0 ) + ( hasLeft ? left[i] : 0 ); |
205 | 0 | } |
206 | 0 | const int num = ( hasTop ? n : 0 ) + ( hasLeft ? n : 0 ); |
207 | |
|
208 | 0 | int best = MAX_INT; |
209 | 0 | auto keepBest = [&]() |
210 | 0 | { |
211 | 0 | const int cost = satd( org, pic.stride, pred, n ); |
212 | 0 | if( cost < best ) |
213 | 0 | { |
214 | 0 | best = cost; |
215 | 0 | std::copy_n( pred, n * n, bestPred ); |
216 | 0 | } |
217 | 0 | }; Unexecuted instantiation: CuTree.cpp:vvenc::predictIntra<16>(vvenc::AreaBuf<short const> const&, int, int, int, vvenc::BlockSatd const&, short*)::{lambda()#1}::operator()() constUnexecuted instantiation: CuTree.cpp:vvenc::predictIntra<8>(vvenc::AreaBuf<short const> const&, int, int, int, vvenc::BlockSatd const&, short*)::{lambda()#1}::operator()() const |
218 | |
|
219 | 0 | const Pel dc = num ? Pel( ( sum + ( num >> 1 ) ) / num ) : Pel( 1 << ( bitDepth - 1 ) ); |
220 | 0 | std::fill_n( pred, n * n, dc ); |
221 | 0 | keepBest(); |
222 | |
|
223 | 0 | if( hasTop ) |
224 | 0 | { |
225 | 0 | for( int j = 0; j < n; j++ ) std::copy_n( top, n, pred + j * n ); |
226 | 0 | keepBest(); |
227 | 0 | } |
228 | 0 | if( hasLeft ) |
229 | 0 | { |
230 | 0 | for( int j = 0; j < n; j++ ) std::fill_n( pred + j * n, n, left[j] ); |
231 | 0 | keepBest(); |
232 | 0 | } |
233 | 0 | if( hasTop && hasLeft ) |
234 | 0 | { |
235 | 0 | for( int j = 0; j < n; j++ ) |
236 | 0 | { |
237 | 0 | for( int i = 0; i < n; i++ ) |
238 | 0 | { |
239 | 0 | const int hor = ( n - 1 - i ) * left[j] + ( i + 1 ) * top[n - 1]; |
240 | 0 | const int ver = ( n - 1 - j ) * top[i] + ( j + 1 ) * left[n - 1]; |
241 | 0 | pred[j * n + i] = Pel( ( hor + ver + n ) >> ( log2n + 1 ) ); |
242 | 0 | } |
243 | 0 | } |
244 | 0 | keepBest(); |
245 | 0 | } |
246 | 0 | } Unexecuted instantiation: CuTree.cpp:void vvenc::predictIntra<16>(vvenc::AreaBuf<short const> const&, int, int, int, vvenc::BlockSatd const&, short*) Unexecuted instantiation: CuTree.cpp:void vvenc::predictIntra<8>(vvenc::AreaBuf<short const> const&, int, int, int, vvenc::BlockSatd const&, short*) |
247 | | |
248 | | // 8-point Hadamard butterflies on v[0], v[s], ..., v[7 s]. |
249 | | static void hadamard8( int32_t* v, int s ) |
250 | 0 | { |
251 | 0 | const int32_t a0 = v[0] + v[4 * s], a1 = v[s] + v[5 * s], a2 = v[2 * s] + v[6 * s], a3 = v[3 * s] + v[7 * s]; |
252 | 0 | const int32_t a4 = v[0] - v[4 * s], a5 = v[s] - v[5 * s], a6 = v[2 * s] - v[6 * s], a7 = v[3 * s] - v[7 * s]; |
253 | 0 | const int32_t b0 = a0 + a2, b1 = a1 + a3, b2 = a0 - a2, b3 = a1 - a3; |
254 | 0 | const int32_t b4 = a4 + a6, b5 = a5 + a7, b6 = a4 - a6, b7 = a5 - a7; |
255 | 0 | v[0] = b0 + b1; v[s] = b0 - b1; v[2 * s] = b2 + b3; v[3 * s] = b2 - b3; |
256 | 0 | v[4 * s] = b4 + b5; v[5 * s] = b4 - b5; v[6 * s] = b6 + b7; v[7 * s] = b6 - b7; |
257 | 0 | } |
258 | | |
259 | | // 16-point Hadamard butterflies: one stage across the two halves, then the 8-point transform of each half. |
260 | | static void hadamard16( int32_t* v, int s ) |
261 | 0 | { |
262 | 0 | for( int i = 0; i < 8; i++ ) |
263 | 0 | { |
264 | 0 | const int32_t a = v[i * s], b = v[( i + 8 ) * s]; |
265 | 0 | v[i * s] = a + b; |
266 | 0 | v[( i + 8 ) * s] = a - b; |
267 | 0 | } |
268 | 0 | hadamard8( v, s ); |
269 | 0 | hadamard8( v + 8 * s, s ); |
270 | 0 | } |
271 | | |
272 | | // The Hadamard butterflies of one row or column of an N x N analysis block. |
273 | | template<int N> |
274 | | static void hadamardBlk( int32_t* v, int s ) |
275 | 0 | { |
276 | 0 | if( N == 16 ) hadamard16( v, s ); |
277 | 0 | else hadamard8 ( v, s ); |
278 | 0 | } Unexecuted instantiation: CuTree.cpp:void vvenc::hadamardBlk<8>(int*, int) Unexecuted instantiation: CuTree.cpp:void vvenc::hadamardBlk<16>(int*, int) |
279 | | |
280 | | // The scalar block coder. Integer Hadamard butterflies are exact in any order, so the SIMD versions give the |
281 | | // same numbers. |
282 | | template<int N> |
283 | | void tplCodeBlockCore( const Pel* org, ptrdiff_t orgStride, const Pel* pred, Pel* recon, ptrdiff_t reconStride, |
284 | | const TplQuant& quant, int maxVal, int64_t& dist, int& bits ) |
285 | 0 | { |
286 | 0 | static constexpr int LOG2N = N == 16 ? 4 : 3; |
287 | 0 | int32_t c[N * N]; |
288 | 0 | for( int y = 0; y < N; y++ ) |
289 | 0 | { |
290 | 0 | for( int x = 0; x < N; x++ ) |
291 | 0 | { |
292 | 0 | c[y * N + x] = org[y * orgStride + x] - pred[y * N + x]; |
293 | 0 | } |
294 | 0 | } |
295 | 0 | for( int y = 0; y < N; y++ ) hadamardBlk<N>( c + y * N, 1 ); |
296 | 0 | for( int x = 0; x < N; x++ ) hadamardBlk<N>( c + x, N ); |
297 | |
|
298 | 0 | dist = 0; |
299 | 0 | bits = 0; |
300 | 0 | for( int i = 0; i < N * N; i++ ) |
301 | 0 | { |
302 | 0 | const int32_t absCoeff = abs( c[i] ); |
303 | 0 | const int32_t level = ( absCoeff * quant.scale + quant.round ) >> 16; |
304 | 0 | const int32_t absDeq = ( level * quant.deqStep + 8 ) >> 4; |
305 | 0 | const int64_t err = absCoeff - absDeq; |
306 | 0 | dist += err * err; |
307 | 0 | bits += level > 0 ? 2 * floorLog2( level ) + 3 : 0; |
308 | 0 | c[i] = c[i] < 0 ? -absDeq : absDeq; |
309 | 0 | } |
310 | |
|
311 | 0 | if( recon ) |
312 | 0 | { |
313 | | // the unnormalised transform applied twice scales by N^2 |
314 | 0 | for( int y = 0; y < N; y++ ) hadamardBlk<N>( c + y * N, 1 ); |
315 | 0 | for( int x = 0; x < N; x++ ) hadamardBlk<N>( c + x, N ); |
316 | 0 | for( int y = 0; y < N; y++ ) |
317 | 0 | { |
318 | 0 | for( int x = 0; x < N; x++ ) |
319 | 0 | { |
320 | 0 | const int32_t res = ( c[y * N + x] + ( 1 << ( 2 * LOG2N - 1 ) ) ) >> ( 2 * LOG2N ); |
321 | 0 | recon[y * reconStride + x] = Pel( Clip3( 0, maxVal, pred[y * N + x] + res ) ); |
322 | 0 | } |
323 | 0 | } |
324 | 0 | } |
325 | 0 | } Unexecuted instantiation: void vvenc::tplCodeBlockCore<8>(short const*, long, short const*, short*, long, vvenc::TplQuant const&, int, long&, int&) Unexecuted instantiation: void vvenc::tplCodeBlockCore<16>(short const*, long, short const*, short*, long, vvenc::TplQuant const&, int, long&, int&) |
326 | | |
327 | | template void tplCodeBlockCore<8> ( const Pel* org, ptrdiff_t orgStride, const Pel* pred, Pel* recon, ptrdiff_t reconStride, |
328 | | const TplQuant& quant, int maxVal, int64_t& dist, int& bits ); |
329 | | template void tplCodeBlockCore<16>( const Pel* org, ptrdiff_t orgStride, const Pel* pred, Pel* recon, ptrdiff_t reconStride, |
330 | | const TplQuant& quant, int maxVal, int64_t& dist, int& bits ); |
331 | | |
332 | | // Codes a half-resolution residual block the way TPL does with a Walsh-Hadamard transform. Distortion and rate are |
333 | | // scaled to the full-resolution block it stands for, twice as wide and high: its low frequencies have twice the |
334 | | // coefficient amplitude, so the step is halved and the squared error counts four times. |
335 | | struct BlockCoder |
336 | | { |
337 | | TplCodeBlockFn codeBlock; |
338 | | int size; |
339 | | TplQuant quant; |
340 | | |
341 | | // step of the orthonormal half-resolution coefficients, dead-zone rounding offset below one half |
342 | 0 | BlockCoder( TplCodeBlockFn fn, int n, double step, double deadZone ) : codeBlock( fn ), size( n ) |
343 | 0 | { |
344 | 0 | const double unnormStep = step * n; |
345 | 0 | quant.scale = int32_t( std::lround( 65536.0 / unnormStep ) ); |
346 | 0 | quant.round = int32_t( std::lround( 65536.0 * deadZone ) ); |
347 | 0 | quant.deqStep = int32_t( std::lround( 16.0 * unnormStep ) ); |
348 | 0 | } |
349 | | |
350 | | struct Result |
351 | | { |
352 | | double dist; // squared error, full-resolution scale |
353 | | double rate; // estimated bits |
354 | | }; |
355 | | |
356 | | Result code( const Pel* org, ptrdiff_t orgStride, const Pel* pred, Pel* recon, ptrdiff_t reconStride, int maxVal ) const |
357 | 0 | { |
358 | 0 | int64_t dist = 0; |
359 | 0 | int bits = 0; |
360 | 0 | codeBlock( org, orgStride, pred, recon, reconStride, quant, maxVal, dist, bits ); |
361 | | // unnormalised domain: size^2 times the sample error; full resolution: 4 times the half-resolution error |
362 | 0 | return { double( dist ) / ( size * size / 4 ), double( bits ) }; |
363 | 0 | } |
364 | | }; |
365 | | |
366 | | // Quantization step and Lagrange multiplier of a QP, in sample units of the internal bit depth. |
367 | | static double qpToStep( int qp, int bitDepth ) |
368 | 0 | { |
369 | 0 | return std::pow( 2.0, ( qp - 4 ) / 6.0 ) * double( 1 << ( bitDepth - 8 ) ); |
370 | 0 | } |
371 | | |
372 | | static double qpToLambda( int qp, int bitDepth ) |
373 | 0 | { |
374 | 0 | return 0.57 * std::pow( 2.0, ( qp - 12 ) / 3.0 ) * double( 1 << ( 2 * ( bitDepth - 8 ) ) ); |
375 | 0 | } |
376 | | |
377 | | // ==================================================================================================================== |
378 | | // Stage |
379 | | // ==================================================================================================================== |
380 | | |
381 | | CuTree::CuTree() |
382 | 0 | : m_codeBlock { tplCodeBlockCore<8>, tplCodeBlockCore<16> } |
383 | 0 | , m_predBlock { tplPredBlockCore<8>, tplPredBlockCore<16> } |
384 | 0 | , m_downsampleRow( tplDownsampleRowCore ) |
385 | 0 | , m_encCfg ( nullptr ) |
386 | 0 | , m_threadPool ( nullptr ) |
387 | 0 | , m_motionSearch ( true ) |
388 | 0 | , m_hadFunc ( nullptr ) |
389 | 0 | , m_lookAheadPics( 0 ) |
390 | 0 | , m_blkSizeIdx ( TPL_BLK_8 ) |
391 | 0 | , m_blkSize ( 8 ) |
392 | 0 | , m_blkWidth ( 0 ) |
393 | 0 | , m_blkHeight ( 0 ) |
394 | 0 | , m_qpBDOffset ( 0 ) |
395 | 0 | , m_lambdaLeaf ( 0.0 ) |
396 | 0 | { |
397 | 0 | } |
398 | | |
399 | | CuTree::~CuTree() |
400 | 0 | { |
401 | 0 | } |
402 | | |
403 | | int CuTree::getNumLookAheadPics( const VVEncCfg& encCfg ) |
404 | 0 | { |
405 | | // the key picture of a GOP is referenced by the whole next GOP |
406 | 0 | return encCfg.m_GOPSize; |
407 | 0 | } |
408 | | |
409 | | void CuTree::init( const VVEncCfg& encCfg, NoMallocThreadPool* threadPool ) |
410 | 0 | { |
411 | 0 | m_encCfg = &encCfg; |
412 | 0 | m_threadPool = threadPool; |
413 | 0 | m_lookAheadPics = getNumLookAheadPics( encCfg ); |
414 | | // the configured size is that of the full-resolution block, 16 or 32 |
415 | 0 | m_blkSizeIdx = encCfg.m_cuTreeBlockSize == 32 ? TPL_BLK_16 : TPL_BLK_8; |
416 | 0 | m_blkSize = tplBlkSide( m_blkSizeIdx ); |
417 | 0 | m_blkWidth = ( encCfg.m_PadSourceWidth / 2 ) / m_blkSize; |
418 | 0 | m_blkHeight = ( encCfg.m_PadSourceHeight / 2 ) / m_blkSize; |
419 | 0 | m_qpBDOffset = 6 * ( encCfg.m_internalBitDepth[CH_L] - 8 ); |
420 | 0 | m_motionSearch.init( encCfg, true, threadPool ); |
421 | | #if defined( TARGET_SIMD_X86 ) && ENABLE_SIMD_OPT |
422 | | initCuTree_X86(); |
423 | | #endif |
424 | 0 | m_rdCost.create(); |
425 | | // the Hadamard SATD of the block size; setDistParam picks it by the block width |
426 | 0 | const CPelBuf blk( nullptr, m_blkSize, m_blkSize ); |
427 | 0 | m_hadFunc = m_rdCost.setDistParam( blk, blk, encCfg.m_internalBitDepth[CH_L], DF_HAD ).distFunc; |
428 | | |
429 | | // TPL weighs rate with the Lagrange multiplier of the unreferenced pictures, the top temporal layer. |
430 | 0 | GOPEntry topEntry; |
431 | 0 | topEntry.setDefaultGOPEntry(); |
432 | 0 | topEntry.copyFromGopCfg( encCfg.m_GOPList[0] ); |
433 | 0 | for( int i = 1; i < encCfg.m_GOPSize && i < VVENC_MAX_GOP; i++ ) |
434 | 0 | { |
435 | 0 | if( encCfg.m_GOPList[i].m_temporalId > topEntry.m_temporalId ) topEntry.copyFromGopCfg( encCfg.m_GOPList[i] ); |
436 | 0 | } |
437 | 0 | topEntry.m_vtl = std::min( 5, encCfg.m_maxTLayer ); |
438 | 0 | const int leafQp = Clip3( -m_qpBDOffset, MAX_QP, EncSlice::getCascadeQP( encCfg, topEntry, false, encCfg.m_QP, m_qpBDOffset ) ); |
439 | 0 | m_lambdaLeaf = qpToLambda( leafQp, encCfg.m_internalBitDepth[CH_L] ); |
440 | 0 | } |
441 | | |
442 | | // The rows of one xForEachRow call, shared by the stage thread and the helper tasks. A helper that starts after |
443 | | // the call has returned still holds them and finds no row left. |
444 | | struct CuTreeRows |
445 | | { |
446 | | std::atomic<int> next{ 0 }; |
447 | | int done = 0; // rows finished, guarded by mutex |
448 | | int numRows = 0; |
449 | | const std::function<void( int )>* fn = nullptr; |
450 | | std::mutex mutex; |
451 | | std::condition_variable allDone; |
452 | | |
453 | | void work() |
454 | 0 | { |
455 | 0 | for( int row = next++; row < numRows; row = next++ ) |
456 | 0 | { |
457 | 0 | ( *fn )( row ); |
458 | 0 | std::lock_guard<std::mutex> lock( mutex ); |
459 | 0 | if( ++done == numRows ) |
460 | 0 | { |
461 | 0 | allDone.notify_one(); |
462 | 0 | } |
463 | 0 | } |
464 | 0 | } |
465 | | |
466 | | void waitAllDone() |
467 | 0 | { |
468 | 0 | std::unique_lock<std::mutex> lock( mutex ); |
469 | 0 | allDone.wait( lock, [this] { return done == numRows; } ); |
470 | 0 | } |
471 | | }; |
472 | | |
473 | | // Runs fn( row ) for rows 0 .. numRows - 1. The stage thread works through the rows itself and workers of the |
474 | | // thread pool join in when they are free. Waiting for row tasks instead stalled the stage thread, and with it the |
475 | | // stages before the encoder, behind the encoder's queued CTU tasks. Rows write disjoint data, so the result does |
476 | | // not depend on which thread runs which row. |
477 | | void CuTree::xForEachRow( int numRows, const std::function<void( int )>& fn ) const |
478 | 0 | { |
479 | 0 | auto rows = std::make_shared<CuTreeRows>(); |
480 | 0 | rows->numRows = numRows; |
481 | 0 | rows->fn = &fn; |
482 | |
|
483 | 0 | const int numHelpers = m_threadPool ? std::min( m_threadPool->numThreads(), numRows - 1 ) : 0; |
484 | 0 | for( int i = 0; i < numHelpers; i++ ) |
485 | 0 | { |
486 | 0 | m_threadPool->addBarrierTask( []( int, void* param ) |
487 | 0 | { |
488 | 0 | std::unique_ptr<std::shared_ptr<CuTreeRows>> held( static_cast<std::shared_ptr<CuTreeRows>*>( param ) ); |
489 | 0 | ( *held )->work(); |
490 | 0 | return true; |
491 | 0 | }, new std::shared_ptr<CuTreeRows>( rows ) ); |
492 | 0 | } |
493 | 0 | rows->work(); |
494 | 0 | rows->waitAllDone(); // for the rows helpers took and still run |
495 | 0 | } |
496 | | |
497 | | // Halves the luma resolution with 2x2 averages and pads the result. |
498 | | void CuTree::xDownsample( const PelStorage& src, PelStorage& dst ) const |
499 | 0 | { |
500 | 0 | const CPelBuf in = src.Y(); |
501 | 0 | const int width = in.width / 2; |
502 | 0 | const int height = in.height / 2; |
503 | 0 | const int rowsPerTask = 16; |
504 | 0 | dst.create( CHROMA_400, Area( 0, 0, width, height ), 0, LOWRES_PAD ); |
505 | 0 | const PelBuf out = dst.Y(); |
506 | |
|
507 | 0 | xForEachRow( ( height + rowsPerTask - 1 ) / rowsPerTask, [&]( int task ) |
508 | 0 | { |
509 | 0 | for( int y = task * rowsPerTask; y < std::min( height, ( task + 1 ) * rowsPerTask ); y++ ) |
510 | 0 | { |
511 | 0 | m_downsampleRow( in.buf + 2 * y * in.stride, in.stride, out.buf + y * out.stride, width ); |
512 | 0 | } |
513 | 0 | } ); |
514 | 0 | dst.extendBorderPel( LOWRES_PAD, LOWRES_PAD ); |
515 | 0 | } |
516 | | |
517 | | void CuTree::initPicture( Picture* pic ) |
518 | 0 | { |
519 | 0 | std::unique_ptr<Frame> frame( new Frame ); |
520 | 0 | frame->poc = pic->poc; |
521 | |
|
522 | 0 | xDownsample( pic->getOrigBuffer(), frame->lowRes[0] ); |
523 | 0 | for( int level = 1; level < NUM_LEVELS; level++ ) |
524 | 0 | { |
525 | 0 | xDownsample( frame->lowRes[level - 1], frame->lowRes[level] ); |
526 | 0 | } |
527 | |
|
528 | 0 | m_frames[pic->poc] = std::move( frame ); |
529 | 0 | } |
530 | | |
531 | | void CuTree::processPictures( const PicList& picList, AccessUnitList& auList, PicList& doneList, PicList& freeList ) |
532 | 0 | { |
533 | 0 | int batchEndPoc = 0; |
534 | 0 | if( picList.empty() || ! xIsBatchReady( picList, batchEndPoc ) ) |
535 | 0 | { |
536 | 0 | return; |
537 | 0 | } |
538 | | |
539 | | // Every picture is coded from reconstructed references, so the references go first: analyse in coding order. |
540 | 0 | const bool flush = picList.back()->isFlush; |
541 | 0 | std::vector<Picture*> codingOrder( picList.begin(), picList.end() ); |
542 | 0 | std::sort( codingOrder.begin(), codingOrder.end(), []( const Picture* a, const Picture* b ) { return a->gopEntry->m_codingNum < b->gopEntry->m_codingNum; } ); |
543 | 0 | for( auto pic : codingOrder ) |
544 | 0 | { |
545 | 0 | Frame* frame = xFindFrame( pic->poc ); |
546 | 0 | if( frame->analysed ) |
547 | 0 | { |
548 | 0 | continue; |
549 | 0 | } |
550 | 0 | xAnalyse( *frame, pic, flush ); |
551 | | |
552 | | // In a hierarchical GOP the top temporal layer is never referenced, its pictures are not needed any more. |
553 | 0 | if( frame->analysed && m_encCfg->m_maxTLayer > 0 && pic->gopEntry->m_temporalId >= m_encCfg->m_maxTLayer ) |
554 | 0 | { |
555 | 0 | for( auto& level : frame->lowRes ) level.destroy(); |
556 | 0 | frame->recon.destroy(); |
557 | 0 | frame->hasImages = false; |
558 | 0 | } |
559 | 0 | } |
560 | |
|
561 | 0 | xPropagate( picList.front()->poc ); |
562 | 0 | xSetPicQpOffsets( codingOrder, batchEndPoc ); |
563 | |
|
564 | 0 | for( auto pic : picList ) |
565 | 0 | { |
566 | 0 | if( pic->poc > batchEndPoc ) |
567 | 0 | { |
568 | 0 | break; |
569 | 0 | } |
570 | 0 | xSetQpOffsets( *xFindFrame( pic->poc ), pic ); |
571 | 0 | doneList.push_back( pic ); |
572 | 0 | freeList.push_back( pic ); |
573 | 0 | } |
574 | | |
575 | | // Keep the pictures from the batch's last one on, which the next GOP references, and every picture a cached |
576 | | // later picture references. Pictures that have not arrived reference only pictures that are still cached. |
577 | 0 | int oldestPocNeeded = batchEndPoc; |
578 | 0 | for( auto itr = m_frames.upper_bound( batchEndPoc ); itr != m_frames.end(); itr++ ) |
579 | 0 | { |
580 | 0 | for( int refPoc : itr->second->refPoc ) |
581 | 0 | { |
582 | 0 | if( refPoc != NO_REF ) oldestPocNeeded = std::min( oldestPocNeeded, refPoc ); |
583 | 0 | } |
584 | 0 | } |
585 | 0 | m_frames.erase( m_frames.begin(), m_frames.lower_bound( oldestPocNeeded ) ); |
586 | 0 | } |
587 | | |
588 | | // A batch is the pictures of the GOP that comes next. It is ready when that GOP and the look-ahead after it |
589 | | // have arrived, or when the encoder is flushed. |
590 | | bool CuTree::xIsBatchReady( const PicList& picList, int& batchEndPoc ) const |
591 | 0 | { |
592 | 0 | const int gopNum = picList.front()->gopEntry->m_gopNum; |
593 | 0 | const bool flush = picList.back()->isFlush; |
594 | 0 | bool gopComplete = false; |
595 | |
|
596 | 0 | batchEndPoc = picList.front()->poc; |
597 | 0 | for( auto pic : picList ) |
598 | 0 | { |
599 | 0 | if( pic->gopEntry->m_gopNum != gopNum ) |
600 | 0 | { |
601 | 0 | gopComplete = true; |
602 | 0 | break; |
603 | 0 | } |
604 | 0 | batchEndPoc = pic->poc; |
605 | 0 | } |
606 | |
|
607 | 0 | return flush || ( gopComplete && picList.back()->poc >= batchEndPoc + m_lookAheadPics ); |
608 | 0 | } |
609 | | |
610 | | CuTree::Frame* CuTree::xFindFrame( int poc ) const |
611 | 0 | { |
612 | 0 | auto itr = m_frames.find( poc ); |
613 | 0 | return itr == m_frames.end() ? nullptr : itr->second.get(); |
614 | 0 | } |
615 | | |
616 | | // Codes every block of the picture as TPL does: picks intra, L0, L1 or bi-prediction by rate-distortion cost with |
617 | | // the original references, then codes the chosen mode with the reconstructed references and keeps the |
618 | | // reconstruction for the pictures that reference this one (Han et al., Eqs. 6 to 11). |
619 | | void CuTree::xAnalyse( Frame& frame, Picture* pic, bool flush ) |
620 | 0 | { |
621 | 0 | const GOPEntry& gopEntry = *pic->gopEntry; |
622 | 0 | const int bitDepth = m_encCfg->m_internalBitDepth[CH_L]; |
623 | 0 | const int maxVal = ( 1 << bitDepth ) - 1; |
624 | 0 | const int numBlocks = m_blkWidth * m_blkHeight; |
625 | |
|
626 | 0 | frame.codingNum = gopEntry.m_codingNum; |
627 | 0 | frame.isIntra = gopEntry.m_sliceType == 'I'; |
628 | 0 | frame.qp = Clip3( -m_qpBDOffset, MAX_QP, EncSlice::getCascadeQP( *m_encCfg, gopEntry, frame.isIntra, m_encCfg->m_QP, m_qpBDOffset ) ); |
629 | | |
630 | | // the nearest reference picture of each list, which must be reconstructed already |
631 | 0 | Frame* refs[2] = { nullptr, nullptr }; |
632 | 0 | if( ! frame.isIntra ) |
633 | 0 | { |
634 | 0 | const int lastPoc = m_frames.rbegin()->first; |
635 | 0 | for( int l = 0; l < 2; l++ ) |
636 | 0 | { |
637 | 0 | if( gopEntry.m_numRefPicsActive[l] <= 0 ) |
638 | 0 | { |
639 | 0 | continue; |
640 | 0 | } |
641 | 0 | const int refPoc = frame.poc - gopEntry.m_deltaRefPics[l][0]; |
642 | 0 | if( l == 1 && refPoc == frame.refPoc[0] ) |
643 | 0 | { |
644 | 0 | continue; |
645 | 0 | } |
646 | 0 | if( refPoc > lastPoc && ! flush ) |
647 | 0 | { |
648 | 0 | return; // the reference has not arrived yet, analyse the picture later |
649 | 0 | } |
650 | 0 | refs[l] = xFindFrame( refPoc ); |
651 | 0 | if( refs[l] && ! refs[l]->hasImages ) |
652 | 0 | { |
653 | 0 | refs[l] = nullptr; // not expected: only unreferenced pictures drop their images |
654 | 0 | } |
655 | 0 | if( refs[l] && ! refs[l]->analysed ) |
656 | 0 | { |
657 | 0 | return; // the reference is not reconstructed yet |
658 | 0 | } |
659 | 0 | if( refs[l] ) |
660 | 0 | { |
661 | 0 | frame.refPoc[l] = refPoc; |
662 | 0 | } |
663 | 0 | } |
664 | 0 | } |
665 | 0 | for( int l = 0; l < 2; l++ ) |
666 | 0 | { |
667 | 0 | if( refs[l] ) |
668 | 0 | { |
669 | 0 | xEstimateMotion( frame, *refs[l], frame.mvs[l] ); |
670 | 0 | } |
671 | 0 | } |
672 | | |
673 | | // samples outside the block grid keep their source values |
674 | 0 | const CPelBuf org = frame.lowRes[0].Y(); |
675 | 0 | frame.recon.create( CHROMA_400, Area( 0, 0, org.width, org.height ), 0, LOWRES_PAD ); |
676 | 0 | frame.recon.Y().copyFrom( org ); |
677 | 0 | frame.predMode.assign( numBlocks, CTP_INTRA ); |
678 | 0 | frame.distRec.assign( numBlocks, 0.f ); |
679 | 0 | for( int l = 0; l < 2; l++ ) |
680 | 0 | { |
681 | 0 | frame.distSrc [l].assign( numBlocks, 0.f ); |
682 | 0 | frame.deltaDist[l].assign( numBlocks, 0.f ); |
683 | 0 | frame.deltaRate[l].assign( numBlocks, 0.f ); |
684 | 0 | } |
685 | |
|
686 | 0 | const int n = m_blkSize; |
687 | 0 | const TplPredBlockFn predBlock = m_predBlock[m_blkSizeIdx]; |
688 | 0 | const BlockSatd satd = { m_hadFunc, bitDepth, n }; |
689 | 0 | const double step = qpToStep( frame.qp, bitDepth ) / 2; |
690 | 0 | const double lambda = qpToLambda( frame.qp, bitDepth ); |
691 | | // rounding offsets of HM's quantizer without RDOQ |
692 | 0 | const BlockCoder intraCoder( m_codeBlock[m_blkSizeIdx], n, step, 1.0 / 3 ); |
693 | 0 | const BlockCoder interCoder( m_codeBlock[m_blkSizeIdx], n, step, 1.0 / 6 ); |
694 | 0 | const PelBuf recon = frame.recon.Y(); |
695 | |
|
696 | 0 | xForEachRow( m_blkHeight, [&]( int by ) |
697 | 0 | { |
698 | 0 | Pel predSrc[2][MAX_BLK_SIZE * MAX_BLK_SIZE], predRec[2][MAX_BLK_SIZE * MAX_BLK_SIZE]; |
699 | 0 | Pel pred[MAX_BLK_SIZE * MAX_BLK_SIZE], predIntra[MAX_BLK_SIZE * MAX_BLK_SIZE]; |
700 | |
|
701 | 0 | for( int bx = 0; bx < m_blkWidth; bx++ ) |
702 | 0 | { |
703 | 0 | const int idx = by * m_blkWidth + bx; |
704 | 0 | const int x = bx * n, y = by * n; |
705 | 0 | const Pel* orgBlk = org.buf + y * org.stride + x; |
706 | 0 | Pel* recBlk = recon.buf + y * recon.stride + x; |
707 | 0 | auto cost = [&]( const BlockCoder::Result& r ) { return r.dist + lambda * r.rate; }; |
708 | |
|
709 | 0 | if( n == 16 ) predictIntra<16>( org, x, y, bitDepth, satd, predIntra ); |
710 | 0 | else predictIntra<8> ( org, x, y, bitDepth, satd, predIntra ); |
711 | 0 | const BlockCoder::Result intra = intraCoder.code( orgBlk, org.stride, predIntra, nullptr, 0, maxVal ); |
712 | | |
713 | | // mode decision with the original references |
714 | 0 | int bestMode = CTP_INTRA; |
715 | 0 | double bestCost = cost( intra ); |
716 | 0 | BlockCoder::Result src[2] = {}; |
717 | 0 | for( int l = 0; l < 2; l++ ) |
718 | 0 | { |
719 | 0 | if( ! refs[l] ) continue; |
720 | 0 | predictBlock( predBlock, n, refs[l]->lowRes[0].Y(), x, y, frame.mvs[l].get( bx, by ), predSrc[l] ); |
721 | 0 | src[l] = interCoder.code( orgBlk, org.stride, predSrc[l], nullptr, 0, maxVal ); |
722 | 0 | if( cost( src[l] ) < bestCost ) |
723 | 0 | { |
724 | 0 | bestCost = cost( src[l] ); |
725 | 0 | bestMode = l == 0 ? CTP_L0 : CTP_L1; |
726 | 0 | } |
727 | 0 | } |
728 | 0 | if( refs[0] && refs[1] ) |
729 | 0 | { |
730 | 0 | for( int i = 0; i < n * n; i++ ) pred[i] = Pel( ( predSrc[0][i] + predSrc[1][i] + 1 ) >> 1 ); |
731 | 0 | const BlockCoder::Result srcBi = interCoder.code( orgBlk, org.stride, pred, nullptr, 0, maxVal ); |
732 | 0 | if( cost( srcBi ) < bestCost ) |
733 | 0 | { |
734 | 0 | bestCost = cost( srcBi ); |
735 | 0 | bestMode = CTP_BI; |
736 | 0 | } |
737 | 0 | } |
738 | 0 | frame.predMode[idx] = uint8_t( bestMode ); |
739 | |
|
740 | 0 | if( bestMode == CTP_INTRA ) |
741 | 0 | { |
742 | | // intra cuts the dependency |
743 | 0 | frame.distRec[idx] = float( intraCoder.code( orgBlk, org.stride, predIntra, recBlk, recon.stride, maxVal ).dist ); |
744 | 0 | continue; |
745 | 0 | } |
746 | | |
747 | | // code the chosen mode from the reconstructed references |
748 | 0 | for( int l = 0; l < 2; l++ ) |
749 | 0 | { |
750 | 0 | if( refs[l] ) predictBlock( predBlock, n, refs[l]->recon.Y(), x, y, frame.mvs[l].get( bx, by ), predRec[l] ); |
751 | 0 | } |
752 | 0 | if( bestMode != CTP_BI ) |
753 | 0 | { |
754 | 0 | const int l = bestMode == CTP_L0 ? 0 : 1; |
755 | 0 | const BlockCoder::Result rec = interCoder.code( orgBlk, org.stride, predRec[l], recBlk, recon.stride, maxVal ); |
756 | 0 | frame.distRec [idx] = float( rec.dist ); |
757 | 0 | frame.distSrc [l][idx] = float( std::min( src[l].dist, rec.dist ) ); |
758 | 0 | frame.deltaDist[l][idx] = float( std::max( 0.0, rec.dist - src[l].dist ) ); |
759 | 0 | frame.deltaRate[l][idx] = float( std::max( 0.0, rec.rate - src[l].rate ) ); |
760 | 0 | continue; |
761 | 0 | } |
762 | | |
763 | | // bi-prediction: the dependency on each reference is measured by keeping the other one reconstructed |
764 | 0 | for( int i = 0; i < n * n; i++ ) pred[i] = Pel( ( predRec[0][i] + predRec[1][i] + 1 ) >> 1 ); |
765 | 0 | const BlockCoder::Result rec = interCoder.code( orgBlk, org.stride, pred, recBlk, recon.stride, maxVal ); |
766 | 0 | frame.distRec[idx] = float( rec.dist ); |
767 | 0 | for( int l = 0; l < 2; l++ ) |
768 | 0 | { |
769 | 0 | const Pel* other = predRec[1 - l]; |
770 | 0 | for( int i = 0; i < n * n; i++ ) pred[i] = Pel( ( predSrc[l][i] + other[i] + 1 ) >> 1 ); |
771 | 0 | const BlockCoder::Result mixed = interCoder.code( orgBlk, org.stride, pred, nullptr, 0, maxVal ); |
772 | 0 | frame.distSrc [l][idx] = float( std::min( mixed.dist, rec.dist ) ); |
773 | 0 | frame.deltaDist[l][idx] = float( std::max( 0.0, rec.dist - mixed.dist ) ); |
774 | 0 | frame.deltaRate[l][idx] = float( std::max( 0.0, rec.rate - mixed.rate ) ); |
775 | 0 | } |
776 | 0 | } |
777 | 0 | } ); |
778 | |
|
779 | 0 | frame.recon.extendBorderPel( LOWRES_PAD, LOWRES_PAD ); |
780 | 0 | frame.propDist.assign( numBlocks, 0.0 ); |
781 | 0 | frame.propRate.assign( numBlocks, 0.0 ); |
782 | 0 | frame.analysed = true; |
783 | 0 | } |
784 | | |
785 | | // Hierarchical motion search: MCTF's integer search of 2N x 2N blocks at eighth resolution, refined by xSearchInteger |
786 | | // at quarter and half resolution, then the N x N analysis blocks to quarter sample by xSearchFractional. |
787 | | void CuTree::xEstimateMotion( const Frame& cur, const Frame& ref, Array2D<MotionVector>& mvs ) const |
788 | 0 | { |
789 | 0 | const int searchSize = 2 * m_blkSize; |
790 | 0 | Array2D<MotionVector> integerMvs[NUM_LEVELS]; |
791 | |
|
792 | 0 | for( int level = NUM_LEVELS - 1; level >= 0; level-- ) |
793 | 0 | { |
794 | 0 | const CPelBuf picLevel = cur.lowRes[level].Y(); |
795 | 0 | integerMvs[level].allocate( picLevel.width / searchSize + 1, picLevel.height / searchSize + 1 ); |
796 | 0 | if( level == NUM_LEVELS - 1 ) |
797 | 0 | { |
798 | 0 | m_motionSearch.motionEstimationLuma( integerMvs[level], cur.lowRes[level], ref.lowRes[level], LOWRES_PAD, searchSize, nullptr, 2 ); |
799 | 0 | } |
800 | 0 | else |
801 | 0 | { |
802 | 0 | xSearchInteger( cur.lowRes[level], ref.lowRes[level], integerMvs[level + 1], integerMvs[level] ); |
803 | 0 | } |
804 | 0 | } |
805 | |
|
806 | 0 | const CPelBuf pic = cur.lowRes[0].Y(); |
807 | 0 | mvs.allocate( pic.width / m_blkSize + 1, pic.height / m_blkSize + 1 ); |
808 | 0 | xSearchFractional( cur, ref, integerMvs[0], mvs ); |
809 | 0 | } |
810 | | |
811 | | // The positions one block's motion search has checked. Checking a position again cannot change the result: its error |
812 | | // is the same, and only a smaller error replaces the best. Neighbouring blocks often share a vector, and the |
813 | | // refinement steps come back to positions checked before, so the searches skip them. |
814 | | class CheckedPositions |
815 | | { |
816 | | public: |
817 | | // Records the position; false when it was checked before. |
818 | | bool add( int x, int y ) |
819 | 0 | { |
820 | 0 | for( int i = 0; i < m_num; i++ ) |
821 | 0 | { |
822 | 0 | if( m_x[i] == x && m_y[i] == y ) |
823 | 0 | { |
824 | 0 | return false; |
825 | 0 | } |
826 | 0 | } |
827 | 0 | if( m_num < CAPACITY ) // more positions than any search checks, see xSearchInteger |
828 | 0 | { |
829 | 0 | m_x[m_num] = x; |
830 | 0 | m_y[m_num++] = y; |
831 | 0 | } |
832 | 0 | return true; |
833 | 0 | } |
834 | | |
835 | | private: |
836 | | static constexpr int CAPACITY = 48; |
837 | | int m_x[CAPACITY], m_y[CAPACITY]; |
838 | | int m_num = 0; |
839 | | }; |
840 | | |
841 | | // Integer motion of the 2N x 2N blocks of a level from the coarser level: the best of the parent's and its |
842 | | // neighbours' vectors (doubled) and zero, refined by a small diamond search; at most 1 + 9 + 8 * 4 positions. |
843 | | void CuTree::xSearchInteger( const PelStorage& curPic, const PelStorage& refPic, const Array2D<MotionVector>& coarse, Array2D<MotionVector>& mvs ) const |
844 | 0 | { |
845 | 0 | const int bs = 2 * m_blkSize; |
846 | 0 | const CPelBuf org = curPic.Y(); |
847 | 0 | const CPelBuf refBuf = refPic.Y(); |
848 | 0 | if( org.height < m_blkSize || org.width < m_blkSize ) |
849 | 0 | { |
850 | 0 | return; |
851 | 0 | } |
852 | 0 | const int rows = ( org.height - m_blkSize ) / bs + 1; |
853 | 0 | const int cols = ( org.width - m_blkSize ) / bs + 1; |
854 | |
|
855 | 0 | xForEachRow( rows, [&]( int by ) |
856 | 0 | { |
857 | 0 | for( int bx = 0; bx < cols; bx++ ) |
858 | 0 | { |
859 | 0 | const int x = bx * bs, y = by * bs; |
860 | 0 | const int bw = std::min<int>( bs, org.width - x ) & ~7; |
861 | 0 | const int bh = std::min<int>( bs, org.height - y ) & ~7; |
862 | 0 | const Pel* orgBlk = org.buf + y * org.stride + x; |
863 | | // keep the displaced block inside the padded reference |
864 | 0 | const int minX = -LOWRES_PAD - x, maxX = int( refBuf.width ) + LOWRES_PAD - bw - x; |
865 | 0 | const int minY = -LOWRES_PAD - y, maxY = int( refBuf.height ) + LOWRES_PAD - bh - y; |
866 | 0 | MotionVector best; |
867 | 0 | CheckedPositions checked; |
868 | 0 | auto check = [&]( int dx, int dy ) |
869 | 0 | { |
870 | 0 | dx = Clip3( minX, maxX, dx ); |
871 | 0 | dy = Clip3( minY, maxY, dy ); |
872 | 0 | if( ! checked.add( dx, dy ) ) |
873 | 0 | { |
874 | 0 | return; |
875 | 0 | } |
876 | 0 | const Pel* refBlk = refBuf.buf + ( y + dy ) * refBuf.stride + x + dx; |
877 | 0 | const int error = m_motionSearch.m_motionErrorLumaInt8( orgBlk, org.stride, refBlk, refBuf.stride, bw, bh, best.error ); |
878 | 0 | if( error < best.error ) |
879 | 0 | { |
880 | 0 | best.set( dx * MV_PREC, dy * MV_PREC, error ); |
881 | 0 | } |
882 | 0 | }; |
883 | |
|
884 | 0 | check( 0, 0 ); |
885 | 0 | for( int py = by / 2 - 1; py <= by / 2 + 1; py++ ) |
886 | 0 | { |
887 | 0 | for( int px = bx / 2 - 1; px <= bx / 2 + 1; px++ ) |
888 | 0 | { |
889 | 0 | if( px >= 0 && py >= 0 && px < coarse.w() && py < coarse.h() && coarse.get( px, py ).error != INT_LEAST32_MAX ) |
890 | 0 | { |
891 | 0 | const MotionVector& c = coarse.get( px, py ); |
892 | 0 | check( 2 * c.x / MV_PREC, 2 * c.y / MV_PREC ); |
893 | 0 | } |
894 | 0 | } |
895 | 0 | } |
896 | 0 | for( int iter = 0; iter < 8; iter++ ) |
897 | 0 | { |
898 | 0 | const MotionVector center = best; |
899 | 0 | check( center.x / MV_PREC - 1, center.y / MV_PREC ); |
900 | 0 | check( center.x / MV_PREC + 1, center.y / MV_PREC ); |
901 | 0 | check( center.x / MV_PREC, center.y / MV_PREC - 1 ); |
902 | 0 | check( center.x / MV_PREC, center.y / MV_PREC + 1 ); |
903 | 0 | if( best.x == center.x && best.y == center.y ) |
904 | 0 | { |
905 | 0 | break; |
906 | 0 | } |
907 | 0 | } |
908 | 0 | mvs.get( bx, by ) = best; |
909 | 0 | } |
910 | 0 | } ); |
911 | 0 | } |
912 | | |
913 | | // Motion of the N x N analysis blocks from the integer motion of the 2N x 2N blocks: the best of the parent's and |
914 | | // its neighbours' vectors and zero, refined to half and then quarter sample with the bilinear predictor. |
915 | | void CuTree::xSearchFractional( const Frame& cur, const Frame& ref, const Array2D<MotionVector>& coarse, Array2D<MotionVector>& mvs ) const |
916 | 0 | { |
917 | 0 | const CPelBuf org = cur.lowRes[0].Y(); |
918 | 0 | const CPelBuf refBuf = ref.lowRes[0].Y(); |
919 | 0 | const int n = m_blkSize; |
920 | 0 | const TplPredBlockFn predBlock = m_predBlock[m_blkSizeIdx]; |
921 | 0 | const int rows = org.height / n; |
922 | 0 | const int cols = org.width / n; |
923 | |
|
924 | 0 | xForEachRow( rows, [&]( int by ) |
925 | 0 | { |
926 | 0 | Pel pred[MAX_BLK_SIZE * MAX_BLK_SIZE]; |
927 | 0 | for( int bx = 0; bx < cols; bx++ ) |
928 | 0 | { |
929 | 0 | const int x = bx * n, y = by * n; |
930 | 0 | const Pel* orgBlk = org.buf + y * org.stride + x; |
931 | 0 | MotionVector best; |
932 | 0 | CheckedPositions checked; |
933 | 0 | auto check = [&]( int mvx, int mvy ) |
934 | 0 | { |
935 | 0 | if( ! checked.add( mvx, mvy ) ) |
936 | 0 | { |
937 | 0 | return; |
938 | 0 | } |
939 | 0 | MotionVector mv; |
940 | 0 | mv.set( mvx, mvy, 0 ); |
941 | 0 | predictBlock( predBlock, n, refBuf, x, y, mv, pred ); |
942 | 0 | const int error = m_motionSearch.m_motionErrorLumaInt8( orgBlk, org.stride, pred, n, n, n, best.error ); |
943 | 0 | if( error < best.error ) |
944 | 0 | { |
945 | 0 | best.set( mvx, mvy, error ); |
946 | 0 | } |
947 | 0 | }; |
948 | |
|
949 | 0 | check( 0, 0 ); |
950 | 0 | for( int py = by / 2 - 1; py <= by / 2 + 1; py++ ) |
951 | 0 | { |
952 | 0 | for( int px = bx / 2 - 1; px <= bx / 2 + 1; px++ ) |
953 | 0 | { |
954 | 0 | if( px >= 0 && py >= 0 && px < coarse.w() && py < coarse.h() ) |
955 | 0 | { |
956 | 0 | const MotionVector& c = coarse.get( px, py ); |
957 | 0 | check( c.x, c.y ); |
958 | 0 | } |
959 | 0 | } |
960 | 0 | } |
961 | 0 | for( int step = MV_PREC / 2; step >= MV_PREC / 4; step /= 2 ) |
962 | 0 | { |
963 | 0 | const MotionVector center = best; |
964 | 0 | for( int dy = -step; dy <= step; dy += step ) |
965 | 0 | { |
966 | 0 | for( int dx = -step; dx <= step; dx += step ) |
967 | 0 | { |
968 | 0 | if( dx || dy ) |
969 | 0 | { |
970 | 0 | check( center.x + dx, center.y + dy ); |
971 | 0 | } |
972 | 0 | } |
973 | 0 | } |
974 | 0 | } |
975 | 0 | mvs.get( bx, by ) = best; |
976 | 0 | } |
977 | 0 | } ); |
978 | 0 | } |
979 | | |
980 | | // Walks the analysed pictures from firstPoc on in reverse coding order and passes each block's dependency to the |
981 | | // reference blocks it predicts from (Han et al., Eqs. 12, 16 and 17): |
982 | | // dD_ref += dD + dD / D_rec * D_prop |
983 | | // dR_ref += dR + log2( 2^(2 R_prop) / ( D_src / D_rec * 2^(2 R_prop) + 1 - D_src / D_rec ) ) |
984 | | void CuTree::xPropagate( int firstPoc ) |
985 | 0 | { |
986 | 0 | std::vector<Frame*> order; |
987 | 0 | for( auto& entry : m_frames ) |
988 | 0 | { |
989 | 0 | Frame& frame = *entry.second; |
990 | 0 | std::fill( frame.propDist.begin(), frame.propDist.end(), 0.0 ); |
991 | 0 | std::fill( frame.propRate.begin(), frame.propRate.end(), 0.0 ); |
992 | 0 | if( frame.poc >= firstPoc && frame.analysed && ! frame.isIntra ) |
993 | 0 | { |
994 | 0 | order.push_back( &frame ); |
995 | 0 | } |
996 | 0 | } |
997 | 0 | std::sort( order.begin(), order.end(), []( const Frame* a, const Frame* b ) { return a->codingNum > b->codingNum; } ); |
998 | |
|
999 | 0 | for( Frame* frame : order ) |
1000 | 0 | { |
1001 | 0 | Frame* refs[2] = { xFindFrame( frame->refPoc[0] ), xFindFrame( frame->refPoc[1] ) }; |
1002 | |
|
1003 | 0 | for( int by = 0; by < m_blkHeight; by++ ) |
1004 | 0 | { |
1005 | 0 | for( int bx = 0; bx < m_blkWidth; bx++ ) |
1006 | 0 | { |
1007 | 0 | const int idx = by * m_blkWidth + bx; |
1008 | 0 | const int mode = frame->predMode[idx]; |
1009 | 0 | if( mode == CTP_INTRA || frame->distRec[idx] <= 0.f ) |
1010 | 0 | { |
1011 | 0 | continue; |
1012 | 0 | } |
1013 | 0 | const double distRec = frame->distRec[idx]; |
1014 | 0 | const double propRate = frame->propRate[idx]; |
1015 | 0 | const double growth = propRate > 0.0 ? std::pow( 2.0, 2.0 * propRate ) : 1.0; |
1016 | |
|
1017 | 0 | for( int l = 0; l < 2; l++ ) |
1018 | 0 | { |
1019 | 0 | const bool usesRef = mode == CTP_BI || mode == ( l == 0 ? CTP_L0 : CTP_L1 ); |
1020 | 0 | if( ! usesRef || ! refs[l] ) |
1021 | 0 | { |
1022 | 0 | continue; |
1023 | 0 | } |
1024 | 0 | const double deltaDist = frame->deltaDist[l][idx]; |
1025 | 0 | const double srcRatio = frame->distSrc[l][idx] / distRec; |
1026 | 0 | const double dist = deltaDist + deltaDist / distRec * frame->propDist[idx]; |
1027 | 0 | const double rate = frame->deltaRate[l][idx] + ( propRate > 0.0 ? std::log2( growth / ( srcRatio * growth + 1.0 - srcRatio ) ) : 0.0 ); |
1028 | 0 | if( dist > 0.0 || rate > 0.0 ) |
1029 | 0 | { |
1030 | 0 | xDistribute( *refs[l], bx, by, frame->mvs[l].get( bx, by ), dist, rate ); |
1031 | 0 | } |
1032 | 0 | } |
1033 | 0 | } |
1034 | 0 | } |
1035 | 0 | } |
1036 | 0 | } |
1037 | | |
1038 | | // Adds the dependency to the up to four reference blocks that the displaced block overlaps, in proportion to the |
1039 | | // overlapping area. Parts outside the picture are dropped. |
1040 | | void CuTree::xDistribute( Frame& ref, int bx, int by, const MotionVector& mv, double dist, double rate ) const |
1041 | 0 | { |
1042 | 0 | const int log2Units = floorLog2( m_blkSize * MV_PREC ); // log2 of one block in motion vector units |
1043 | 0 | const int units = 1 << log2Units; |
1044 | 0 | const int posX = bx * units + mv.x; |
1045 | 0 | const int posY = by * units + mv.y; |
1046 | | // floor division and remainder; positions can be negative, and >> rounds those down |
1047 | 0 | const int x0 = posX >> log2Units; |
1048 | 0 | const int y0 = posY >> log2Units; |
1049 | 0 | const int fx = posX & ( units - 1 ); |
1050 | 0 | const int fy = posY & ( units - 1 ); |
1051 | 0 | const double distScale = dist / double( units * units ); |
1052 | 0 | const double rateScale = rate / double( units * units ); |
1053 | |
|
1054 | 0 | const int xs[2] = { x0, x0 + 1 }; |
1055 | 0 | const int ys[2] = { y0, y0 + 1 }; |
1056 | 0 | const int ws[2] = { units - fx, fx }; |
1057 | 0 | const int hs[2] = { units - fy, fy }; |
1058 | |
|
1059 | 0 | for( int j = 0; j < 2; j++ ) |
1060 | 0 | { |
1061 | 0 | if( ys[j] < 0 || ys[j] >= m_blkHeight || hs[j] == 0 ) |
1062 | 0 | { |
1063 | 0 | continue; |
1064 | 0 | } |
1065 | 0 | for( int i = 0; i < 2; i++ ) |
1066 | 0 | { |
1067 | 0 | if( xs[i] < 0 || xs[i] >= m_blkWidth || ws[i] == 0 ) |
1068 | 0 | { |
1069 | 0 | continue; |
1070 | 0 | } |
1071 | 0 | const int idx = ys[j] * m_blkWidth + xs[i]; |
1072 | 0 | const double area = double( ws[i] * hs[j] ); |
1073 | 0 | ref.propDist[idx] += distScale * area; |
1074 | 0 | ref.propRate[idx] += rateScale * area; |
1075 | 0 | } |
1076 | 0 | } |
1077 | 0 | } |
1078 | | |
1079 | | // The picture-level QP offsets of BIM 2 (MCTF): MCTF's offset for the pictures it analysed, and for the others 3/4 of |
1080 | | // the mean of their two references' offsets, as EncGOP's initPicAuxQPOffsets derives them for BIM 2. They lower the QP |
1081 | | // of whole pictures in static content, which CU-tree's CTU offsets, relative to the picture's own mean dependency, |
1082 | | // cannot do. Runs in coding order, so the references come first; MCTF has to have run, its offsets are read before |
1083 | | // xSetQpOffsets replaces them. |
1084 | | void CuTree::xSetPicQpOffsets( const std::vector<Picture*>& codingOrder, int batchEndPoc ) |
1085 | 0 | { |
1086 | 0 | for( auto pic : codingOrder ) |
1087 | 0 | { |
1088 | 0 | Frame* frame = xFindFrame( pic->poc ); |
1089 | 0 | if( pic->poc > batchEndPoc || ! frame ) |
1090 | 0 | { |
1091 | 0 | continue; |
1092 | 0 | } |
1093 | 0 | if( ! pic->m_picShared->m_ctuBimQpOffset.empty() ) |
1094 | 0 | { |
1095 | 0 | frame->picQpOffset = pic->m_picShared->m_picAuxQpOffset; |
1096 | 0 | continue; |
1097 | 0 | } |
1098 | 0 | const Frame* ref0 = xFindFrame( frame->refPoc[0] ); |
1099 | 0 | const Frame* ref1 = xFindFrame( frame->refPoc[1] ); |
1100 | 0 | if( ref0 && ref1 ) |
1101 | 0 | { |
1102 | 0 | const int sum = ref0->picQpOffset + ref1->picQpOffset; |
1103 | 0 | frame->picQpOffset = ( 3 * sum + ( sum < 0 ? 3 : 4 ) ) >> 3; // rounding as in initPicAuxQPOffsets |
1104 | 0 | } |
1105 | 0 | } |
1106 | 0 | } |
1107 | | |
1108 | | // The QP offset of each CTU (Han et al., Eqs. 19 to 21): with the dependency factor |
1109 | | // alpha = ( distortion + lambda * rate owed by later pictures ) / own distortion |
1110 | | // of the CTU and of the picture, lambda_ctu = lambda * ( 1 + alpha_pic ) / ( 1 + alpha_ctu ). As lambda goes with |
1111 | | // 2^(QP / 3), the QP offset is a multiple of -log2( beta ), beta = ( 1 + alpha_ctu ) / ( 1 + alpha_pic ). Like |
1112 | | // libaom's superblock QP, the change is damped to the square root of beta for intra pictures and for CTUs below |
1113 | | // the picture's average, and to the fourth root for CTUs above it in inter pictures. The offsets go where QPA |
1114 | | // and the non-QPA path pick up the BIM offsets. The picture QP gets BIM 2's picture offset (xSetPicQpOffsets) and, |
1115 | | // rounded, the mean of the CTU offsets, which the CTUs lose again: as with BIM 2, the CTU offsets average 0, so rate |
1116 | | // control, which sets the picture QP itself, still gets the QP it chose. |
1117 | | void CuTree::xSetQpOffsets( const Frame& frame, Picture* pic ) const |
1118 | 0 | { |
1119 | 0 | const int ctuSize = m_encCfg->m_bimCtuSize; |
1120 | 0 | const int widthInCtus = ( m_encCfg->m_PadSourceWidth + ctuSize - 1 ) / ctuSize; |
1121 | 0 | const int heightInCtus = ( m_encCfg->m_PadSourceHeight + ctuSize - 1 ) / ctuSize; |
1122 | 0 | const int blksPerCtu = ctuSize / ( 2 * m_blkSize ); |
1123 | |
|
1124 | 0 | PicShared* picShared = pic->m_picShared; |
1125 | 0 | picShared->m_ctuBimQpOffset.assign( widthInCtus * heightInCtus, 0 ); |
1126 | 0 | picShared->m_picAuxQpOffset = frame.picQpOffset; |
1127 | 0 | if( m_blkWidth == 0 || m_blkHeight == 0 || ! frame.analysed ) |
1128 | 0 | { |
1129 | 0 | return; // picture smaller than one analysis block, or not analysed |
1130 | 0 | } |
1131 | | |
1132 | 0 | auto blockDep = [&]( int idx ) { return frame.propDist[idx] + m_lambdaLeaf * frame.propRate[idx]; }; |
1133 | |
|
1134 | 0 | double picDep = 0.0, picDist = 0.0; |
1135 | 0 | for( int idx = 0; idx < m_blkWidth * m_blkHeight; idx++ ) |
1136 | 0 | { |
1137 | 0 | picDep += blockDep( idx ); |
1138 | 0 | picDist += frame.distRec[idx]; |
1139 | 0 | } |
1140 | 0 | const double alphaPic = picDep / std::max( picDist, 1.0 ); |
1141 | |
|
1142 | 0 | for( int cy = 0; cy < heightInCtus; cy++ ) |
1143 | 0 | { |
1144 | | // CTUs at the right and bottom edge may lie beyond the last full analysis block |
1145 | 0 | const int by0 = std::min( cy * blksPerCtu, m_blkHeight - 1 ); |
1146 | 0 | const int by1 = std::max( by0 + 1, std::min( ( cy + 1 ) * blksPerCtu, m_blkHeight ) ); |
1147 | 0 | for( int cx = 0; cx < widthInCtus; cx++ ) |
1148 | 0 | { |
1149 | 0 | const int bx0 = std::min( cx * blksPerCtu, m_blkWidth - 1 ); |
1150 | 0 | const int bx1 = std::max( bx0 + 1, std::min( ( cx + 1 ) * blksPerCtu, m_blkWidth ) ); |
1151 | |
|
1152 | 0 | double dep = 0.0, dist = 0.0; |
1153 | 0 | for( int by = by0; by < by1; by++ ) |
1154 | 0 | { |
1155 | 0 | for( int bx = bx0; bx < bx1; bx++ ) |
1156 | 0 | { |
1157 | 0 | dep += blockDep( by * m_blkWidth + bx ); |
1158 | 0 | dist += frame.distRec[by * m_blkWidth + bx]; |
1159 | 0 | } |
1160 | 0 | } |
1161 | 0 | const double alphaCtu = dep / std::max( dist, 1.0 ); |
1162 | 0 | const double beta = ( 1.0 + alphaCtu ) / ( 1.0 + alphaPic ); |
1163 | 0 | const double damping = beta < 1.0 || frame.isIntra ? 0.5 : 0.25; |
1164 | 0 | picShared->m_ctuBimQpOffset[cy * widthInCtus + cx] = int( std::lround( -m_encCfg->m_cuTreeScale * damping * std::log2( beta ) ) ); |
1165 | 0 | } |
1166 | 0 | } |
1167 | | |
1168 | | // move the mean into the picture QP, rounded as MCTF does for BIM 2 |
1169 | 0 | const int numCtu = widthInCtus * heightInCtus; |
1170 | 0 | int sum = 0; |
1171 | 0 | for( int offset : picShared->m_ctuBimQpOffset ) sum += offset; |
1172 | 0 | const int mean = ( sum + ( sum < 0 ? -( numCtu >> 1 ) : numCtu >> 1 ) ) / numCtu; |
1173 | 0 | for( int& offset : picShared->m_ctuBimQpOffset ) offset -= mean; |
1174 | 0 | picShared->m_picAuxQpOffset += mean; |
1175 | 0 | } |
1176 | | |
1177 | | } // namespace vvenc |
1178 | | |
1179 | | //! \} |