Commit | Line | Data |
---|---|---|
72b9787e JB |
1 | /***************************************************************************** |
2 | * Copyright (C) 2013 x265 project | |
3 | * | |
4 | * Authors: Steve Borho <steve@borho.org> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program; if not, write to the Free Software | |
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA. | |
19 | * | |
20 | * This program is also available under a commercial proprietary license. | |
21 | * For more information, contact us at license @ x265.com. | |
22 | *****************************************************************************/ | |
23 | ||
24 | #include "common.h" | |
25 | #include "primitives.h" | |
26 | #include "threadpool.h" | |
27 | #include "param.h" | |
28 | #include "frame.h" | |
29 | #include "framedata.h" | |
30 | #include "picyuv.h" | |
31 | ||
32 | #include "bitcost.h" | |
33 | #include "encoder.h" | |
34 | #include "slicetype.h" | |
35 | #include "frameencoder.h" | |
36 | #include "ratecontrol.h" | |
37 | #include "dpb.h" | |
38 | #include "nal.h" | |
39 | ||
40 | #include "x265.h" | |
41 | ||
42 | namespace x265 { | |
43 | const char g_sliceTypeToChar[] = {'B', 'P', 'I'}; | |
44 | } | |
45 | ||
46 | static const char *summaryCSVHeader = | |
47 | "Command, Date/Time, Elapsed Time, FPS, Bitrate, " | |
48 | "Y PSNR, U PSNR, V PSNR, Global PSNR, SSIM, SSIM (dB), " | |
49 | "I count, I ave-QP, I kpbs, I-PSNR Y, I-PSNR U, I-PSNR V, I-SSIM (dB), " | |
50 | "P count, P ave-QP, P kpbs, P-PSNR Y, P-PSNR U, P-PSNR V, P-SSIM (dB), " | |
51 | "B count, B ave-QP, B kpbs, B-PSNR Y, B-PSNR U, B-PSNR V, B-SSIM (dB), " | |
52 | "Version\n"; | |
53 | ||
54 | using namespace x265; | |
55 | ||
56 | Encoder::Encoder() | |
57 | { | |
58 | m_aborted = false; | |
59 | m_encodedFrameNum = 0; | |
60 | m_pocLast = -1; | |
61 | m_curEncoder = 0; | |
62 | m_numLumaWPFrames = 0; | |
63 | m_numChromaWPFrames = 0; | |
64 | m_numLumaWPBiFrames = 0; | |
65 | m_numChromaWPBiFrames = 0; | |
66 | m_lookahead = NULL; | |
67 | m_frameEncoder = NULL; | |
68 | m_rateControl = NULL; | |
69 | m_dpb = NULL; | |
70 | m_exportedPic = NULL; | |
71 | m_numDelayedPic = 0; | |
72 | m_outputCount = 0; | |
73 | m_csvfpt = NULL; | |
74 | m_param = NULL; | |
75 | m_cuOffsetY = NULL; | |
76 | m_cuOffsetC = NULL; | |
77 | m_buOffsetY = NULL; | |
78 | m_buOffsetC = NULL; | |
79 | m_threadPool = 0; | |
80 | m_numThreadLocalData = 0; | |
81 | } | |
82 | ||
83 | void Encoder::create() | |
84 | { | |
85 | if (!primitives.sad[0]) | |
86 | { | |
87 | // this should be an impossible condition when using our public API, and indicates a serious bug. | |
88 | x265_log(m_param, X265_LOG_ERROR, "Primitives must be initialized before encoder is created\n"); | |
89 | abort(); | |
90 | } | |
91 | ||
92 | x265_param* p = m_param; | |
93 | ||
94 | int rows = (p->sourceHeight + p->maxCUSize - 1) >> g_log2Size[p->maxCUSize]; | |
95 | ||
96 | // Do not allow WPP if only one row, it is pointless and unstable | |
97 | if (rows == 1) | |
98 | p->bEnableWavefront = 0; | |
99 | ||
100 | int poolThreadCount = p->poolNumThreads ? p->poolNumThreads : getCpuCount(); | |
101 | ||
102 | // Trim the thread pool if --wpp, --pme, and --pmode are disabled | |
103 | if (!p->bEnableWavefront && !p->bDistributeModeAnalysis && !p->bDistributeMotionEstimation) | |
104 | poolThreadCount = 0; | |
105 | ||
106 | if (poolThreadCount > 1) | |
107 | { | |
108 | m_threadPool = ThreadPool::allocThreadPool(poolThreadCount); | |
109 | poolThreadCount = m_threadPool->getThreadCount(); | |
110 | } | |
111 | else | |
112 | poolThreadCount = 0; | |
113 | ||
114 | if (!poolThreadCount) | |
115 | { | |
116 | // issue warnings if any of these features were requested | |
117 | if (p->bEnableWavefront) | |
118 | x265_log(p, X265_LOG_WARNING, "No thread pool allocated, --wpp disabled\n"); | |
119 | if (p->bDistributeMotionEstimation) | |
120 | x265_log(p, X265_LOG_WARNING, "No thread pool allocated, --pme disabled\n"); | |
121 | if (p->bDistributeModeAnalysis) | |
122 | x265_log(p, X265_LOG_WARNING, "No thread pool allocated, --pmode disabled\n"); | |
123 | ||
124 | // disable all pool features if the thread pool is disabled or unusable. | |
125 | p->bEnableWavefront = p->bDistributeModeAnalysis = p->bDistributeMotionEstimation = 0; | |
126 | } | |
127 | ||
128 | if (!p->frameNumThreads) | |
129 | { | |
130 | // auto-detect frame threads | |
131 | int cpuCount = getCpuCount(); | |
132 | if (!p->bEnableWavefront) | |
133 | p->frameNumThreads = X265_MIN(cpuCount, (rows + 1) / 2); | |
134 | else if (cpuCount > 32) | |
135 | p->frameNumThreads = 6; // dual-socket 10-core IvyBridge or higher | |
136 | else if (cpuCount >= 16) | |
137 | p->frameNumThreads = 5; // 8 HT cores, or dual socket | |
138 | else if (cpuCount >= 8) | |
139 | p->frameNumThreads = 3; // 4 HT cores | |
140 | else if (cpuCount >= 4) | |
141 | p->frameNumThreads = 2; // Dual or Quad core | |
142 | else | |
143 | p->frameNumThreads = 1; | |
144 | } | |
145 | ||
146 | x265_log(p, X265_LOG_INFO, "WPP streams / frame threads / pool : %d / %d / %d%s%s\n", | |
147 | p->bEnableWavefront ? rows : 0, p->frameNumThreads, poolThreadCount, | |
148 | p->bDistributeMotionEstimation ? " / pme" : "", p->bDistributeModeAnalysis ? " / pmode" : ""); | |
149 | ||
150 | m_frameEncoder = new FrameEncoder[m_param->frameNumThreads]; | |
151 | for (int i = 0; i < m_param->frameNumThreads; i++) | |
152 | m_frameEncoder[i].setThreadPool(m_threadPool); | |
153 | ||
154 | if (!m_scalingList.init()) | |
155 | { | |
156 | x265_log(m_param, X265_LOG_ERROR, "Unable to allocate scaling list arrays\n"); | |
157 | m_aborted = true; | |
158 | } | |
159 | else if (!m_param->scalingLists || !strcmp(m_param->scalingLists, "off")) | |
160 | m_scalingList.m_bEnabled = false; | |
161 | else if (!strcmp(m_param->scalingLists, "default")) | |
162 | m_scalingList.setDefaultScalingList(); | |
163 | else if (m_scalingList.parseScalingList(m_param->scalingLists)) | |
164 | m_aborted = true; | |
165 | m_scalingList.setupQuantMatrices(); | |
166 | ||
167 | /* Allocate thread local data, one for each thread pool worker and | |
168 | * if --no-wpp, one for each frame encoder */ | |
169 | m_numThreadLocalData = poolThreadCount; | |
170 | if (!m_param->bEnableWavefront) | |
171 | m_numThreadLocalData += m_param->frameNumThreads; | |
172 | m_threadLocalData = new ThreadLocalData[m_numThreadLocalData]; | |
173 | for (int i = 0; i < m_numThreadLocalData; i++) | |
174 | { | |
175 | m_threadLocalData[i].analysis.setThreadPool(m_threadPool); | |
176 | m_threadLocalData[i].analysis.initSearch(*m_param, m_scalingList); | |
177 | m_threadLocalData[i].analysis.create(m_threadLocalData); | |
178 | } | |
179 | ||
180 | if (!m_param->bEnableWavefront) | |
181 | for (int i = 0; i < m_param->frameNumThreads; i++) | |
182 | m_frameEncoder[i].m_tld = &m_threadLocalData[poolThreadCount + i]; | |
183 | ||
184 | m_lookahead = new Lookahead(m_param, m_threadPool); | |
185 | m_dpb = new DPB(m_param); | |
186 | m_rateControl = new RateControl(m_param); | |
187 | ||
188 | initSPS(&m_sps); | |
189 | initPPS(&m_pps); | |
190 | ||
191 | /* Try to open CSV file handle */ | |
192 | if (m_param->csvfn) | |
193 | { | |
194 | m_csvfpt = fopen(m_param->csvfn, "r"); | |
195 | if (m_csvfpt) | |
196 | { | |
197 | // file already exists, re-open for append | |
198 | fclose(m_csvfpt); | |
199 | m_csvfpt = fopen(m_param->csvfn, "ab"); | |
200 | } | |
201 | else | |
202 | { | |
203 | // new CSV file, write header | |
204 | m_csvfpt = fopen(m_param->csvfn, "wb"); | |
205 | if (m_csvfpt) | |
206 | { | |
207 | if (m_param->logLevel >= X265_LOG_DEBUG) | |
208 | { | |
209 | fprintf(m_csvfpt, "Encode Order, Type, POC, QP, Bits, "); | |
210 | if (m_param->rc.rateControlMode == X265_RC_CRF) | |
211 | fprintf(m_csvfpt, "RateFactor, "); | |
212 | fprintf(m_csvfpt, "Y PSNR, U PSNR, V PSNR, YUV PSNR, SSIM, SSIM (dB), " | |
213 | "Encoding time, Elapsed time, List 0, List 1\n"); | |
214 | } | |
215 | else | |
216 | fputs(summaryCSVHeader, m_csvfpt); | |
217 | } | |
218 | } | |
219 | } | |
220 | ||
221 | m_aborted |= parseLambdaFile(m_param); | |
222 | } | |
223 | ||
224 | void Encoder::destroy() | |
225 | { | |
226 | if (m_exportedPic) | |
227 | { | |
228 | ATOMIC_DEC(&m_exportedPic->m_countRefEncoders); | |
229 | m_exportedPic = NULL; | |
230 | } | |
231 | ||
232 | if (m_rateControl) | |
233 | m_rateControl->terminate(); // unblock all blocked RC calls | |
234 | ||
235 | if (m_frameEncoder) | |
236 | { | |
237 | for (int i = 0; i < m_param->frameNumThreads; i++) | |
238 | { | |
239 | // Ensure frame encoder is idle before destroying it | |
240 | m_frameEncoder[i].getEncodedPicture(m_nalList); | |
241 | m_frameEncoder[i].destroy(); | |
242 | } | |
243 | ||
244 | delete [] m_frameEncoder; | |
245 | } | |
246 | ||
247 | for (int i = 0; i < m_numThreadLocalData; i++) | |
248 | m_threadLocalData[i].destroy(); | |
249 | ||
250 | delete [] m_threadLocalData; | |
251 | ||
252 | if (m_lookahead) | |
253 | { | |
254 | m_lookahead->destroy(); | |
255 | delete m_lookahead; | |
256 | } | |
257 | ||
258 | delete m_dpb; | |
259 | if (m_rateControl) | |
260 | { | |
261 | m_rateControl->destroy(); | |
262 | delete m_rateControl; | |
263 | } | |
264 | // thread pool release should always happen last | |
265 | if (m_threadPool) | |
266 | m_threadPool->release(); | |
267 | ||
268 | X265_FREE(m_cuOffsetY); | |
269 | X265_FREE(m_cuOffsetC); | |
270 | X265_FREE(m_buOffsetY); | |
271 | X265_FREE(m_buOffsetC); | |
272 | ||
273 | if (m_csvfpt) | |
274 | fclose(m_csvfpt); | |
275 | free(m_param->rc.statFileName); // alloc'd by strdup | |
276 | ||
277 | X265_FREE(m_param); | |
278 | } | |
279 | ||
280 | void Encoder::init() | |
281 | { | |
282 | if (m_frameEncoder) | |
283 | { | |
284 | int numRows = (m_param->sourceHeight + g_maxCUSize - 1) / g_maxCUSize; | |
285 | int numCols = (m_param->sourceWidth + g_maxCUSize - 1) / g_maxCUSize; | |
286 | for (int i = 0; i < m_param->frameNumThreads; i++) | |
287 | { | |
288 | if (!m_frameEncoder[i].init(this, numRows, numCols, i)) | |
289 | { | |
290 | x265_log(m_param, X265_LOG_ERROR, "Unable to initialize frame encoder, aborting\n"); | |
291 | m_aborted = true; | |
292 | } | |
293 | } | |
294 | } | |
295 | if (m_param->bEmitHRDSEI) | |
296 | m_rateControl->initHRD(&m_sps); | |
297 | if (!m_rateControl->init(&m_sps)) | |
298 | m_aborted = true; | |
299 | m_lookahead->init(); | |
300 | m_encodeStartTime = x265_mdate(); | |
301 | } | |
302 | ||
303 | void Encoder::updateVbvPlan(RateControl* rc) | |
304 | { | |
305 | for (int i = 0; i < m_param->frameNumThreads; i++) | |
306 | { | |
307 | FrameEncoder *encoder = &m_frameEncoder[i]; | |
308 | if (encoder->m_rce.isActive && encoder->m_rce.poc != rc->m_curSlice->m_poc) | |
309 | { | |
310 | int64_t bits = (int64_t) X265_MAX(encoder->m_rce.frameSizeEstimated, encoder->m_rce.frameSizePlanned); | |
311 | rc->m_bufferFill -= bits; | |
312 | rc->m_bufferFill = X265_MAX(rc->m_bufferFill, 0); | |
313 | rc->m_bufferFill += encoder->m_rce.bufferRate; | |
314 | rc->m_bufferFill = X265_MIN(rc->m_bufferFill, rc->m_bufferSize); | |
315 | if (rc->m_2pass) | |
316 | rc->m_predictedBits += bits; | |
317 | } | |
318 | } | |
319 | } | |
320 | ||
321 | /** | |
322 | * Feed one new input frame into the encoder, get one frame out. If pic_in is | |
323 | * NULL, a flush condition is implied and pic_in must be NULL for all subsequent | |
324 | * calls for this encoder instance. | |
325 | * | |
326 | * pic_in input original YUV picture or NULL | |
327 | * pic_out pointer to reconstructed picture struct | |
328 | * | |
329 | * returns 0 if no frames are currently available for output | |
330 | * 1 if frame was output, m_nalList contains access unit | |
331 | * negative on malloc error or abort */ | |
332 | int Encoder::encode(const x265_picture* pic_in, x265_picture* pic_out) | |
333 | { | |
334 | if (m_aborted) | |
335 | return -1; | |
336 | ||
337 | if (m_exportedPic) | |
338 | { | |
339 | ATOMIC_DEC(&m_exportedPic->m_countRefEncoders); | |
340 | m_exportedPic = NULL; | |
341 | m_dpb->recycleUnreferenced(); | |
342 | } | |
343 | ||
344 | if (pic_in) | |
345 | { | |
346 | if (pic_in->colorSpace != m_param->internalCsp) | |
347 | { | |
348 | x265_log(m_param, X265_LOG_ERROR, "Unsupported color space (%d) on input\n", | |
349 | pic_in->colorSpace); | |
350 | return -1; | |
351 | } | |
352 | if (pic_in->bitDepth < 8 || pic_in->bitDepth > 16) | |
353 | { | |
354 | x265_log(m_param, X265_LOG_ERROR, "Input bit depth (%d) must be between 8 and 16\n", | |
355 | pic_in->bitDepth); | |
356 | return -1; | |
357 | } | |
358 | ||
359 | Frame *inFrame; | |
360 | if (m_dpb->m_freeList.empty()) | |
361 | { | |
362 | inFrame = new Frame; | |
363 | if (inFrame->create(m_param)) | |
364 | { | |
365 | /* the first PicYuv created is asked to generate the CU and block unit offset | |
366 | * arrays which are then shared with all subsequent PicYuv (orig and recon) | |
367 | * allocated by this top level encoder */ | |
368 | if (m_cuOffsetY) | |
369 | { | |
370 | inFrame->m_origPicYuv->m_cuOffsetC = m_cuOffsetC; | |
371 | inFrame->m_origPicYuv->m_cuOffsetY = m_cuOffsetY; | |
372 | inFrame->m_origPicYuv->m_buOffsetC = m_buOffsetC; | |
373 | inFrame->m_origPicYuv->m_buOffsetY = m_buOffsetY; | |
374 | } | |
375 | else | |
376 | { | |
377 | if (!inFrame->m_origPicYuv->createOffsets(m_sps)) | |
378 | { | |
379 | m_aborted = true; | |
380 | x265_log(m_param, X265_LOG_ERROR, "memory allocation failure, aborting encode\n"); | |
381 | inFrame->destroy(); | |
382 | delete inFrame; | |
383 | return -1; | |
384 | } | |
385 | else | |
386 | { | |
387 | m_cuOffsetC = inFrame->m_origPicYuv->m_cuOffsetC; | |
388 | m_cuOffsetY = inFrame->m_origPicYuv->m_cuOffsetY; | |
389 | m_buOffsetC = inFrame->m_origPicYuv->m_buOffsetC; | |
390 | m_buOffsetY = inFrame->m_origPicYuv->m_buOffsetY; | |
391 | } | |
392 | } | |
393 | } | |
394 | else | |
395 | { | |
396 | m_aborted = true; | |
397 | x265_log(m_param, X265_LOG_ERROR, "memory allocation failure, aborting encode\n"); | |
398 | inFrame->destroy(); | |
399 | delete inFrame; | |
400 | return -1; | |
401 | } | |
402 | } | |
403 | else | |
404 | inFrame = m_dpb->m_freeList.popBack(); | |
405 | ||
406 | /* Copy input picture into a Frame and PicYuv, send to lookahead */ | |
407 | inFrame->m_poc = ++m_pocLast; | |
408 | inFrame->m_origPicYuv->copyFromPicture(*pic_in, m_sps.conformanceWindow.rightOffset, m_sps.conformanceWindow.bottomOffset); | |
409 | inFrame->m_intraData = pic_in->analysisData.intraData; | |
410 | inFrame->m_interData = pic_in->analysisData.interData; | |
411 | inFrame->m_userData = pic_in->userData; | |
412 | inFrame->m_pts = pic_in->pts; | |
413 | inFrame->m_forceqp = pic_in->forceqp; | |
414 | ||
415 | if (m_pocLast == 0) | |
416 | m_firstPts = inFrame->m_pts; | |
417 | if (m_bframeDelay && m_pocLast == m_bframeDelay) | |
418 | m_bframeDelayTime = inFrame->m_pts - m_firstPts; | |
419 | ||
420 | /* Encoder holds a reference count until stats collection is finished */ | |
421 | ATOMIC_INC(&inFrame->m_countRefEncoders); | |
422 | bool bEnableWP = m_param->bEnableWeightedPred || m_param->bEnableWeightedBiPred; | |
423 | if (m_param->rc.aqMode || bEnableWP) | |
424 | { | |
425 | if (m_param->rc.cuTree && m_param->rc.bStatRead) | |
426 | { | |
427 | if (!m_rateControl->cuTreeReadFor2Pass(inFrame)) | |
428 | { | |
429 | m_aborted = 1; | |
430 | return -1; | |
431 | } | |
432 | } | |
433 | else | |
434 | m_rateControl->calcAdaptiveQuantFrame(inFrame); | |
435 | } | |
436 | ||
437 | /* Use the frame types from the first pass, if available */ | |
438 | int sliceType = (m_param->rc.bStatRead) ? m_rateControl->rateControlSliceType(inFrame->m_poc) : pic_in->sliceType; | |
439 | m_lookahead->addPicture(inFrame, sliceType); | |
440 | m_numDelayedPic++; | |
441 | } | |
442 | else | |
443 | m_lookahead->flush(); | |
444 | ||
445 | FrameEncoder *curEncoder = &m_frameEncoder[m_curEncoder]; | |
446 | m_curEncoder = (m_curEncoder + 1) % m_param->frameNumThreads; | |
447 | int ret = 0; | |
448 | ||
449 | // getEncodedPicture() should block until the FrameEncoder has completed | |
450 | // encoding the frame. This is how back-pressure through the API is | |
451 | // accomplished when the encoder is full. | |
452 | Frame *outFrame = curEncoder->getEncodedPicture(m_nalList); | |
453 | ||
454 | if (outFrame) | |
455 | { | |
456 | Slice *slice = outFrame->m_encData->m_slice; | |
457 | if (pic_out) | |
458 | { | |
459 | PicYuv *recpic = outFrame->m_reconPicYuv; | |
460 | pic_out->poc = slice->m_poc; | |
461 | pic_out->bitDepth = X265_DEPTH; | |
462 | pic_out->userData = outFrame->m_userData; | |
463 | pic_out->colorSpace = m_param->internalCsp; | |
464 | ||
465 | pic_out->pts = outFrame->m_pts; | |
466 | pic_out->dts = outFrame->m_dts; | |
467 | ||
468 | switch (slice->m_sliceType) | |
469 | { | |
470 | case I_SLICE: | |
471 | pic_out->sliceType = outFrame->m_lowres.bKeyframe ? X265_TYPE_IDR : X265_TYPE_I; | |
472 | break; | |
473 | case P_SLICE: | |
474 | pic_out->sliceType = X265_TYPE_P; | |
475 | break; | |
476 | case B_SLICE: | |
477 | pic_out->sliceType = X265_TYPE_B; | |
478 | break; | |
479 | } | |
480 | ||
481 | pic_out->planes[0] = recpic->m_picOrg[0]; | |
482 | pic_out->stride[0] = (int)(recpic->m_stride * sizeof(pixel)); | |
483 | pic_out->planes[1] = recpic->m_picOrg[1]; | |
484 | pic_out->stride[1] = (int)(recpic->m_strideC * sizeof(pixel)); | |
485 | pic_out->planes[2] = recpic->m_picOrg[2]; | |
486 | pic_out->stride[2] = (int)(recpic->m_strideC * sizeof(pixel)); | |
487 | } | |
488 | ||
489 | if (m_param->analysisMode) | |
490 | { | |
491 | pic_out->analysisData.interData = outFrame->m_interData; | |
492 | pic_out->analysisData.intraData = outFrame->m_intraData; | |
493 | pic_out->analysisData.numCUsInFrame = slice->m_sps->numCUsInFrame; | |
494 | pic_out->analysisData.numPartitions = slice->m_sps->numPartitions; | |
495 | } | |
496 | ||
497 | if (slice->m_sliceType == P_SLICE) | |
498 | { | |
499 | if (slice->m_weightPredTable[0][0][0].bPresentFlag) | |
500 | m_numLumaWPFrames++; | |
501 | if (slice->m_weightPredTable[0][0][1].bPresentFlag || | |
502 | slice->m_weightPredTable[0][0][2].bPresentFlag) | |
503 | m_numChromaWPFrames++; | |
504 | } | |
505 | else if (slice->m_sliceType == B_SLICE) | |
506 | { | |
507 | bool bLuma = false, bChroma = false; | |
508 | for (int l = 0; l < 2; l++) | |
509 | { | |
510 | if (slice->m_weightPredTable[l][0][0].bPresentFlag) | |
511 | bLuma = true; | |
512 | if (slice->m_weightPredTable[l][0][1].bPresentFlag || | |
513 | slice->m_weightPredTable[l][0][2].bPresentFlag) | |
514 | bChroma = true; | |
515 | } | |
516 | ||
517 | if (bLuma) | |
518 | m_numLumaWPBiFrames++; | |
519 | if (bChroma) | |
520 | m_numChromaWPBiFrames++; | |
521 | } | |
522 | if (m_aborted) | |
523 | return -1; | |
524 | ||
525 | finishFrameStats(outFrame, curEncoder, curEncoder->m_accessUnitBits); | |
526 | // Allow this frame to be recycled if no frame encoders are using it for reference | |
527 | if (!pic_out) | |
528 | { | |
529 | ATOMIC_DEC(&outFrame->m_countRefEncoders); | |
530 | m_dpb->recycleUnreferenced(); | |
531 | } | |
532 | else | |
533 | m_exportedPic = outFrame; | |
534 | ||
535 | m_numDelayedPic--; | |
536 | ||
537 | ret = 1; | |
538 | } | |
539 | ||
540 | // pop a single frame from decided list, then provide to frame encoder | |
541 | // curEncoder is guaranteed to be idle at this point | |
542 | Frame* frameEnc = m_lookahead->getDecidedPicture(); | |
543 | if (frameEnc) | |
544 | { | |
545 | // give this picture a FrameData instance before encoding | |
546 | if (m_dpb->m_picSymFreeList) | |
547 | { | |
548 | frameEnc->m_encData = m_dpb->m_picSymFreeList; | |
549 | m_dpb->m_picSymFreeList = m_dpb->m_picSymFreeList->m_freeListNext; | |
550 | frameEnc->reinit(m_sps); | |
551 | } | |
552 | else | |
553 | { | |
554 | frameEnc->allocEncodeData(m_param, m_sps); | |
555 | Slice* slice = frameEnc->m_encData->m_slice; | |
556 | slice->m_sps = &m_sps; | |
557 | slice->m_pps = &m_pps; | |
558 | slice->m_maxNumMergeCand = m_param->maxNumMergeCand; | |
559 | slice->m_endCUAddr = slice->realEndAddress(m_sps.numCUsInFrame * NUM_CU_PARTITIONS); | |
560 | frameEnc->m_reconPicYuv->m_cuOffsetC = m_cuOffsetC; | |
561 | frameEnc->m_reconPicYuv->m_cuOffsetY = m_cuOffsetY; | |
562 | frameEnc->m_reconPicYuv->m_buOffsetC = m_buOffsetC; | |
563 | frameEnc->m_reconPicYuv->m_buOffsetY = m_buOffsetY; | |
564 | } | |
565 | curEncoder->m_rce.encodeOrder = m_encodedFrameNum++; | |
566 | if (m_bframeDelay) | |
567 | { | |
568 | int64_t *prevReorderedPts = m_prevReorderedPts; | |
569 | frameEnc->m_dts = m_encodedFrameNum > m_bframeDelay | |
570 | ? prevReorderedPts[(m_encodedFrameNum - m_bframeDelay) % m_bframeDelay] | |
571 | : frameEnc->m_reorderedPts - m_bframeDelayTime; | |
572 | prevReorderedPts[m_encodedFrameNum % m_bframeDelay] = frameEnc->m_reorderedPts; | |
573 | } | |
574 | else | |
575 | frameEnc->m_dts = frameEnc->m_reorderedPts; | |
576 | ||
577 | // determine references, setup RPS, etc | |
578 | m_dpb->prepareEncode(frameEnc); | |
579 | ||
580 | if (m_param->rc.rateControlMode != X265_RC_CQP) | |
581 | m_lookahead->getEstimatedPictureCost(frameEnc); | |
582 | ||
583 | // Allow FrameEncoder::compressFrame() to start in the frame encoder thread | |
584 | if (!curEncoder->startCompressFrame(frameEnc)) | |
585 | m_aborted = true; | |
586 | } | |
587 | else if (m_encodedFrameNum) | |
588 | m_rateControl->setFinalFrameCount(m_encodedFrameNum); | |
589 | ||
590 | return ret; | |
591 | } | |
592 | ||
593 | void EncStats::addPsnr(double psnrY, double psnrU, double psnrV) | |
594 | { | |
595 | m_psnrSumY += psnrY; | |
596 | m_psnrSumU += psnrU; | |
597 | m_psnrSumV += psnrV; | |
598 | } | |
599 | ||
600 | void EncStats::addBits(uint64_t bits) | |
601 | { | |
602 | m_accBits += bits; | |
603 | m_numPics++; | |
604 | } | |
605 | ||
606 | void EncStats::addSsim(double ssim) | |
607 | { | |
608 | m_globalSsim += ssim; | |
609 | } | |
610 | ||
611 | void EncStats::addQP(double aveQp) | |
612 | { | |
613 | m_totalQp += aveQp; | |
614 | } | |
615 | ||
616 | char* Encoder::statsCSVString(EncStats& stat, char* buffer) | |
617 | { | |
618 | if (!stat.m_numPics) | |
619 | { | |
620 | sprintf(buffer, "-, -, -, -, -, -, -, "); | |
621 | return buffer; | |
622 | } | |
623 | ||
624 | double fps = (double)m_param->fpsNum / m_param->fpsDenom; | |
625 | double scale = fps / 1000 / (double)stat.m_numPics; | |
626 | ||
627 | int len = sprintf(buffer, "%-6u, ", stat.m_numPics); | |
628 | ||
629 | len += sprintf(buffer + len, "%2.2lf, ", stat.m_totalQp / (double)stat.m_numPics); | |
630 | len += sprintf(buffer + len, "%-8.2lf, ", stat.m_accBits * scale); | |
631 | if (m_param->bEnablePsnr) | |
632 | { | |
633 | len += sprintf(buffer + len, "%.3lf, %.3lf, %.3lf, ", | |
634 | stat.m_psnrSumY / (double)stat.m_numPics, | |
635 | stat.m_psnrSumU / (double)stat.m_numPics, | |
636 | stat.m_psnrSumV / (double)stat.m_numPics); | |
637 | } | |
638 | else | |
639 | len += sprintf(buffer + len, "-, -, -, "); | |
640 | ||
641 | if (m_param->bEnableSsim) | |
642 | sprintf(buffer + len, "%.3lf, ", x265_ssim2dB(stat.m_globalSsim / (double)stat.m_numPics)); | |
643 | else | |
644 | sprintf(buffer + len, "-, "); | |
645 | return buffer; | |
646 | } | |
647 | ||
648 | char* Encoder::statsString(EncStats& stat, char* buffer) | |
649 | { | |
650 | double fps = (double)m_param->fpsNum / m_param->fpsDenom; | |
651 | double scale = fps / 1000 / (double)stat.m_numPics; | |
652 | ||
653 | int len = sprintf(buffer, "%6u, ", stat.m_numPics); | |
654 | ||
655 | len += sprintf(buffer + len, "Avg QP:%2.2lf", stat.m_totalQp / (double)stat.m_numPics); | |
656 | len += sprintf(buffer + len, " kb/s: %-8.2lf", stat.m_accBits * scale); | |
657 | if (m_param->bEnablePsnr) | |
658 | { | |
659 | len += sprintf(buffer + len, " PSNR Mean: Y:%.3lf U:%.3lf V:%.3lf", | |
660 | stat.m_psnrSumY / (double)stat.m_numPics, | |
661 | stat.m_psnrSumU / (double)stat.m_numPics, | |
662 | stat.m_psnrSumV / (double)stat.m_numPics); | |
663 | } | |
664 | if (m_param->bEnableSsim) | |
665 | { | |
666 | sprintf(buffer + len, " SSIM Mean: %.6lf (%.3lfdB)", | |
667 | stat.m_globalSsim / (double)stat.m_numPics, | |
668 | x265_ssim2dB(stat.m_globalSsim / (double)stat.m_numPics)); | |
669 | } | |
670 | return buffer; | |
671 | } | |
672 | ||
673 | void Encoder::printSummary() | |
674 | { | |
675 | if (m_param->logLevel < X265_LOG_INFO) | |
676 | return; | |
677 | ||
678 | char buffer[200]; | |
679 | if (m_analyzeI.m_numPics) | |
680 | x265_log(m_param, X265_LOG_INFO, "frame I: %s\n", statsString(m_analyzeI, buffer)); | |
681 | if (m_analyzeP.m_numPics) | |
682 | x265_log(m_param, X265_LOG_INFO, "frame P: %s\n", statsString(m_analyzeP, buffer)); | |
683 | if (m_analyzeB.m_numPics) | |
684 | x265_log(m_param, X265_LOG_INFO, "frame B: %s\n", statsString(m_analyzeB, buffer)); | |
685 | if (m_analyzeAll.m_numPics) | |
686 | x265_log(m_param, X265_LOG_INFO, "global : %s\n", statsString(m_analyzeAll, buffer)); | |
687 | if (m_param->bEnableWeightedPred && m_analyzeP.m_numPics) | |
688 | { | |
689 | x265_log(m_param, X265_LOG_INFO, "Weighted P-Frames: Y:%.1f%% UV:%.1f%%\n", | |
690 | (float)100.0 * m_numLumaWPFrames / m_analyzeP.m_numPics, | |
691 | (float)100.0 * m_numChromaWPFrames / m_analyzeP.m_numPics); | |
692 | } | |
693 | if (m_param->bEnableWeightedBiPred && m_analyzeB.m_numPics) | |
694 | { | |
695 | x265_log(m_param, X265_LOG_INFO, "Weighted B-Frames: Y:%.1f%% UV:%.1f%%\n", | |
696 | (float)100.0 * m_numLumaWPBiFrames / m_analyzeB.m_numPics, | |
697 | (float)100.0 * m_numChromaWPBiFrames / m_analyzeB.m_numPics); | |
698 | } | |
699 | int pWithB = 0; | |
700 | for (int i = 0; i <= m_param->bframes; i++) | |
701 | pWithB += m_lookahead->m_histogram[i]; | |
702 | ||
703 | if (pWithB) | |
704 | { | |
705 | int p = 0; | |
706 | for (int i = 0; i <= m_param->bframes; i++) | |
707 | p += sprintf(buffer + p, "%.1f%% ", 100. * m_lookahead->m_histogram[i] / pWithB); | |
708 | ||
709 | x265_log(m_param, X265_LOG_INFO, "consecutive B-frames: %s\n", buffer); | |
710 | } | |
711 | if (m_param->bLossless) | |
712 | { | |
713 | float frameSize = (float)(m_param->sourceWidth - m_sps.conformanceWindow.rightOffset) * | |
714 | (m_param->sourceHeight - m_sps.conformanceWindow.bottomOffset); | |
715 | float uncompressed = frameSize * X265_DEPTH * m_analyzeAll.m_numPics; | |
716 | ||
717 | x265_log(m_param, X265_LOG_INFO, "lossless compression ratio %.2f::1\n", uncompressed / m_analyzeAll.m_accBits); | |
718 | } | |
719 | ||
720 | if (!m_param->bLogCuStats) | |
721 | return; | |
722 | ||
723 | for (int sliceType = 2; sliceType >= 0; sliceType--) | |
724 | { | |
725 | if (sliceType == P_SLICE && !m_analyzeP.m_numPics) | |
726 | continue; | |
727 | if (sliceType == B_SLICE && !m_analyzeB.m_numPics) | |
728 | continue; | |
729 | ||
730 | StatisticLog finalLog; | |
731 | for (uint32_t depth = 0; depth <= g_maxCUDepth; depth++) | |
732 | { | |
733 | for (int i = 0; i < m_param->frameNumThreads; i++) | |
734 | { | |
735 | StatisticLog& enclog = m_frameEncoder[i].m_sliceTypeLog[sliceType]; | |
736 | if (!depth) | |
737 | finalLog.totalCu += enclog.totalCu; | |
738 | finalLog.cntIntra[depth] += enclog.cntIntra[depth]; | |
739 | for (int m = 0; m < INTER_MODES; m++) | |
740 | { | |
741 | if (m < INTRA_MODES) | |
742 | finalLog.cuIntraDistribution[depth][m] += enclog.cuIntraDistribution[depth][m]; | |
743 | finalLog.cuInterDistribution[depth][m] += enclog.cuInterDistribution[depth][m]; | |
744 | } | |
745 | ||
746 | if (depth == g_maxCUDepth) | |
747 | finalLog.cntIntraNxN += enclog.cntIntraNxN; | |
748 | if (sliceType != I_SLICE) | |
749 | { | |
750 | finalLog.cntTotalCu[depth] += enclog.cntTotalCu[depth]; | |
751 | finalLog.cntInter[depth] += enclog.cntInter[depth]; | |
752 | finalLog.cntSkipCu[depth] += enclog.cntSkipCu[depth]; | |
753 | } | |
754 | } | |
755 | ||
756 | uint64_t cntInter, cntSkipCu, cntIntra = 0, cntIntraNxN = 0, encCu = 0; | |
757 | uint64_t cuInterDistribution[INTER_MODES], cuIntraDistribution[INTRA_MODES]; | |
758 | ||
759 | // check for 0/0, if true assign 0 else calculate percentage | |
760 | for (int n = 0; n < INTER_MODES; n++) | |
761 | { | |
762 | if (!finalLog.cntInter[depth]) | |
763 | cuInterDistribution[n] = 0; | |
764 | else | |
765 | cuInterDistribution[n] = (finalLog.cuInterDistribution[depth][n] * 100) / finalLog.cntInter[depth]; | |
766 | ||
767 | if (n < INTRA_MODES) | |
768 | { | |
769 | if (!finalLog.cntIntra[depth]) | |
770 | { | |
771 | cntIntraNxN = 0; | |
772 | cuIntraDistribution[n] = 0; | |
773 | } | |
774 | else | |
775 | { | |
776 | cntIntraNxN = (finalLog.cntIntraNxN * 100) / finalLog.cntIntra[depth]; | |
777 | cuIntraDistribution[n] = (finalLog.cuIntraDistribution[depth][n] * 100) / finalLog.cntIntra[depth]; | |
778 | } | |
779 | } | |
780 | } | |
781 | ||
782 | if (!finalLog.totalCu) | |
783 | encCu = 0; | |
784 | else if (sliceType == I_SLICE) | |
785 | { | |
786 | cntIntra = (finalLog.cntIntra[depth] * 100) / finalLog.totalCu; | |
787 | cntIntraNxN = (finalLog.cntIntraNxN * 100) / finalLog.totalCu; | |
788 | } | |
789 | else | |
790 | encCu = ((finalLog.cntIntra[depth] + finalLog.cntInter[depth]) * 100) / finalLog.totalCu; | |
791 | ||
792 | if (sliceType == I_SLICE) | |
793 | { | |
794 | cntInter = 0; | |
795 | cntSkipCu = 0; | |
796 | } | |
797 | else if (!finalLog.cntTotalCu[depth]) | |
798 | { | |
799 | cntInter = 0; | |
800 | cntIntra = 0; | |
801 | cntSkipCu = 0; | |
802 | } | |
803 | else | |
804 | { | |
805 | cntInter = (finalLog.cntInter[depth] * 100) / finalLog.cntTotalCu[depth]; | |
806 | cntIntra = (finalLog.cntIntra[depth] * 100) / finalLog.cntTotalCu[depth]; | |
807 | cntSkipCu = (finalLog.cntSkipCu[depth] * 100) / finalLog.cntTotalCu[depth]; | |
808 | } | |
809 | ||
810 | // print statistics | |
811 | int cuSize = g_maxCUSize >> depth; | |
812 | char stats[256] = { 0 }; | |
813 | int len = 0; | |
814 | if (sliceType != I_SLICE) | |
815 | len += sprintf(stats + len, " EncCU "X265_LL "%% Merge "X265_LL "%%", encCu, cntSkipCu); | |
816 | ||
817 | if (cntInter) | |
818 | { | |
819 | len += sprintf(stats + len, " Inter "X265_LL "%%", cntInter); | |
820 | if (m_param->bEnableAMP) | |
821 | len += sprintf(stats + len, "(%dx%d "X265_LL "%% %dx%d "X265_LL "%% %dx%d "X265_LL "%% AMP "X265_LL "%%)", | |
822 | cuSize, cuSize, cuInterDistribution[0], | |
823 | cuSize / 2, cuSize, cuInterDistribution[2], | |
824 | cuSize, cuSize / 2, cuInterDistribution[1], | |
825 | cuInterDistribution[3]); | |
826 | else if (m_param->bEnableRectInter) | |
827 | len += sprintf(stats + len, "(%dx%d "X265_LL "%% %dx%d "X265_LL "%% %dx%d "X265_LL "%%)", | |
828 | cuSize, cuSize, cuInterDistribution[0], | |
829 | cuSize / 2, cuSize, cuInterDistribution[2], | |
830 | cuSize, cuSize / 2, cuInterDistribution[1]); | |
831 | } | |
832 | if (cntIntra) | |
833 | { | |
834 | len += sprintf(stats + len, " Intra "X265_LL "%%(DC "X265_LL "%% P "X265_LL "%% Ang "X265_LL "%%", | |
835 | cntIntra, cuIntraDistribution[0], | |
836 | cuIntraDistribution[1], cuIntraDistribution[2]); | |
837 | if (sliceType != I_SLICE) | |
838 | { | |
839 | if (depth == g_maxCUDepth) | |
840 | len += sprintf(stats + len, " %dx%d "X265_LL "%%", cuSize / 2, cuSize / 2, cntIntraNxN); | |
841 | } | |
842 | ||
843 | len += sprintf(stats + len, ")"); | |
844 | if (sliceType == I_SLICE) | |
845 | { | |
846 | if (depth == g_maxCUDepth) | |
847 | len += sprintf(stats + len, " %dx%d: "X265_LL "%%", cuSize / 2, cuSize / 2, cntIntraNxN); | |
848 | } | |
849 | } | |
850 | const char slicechars[] = "BPI"; | |
851 | if (stats[0]) | |
852 | x265_log(m_param, X265_LOG_INFO, "%c%-2d:%s\n", slicechars[sliceType], cuSize, stats); | |
853 | } | |
854 | } | |
855 | } | |
856 | ||
857 | void Encoder::fetchStats(x265_stats *stats, size_t statsSizeBytes) | |
858 | { | |
859 | if (statsSizeBytes >= sizeof(stats)) | |
860 | { | |
861 | stats->globalPsnrY = m_analyzeAll.m_psnrSumY; | |
862 | stats->globalPsnrU = m_analyzeAll.m_psnrSumU; | |
863 | stats->globalPsnrV = m_analyzeAll.m_psnrSumV; | |
864 | stats->encodedPictureCount = m_analyzeAll.m_numPics; | |
865 | stats->totalWPFrames = m_numLumaWPFrames; | |
866 | stats->accBits = m_analyzeAll.m_accBits; | |
867 | stats->elapsedEncodeTime = (double)(x265_mdate() - m_encodeStartTime) / 1000000; | |
868 | if (stats->encodedPictureCount > 0) | |
869 | { | |
870 | stats->globalSsim = m_analyzeAll.m_globalSsim / stats->encodedPictureCount; | |
871 | stats->globalPsnr = (stats->globalPsnrY * 6 + stats->globalPsnrU + stats->globalPsnrV) / (8 * stats->encodedPictureCount); | |
872 | stats->elapsedVideoTime = (double)stats->encodedPictureCount * m_param->fpsDenom / m_param->fpsNum; | |
873 | stats->bitrate = (0.001f * stats->accBits) / stats->elapsedVideoTime; | |
874 | } | |
875 | else | |
876 | { | |
877 | stats->globalSsim = 0; | |
878 | stats->globalPsnr = 0; | |
879 | stats->bitrate = 0; | |
880 | stats->elapsedVideoTime = 0; | |
881 | } | |
882 | } | |
883 | ||
884 | /* If new statistics are added to x265_stats, we must check here whether the | |
885 | * structure provided by the user is the new structure or an older one (for | |
886 | * future safety) */ | |
887 | } | |
888 | ||
889 | void Encoder::writeLog(int argc, char **argv) | |
890 | { | |
891 | if (m_csvfpt) | |
892 | { | |
893 | if (m_param->logLevel >= X265_LOG_DEBUG) | |
894 | { | |
895 | // adding summary to a per-frame csv log file needs a summary header | |
896 | fprintf(m_csvfpt, "\nSummary\n"); | |
897 | fputs(summaryCSVHeader, m_csvfpt); | |
898 | } | |
899 | // CLI arguments or other | |
900 | for (int i = 1; i < argc; i++) | |
901 | { | |
902 | if (i) fputc(' ', m_csvfpt); | |
903 | fputs(argv[i], m_csvfpt); | |
904 | } | |
905 | ||
906 | // current date and time | |
907 | time_t now; | |
908 | struct tm* timeinfo; | |
909 | time(&now); | |
910 | timeinfo = localtime(&now); | |
911 | char buffer[200]; | |
912 | strftime(buffer, 128, "%c", timeinfo); | |
913 | fprintf(m_csvfpt, ", %s, ", buffer); | |
914 | ||
915 | x265_stats stats; | |
916 | fetchStats(&stats, sizeof(stats)); | |
917 | ||
918 | // elapsed time, fps, bitrate | |
919 | fprintf(m_csvfpt, "%.2f, %.2f, %.2f,", | |
920 | stats.elapsedEncodeTime, stats.encodedPictureCount / stats.elapsedEncodeTime, stats.bitrate); | |
921 | ||
922 | if (m_param->bEnablePsnr) | |
923 | fprintf(m_csvfpt, " %.3lf, %.3lf, %.3lf, %.3lf,", | |
924 | stats.globalPsnrY / stats.encodedPictureCount, stats.globalPsnrU / stats.encodedPictureCount, | |
925 | stats.globalPsnrV / stats.encodedPictureCount, stats.globalPsnr); | |
926 | else | |
927 | fprintf(m_csvfpt, " -, -, -, -,"); | |
928 | if (m_param->bEnableSsim) | |
929 | fprintf(m_csvfpt, " %.6f, %6.3f,", stats.globalSsim, x265_ssim2dB(stats.globalSsim)); | |
930 | else | |
931 | fprintf(m_csvfpt, " -, -,"); | |
932 | ||
933 | fputs(statsCSVString(m_analyzeI, buffer), m_csvfpt); | |
934 | fputs(statsCSVString(m_analyzeP, buffer), m_csvfpt); | |
935 | fputs(statsCSVString(m_analyzeB, buffer), m_csvfpt); | |
936 | fprintf(m_csvfpt, " %s\n", x265_version_str); | |
937 | } | |
938 | } | |
939 | ||
940 | /** | |
941 | * Produce an ascii(hex) representation of picture digest. | |
942 | * | |
943 | * Returns: a statically allocated null-terminated string. DO NOT FREE. | |
944 | */ | |
945 | static const char*digestToString(const unsigned char digest[3][16], int numChar) | |
946 | { | |
947 | const char* hex = "0123456789abcdef"; | |
948 | static char string[99]; | |
949 | int cnt = 0; | |
950 | ||
951 | for (int yuvIdx = 0; yuvIdx < 3; yuvIdx++) | |
952 | { | |
953 | for (int i = 0; i < numChar; i++) | |
954 | { | |
955 | string[cnt++] = hex[digest[yuvIdx][i] >> 4]; | |
956 | string[cnt++] = hex[digest[yuvIdx][i] & 0xf]; | |
957 | } | |
958 | ||
959 | string[cnt++] = ','; | |
960 | } | |
961 | ||
962 | string[cnt - 1] = '\0'; | |
963 | return string; | |
964 | } | |
965 | ||
966 | void Encoder::finishFrameStats(Frame* curFrame, FrameEncoder *curEncoder, uint64_t bits) | |
967 | { | |
968 | PicYuv* reconPic = curFrame->m_reconPicYuv; | |
969 | ||
970 | //===== calculate PSNR ===== | |
971 | int width = reconPic->m_picWidth - m_sps.conformanceWindow.rightOffset; | |
972 | int height = reconPic->m_picHeight - m_sps.conformanceWindow.bottomOffset; | |
973 | int size = width * height; | |
974 | ||
975 | int maxvalY = 255 << (X265_DEPTH - 8); | |
976 | int maxvalC = 255 << (X265_DEPTH - 8); | |
977 | double refValueY = (double)maxvalY * maxvalY * size; | |
978 | double refValueC = (double)maxvalC * maxvalC * size / 4.0; | |
979 | uint64_t ssdY, ssdU, ssdV; | |
980 | ||
981 | ssdY = curEncoder->m_SSDY; | |
982 | ssdU = curEncoder->m_SSDU; | |
983 | ssdV = curEncoder->m_SSDV; | |
984 | double psnrY = (ssdY ? 10.0 * log10(refValueY / (double)ssdY) : 99.99); | |
985 | double psnrU = (ssdU ? 10.0 * log10(refValueC / (double)ssdU) : 99.99); | |
986 | double psnrV = (ssdV ? 10.0 * log10(refValueC / (double)ssdV) : 99.99); | |
987 | ||
988 | FrameData& curEncData = *curFrame->m_encData; | |
989 | Slice* slice = curEncData.m_slice; | |
990 | ||
991 | //===== add bits, psnr and ssim ===== | |
992 | m_analyzeAll.addBits(bits); | |
993 | m_analyzeAll.addQP(curEncData.m_avgQpAq); | |
994 | ||
995 | if (m_param->bEnablePsnr) | |
996 | m_analyzeAll.addPsnr(psnrY, psnrU, psnrV); | |
997 | ||
998 | double ssim = 0.0; | |
999 | if (m_param->bEnableSsim && curEncoder->m_ssimCnt) | |
1000 | { | |
1001 | ssim = curEncoder->m_ssim / curEncoder->m_ssimCnt; | |
1002 | m_analyzeAll.addSsim(ssim); | |
1003 | } | |
1004 | if (slice->isIntra()) | |
1005 | { | |
1006 | m_analyzeI.addBits(bits); | |
1007 | m_analyzeI.addQP(curEncData.m_avgQpAq); | |
1008 | if (m_param->bEnablePsnr) | |
1009 | m_analyzeI.addPsnr(psnrY, psnrU, psnrV); | |
1010 | if (m_param->bEnableSsim) | |
1011 | m_analyzeI.addSsim(ssim); | |
1012 | } | |
1013 | else if (slice->isInterP()) | |
1014 | { | |
1015 | m_analyzeP.addBits(bits); | |
1016 | m_analyzeP.addQP(curEncData.m_avgQpAq); | |
1017 | if (m_param->bEnablePsnr) | |
1018 | m_analyzeP.addPsnr(psnrY, psnrU, psnrV); | |
1019 | if (m_param->bEnableSsim) | |
1020 | m_analyzeP.addSsim(ssim); | |
1021 | } | |
1022 | else if (slice->isInterB()) | |
1023 | { | |
1024 | m_analyzeB.addBits(bits); | |
1025 | m_analyzeB.addQP(curEncData.m_avgQpAq); | |
1026 | if (m_param->bEnablePsnr) | |
1027 | m_analyzeB.addPsnr(psnrY, psnrU, psnrV); | |
1028 | if (m_param->bEnableSsim) | |
1029 | m_analyzeB.addSsim(ssim); | |
1030 | } | |
1031 | ||
1032 | // if debug log level is enabled, per frame logging is performed | |
1033 | if (m_param->logLevel >= X265_LOG_DEBUG) | |
1034 | { | |
1035 | char c = (slice->isIntra() ? 'I' : slice->isInterP() ? 'P' : 'B'); | |
1036 | int poc = slice->m_poc; | |
1037 | if (!IS_REFERENCED(curFrame)) | |
1038 | c += 32; // lower case if unreferenced | |
1039 | ||
1040 | char buf[1024]; | |
1041 | int p; | |
1042 | p = sprintf(buf, "POC:%d %c QP %2.2lf(%d) %10d bits", poc, c, curEncData.m_avgQpAq, slice->m_sliceQp, (int)bits); | |
1043 | if (m_param->rc.rateControlMode == X265_RC_CRF) | |
1044 | p += sprintf(buf + p, " RF:%.3lf", curEncData.m_rateFactor); | |
1045 | if (m_param->bEnablePsnr) | |
1046 | p += sprintf(buf + p, " [Y:%6.2lf U:%6.2lf V:%6.2lf]", psnrY, psnrU, psnrV); | |
1047 | if (m_param->bEnableSsim) | |
1048 | p += sprintf(buf + p, " [SSIM: %.3lfdB]", x265_ssim2dB(ssim)); | |
1049 | ||
1050 | if (!slice->isIntra()) | |
1051 | { | |
1052 | int numLists = slice->isInterP() ? 1 : 2; | |
1053 | for (int list = 0; list < numLists; list++) | |
1054 | { | |
1055 | p += sprintf(buf + p, " [L%d ", list); | |
1056 | for (int ref = 0; ref < slice->m_numRefIdx[list]; ref++) | |
1057 | { | |
1058 | int k = slice->m_refPOCList[list][ref] - slice->m_lastIDR; | |
1059 | p += sprintf(buf + p, "%d ", k); | |
1060 | } | |
1061 | ||
1062 | p += sprintf(buf + p, "]"); | |
1063 | } | |
1064 | } | |
1065 | ||
1066 | // per frame CSV logging if the file handle is valid | |
1067 | if (m_csvfpt) | |
1068 | { | |
1069 | fprintf(m_csvfpt, "%d, %c-SLICE, %4d, %2.2lf, %10d,", m_outputCount++, c, poc, curEncData.m_avgQpAq, (int)bits); | |
1070 | if (m_param->rc.rateControlMode == X265_RC_CRF) | |
1071 | fprintf(m_csvfpt, "%.3lf,", curEncData.m_rateFactor); | |
1072 | double psnr = (psnrY * 6 + psnrU + psnrV) / 8; | |
1073 | if (m_param->bEnablePsnr) | |
1074 | fprintf(m_csvfpt, "%.3lf, %.3lf, %.3lf, %.3lf,", psnrY, psnrU, psnrV, psnr); | |
1075 | else | |
1076 | fprintf(m_csvfpt, " -, -, -, -,"); | |
1077 | if (m_param->bEnableSsim) | |
1078 | fprintf(m_csvfpt, " %.6f, %6.3f,", ssim, x265_ssim2dB(ssim)); | |
1079 | else | |
1080 | fprintf(m_csvfpt, " -, -,"); | |
1081 | fprintf(m_csvfpt, " %.3lf, %.3lf", curEncoder->m_frameTime, curEncoder->m_elapsedCompressTime); | |
1082 | if (!slice->isIntra()) | |
1083 | { | |
1084 | int numLists = slice->isInterP() ? 1 : 2; | |
1085 | for (int list = 0; list < numLists; list++) | |
1086 | { | |
1087 | fprintf(m_csvfpt, ", "); | |
1088 | for (int ref = 0; ref < slice->m_numRefIdx[list]; ref++) | |
1089 | { | |
1090 | int k = slice->m_refPOCList[list][ref] - slice->m_lastIDR; | |
1091 | fprintf(m_csvfpt, " %d", k); | |
1092 | } | |
1093 | } | |
1094 | ||
1095 | if (numLists == 1) | |
1096 | fprintf(m_csvfpt, ", -"); | |
1097 | } | |
1098 | else | |
1099 | fprintf(m_csvfpt, ", -, -"); | |
1100 | fprintf(m_csvfpt, "\n"); | |
1101 | } | |
1102 | ||
1103 | if (m_param->decodedPictureHashSEI && m_param->logLevel >= X265_LOG_FULL) | |
1104 | { | |
1105 | const char* digestStr = NULL; | |
1106 | if (m_param->decodedPictureHashSEI == 1) | |
1107 | { | |
1108 | digestStr = digestToString(curEncoder->m_seiReconPictureDigest.m_digest, 16); | |
1109 | p += sprintf(buf + p, " [MD5:%s]", digestStr); | |
1110 | } | |
1111 | else if (m_param->decodedPictureHashSEI == 2) | |
1112 | { | |
1113 | digestStr = digestToString(curEncoder->m_seiReconPictureDigest.m_digest, 2); | |
1114 | p += sprintf(buf + p, " [CRC:%s]", digestStr); | |
1115 | } | |
1116 | else if (m_param->decodedPictureHashSEI == 3) | |
1117 | { | |
1118 | digestStr = digestToString(curEncoder->m_seiReconPictureDigest.m_digest, 4); | |
1119 | p += sprintf(buf + p, " [Checksum:%s]", digestStr); | |
1120 | } | |
1121 | } | |
1122 | x265_log(m_param, X265_LOG_DEBUG, "%s\n", buf); | |
1123 | fflush(stderr); | |
1124 | } | |
1125 | } | |
1126 | ||
1127 | #if defined(_MSC_VER) | |
1128 | #pragma warning(disable: 4800) // forcing int to bool | |
1129 | #pragma warning(disable: 4127) // conditional expression is constant | |
1130 | #endif | |
1131 | ||
1132 | void Encoder::getStreamHeaders(NALList& list, Entropy& sbacCoder, Bitstream& bs) | |
1133 | { | |
1134 | sbacCoder.setBitstream(&bs); | |
1135 | ||
1136 | /* headers for start of bitstream */ | |
1137 | bs.resetBits(); | |
1138 | sbacCoder.codeVPS(m_vps); | |
1139 | bs.writeByteAlignment(); | |
1140 | list.serialize(NAL_UNIT_VPS, bs); | |
1141 | ||
1142 | bs.resetBits(); | |
1143 | sbacCoder.codeSPS(m_sps, m_scalingList, m_vps.ptl); | |
1144 | bs.writeByteAlignment(); | |
1145 | list.serialize(NAL_UNIT_SPS, bs); | |
1146 | ||
1147 | bs.resetBits(); | |
1148 | sbacCoder.codePPS(m_pps); | |
1149 | bs.writeByteAlignment(); | |
1150 | list.serialize(NAL_UNIT_PPS, bs); | |
1151 | ||
1152 | if (m_param->bEmitInfoSEI) | |
1153 | { | |
1154 | char *opts = x265_param2string(m_param); | |
1155 | if (opts) | |
1156 | { | |
1157 | char *buffer = X265_MALLOC(char, strlen(opts) + strlen(x265_version_str) + | |
1158 | strlen(x265_build_info_str) + 200); | |
1159 | if (buffer) | |
1160 | { | |
1161 | sprintf(buffer, "x265 (build %d) - %s:%s - H.265/HEVC codec - " | |
1162 | "Copyright 2013-2014 (c) Multicoreware Inc - " | |
1163 | "http://x265.org - options: %s", | |
1164 | X265_BUILD, x265_version_str, x265_build_info_str, opts); | |
1165 | ||
1166 | bs.resetBits(); | |
1167 | SEIuserDataUnregistered idsei; | |
1168 | idsei.m_userData = (uint8_t*)buffer; | |
1169 | idsei.m_userDataLength = (uint32_t)strlen(buffer); | |
1170 | idsei.write(bs, m_sps); | |
1171 | bs.writeByteAlignment(); | |
1172 | list.serialize(NAL_UNIT_PREFIX_SEI, bs); | |
1173 | ||
1174 | X265_FREE(buffer); | |
1175 | } | |
1176 | ||
1177 | X265_FREE(opts); | |
1178 | } | |
1179 | } | |
1180 | ||
1181 | if (m_param->bEmitHRDSEI || !!m_param->interlaceMode) | |
1182 | { | |
1183 | /* Picture Timing and Buffering Period SEI require the SPS to be "activated" */ | |
1184 | SEIActiveParameterSets sei; | |
1185 | sei.m_selfContainedCvsFlag = true; | |
1186 | sei.m_noParamSetUpdateFlag = true; | |
1187 | ||
1188 | bs.resetBits(); | |
1189 | sei.write(bs, m_sps); | |
1190 | bs.writeByteAlignment(); | |
1191 | list.serialize(NAL_UNIT_PREFIX_SEI, bs); | |
1192 | } | |
1193 | } | |
1194 | ||
1195 | void Encoder::initSPS(SPS *sps) | |
1196 | { | |
1197 | m_vps.ptl.progressiveSourceFlag = !m_param->interlaceMode; | |
1198 | m_vps.ptl.interlacedSourceFlag = !!m_param->interlaceMode; | |
1199 | m_vps.ptl.nonPackedConstraintFlag = false; | |
1200 | m_vps.ptl.frameOnlyConstraintFlag = false; | |
1201 | ||
1202 | sps->conformanceWindow = m_conformanceWindow; | |
1203 | sps->chromaFormatIdc = m_param->internalCsp; | |
1204 | sps->picWidthInLumaSamples = m_param->sourceWidth; | |
1205 | sps->picHeightInLumaSamples = m_param->sourceHeight; | |
1206 | sps->numCuInWidth = (m_param->sourceWidth + g_maxCUSize - 1) / g_maxCUSize; | |
1207 | sps->numCuInHeight = (m_param->sourceHeight + g_maxCUSize - 1) / g_maxCUSize; | |
1208 | sps->numCUsInFrame = sps->numCuInWidth * sps->numCuInHeight; | |
1209 | sps->numPartitions = NUM_CU_PARTITIONS; | |
1210 | sps->numPartInCUSize = 1 << g_maxFullDepth; | |
1211 | ||
1212 | sps->log2MinCodingBlockSize = g_maxLog2CUSize - g_maxCUDepth; | |
1213 | sps->log2DiffMaxMinCodingBlockSize = g_maxCUDepth; | |
1214 | ||
1215 | sps->quadtreeTULog2MaxSize = X265_MIN(g_maxLog2CUSize, 5); | |
1216 | sps->quadtreeTULog2MinSize = 2; | |
1217 | sps->quadtreeTUMaxDepthInter = m_param->tuQTMaxInterDepth; | |
1218 | sps->quadtreeTUMaxDepthIntra = m_param->tuQTMaxIntraDepth; | |
1219 | ||
1220 | sps->bUseSAO = m_param->bEnableSAO; | |
1221 | ||
1222 | sps->bUseAMP = m_param->bEnableAMP; | |
1223 | sps->maxAMPDepth = m_param->bEnableAMP ? g_maxCUDepth : 0; | |
1224 | ||
1225 | sps->maxDecPicBuffering = m_vps.maxDecPicBuffering; | |
1226 | sps->numReorderPics = m_vps.numReorderPics; | |
1227 | ||
1228 | sps->bUseStrongIntraSmoothing = m_param->bEnableStrongIntraSmoothing; | |
1229 | sps->bTemporalMVPEnabled = m_param->bEnableTemporalMvp; | |
1230 | ||
1231 | VUI& vui = sps->vuiParameters; | |
1232 | vui.aspectRatioInfoPresentFlag = !!m_param->vui.aspectRatioIdc; | |
1233 | vui.aspectRatioIdc = m_param->vui.aspectRatioIdc; | |
1234 | vui.sarWidth = m_param->vui.sarWidth; | |
1235 | vui.sarHeight = m_param->vui.sarHeight; | |
1236 | ||
1237 | vui.overscanInfoPresentFlag = m_param->vui.bEnableOverscanInfoPresentFlag; | |
1238 | vui.overscanAppropriateFlag = m_param->vui.bEnableOverscanAppropriateFlag; | |
1239 | ||
1240 | vui.videoSignalTypePresentFlag = m_param->vui.bEnableVideoSignalTypePresentFlag; | |
1241 | vui.videoFormat = m_param->vui.videoFormat; | |
1242 | vui.videoFullRangeFlag = m_param->vui.bEnableVideoFullRangeFlag; | |
1243 | ||
1244 | vui.colourDescriptionPresentFlag = m_param->vui.bEnableColorDescriptionPresentFlag; | |
1245 | vui.colourPrimaries = m_param->vui.colorPrimaries; | |
1246 | vui.transferCharacteristics = m_param->vui.transferCharacteristics; | |
1247 | vui.matrixCoefficients = m_param->vui.matrixCoeffs; | |
1248 | ||
1249 | vui.chromaLocInfoPresentFlag = m_param->vui.bEnableChromaLocInfoPresentFlag; | |
1250 | vui.chromaSampleLocTypeTopField = m_param->vui.chromaSampleLocTypeTopField; | |
1251 | vui.chromaSampleLocTypeBottomField = m_param->vui.chromaSampleLocTypeBottomField; | |
1252 | ||
1253 | vui.defaultDisplayWindow.bEnabled = m_param->vui.bEnableDefaultDisplayWindowFlag; | |
1254 | vui.defaultDisplayWindow.rightOffset = m_param->vui.defDispWinRightOffset; | |
1255 | vui.defaultDisplayWindow.topOffset = m_param->vui.defDispWinTopOffset; | |
1256 | vui.defaultDisplayWindow.bottomOffset = m_param->vui.defDispWinBottomOffset; | |
1257 | vui.defaultDisplayWindow.leftOffset = m_param->vui.defDispWinLeftOffset; | |
1258 | ||
1259 | vui.frameFieldInfoPresentFlag = !!m_param->interlaceMode; | |
1260 | vui.fieldSeqFlag = !!m_param->interlaceMode; | |
1261 | ||
1262 | vui.hrdParametersPresentFlag = m_param->bEmitHRDSEI; | |
1263 | ||
1264 | vui.timingInfo.numUnitsInTick = m_param->fpsDenom; | |
1265 | vui.timingInfo.timeScale = m_param->fpsNum; | |
1266 | } | |
1267 | ||
1268 | void Encoder::initPPS(PPS *pps) | |
1269 | { | |
1270 | bool bIsVbv = m_param->rc.vbvBufferSize > 0 && m_param->rc.vbvMaxBitrate > 0; | |
1271 | ||
1272 | if (!m_param->bLossless && (m_param->rc.aqMode || bIsVbv)) | |
1273 | { | |
1274 | pps->bUseDQP = true; | |
1275 | pps->maxCuDQPDepth = 0; /* TODO: make configurable? */ | |
1276 | } | |
1277 | else | |
1278 | { | |
1279 | pps->bUseDQP = false; | |
1280 | pps->maxCuDQPDepth = 0; | |
1281 | } | |
1282 | ||
1283 | pps->chromaCbQpOffset = m_param->cbQpOffset; | |
1284 | pps->chromaCrQpOffset = m_param->crQpOffset; | |
1285 | ||
1286 | pps->bConstrainedIntraPred = m_param->bEnableConstrainedIntra; | |
1287 | pps->bUseWeightPred = m_param->bEnableWeightedPred; | |
1288 | pps->bUseWeightedBiPred = m_param->bEnableWeightedBiPred; | |
1289 | pps->bTransquantBypassEnabled = m_param->bCULossless || m_param->bLossless; | |
1290 | pps->bTransformSkipEnabled = m_param->bEnableTransformSkip; | |
1291 | pps->bSignHideEnabled = m_param->bEnableSignHiding; | |
1292 | ||
1293 | /* If offsets are ever configured, enable bDeblockingFilterControlPresent and set | |
1294 | * deblockingFilterBetaOffsetDiv2 / deblockingFilterTcOffsetDiv2 */ | |
1295 | bool bDeblockOffsetInPPS = 0; | |
1296 | pps->bDeblockingFilterControlPresent = !m_param->bEnableLoopFilter || bDeblockOffsetInPPS; | |
1297 | pps->bPicDisableDeblockingFilter = !m_param->bEnableLoopFilter; | |
1298 | pps->deblockingFilterBetaOffsetDiv2 = 0; | |
1299 | pps->deblockingFilterTcOffsetDiv2 = 0; | |
1300 | ||
1301 | pps->bEntropyCodingSyncEnabled = m_param->bEnableWavefront; | |
1302 | } | |
1303 | ||
1304 | void Encoder::configure(x265_param *p) | |
1305 | { | |
1306 | this->m_param = p; | |
1307 | ||
1308 | if (p->keyframeMax < 0) | |
1309 | { | |
1310 | /* A negative max GOP size indicates the user wants only one I frame at | |
1311 | * the start of the stream. Set an infinite GOP distance and disable | |
1312 | * adaptive I frame placement */ | |
1313 | p->keyframeMax = INT_MAX; | |
1314 | p->scenecutThreshold = 0; | |
1315 | } | |
1316 | else if (p->keyframeMax <= 1) | |
1317 | { | |
1318 | // disable lookahead for all-intra encodes | |
1319 | p->bFrameAdaptive = 0; | |
1320 | p->bframes = 0; | |
1321 | } | |
1322 | if (!p->keyframeMin) | |
1323 | { | |
1324 | double fps = (double)p->fpsNum / p->fpsDenom; | |
1325 | p->keyframeMin = X265_MIN((int)fps, p->keyframeMax / 10); | |
1326 | } | |
1327 | p->keyframeMin = X265_MAX(1, X265_MIN(p->keyframeMin, p->keyframeMax / 2 + 1)); | |
1328 | ||
1329 | if (p->bBPyramid && !p->bframes) | |
1330 | p->bBPyramid = 0; | |
1331 | ||
1332 | /* Disable features which are not supported by the current RD level */ | |
1333 | if (p->rdLevel < 4) | |
1334 | { | |
1335 | if (p->psyRdoq > 0) /* impossible */ | |
1336 | x265_log(p, X265_LOG_WARNING, "--psy-rdoq disabled, requires --rdlevel 4 or higher\n"); | |
1337 | p->psyRdoq = 0; | |
1338 | } | |
1339 | if (p->rdLevel < 3) | |
1340 | { | |
1341 | if (p->bCULossless) /* impossible */ | |
1342 | x265_log(p, X265_LOG_WARNING, "--cu-lossless disabled, requires --rdlevel 3 or higher\n"); | |
1343 | if (p->bEnableTransformSkip) /* impossible */ | |
1344 | x265_log(p, X265_LOG_WARNING, "--tskip disabled, requires --rdlevel 3 or higher\n"); | |
1345 | p->bCULossless = p->bEnableTransformSkip = 0; | |
1346 | } | |
1347 | if (p->rdLevel < 2) | |
1348 | { | |
1349 | if (p->bDistributeModeAnalysis) /* not useful */ | |
1350 | x265_log(p, X265_LOG_WARNING, "--pmode disabled, requires --rdlevel 2 or higher\n"); | |
1351 | p->bDistributeModeAnalysis = 0; | |
1352 | ||
1353 | if (p->psyRd > 0) /* impossible */ | |
1354 | x265_log(p, X265_LOG_WARNING, "--psy-rd disabled, requires --rdlevel 2 or higher\n"); | |
1355 | p->psyRd = 0; | |
1356 | ||
1357 | if (p->bEnableRectInter) /* broken, not very useful */ | |
1358 | x265_log(p, X265_LOG_WARNING, "--rect disabled, requires --rdlevel 2 or higher\n"); | |
1359 | p->bEnableRectInter = 0; | |
1360 | } | |
1361 | ||
1362 | if (!p->bEnableRectInter) /* not useful */ | |
1363 | p->bEnableAMP = false; | |
1364 | ||
1365 | /* In 444, chroma gets twice as much resolution, so halve quality when psy-rd is enabled */ | |
1366 | if (p->internalCsp == X265_CSP_I444 && p->psyRd) | |
1367 | { | |
1368 | p->cbQpOffset += 6; | |
1369 | p->crQpOffset += 6; | |
1370 | } | |
1371 | ||
1372 | if (p->bLossless) | |
1373 | { | |
1374 | p->rc.rateControlMode = X265_RC_CQP; | |
1375 | p->rc.qp = 4; // An oddity, QP=4 is more lossless than QP=0 and gives better lambdas | |
1376 | p->bEnableSsim = 0; | |
1377 | p->bEnablePsnr = 0; | |
1378 | } | |
1379 | ||
1380 | if (p->rc.rateControlMode == X265_RC_CQP) | |
1381 | { | |
1382 | p->rc.aqMode = X265_AQ_NONE; | |
1383 | p->rc.bitrate = 0; | |
1384 | p->rc.cuTree = 0; | |
1385 | p->rc.aqStrength = 0; | |
1386 | } | |
1387 | ||
1388 | if (p->rc.aqMode == 0 && p->rc.cuTree) | |
1389 | { | |
1390 | p->rc.aqMode = X265_AQ_VARIANCE; | |
1391 | p->rc.aqStrength = 0.0; | |
1392 | } | |
1393 | ||
1394 | if (p->lookaheadDepth == 0 && p->rc.cuTree && !p->rc.bStatRead) | |
1395 | { | |
1396 | x265_log(p, X265_LOG_WARNING, "cuTree disabled, requires lookahead to be enabled\n"); | |
1397 | p->rc.cuTree = 0; | |
1398 | } | |
1399 | ||
1400 | if (p->rc.aqStrength == 0 && p->rc.cuTree == 0) | |
1401 | p->rc.aqMode = X265_AQ_NONE; | |
1402 | ||
1403 | if (p->rc.aqMode == X265_AQ_NONE && p->rc.cuTree == 0) | |
1404 | p->rc.aqStrength = 0; | |
1405 | ||
1406 | if (p->internalCsp != X265_CSP_I420) | |
1407 | { | |
1408 | x265_log(p, X265_LOG_WARNING, "!! HEVC Range Extension specifications are not finalized !!\n"); | |
1409 | x265_log(p, X265_LOG_WARNING, "!! This output bitstream may not be compliant with the final spec !!\n"); | |
1410 | } | |
1411 | ||
1412 | if (p->scalingLists && p->internalCsp == X265_CSP_I444) | |
1413 | { | |
1414 | x265_log(p, X265_LOG_WARNING, "Scaling lists are not yet supported for 4:4:4 color space\n"); | |
1415 | p->scalingLists = 0; | |
1416 | } | |
1417 | ||
1418 | if (p->interlaceMode) | |
1419 | x265_log(p, X265_LOG_WARNING, "Support for interlaced video is experimental\n"); | |
1420 | ||
1421 | if (p->rc.rfConstantMin > p->rc.rfConstant) | |
1422 | { | |
1423 | x265_log(m_param, X265_LOG_WARNING, "CRF min must be less than CRF\n"); | |
1424 | p->rc.rfConstantMin = 0; | |
1425 | } | |
1426 | ||
1427 | m_bframeDelay = p->bframes ? (p->bBPyramid ? 2 : 1) : 0; | |
1428 | ||
1429 | p->bFrameBias = X265_MIN(X265_MAX(-90, p->bFrameBias), 100); | |
1430 | ||
1431 | if (p->logLevel < X265_LOG_INFO) | |
1432 | { | |
1433 | /* don't measure these metrics if they will not be reported */ | |
1434 | p->bEnablePsnr = 0; | |
1435 | p->bEnableSsim = 0; | |
1436 | } | |
1437 | /* Warn users trying to measure PSNR/SSIM with psy opts on. */ | |
1438 | if (p->bEnablePsnr || p->bEnableSsim) | |
1439 | { | |
1440 | const char *s = NULL; | |
1441 | ||
1442 | if (p->psyRd || p->psyRdoq) | |
1443 | { | |
1444 | s = p->bEnablePsnr ? "psnr" : "ssim"; | |
1445 | x265_log(p, X265_LOG_WARNING, "--%s used with psy on: results will be invalid!\n", s); | |
1446 | } | |
1447 | else if (!p->rc.aqMode && p->bEnableSsim) | |
1448 | { | |
1449 | x265_log(p, X265_LOG_WARNING, "--ssim used with AQ off: results will be invalid!\n"); | |
1450 | s = "ssim"; | |
1451 | } | |
1452 | else if (p->rc.aqStrength > 0 && p->bEnablePsnr) | |
1453 | { | |
1454 | x265_log(p, X265_LOG_WARNING, "--psnr used with AQ on: results will be invalid!\n"); | |
1455 | s = "psnr"; | |
1456 | } | |
1457 | if (s) | |
1458 | x265_log(p, X265_LOG_WARNING, "--tune %s should be used if attempting to benchmark %s!\n", s, s); | |
1459 | } | |
1460 | ||
1461 | //========= set default display window ================================== | |
1462 | m_conformanceWindow.bEnabled = false; | |
1463 | m_conformanceWindow.rightOffset = 0; | |
1464 | m_conformanceWindow.topOffset = 0; | |
1465 | m_conformanceWindow.bottomOffset = 0; | |
1466 | m_conformanceWindow.leftOffset = 0; | |
1467 | ||
1468 | //======== set pad size if width is not multiple of the minimum CU size ========= | |
1469 | const uint32_t minCUSize = MIN_CU_SIZE; | |
1470 | if (p->sourceWidth & (minCUSize - 1)) | |
1471 | { | |
1472 | uint32_t rem = p->sourceWidth & (minCUSize - 1); | |
1473 | uint32_t padsize = minCUSize - rem; | |
1474 | p->sourceWidth += padsize; | |
1475 | ||
1476 | /* set the confirmation window offsets */ | |
1477 | m_conformanceWindow.bEnabled = true; | |
1478 | m_conformanceWindow.rightOffset = padsize; | |
1479 | } | |
1480 | ||
1481 | //======== set pad size if height is not multiple of the minimum CU size ========= | |
1482 | if (p->sourceHeight & (minCUSize - 1)) | |
1483 | { | |
1484 | uint32_t rem = p->sourceHeight & (minCUSize - 1); | |
1485 | uint32_t padsize = minCUSize - rem; | |
1486 | p->sourceHeight += padsize; | |
1487 | ||
1488 | /* set the confirmation window offsets */ | |
1489 | m_conformanceWindow.bEnabled = true; | |
1490 | m_conformanceWindow.bottomOffset = padsize; | |
1491 | } | |
1492 | } |