Cookie 和 Session

Cookie 和 Session HTTP 协议是一种无状态协议,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录;Session 和 Cookie 的主要目的就是为了弥补 HTTP 的无状态特性。 Session 是什么 客户端请求服务端,服务端会为这次请求开辟一块内存空间,这个对象便是 Session 对象,存储结构为 ConcurrentHashMap。Session 弥补了 HTTP 无状态特性,服务器可以利用 Session 存储客户端在同一个会话期间的一些操作记录。 Session 如何判断是否是同一会话 服务器第一次接收到请求时,开辟了一块 Session 空间(创建了Session对象),同时生成一个 sessionId ,并通过响应头的 **Set-Cookie:JSESSIONID=XXXXXXX **命令,向客户端发送要求设置 Cookie 的响应; 客户端收到响应后,在本机客户端设置了一个 JSESSIONID=XXXXXXX 的 Cookie 信息,该 Cookie 的过期时间为浏览器会话结束; 接下来客户端每次向同一个网站发送请求时,请求头都会带上该 Cookie信息(包含 sessionId ), 然后,服务器通过读取请求头中的 Cookie 信息,获取名称为 JSESSIONID 的值,得到此次请求的 sessionId。 Session 的缺点 Session 机制有个缺点,比如 A 服务器存储了 Session,就是做了负载均衡后,假如一段时间内 A 的访问量激增,会转发到 B 进行访问,但是 B 服务器并没有存储 A 的 Session,会导致 Session 的失效。 Cookies 是什么 HTTP 协议中的 Cookie 包括 Web Cookie 和浏览器 Cookie,它是服务器发送到 Web 浏览器的一小块数据。服务器发送到浏览器的 Cookie,浏览器会进行存储,并与下一个请求一起发送到服务器。通常,它用于判断两个请求是否来自于同一个浏览器,例如用户保持登录状态。 ...

July 28, 2025

Context API

先看看React官网对于Context的介绍 Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。 个人理解转成大白话:Context提供了一个局部的全局作用域,使用Context则无需再手动的逐层传递props。 本文主要介绍3种Context的使用方式: React.createContext提供的Provider和Consumer 函数组件:React.createContext提供的Provider和useContext钩子 Class组件:React.createContext提供的Provider和class的contextType属性 第一种:React.createContext提供的Provider和Consumer 先写好使用Context的基础环境条件,后续的代码都是基于此环境 //创建一个文件,暂且命名为context.js,导出createContext()的返回值 import { createContext } from "react"; export default createContext(); 在根组件App.jsx中,导入上面写的context,并使用context提供的Provider组件进行包裹,圈定局部的全局作用域,传值后可以提供给子组件进行消费 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。 import React, { createContext } from "react"; import MyContext from "./context"; import GeneralC from "./GeneralC"; import FnC from "./FnC"; import ClassC from "./ClassC"; export default function App() { return ( //Provider组件接收一个value属性,此处传入一个带有name属性的对象 <MyContext.Provider value={{ name: `context's value is string!` }}> {/*这里写后面要进行包裹的子组件,此处先行导入后续需要消费context的3个组件*/} <GeneralC/> <hr/> <FnC/> <hr/> <ClassC/> </MyContext.Provider> ); } 在GeneralC组件中,导入context,使用其提供的Consumer组件来订阅Context的变更,需要一个函数作为子元素,函数的第一个形参便是Provider组件提供的value值 import React, { useReducer } from "react"; import MyContext from "./context"; const GeneralC = () => { return ( // <MyContext.Consumer> {(value) => { return ( <div> 第一种使用Context方式获取的值:{JSON.stringify(value)} </div> ); }} </MyContext.Consumer> ); }; export default GeneralC; 此时页面中应该出现json格式的value值 ...

December 17, 2024

变量对象

这篇文章要给大家介绍的是变量对象。 在JavaScript中,我们肯定不可避免的需要声明变量和函数,可是JS解析器是如何找到这些变量的呢?我们还得对执行上下文有一个进一步的了解。 在知识点《执行上下文》中,我们已经知道,当调用一个函数时(激活),一个新的执行上下文就会被创建。而一个执行上下文的生命周期可以分为两个阶段。 创建阶段 在这个阶段中,执行上下文会分别创建变量对象,建立作用域链,以及确定this的指向。 代码执行阶段 创建完成之后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其他代码。 从这里我们就可以看出详细了解执行上下文极为重要,因为其中涉及到了变量对象,作用域链,this等很多人没有怎么弄明白,但是却极为重要的概念,它关系到我们能不能真正理解JavaScript。在后面的文章中我们会一一详细总结,这里我们先重点了解变量对象。 变量对象(Variable Object) 变量对象的创建,依次经历了以下几个过程。 建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值。 检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。 检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。 许多读者在阅读到这的时候会因为下面的这样场景对于“跳过”一词产生疑问。既然变量声明的foo遇到函数声明的foo会跳过,可是为什么最后foo的输出结果仍然是被覆盖了? function foo() { console.log('function foo') } var foo = 20; console.log(foo); // 20 其实只是大家在阅读的时候不够仔细,因为上面的三条规则仅仅适用于变量对象的创建过程。也就是执行上下文的创建过程。而foo = 20是在执行上下文的执行过程中运行的,输出结果自然会是20。对比下例。 console.log(foo); // function foo function foo() { console.log('function foo') } var foo = 20; // 上栗的执行顺序为 // 首先将所有函数声明放入变量对象中 function foo() { console.log('function foo') } // 其次将所有变量声明放入变量对象中,但是因为foo已经存在同名函数,因此此时会跳过undefined的赋值 // var foo = undefined; // 然后开始执行阶段代码的执行 console.log(foo); // function foo foo = 20; 根据这个规则,理解变量提升就变得十分简单了。在很多文章中虽然提到了变量提升,但是具体是怎么回事还真的很多人都说不出来,以后在面试中用变量对象的创建过程跟面试官解释变量提升,保证瞬间提升逼格。 在上面的规则中我们看出,function声明会比var声明优先级更高一点。为了帮助大家更好的理解变量对象,我们结合一些简单的例子来进行探讨。 // demo01 function test() { console.log(a); console.log(foo()); var a = 1; function foo() { return 2; } } test(); 在上例中,我们直接从test()的执行上下文开始理解。全局作用域中运行test()时,test()的执行上下文开始创建。为了便于理解,我们用如下的形式来表示 ...

November 6, 2024

background

引言 在日常前端开发中,经常需要进行背景或背景图的处理。但是大多数前端er并未真正清楚背景的正确使用方式。经过本章的学习,相信你一定可以解决99%的背景处理问题。 一:简单使用 背景颜色和背景图片是可以共同出现的 div { width: 300px; height: 300px; background-color: red; background-image: url(./imgs/1.jpg); background-repeat: no-repeat; } 页面展示 一:background-repeat background-repeat决定背景图片的平铺方式 属性值 background-repeat:repeat (默认值) background-repeat:no-repeat (不平铺) background-repeat:repeat-x (水平方向平铺) background-repeat:repeat-y (垂直方向平铺) 1. repeat 默认情况下,如果背景图片不能铺满整个盒子时,系统会在水平和垂直方向同时平铺直到覆盖整个盒子 div { width: 300px; height: 300px; background-color: red; background-image: url(./imgs/1.jpg); background-repeat: repeat; } 2.repeat-x 如果背景图片不能将盒子的水平方向铺满,则在水平方向采取平铺处理直到铺满盒子的水平方向。 div { width: 300px; height: 300px; background-color: red; background-image: url(./imgs/1.jpg); background-repeat: repeat-x; } 3.repeat-y 如果背景图片不能将盒子的垂直方向铺满,则在垂直方向采取平铺处理直到铺满盒子的垂直方向。 div { width: 300px; height: 300px; background-color: red; background-image: url(./imgs/1.jpg); background-repeat: repeat-y; } 二:background-position background-positiont决定背景图片在盒子区域的定位位置。其方位由水平和垂直决定 1.px设置 px决定了背景图片在盒子水平和垂直方向偏移指定px的距离。 div { width: 300px; height: 300px; background-color: red; background-image: url(./imgs/1.jpg); background-repeat: no-repeat; background-position: 100px 100px; } ...

October 10, 2024

methods

Methods 的初始化 在 Vue.js 中,每个组件实例(vm)都会有一个 methods 属性,这个属性包含了组件中定义的所有方法。这些方法需要被初始化到实例上,以便可以在模板和生命周期钩子中被调用。初始化过程如下: function initMethods(vm, methods) { for (var key in methods) { vm[key] = methods[key] == null ? noop : bind(methods[key], vm); } } 这段代码遍历 methods 对象中的每个属性,并将其复制到 Vue 实例(vm)上。如果方法不存在(methods[key] == null),则赋值为一个空函数(noop),以避免对 undefined 的调用。否则,使用 bind 方法将每个方法的 this 绑定到 Vue 实例上。 Methods 作用域的固定 在 JavaScript 中,函数的 this 值是在函数被调用时确定的,而不是在定义时。这可能导致在 Vue 实例的方法中 this 并不指向实例本身,尤其是在方法被传递给其他函数或作为回调函数时。Vue 使用 bind 来解决这个问题,确保 this 总是指向 Vue 实例。 原生和兼容性 bind 实现 Vue.js 提供了两种 bind 的实现:原生的和兼容性的。兼容性实现是为了兼容那些不支持 Function.prototype.bind 的旧浏览器。 function polyfillBind(fn, ctx) { function boundFn(a) { var l = arguments.length; return l ? (l > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a)) : fn.call(ctx); } boundFn._length = fn.length; return boundFn; } function nativeBind(fn, ctx) { return fn.bind(ctx); } var bind = Function.prototype.bind ? nativeBind : polyfillBind; polyfillBind 函数创建了一个新的函数 boundFn,它在调用时使用 call 或 apply 将 fn 的 this 绑定到 ctx 上。 nativeBind 函数直接使用原生的 bind 方法。 bind 变量根据浏览器是否支持原生 bind 来选择使用哪种实现。 使用 bind 的好处 使用 bind 后,我们可以在组件的方法中直接使用 this 来访问实例的属性和方法,而不用担心 this 的指向问题。这使得代码更加简洁和安全。 ...

October 4, 2024

密码存储

我们在开发网站或者APP时,首先要解决的问题,就是如何安全地传输和存储用户的密码。一些大公司的用户数据库泄露事件也时有发生,带来非常大的负面影响。因此,如何安全传输存储用户密码,是每位程序员必备的基础。本文将跟大家一起学习,如何安全传输存储用户的密码。 1. 如何安全地传输用户的密码 要拒绝用户密码在网络上裸奔,我们很容易就想到使用https协议,那先来回顾下https相关知识吧~ 1.1 https 协议 「http的三大风险」 为什么要使用https协议呢?http它不香吗? 因为http是明文信息传输的。如果在茫茫的网络海洋,使用http协议,有以下三大风险: 窃听/嗅探风险:第三方可以截获通信数据。 数据篡改风险:第三方获取到通信数据后,会进行恶意修改。 身份伪造风险:第三方可以冒充他人身份参与通信。 如果传输不重要的信息还好,但是传输用户密码这些敏感信息,那可不得了。所以一般都要使用https协议传输用户密码信息。 「https 原理」 https原理是什么呢?为什么它能解决http的三大风险呢? https = http + SSL/TLS, SSL/TLS 是传输层加密协议,它提供内容加密、身份认证、数据完整性校验,以解决数据传输的安全性问题。 为了加深https原理的理解,我们一起复习一下 一次完整https的请求流程吧~ 客户端发起https请求 服务器必须要有一套数字证书,可以自己制作,也可以向权威机构申请。这套证书其实就是一对公私钥。 服务器将自己的数字证书(含有公钥、证书的颁发机构等)发送给客户端。 客户端收到服务器端的数字证书之后,会对其进行验证,主要验证公钥是否有效,比如颁发机构,过期时间等等。如果不通过,则弹出警告框。如果证书没问题,则生成一个密钥(对称加密算法的密钥,其实是一个随机值),并且用证书的公钥对这个随机值加密。 客户端会发起https中的第二个请求,将加密之后的客户端密钥(随机值)发送给服务器。 服务器接收到客户端发来的密钥之后,会用自己的私钥对其进行非对称解密,解密之后得到客户端密钥,然后用客户端密钥对返回数据进行对称加密,这样数据就变成了密文。 服务器将加密后的密文返回给客户端。 客户端收到服务器发返回的密文,用自己的密钥(客户端密钥)对其进行对称解密,得到服务器返回的数据。 「https一定安全吗?」 https的数据传输过程,数据都是密文的,那么,使用了https协议传输密码信息,一定是安全的吗?其实不然 比如,https 完全就是建立在证书可信的基础上的呢。但是如果遇到中间人伪造证书,一旦客户端通过验证,安全性顿时就没了哦!平时各种钓鱼不可描述的网站,很可能就是黑客在诱导用户安装它们的伪造证书! 通过伪造证书,https也是可能被抓包的哦。 1.2 对称加密算法 既然使用了https协议传输用户密码,还是 「不一定安全」,那么,我们就给用户密码 「加密再传输」 呗~ 加密算法有 「对称加密」 和 「非对称加密」 两大类。用哪种类型的加密算法 「靠谱」 呢? 对称加密:加密和解密使用 「相同密钥」 的加密算法。 常用的对称加密算法主要有以下几种哈: 如果使用对称加密算法,需要考虑 「密钥如何给到对方」 ,如果密钥还是网络传输给对方,传输过程,被中间人拿到的话,也是有风险的哦。 1.3 非对称加密算法 再考虑一下非对称加密算法呢? 「非对称加密:」 非对称加密算法需要两个密钥(公开密钥和私有密钥)。公钥与私钥是成对存在的,如果用公钥对数据进行加密,只有对应的私钥才能解密。 ...

October 2, 2024

展开运算符

ES6 新增了很多特性,扩展运算符就是其中一个。简单的三个点号,让原本繁琐的代码瞬间变得简洁、优雅。就让我们看看它有哪些常见用法吧。 让函数的 Apply 调用方式成为过去 有时候我们需要通过 Function.prototype.apply 来调用函数,参数就放在一个数组里。 function doStuff (x, y, z) { } var args = [0, 1, 2]; // 调用函数并传参 doStuff.apply(null, args); 现在有了展开运算符,我们可以完全避免使用 apply,只需在调用函数时在数组前加上展开运算符: doStuff(...args); 代码变得更短、更简洁,也不需要一个毫无用处的 null! 合并数组 合并数组有各种方式,但是展开运算符为我们提供了一种新思路: arr1.push(...arr2) // 在数组后面添加 arr2 里的元素 arr1.unshift(...arr2) //在数组前面添加 arr2 里的元素 如果你想合并两个数组,并且想把元素放到数组中的任何位置,你可以这么做: var arr1 = ['two', 'three']; var arr2 = ['one', ...arr1, 'four', 'five']; // ["one", "two", "three", "four", "five"] 代码少了很多,还可以随意控制位置! 拷贝数组 拷贝数组是个常见的任务,过去我们使用 Array.prototype.slice 来完成,但现在我们可以通过展开运算符得到数组的副本: var arr = [1,2,3]; var arr2 = [...arr]; // 等同于 arr.slice() arr2.push(4) 这里要注意,数组里的对象仍然是引用类型的,因此实际上并不是所有内容都被“拷贝”了。 将参数或 NodeList 转成数组 跟拷贝数组类似,我们用 Array.Prototype.slice 将 NodeList和 arguments 对象转成真正的数组,但是现在我们可以使用展开运算符完成这个任务: ...

October 2, 2024

表单控件

01、<form>表单 表单是比较重要的HTML元素,主要作用是向服务端提交数据。比如结合表单元素input使用,通过内部的button按钮提交(type=“submit”)表单数据。 元素/属性 描述 值/备注 <form> 表单元素 action 提交表单的目标(服务端)地址url method 提交数据的方式,就是数据传输的方式 get:通过URL提交数据 url?uname=1&age=2;post,通过HTTP表单数据提交,键值格式。 target 提交数据时打开action url的方式 _self:当前窗口(默认值);_blank:新窗口 enctype 编码类型(encode type),规定了form表单在发送到服务器时候编码方式,不常用。 application/x-www-form-urlencoded:编码所有字符(默认);multipart/form-data :混合类型, 表单中有文件上传时使用;text/plain:纯文体,空格转换为 “+” 加号,不对特殊字符编码 submit() 提交表单数据,通过js代码调用 <fieldset> 表单分组,默认样式:一个框 便于表单样式管理的语义化元素 form from的id,当<fieldset>不在form中时 disabled 整个分组都不可用 <legend> 作为<fieldset>的标题,显示在框上 (legend /ˈledʒənd/ 铭文、图例) <style> #form fieldset { border: 1px solid skyblue; padding: 20px 10px; border-radius: 5px; text-align: center; margin: 10px 0px; } #form fieldset legend { font-size: 1em; border: 1px solid rgb(236, 175, 43); border-radius: 1em; padding: 3px 15px; } </style> <form id="form" action="#" target="_self" method="post"> <fieldset> <legend>登录</legend> <input type="text" name="uname" placeholder="请输入用户名" required maxlength="36"> <input type="password" name="upwd" required maxlength="12" placeholder="输入密码"> <input type="submit" value="submit-登录"> </fieldset> </form> 📢注意:<form>提交数据时参数名为表单元素的name,因此表单控件须设置name属性。 ...

September 25, 2024

Objective-C与Swift区别

语言设计 Objective-C 是C的超集,在C的基础上添加了Smalltalk风格的消息传递机制 对象间交互不通过调用方法而是发送消息,以实现面向对象编程和超高的动态性 动态性由 Objective-C Runtime 系统支持,可以在运行时修改类结构、方法交换等,详见 Runtime 弱类型语言,核心类型id可以指向任何Objective-C对象 Swift 从很多现代化语言中汲取精华,是一门偏重于安全、性能的现代化语言 Swift 也有自己的运行时系统(Swift Runtime),但设计哲学完全不同,更强调编译期优化 强类型语言,编译器在编译阶段进行严格的类型检查 语法特性 Objective-C 语法冗长,需要显式声明类型 命名通常较长且需要添加前缀避免冲突 需要单独的.h和.m文件声明接口和实现 引用其他文件/模块必须要import(Objective-C中import详解) 协议不支持默认实现 泛型是"轻量级泛型",仅用于编译时类型检查,运行时类型信息会被擦除 Swift 语法简洁,强大的类型推断减少冗余的类型声明 有命名空间(模块)的存在避免命名冲突 不需要单独的头文件声明接口 默认情况下变量必须初始化 引用同模块的其他文件不需要import(Swift中import详解) 协议支持默认实现、支持拓展,支持关联类型以实现泛型协议 值类型也可以遵守协议 泛型在编译时进行类型特化优化,支持完整的泛型元编程 内存管理 Objective-C 大部分都是引用类型,依赖引用计数管理内存 ARC在编译时自动插入retain、release、autorelease调用 64位系统使用优化的isa(Non-pointer isa),通过位域在isa中嵌入引用计数(extra_rc字段),溢出时使用SideTable存储 实例变量(ivar)会被Runtime自动初始化为0/nil Swift 引用类型也是依赖引用计数管理内存 存在大量的值类型,例如结构体、枚举、数组、字典等 非逃逸的值类型的内存存在栈区由系统进行自动管理 纯Swift类的引用计数直接存储在对象头部的RefCount字段中,无需SideTable,访问更高效 编译器在编译时就确保了初始化,不需要运行时统一清零 拓展:值类型和引用类型的区别 拓展:iOS中的内存管理 性能对比 Objective-C 基于消息传递机制本质上是动态派发,运行时查找方法实现,有一定开销 ARC管理引用计数也有开销 引用类型内存存在堆区,内存管理的性能开销会比栈区多很多 Swift 方法派发尽可能使用静态派发(如对final类/方法、私有方法、值类型方法的调用) 编译器可以内联优化,速度接近C 内联优化:比如A方法调用B方法,B方法调用C方法,代码编写调用A方法时编译器通过内联直接调用C方法的实现 值类型内存分配在栈区,性能开销相对较小 虽然值类型是深拷贝,Swift为了优化值类型性能引入写时拷贝(Copy-on-Write)机制,值类型只在写操作发生深拷贝,其他时候都是浅拷贝 安全性 Objective-C 弱类型(动态类型)占主导 核心类型id可以指向任何Objective-C对象 编译器几乎不做类型检查,会有运行时崩溃的风险(如向不响应某消息的对象发送该消息) nil消息发送不会崩溃但可能导致逻辑错误,不容易感知 局部变量不会自动初始化,可能包含栈上的垃圾数据(未定义行为) Swift 可选类型强制处理空值,编译器强制解包检查 强类型和编译时检查阻止了大量常见错误 变量必须初始化后才能使用,编译器在编译阶段强制检查,消除未定义行为 强制初始化使编译器能更精确地追踪变量生命周期,带来更多优化机会 数组越界等操作会在运行时触发明确的崩溃,便于定位问题 编程范式 Objective-C 主要是面向对象编程(OOP) 通过继承实现代码复用 依赖Runtime实现面向切面编程(AOP),如Method Swizzling Swift 拥抱多范式,对函数式编程、面向协议编程(POP)、声明式编程很友好 通过协议扩展提供默认实现,协议组合替代多继承 值类型(struct/enum)也可以遵循协议,不再局限于类 拓展:OOP、POP与AOP ...

May 8, 2026

iOS音视频原理

音视频开发是iOS中一个庞大且核心的技术领域,涵盖采集、编码、传输、解码、渲染等完整链路。本文从基础概念出发,深入iOS平台提供的音视频框架和底层原理。 音视频处理全链路 一个完整的音视频系统(如直播、视频通话、短视频)包含以下环节: flowchart LR A["采集Camera/Mic"] --> B["前处理美颜/降噪"] B --> C["编码H.264/AAC"] C --> D["封装FLV/MP4"] D --> E["传输RTMP/HLS"] E --> F["解封装解析容器"] F --> G["解码VideoToolbox"] G --> H["后处理滤镜/混音"] H --> I["渲染Metal/Speaker"] 环节 做什么 iOS中对应的技术 采集 从摄像头/麦克风获取原始数据 AVCaptureSession 前处理 美颜、滤镜、降噪、回声消除 CoreImage、vDSP 编码 将原始数据压缩为体积更小的格式 VideoToolbox、AudioToolbox 封装 将编码后的音视频数据打包到容器中 AVAssetWriter 传输 通过网络发送到服务器或其他端 URLSession、第三方推流SDK 解封装 从容器中分离出音频流和视频流 AVAssetReader 解码 将压缩数据还原为原始像素/采样数据 VideoToolbox、AudioToolbox 后处理 滤镜、水印、混音等 CoreImage、Metal 渲染 将画面显示到屏幕、声音输出到扬声器 AVSampleBufferDisplayLayer、Metal、AudioUnit 核心术语 在深入细节之前,先了解几个贯穿全文的核心概念: 什么是编解码器(Codec) Codec = Coder + Decoder,即编码器+解码器的合称。编码器负责将原始音视频数据压缩为更小的格式,解码器负责将压缩数据还原。 音视频中有两类完全不同的"格式"概念,初学者极易混淆: 编码格式(Codec):定义数据怎么压缩 视频:H.264、H.265、VP9、AV1 音频:AAC、MP3、Opus、FLAC 封装格式(Container):定义压缩后的数据怎么组织存储 MP4、MOV、FLV、MKV、TS 两者是独立的,可以自由组合: MP4容器 + H.264视频 + AAC音频 (最常见的组合) MOV容器 + H.265视频 + AAC音频 (iPhone录制默认) FLV容器 + H.264视频 + AAC音频 (直播推流常用) MKV容器 + AV1视频 + Opus音频 (新一代免费组合) 什么是H.264和H.265 H.264(也叫AVC,Advanced Video Coding)是目前使用最广泛的视频编码标准,由ITU-T和ISO/IEC联合制定,2003年发布。它定义了一套将视频帧压缩为比特流的算法规范——包括如何划分图像块、如何预测、如何变换量化等。几乎所有设备和平台都支持H.264硬件解码。 ...

May 2, 2026