0%

最近碰到一个蛮有意思的问题. 我原来在windows平台下有使用一款叫timesnapper的软件. 这个软件的作用是可以在间隔一段固定的时间后做一次屏幕截图, 将图片保存下来. 我用这个功能主要是用来做时间开销的记录. 此外, 之前在hackernews上看到过一个有意思的帖子是对比20年来的桌面的变化. 所以我觉得定期记录一下自己的电脑也算是蛮有意思的一件事情.

不过在我换到mac平台的时候, 没有找到一款类似的软件. 于是我写了一个很简单的定时脚本, 每隔几分钟调用一下macos自带的截屏工具保存图片. 这里的代码很简单, 大致如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/local/env python
#coding=utf8
import datetime
import subprocess
import os


def main():
path = '/Users/xdsoar/Documents/ScreenRecord/'
time = str(datetime.datetime.now())
date = time[0:10]
month = date[0:7]
path = path + month + '/'
newPath = path + date + '/'
if not os.path.exists(path):
os.mkdir(path)
if not os.path.exists(newPath):
os.mkdir(newPath)
time = time.replace(' ', '_').replace(':','.')
file1 = newPath + time + "_1.png"
file2 = newPath + time + "_2.png"
file3 = newPath + time + "_3.png"
param = "screencapture -x " + file1 + " " + file2 + " " + file3
print("calling the cmd" + param)
subprocess.call(['/usr/sbin/screencapture', '-x', file1, file2, file3])

if __name__ == '__main__':
main()

写完以后放了一个定时任务在crontab里就算完工了. 这几年来一直运转正常. 不过有一个小毛病过去倒是一直困扰着我. pc上的timesnapper有一项功能就是检测到画面没有变化时, 就不会截屏. 我猜测原理可能是识别如键盘、鼠标的活动时间, 或者干脆就是识别画面. 这个功能可以节省非常多的冗余图片. 而我自行写的这个截图小程序自然是没有这个功能的.

于是这个周末稍微花了点时间研究了一下图像查重的方法. 很容易查到了一个叫做ssim的算法, 用于判断图片之间的相似度. 这里的相似度是基于结构相似度, 虽说可能和我的场景不是完全一致, 不过实际测试过以后发现还是比较符合预期的. 基于方便的原则, 还是用python写了一个, 基于opencv的ssim实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import os
from skimage.measure import compare_ssim
import cv2
import cProfile

def get_file_list(path):
files = os.listdir(path)
gray_files = []
file_compare_result = []
times = 0
for file in files:
image_file = cv2.imread(path+'/'+file)
gray_image = cv2.cvtColor(image_file, cv2.COLOR_BGR2GRAY)
is_dedup = False
for i in range(0, len(gray_files)):
gray_file = gray_files[i]
if gray_file.shape != gray_image.shape:
continue
(score, _) = compare_ssim(gray_image, gray_file, full=True)
if score > 0.99:
gray_files.pop(i)
gray_files.insert(0, gray_file)
is_dedup = True
break
file_compare_result.append((file, is_dedup))
if len(gray_files) > 10:
gray_files.pop()
gray_files.insert(0, gray_image)
print('done once ' + str(times))
times += 1
return file_compare_result



if __name__ == "__main__":
cProfile.run('result = get_file_list("/Users/xdsoar/Documents/project/image-dedup/sample")')
print(result)

这段代码里用了cProfile, 是因为写完以后对性能有点好奇, 不知道是花在图像加载上, 还是实际的ssim算法上. 跑了一下发现基本都是花在图像加载上. 因此代码里对于图片的比较稍微做了点优化. 虽然说只比较前后两张图片的话, 时间复杂度就会很美好了. 不过实际上会遇到一种情况就是电脑锁屏以后的待机画面经常被不连续的截进来. 因此还是希望尽可能放大对比的范围. 当然如此一来, 时间复杂度就可能上升到o(n²). 对于本身就比较耗时的ssim算法而言, 时间开销就会相当庞大.

顺带一提, 我原来在mac上运行这个代码的时候, 基本上cpu都被吃满了, 因此我推测ssim算法是可以用到多线程的. 于是我尝试同样的代码和文件搬到了台式机上, 因为台式机用的是8核cpu而笔记本2核, 预期是可以有数倍的性能提升的. 结果台式机上运行反而慢过笔记本, 而且cpu并没有吃满, 只使用了10%左右. 这里我的猜测是笔记本上的opencv可能是使用了pyopencl, 用核显做了加速. 而台式机因为显卡是nvidia的, 可能并不支持加速.

于是我开始考虑如果能使用nvidia的显卡(我用的是1060)来做计算的话, 或许还能快一些. 于是开始找支持n卡加速的ssim算法库. 看了一圈发现pytorch支持n卡的cuda框架, 并且也有ssim算法的实现. 于是几经波折在pc上装上了pytorch. 代码也改成了如下版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import os
from pytorch_msssim import ssim, ms_ssim
import torch
import numpy as np
from PIL import Image
import shutil


def get_file_list(path):
files = os.listdir(path)
gray_files = []
file_compare_result = []
times = 0
for file in files:
if not (os.path.isfile(path+file)):
continue
image_file = Image.open(path + file)
size = image_file.size
resize_image = image_file.resize((int(size[0]/2), int(size[1]/2)),Image.ANTIALIAS)
image_array = np.array(resize_image).astype(np.float32)
image_torch = torch.from_numpy(image_array).unsqueeze(0).permute(0, 3, 1, 2) # 1, C, H, W
image_torch = image_torch.cuda()
is_dedup = False
for i in range(0, len(gray_files)):
gray_file = gray_files[i]
if gray_file.shape != image_torch.shape:
continue
score = ssim(image_torch, gray_file)
if score.item() > 0.99:
gray_files.pop(i)
gray_files.insert(0, gray_file)
is_dedup = True
file_compare_result.append((file, is_dedup))
if len(gray_files) > 100:
gray_files.pop()
gray_files.insert(0, image_torch)
print('done once with ' + str(times))
times += 1
return file_compare_result


def compare(file1, file2):
ima1 = to_cuda(Image.open(file1))
ima2 = to_cuda(Image.open(file2))
score = ssim(ima1, ima2)
print(score.item())

def to_cuda(ima):
image_array = np.array(ima).astype(np.float32)
image_torch = torch.from_numpy(image_array).unsqueeze(0).permute(0, 3, 1, 2) # 1, C, H, W
return image_torch.cuda()


if __name__ == "__main__":
path_arg = "C:\\Users\\qwerp\\Documents\\source\\dedup\\sample\\"
results = get_file_list(path_arg)
os.mkdir(path_arg + 'dedup')
for result in results:
if result[1]:
shutil.move(path_arg+result[0], path_arg+'dedup/'+result[0])
print(results)

结果是速度上确实有了非常感人的提升, 体感应该有5到10倍吧. 以后如果换用更好的显卡, 应该会有更好的效果. 另外在做优化的时候小开了一个脑洞, 用PIL将图像长宽都缩小到原来的一半, 结果不仅在计算速度上有非常大的提升, 而且显存能存储的图片数量也是有了一个量级的提高. 是的, 这里依旧限制了只保存100张图片进行对比, 原因就是将大量图片转换成向量以后保存在显存里还蛮大的, 未缩减大概是单张80mb. 最后的对比结果也基本符合预期, 重复的图片都找了出来. 只是这里阈值设置低一点的话(比如95), 那么一些稍微有点区别的截图也会被抓进来. 很难说哪样取舍更好. 我想最终我会使用的方案是删掉那些完全重复(重合度超过99%)的截图释放空间, 而把那些高重复度的截图单独建目录存放.

做完以后我又想到一个点子. 同样用ssim算法, 不知道能不能找出所有截图里最另类的一批? 因为图片存着以后我还是会定期回顾的. 但是大量图片的回顾确实也非常累, 如果能从大量图片中找到相对高价值的那些, 应该能节省不少精力. 关于这一点, 接下来有时间我想我会琢磨琢磨.

发布地址, 以及github的链接.

本身是在这个月初的时候做的东西, 初版发布的时候还在nga发了帖子. 起因是dnf新的100级版本是个深渊版本, 想大致算一下需要投入的时间.

周末某天早上的时候突发奇想, 用python写了点代码用蒙特卡洛来算概率. 算了几组以后发觉对界面操作的需求还挺大的, 于是决定gui重构一版. 因为要画界面, 因此也就不想用python了, 于是盯上了vue. 本来是打算用vue+electron做一个跨平台的客户端版本的. electron也一直是我想了解的一项技术, 用vue只是单纯觉得更轻量一点, 上手可能可以快一点.

最后整套技术栈用的就是vue+typescript+electron(可有可无). 实际开发中基本感知不到electron, 包括调试和发布最后其实也只是基于传统web的形式. 开始趟了点小坑, 用的electron-vue的模板, 结果这玩意连启动hello world都会报错……后面改用vue-clielectron-builder, 总算是顺利多了. 改typescript稍微花了点时间, 主要是vue本身的教程都是基于js的, 用ts的思路来理解的话多少有点不顺, 这篇博客看看还是蛮有收获的.

最后发布用了和博客类似的基础设施. 只不过之前博客用的是hexo的s3插件, 这次是更通用的npm插件, 以后如果有类似实验性质的项目, 应该都可以用类似的方式来发布. 我特意做了这个lab的二级域名, 也是希望以后能再往里面填一些东西. 因为原来用的就是s3, 所以这次发布也还是在s3, 然后用cloudfront做加速, 国内访问确实算不上非常快, 后面有机会再看是不是迁到阿里云之类国内的服务商吧. 本次部署的时候遇到了一些坑, 基本都是我在这篇博文里提到的, 想不到完全适用, 还是挺意外的.

发布以后我想着针对像多选, 更复杂的计算条件做了一些优化. 不过本身这个应用的使用场景还是蛮有限的, 后续是否继续更新, 就看心情了…..不过作为vue的上手项目, 还算不错.

我一直习惯用笔记本外接dock的用法,特别是最近几代的笔记本(不管是pc还是mac)配备了thunderbolt3接口以后。tb3接口的高带宽,实现了单一接口外接多个高分辨率显示器,使用体验上感觉还是非常好的。像是我带笔记本在公司里用。只要在工位上放置一个tb3的扩展坞,使用的时候只要连接一根tb3数据线,就能满足电脑充电、外接显示器等功能。外设的连接,像鼠标和键盘当然也是可以的。不过因为这样一来桌面的线材会非常多,所以键鼠一类低速设备我还是倾向于使用无线的。

不过这种使用方式在我的实际使用中存在一个不大不小的痒点。就是在笔记本连接了扩展坞以后,显示器并不能自动的从台式机切换到笔记本上。而且自动切换也不现实,因为设备当然不知道我什么时候想用笔记本,什么时候想用台式机。在只有单个屏幕的时候,这件事还不算很麻烦,我只要通过显示的OSD菜单切换输入源就可以了。但是当我连接了多个显示器以后,这个问题就愈加麻烦。直到某次在论坛偶然看到有人提起DDC协议,我才知道这个问题是可以解决的。

简而言之,显示器的数字接口(如DisplayPort和HDMI),是可以双向通信的,因此从主机向显示器发送命令也就是可行的。DDC协议也就因此应运而生。像笔记本上的功能键控制屏幕亮度等功能,其实本质上也是通过这个协议完成。知道了协议以后我就去找了找是否有现成的软件。还算顺利的找到了。

Windows下我使用的是一款别人推荐的免费软件ClickMonitorDDC,有图形界面,也有命令行接口。虽然CLI命令和常用的UNIX系命令不太像,不过使用上还算比较简单。以我用的LG显示器为例,要将输入源切换成HDMI,可以用命令ClickMonitorDDC_7_0.exe s LGUltraHD HDMI1。显示器的命令可以事先通过图形界面的控制面板拿到。最后我选择的用法是将切换命令存到我的AHK脚步里,实现一键切换。

Mac下则是搜索到了github上的一个开源项目,ddcctl。需要自行下载源码编译安装。编译安装好了以后,就可以通过如ddcctl -d 1 -i 15的命令,将指定显示器切换成指定的输入源。对比windows平台的软件,有一个小的不足是显示器是通过编号识别的。但是我实测通过tb3连接外接显示器时,每个显示器的编号并不固定,想要准确切换还需要自己写脚步识别每个编号的显示器具体是什么。当然如果每个显示器的连接方式都一样,那就没有这个烦恼了。Mac上的快捷工具我习惯用Alfred,因此就把相关的命令写成Alfred workflow来用就可以了。

一些小坑

  1. Dell的显示器(以我用的U2312为例)只能接收当前输入源的主机输入的命令。举例来说,我的台式机通过HDMI连接显示器,笔记本通过DP连接。当显示器输入源为HDMI时,只有台式机发送的DDC命令才会被执行。反之,当输入源为DP时,只有笔记本发送的命令会被执行。
  2. 几年前买的EIZO显示器,不支持DDC命令,那自然是没有办法控制了,比较遗憾。我觉得以后我会把这一条放在购买的要求里。如果不支持DDC,可能就不会考虑购买了。
  3. Mac我有两个dock,在家里用的是thinkpad的tb3 dock,公司里是belkin的tb3 dock。其中通过thinkpad的dock连接显示,ddcctl会提示发送命令失败,而belkin的则没有问题。本身显示器端的问题我也已经排除了,是支持ddc的。看来应该是dock本身对ddc支持的问题。本来买thinkpad的dock是出于兼顾window和mac的考虑。如此看来,以后还是要针对专项的笔记本选购最适用的dock才行。如果下一台笔记本还是用mac的话,就要考虑把家里的dock换掉了。

前阵子游戏上录了几个视频, 传到b站, 顺便学了点视频编辑和压制的知识. 主要是x264ffmpeg相关的. 不过浅尝辄止, 这部分就先不展开说了.

由于b站最高能支持1080p的视频上传, 而录的游戏视频原始分辨率是720p的. 720p一方面本身分辨率不如1080p, 另一方面对码率的限制更严.(1080p能上传6k码率的视频, 720p最高只能上传3k码率). 根据我压视频的经验, 码率上升带来的画质提升是大于分辨率上升带来的负担的. 因此有条件当然是传1080p的更好.

这就涉及到视频upscale的问题. 几年前用过waifu2x做一些图片的放大, 效果还不错, 一些比较老的低分辨率图片放大到高分辨率后可以拿来做现在显示器或者手机的壁纸. 不过视频的upscale我推测和单纯图片的放大还是不太一样, 因此还是趟了点坑.

拿自己录的原始720p视频作为素材, 第一步是拆帧. 因为waifu2x本身只支持图片放大, 所以需要把视频先逐帧拆成图片. 可以用ffmpeg拆, 也可以用adobe premire. 如果视频需要先编辑的话, 建议就直接用pr编辑完导出图片序列了. 纯拆帧用ffmpeg也很快, 命令格式如下ffmpeg -i <video_file_name> <image_name>_%05d.png. %05d是指定图片序列的名字, 如果视频很长的话, 也可以将位数扩长. 当然图片太多的话, 本身用waifu2x也会有性能问题.

接着就是导进waifu2x里面跑了. 因为之前我下载的是带gui的版本, 直接选择图片所在目录, 指定渲染的参数就好了. 作为参考, 我用的放大2倍, 开3级降噪, 对720p图片做放大, 速度大约是1秒1张. 2分多钟60帧的视频, waifu2x渲染完大约用了1小时50分钟, 显卡是gtx1060 6g版本, 应该说还是挺久的.

最后在pr里导入图片序列再合成视频即可. 因为是图片合成, 声音自然是没有的. 将原先视频的声音合并进来就好了, 这一步不管用pr还是ffmpeg都很快. 最后再导出视频, 这一步又会花不少时间. pr的h264实现据说效果不如x264. 但是无损格式导出实在太大了. 暂时没有配置帧服务器, 所以我是先用pr导出码率较高的版本, 再用x264压制一遍上传的版本. 两步各会花费视频时间2~5倍的压制时间. 因为视频不长, 所以还算可以接受.

渲染完的视频可以参考这个链接.waifu2x渲染测试视频

一些坑

waifu2x渲染本身对于图片质量有一定要求, 如果图片很糊的话, 渲染效果会比较差, 因此就对视频源的质量有比较高的要求. 哪怕是本来实际观感还可以的视频, 如果本身码率不足, 拆完的图片有一些细节丢失的话, 对渲染的画面会有比较大的影响. 也就是说, 对于低分辨率低码率视频->低码率图片->waifu2x渲染->高码率高分辨率图片->高码率高分辨率视频, 最终出来的高分辨率视频, 观感不会很好.

实际测试下来, 比较适用的场景是低分辨率高码率视频->高码率图片->waifu2x渲染->高码率高分辨率图片->高分辨率高码率视频->高分辨率低码率视频. 这种路径是是适用的, 最终压制完上传到视频网站的视频可以保持如6k这种方便网络传输的码率. 但是原始视频需要的可能是高一个数量级的码率(比如这里我的测试视频使用了60k的码率).

另外一个有意思的现象是, waifu2x对图片做降噪的时候, 会有明显的画风变化, 产生类似”油画感”. 这种风格对于动画风格的图片, 不得不说还蛮受用的, 在很多场合下效果都不错.

不过这种变化随着图片分辨率的上升会呈下降趋势, 对于720p的图片, 降噪后”油画感”很明显, 升上到900p, 乃至1080p后, “油画感”逐渐消失. 对同一张图片, 原始分辨率是1440p, 逐步下降下720p, 对比比较明显. 参考下图. 第一张是原图, 第二张是经过waifu2x渲染后的. 因为上传博客原因, 尺寸统一缩到了720p, 不过画风变化还是可以直观看出来.

frame_09196

frame_091962

个人猜想是因为随着分辨率上升, waifu2x在渲染时能补充的细节变少, 因此这种画风变化也逐渐变少. 如果比较想要这种滤镜效果, 且原始图片(视频)分辨率较高, 那么还要做一次downscale. 这方面也有一些不错的算法, 不过我暂时也没空折腾这些, 就不展开讲了.

依旧是上次的python 项目, 周末抽时间把流水线建了, 顺便加了一个badge. python项目的流水线相对简单, 对于编译流水线来说, 其实只要跑一下测试就好了.

这里我用的测试框架是pytest, 以azure流水线为例, yaml内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
trigger:
- master
- dev
- azure

pool:
vmImage: 'ubuntu-latest'
strategy:
matrix:
Python36:
python.version: '3.6'
Python37:
python.version: '3.7'

steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(python.version)'
displayName: 'Use Python $(python.version)'

- script: |
python -m pip install --upgrade pip
cd taskcommander
pip install .
displayName: 'Install dependencies'

- script: |
pip install pytest-azurepipelines
pip install taskcommander/.[test]
pytest --cov=./taskcommander/
displayName: 'pytest'

- script: |
pip install codecov && codecov -t $(CODECOV_TOKEN)
displayName: 'upload code coverage'

azure对于python项目支持多版本测试, 写一套流水线用多个python版本运行, 如上面的yaml中就写了3.6和3.7两个版本, 2.7因为一开始就没想着支持, 3.5因为对于type hint的支持不太一样, 因此这两个版本就去掉了.

步骤是非常简单的三步

  1. pip install安装python包(其实不需要发布的话这步骤也可以去掉, 不过我觉得安装也算是一个测试环节吧)
  2. pip install安装pytest以及测试需要的包, 并运行测试
  3. 将代码覆盖率(code coverage)上传到codecov.

每个步骤都很简单, 就不展开讲了. 唯一需要注意的是上传codecov中用了一个环境变量. 这是因为流水线的配置yaml一般是写到代码库(github)上的, 一般不推荐把诸如用户/密码等敏感信息写到代码库里去, 因此用环境变量的做法更合适一些. 而且azure pipeline支持定义密码类环境变量, 对于此类变量, 在pipeline的输出日志里也会隐藏, 提供更好的保密性.

配置好以后, 每次提交代码到masterdev分支就会自动触发流水线运行, 如果运行报错, 也会有邮件提醒. 另外, 配置了codecov以后, 创建pr时也会自动提醒合并代码会带来的覆盖率变化.

最后是给项目readme加上codecov的badge, 个人感觉算是展示项目一个比较实用的badge. 在readme里加入类似以下内容, 就能正常展示badge了.

1
[![codecov](https://codecov.io/gh/xdsoar/TaskCommander/branch/dev/graph/badge.svg)](https://codecov.io/gh/xdsoar/TaskCommander)

每个项目的badge可以在登录codecov后, 在项目的settings->badge中找到, 不需要自己敲.

GTD, 或者说todo类的产品, 是我一直有频繁使用的一类应用. 不管是哪个平台, 从桌面系统windows\mac到移动端ios\android, 我都有不少尝试. 其中不乏佼佼者, 遗憾的是总是因为跨平台的一些原因, 在这个平台上好用的应用, 换了平台要么不好用了要么就是根本没有.(比如仅限mac系统的omnifocus, 仅限windows系统的mlo).

于是这次筹划干脆自己写一个吧. 初期打算先写一个cli版本的, 有时间了会再研究用诸如electron来写一个跨平台gui版本. 当然移动端依旧是个问题, 这个问题就交给以后去烦恼了.

这个应用的架构会按照这一年里实践了很多次的洋葱架构来写. 关于洋葱架构, 以后有时间会具体来写.

这次主要搞定的是python项目发布的问题. 既然是做成cli应用, 那么能用pip安装自然是最好的.

python的项目结构参考了https://python-packaging.readthedocs.io/en/latest/minimal.html这里的说明. 最小化发布至少需要有一个setup.py文件, 其中包含如发布的名字, 版本号, 依赖等信息. 这里的依赖配置还没研究透, 原来的习惯是写一个requirement.txt文件, 然后在部署的时候pip install, 不过这种做法对于发布成pip包的应用大概不是很友好.

发布的环节与上面网页上描述的略有不同. 因为在使用sdist upload时看到提示建议使用twine来做upload. 因此发布前先pip install twine, 然后使用twine uploa命令发布制品. 发布前需要先去pypi网站上注册账号, 然后才能发布.

我从去年回来玩DNF开始, 就经常录制视频记录自己的游戏经历. 特别是在换了电脑, 录制视频的性能开销基本影响不到游戏体验以后.

途中也更换了好几次录制软件, 因此记录一下工具链更迭的经历.

最初的选择 windows game bar

最开始我选用的是最简单的方案. win10系统自带的游戏录制功能. 这个功能本身还是比较良心的, 特别是在系统更新到1903以后, game bar有了非常丰富的功能. 不过仅针对游戏录制这一点, 不得不说还是有所不足.

首先是录制的码率可选的范围比较有限, 通常需要录完以后再自己用x264之类重新压一遍. 多次压缩对于视频质量也是一点折损. 此外对于音频也没有太多的选项, 反正有声音就录下来了.

另外对于我录DNF来说, 还有一个额外的不足是它只能录制单个游戏窗口. 因此当我使用游戏的外置聊天窗口时, 聊天窗口是无法被录进来的.

这个方案我一直用到今年三四月份. 直到我开始为游戏竞速比赛录制视频时, 才逐渐感觉到工具的不足, 遂开始找其他替换方案.

更强的NVIDIA experience

因为电脑配的显卡是N卡, 因此我很自然的开始用N卡自带的游戏工具来尝试录屏. NVIDIA experience基本延续了windows game bar的体验, 提供的选项非常有限. 不过功能上还是要强一些.

首先他提供了全屏录制的功能, 因此我不用再为聊天窗口无法录制烦恼. 其次是提供了非常丰富的视频码率范围选择, 可以比较容易兼顾画质和文件尺寸.此外还可以为麦克风设置独立的音轨, 可以把游戏声音和语音区分出来.

不过NE的功能虽然更强, 但依然有几个美中不足的点. 其一是虽然麦克风的声音可以被单独分到一个音轨上, 但是语音中听到的声音, 依旧和游戏(或者说整个电脑)的声音混杂在一起. 我有时会把录完的视频上传到视频网站, 但包含语音的话就不太好, 一般来说这时候我希望上传的视频只包含游戏本身的声音. 我在网上搜到过一个方案, 能将语音聊天双方的声音按左右声道放到一个音轨中, 但我尝试后发现还是没办法把声音从游戏声音的那条音轨中去掉.

另外一个点是由于全屏录制的缘故, 所以视频录制的对象是屏幕, 而不是窗口, 当我的游戏画面被别的窗口(比如网页或者聊天窗口)覆盖的时候, 这一情况也会反应在我录制的视频里.

此外因为录制是全屏的关系, 但实际游戏窗口大概只是整个屏幕的3/4, 因此录制完, 如果需要上传到视频网站的话, 我还需要用pr对视频做一定的裁剪, 使得视频的整个编辑流程变的很长.

最终选择 OBS

最后我在上个月开始使用OBS, 也就是我现在使用的方案. OBS是一款有一点历史的软件, 我在很久以前也用过. 也正是因此, 我对当时的OBS录制的视频画质有一点反感.(因为本质上OBS是一款同时面向视频和直播的软件, 相对画质, 会更注重录制时效性和性能).

不过可能是因为与NVIDIA合作的关系, OBS在今年起支持了NVIDIA的编码器NVENC, 使得录制的画质有了很大的提升. 我在实测以后也觉得画质完全没有问题, 甚至相比NVIDIA experience还提供了更低码率的选项, 同时画质也还不错.

此外OBS支持多窗口录制, 因此我终于可以以窗口的形式录制游戏本体和聊天窗口, 并且不用担心游戏被其他窗口覆盖.

多音轨也有了更好的支持, 我可以将麦克风、游戏音效、游戏语音分别设置成不同的音轨.(因为测试NVIDIA experience的关系, 我在使用OBS前已经安装了voice meter banana. 使用虚拟设备将游戏音效和语音分到了两个独立的虚拟设备上).

因为NVENC提供的编码策略已经足够高效了, 因此我已经可以直接将录制好的视频上传到视频网站且不用担心视频被二次压缩. 因此这条工作流甚至还省却了视频编辑的不少事情.

我还是很看好windows game bar未来的发展, 可以将语音聊天, 视频录制, 推流等功能都收纳进来. 不过就目前而言, 对于有复杂录制需求的场景, OBS显然还是最好的选择.

15年前, 我在qq空间写博客, 10年前, 我在新浪博客上写, 5年前我在WordPress上写. 今天, 我又换了一种不同的方式.

前言

距离六月份这个博客正式建立好, 又过去了两个月, 我终于记得把这篇建设之初就应该发上来的博文写好.

我特意起了这么一个有年代感的标题. 说是年代感而不是时代感是因为, 这个标题不是为了蹭上最新的热度而起的. 而是希望在多年以后重新翻捡出来时, 能看到过去岁月的痕迹. 在那些年里, 我认为有价值的东西, 我希望有些东西在过去很多年以后, 也还能保有价值.

理念

在这个博客建设之初, 我已经想好了一些原则, 这些是我从过往的经历中提炼出, 我认为重要的事情. 过去, 我没有太多选择的权利. 但如今, 作为一个互联网上的自由人, 我有充分的资源来实践这些我认为重要的理念.

  • 数据独立保存

    博文是发布在网络上的数据. 但没有一种网络是持久稳定的, 诚然时至今日, 不管是新浪微博还是qq空间都还存在. 但那也只是凑巧我在过去选了两家还算靠谱的博客站点. 不过从长远考虑, 我还是希望自己掌握内容.

    基于同样的理由, 对于数据的格式, 我也希望是开放的格式, 如纯文本、markdown或是html, 而不是word、Evernote或者某个网站数据库里的一个字段.

  • 好用的编辑器和轻量、便捷的发布流程

    各个站点稀奇古怪的富文本编辑器是很令我苦恼的东西, 每每面对都感觉是一场恶战, 更别提图文混排等要求. 我相信有朝一日web端的文本编辑工具可以做的和本地应用一样好(而且可能在数据同步等方面更有优势), 但显然还需要时间.

    而若是使用本地的文本编辑工具, 有一套便捷的发布流程是必要的. 在word中写完整篇博文, 排完版, 再复制粘贴到某个网页端的富文本编辑器中, 再处理一次格式问题, 这显然不是我希望的形式. 而且一旦存在数据两端存储的问题, 保持内容的一致就成了一件很烦人的事情.

实践

基于上述原则, 选定方案并不是很艰难的事情. 包括以下几点

  • 使用markdown格式写博文.

    纯文本太单调, 富文本又太重, 简单的markdown已经够用了. 虽说在几类简化的标记语言中, 相比md我更喜欢emacs的org mode, 但markdown已经够用, 而且更加流行, 眼下没有理由花额外的effort选择org mode, 而即使以后想换, 从简单的markdown切换到org mode, 我想一定比反过来更容易.

  • 单独建立博文仓库

    我的博文存放在blog这个仓库, 这个仓库很干净. 除了一个pipeline的配置文件以外, 其余的全部是内容产物. 而与博客站点相关的基础设施代码则放在hexo-site这个仓库. 后者相对而言不是那么重要, 假以时日如果我不用hexo了, 完全可以用另外一套基础设施来替换.

  • 使用git&原生的markdown编辑器撰写

    用git来管理内容, 完成跨平台的需求. 周末我可以在家里用windows电脑写; 工作日我可以在办公室里在MacBook上写; 出差或出游时, 我可能在平板电脑(Android或是iPad)上写; 甚至碎片时间, 我可以在手机上写. 用git做数据同步在碎片时间的利用并不如诸如Evernote等同步笔记软件方便, 但我更倾向于保留这种选择软件的自由.

    顺便一提, 在windows和mac上, 我一般用typora, 但也不排除会用vs code. 平板电脑和手机上通常用jotpad.

  • 使用pipeline发布博客

    2019年, 要说最大的不同是什么, 就是devops工具链终于可以应用到各方各面. 几年前静态博客流行的时候, 并非没有考虑过用git+jekyll的组合, 但没有devops pipeline的配合, 每次敲命令生成网站再手动发布的过程实在不像在写博客.

    好在这在现在都不是事了. 免费的Travis CI(针对开源仓库)就已经非常好用了. 我因为有私有仓库发布的需求, 所以用的是azure pipeline. 但从流程上来说是类似的. 因为有独立的两个仓库, 所以需要一个发布流水线拉去两个仓库的代码, 将博文放在合适的目录下, 并调用hexo生成站点. 最后再将站点发布到s3. (关于流水线搭建的内容比较多, 这里就不展开讲了, 后续再详细些吧.)

以后

每次尝试重新开始写博客, 感觉上都是一次新的历险, 有时可以走的很远, 有时会半途而废. 我希望这次能走的足够远, 因为这一次我规划了很长时间.

我期望在很远的未来, 还能看到过去留在这里的文字, 在整个互联网成为遗迹, 风干了以后, 这里还会留存可以凭吊当年的化石.

继上篇最后给s3增加了cdn之后, 访问性能其实还算堪堪够用, 剩下倒是些细枝末节的优化. 虽然俗话说过早的优化是万恶之源. 不过身为程序员的代码洁癖还是让我忍不住把这些问题处理掉.

发现问题

如何发现性能问题是第一步. 对于网页访问的响应速度分析, 最简单的粗暴的做法是通过浏览器控制台自带的响应时间捕捉数据. 以chrome开发工具为例

屏幕快照 2019-06-14 上午11.34.40

这里我测试的是加载部署在aws ec2上的hexo博客的情况, 并且使用的是手机4g作为热点. 可以看到加载时间长达27秒. 其中dom文档下载花费了15秒, 而所有资源(主要是图片)则是耗时长达27秒.

另外一个选项, 对于互联网网页, 可以用google的pagespeed insights对网页进行彻底的分析. 使用这个分析的另一个好处是, 在pagespeed insights上获得高评分有利于提高在搜索引擎上获得的权重.(即seo优化)

下图是本博客在优化前的评分情况.

pagespeed

除了提供评分以后, pagespeed insights还提供了很多优化建议. 比如延迟加载屏幕外图片, 在header中增加cache时间, 使用gzip做文本压缩等.

性能优化

优化图片加载

那么优化的第一步就以图片为例, 图片加载的优化路子有很多. 比如这里的图片大小高达800kb,虽然这已经是我在原始照片的基础上做了有损压缩的结果, 但是对于网页而言, 这个尺寸还是大了一点. 比较合适的做法应该是在网页上提供一个低保真的缩放(将图片分辨率降低比如800x600的尺寸, 并降低图片质量), 同时提供如点击后浏览大图的功能. 这也是我认为比较好的一个策略.

实施这一做法也有很多办法. 我搜到一个比较新奇的做法是用aws lambda做实时的图片处理. 这个做法的优势在于可以做任意尺寸的图片缩放. 但是采用实时压缩, 我个人觉得在响应速度上并不存在优势. 另一个做法是在hexo生成博客的时候, 同时生成多份图片资源, 比如生成如xx_tumb.jpg格式的缩略图供内嵌页面使用, 并在点击查看大图时, 使用xx.jpg的链接.

遗憾的是似乎没有现有的hexo插件可以提供这一功能, 暂时性的, 我使用的是hexo-lazyload-image这个插件, 间接避免了加载过多图片的问题. 当然这个插件依旧不能避免当首页有大图时的问题, 这个问题就留给以后优化了.

增加缓存时间

在header中增加CacheControl的做法有很多种. 这里我的做法是在将文件上传到s3时增加相应标签. 使用的部署插件是自己改写的s3部署插件, 然后注意在cloudfront中保留原始的header内容.

完成后通过浏览器访问, 并在控制台查看header中是否有对应内容即可.

增加gzip压缩

gzip压缩主要针对html页面或是js脚本等, 能提供比较客观的流量压缩. 如果是使用nginx或者apache等web服务器的话, 一般都有实时的gzip压缩功能. 不过遗憾的是s3并没有这个功能. 本来想着可以在部署阶段先通过gzip压缩, 然后再发布到s3上. 同时在header中增加encoding=gzip. 这当然也是一个可选的做法, 但是这么做的话, 如果客户端使用不支持gzip格式的浏览器(虽然可能性几乎为0), 那么就无法访问了.

好在s3虽然没有提供压缩功能, 但是cf提供了. 在cf中将压缩选项打开, 就会自动根据资源类型进行压缩.(只压缩html、js脚本等资源)

cf的变更需要一定时间生效. 稍后通过浏览器查看, 同样可以在response header中看到encoding相关内容. 另外, 也可以与压缩前的文件大小做对比, 文本文件通常可以降低2/3的大小.

复核pagespeed insights评分

pagespeed insights的评分有一定滞后性, 可能是google会对网页做缓存的缘故. 下图是上述优化都生效以后的评分情况.

屏幕快照 2019-06-14 上午11.15.11

这时候的评分就理想多了. 虽然还存在一个首次有效绘制时间过晚的问题. 猜测是next这个主题引起的, 要等到页面相关的资源(html、css、js等)全部就位以后才会绘制页面.

这个就暂时不管了, 等有时间研究next主题了, 再来考虑这个优化.

博客搭完之后, 博文还没写几篇, 站点托管的事情倒是折腾了很久. 在这通折腾之前 ,这个博客是架在aws ec2上, 用hexo自带的http server启动. 看起来用一个vps来部署博客看起来已经足够了, 为什么要这么折腾呢. 简单分析一下, 用静态托管的方式来做网站还是有不少好处的:

  1. 运维的时间成本, 需要管理的是纯粹的站点文件, 不需要关心服务器是什么
  2. 运维的金钱成本, s3托管按照文件大小和流量计费, 要比vps整机便宜
  3. s3结合cdn使用, 在访问速度上必然要好过固定机房的

当然一台vps除了做站点以外还可以提供其他应用, 这一点是静态托管做不到的. 这里就不详细展开了.

除了aws以外, 阿里云也提供对象存储(oos)做静态站点托管. 不过限于国内政策的原因, 需要网站备案才可以使用. 而由于我用的这个me域名在国内没有备案, 所以暂时没有选择阿里云来做站点托管, 不过从实现上来讲都是类似的.

下面是做静态站点托管的主要步骤:

注册域名

托管在s3上的文件, 默认会使用s3的域名(与区域有关), 在实际使用中, 一般会使用自己注册的域名. 原来我这个域名是托管在godaddy上, 为了配合aws的相关功能使用, 也顺势转到了aws 的router 53上, 在实际配置中还是有一定便利性. 另外如果是部署在阿里云上, 自定义域名是必须的, 阿里云oos默认的域名在访问时, 会被作为文件下载, 而不是网站.

域名注册的流程避开不谈, 不过强烈建议使用云服务商自带的域名托管.(比如aws就用router 53, 阿里云就用阿里云的域名托管). 在实际使用中肯定会方便不少.

创建s3 bucket托管站点

整体步骤都可以参考aws官方提供的指南

  1. 首先创建s3 bucket. 可以先评估一下各个机房的访问速度再做决定.(如果后续还使用cdn的就没有必要的). 注意这里有个小坑, s3 bucket的名字必须取成和站点域名一样的名字, 否则在router 53注册别名时会检索不到

  2. 创建完成之后先在bucket设置中将权限->访问控制列表Everyone组加上读取权限. 并在属性中配置静态网站托管.选择使用此存储桶托管网站, 并设置索引文档(如index.html).

  3. 由于s3上传文件在默认情况下是私有的, 因此还需要在权限->存储桶策略中配置访问权限

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Sid": "AddPerm",
    "Effect": "Allow",
    "Principal": "*",
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::example.com/*"
    }
    ]
    }
  4. 设置完成之后就可以上传站点文件了, 直接在网页上操作就好了. 后续自动部署的事情以后再做介绍.

  5. 此时应该已经可以使用s3自带的域名进行访问了. 地址格式为:<bucket-name>.s3-website-<AWS-region>.amazonaws.com. 要注意的一点是, s3自带的域名可能会被墙, 需要科学上网来验证是地址被墙了, 还是配置没生效.

  6. 到此, 部署到s3上的步骤结束, 如果测试没有问题, 那么接下来可以配置自定义的域名解析

配置域名解析

以router 53为例, 在管理界面的托管区域中选择自己要使用的域名, 选择创建记录集,名称中设置自己想要使用的子域名(如果想直接使用顶级域名, 那么名称可以不写), 类型选择A - IPv4地址, 勾选别名, 选择刚才创建的s3bucket即可.

注意这里创建的域名名字必须和s3 bucket名字一样, 否则在别名中是无法检索到的. 如果使用的而不是router 53而是其他服务, 那么我理解这里应该选择创建的是一个CNAME, 然后配置s3的域名即可.

DNS生效可能需要一点时间, 可以通过ping 域名的方式检查dns解析是否生效. 生效以后, 就可以通过自己的站点域名访问s3的资源了.

(可选) 配置CDN加速

在配置完S3以后, 我简单测试了访问的速度, 感觉上比预想的要差. 于是想到了结合cdn做加速. 以下依旧以aws的cloud front为例, 阿里云的cdn加速也可以实现类似的功能(而且可能在国内访问会更快).

  1. 如果网站想支持https访问, 那么需要在cloud front中配置ssl证书. aws也有提供ssl证书服务. 在证书管理中申请就好. 选择dns验证, 如果使用的是router 53, 那么界面上点击即可配置验证dns的cname. 如果是其他dns服务商, 那么需要自行配置cname. 配置完成后, 同样是等待一段时间等dns生效验证即可. 此时域名对应的证书就已经生成好了.(建议申请泛域名证书, 即*.example.com这种格式的域名证书, 否则申请的单个证书只针对单域名有效, 子域名还需要重新申请.

  2. 在aws控制台登录cloud front控制页面, 在分配菜单中选择创建分配, 然后选择Web. **注意这里不要直接选列出的s3存储桶.**这里的一个小问题是, hexo生成的所有页面, 都遵从default root=index.html这一规则. 在s3的静态站点托管中, 我们已经配置了这一规则. 但是cloudfront中的default root object只适用于根目录, 而对于子目录(比如每天博文的独立页面)是不生效的. 手动输入s3的endpoint地址可以避免这个问题.(可以看到这个地址和自动列出的s3 bucket地址存在一些不同)

  3. 分配设置备用域名中, 填入自定义的域名, 并选择自定义SSL证书, 可以直接检索到创建的证书, 直接点选即可. 其他选项可以都使用默认配置, 或者自己按需修改.

  4. 配置完成后, 需要等待一段时间等cdn分发完成.

  5. 此时会获得一个cdn的地址, 如xxxxxxx.cloudfront.net这样的格式. 需要在域名解析中用这个地址, 替换原来s3的别名.

  6. 等cdn生效以后, 就可以再次访问, 可以看看速度有没有提升.

如何验证结果是从cdn发来的呢?

打开浏览器的控制台窗口, 在网络标签下, 在response headers中查看X-cache一项, 从could front发来的数据带有Hit from cloudfont的标签, 如果缓存没有命中, 数据从s3获取, 则会有类似miss from cloudfront的标记.

后续优化

上面这些都做完以后, 访问速度还是不理想怎么办呢, 其实还是有不少内容可以优化的, 最近也在逐项实践, 等有时间会再发上来.