classLookahead : public JobProvider { public: // 输入队列,PTS顺序 PicList m_inputQueue; // input pictures in order received // 输出队列,DTS顺序 PicList m_outputQueue; // pictures to be encoded, in encode order }
/* The number of frames that must be queued in the lookahead before it may * make slice decisions. Increasing this value directly increases the encode * latency. The longer the queue the more optimally the lookahead may make * slice decisions, particularly with b-adapt 2. When cu-tree is enabled, * the length of the queue linearly increases the effectiveness of the * cu-tree analysis. Default is 40 frames, maximum is 250 */ int lookaheadDepth; // 进行预处理必须的帧数,只有待处理帧数大于该值才会进行处理
voidLookahead::checkLookaheadQueue(int &frameCnt) { /* determine if the lookahead is (over) filled enough for frames to begin to * be consumed by frame encoders */ if (!m_filled) { if (!m_param->bframes & !m_param->lookaheadDepth) m_filled = true; /* zero-latency */ elseif (frameCnt >= m_param->lookaheadDepth + 2 + m_param->bframes) m_filled = true; /* full capacity plus mini-gop lag */// 当剩余输入的帧数量达到一定值,才会开启编码,为了避免encode阻塞 }
/* perform pre-analysis on frames which need it, using a bonded task group */ if (pre.m_jobTotal) { // 线程池分发任务,异步处理,进行1/2下采样,并计算帧内预测的成本估计,frames[i].costEst[0][0] if (m_pool) pre.tryBondPeers(*m_pool, pre.m_jobTotal); pre.processTasks(-1); // 等待任务组处理完成 pre.waitForExit(); }
if (m_bBatchMotionSearch) { // 成本估计任务组,frames为数据源 CostEstimateGroup estGroup(*this, frames); // b表示第几帧,从1开始,由于第1帧是上一个非B帧,所以从第2帧开始计算 for (int b = 2; b < numFrames; b++) { // 每帧预测的帧范围, 前向参考帧范围[b - (bframes + 1), b),后向参考帧范围(b, b + (bframes + 1)] for (int i = 1; i <= m_param->bframes + 1; i++) { int p0 = b - i; // 前向参考帧 if (p0 < 0) continue;
/* Skip search if already done */ if (frames[b]->lowresMvs[0][i][0].x != 0x7FFF) continue;
/* perform search to p1 at same distance, if possible */ int p1 = b + i; // 后向参考帧,前向参考帧和后向参考帧按照相同的距离去搜索 if (p1 >= numFrames || frames[b]->lowresMvs[1][i][0].x != 0x7FFF) p1 = b;
estGroup.add(p0, p1, b); // 添加任务 } } /* auto-disable after the first batch if pool is small */ m_bBatchMotionSearch &= m_pool->m_numWorkers >= 4; estGroup.finishBatch(); // 异步处理所有任务,阻塞等待任务完成
if (m_bBatchFrameCosts) { /* pre-calculate all frame cost estimates, using many worker threads */ for (int b = 2; b < numFrames; b++) { // 前向参考帧范围[b - (bframes + 1), b) for (int i = 1; i <= m_param->bframes + 1; i++) { if (b < i) continue;
/* only measure frame cost in this pass if motion searches * are already done */ if (frames[b]->lowresMvs[0][i][0].x == 0x7FFF) continue;
int p0 = b - i; // 前向参考帧,前向参考帧和后向参考帧按照不同的距离去搜索 // 后向参考帧范围[b, b + (bframes )] for (int j = 0; j <= m_param->bframes; j++) { int p1 = b + j; // 后向参考帧 if (p1 >= numFrames) break;
/* ensure P1 search is done */ if (j && frames[b]->lowresMvs[1][j][0].x == 0x7FFF) continue;
/* ensure frame cost is not done */ if (frames[b]->costEst[i][j] >= 0) continue;
estGroup.add(p0, p1, b); // 添加任务 } } }
/* auto-disable after the first batch if the pool is not large */ m_bBatchFrameCosts &= m_pool->m_numWorkers > 12; estGroup.finishBatch(); // 异步处理所有任务,阻塞等待任务完成 } }
场景切换
检测决策数组中第一帧是否和上个非B帧是场景切换,如果是则需要重新定义上一个参考帧,本次决策退出
1 2 3 4 5 6 7 8 9 10 11
bool isScenecut = false;
/* When scenecut threshold is set, use scenecut detection for I frame placements */ if (!m_param->bHistBasedSceneCut || (m_param->bHistBasedSceneCut && frames[1]->bScenecut)) isScenecut = scenecut(frames, 0, 1, true, origNumFrames); // 检测当前frames[0]和frames[1]是否是场景切换
// 将第一段连续B帧后面的帧类型都重置为0 for (int j = resetStart; j <= numFrames; j++) { frames[j]->sliceType = X265_TYPE_AUTO; /* If any frame marked as scenecut is being restarted for sliceDecision, * undo scene Transition flag */ if (j <= maxp1 && frames[j]->bScenecut && m_isSceneTransition) m_isSceneTransition = false; } }
# numFrames = 20, bframes = 4 [ (前19个帧最优路径) p (前18个帧最优路径) B P (前17个帧最优路径) BB p (前16个帧最优路径) BBB p (前15个帧最优路径) BBBB p ]
计算上面每条路径的最优成本,根据上面已经计算过的参考帧成本
Example: numFrames = 1, bframes = 4 [ P ] numFrames = 2, bframes = 4 [ P P B P ] numFrames = 3, bframes = 4 [ BP P P B P BB P ] numFrames = 4, bframes = 4 [ BBP P BP B P P BB P BBB P ] numFrames = 5, bframes = 4 [ BBBP P BBP B P BP BB P P BBB P BBBB P ] numFrames = 6, bframes = 4 [ BBBBP P BBBP B P BBP BB P BP BBB P P BBBB P ]
// 收集PTS int64_t pts[X265_BFRAME_MAX + 1]; for (int i = 0; i <= bframes; i++) { Frame *curFrame; curFrame = m_inputQueue.popFront(); pts[i] = curFrame->m_pts; maxSearch--; } m_inputLock.release();
m_outputLock.acquire(); /* add non-B to output queue */ int idx = 0; // m_reorderedPts为DTS list[bframes]->m_reorderedPts = pts[idx++]; m_outputQueue.pushBack(*list[bframes]); //先将最后的非B帧入队
// 加入B参考帧 if (brefs) { for (int i = 0; i < bframes; i++) { if (list[i]->m_lowres.sliceType == X265_TYPE_BREF) { list[i]->m_reorderedPts = pts[idx++]; m_outputQueue.pushBack(*list[i]); } } }
// 加入B帧 for (int i = 0; i < bframes; i++) { /* push all the B frames into output queue except B-ref, which already pushed into output queue */ if (list[i]->m_lowres.sliceType != X265_TYPE_BREF) { list[i]->m_reorderedPts = pts[idx++]; m_outputQueue.pushBack(*list[i]); } }