原创博客,转载请联系博主!

题外话:自学了快两个月的Perl语言,本来打算写两篇基础介绍的博文来科普一下一些小技巧,但是仔细想想还是没有必要了吧,毕竟现在无论是在用Perl5还是Perl6的人都是小众了,回头写几个中小型的项目再拿出来深入说会更好点,毕竟Perl的学习曲线比较陡峭也不是几篇博文能说完的事儿,好了废话到此为止,下文进入正题!

有关于提到的加解码器THOR的源代码托管在github上:https://github.com/cisco/thor

Github上面这个项目的文档写得不是一般的简洁,我先大概整理下这个编译后的二进制文件大概用法再讨论里面的构造(linux平台下编译):

build/Thorenc -if input_filename -of output_filename [options...]

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, &params->infilestr);
add_param_to_list(&list, "-ph", "0", ARG_INTEGER, &params->file_headerlen);
add_param_to_list(&list, "-fh", "0", ARG_INTEGER, &params->frame_headerlen);
add_param_to_list(&list, "-of", NULL, ARG_FILENAME, &params->outfilestr);
add_param_to_list(&list, "-rf", NULL, ARG_FILENAME, &params->reconfilestr);
add_param_to_list(&list, "-stat", NULL, ARG_FILENAME, &params->statfilestr);
add_param_to_list(&list, "-n", "600", ARG_INTEGER, &params->num_frames);
add_param_to_list(&list, "-skip", "0", ARG_INTEGER, &params->skip);
add_param_to_list(&list, "-width", "1920", ARG_INTEGER, &params->width);
add_param_to_list(&list, "-height", "1080", ARG_INTEGER, &params->height);
add_param_to_list(&list, "-qp", "32", ARG_INTEGER, &params->qp);
add_param_to_list(&list, "-f", "60", ARG_FLOAT, &params->frame_rate);
add_param_to_list(&list, "-lambda_coeffI", "1.0", ARG_FLOAT, &params->lambda_coeffI);
add_param_to_list(&list, "-lambda_coeffP", "1.0", ARG_FLOAT, &params->lambda_coeffP);
add_param_to_list(&list, "-lambda_coeffB", "1.0", ARG_FLOAT, &params->lambda_coeffB);
add_param_to_list(&list, "-early_skip_thr", "0.0", ARG_FLOAT, &params->early_skip_thr);
add_param_to_list(&list, "-enable_tb_split", "0", ARG_INTEGER, &params->enable_tb_split);
add_param_to_list(&list, "-enable_pb_split", "0", ARG_INTEGER, &params->enable_pb_split);
add_param_to_list(&list, "-max_num_ref", "1", ARG_INTEGER, &params->max_num_ref);
add_param_to_list(&list, "-HQperiod", "1", ARG_INTEGER, &params->HQperiod);
add_param_to_list(&list, "-num_reorder_pics", "0", ARG_INTEGER, &params->num_reorder_pics);
add_param_to_list(&list, "-dqpP", "0", ARG_INTEGER, &params->dqpP);
add_param_to_list(&list, "-dqpB", "0", ARG_INTEGER, &params->dqpB);
add_param_to_list(&list, "-mqpP", "1.0", ARG_FLOAT, &params->mqpP);
add_param_to_list(&list, "-mqpB", "1.0", ARG_FLOAT, &params->mqpB);
add_param_to_list(&list, "-dqpI", "0", ARG_INTEGER, &params->dqpI);
add_param_to_list(&list, "-intra_period", "0", ARG_INTEGER, &params->intra_period);
add_param_to_list(&list, "-intra_rdo", "0", ARG_INTEGER, &params->intra_rdo);
add_param_to_list(&list, "-rdoq", "0", ARG_INTEGER, &params->rdoq);
add_param_to_list(&list, "-max_delta_qp", "0", ARG_INTEGER, &params->max_delta_qp);
add_param_to_list(&list, "-encoder_speed", "0", ARG_INTEGER, &params->encoder_speed);
add_param_to_list(&list, "-deblocking", "1", ARG_INTEGER, &params->deblocking);
add_param_to_list(&list, "-clpf", "1", ARG_INTEGER, &params->clpf);
add_param_to_list(&list, "-snrcalc", "1", ARG_INTEGER, &params->snrcalc);
add_param_to_list(&list, "-use_block_contexts", "0", ARG_INTEGER, &params->use_block_contexts);
add_param_to_list(&list, "-enable_bipred", "0", ARG_INTEGER, &params->enable_bipred);
...

这些是编码器进入编码循环之前真正的参数,如果并没有在argv里明确指明参数的值,那么就会在这里使参数被赋予默认缺省值,具体来讲:

static int parse_params(int argc, char **argv, enc_params *params, param_list *list)

这个函数是从命令行调用参数中得到参数值的函数

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至今有些流产了的原因吧,但是作为研究还是非常有价值。

<开源项目分析>Cisco的开源视频加解码器THOR(H.264解码)的更多相关文章

  1. JavaScript判断视频编码是否为h.264

    1.视频编码是什么? 现在视频编码主流是h.264,对应着输入格式为AVC H.264/AVC是2003年制定的视频编码压缩标准 ,集中了以往标准的优点,并吸收了以往标准制定中积累的经验,采用简洁设计 ...

  2. H.264开源解码器评测

    转自:http://wmnmtm.blog.163.com/blog/static/38245714201142883032575/ 要播放HDTV,就首先要正确地解开封装,然后进行视频音频解码.所以 ...

  3. 【图像处理】H.264开源解码器评测

    转自:http://wmnmtm.blog.163.com/blog/static/38245714201142883032575/ 要播放HDTV,就首先要正确地解开封装,然后进行视频音频解码.所以 ...

  4. H.264视频在android手机端的解码与播放(转)

    随着无线网络和智能手机的发展,智能手机与人们日常生活联系越来越紧密,娱乐.商务应用.金融应用.交通出行各种功能的软件大批涌现,使得人们的生活丰富多彩.快捷便利,也让它成为人们生活中不可取代的一部分.其 ...

  5. FFmpeg的H.264解码器源代码简单分析:解析器(Parser)部分

    ===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...

  6. 基于RTP的h.264视频传输系统设计(一)

    一.H.264 的层次介绍 H.264 定义三个层次,每一个层次支持一组特定的编码功能.而且按照各个层次指定所指定的功能.基础层次(baselineprofile)支持I 帧和 P 帧[1]的帧内和帧 ...

  7. 【H.264/AVC视频编解码技术具体解释】十三、熵编码算法(4):H.264使用CAVLC解析宏块的残差数据

    <H.264/AVC视频编解码技术具体解释>视频教程已经在"CSDN学院"上线,视频中详述了H.264的背景.标准协议和实现,并通过一个实战project的形式对H.2 ...

  8. Android IOS WebRTC 音视频开发总结(七九)-- WebRTC选择H.264的四大理由

    本文主要介绍WebRTC选择H.264的理由(我们翻译和整理的,译者:weizhenwei,校验:blacker),最早发表在[编风网] 支持原创,转载必须注明出处,欢迎关注我的微信公众号blacke ...

  9. FFmpeg的H.264解码器源代码简单分析:概述

    ===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...

随机推荐

  1. jQuery的$.fn使用

    jquery中文网为您提供jQuery的$.fn使用等资源,欢迎您收藏本站,我们将为您提供最新的jQuery的$.fn使用资源 $.fn是指jquery的命名空间,加上fn上的方法及属性,会对jque ...

  2. d3系列2--api攻坚战02

    <html> <head> <style type="text/css"> .area{ fill:steelblue; } </styl ...

  3. 面向对象-Object类

    一.Object类中的equals()方法 equals(Object obj) :指示其它某个对象是否与此对象"相等". 返回值类型是boolean Oblect类中的equal ...

  4. java之CGLIB动态代理

    © 版权声明:本文为博主原创文章,转载请注明出处 CGLIB动态代理: CGLIB动态代理就是对指定的类生成一个子类,覆盖其中所有的方法并环绕增强 优势: - 1. 业务类只需要关注业务逻辑本身,保证 ...

  5. java严格验证日期是否正确的代码

    package com.xxxx.util; /** * 输入日期 并进行验证格式是否正确 */ public class FDate { public static void main(String ...

  6. Apollo配置中心解惑(一):关于一个portal管理多个环境,要求环境相互之间不影响,独立

    关于作者的回答很官方,不太懂: https://github.com/ctripcorp/apollo/wiki/%E5%88%86%E5%B8%83%E5%BC%8F%E9%83%A8%E7%BD% ...

  7. ”非常危险“的Linux命令

    Linux命令是一种很有趣且有用的东西,但在你不知道会带来什么后果的时候,它又会显得非常危险.所以,在输入某些命令前,请多多检查再敲回车. rm –rf rm –rf是删除文件夹和里面附带内容的一种最 ...

  8. 大师养成计划之一:搭建springmvc框架

    搭建spring-mvc框架 搭建spring-mvc框架步骤: 1.搭建web项目spring-mvc1 2.引入jar包 3.配置web.xml 3.1拷贝头文件: <web-app xml ...

  9. linux系统寻找僵尸进程

    1. 用top命令来查看服务器当前是否有僵尸进程. 2. 用ps和grep命令寻找僵尸进程 $ ps -A -ostat, pid, ppid, cmd | grep -e '^[Zz]' 命令解释: ...

  10. 嵌入式开发之davinci--- 8148/8168/8127 中的High-DefinitionVideo Processing Subsystem (HDVPSS)

    High-DefinitionVideo Processing Subsystem (HDVPSS) 这一章介绍了高清视频处理子系统(HDVPSS). 2.1导论 2.1.1 简介 HDVPSS 使用 ...