关怀系统不该止步于关怀学生

千万不必去打听丧钟为谁而鸣;
丧钟为你而鸣。

约翰·堂恩

从其他同学那里得知学校要启用学生关怀系统了。我以前没有了解过什么是学生关怀系统,了解之后我发现这种关怀系统真是一个好东西,它太好了以至于不应该局限于校园,局限于学生,这种关怀系统如果推广到全社会将发挥更大的作用。

学生关怀系统的主要功能有:异常学生提醒、消费情况查看、行为轨迹追踪、上网信息监控、成绩变化波动提醒、用餐出勤归宿情况计算、相似轨迹同行者推荐。根据介绍,异常学生提醒能够分析学生的消费、行为轨迹、成绩波动数据,计算出消费异常、疑似不在校、成绩波动大等等异常情况。消费情况查看功能可以分析学生的消费数据,精准发现贫困学生,通过数据解决贫困生认定困难或碍于自尊不愿主动申请困难认定的问题。行为轨迹追踪可以通过学生网络接入情况分析学生的活动热区,发现学生的动向,防止学生失联。上网信息监控也是根据学生的网络接入情况工作,它能找到网络成瘾的问题学生,它还能对学生在网络上浏览的信息和发布的言论进行敏感监控来及早发现问题学生。成绩变化波动提醒支持查看学生每个学年学期的成绩和排名波动情况,对波动较大的学生及时标注提醒。用餐出勤归宿情况计算可以通过对数据进行综合分析得到学生的用餐、出勤、归宿情况,用数据说话。相似轨迹同行者推荐结合学生的活动范围和时间区间来寻找与某个学生轨迹相似、行为相近的人,进一步了解学生。

学生关怀系统正常工作的一个条件就是有数据供其进行分析,这一条件在大多数学校都已经得到满足。以我所在的学校为例,可以提供给学生关怀系统的数据就包括:校园网使用情况、教务系统成绩数据、校园一卡通使用情况、各处的人脸识别门禁数据。至于遍布四处的校园视频监控探头,我认为我所在的学校还不具备对其信息进行大规模分析处理的能力,但不排除其被使用的可能。

为什么我说关怀系统不该止步于关怀学生?这有两个方面的原因。一方面,关怀系统作为一种数据分析工具能通过已经搜集的数据发现更多信息,为管理者更详尽的内容来帮助管理被管理者。在校园里它只能用来管理学生,最多只是让学校朝着更好的方向发展,有些委屈它的才能了。如果推广到社会,它将在脱贫、反腐、疫情防控、犯罪预防、经济管理、舆情监控等多个领域发挥更大的作用,让社会朝着更好的方向发展。另一方面,现在的社会已经具备这种关怀系统工作所需的最基本的客观条件,即数据。银行卡的数据可以充当校园一卡通的数据,运营商基站接入数据可以充当校园网使用情况数据,信用分、所得收入、社会贡献等数据可以充当教务系统的成绩数据,手机定位数据可以充当校园里各处的人脸识别门禁数据。可以看到,我们的社会已经有足够的数据支撑这样一个关怀系统的正常工作,它能为管理者带来的好处也是显而易见的,所以我说关怀系统不该止步于关怀学生,它完全可以发挥更大的作用。

根据软件公司的介绍,2016年9月西安交通大学就已经试用了学生关怀系统,而中山大学从2017年3月开始使用学生关怀系统。现在是2022年9月,我的学校才跟上名校的步伐,这种好东西应该更早引入我们的校园。现在是2022年9月,我看社会上还没有大规模使用这种关怀系统,这种好东西应该更快推广到我们的社会。

提升公开讲话自信的三个技巧

“自”信

在进行其他训练之前,每个人都能做到的最简单的事情就是“自”信。

当出现小失误的时候,不要一直“抱歉”、“对不起”说个不停。当感觉大家都没有跟上思维时,不要说“可能大家都没听懂我在说什么”。自己发现问题后,自己把问题解决就好。出现失误,更正即可;表述不清,换种方式重新说清即可。

无论是接连不断的道歉还是反复强调自己没有让大家听懂,都不会帮助你解决问题,更不会帮助你的听众,反而会让你更加紧张或者自我否定,所以在公开讲话时不要这样做,要“自”信。

听众并非时刻注意你的一举一动

当进行公开讲话时,你可能以为听众都会把100%的注意力投入到你身上,以为听众会注意到你说的每一个字,以为听众会注意到你做的每一个动作。事实是,听众更可能会在自己的头脑里构建自己的理解,而忽略了你演讲的细节。

很多对你而言非常明显的细节,对听众而言,甚至会被全部忽略。这也是人们会反复强调重点的原因——为了不让听众错过重点——听众总是在忽略信息。知道了这一点,下次公开讲话时你会更加自信。

训练克服恐惧

正常情况下,每个人从小到大都在讲话,经过这么多年的练习,一个人的语言能力已经足以支撑其自如地讲话。每个人都至少能和某个人自然舒适地讲话,这个人可能是家人、朋友或伴侣,甚至可以是自己。

在公开讲话时不自信,更多的情况是思想没有跟上能力,具体一点就是不相信自己能在压力之下把话讲好。这种压力往往来自于对尴尬的恐惧。让自己多尴尬几次是一种解决办法。

可以这样训练:在一个安全的环境里——比如找上一些志同道合的朋友,大家都是一起进行训练的,所以并不会真的产生实质性损失——让自己处于各种可能出现的尴尬场景之中。可以让“听众”问你一些尖锐的问题;可以让“听众”“强迫”你把话讲明白,把句子说完整;还可以让“听众”在你讲话时打断你,使你不得不在讲话正中间冷场很长一段时间。总之,让“听众”想方设法使你尴尬难堪。多次进行这种训练,日积月累你会发现自己能处理好各种情况,在压力之下也能自信从容地完成自己的公开讲话。

Python PuLP 简单求解线性规划问题

一般步骤

  1. 导入 PuLP
  2. 使用 LpProblem 定义问题(名称、类型)
  3. 使用 LpVariable 定义变量(名称、下限、上限、类型)
  4. 添加目标表达式
  5. 添加约束条件
  6. 求解
  7. 输出结果

简单实例

问题

要用最低成本生产一种猫粮,同时使这种猫粮的营养成分满足一定的要求。生产者希望通过改变每种原料的添加量来实现这一目标。

每份猫粮为100克,其营养成分要求为:蛋白质不少于8克,脂肪不少于6克,纤维不多于2克,盐不多于0.4克。

猫粮的主要原料和其每克成本为:鸡肉($0.013)、牛肉($0.008)、羊肉($0.010)、大米($0.002)、小麦($0.005)、凝胶($0.001)。

每克原料对最终猫粮的营养贡献如下表所示:

原料蛋白质脂肪纤维
鸡肉0.1000.0800.0010.002
牛肉0.2000.1000.0050.005
羊肉0.1500.1100.0030.007
大米0.0000.0100.1000.002
小麦0.0400.0100.1500.008
凝胶0.0000.0000.0000.000
每克原料对最终猫粮的营养贡献,以克为单位。

求成本最低的原料添加量方案。

简化版问题

先考虑简化后的问题,假设只有鸡肉和牛肉两种原料。原料的添加量不能为负值。

简化版问题求解

导入 PuLP

from pulp import *

使用 LpProblem 定义问题

prob = LpProblem("The Cat Food Problem", LpMinimize)

LpProblem 第一个参数为问题的名称,可自定义。第二个参数为问题类型,可在 LpMinimizeLpMaximize 中选择。本题求成本最小值。

使用 LpVariable 定义变量

x1 = LpVariable("ChickenPercent", 0, None, LpContinuous)
x2 = LpVariable("BeefPercent", 0)

LpVariable 第一个参数为变量的名称,可自定义。第二个参数为变量的下限,本题为 0。第三个参数为变量的上限,None 在上下限处表示正无穷或负无穷。第四个参数为变量的类型,可选的值有 LpContinuous, LpIntegerLpBinary,意思即为字面意思,默认为 LpContinuous

添加目标表达式

prob += 0.013 * x1 + 0.008 * x2, "Cost per can"

"Cost per can" 为标识符,可自定义也可省略。注意使用 += 而不是 =

添加约束条件

prob += x1 + x2 == 100, "PercentSum"
prob += 0.100 * x1 + 0.200 * x2 >= 8.0, "Protein"
prob += 0.080 * x1 + 0.100 * x2 >= 6.0, "Fat"
prob += 0.001 * x1 + 0.005 * x2 <= 2.0, "Fibre"
prob += 0.002 * x1 + 0.005 * x2 <= 0.4, "Salt"

同样,引号中的内容为标识符,可自定义也可直接省略整个引号。注意使用 += 而不是 =

求解

prob.solve()

使用默认求解器进行求解。

输出结果

print("Status:", LpStatus[prob.status])
for v in prob.variables():
    print(v.name, "=", v.varValue)
print("Cost per can = ", value(prob.objective))

prob.status 是求解的状态,为整数型,有 LpStatusOptimal LpStatusNotSolved, LpStatusInfeasible, LpStatusUnboundedLpStatusUndefined 这四种返回,意思即为字面意思。该值是整数型,可以通过 LpStatus[prob.status] 来获取字符串类型的状态。

prob.objective 是求解后目标表达式的值,在本题中即为所求的最低成本。

完整问题求解

求解的基本步骤是一样的,不过在使用 LpVariable 定义变量、添加目标表达式和添加约束条件时可以使用一些方法来减少需要编写的代码。

导入 PuLP

from pulp import *

定义问题中的数据

Ingredients = ["CHICKEN", "BEEF", "MUTTON", "RICE", "WHEAT", "GEL"]

costs = {
    "CHICKEN": 0.013,
    "BEEF": 0.008,
    "MUTTON": 0.010,
    "RICE": 0.002,
    "WHEAT": 0.005,
    "GEL": 0.001,
}

proteinPercent = {
    "CHICKEN": 0.100,
    "BEEF": 0.200,
    "MUTTON": 0.150,
    "RICE": 0.000,
    "WHEAT": 0.040,
    "GEL": 0.000,
}

fatPercent = {
    "CHICKEN": 0.080,
    "BEEF": 0.100,
    "MUTTON": 0.110,
    "RICE": 0.010,
    "WHEAT": 0.010,
    "GEL": 0.000,
}

fibrePercent = {
    "CHICKEN": 0.001,
    "BEEF": 0.005,
    "MUTTON": 0.003,
    "RICE": 0.100,
    "WHEAT": 0.150,
    "GEL": 0.000,
}

saltPercent = {
    "CHICKEN": 0.002,
    "BEEF": 0.005,
    "MUTTON": 0.007,
    "RICE": 0.002,
    "WHEAT": 0.008,
    "GEL": 0.000,
}

为了简化后续所需编写的代码,在此把问题中需要使用的数据先进行定义。

使用 LpProblem 定义问题

prob = LpProblem("The Cat Food Problem", LpMinimize)

使用 LpVariable 定义变量

ingredient_vars = LpVariable.dicts("Ingredients", Ingredients, 0)

在这里我们直接使用先前定义的 Ingredients 列表来定义变量。

添加目标表达式

prob += (
    lpSum([costs[i] * ingredient_vars[i] for i in Ingredients]),
    "Cost per can",
)

同样,在这里我们直接使用先前定义的 costs, ingredient_vars, Ingredients 来定义变量。

其中,lpSum 会把其参数列表的各项相加。

添加约束条件

prob += lpSum([ingredient_vars[i] for i in Ingredients]) == 100, "PercentSum"
prob += (
    lpSum([proteinPercent[i] * ingredient_vars[i] for i in Ingredients]) >= 8.0,
    "Protein",
)
prob += (
    lpSum([fatPercent[i] * ingredient_vars[i] for i in Ingredients]) >= 6.0,
    "Fat",
)
prob += (
    lpSum([fibrePercent[i] * ingredient_vars[i] for i in Ingredients]) <= 2.0,
    "Fibre",
)
prob += (
    lpSum([saltPercent[i] * ingredient_vars[i] for i in Ingredients]) <= 0.4,
    "Salt",
)

同上,使用预先定义的数据来简化代码。

求解

prob.solve()

输出结果

print("Status:", LpStatus[prob.status])
for v in prob.variables():
    print(v.name, "=", v.varValue)
print("Cost per can = ", value(prob.objective))

参考资料

Optimization with PuLP(外部链接)

为移动硬盘禁用 UAS

使用 UAS 可能导致 smartmontools 等工具无法正常工作。可以通过编辑 GRUB 配置给内核传递参数,为某一特定设备禁用 UAS。

例如,编辑 /etc/grub.d/10_linux 文件,在文件首部添加 GRUB_CMDLINE_LINUX="usb_storage.quirks=idVendor:idProduct:u" 这一行配置,将 idVendor 和 idProduct 替换为设备的实际值。

idVendor 和 idProduct 可以通过 dmesg 获得。

比如下面的 dmesg 信息:

usb 2-1: new SuperSpeed USB device number 8 using xhci_hcd
usb 2-1: New USB device found, idVendor=0bc2, idProduct=231a
usb 2-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[...]
scsi host4: uas
scsi 4:0:0:0: Direct-Access     VENDOR-NAME               0204 PQ: 0 ANSI: 6
sd 4:0:0:0: Attached scsi generic sg2 type 0
sd 4:0:0:0: [sdc] 1953525168 512-byte logical blocks: (1.00 TB/932 GiB)
sd 4:0:0:0: [sdc] 4096-byte physical blocks
sd 4:0:0:0: [sdc] Write Protect is off
sd 4:0:0:0: [sdc] Mode Sense: 53 00 00 08
sd 4:0:0:0: [sdc] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA
 sdc: sdc1 sdc2
sd 4:0:0:0: [sdc] Attached SCSI disk

从中我们可以得到 idVendor 为 0bc2idProduct 为 231a

编辑完 GRUB 配置文件,运行 update-grub 使配置生效。重启后,即可为 idVendor:idProduct 设备禁用 UAS。

深夜出门撸串?不好意思,给不了你这种安全感

共青团中央微博截图
截图的网页地址:https://m.weibo.cn/status/4125115450451265
唐山市公安局路北分局警情通报
来源:https://m.weibo.cn/detail/4778869062633148

最近唐山烧烤店打人事件让不少人想起了共青团中央 2017 年发的一条微博。或许有人会说这是“恶意翻书”,但人们想起来这条微博更可能是因为一些要素引起了联想:“凌晨两点”、“撸串”、“催命符”……

有人觉得这是名副其实的“打脸”行为。但也许一些人错误解读了这条微博,或多或少产生了幻想。因为从文字本身来看,这条微博的内容只不过:

  • 凌晨两点在路边撸串,半夜独自外出……这些行为在中国是习以为常的生活方式;
  • 凌晨两点在路边撸串,半夜独自外出……这些行为在美国可能成为催命符;
  • 深夜出门,在国外不像在中国这么简单;
  • 美国给不了你深夜出门撸串的安全感。

瞧,这条微博的内容只有这些,再没有其他的东西了。别的意思都是读者自己的想法,而不是微博本身的内容。

下面两点认识有助于避免进入误区:

  • 习以为常的行为不一定是安全的行为。
  • 生活中的很多事情不能当作对立事件来看待。

习以为常是指某种事情经常去做,或某种现象经常看到,也就觉得很平常了。比如某人乘坐机动车时经常不系安全带,也一直没遇到什么事故,他/她对不系安全带习以为常,但我们并不能由此推出这种行为是安全的。

对立事件是指其中必有一个发生的两个互斥事件,互斥事件是指不可能同时发生的事件。比如从一个只有黑球和白球的袋子里任取一个球,“取出的球是黑球”和“取出的球是白球”为一组对立事件;如果取出的不是黑球,那么就一定是白球,反之亦然。而从世界上所有人中随机选择两个人 A 和 B,“A 出生于越南”和“B 出生于印度”既非对立事件又非互斥事件;无论 A 出生在哪里,我们都无法由此得知 B 的出生地,这两者并不相关。

无论对于“凌晨两点在路边撸串,半夜独自外出”多么习以为常,都不能说明这种行为本身是否安全。就算“美国给不了你深夜出门撸串的安全感”,也不会影响“美国之外的某个地方能否给你深夜出门撸串的安全感”。

网站使用 CDN 之后获取访客原 IP 的方法

网络上大多数教程给出的方法都是通过修改程序代码,直接取 X-Forwarded-For 等请求标头的值替换原来 REMOTE_ADDR 变量的值作为原 IP,这种做法有很大的安全隐患,我认为不值得提倡。

安全隐患

根据 RFC 3875 文件,REMOTE_ADDR 变量的值必须为发送请求到服务器的客户端的网络地址。在实践中 REMOTE_ADDR 变量的值往往由服务器直接设置为发送请求的客户端网络地址,而不是由客户端提供,因此其真实性有保证。

与 REMOTE_ADDR 变量不同,X-Forwarded-For 请求标头的值不是由服务器直接决定,而可以由客户端自行设置,实践中一般由代理服务器在转发请求时设置。因为 X-Forwarded-For 请求标头的值可能被客户端伪造,所以不应该轻易相信其真实性。

由于 REMOTE_ADDR 变量的值和 X-Forwarded-For 请求标头的值具有不同的可信度,使用低可信度 X-Forwarded-For 请求标头的值去替换高可信度 REMOTE_ADDR 变量的值便会产生不可忽视的安全隐患。比如,攻击者可以通过伪造 X-Forwarded-For 请求标头来绕过基于 IP 的频率限制、访问控制。更进一步,攻击者还可以构造具有危害性 X-Forwarded-For 请求标头来对服务器进行多种攻击,例如注入恶意代码进行 XSS 攻击就是一种可能的行为。

正确做法

在使用 X-Forwarded-For 请求标头的值之前应该确保其内容可信。因为 X-Forwarded-For 请求标头一般由代理服务器添加,所以至少应该先确认请求来自可信的代理服务器才可使用 X-Forwarded-For 标头的值。

对于使用 CDN 的网站,如果服务器是 nginx,那么在 ngx_http_realip_module 模块的帮助下这一过程将会十分简单。只需编辑相应的 nginx 配置文件,在其中的 http、server 或 location 段,使用 set_real_ip_from 指定可信代理服务器地址(即 CDN 节点的地址),然后使用 real_ip_header 指明一个请求标头(即 X-Forwarded-For 标头),保存并重载 nginx 使配置生效。这样 nginx 收到请求后会检查请求的 REMOTE_ADDR 是否与 set_real_ip_from 相符,如果相符,nginx 会用 real_ip_header 对应标头的值替换 REMOTE_ADDR 原有的值,后端程序只需要正常使用 REMOTE_ADDR 的值即可。这种做法一般不需要改变后端程序。

参考资料

看到亚马逊停止 Kindle 电子书店在中国运营的消息有感

对于 Kindle 中国区用户:从 2023 年 6 月 30 日起将不能购买新的电子书,但仍可下载此前已购买的电子书;从 2024 年 6 月 30 日起将不能从 Kindle 下载任何电子书,只可阅读设备上已经下载的电子书。

亚马逊的这次调整算不上突然。早在半年前,国内主要电商平台的 Kindle 就已经开始缺货,那时是 2022 年 1 月左右。虽然这次亚马逊的通知中提到每位用户可以在 2022 年 10 月 31 日之前退回至多 3 台于 2022 年 1 月 1 日及以后从国内授权经销渠道购买的至今没有质量问题及人为损坏的 Kindle 设备,但实际上能满足亚马逊退货条件的用户并不多。因为国内授权经销渠道在 2022 年 1 月之后基本都是缺货状态,所以大部分满足时间要求的用户都是从非国内授权经销渠道购买到 Kindle 设备的。

之前我就说过纸质书比 Kindle 电子书更可靠,不过那时并没有太多实例。这次 Kindle 电子书店在中国停止运营为我的“反电子书”观点提供了新的证据支持。不要简单地将这次事件视作一家国际公司的区域性运营调整。问题的根源不在这家公司,而在当前的电子书商业运作模式。只不过亚马逊在这个行业中一直扮演着“老大”的角色,所以问题最先从它身上暴露出来。我相信如果当前这种电子书商业运作模式不改变,那么支撑我“反电子书”观点的证据还会越来越多。

自愿行为不一定取决于自己

从前我以为我的人生掌握在自己手里,因为我的自愿行为取决于自己。现在我对这一点不那么确信了。自愿行为可能更多出自一定的欲望和价值观,而欲望和价值观似乎并非取决于自己。

要想知道欲望或价值观能否被自己的意志改变,可以通过一些思想实验来完成。比如,试着说服自己伤害小动物是正确的、善良的行为。或者,试着让自己不再渴望原先渴望得到的东西,无论它是钱、权力、成就、名声、健康、快乐、亲情还是别的什么,试着用意志让自己不再渴望它。

尝试了这样的思想实验,我发觉自己的欲望和价值观好像并不取决于自己,进而产生怀疑:那些我原以为是自愿的行为真的取决于自己吗?我的人生真的掌握在自己手里吗?我的答案不再那么明确。

现在依旧偏爱纸质书的原因

在这篇文章里,如果没有特别说明,电子书均指亚马逊类的电子书。

抛开情怀、习惯等原因,在这个时代依旧偏爱纸质书的一个重要原因是纸质书比电子书更尊重人们的权利。

购买纸质书的行为可以匿名完成,而购买电子书则不行。在书店选好要买的书之后,只用支付现金就能把书带走。在电子书商城买书,除了需要付钱,还需要表明自己的身份(需要登录)。此外,虽然这本电子书已经被“买到手”,但是在阅读时读者仍然需要先表明自己的身份。

购买完一本纸质书,就拥有了这本纸质书;购买完一本电子书,却并不拥有这本电子书。纸质书可以送给别人、借给别人,还可以转手卖掉,电子书却不行。这是因为购买纸质书之后,购买者便对这本纸质书享有所有权,而电子书的购买者却不具有电子书的所有权。这一问题是现在电子书一系列问题的根源。

纸质书比电子书更可靠。这里的可靠不是指内容的可信度,而是指内容能被正常读取的可能性。使用加密格式的电子书需要通过专有软件进行读取,一旦软件公司倒闭、专有软件无法正常运行,电子书可能也就无法再被阅读。阅读纸质书就没有这种顾虑,因为哪怕出版社倒闭了,书也还在自己手里,还是能读。说到这里不得不提电子书公司曾经的行为:在 2009 年,亚马逊远程删除了许多用户设备中的乔治·奥威尔小说《1984》。如果当初这些用户购买的是纸质书,那么亚马逊断然不会闯进用户家中撕毁用户手中的纸质书。

实际上,不是电子书不尊重人们的权利,比如古腾堡计划的电子书做得就很好。电子书原来是没有这些问题的,但当电子书公司介入之后,问题便产生了。

在电子书公司转变这些令人不满的做法之前,对纸质书的偏爱将一直保持下去。

解决 MP4 视频无法在 iPhone 手机播放的问题

本文适用于解决因视频编码导致无法播放的问题。

前言

使用 iPhone、iPad 等苹果设备时会发现有的 MP4 格式视频可以正常播放,而有的却无法播放,在部分 Android 设备上也存在类似问题。大部分情况下,这类无法播放的问题都是因为缺少对应视频编码的解码器造成的,要解决这类问题,一种方法是下载能够解码该视频的播放器,另一种方法是对视频转码。本文介绍后者,即如何对视频进行转码使其能被大多数设备播放。

如果去搜索格式转换、视频转码等内容,会找到各类视频处理工具,其中大部分都需要付费或者含有许多广告。实际上,很多视频问题使用 FFmpeg 就能解决问题,这款免费、开源且无广告的工具也能通过转码解决 MP4 无法在苹果手机播放的问题。

下载 FFmpeg

如果你的电脑上还没有 FFmpeg 程序,你可以前往 FFmpeg 官网(外部链接)下载最新版本的 FFmpeg 程序。

对于 Windows 用户,我在这里提供了一份副本,可以直接下载适用于 64 位操作系统的 FFmpeg n5.0 压缩包(ZIP,103.8 MiB),解压后即可使用命令行调用程序。

对于 Linux 用户,大多数 Linux 发行版的软件仓库中都包含了 FFmpeg 程序,可以直接使用包管理器安装。例如 Debian 和 Ubuntu 用户可直接使用 apt-get install ffmpeg 命令安装 FFmpeg 程序。

执行命令进行转码

使用 FFmpeg 对视频转码,让视频获得最好的兼容性,只用把下面命令中的 <input> 替换为需要转换的视频文件名,然后执行即可:

ffmpeg -i <input> -c:v libx264 -crf 23 -profile:v baseline -level 3.0 -pix_fmt yuv420p -c:a aac -ac 2 -b:a 128k -movflags faststart ksk-example.mp4

得到的 ksk-example.mp4 文件就是转码后生成的能够被大多数设备(包括使用 iOS 的 iPhone 和 iPad 设备)直接正常播放的 MP4 视频。

解释说明

现在问题已经解决。接下来我将进行一些解释说明,如果不感兴趣,可以直接略过。

H.264/AAC 的 MP4 是 HTML5 下兼容性最好的组合。对于没有 H.264 解码器的浏览器,则应该提供 VP9/Vorbis 的 WebM 视频。有这两种组合,基本上就可以兼容目前的所有主流浏览器。对于本文来说, H.264 编码的视频能够在大多数移动设备上正常播放,所以我们采用 H.264 编码。

用 -profile:v baseline -level 3.0 参数可以兼容一些无法处理 H.264 中 CPU 密集型特性的老设备,如果没有这个需求,可以去掉这一参数。

-crf 23 参数影响视频的质量,数值越低质量越好,一般设置在18到28这个区间内即可。如果需要指定视频码率,可以使用类似 b:v 1000k 这样的参数,这么做是因为一些性能不太强劲的设备可能会无法处理过高码率。

-movflags faststart 参数在流媒体播放的时候起作用,这个命令把视频容器的元信息移到文件头部。默认情况下(没有这个参数时),视频元信息在文件尾部,造成的结果是只有整个文件都加载完成后才能开始播放;有了这个参数,视频不必全部加载就可以播放。