首页 文章 精选 留言 我的

精选列表

搜索[快速入门],共10000篇文章
优秀的个人博客,低调大师

Hadoop框架:MapReduce基本原理和入门案例

本文源码:GitHub·点这里 || GitEE·点这里 一、MapReduce概述 1、基本概念 Hadoop核心组件之一:分布式计算的方案MapReduce,是一种编程模型,用于大规模数据集的并行运算,其中Map(映射)和Reduce(归约)。 MapReduce既是一个编程模型,也是一个计算组件,处理的过程分为两个阶段,Map阶段:负责把任务分解为多个小任务,Reduce负责把多个小任务的处理结果进行汇总。其中Map阶段主要输入是一对Key-Value,经过map计算后输出一对Key-Value值;然后将相同Key合并,形成Key-Value集合;再将这个Key-Value集合转入Reduce阶段,经过计算输出最终Key-Value结果集。 2、特点描述 MapReduce可以实现基于上千台服务器并发工作,提供很强大的数据处理能力,如果其中单台服务挂掉,计算任务会自动转义到另外节点执行,保证高容错性;但是MapReduce不适应于实时计算与流式计算,计算的数据是静态的。 二、操作案例 1、流程描述 数据文件一般以CSV格式居多,数据行通常以空格分隔,这里需要考虑数据内容特点; 文件经过切片分配在不同的MapTask任务中并发执行; MapTask任务执行完毕之后,执行ReduceTask任务,依赖Map阶段的数据; ReduceTask任务执行完毕后,输出文件结果。 2、基础配置 hadoop: # 读取的文件源 inputPath: hdfs://hop01:9000/hopdir/javaNew.txt # 该路径必须是程序运行前不存在的 outputPath: /wordOut 3、Mapper程序 public class WordMapper extends Mapper<LongWritable, Text, Text, IntWritable> { Text mapKey = new Text(); IntWritable mapValue = new IntWritable(1); @Override protected void map (LongWritable key, Text value, Context context) throws IOException, InterruptedException { // 1、读取行 String line = value.toString(); // 2、行内容切割,根据文件中分隔符 String[] words = line.split(" "); // 3、存储 for (String word : words) { mapKey.set(word); context.write(mapKey, mapValue); } } } 4、Reducer程序 public class WordReducer extends Reducer<Text, IntWritable, Text, IntWritable> { int sum ; IntWritable value = new IntWritable(); @Override protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException { // 1、累加求和统计 sum = 0; for (IntWritable count : values) { sum += count.get(); } // 2、输出结果 value.set(sum); context.write(key,value); } } 5、执行程序 @RestController public class WordWeb { @Resource private MapReduceConfig mapReduceConfig ; @GetMapping("/getWord") public String getWord () throws IOException, ClassNotFoundException, InterruptedException { // 声明配置 Configuration hadoopConfig = new Configuration(); hadoopConfig.set("fs.hdfs.impl", org.apache.hadoop.hdfs.DistributedFileSystem.class.getName() ); hadoopConfig.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName() ); Job job = Job.getInstance(hadoopConfig); // Job执行作业 输入路径 FileInputFormat.addInputPath(job, new Path(mapReduceConfig.getInputPath())); // Job执行作业 输出路径 FileOutputFormat.setOutputPath(job, new Path(mapReduceConfig.getOutputPath())); // 自定义 Mapper和Reducer 两个阶段的任务处理类 job.setMapperClass(WordMapper.class); job.setReducerClass(WordReducer.class); // 设置输出结果的Key和Value的类型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); //执行Job直到完成 job.waitForCompletion(true); return "success" ; } } 6、执行结果查看 将应用程序打包放到hop01服务上执行; java -jar map-reduce-case01.jar 三、案例分析 1、数据类型 Java数据类型与对应的Hadoop数据序列化类型; Java类型 Writable类型 Java类型 Writable类型 String Text float FloatWritable int IntWritable long LongWritable boolean BooleanWritable double DoubleWritable byte ByteWritable array DoubleWritable map MapWritable 2、核心模块 Mapper模块:处理输入的数据,业务逻辑在map()方法中完成,输出的数据也是KV格式; Reducer模块:处理Map程序输出的KV数据,业务逻辑在reduce()方法中; Driver模块:将程序提交到yarn进行调度,提交封装了运行参数的job对象; 四、序列化操作 1、序列化简介 序列化:将内存中对象转换为二进制的字节序列,可以通过输出流持久化存储或者网络传输; 反序列化:接收输入字节流或者读取磁盘持久化的数据,加载到内存的对象过程; Hadoop序列化相关接口:Writable实现的序列化机制、Comparable管理Key的排序问题; 2、案例实现 案例描述:读取文件,并对文件相同的行做数据累加计算,输出计算结果;该案例演示在本地执行,不把Jar包上传的hadoop服务器,驱动配置一致。 实体对象属性 public class AddEntity implements Writable { private long addNum01; private long addNum02; private long resNum; // 构造方法 public AddEntity() { super(); } public AddEntity(long addNum01, long addNum02) { super(); this.addNum01 = addNum01; this.addNum02 = addNum02; this.resNum = addNum01 + addNum02; } // 序列化 @Override public void write(DataOutput dataOutput) throws IOException { dataOutput.writeLong(addNum01); dataOutput.writeLong(addNum02); dataOutput.writeLong(resNum); } // 反序列化 @Override public void readFields(DataInput dataInput) throws IOException { // 注意:反序列化顺序和写序列化顺序一致 this.addNum01 = dataInput.readLong(); this.addNum02 = dataInput.readLong(); this.resNum = dataInput.readLong(); } // 省略Get和Set方法 } Mapper机制 public class AddMapper extends Mapper<LongWritable, Text, Text, AddEntity> { Text myKey = new Text(); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { // 读取行 String line = value.toString(); // 行内容切割 String[] lineArr = line.split(","); // 内容格式处理 String lineNum = lineArr[0]; long addNum01 = Long.parseLong(lineArr[1]); long addNum02 = Long.parseLong(lineArr[2]); myKey.set(lineNum); AddEntity myValue = new AddEntity(addNum01,addNum02); // 输出 context.write(myKey, myValue); } } Reducer机制 public class AddReducer extends Reducer<Text, AddEntity, Text, AddEntity> { @Override protected void reduce(Text key, Iterable<AddEntity> values, Context context) throws IOException, InterruptedException { long addNum01Sum = 0; long addNum02Sum = 0; // 处理Key相同 for (AddEntity addEntity : values) { addNum01Sum += addEntity.getAddNum01(); addNum02Sum += addEntity.getAddNum02(); } // 最终输出 AddEntity addRes = new AddEntity(addNum01Sum, addNum02Sum); context.write(key, addRes); } } 案例最终结果: 五、源代码地址 GitHub·地址 https://github.com/cicadasmile/big-data-parent GitEE·地址 https://gitee.com/cicadasmile/big-data-parent

优秀的个人博客,低调大师

分享 | OpenVINO™入门 · 从零开始安装配置 OpenVINO

点击蓝字 关注我们,让开发变得更有趣 内 容 来 源 |郭春旭 排 版 | 卢书晴 原文链接:https://mc.dfrobot.com.cn/thread-306568-1-1.html 一、简要介绍 笔者之前对于神经网络的边缘计算加速和部署并不是很了解,在队友的帮助下逐步了解 OpenVINO 和神经计算棒的一些用法,期间由于没有经验踩了不少的坑,在此分享一下 OpenVINO 的安装和跑通自带示例的过程。 主要参考了 OpenVINO 官网的安装教程【https://docs.openvinotoolkit.org/latest/openvino_docs_install_guides_installing_openvino_windows.html】,从小白(不就是我吗)视角讲如何将 OpenVINO 安装在 win10 系统上! 首先,OpenVINO 本身需要 CMAKE , python 和 VS 这三个依赖项,其本身是对神经网络模型的优化与部署工具,简而言之就是模型本身比较大、可能不适合在边缘计算设备上运行,这个工具包可以将其从各类框架的模型转化为一种网络的中间表示(IR),用于描述这个模型,包括了两个部分: ①.xml 描述网络的拓扑结构 ②.bin网络的权重与偏置的二进制数据 推理引擎使用的是一套通用的 CPU ,可以在 CPU 、 GPU 以及 VPU 上进行运算,之前听OpenVINO的讲座发现这也可以算是这个工具套件的一大亮点,使得使用更加方便了。我们只需要在推理的时候选择运行设备为 VPU ,就可以自动部署网络到这类硬件级别的加速单元上进行高效的推理了,听说这款 Movidius 神经计算棒的功耗仅有1w左右,这样的效能比还是非常惊人的。 这类低成本的边缘神经网络的推理神器很适合编程爱好者与创客做一些需要用到神经网络的小作品。 二、下载安装 1.下载并安装 OpenVINO 安装包的 win10 发行版 这个工具包需要在https://software.intel.com/content/www/us/en/develop/tools/openvino-toolkit/choose-download/windows.html?elq_cid=6430695 下载,是一个免费的工具包,但是英特尔需要用户使用邮箱进行注册,我们按照提示选择页面上的【Register & Download】。进入注册页面,填写邮箱,选择下载 win10 版本。我们尽量在接下去的安装过程中都选择默认目录进行安装,将组件全部勾选进行安装,安装完成后可能提示缺少一些安装项,不要紧我们继续点击【Finish】完成即可。 2.下载与安装特定版本的python 【注意版本要求!!】 在 python 官网的 win10 版下载页面【https://www.python.org/downloads/windows/】 下载 python 文件进行安装, OpenVINO 对 python 的版本要求为 3.5-3.7 ,不要选择太高的版本,笔者之前一直用的最新的版本 3.8.x ,之后再运行 OpenVINO 的时候会报错。与我们平常安装 python 一样,记得要勾选将 python 添加到路径的选项,不然会运行时找不到。 3.下载安装VS2019与CMake3.14 【选对工作负载和版本!!尽量安装在默认目录!!】 官网对这两项依赖的要求是 CMake2.8.12 或更高以及 VS2017 或 2019 ,但是如果要使用 VS2019 的话就必须使用 CMake3.14 ,正常下载安装即可,但是有一点值得注意,那就是在 V S 进行安装的时候,需要勾选三个负载 【.NET 桌面开发】【使用 C++ 的桌面开发】【通用 windows 平台开发】,笔者一开始只勾选了一项导致后面在命令行中验证 OpenVINO 安装的时候会报错。 4.确定系统硬件要求 【看某些处理器是否不支持!!】 官网上列出的支持的硬件包括六代之后的酷睿、至强和神经计算棒等,但是笔者在实测 LattePanda Delta 时会报错,网上解释是不支持 AVX512 指令集,查遍网络也没发现解决方案,最终将运行设备从 CPU 切换为神经计算棒后可以正常运行,可能就是因为这块 LattePanda Delta 使用的是 N4100 处理器,不在官方支持的 N4200 / 5 , N3350 / 5 或 N3450 / 5 之列吧,在个人 PC(i7-8750H) 上运行时就没有这样的问题。 5.设置环境变量与配置模型优化器 只有设置完临时系统环境变量后才能进行下一步的工作,运行 C:\ Program Files(x86)\ IntelSWTools \ openvino \ bin \setupvars.bat 即可,后期可以按照官网上的教程手动设置永久环境变量。 之后要设置模型优化器,模型优化器是一个 python 文件,输入各种框架的模型文件或者 ONNX 这种通用模型文件格式,将其输出为中间表示文件 【IR】,用于进一步的部署与推理。目前了解下来, Caffe 和 Tensorflow 等框架是直接支持转换的,其他如 pytorch 等则需要先转化为 ONNX 再进行模型优化。 安装时可以选择一次配置完所有模型的优化器或者单独为某个模型配置优化器,运行 C:\ Program Files(x86)\ IntelSWTools \ openvino \ deployment_tools \ model_optimizer \ install_prerequisites目录下的install_prerequisites.bat 或 install_prerequisites_tf.bat 等文件即可。 笔者在为 tensorflow 配置优化器的时候报出了 tensorflow 版本不匹配的错误,卸载 tensorflow 后再运行这个批处理文件会自动再安装上,可以正常使用,推荐将 pip 先更换至国内镜像加快下载速度。 6.验证步骤 验证部分也只需要处理两个批处理文件。 在推理引擎目录 C:\ Program Files(x86)\ IntelSWTools \ openvino \ deployment_tools \ demo \ 下运行 demo_squeezenet_download_convert_run.bat 可以进行图形分类验证, 运行 demo_security_barrier_camera.bat 可以进行车牌识别,这两个模型也都是需要再重新下载和安装的,可能需要一些时间。 7.BAT中目录错误问题 刚才就提到了在下载和安装依赖的时候要尽量安装在默认目录,但是由于 LattePanda Delta 板载存储只有 32G ,笔者最后加了个128G 固态并把软件都安装在了非默认位置,但是点开 BAT 看发现疑似目录都是写在默认位置的,之后强行修改了 BAT 中的各目录才解决了这个问题。 END 如果你还在为自己的模型而发愁,不妨使用 OpenVINO 。如果你对建模感兴趣,不妨学学 OpenVINO 。更多精彩内容请关注 OpenVINO 中文社区。从小白到大佬的进阶之路,我们一路陪伴。 👇欢迎在留言区与我们互动哦, 点击小程序 留言区 即可参与 留言区 --------------------------------------- *OpenVINO and the OpenVINO logo are trademarks of Intel Corporation or its subsidiaries. ----------------------------- OpenVINO 中文社区 微信号 : openvinodev B站:OpenVINO中文社区 “开放、开源、共创” 致力于通过定期举办线上与线下的沙龙、动手实践及开发者交流大会等活动,促进人工智能开发者之间的交流学习。 ○点击“在看”,让更多人看见 点击阅读原文立即体验OpenVINO 本文分享自微信公众号 - OpenVINO 中文社区(openvinodev)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

十分钟带你轻松入门Shiro

Shiro集成Spring 首先集成Spring、SpringMVC和Shiro <dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.3.18.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>4.3.18.RELEASE</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-all</artifactId><version>1.3.2</version></dependency><dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache-core</artifactId><version>2.6.2</version></dependency></dependencies> 在web.xml文件中配置Shiro的过滤器 <!--1.配置Shiro的shiroFilter.2.DelegatingFilterProxy实际上是Filter的一个代理对象.默认情况下,Spring会到IOC容器中查找和<filter-name>对应的filterbean.也可以通过targetBeanName的初始化参数来配置filterbean的id.--><filter><filter-name>shiroFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class><init-param><param-name>targetFilterLifecycle</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>shiroFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping> 创建Shiro的配置文件(ehcache-shiro.xml) <ehcacheupdateCheck="false"name="shiroCache"><defaultCachemaxElementsInMemory="10000"eternal="false"timeToIdleSeconds="120"timeToLiveSeconds="120"overflowToDisk="false"diskPersistent="false"diskExpiryThreadIntervalSeconds="120"/></ehcache> 在Spring的配置文件中对Shiro进行配置 <?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!--1.配置SecurityManager!--><beanid="securityManager"class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><propertyname="cacheManager"ref="cacheManager"/><propertyname="authenticator"ref="authenticator"/></bean><!--2.配置CacheManager.2.1需要加入ehcache的jar包及配置文件.--><beanid="cacheManager"class="org.apache.shiro.cache.ehcache.EhCacheManager"><propertyname="cacheManagerConfigFile"value="classpath:ehcache-shiro.xml"/></bean></bean><!--=========================================================ShiroSpring-specificintegration=========================================================--><!--Postprocessorthatautomaticallyinvokesinit()anddestroy()methodsforSpring-configuredShiroobjectssoyoudon'thaveto1)specifyaninit-methodanddestroy-methodattributesforeverybeandefinitionand2)evenknowwhichShiroobjectsrequirethesemethodstobecalled.--><!--4.配置LifecycleBeanPostProcessor.可以自动调用配置在SpringIOC容器中shirobean的生命周期方法.--><beanid="lifecycleBeanPostProcessor"class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/><!--5.启用IOC容器中使用shiro的注解.但必须在配置了LifecycleBeanPostProcessor之后才可以使用.--><beanclass="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"depends-on="lifecycleBeanPostProcessor"/><beanclass="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"><propertyname="securityManager"ref="securityManager"/></bean><!--6.配置ShiroFilter.6.1id必须和web.xml文件中配置的DelegatingFilterProxy的<filter-name>一致.若不一致,则会抛出:NoSuchBeanDefinitionException.因为Shiro会来IOC容器中查找和<filter-name>名字对应的filterbean.--><beanid="shiroFilter"class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><propertyname="securityManager"ref="securityManager"/><propertyname="loginUrl"value="/login.jsp"/><propertyname="successUrl"value="/list.jsp"/><propertyname="unauthorizedUrl"value="/unauthorized.jsp"/><!--配置哪些页面需要受保护.以及访问这些页面需要的权限.1).anon可以被匿名访问2).authc必须认证(即登录)后才可能访问的页面. 3). logout 登出.4).roles角色过滤器--><propertyname="filterChainDefinitions"><value>/login.jsp=anon#everythingelserequiresauthentication:/**=authc</value></property></bean></beans> 配置完成,启动项目即可 工作流程 在这里插入图片描述 Shiro通过在web.xml配置文件中配置的ShiroFilter来拦截所有请求,并通过配置filterChainDefinitions来指定哪些页面受保护以及它们的权限。 URL权限配置 [urls]部分的配置,其格式为:url=拦截器[参数];如果当前请求的url匹配[urls]部分的某个url模式(url模式使用Ant风格匹配),将会执行其配置的拦截器,其中: anon:该拦截器表示匿名访问,即不需要登录便可访问 authc:该拦截器表示需要身份认证通过后才可以访问 logout:登出 roles:角色过滤器 例: <propertyname="filterChainDefinitions"><value>/login.jsp=anon#everythingelserequiresauthentication:/**=authc</value></property> 需要注意的是,url权限采取第一次匹配优先的方式,即从头开始使用第一个匹配的url模式对应的拦截器链,如: /bb/**=filter1 /bb/aa=filter2 /**=filter3 如果请求的url是/bb/aa,因为按照声明顺序进行匹配,那么将使用filter1进行拦截。 Shiro认证流程 获取当前的Subject —— SecurityUtils.getSubject() 校验当前用户是否已经被认证 —— 调用Subject的isAuthenticated()方法 若没有被认证,则把用户名和密码封装为UsernamePasswordToken对象 执行登录 —— 调动Subject的login(UsernamePasswordToken)方法 自定义Realm的方法,从数据库中获取对应的记录,返回给Shiro 自定义类继承org.apache.shiro.realm.AuthenticatingRealm 实现doGetAuthenticationInfo(AuthenticationToken)方法 由Shiro完成对用户名密码的比对 下面具体实现一下,首先创建login.jsp: <%@pagecontentType="text/html;charset=UTF-8"language="java"%><html><head><title>Title</title></head><body><h4>LoginPage</h4><formaction="shiroLogin"method="post">username:<inputtype="text"name="username"/><br/><br/>password:<inputtype="password"name="password"/><br/><br/><inputtype="submit"value="Submit"/></form></body></html> 然后编写控制器: packagecom.wwj.shiro.handlers;importorg.apache.shiro.SecurityUtils;importorg.apache.shiro.authc.AuthenticationException;importorg.apache.shiro.authc.UsernamePasswordToken;importorg.apache.shiro.subject.Subject;importorg.springframework.stereotype.Controller;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestParam;@ControllerpublicclassShiroHandler{@RequestMapping("/shiroLogin")publicStringlogin(@RequestParam("username")Stringusername,@RequestParam("password")Stringpassword){//获取当前的SubjectSubjectcurrentUser=SecurityUtils.getSubject();//校验当前用户是否已经被认证if(!currentUser.isAuthenticated()){//把用户名和密码封装为UsernamePasswordToken对象UsernamePasswordTokentoken=newUsernamePasswordToken(username,password);token.setRememberMe(true);try{//执行登录currentUser.login(token);}catch(AuthenticationExceptionae){System.out.println("登录失败"+ae.getMessage());}}return"redirect:/list.jsp";}} 编写自定义的Realm: packagecom.wwj.shiro.realms;importorg.apache.shiro.authc.*;importorg.apache.shiro.realm.AuthenticatingRealm;publicclassShiroRealmextendsAuthenticatingRealm{/***@paramauthenticationToken该参数实际上是控制器方法中封装用户名和密码后执行login()方法传递进去的参数token*@return*@throwsAuthenticationException*/@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokenauthenticationToken)throwsAuthenticationException{//将参数转回UsernamePasswordTokenUsernamePasswordTokentoken=(UsernamePasswordToken)authenticationToken;//从UsernamePasswordToken中取出用户名Stringusername=token.getUsername();//调用数据库方法,从数据表中查询username对应的记录System.out.println("从数据库中获取Username:"+username+"对应的用户信息");//若用户不存在,则可以抛出异常if("unknow".equals(username)){thrownewUnknownAccountException("用户不存在!");}//根据用户信息的情况,决定是否需要抛出其它异常if("monster".equals(username)){thrownewLockedAccountException("用户被锁定!");}/*根据用户信息的情况,构建AuthenticationInfo对象并返回,通常使用的实现类是SimpleAuthenticationInfo*以下信息是从数据库中获取的:*principal:认证的实体信息,可以是username,也可以是数据表对应的用户实体类对象*credentials:密码*realmName:当前realm对象的name,调用父类的getName()方法即可*/Objectprincipal=username;Objectcredentials="123456";StringrealmName=getName();SimpleAuthenticationInfoinfo=newSimpleAuthenticationInfo(principal,credentials,realmName);returninfo;}} 记得在Spring配置文件中拦截表单请求: <propertyname="filterChainDefinitions"><value>/login.jsp=anon<!--拦截表单请求-->/shiroLogin=anon<!--登出-->/logout=logout#everythingelserequiresauthentication:/**=authc</value></property> 登录成功后跳转至list.jsp: <%@pagecontentType="text/html;charset=UTF-8"language="java"%><html><head><title>Title</title></head><body><h4>ListPage</h4><ahref="logout">logout</a></body></html> 这里实现了一个登出请求,是因为Shiro在登录成功后会有缓存,此时无论用户名是否有效,都将成功登录,所以这里进行一个登出操作。 编写完成,最后启动项目即可。 在这里插入图片描述 若没有进行登录,将无法访问其它页面,若输入错误的用户名,则无法成功登录,也无法访问其它页面: 在这里插入图片描述 若输入正确的用户名和密码,则登录成功,可以访问其它页面: 在这里插入图片描述 重新来回顾一下上述的认证流程: 首先在login.jsp页面中有一个表单用于登录,当用户输入用户名和密码点击登录后,请求会被ShiroHandler控制器拦截 在ShiroHandler中校验用户是否已经被认证,若未认证,则将用户名和密码封装成UsernamePasswordToken对象,并执行登录 当执行登录后,UsernamePasswordToken对象会被传入ShiroRealm类的doGetAuthenticationInfo()方法的入参中,在该方法中对数据作进一步的校验 密码校验的过程 在刚才的例子中,我们实现了在用户登录前后对页面权限的控制,事实上,在程序中我们并没有去编写密码比对的代码,而登录逻辑显然对密码进行了校验,可以猜想这一定是Shiro帮助我们完成了密码的校验。 我们在UserNamePasswordToken类中的getPassword()方法中打一个断点: 在这里插入图片描述 此时以debug的方式启动项目,在表单中输入用户名和密码,点击登录,程序就可以在该方法处暂停运行: 在这里插入图片描述 我们往前找在哪执行了密码校验的逻辑,发现在doCredentialsMatch()方法: 在这里插入图片描述 再观察右边的参数: 在这里插入图片描述 这不正是我在表单输入的密码和数据表中查询出来的密码吗?由此确认在此处Shiro帮助我们对密码进行了校验。 在往前找找可以发现: 在这里插入图片描述 Shiro实际上是用CredentialsMatcher对密码进行校验的,那么为什么要大费周章地来找CredentialsMatcher呢? CredentialsMatcher是一个接口,我们来看看它的实现类: 在这里插入图片描述 那么相信大家已经知道接下来要做什么了,没错,密码的加密,而加密就是通过CredentialsMatcher来完成的。 MD5加密 加密算法其实有很多,这里以md5加密为例。 修改Spring配置文件中对自定义Realm的配置: <beanid="myRealm"class="com.wwj.shiro.realms.ShiroRealm"><propertyname="credentialsMatcher"><beanclass="org.apache.shiro.authc.credential.HashedCredentialsMatcher"><propertyname="hashAlgorithmName"value="MD5"/><!--指定加密次数--><propertyname="hashIterations"value="5"/></bean></property></bean> 这里因为Md5CredentialsMatcher类已经过期了,Shiro推荐直接使用HashedCredentialsMatcher。 这样配置以后,从表单中输入的密码就能够自动地进行MD5加密,但是从数据表中获取的密码仍然是明文状态,所以还需要对该密码进行MD5加密: publicstaticvoidmain(String[]args){StringalgorithmName="MD5";Objectcredentials="123456";Objectsalt=null;inthashIterations=5;Objectresult=newSimpleHash(algorithmName,credentials,salt,hashIterations);System.out.println(result);} 该代码可以参考Shiro底层实现,我们以Shiro同样的方式对其进行MD5加密,两份密码都加密完成了,以debug运行项目,再次找到Shiro校验密码的地方: 在这里插入图片描述 我在表单输入的密码是123456,经过校验发现,两份密码的密文是一致的,所以登录成功。 考虑密码重复的情况 刚才对密码进行了加密,进一步解决了密码的安全问题,但又有一个新问题摆在我们面前,倘若有两个用户的密码是一样的,这样即使进行了加密,因为密文是一样的,这样仍然会有安全问题,那么能不能够实现即使密码一样,但生成的密文却可以不一样呢? 当然是可以的,这里需要借助一个credentialsSalt属性(这里我们假设以用户名为标识进行密文的重新加密): publicstaticvoidmain(String[]args){StringalgorithmName="MD5";Objectcredentials="123456";Objectsalt=ByteSource.Util.bytes("aaa");//Objectsalt=ByteSource.Util.bytes("bbb");inthashIterations=5;Objectresult=newSimpleHash(algorithmName,credentials,salt,hashIterations);System.out.println(result);} 通过该方式,我们生成了两个不一样的密文,即使密码一样: c8b8a6de6e890dea8001712c9e1494963d12ecfbb349ddbe824730eb5e45deca 既然这里对加密进行了修改,那么在表单密码进行加密的时候我们也要进行修改: @OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokenauthenticationToken)throwsAuthenticationException{//将参数转回UsernamePasswordTokenUsernamePasswordTokentoken=(UsernamePasswordToken)authenticationToken;//从UsernamePasswordToken中取出用户名Stringusername=token.getUsername();//调用数据库方法,从数据表中查询username对应的记录System.out.println("从数据库中获取Username:"+username+"对应的用户信息");//若用户不存在,则可以抛出异常if("unknow".equals(username)){thrownewUnknownAccountException("用户不存在!");}//根据用户信息的情况,决定是否需要抛出其它异常if("monster".equals(username)){thrownewLockedAccountException("用户被锁定!");}/*根据用户信息的情况,构建AuthenticationInfo对象并返回,通常使用的实现类是SimpleAuthenticationInfo*以下信息是从数据库中获取的:*principal:认证的实体信息,可以是username,也可以是数据表对应的用户实体类对象*credentials:密码*realmName:当前realm对象的name,调用父类的getName()方法即可*/Objectprincipal=username;Objectcredentials=null;//对用户名进行判断if("aaa".equals(username)){credentials="c8b8a6de6e890dea8001712c9e149496";}elseif("bbb".equals(username)){credentials="3d12ecfbb349ddbe824730eb5e45deca";}StringrealmName=getName();ByteSourcecredentialsSalt=ByteSource.Util.bytes(username);SimpleAuthenticationInfoinfo=newSimpleAuthenticationInfo(principal,credentials,credentialsSalt,realmName);returninfo;} 这样就轻松解决了密码重复的安全问题了。 多Relam的配置 刚才实现的是单个Relam的情况,下面来看看多个Relam之间的配置。 首先自定义第二个Relam: packagecom.wwj.shiro.realms;importorg.apache.shiro.authc.*;importorg.apache.shiro.crypto.hash.SimpleHash;importorg.apache.shiro.realm.AuthenticatingRealm;importorg.apache.shiro.util.ByteSource;publicclassShiroRealm2extendsAuthenticatingRealm{@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokenauthenticationToken)throwsAuthenticationException{System.out.println("ShiroRealm2...");UsernamePasswordTokentoken=(UsernamePasswordToken)authenticationToken;Stringusername=token.getUsername();System.out.println("从数据库中获取Username:"+username+"对应的用户信息");if("unknow".equals(username)){thrownewUnknownAccountException("用户不存在!");}if("monster".equals(username)){thrownewLockedAccountException("用户被锁定!");}Objectprincipal=username;Objectcredentials=null;if("aaa".equals(username)){credentials="ba89744a3717743bef169b120c052364621e6135";}elseif("bbb".equals(username)){credentials="29aa55fcb266eac35a6b9c1bd5eb30e41d4bfd8d";}StringrealmName=getName();ByteSourcecredentialsSalt=ByteSource.Util.bytes(username);SimpleAuthenticationInfoinfo=newSimpleAuthenticationInfo(principal,credentials,credentialsSalt,realmName);returninfo;}publicstaticvoidmain(String[]args){StringalgorithmName="SHA1";Objectcredentials="123456";Objectsalt=ByteSource.Util.bytes("bbb");inthashIterations=5;Objectresult=newSimpleHash(algorithmName,credentials,salt,hashIterations);System.out.println(result);}} 这里简单复制了第一个Relam的代码,并将加密方式改为了SHA1。 接下来修改Spring的配置文件: <?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><beanid="securityManager"class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><propertyname="cacheManager"ref="cacheManager"/><!--添加此处配置--><propertyname="authenticator"ref="authenticator"/></bean><beanid="cacheManager"class="org.apache.shiro.cache.ehcache.EhCacheManager"><propertyname="cacheManagerConfigFile"value="classpath:ehcache-shiro.xml"/></bean><!--添加此处配置--><beanid="authenticator"class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"><propertyname="realms"><list><refbean="myRealm"/><refbean="myRealm2"/></list></property></bean><beanid="myRealm"class="com.wwj.shiro.realms.ShiroRealm"><propertyname="credentialsMatcher"><beanclass="org.apache.shiro.authc.credential.HashedCredentialsMatcher"><propertyname="hashAlgorithmName"value="MD5"/><propertyname="hashIterations"value="5"/></bean></property></bean><!--添加此处配置--><beanid="myRealm2"class="com.wwj.shiro.realms.ShiroRealm2"><propertyname="credentialsMatcher"><beanclass="org.apache.shiro.authc.credential.HashedCredentialsMatcher"><propertyname="hashAlgorithmName"value="SHA1"/><propertyname="hashIterations"value="5"/></bean></property></bean><beanid="lifecycleBeanPostProcessor"class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/><beanclass="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"depends-on="lifecycleBeanPostProcessor"/><beanclass="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"><propertyname="securityManager"ref="securityManager"/></bean><beanid="shiroFilter"class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><propertyname="securityManager"ref="securityManager"/><propertyname="loginUrl"value="/login.jsp"/><propertyname="successUrl"value="/list.jsp"/><propertyname="unauthorizedUrl"value="/unauthorized.jsp"/><propertyname="filterChainDefinitions"><value>/login.jsp=anon/shiroLogin=anon/logout=logout#everythingelserequiresauthentication:/**=authc</value></property></bean></beans> 注释的地方就是需要修改的地方。 此时我们启动项目进行登录,查看控制台信息: 在这里插入图片描述 可以看到两个Relam都被调用了。 认证策略 既然有多个Relam,那么就一定会有认证策略的区别,比如多个Relam中是一个认证成功即为成功还是要所有Relam都认证成功才算成功,Shiro对此提供了三种策略: FirstSuccessfulStrategy:只要有一个Relam认证成功即可,只返回第一个Relam身份认证成功的认证信息,其它的忽略 AtLeastOneSuccessfulStrategy:只要有一个Relam认证成功即可,和FirstSuccessfulStrategy不同,它将返回所有Relam身份认证成功的认证信息 AllSuccessfulStrategy:所有Relam认证成功才算成功,且返回所有Relam身份认证成功的认证信息 默认使用的策略是AtLeastOneSuccessfulStrategy,具体可以通过查看源码来体会。 若要修改默认的认证策略,可以修改Spring的配置文件: <beanid="authenticator"class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"><propertyname="realms"><list><refbean="myRealm"/><refbean="myRealm2"/></list></property><!--修改认证策略--><propertyname="authenticationStrategy"><beanclass="org.apache.shiro.authc.pam.AllSuccessfulStrategy"/></property></bean> 授权 授权也叫访问控制,即在应用中控制谁访问哪些资源,在授权中需要了解以下几个关键对象: 主体:访问应用的用户 资源:在应用中用户可以访问的url 权限:安全策略中的原子授权单位 角色:权限的集合 下面实现一个案例来感受一下授权的作用,新建aaa.jsp和bbb.jsp文件,并修改list.jsp: <%@pagecontentType="text/html;charset=UTF-8"language="java"%><html><head><title>Title</title></head><body><h4>ListPage</h4><ahref="aaa.jsp">aaaPage</a><br/><br/><ahref="bbb.jsp">bbbPage</a><br/><br/><ahref="logout">logout</a></body></html> 现在的情况是登录成功之后就能够访问aaa和bbb页面了: 在这里插入图片描述 但是我想实现这样一个效果,只有具备当前用户的权限才能够访问到指定页面,比如我以aaa用户的身份登录,那么我将只能访问aaa.jsp而无法访问bbb.jsp;同样地,若以bbb用户的身份登录,则只能访问bbb.jsp而无法访问aaa.jsp,该如何实现呢? 实现其实非常简单,修改Sping的配置文件: <propertyname="filterChainDefinitions"><value>/login.jsp=anon/shiroLogin=anon/logout=logout<!--添加角色过滤器-->/aaa.jsp=roles[aaa]/bbb.jsp=roles[bbb]#everythingelserequiresauthentication:/**=authc</value></property> 启动项目看看效果: 在这里插入图片描述 这里有一个坑,就是在编写授权之前,你需要将Relam的引用放到securityManager中: <beanid="securityManager"class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><propertyname="cacheManager"ref="cacheManager"/><propertyname="authenticator"ref="authenticator"/><propertyname="realms"><list><refbean="myRealm"/><refbean="myRealm2"/></list></property></bean> 否则程序将无法正常运行。 现在虽然把权限加上了,但无论你是aaa用户还是bbb用户,你都无法访问到页面了,Shiro都自动跳转到了无权限页面,我们还需要做一些操作,对ShiroRelam类进行修改: packagecom.wwj.shiro.realms;importorg.apache.shiro.authc.*;importorg.apache.shiro.authz.AuthorizationInfo;importorg.apache.shiro.authz.SimpleAuthorizationInfo;importorg.apache.shiro.crypto.hash.SimpleHash;importorg.apache.shiro.realm.AuthorizingRealm;importorg.apache.shiro.subject.PrincipalCollection;importorg.apache.shiro.util.ByteSource;importjava.util.HashSet;importjava.util.Set;publicclassShiroRealmextendsAuthorizingRealm{@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokenauthenticationToken)throwsAuthenticationException{UsernamePasswordTokentoken=(UsernamePasswordToken)authenticationToken;Stringusername=token.getUsername();System.out.println("从数据库中获取Username:"+username+"对应的用户信息");if("unknow".equals(username)){thrownewUnknownAccountException("用户不存在!");}if("monster".equals(username)){thrownewLockedAccountException("用户被锁定!");}Objectprincipal=username;Objectcredentials=null;if("aaa".equals(username)){credentials="c8b8a6de6e890dea8001712c9e149496";}elseif("bbb".equals(username)){credentials="3d12ecfbb349ddbe824730eb5e45deca";}StringrealmName=getName();ByteSourcecredentialsSalt=ByteSource.Util.bytes(username);SimpleAuthenticationInfoinfo=newSimpleAuthenticationInfo(principal,credentials,credentialsSalt,realmName);returninfo;}/***授权时会被Shiro回调的方法*@paramprincipalCollection*@return*/@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipalCollection){//获取登录用户的信息Objectprincipal=principalCollection.getPrimaryPrincipal();//获取当前用户的角色Set<String>roles=newHashSet<>();roles.add("aaa");if("bbb".equals(principal)){roles.add("bbb");}//创建SimpleAuthorizationInfo,并设置其roles属性SimpleAuthorizationInfoinfo=newSimpleAuthorizationInfo(roles);returninfo;}} 首先将继承的类做了修改,改为继承AuthorizingRealm类,可以通过实现该类的doGetAuthenticationInfo()方法完成认证,通过doGetAuthorizationInfo()方法完成授权,所以源代码不用动,直接添加下面的doGetAuthorizationInfo()方法即可,看运行效果: 在这里插入图片描述 可以看到aaa用户只能访问到aaa.jsp而无法访问bbb.jsp,但是bbb用户却能够访问到两个页面,如果你仔细观察刚才添加的方法你就能够明白为什么。 @OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipalCollection){//获取登录用户的信息Objectprincipal=principalCollection.getPrimaryPrincipal();//获取当前用户的角色Set<String>roles=newHashSet<>();roles.add("aaa");if("bbb".equals(principal)){roles.add("bbb");}//创建SimpleAuthorizationInfo,并设置其roles属性SimpleAuthorizationInfoinfo=newSimpleAuthorizationInfo(roles);returninfo;} 因为不管是什么用户登录,我都将aaa用户添加到了roles中,所以bbb用户是具有aaa用户权限的,权限完全是由你自己控制的,想怎么控制你就怎么写。 注解实现授权 先来看看关于授权的几个注解: @RequiresAuthentication:表示当前Subject已经通过login进行了身份验证;即 Subject. isAuthenticated()返回 true @RequiresUser:表示当前 Subject 已经身份验证或者通过记住我登录的 @RequiresGuest:表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。 @RequiresRoles(value={“aaa”, “bbb”}, logical=Logical.AND):表示当前 Subject 需要角色aaa和bbb @RequiresPermissions (value={“user:a”, “user:b”},logical= Logical.OR):表示当前 Subject 需要权限user:a 或user:b 把Spring配置文件中的角色过滤器删掉,然后定义一个Service: packagecom.wwj.shiro.service;importorg.apache.shiro.authz.annotation.RequiresRoles;importorg.springframework.stereotype.Service;@ServicepublicclassShiroService{@RequiresRoles({"aaa"})publicvoidtest(){System.out.println("test...");}} 在test()方法上添加注解@RequiresRoles({"aaa"}),意思是该方法只有aaa用户才能访问,接下来在ShiroHandler中添加一个方法: @AutowiredprivateShiroServiceshiroService;@RequestMapping("/testAnnotation")publicStringtestAnnotation(){shiroService.test();return"redirect:/list.jsp";} 此时当你访问testAnnotation请求时,只有aaa用户能够成功访问,bbb用户就会抛出异常。 uiresRoles(value={“aaa”, “bbb”}, logical=Logical.AND):表示当前 Subject 需要角色aaa和bbb @RequiresPermissions (value={“user:a”, “user:b”},logical= Logical.OR):表示当前 Subject 需要权限user:a 或user:b 把Spring配置文件中的角色过滤器删掉,然后定义一个Service: packagecom.wwj.shiro.service;importorg.apache.shiro.authz.annotation.RequiresRoles;importorg.springframework.stereotype.Service;@ServicepublicclassShiroService{@RequiresRoles({"aaa"})publicvoidtest(){System.out.println("test...");}} 在test()方法上添加注解@RequiresRoles({"aaa"}),意思是该方法只有aaa用户才能访问,接下来在ShiroHandler中添加一个方法: @AutowiredprivateShiroServiceshiroService;@RequestMapping("/testAnnotation")publicStringtestAnnotation(){shiroService.test();return"redirect:/list.jsp";} 此时当你访问testAnnotation请求时,只有aaa用户能够成功访问,bbb用户就会抛出异常。 本文分享自微信公众号 - 码视界(otc_18679428729)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

零基础,史上最通俗视频编码技术入门

1、引言 如今我们所处的时代,是移动互联网时代,也可以说是视频时代。从快播到抖音,从“三生三世”到“延禧攻略”,我们的生活,被越来越多的视频元素所影响。 而这一切,离不开视频拍摄技术的不断升级,还有视频制作产业的日益强大。 此外,也离不开通信技术的飞速进步。试想一下,如果还是当年的56K Modem拨号,或者是2G手机,你还能享受到现在动辄1080P甚至4K的视频体验吗? 除了视频拍摄工具和网络通信技术升级之外,我们能享受到视频带来的便利和乐趣,还有一个重要因素,就是视频编码技术的突飞猛进。 视频编码技术涉及的内容太过专业和庞杂,市面上的书籍或博客多数都只是枯燥的技术概念罗列,对于新手来说读完依旧蒙逼是常态,本文将借此机会,专门给大家做一个关于视频编码的零基础科普。 2、图像基础知识 2.1 什么是像素? 说视频之前,先要说说图像。图像,大家都知道,是由很多“带有颜色的点”组成的。这个点,就是“像素点”。 像素点的英文叫Pixel(缩写为PX)。这个单词是由 Picture(图像) 和 Element(元素)这两个单词的字母所组成的。 ▲ 电影《像素大战(Pixels)》,2015年 像素是图像显示的基本单位。我们通常说一幅图片的大小,例如是1920×1080,就是长度为1920个像素点,宽度为1080个像素点。乘积是2,073,600,也就是说,这个图片是两百万像素的。 1920×1080,这个也被称为这幅图片的分辨率。 ▲ 分辨率也是显示器的重要指标 2.2 什么是PPI? 那么,我们经常所说的PPI又是什么东西呢? PPI,就是“Pixels Per Inch”,每英寸像素数。也就是,手机(或显示器)屏幕上每英寸面积,到底能放下多少个“像素点”。这个值当然是越高越好啦!PPI越高,图像就越清晰细腻。 以前的功能机,例如诺基亚,屏幕PPI都很低,有很强烈的颗粒感。 后来,苹果开创了史无前例的“视网膜”(Retina)屏幕,PPI值高达326(每英寸屏幕有326像素),画质清晰,再也没有了颗粒感。 2.3 颜色在计算机里是如何表示的? 像素点必须要有颜色,才能组成缤纷绚丽的图片。那么,这个颜色,又该如何表示呢? 大家都知道,我们生活中的颜色,可以拥有无数种类别。 ▲ 光是妹纸们的口红色号,就足以让我们这些屌丝瞠目结舌。。。 在计算机系统里,我们不可能用文字来表述颜色。不然,就算我们不疯,计算机也会疯掉的。在数字时代,当然是用数字来表述颜色。这就牵出了“彩色分量数字化”的概念。 以前我们美术课学过,任何颜色,都可以通过红色(Red)、绿色(Green)、蓝色(Blue)按照一定比例调制出来。这三种颜色,被称为“三原色”。 在计算机里,R、G、B也被称为“基色分量”。它们的取值,分别从0到255,一共256个等级(256是2的8次方)。所以,任何颜色,都可以用R、G、B三个值的组合表示。 ▲ RGB=(183,67,21) 通过这种方式,一共能表达多少种颜色呢?256×256×256=16,777,216种,因此也简称为1600万色。RGB三色,每色有8bit,这种方式表达出来的颜色,也被称为24位色(占用24bit)。这个颜色范围已经超过了人眼可见的全部色彩,所以又叫真彩色。再高的话,对于我们人眼来说,已经没有意义了,完全识别不出来。 3、视频编码基础知识 3.1 视频和图像和关系 好了,刚才说了图像,现在,我们开始说视频。所谓视频,大家从小就看动画,都知道视频是怎么来的吧?没错,大量的图片连续起来,就是视频。 衡量视频,又是用的什么指标参数呢?最主要的一个,就是帧率(Frame Rate)。在视频中,一个帧(Frame)就是指一幅静止的画面。帧率,就是指视频每秒钟包括的画面数量(FPS,Frame per second)。 帧率越高,视频就越逼真、越流畅。 3.2 未经编码的视频数据量会有多大? 有了视频之后,就涉及到两个问题: 一个是存储; 二个是传输。 而之所以会有视频编码,关键就在于此:一个视频,如果未经编码,它的体积是非常庞大的。 以一个分辨率1920×1280,帧率30的视频为例: 共:1920×1280=2,073,600(Pixels 像素),每个像素点是24bit(前面算过的哦); 也就是:每幅图片2073600×24=49766400 bit,8 bit(位)=1 byte(字节); 所以:49766400bit=6220800byte≈6.22MB。 这是一幅1920×1280图片的原始大小,再乘以帧率30。 也就是说:每秒视频的大小是186.6MB,每分钟大约是11GB,一部90分钟的电影,约是1000GB。。。 吓尿了吧?就算你现在电脑硬盘是4TB的(实际也就3600GB),也放不下几部大姐姐啊!不仅要存储,还要传输,不然视频从哪来呢?如果按照100M的网速(12.5MB/s),下刚才那部电影,需要22个小时。。。再次崩溃。。。 正因为如此,屌丝工程师们就提出了,必须对视频进行编码。 3.3 什么是编码? 编码:就是按指定的方法,将信息从一种形式(格式),转换成另一种形式(格式)。视频编码:就是将一种视频格式,转换成另一种视频格式。 编码的终极目的,说白了,就是为了压缩。各种五花八门的视频编码方式,都是为了让视频变得体积更小,有利于存储和传输。 我们先来看看,视频从录制到播放的整个过程,如下: 首先是视频采集。通常我们会使用摄像机、摄像头进行视频采集。限于篇幅,我就不打算和大家解释CCD成像原理了。 采集了视频数据之后,就要进行模数转换,将模拟信号变成数字信号。其实现在很多都是摄像机(摄像头)直接输出数字信号。信号输出之后,还要进行预处理,将RGB信号变成YUV信号。 前面我们介绍了RGB信号,那什么是YUV信号呢? 简单来说,YUV就是另外一种颜色数字化表示方式。视频通信系统之所以要采用YUV,而不是RGB,主要是因为RGB信号不利于压缩。在YUV这种方式里面,加入了亮度这一概念。在最近十年中,视频工程师发现,眼睛对于亮和暗的分辨要比对颜色的分辨更精细一些,也就是说,人眼对色度的敏感程度要低于对亮度的敏感程度。 所以,工程师认为,在我们的视频存储中,没有必要存储全部颜色信号。我们可以把更多带宽留给黑—白信号(被称作“亮度”),将稍少的带宽留给彩色信号(被称作“色度”)。于是,就有了YUV。 YUV里面的“Y”,就是亮度(Luma),“U”和“V”则是色度(Chroma)。 大家偶尔会见到的Y'CbCr,也称为YUV,是YUV的压缩版本,不同之处在于Y'CbCr用于数字图像领域,YUV用于模拟信号领域,MPEG、DVD、摄像机中常说的YUV其实就是Y'CbCr。 ▲ YUV(Y'CbCr)是如何形成图像的 YUV码流的存储格式其实与其采样的方式密切相关。(采样,就是捕捉数据) 主流的采样方式有三种: 1)YUV4:4:4; 2)YUV4:2:2; 3)YUV4:2:0。 具体解释起来有点繁琐,大家只需记住,通常用的是YUV4:2:0的采样方式,能获得1/2的压缩率。 这些预处理做完之后,就是正式的编码了。 有关视频编码的更多专业知识,可以详细阅读以下文章: 《即时通讯音视频开发(一):视频编解码之理论概述》 《即时通讯音视频开发(二):视频编解码之数字视频介绍》 《即时通讯音视频开发(三):视频编解码之编码基础》 《即时通讯音视频开发(四):视频编解码之预测技术介绍》 《即时通讯音视频开发(五):认识主流视频编码技术H.264》 4、视频编码的实现原理 4.1 视频编码技术的基本原理 前面我们说了,编码就是为了压缩。要实现压缩,就要设计各种算法,将视频数据中的冗余信息去除。当你面对一张图片,或者一段视频的时候,你想一想,如果是你,你会如何进行压缩呢? ▲ 对于新垣女神,我一bit也不舍得压缩… 我觉得,首先你想到的,应该是找规律。是的,寻找像素之间的相关性,还有不同时间的图像帧之间,它们的相关性。 举个例子:如果一幅图(1920×1080分辨率),全是红色的,我有没有必要说2073600次[255,0,0]?我只要说一次[255,0,0],然后再说2073599次“同上”。 如果一段1分钟的视频,有十几秒画面是不动的,或者,有80%的图像面积,整个过程都是不变(不动)的。那么,是不是这块存储开销,就可以节约掉了? ▲ 以上图为例,只有部分元素在动,大部分是不动的 是的,所谓编码算法,就是寻找规律,构建模型。谁能找到更精准的规律,建立更高效的模型,谁就是厉害的算法。 通常来说,视频里面的冗余信息包括: 视频编码技术优先消除的目标,就是空间冗余和时间冗余。 接下来,就和大家介绍一下,究竟是采用什么样的办法,才能干掉它们。以下内容稍微有点高能,不过我相信大家耐心一些还是可以看懂的。 4.2 视频编码技术的实现方法 视频是由不同的帧画面连续播放形成的。 这些帧,主要分为三类,分别是: 1)I帧; 2)B帧; 3)P帧。 I帧:是自带全部信息的独立帧,是最完整的画面(占用的空间最大),无需参考其它图像便可独立进行解码。视频序列中的第一个帧,始终都是I帧。 P帧:“帧间预测编码帧”,需要参考前面的I帧和/或P帧的不同部分,才能进行编码。P帧对前面的P和I参考帧有依赖性。但是,P帧压缩率比较高,占用的空间较小。 ▲ P帧 B帧:“双向预测编码帧”,以前帧后帧作为参考帧。不仅参考前面,还参考后面的帧,所以,它的压缩率最高,可以达到200:1。不过,因为依赖后面的帧,所以不适合实时传输(例如视频会议)。 ▲ B帧 通过对帧的分类处理,可以大幅压缩视频的大小。毕竟,要处理的对象,大幅减少了(从整个图像,变成图像中的一个区域)。 如果从视频码流中抓一个包,也可以看到I帧的信息,如下: 我们来通过一个例子看一下。 这有两个帧: 好像是一样的? 不对,我做个GIF动图,就能看出来,是不一样的: 人在动,背景是没有在动的。 第一帧是I帧,第二帧是P帧。两个帧之间的差值,就是如下: 也就是说,图中的部分像素,进行了移动。移动轨迹如下: 这个,就是运动估计和补偿。 当然了,如果总是按照像素来算,数据量会比较大,所以,一般都是把图像切割为不同的“块(Block)”或“宏块(MacroBlock)”,对它们进行计算。一个宏块一般为16像素×16像素。 ▲ 将图片切割为宏块 好了,我来梳理一下。 对I帧的处理,是采用帧内编码方式,只利用本帧图像内的空间相关性。对P帧的处理,采用帧间编码(前向运动估计),同时利用空间和时间上的相关性。简单来说,采用运动补偿(motion compensation)算法来去掉冗余信息。 需要特别注意,I帧(帧内编码),虽然只有空间相关性,但整个编码过程也不简单。 如上图所示,整个帧内编码,还要经过DCT(离散余弦变换)、量化、编码等多个过程。限于篇幅,加之较为复杂,今天就放弃解释了。 那么,视频经过编码解码之后,如何衡量和评价编解码的效果呢? 一般来说,分为客观评价和主观评价。客观评价,就是拿数字来说话。例如计算“信噪比/峰值信噪比”。 信噪比的计算,我就不介绍了,丢个公式,有空可以自己慢慢研究... 除了客观评价,就是主观评价了。主观评价,就是用人的主观感知直接测量,额,说人话就是——“好不好看我说了算”。 5、视频编码的国际标准 5.1 视频编码格式的标准化 接下来,我们再说说标准(Standard)。任何技术,都有标准。自从有视频编码以来,就诞生过很多的视频编码标准。 提到视频编码标准,先介绍几个制定标准的组织。 首先,就是大名鼎鼎的ITU(国际电信联盟)。 ITU是联合国下属的一个专门机构,其总部在瑞士的日内瓦。 ITU下属有三个部门: 1)分别是ITU-R(前身是国际无线电咨询委员会CCIR); 2)ITU-T(前身是国际电报电话咨询委员会CCITT); 3)ITU-D。 除了ITU之外,另外两个和视频编码关系密切的组织,是ISO/IEC。 ISO大家都知道,就是推出ISO9001质量认证的那个“国际标准化组织”。IEC,是“国际电工委员会”。1988年,ISO和IEC联合成立了一个专家组,负责开发电视图像数据和声音数据的编码、解码和它们的同步等标准。这个专家组,就是大名鼎鼎的MPEG,Moving Picture Expert Group(动态图像专家组)。 三十多年以来,世界上主流的视频编码标准,基本上都是它们提出来的: 1)ITU提出了H.261、H.262、H.263、H.263+、H.263++,这些统称为H.26X系列,主要应用于实时视频通信领域,如会议电视、可视电话等; 2)ISO/IEC提出了MPEG1、MPEG2、MPEG4、MPEG7、MPEG21,统称为MPEG系列。 ITU和ISO/IEC一开始是各自捣鼓,后来,两边成立了一个联合小组,名叫JVT(Joint Video Team,视频联合工作组)。 JVT致力于新一代视频编码标准的制定,后来推出了包括H.264在内的一系列标准。 ▲ 压缩率对比 ▲ 视频编码标准的发展关系 大家特别注意一下上图里面的HEVC,也就是现在风头正盛的H.265。 作为一种新编码标准,相比H.264有极大的性能提升,目前已经成为最新视频编码系统的标配。 最后,我再说说封装。 5.2 视频数据的封装 对于任何一部视频来说,只有图像,没有声音,肯定是不行的。所以,视频编码后,加上音频编码,要一起进行封装。 封装:就是封装格式,简单来说,就是将已经编码压缩好的视频轨和音频轨按照一定的格式放到一个文件中。再通俗点,视频轨相当于饭,而音频轨相当于菜,封装格式就是一个饭盒,用来盛放饭菜的容器。 目前主要的视频容器有如下:MPG、VOB、MP4、3GP、ASF、RMVB、WMV、MOV、Divx、MKV、FLV、TS/PS等。 封装之后的视频,就可以传输了,你也可以通过视频播放器进行解码观看。(本文同步发布于:http://www.52im.net/thread-2840-1-1.html)

优秀的个人博客,低调大师

Docker入门-构建第一个Java程序

定制镜像 准备一个没有第三方依赖的java web项目,可能参考示例maven结构项目: session-web.war 把该war上传到安装有docker软件的服务器上宿主目录下。在同级目录创建Dockerfile touch Dockerfile vim Dockerfile 按照前面文章所学的Dockerfile定制镜像知识来编写Dockerfile文件内容如下: # 基础镜像使用tomcat:7.0.88-jre8 FROM tomcat:7.0.88-jre8 # 作者 MAINTAINER simon <xueyao.me@gmail.com> # 定义环境变量 ENV TOMCAT_BASE /usr/local/tomcat # 复制war包 COPY ./session-web.war $TOMCAT_BASE/webapps/ 执行构建: docker bulid -t session-web:latest . 如果构建成功,则会显示构建的分层信息及结果。 构建成功后使用docker images命令查看本地是否有该镜像 运行镜像 镜像制作好之后我们就要把它运行起来 docker run --name session-web -d -p 8888:8080 session-web:latest 启动后使用netstat -na|grep 8888 验证端口是否是在监听状态 浏览器中访问http://ip:8888/session-web/user/login 本文中war包在此仓库下https://github.com/flowstone/blog-example-code

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册