就要进入公司工作了,要接收眭师兄的编码工作,一直对H264和平台使用的T264的对应关系不是很清楚,所知道的就是很有限的几个算法,总体的概念还需要加强。这几天终于把前面的部分整合起来了。
所用的t264的版本是师兄们改过的,对原始的版本进行了一定程度的简化。但是总体结构还是一样的。
进入mian函数,在直接看主要的return encode();->len = C264enc_Frame(t, buf, dst );-> len = T264_encode_frame(t, nal_pos, nal_num, (uint8_t*)dst, dst_size );
这就到了按帧编码的部分了,在这个函数中主要看一个大循环。前面的很多初始化我们可以先不要浪费过多精力。虽然这个在整个看代码过程中是最头痛的,太多的结构体,太多的出错异常等处理,再加上H264本身的各种处理很容易让人眼花缭乱。个人认为前面的初始化要结合后面的函数调用来看就明确了,首先对H264的数据分层结构有个大概的了解就看主线,这样就会让人有成就感。而且在后面看到的重要函数在回去看初始化就知道前面用结构体,复杂的结构体来初始化以及各种处理是多么的明智了(这也可能有更好的办法)。
那么我们就先看这个函数中的大循环吧。
for(i = 0 ; i < t->mb_height ; i ++)//从第一行到最后一行,分宏块
{
T264_mb_load_context_all(t, i, t->mb_width);//加载预测信息,为后面的预测编码做准备
/*QueryPerformanceCounter(&t3);*/
//t1 = CLK_getltime();
for(k = 0 ; k < t->mb_width ; k ++)//从第一列到最后一列宏块初始化
{
T264_mb_mode_decision_all(t, k, skip_thrsh);//预测模式选择
//T264_mb_save_context_ME(t, k);
T264_mb_save_context_all(t, k);
if( k != t->mb_width-1)
T264_mb_reset_context_all(t, k+1);
t->sad_all += t->mb_line[k].sad;
}
//QueryPerformanceCounter(&t4);
//pred_time += t4.LowPart - t3.LowPart;
//t2 = CLK_getltime();
//pred_time += t2-t1;
for(k = 0 ; k < t->mb_width; k ++)
{
T264_mb_encode(t, k); //宏块编码,从第一列到最后一列
}
//QueryPerformanceCounter(&t1);
//encode_time += t1.LowPart - t4.LowPart;
//t3 = CLK_getltime();
//encode_time +=t3-t2;
for(k = 0 ; k < t->mb_width ; k ++)
{
T264_mb_encode_post(t, k);
/** save the rec frame */
T264_mb_save_context_all(t,k);
循环并未完成,但是主要的处理已经结束了,后面的就是对编码以后的处理了。当编码一个宏块后,它的信息就要进入预测的范围内,就是来更新预测集里面的宏块这样的处理。这部分的主要注释见上。
那么现在就进入T264_mb_encode(t, k);函数意义:对t帧的第k个宏块进行编码。
void
T264_mb_encode(C264_t* t, int32_t k) //在这里进行宏块编码
{
if(t->mb_line[k].mb_mode == P_MODE)//p模式,帧间
{
T264_encode_inter_y(t, k);
T264_encode_inter_uv(t, k);
t->stat.p_block_num[t->mb_line[k].mb_part] ++;
}
else if(t->mb_line[k].mb_mode == P_SKIP)
{
t->stat.skip_block_num++;
}
//else if (t->mb_line[k].mb_mode == B_MODE)
//{
// T264_encode_inter_y(t);
// T264_encode_interb_uv(t);
// t->stat.p_block_num[0] ++;
//}
else if (t->mb_line[k].mb_mode == I_4x4 || t->mb_line[k].mb_mode == I_16x16)
{
T264_encode_intra_y(t, k);//帧内4x4或是16x16
//
// Chroma
//
T264_mode_decision_intra_uv(t, k);
T264_encode_intra_uv(t, k);
t->stat.i_block_num[t->mb_line[k].mb_mode] ++;
}
}
主线函数都位于t264enc.c。进入该函数后根据先前判断的帧类型以及预测类型选择不同的编码函数。
T264_mode_decision_intra_uv(t, k);不要被这个函数的名称迷惑,类型已经确定。自认为这个函数的名称取的并不
好,我们进入该函数。
uint32_t
T264_mode_decision_intra_uv(_RW C264_t* t, int32_t k)
{
DECLARE_ALIGNED_MATRIX(pred8x8u, 8, 8, uint8_t, CACHE_SIZE);
DECLARE_ALIGNED_MATRIX(pred8x8v, 8, 8, uint8_t, CACHE_SIZE);
DECLARE_ALIGNED_MATRIX(topcacheu, 1, 8 + CACHE_SIZE, uint8_t, CACHE_SIZE);
DECLARE_ALIGNED_MATRIX(leftcacheu, 1, 8 + CACHE_SIZE, uint8_t, CACHE_SIZE);
DECLARE_ALIGNED_MATRIX(topcachev, 1, 8 + CACHE_SIZE, uint8_t, CACHE_SIZE);
DECLARE_ALIGNED_MATRIX(leftcachev, 1, 8 + CACHE_SIZE, uint8_t, CACHE_SIZE);
uint32_t sad8x8 =0xffffffff;
uint8_t* pred8x8freeu0 = pred8x8u;
uint8_t* pred8x8freeu1 = t->mb_line[k].pred_i8x8u;
uint8_t* pred8x8freev0 = pred8x8v;
uint8_t* pred8x8freev1 = t->mb_line[k].pred_i8x8v;
int32_t modes;
int32_t bestmode;
int32_t preds[9];
int32_t i;
uint8_t* top_u, *left_u;
uint8_t* top_v, *left_v;
static const uint8_t fixmode[] =
{
Intra_8x8_DC,
Intra_8x8_LEFT,
Intra_8x8_TOP,
Intra_8x8_PLANE,
Intra_8x8_DC,
Intra_8x8_DC,
Intra_8x8_DC
};
top_u = &topcacheu[CACHE_SIZE];
top_v = &topcachev[CACHE_SIZE];
left_u = &leftcacheu[CACHE_SIZE];
left_v = &leftcachev[CACHE_SIZE];
T264_intra_8x8_available(t, preds, &modes, top_u, left_u, top_v, left_v, k);
for(i = 0 ; i < modes ; i ++)//按模式计算sad来决定模式
{
int32_t mode = preds[i];
uint32_t sad;
t->pred8x8[mode](
pred8x8freeu1,
8,
top_u,
left_u);
t->pred8x8[mode](
pred8x8freev1,
8,
top_v,
left_v);
sad = t->cmp[MB_8x8](t->mb_line[k].src_u, t->stride_uv, pred8x8freeu1, 8) +
t->cmp[MB_8x8](t->mb_line[k].src_v, t->stride_uv, pred8x8freev1, 8) +
t->mb_line[k].lambda * eg_size_ue(t->bs, fixmode[mode]);
if (sad < sad8x8)
{
SWAP(uint8_t, pred8x8freeu0, pred8x8freeu1);
SWAP(uint8_t, pred8x8freev0, pred8x8freev1);
sad8x8 = sad;
bestmode = mode;
}
}
if (pred8x8freeu0 != t->mb_line[k].pred_i8x8u)
{
memcpy(t->mb_line[k].pred_i8x8u, pred8x8freeu0, sizeof(uint8_t) * 8 * 8);
}
if (pred8x8freev0 != t->mb_line[k].pred_i8x8v)
{
memcpy(t->mb_line[k].pred_i8x8v, pred8x8freev0, sizeof(uint8_t) * 8 * 8);
}
//fixed prediction mode DCLEFT DCTOP DC128 = DC
t->mb_line[k].mb_mode_uv = fixmode[bestmode];
return sad8x8; //选择较小的残差
}
t->pred8x8[mode](pred8x8freeu1, 8 top_u, left_u);我们以这个来说明前面的初始化。在t264enc.c中的781行:
T264_init_cpu(C264_t* t)函数的
t->pred16x16[Intra_16x16_TOP] = T264_predict_16x16_mode_0_c;
t->pred16x16[Intra_16x16_LEFT] = T264_predict_16x16_mode_1_c;
t->pred16x16[Intra_16x16_DC] = T264_predict_16x16_mode_2_c;
t->pred16x16[Intra_16x16_PLANE] = T264_predict_16x16_mode_3_c;
t->pred16x16[Intra_16x16_DCTOP] = T264_predict_16x16_mode_20_c;
t->pred16x16[Intra_16x16_DCLEFT] = T264_predict_16x16_mode_21_c;
t->pred16x16[Intra_16x16_DC128] = T264_predict_16x16_mode_22_c;
在对应在T264_mode_decision_intra_uv(_RW C264_t* t, int32_t k)中的
static const uint8_t fixmode[] =
{
Intra_8x8_DC,
Intra_8x8_LEFT,
Intra_8x8_TOP,
Intra_8x8_PLANE,
Intra_8x8_DC,
Intra_8x8_DC,
Intra_8x8_DC
};就知道了这个过程。首先让帧结构体C264_t:t的成员变量指针只想几个预测函数(在predict.c中),再在定
义了几个modes的静态结构后分别对比几种预测模式的残差大小,取最小的。此时保存预测类型,保存残差回去编码,就退后到了函数T264_mb_encode(C264_t* t, int32_t k)中的T264_encode_intra_uv(t, k);再向上返回就可以看见dct变换和量化了,在此就不往下说明了。
值得说明的是,也许会怀疑在前面的帧间和帧内的亮度预测中并没有这个名字为类型选择而实质上进行残差选择的函数,这可以在色度的这个函数得到启发,再去寻找他们的相似函数,在T264_encode_frame函数,t264enc.c的1552行调用的T264_mb_mode_decision_all满足了我们这样的寻找。
这样我们就说明了编码的前部分工作。之后就是了解游程编码,大概码率控制也在后面的部分。下部分就是了解这部分代码了,而首先就是看看标准。
毕厚杰的新一代视频压缩标准-H.264/AVC很好。人民邮电出版,在此推荐一下。