原創博客,轉載請聯繫博主!linux
題外話:自學了快兩個月的Perl語言,原本打算寫兩篇基礎介紹的博文來科普一下一些小技巧,可是仔細想一想仍是沒有必要了吧,畢竟如今不管是在用Perl5仍是Perl6的人都是小衆了,回頭寫幾個中小型的項目再拿出來深刻說會更好點,畢竟Perl的學習曲線比較陡峭也不是幾篇博文能說完的事兒,好了廢話到此爲止,下文進入正題!git
有關於提到的加解碼器THOR的源代碼託管在github上:https://github.com/cisco/thorgithub
Github上面這個項目的文檔寫得不是通常的簡潔,我先大概整理下這個編譯後的二進制文件大概用法再討論裏面的構造(linux平臺下編譯):算法
build/Thorenc -if input_filename -of output_filename [options...]ide
build/Thordex input_filename output_filename函數
雖然編碼和解碼的參數用法不是很對稱,但仍是好在比較簡明,其實解碼器的參數是固定的,可是編碼過程的參數比較複雜,以下所示:學習
static void add_param_to_list(param_list *list, char *name, char *default_string, int type, void *value)
{
list->params[list->num].name = name;
list->params[list->num].default_string = default_string;
list->params[list->num].type = type;
list->params[list->num].value = value;
list->num++;
}
.....
add_param_to_list(&list, "-cf", NULL, ARG_FILENAME, NULL); add_param_to_list(&list, "-if", NULL, ARG_FILENAME, ¶ms->infilestr); add_param_to_list(&list, "-ph", "0", ARG_INTEGER, ¶ms->file_headerlen); add_param_to_list(&list, "-fh", "0", ARG_INTEGER, ¶ms->frame_headerlen); add_param_to_list(&list, "-of", NULL, ARG_FILENAME, ¶ms->outfilestr); add_param_to_list(&list, "-rf", NULL, ARG_FILENAME, ¶ms->reconfilestr); add_param_to_list(&list, "-stat", NULL, ARG_FILENAME, ¶ms->statfilestr); add_param_to_list(&list, "-n", "600", ARG_INTEGER, ¶ms->num_frames); add_param_to_list(&list, "-skip", "0", ARG_INTEGER, ¶ms->skip); add_param_to_list(&list, "-width", "1920", ARG_INTEGER, ¶ms->width); add_param_to_list(&list, "-height", "1080", ARG_INTEGER, ¶ms->height); add_param_to_list(&list, "-qp", "32", ARG_INTEGER, ¶ms->qp); add_param_to_list(&list, "-f", "60", ARG_FLOAT, ¶ms->frame_rate); add_param_to_list(&list, "-lambda_coeffI", "1.0", ARG_FLOAT, ¶ms->lambda_coeffI); add_param_to_list(&list, "-lambda_coeffP", "1.0", ARG_FLOAT, ¶ms->lambda_coeffP); add_param_to_list(&list, "-lambda_coeffB", "1.0", ARG_FLOAT, ¶ms->lambda_coeffB); add_param_to_list(&list, "-early_skip_thr", "0.0", ARG_FLOAT, ¶ms->early_skip_thr); add_param_to_list(&list, "-enable_tb_split", "0", ARG_INTEGER, ¶ms->enable_tb_split); add_param_to_list(&list, "-enable_pb_split", "0", ARG_INTEGER, ¶ms->enable_pb_split); add_param_to_list(&list, "-max_num_ref", "1", ARG_INTEGER, ¶ms->max_num_ref); add_param_to_list(&list, "-HQperiod", "1", ARG_INTEGER, ¶ms->HQperiod); add_param_to_list(&list, "-num_reorder_pics", "0", ARG_INTEGER, ¶ms->num_reorder_pics); add_param_to_list(&list, "-dqpP", "0", ARG_INTEGER, ¶ms->dqpP); add_param_to_list(&list, "-dqpB", "0", ARG_INTEGER, ¶ms->dqpB); add_param_to_list(&list, "-mqpP", "1.0", ARG_FLOAT, ¶ms->mqpP); add_param_to_list(&list, "-mqpB", "1.0", ARG_FLOAT, ¶ms->mqpB); add_param_to_list(&list, "-dqpI", "0", ARG_INTEGER, ¶ms->dqpI); add_param_to_list(&list, "-intra_period", "0", ARG_INTEGER, ¶ms->intra_period); add_param_to_list(&list, "-intra_rdo", "0", ARG_INTEGER, ¶ms->intra_rdo); add_param_to_list(&list, "-rdoq", "0", ARG_INTEGER, ¶ms->rdoq); add_param_to_list(&list, "-max_delta_qp", "0", ARG_INTEGER, ¶ms->max_delta_qp); add_param_to_list(&list, "-encoder_speed", "0", ARG_INTEGER, ¶ms->encoder_speed); add_param_to_list(&list, "-deblocking", "1", ARG_INTEGER, ¶ms->deblocking); add_param_to_list(&list, "-clpf", "1", ARG_INTEGER, ¶ms->clpf); add_param_to_list(&list, "-snrcalc", "1", ARG_INTEGER, ¶ms->snrcalc); add_param_to_list(&list, "-use_block_contexts", "0", ARG_INTEGER, ¶ms->use_block_contexts); add_param_to_list(&list, "-enable_bipred", "0", ARG_INTEGER, ¶ms->enable_bipred);
...
這些是編碼器進入編碼循環以前真正的參數,若是並無在argv裏明確指明參數的值,那麼就會在這裏使參數被賦予默認缺省值,具體來說:測試
static int parse_params(int argc, char **argv, enc_params *params, param_list *list)優化
這個函數是從命令行調用參數中獲得參數值的函數ui
static void add_param_to_list(param_list *list, char *name, char *default_string, int type, void *value)
這個函數是給函數列表賦以默認值和約束參數類型的函數
參數的讀取先到這裏,下文對參數會有更細的分析和補充。Thorenc能夠編碼的是一種後綴爲.y4m格式的文件,與傳統格式的視頻文件不一樣,這裏看下.y4m格式文件的具體格式參數:
y4m格式視頻文件文件最開頭是以一段長度爲10的ascii字符串"YUV4MPEG2"做爲魔數簽名,接着是一個空格(0x20)做爲分隔符,接下來的數據流是關於這個視頻文件的各類參數信息:
W--視頻單畫面幀的寬度 e.g.W1080
H--視頻單畫面幀的高度 e.g.H1920
F--視頻單畫面幀的頻率 e.g.F24:1表明24幀每秒,F25:1表明25幀每秒
C--色彩空間,常見的有4:4:4,4:2:2,4:2:0表明了Y值與UV值的交叉程度,具體差異有不少文章科普篇幅較大這裏暫不贅述
A--像素寬高比
在每個視頻的參數之間也都有一個空格做爲間隔符(0x20),在最後一個(0x0A)間隔符以後是真正原始的幀數據,大小以下所示:
C444--width*height*3
C422--width*height*2
C420--width*height*3/2
//解析y4m文件參數的switch-case
while (pos < len && buf[pos] != '\n') { switch (buf[pos++]) { case 'W': params->width = strtol(buf+pos, &end, 10); pos = end-buf+1; break; case 'H': params->height = strtol(buf+pos, &end, 10); pos = end-buf+1; break; case 'F': den = strtol(buf+pos, &end, 10); pos = end-buf+1; num = strtol(buf+pos, &end, 10); pos = end-buf+1; params->frame_rate = (double)den/num; break; case 'I': if (buf[pos] != 'p') { fprintf(stderr, "Only progressive input supported\n"); return NULL; } break; case 'C': if (strcmp(buf+pos, "C420")) { } /* Fallthrough */ case 'A': /* Ignored */ case 'X': default: while (buf[pos] != ' ' && buf[pos] != '\n' && pos < len) pos++; break; } }
而後接下來是視頻解碼過程當中必須清楚的幾個概念:
SB(Super Block 超級塊):64*64的亮度像素(Luma Pixel)單元組成的塊,能夠被分解爲CB。 ///關於亮度像素和色彩像素(Chroma Pixel)的概念見上文色彩空間C的定義,具體分佈下文默認爲4:2:0的分佈,瞭解細節見wiki和google.
CB(Coding Block 編碼塊):8*8的亮度像素單元組成的塊,是超級塊的子單元。
PB(Prediction Block 預測塊):是編碼塊的一種子塊,一個編碼塊能夠分爲1,2或者4個相同的預測塊。
TB(Transform Block 變換塊):是編碼塊的另外一種子塊,一個編碼塊能夠分爲1或者4個相同的變換塊。
邊界問題: 因爲屏幕的分辨率種類繁多,有許多尺寸不能按超級塊完整地進行等分,例如1920*1080分辨率的屏幕,在縱向上1080=64*16+56致使最後會剩餘一個長方形的不完整超級塊:
----------------〉〉〉
如上圖所示,具體的解決的辦法是將64*56的超級塊分爲兩對32*32的塊和32*24的塊,32*24的塊再具體對分再分辦直到最後只有8*8塊做爲編碼塊,具體實現源碼中有完總體現。
接下來是分幀和編碼循環:
在thor中的main函數中全部真正編碼文件的過程都體現爲如下幾段代碼:
/* Read input frame */ fseek(infile, frame_num*(frame_size+params->frame_headerlen)+params->file_headerlen+params->frame_headerlen, SEEK_SET); read_yuv_frame(&orig,width,height,infile); orig.frame_num = encoder_info.frame_info.frame_num; /* Encode frame */ start_bits = get_bit_pos(&stream); encode_frame(&encoder_info); rec_available[rec_buffer_idx]=1; end_bits = get_bit_pos(&stream); num_bits = end_bits-start_bits; num_encoded_frames++;
在thor中一直有一個全局的對象stream,編碼解碼的過程都是圍繞stream而展開的,包括將和編碼有關的參數先寫入stream中,隨後將每一幀編碼後的結果都寫入stream,在stream使用一個經典的「滑窗」結構來進行二進制數據的讀/寫,orig是從原始的yuv文件讀取獲得的幀數據,編碼的工做也是以orig爲基礎進行的。
encode_frame(&encoder_info);
其實真正編碼的過程是一個很是複雜的過程,也是當前全部H.264行業都在關注的一項龐大的技術,之後會寫幾篇博文深刻探討相關技術。在thor中最後一步是計算視頻的psnr,這是一個評價視頻編碼標準的重要參數,也是做爲考量算法效率的重要反饋結果。
snr_yuv(&psnr,&orig,&rec[rec_buffer_idx],height,width,input_stride_y,input_stride_c);
分析完psnr參數,整個函數代碼就進入了收尾的階段:關句柄,收內存,thor的工做基本也就完成了,thor和openh264相比整個項目小了不少,可是也少了一些對OS的區分支持,有一些代碼須要優化,和一些測試代碼的刪減,總的來講,不是作的很結構化的一個項目,用軟件工程的說法就是模塊耦合度過高了,我想這也是thor至今有些流產了的緣由吧,可是做爲研究仍是很是有價值。