作用基本上就跟top差不多,不过视觉效果还不错。对比一下,
top命令:
bashtop命令:
其中,最上面是CPU资源使用情况,左侧是内存和网络资源使用情况,右边是和top一样的进程状态,可以使用方向键来选择进程,或者翻页键来翻页,按f可以filter需要找的进程。
在选中进程之后,可以使用回车键查看该进程详细信息,t和k健可以terminate或者kill进程,i可以中断进程(interrupt)。
按q退出,按ESC可以跳回主界面,如果不清楚命令或者快捷键,可以选择HELP查看,总之使用方法比top简单不少。
缺点,就是感觉没有top出来的快,可能是因为要在terminal上绘图。
xxd是linux上比较老的文件格式转换工具了,经常使用vim的在打开二进制文件的时候输入的!xxd
就是这个命令,一般作用是将文件转为十六进制。
最简单的方式是xxd file
,会在终端输出转换后该文件的十六进制内容,使用重定向或者管道可以写到文件或作用于其他输入。
如果文件比较大,可能转换比较慢,而且很多并不是我们需要的话,可以使用-s
或者-l
参数。-s
参数如上所示,“start at -l
参数则是指定要转换内容的长度。
如果要比较两个二进制文件内容是否一致,可以借助于xxd
命令。
例如,需要比较两个原始的yuv格式视频文件是否一致,比如两种方式通过h264解码出来的文件,平常的diff
命令是无法完成的,可以先利用xxd命令转成十六进制后再进行比较。
之所以举这个例子,是因为yuv是原始视频数据,比如一个1920x1080的h264编码后的视频大小只有8M多,但是其对应的解码后的yuv大小则可能有1.1G多,而使用xxd转成十六进制文件之后,体积可以达到4.5G,直接用diff
命令比较费时,而且用vim打开还容易卡死。而通常只需要比较其中一部分可以判断出解码的文件是否正常,这时候就可以在xxd
命令转换的时候加上-l
或者-s
参数,指定比较的文件位置和长度,可以很快的得到结果。
当然,比较方式也不是绝对的,对于视频文件,使用ffmpeg
也可以查看许多详细信息。
这个其实很早以前打算说的,不过忘记了。作用有点像chrome里的SwitchyOmega之类的扩展程序,不同的是,SwitchyOmega是针对特定网页选择本地的代理端口和协议,proxychains是针对某个命令或者应用选择本地代理端口和协议。
安装方法比较简单,ubuntu下直接使用apt命令可以安装:
安装好之后,配置文件放在/etc
目录下的proxychains.conf
文件,修改里面的协议和端口即可。
使用方式是直接在命令前加上proxychains
,这样只对该命令代理,不影响其他应用。
比如使用ubuntu的snap
命令下载软件时,通常国内会比较慢,如果有代理,则可以使用:
其实很多工具也不是啥新鲜玩意,不过有时候确实挺实用的,不过仅仅对工具而言,没有任何性质,怎么用在于个人。
]]>图像传感器主要有两种,线阵和面阵,平时看到的线阵相机和面阵相机就是因为用的Sensor类型不同。
线阵Sensor以CCD为主,一行的数据可以到几K甚至几十K,但是高度叧有几个像素,行频很高,可以到每秒几万行,适合做非常高精度、宽画幅的扫描。
面阵Sensor,包含CCD和CMOS,单行数据宽度远小于线阵,优点是成像快,一次可以成像很多行,即单次扫描的像素高度比线阵高很多。另外开发简单,成像快,不用进行每行数据的拼接。而且价格便宜不少,这个大概是应用更广泛的决定性条件。
CMOS传感器的大致结构如图,由于其感光方式不同,因此有两种类型快门。
滚动快门在感光时是逐行进行的,从第一行开始,一边曝光一边输出图像,一直滚动到最后一行,模数转换器则是逐行共用。这样,实际上每一行曝光的时间点会不一样,时间上存在一个位移,这样导致每行图像会有一些位移偏差,特别是当对象是高速运动物体时更明显,会导致图像的扭曲变形。这种变形和平时拍摄运动物体的拖影不一样,拖影是由于拍摄物体运动速度太快,而且曝光时间设置太长造成,会带有一些图像模糊。滚动快门的变形是每行时间上的不同步拍摄而造成的变形,图像清晰度不会受到影响。
为了改善这种变形,则可以在每个像素处增加采样保持单元或者模数转换器。增加采样保持单元可以短暂保存得到的模拟数据,再等待模数转换器进行逐行转换,转换期间可以继续进行拍摄。而这种方案会浪费比较多的CMOS面积来摆放这些用来短暂保存的像素,使得填充系数降低,而且采样保持单元还引入了新的噪声源。因此在每个像素处增加模数转换器则是一种新的方案(如索尼做的),每个像素采集到了就可以直接转换,不用等待,实现真正意义上的全局快门。
CCD(电荷耦合器件)的结构大致如图,其结构决定了CCD具有“免费全局快门”的优点,所有像素在同一时刻曝光,所有像素同时移入传输寄存器,曝光完成后,每个像素被串行传输到单个模数转换器中,因此其传输帧率受限于单个像素数字化速率和传感器中的像素数量。
光学尺寸是指其感光区域的大小,通常高分辨率的面阵相机或者线阵相机的相机芯片尺寸要大于低分辨率的,其尺寸没有特定标准,是有其分辨率和像素大小决定的。从理论上讲,可以有无数种类型,只要价格到位。尺寸通常用感光元件的对角线来表示,单位是英寸,不过由于历史原因,这里的1英寸是16mm,不是25.4mm。常见的有1/4”、1/3”、1/2.5”、1/2.3”、1/2”、2/3”、1”、1.1”等。
介绍光学尺寸的原因是,通常为相机选择镜头的时候要考虑到,镜头靶面尺寸要配合其光学尺寸。
靶面尺寸,或者靶面直径,单位也是英寸,理想情况下,1/2”的镜头应该安装在1/2”的光学芯片上,这样可以尽可能的利用靶面,但是如果安装在2/3”的芯片上,由于感光区域大于感光范围,那么感光区域中无法感光的部分则会在最终的图像中出现暗角或者晕影。不过如果采用2/3”的 镜头匹配1/2”的芯片,则可以完全利用光学尺寸,实际上使用大的镜头可以形成更大的靶面,图像从中心到边缘的锐化可以保持一致,但这种情况下,很大一部分罢免无法使用,造成浪费。图像的大小是有光学尺寸决定的,而镜头越大,则价格越贵,如果想节约点,对于比较小的光学尺寸,还是选择较小的镜头。
镜头接口是连接镜头和相机的接口,有螺纹接口和卡口两类。比较常见的C口、CS口等都是螺纹接口。
最常见的C口和CS口的工业相机,接口实际上比较相似,也有其转换环,因为它们的接口直径、螺纹间距都一样,只是法兰距不同。C接口的法兰距是17.526mm,CS接口的法兰距为12.5mm。因此所谓转接环,就是一个5mm左右的垫圈了。
此外,螺纹接口还有M12、M45、M58等,具体规格在需要的时候查询即可,这里不在赘述。
至于卡口相机,平常见到的单反基本上都是卡口,如尼康的F口或者佳能的EF口,这俩外观上也不容易区分,不过F口的法兰距比EF口的要长。
分辨率,泛指量测或显示系统对细节的分辨能力。相机制造商一般直接用像素数目表示分辨率,实际上这是分辨率上限。
因为这种情况,是当镜头能够解析像素大小时候才成立。只有使用高分辨率镜头,才能最终得到高分辨率图像。
镜头的分辨率通常通过每毫米线对数衡量,表示每毫米中可以相互分离的行的数量。每毫米线对数越多,分辨率越高,镜头质量越好。镜头分辨率确定了可以解析的像素大小,方便起见,一般情况下直接指定镜头可以解析的百万像素数,当镜头分辨率可以完全解析感光元件的所有像素点时,则可以获得最高分辨率。
表示镜头分辨率性能的指标有MTF曲线(调制传递函数),描述了镜头从图像中心到边缘的分辨率性能,通常可以找制造商要到这些曲线。
焦距是镜头光学中心和焦点之间的距离,通常长焦镜头适合拍远景,但视场小;短焦适合拍广角,常用的鱼眼或者微距镜头就是。
光圈的参数通常用F Number来表示,是焦距与光圈直径的比值,表示光圈全开时的宽度。
光圈的选择直接影响的是进光量,最终影响的是图像质量和亮度。F值越高,则光圈越小,最终感光元件获得的进光量越少,反之亦然。通常可以根据光源亮度调整。
减小光圈,可以减少相机光晕效果,景深越大,不过光圈太小,容易产生衍射模糊。
带帧存功能的相机,是指该相机内部具有保存一帧完整图像的能力,当传输带宽不够或者不稳定时,由于缓存了整个图像帧,所以仍然可以断点续传之后重建图像。
带缓存功能的相机,是指该相机内部具有缓存一部分图像数据的能力,但是无法缓存一整个帧,当传输带宽不够或者不稳定时候,有可能造成缓存溢出,最后无法重建图像从而造成丢帧等问题。
平常见到的工业相机一般都是带缓存的,不一定有帧存,在结构和价格上也有区别。只带缓存的相机结构简单,价格便宜。
此外,还有一些简单的参数,如相机图像的帧率FPS,图像的亮度、饱和度、对比度等等,由于比较常见,顾名思义,就不继续赘述。
先偷个懒,改天想到了啥,再续狗尾。
一维数轴上的复数对应于一个二维实数空间,比如一个二维空间坐标为 $(x,y)$ 的复数表示为 $x + yi$ 。
给定两个复数 $z1 = a + bi$, $z2 = c +di$, 其乘积可以表示为:
$$ z1z2 = (a+bi)(c+di) = (ac-bd)+(ad+bc)i $$
对于向量$z2$,与$z1$的乘积可以表示为矩阵形式,即:
$$
\begin{bmatrix}
a & -b \\
b & a \\
\end{bmatrix}
\cdot z2
$$
如果将$z2$也看做一个变换表示成矩阵形式,则
$$
z1z2 =
\begin{bmatrix}
a & -b \\
b & a \\
\end{bmatrix}
\cdot
\begin{bmatrix}
c & -d \\
d & a \\
\end{bmatrix}
$$
此时满足交换律。
设向量模长为1,即$\sqrt[2]{a^2 + b ^2} =1$,则$a = cos\theta, b=sin\theta$, 则对一个向量$\vec{v}=x+yi$,其乘积为$\vec{v} \dot z = (xcos\theta -ysin\theta) +(xsin\theta +ycos\theta)i$,
设向量$\vec{v}$的模为r,则$\vec{v}\cdot \vec{z} = r(cos\theta_1cos\theta_2 -sin\theta_1sin\theta_2)+r(cos\theta_1sin\theta_1 + sin\theta_1cos\theta_2)i = rcos(\theta_1 +\theta_2) +rsin(\theta_1 + \theta_2) i $
可以看出,一个单位的二维向量,或一维复数,可以表示成一个旋转变换,即逆时针旋转$\theta$角。
所以二维的旋转矩阵可以很直观的求得:
$$
\begin{bmatrix}
cos\theta & -sin\theta \\
sin\theta & cos\theta \\
\end{bmatrix}
$$
其实将复数表示成极坐标形式,欧拉公式将三角函数和复平面关联起来,于是可以很直接的将$ e^{i\theta} = cos\theta + isin\theta$带入,角度旋转即$$ e^{i\theta_1} * e^{i\theta_2} = e^{i(\theta_1 + \theta_2)}$$
先看向量的旋转: 将向量$\vec{v}$绕旋转轴$\vec{u}$旋转$\theta$角。
将$\vec{v}$分解成两个正交向量的和,分别是平行于$\vec{u}$和垂直于$\vec{u}$的向量,记为$\vec{v_{||}}$和$\vec{v_\bot}$,平行的向量旋转不变,因此只需要考虑垂直向量即可。
对于$\vec{v_{||}}$,其实就是在$\vec{u}$上的正交投影,因此有$\vec{v_{||}} = \frac{\vec{u}\cdot \vec{v}}{||\vec{u}||^2} \vec{u}$,设$\vec{u}$为单位向量,则可以表示为$\vec{v_{||}} = (\vec{u}\cdot \vec{v})\vec{u}$ 。
所以,$$\vec{v_\bot} = \vec{v} - \vec{v_{||}} = \vec{v} - (\vec{u}\cdot \vec{v})\vec{u}$$
因为$\vec{v_\bot}$和$\vec{u}$垂直,所以旋转可以转化成二维平面的旋转,构造一个向量$\vec{w} = \vec{u} \times \vec{v}$,如图所示,
所以旋转后的向量为$$\begin{aligned} \vec{v_\bot}^\prime & = \vec{v_\bot}cos\theta + \vec{w}sin\theta \\ & = \vec{v_\bot}cos\theta + \vec{u} \times \vec{v_\bot} sin\theta \end{aligned}$$
最后旋转后的向量为 $$ \begin{aligned} \vec{v}^\prime & = \vec{v_{||}} + \vec{v_\bot}^\prime \\ & = \vec{v_{||}} + \vec{v_\bot}cos\theta + \vec{u} \times \vec{v_\bot} sin\theta \\ & = (\vec{u}\cdot \vec{v})\vec{u} + \vec{v_\bot}cos\theta + \vec{u} \times \vec{v_\bot} sin\theta \\ & = (\vec{u}\cdot \vec{v})\vec{u} + (\vec{v} - (\vec{u}\cdot \vec{v})\vec{u} )cos\theta + \vec{u} \times (\vec{v} - (\vec{u}\cdot \vec{v})\vec{u}) sin\theta \\ & = \vec{v}cos\theta +(1-cos\theta)(\vec{u}\cdot \vec{v})\vec{u} +(\vec{u}\times \vec{v}))sin\theta \end{aligned}$$
四元数可以看做一个四元向量,或是有三个虚部的复数,如$q = a + bi+cj+dk$,也可以写成矩阵形式,
$$\vec{q} =
\begin{bmatrix}
a \\
b \\
c \\
d \\
\end{bmatrix}
$$
如三维坐标轴的顺序,复数相乘有,
$$ij =k \\
jk =i \\
ki =j$$
令$q1 = a + bi+cj+dk$, $q2 = e + fi+gj+hk$
则左乘$q1$可以为
$$
\begin{aligned}
q1q2 & = ae + a f i + agj + ahk + \\
& bei − b f + bgk − bhj + \\
& cej − c f k − cg + chi + \\
& dek + d f j − dgi − dh \\
& = ( ae − b f − cg − dh )+ \\
& ( be + a f − dg + ch ) i \\
& ( ce + d f + ag − bh ) j \\
& ( de − c f + bg + ah ) k
\end{aligned}
$$
矩阵形式可以写成,
$$q1q2 =
\begin{bmatrix}
a & -b & -c & -d \\
b & a & -d & c \\
c & d & a & -b \\
d & -c & b & a
\end{bmatrix}
\begin{bmatrix}
e \\
f \\
g \\
h \\
\end{bmatrix}
$$
四元数向量不满足交换律,右乘会有一些区别。
将四元数的虚部表示成一个向量,即$ q1 = [a,\vec{v}]$,$q2 = [e, \vec{u}]$,其中,$\vec{v} = bi+cj+dk$,$\vec{u} = fi+gj+hk$。
则左乘$q1$可以化简成
$$q1q2 = [ ae − \vec{v} \cdot \vec{u} , a\vec{u} + e\vec{v} + \vec{v} \times \vec{u} ]$$
这个结果也被称为 Graßmann 积。
这样,当a和e为零时,两者乘积可以写成,
$$ q1q2 = [− \vec{v} \cdot \vec{u}, \vec{v} \times \vec{u} ]$$
同纯虚数的说法,这时q1和q2叫纯四元数。
与二元虚数类似,四元数的共轭也是将虚部方向取反,即 $q^* = a - bi - cj - dk$,则
$$
\begin{aligned}
qq^* & = [s,\vec{v}] \cdot [s,-\vec{v}] \\
& = [s^2 - \vec{v} \cdot (-\vec{v}), s(-\vec{v}) + s\vec{v} + \vec{v}\times(-\vec{v})] \\
& = [s^2 + \vec{v} \cdot \vec{v}, \vec{0}] \\
\end{aligned}
$$
实部平方与虚部平方和,即该向量的模的平方,最后虚部为零,所以
$$
\begin{aligned}
qq^* & = [s^2 + \vec{v} \cdot \vec{v}, \vec{0}] \\
& = s^2 + |\vec{v}|^2 \\
& = a^2 + b^2 + c^2 +d^2 \\
\end{aligned}
$$
由于q与其共轭的积最后是个标量,为其模长,所以该乘法是满足交换律的。即$qq^* = q^*q = |q|^2$。
这样,
$$
q^*q = |q|^2 \\
\frac{q^*}{|q|^2}q =1
$$
则可以发现$q^{-1} = \frac{q^*}{|q|^2}$ 满足$q^{-1}q = qq^{-1} =1$,即为该四元数的逆。
而单位四元数的逆即为其共轭四元数。
旋转轴$\vec{u}$不妨设为单位向量,与之前的旋转类似,
$$ \vec{v’} = \vec{v’_{||}} +\vec{v’_\bot} = \vec{v_{||}} +\vec{v’_\bot}
$$
之前计算过正交与旋转轴的向量旋转得到的结果,$$\vec{v’_\bot} = \vec{v_\bot}cos\theta + (\vec{u}\times \vec{v_\bot})sin\theta$$
设u,v都是纯四元数,即$u = [0,\vec{u}]$,$v = [0,\vec{v}]$,两个纯四元数的Graßmann积为$$uv_\bot = [− \vec{v_\bot} \cdot \vec{u}, \vec{v_\bot} \times \vec{u} ] = [ 0, \vec{v_\bot} \times \vec{u} ] = \vec{v_\bot} \times \vec{u} $$
也是一个纯四元数。
所以,
$$
\begin{aligned}
v’_\bot & = v_\bot cos\theta + (u v_\bot)sin\theta \\
& = (cos\theta + usin\theta)v_\bot
\end{aligned}
$$
令四元数$q = (cos\theta + usin\theta)v_\bot$,则$ v’_\bot = qv_\bot$
所以对于垂直于旋转轴的向量,旋转$\theta$角度之后的向量可以用四元数的乘法来获得, 用向量表示为$q = [cos\theta, \vec{u}sin\theta]$
由于$\vec{u}$是单位向量,所以$$||q|| = cos^2\theta + ||\vec{u}||^2 sin^2\theta =1 $$
同样的表示方式,$qqv_\bot = q(qv_\bot)$几何上表示旋转两次,因此有$qqv_\bot = (cos2\theta + usin2\theta)v_\bot $
所以最后旋转之后的四元数,
$$\begin{aligned}
v’ & = v’_{||} + v’_\bot \\
& = v_{||} + qv_\bot \\
& = pp^{-1}v_{||} + ppv_\bot \\
& = pp^*v_{||} + ppv_\bot
\end{aligned}
$$
其中,$p = [cos(\frac{\theta}{2}),\vec{u}sin(\frac{\theta}{2})]$,是旋转半角的单位向量,因此$q=p^2$。
交换性质:
由之前的Graßmann积,上式中,将q写成向量形式,$q = [\alpha, \beta\vec{u}]$
$$ \begin{aligned}
qv_{||} & = [\alpha, \beta \vec{u}] \cdot [0,\vec{v}_{||}] \\
& = [-\beta \vec{u} \cdot \vec{v}_{||} , \alpha \vec{v}_{||} + \beta \vec{u} \times \vec{v}_{||}] \\
& = [-\beta \vec{u} \cdot \vec{v}_{||} , \alpha \vec{v}_{||}] \\
\end{aligned}
$$
右乘,
$$ \begin{aligned}
v_{||}q & = [0,\vec{v}_{||}] \cdot [\alpha, \beta \vec{u}] \cdot \\
& = [-\beta \vec{u} \cdot \vec{v}_{||} , \alpha \vec{v} +\vec{u} \times \vec{v}_{||}] \\
& = [-\beta \vec{u} \cdot \vec{v}_{||} , \alpha \vec{v} ] \\
& = qv_{||}
\end{aligned}
$$
再看垂直部分,
$$ \begin{aligned}
qv_\bot & = [\alpha, \beta \vec{u}] \cdot [0,\vec{v}_{}] \\
& = [-\beta \vec{u} \cdot \vec{v}_\bot , \alpha \vec{v}_\bot + \beta \vec{u} \times \vec{v}_\bot] \\
& = [0, \alpha \vec{v}_\bot + \beta \vec{u} \times \vec{v}_\bot] \\
\end{aligned}
$$
右乘共轭,
$$ \begin{aligned}
v_\bot q^* & = [0,\vec{v}_\bot] \cdot [\alpha, -\beta \vec{u}] \cdot \\
& = [-\beta \vec{u} \cdot \vec{v}_\bot , \alpha \vec{v}_\bot + \beta \vec{u} \times \vec{v}_\bot] \\
& = [0, \alpha \vec{v}_\bot + \beta \vec{u} \times \vec{v}_\bot] \\
& = qv_\bot
\end{aligned}
$$
所以旋转之后的结果可以化简为
$$\begin{aligned}
v’ & = pp^*v_{||} + ppv_\bot \\
& = pv_{||}p^* + pv_\bot p^* \\
& = p(v_{||} + v_\bot) p^* \\
& = pvp^*
\end{aligned}
$$
实际上,从计算过程可以看出,对于平行分量,乘$pp^*$,实际上是没有变化,对于垂直分量,乘$pp$,旋转了$\frac{\theta}{2} + \frac{\theta}{2} = \theta$角度。因此可以用旋转半角的四元数乘法来表示绕单位向量$\vec{u}$的旋转。
单位向量$p= [cos(\frac{\theta}{2}), \vec{u}sin(\frac{\theta}{2})]$,以通用四元数方式表示为$p = a + bi + cj + dk$
其中$a=cos(\frac{\theta}{2}), b=u_x sin(\frac{\theta}{2}),c=u_y sin(\frac{\theta}{2}), b=u_z sin(\frac{\theta}{2})$
写成矩阵形式,之前说了四元数的矩阵形式左乘和右乘有点区别,左乘矩阵为
$$L=
\begin{bmatrix}
a & -b & -c & -d \\
b & a & -d & c \\
c & d & a & -b \\
d & -c & b & a
\end{bmatrix}
$$
右乘的矩阵等同于左乘矩阵
$$R = \begin{bmatrix}
a & -b & -c & -d \\
b & a & -d & -c \\
c & -d & a & b \\
d & c & -b & a
\end{bmatrix}
$$
所以有,
$$
\begin{aligned}
qvq^* & = L(q)R(q^*)v \\
& =
\begin{bmatrix}
a & -b & -c & -d \\
b & a & -d & c \\
c & d & a & -b \\
d & -c & b & a \\
\end{bmatrix}
\begin{bmatrix}
a & b & c & d \\
-b & a & -d & c \\
-c & d & a & -b \\
-d & -c & b & a \\
\end{bmatrix}
v \\
& =
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1-2c^2-2d^2 & 2bc-2ad & 2ac+2bd \\
0 & 2bc+2ad & 1-2b^2-2d^2 & 2cd -2ab \\
0 & 2bd-2ac & 2ab+2cd & 1-2b^2-2c^2 \\
\end{bmatrix}
v \\
\end{aligned}
$$
矩阵最外圈不会有任何影响,所以可以得出向量$\vec{v}$绕单位向量旋转轴$\vec{u}$旋转的三维矩阵变换,即
$$\vec{v’} =
\begin{bmatrix}
1-2c^2-2d^2 & 2bc-2ad & 2ac+2bd \\
2bc+2ad & 1-2b^2-2d^2 & 2cd -2ab \\
2bd-2ac & 2ab+2cd & 1-2b^2-2c^2 \\
\end{bmatrix}
\vec{v}
$$
其中$a=cos(\frac{\theta}{2}), b=u_x sin(\frac{\theta}{2}),c=u_y sin(\frac{\theta}{2}), b=u_z sin(\frac{\theta}{2})$。
]]>XClip工具可以非常方便的操作,清空剪贴板,高效的复制粘贴等,直接从apt
商店就可以直接安装(测试所用Ubuntu环境)。
选中区是指用鼠标选中区域的内容,在Ubuntu下有个快捷的复制粘贴方式就是利用选中区,通常鼠标中键是粘贴选中区内容。用xclip输出选中区内容命令:
|
|
如果要读取系统剪贴板的内容,可以加个参数:
|
|
或者简写为:
|
|
输出到文件
|
|
平时用鼠标和Ctrl C\V
或者Ctrl Shift C\V
即可完成一般的复制粘贴,但有大量文字时,满满的移动鼠标就显得比较繁琐,利用xlip
可以不借助鼠标比较方便的实现。
|
|
其他几种形式:
|
|
可以将包含公式的图像直接转化为LaTeX语法。
|
|
可以使用键盘快捷键 Ctrl+Alt+M 开始使用 Mathpix 截图,它会立即将图中公式转换为 LaTeX 语法并保存在剪贴板中。
然而现在开始收费了,免费版的一个月只能用50次。
与传统的浏览器广告拦截插件不同,声称是通过个人Linux硬件进行全网广告拦截,通过DNS污染实现,具体仓库在此。
安装方式很多,最直接就是curl直接拉脚本运行:
|
|
安装完成后可以配置路由器来使所有的经过动态主机分配协议(DHCP)的主机使用Pi-hole作为其DNS服务器,来确保该网络所有的主机都可以进行广告拦截。配制方法在此。
如果路由器不支持DNS设置,可以禁用DHCP后,使用Pi-hole的内置DHCP服务器。
]]>目前已经有比较优秀的DzzOffice了,而且开源,仓库地址在此,可以在此处查看演示。
可以自己搭建一个,另外该仓库也提供了Docker部署版本。克隆仓库之后直接使用docker-compose up -d
即可部署。
|
|
不过目前编译,会出现一些问题:
|
|
原因主要在两个方面: 一是本机Docker的DNS设置:
|
|
将DNS修改正确;
另一个问题是alpine镜像的DNS问题,测试一下:
显示bad address
.
在php的Dockerfile中加一行,然后重启服务
|
|
|
|
同样修改Dockerfile,安装完compose
之后,添加一行:
|
|
|
|
可能是镜像更新了,里面用的ubuntu amd64环境,是apt-get
安装,修改pma的Dockerfile,指定一个稍老的版本即可。
至此,服务搭建成功。
数据库用户名和密码在部署环境之前,可以在docker-compose.yml
中配置,然后在浏览器中打开localhost
开始进行配置。
登录之后,需要进行配置,添加应用,比如office,如添加onlyoffice,可以先装一个onlyoffice的服务:
|
|
然后在应用库中添加onlyoffice,设置api地址,
|
|
然后就可以编辑文档了。
类似如Visio的工具,目前体验比较好的有DrawIO,也是开源的,可以直接部署到自己的服务器上,在浏览器中绘图,快速轻便,易于分享。
|
|
在浏览器中打开指定端口地址即可开始绘制。
就推荐一些小工具吧,这段时间发现的,感觉挺有意思的。以后也不知道会不会继续,先假设是个连续剧吧,写完拖更的那种。
一个类似单词卡的小工具,也可以用来放代码,示例仓库FlashCards
方法很简单,直接用Docker启动,可以放在自己的电脑上,也可以放在自己的服务器上,挂一个端口,然后可以Web端远程访问。另外,单词数据可以直接上传Github仓库,环境不需要。
这样随时随地,就是一个私人的Note?
搭好之后访问大概是这样:
主要功能是将Linux下的ls
命令输出结果美化一下,不同的文件类型会有不同的图标,不过目前颜色还不支持修改。大概效果如下:
就是将树形结构描述转化为DOT描述。 DOT语言是一种文本图形描述语言,可用于画有向无向图、流程图,语法比较简单,网上一搜就有,这里不做介绍。
比如将当前目录下的文件转成关系图,使用tree2dotx工具,命令为:
Graphviz(Graph Visualization Software)是一个由AT&T实验室启动的开源工具包,用于绘制DOT语言脚本描述的图形,官网在这,可以从DOT文件生成图像,常见的有png/gif/svg等。
如果将之前的树形目录保存为关系图,只需要继续将上面的命令重定向即可。
|
|
保存为list.png
文件,大致就是这个样子:
一个代码性能分析工具,结合Gdb可以很方便的分析所写的代码。
主要方式是在使用gdb编译时加上-pg
参数,然后正常运行程序,最后会出现一个gmon.out
的文件,里面就是各个函数的信息。
结合Graphviz,可以得到函数关系调用图。
其中gprof2dot
工具可以通过pip安装。
最后结果如下:
里面有各个函数调用次数、运行时间等情况,保存为svg也可以在浏览器中看。
EMMMM, To be continued…
]]>然后又像以前一样,对比了很多现有的图床,依然没找到能安心存放的地方。于是想到手里还有VPS,便打算搭建一个了。
最开始是一个同学送的天翼云,估计是办宽带送的,结果发现80和443端口全被封了,问客服说要备案才行,想着备案就备案吧,然而当看到备案要填的资料时,立马放弃了。
另一个就是Vultr上的,国外的云主机商相比于国内,条件限制宽了不少,不用动不动就实名或者备案,虽不会发表什么不当言论,但吃相看着令人难受。
现有图床挺多的,目前打算用一个开源的荔枝图床,有现成的Docker镜像,界面比较美观,官网在这。
其实云主机还好,如果觉得官方的Docker下载太慢,也有Daocloud的CDN加速的镜像,直接一条命令就可以完成:
等安装完成之后,就可以拉取镜像了,
或者直接试运行,看看效果:
这时,在本地浏览器输入云主机的IP,就可以看到一个基本的界面了。
完整的命令是:
分别挂载上传图片的uploads
文件夹、data
文件夹和数据库储存的mysql
文件夹,并映射80端口。
这时候再登陆该地址,会提示要初始化一些配置,可以用官方提供的配置:
然后就是创建用户名和密码了,创建成功,图床已经初步建成。
但是现在直接做图床,看到的链接都是丑陋的IP和http字段组成的地址,干干巴巴,麻麻赖赖的,一点都不圆润。
所以接下来就要盘它了。一方面是IP更换为域名,另一方面是HTTP更换为更为安全的HTTPS。
为IP申请一个域名,然后配置DNS,将域名直接以A类指向云主机IP即可。过一段时间应该就可以在本地看到域名解析生效:
然后直接在浏览器输入域名即可访问。
要将HTTP转为HTTPS主要有两个步骤,一个是申请证书,一个是安装证书。
偶然发现了FreeSSL这个网站,申请证书是真的方便,还有一个支持各大平台的客户端KeyManager,可以直接在里面申请Let’s Encrypt证书或者TrustAsia证书,一般前者半年,后者一年,因为比较懒,所以选后者。
在里面申请证书后,会有两种方法验证,一种是DNS验证,另一种是文件验证。
对于DNS验证,它会给你一串字符,让你到DNS解析设置里添加一个TXT解析,并粘贴为该字串。但是验证结果是香港和美国通过了,大陆总是验证失败,提示CNAME超时。因此选择文件验证了。
要将文件放在网站中进行验证,需要将其拷进Docker中,或者直接Docker容器中拷出来。
然后将SSL验证文件放入该文件夹中重新挂载:
然后就可以验证成功了,之后可以生成证书并下载Nginx证书。
接下来就是容器配置了,将HTTP转换为HTTPS。
首先将生成的证书放到网站的某个目录中,一个公钥和一个私钥。然后修改Docker容器的Nginx配置文件,也可以将其从容器中拷贝出来,再作修改:
然后进入nginx/sites-enabled
目录,修改lychee
文件。
加入HTTPS的端口以及SSL证书的地址:
当使用HTTP访问时,直接跳转到HTTPS,有很多方法,这里使用497的错误码实现跳转:
在Nginx的Server配置中加上
|
|
然后重新挂载容器,此时需要指定HTTPS协议的443端口,以及Nginx配置文件目录:
|
|
此时即可正常访问图床,示例如下:
clone pull add commit push
这些类Ctrl+C/V
的命令(Office中),连操作Head指针实现Ctrl+Z/Y
都没怎么用,想起去年收到了Leancloud的10X
程序员笔记本,里面附页还写着几行Git命令,突然觉得有些陌生了。 也只是突然想到,回忆一下,当是补上多年前未肯作的笔记了。
Git检查文件状态可以使用git status
,可以看到已经提交的修改和未提交的修改:
使用git diff
可以查看尚未暂存的文件的修改:
|
|
另外加上--cached
或者--staged
(新版支持)参数,可以直接查看已暂存的和上次提交时的差异。
除去系统自带的mv
或者rm
命令,Git也有自己的git mv
和git rm
命令,在Git仓库中,后者不仅仅是对文件做了前者的操作,也在工作目录中做了前者的操作。
如git rm
在删除文件后,也从跟踪文件清单中删除了该文件(使用--cached
只是从暂存区中删除,使用-f
同时也删除文件),以后不会再跟踪该文件,而rm
命令的操作记录依然会被记录在跟踪文件清单中。
一个简单的例子,先创建一个文件:
此时未放入暂存区,直接删除就可以,Git也不会记录,但是如果Git已经跟踪了该文件,则直接删除状态为:
如果使用git rm test
,可以看到:
可以看到,test
文件的记录已经被删除了。
同样,git mv
也是一样的类型,git mv file1 file2
相当于:
查看每次的提交历史可以直接使用git log
,可以看到每次的提交记录。另外,加上-p
参数可以展开每次提交的内容差异,加上-{d}
可以指定显示最近次数的差异,如-2
显示最近两次提交的差异。加上--since
或者--until
可以限制时间查询,如可以用git long --since=2.weeks
显示最近两周的修改。加上--word-diff
可以进行单词层面的对比,加上--graph
以ASCII
图形表示的分支合并历史。如果只想看每次提交的简略信息,可以加上-stat
参数。另外,可以使用--pretty
指定展示提交历史的格式,如用oneline
将每个提交放在一行显示(--pretty
常用参数有oneline,short,full,fuller和format(后跟指定格式)
)。
如果提交信息写错了,或者有些文件漏掉了未添加到暂存区,可以使用amend
指令重新提交:
这样就完成了提交信息的修改。
如果想取消暂存区的某个文件的暂存,有两种方法。一是上面的git rm --cached
直接将文件从暂存区中删除,实际文件不受影响。另外一个是HEAD
指针的操作。HEAD
可以理解为指向当前分支的指针,指向该分支最近一次的调用,操作HEAD
指针即可实现版本回退等操作。
这里直接使用reset
命令,将某个文件重置到最近一次提交时的状态:
因为上次test
未暂存,所以相当于从暂存区中取消该文件。
使用git checkout -- file
可以撤销上次提交以来,对某个文件的所有修改,本质上是拷贝了上次提交时的该文件来覆盖它,因此对该文件做的任何修改都会消失。该命令需要谨慎使用,最好的方式是通过分支的保存进度来恢复。
Git中所有已经提交的东西基本上都是可以恢复的,但未暂存的就不属于Git恢复的范畴了。
Git主要是在本地修改好了再推送到远程仓库,实际上对远程仓库的操作比较少,就一些基本的推拉行为。
git remote
即可查看当前的远程仓库,加上-v
选项可以以详细模式查看。 git remote add <shortname> <url>
,将仓库名和地址添加即可。 git fetch <remotename>
命令。git clone
获取的远程仓库会自动归于origin
名下。git pull
命令。 git push <remotename> <branch>
。 查看远程仓库信息。
|
|
远程仓库的删除和重命名。
git remote rm <remotename>
git remote rename <orignname> <newname>
Git可以给历史中的某个提交打上标签,以示其重要性,如v1.0
等。
列出已有标签,可以直接使用git tag
命令,加上-l
参数可以过滤选项。如
|
|
标签分为轻量标签和附注标签,轻量标签如其名轻量,只是一个特定提交的引用,本质上是将提交校验和存储到一个文件中,没有保存其他任何信息,因此创建也比较简单。附注标签则是Git数据库中的一个完整对象,是可以被校验的。附注标签通常包含打标签者的姓名、邮件地址、日期、标签信息等,并可以使用GPG(GNU Privacy Guard)签名及验证。
tag
的-a
选项:
|
|
查看标签:
其中,-m
是存储在标签中的信息,是必填内容。使用git show
也可以看到标签信息与对应的提交信息。
|
|
查看标签:
此时用git show
只能看到标签的提交信息,没有额外信息。
也可以对过去的提交上标签,使用git log --pretty=oneline
时可以看到每次提交的校验和,如某次校验和是e0c29751bf13be3df3b5030cc589685752bd9fb6
,则可以通过该校验和给该次提交打上标签:
实际只需要部分校验和即可。
通常情况,git push
并不会将标签推送到服务器上,需要通过显示命令才能分享标签到远程仓库。
如果要一次性推送所有本地新增标签到服务器上,则可以使用--tags
参数:
删除本地仓库的标签,可以使用:
如果要同时删除远程标签,则需要使用git push <remotename> :refs/tags/<tagname>
来更新远程仓库标签。
可以使用git checkout
命令查看某个标签指向的文件版本。但会使仓库处于头指针分离(“detacthed HEAD”)的状态:在”头指针分离“状态下,如果做了某些更改然后提交他们,标签不会发生变化,但新的提交不属于任何分支,也无法访问,除非确切的提交哈希。所以如果要进行更改,通常需要创建一个新分支:
如果继续对newversion
分支做改动,该分支的提交指针会继续向前移动,就不是原来的v1.12
标签了。
Git好用很大原因是其极具优势的分支模型,使得分支处理方式更为轻量。
在使用git commit
新建一个提交对象前,Git会先计算每一个子目录的校验和,然后在Git仓库将这些目录保存为一个Tree对象,然后就可以创造一个提交对象,并包含了指向这个Tree对象的指针。Git使用blob类型的对象存储此次保存的快照。
关于Git的树结构,可以用Git官方仓库中的一张图说明:
这是首次提交后的结构图,此时Git仓库中有五个对象(五个校验和),最右侧的是三个存储文件快照的blob对象,中间是记录目录结构和blob对象索引的树对象,最左侧是包含指向书对象的指针和所有提交信息的提交对象。
此时因为是第一次提交,相当于祖先提交,提交对象中没有父对象,但之后的所有提交对象中,都会多一个父对象指针,指向上次提交。
Git分支在本质上是一个指向最新提交对象的指针,每次提交操作之后,指针都会更新到最新提交。
分支就是某个提交对象往回看的历史。
使用git branch
可以列出所有的分支,加上--merged
或--no-merged
可以显示已合并或未合并的分支。
Git使用master
作为默认的分支名,如果要创建分支,可以使用branch
选项。
但此时只是新建了一个分支,并未将当前工作分支切换过去。Git确定当前工作的分支是使用HEAD
指针,HEAD指针指向哪个分支,当前就在哪个分支工作。
也可以使用git log -decorate
命令查看各个分支当前所指的对象。
切换分支即修改HEAD
指针指向,可以使用chenkout
命令实现。
在每次提交后,HEAD
指针会随着当前分支一起向前移动以保证以后分支能正确切换回来。
或者直接使用命令:
可以在新建分支的同时切换到该分支,-b
可以理解为branch
,相当于:
在某个分支上进行操作,使得该分支指针向前移动后,如果要将该分支合并到其他分支,则可以切换到其他分支进行merge
操作:
当两个分支没有需要解决的分歧时,可以直接合并。
当分支不再使用时,可以删除:
对于未合并的分支,直接删除会失败,可以使用-D
强制删除。
如果合并的两个分支,并不是直接祖先关系,两个分支在其共同祖先分支上都做了修改,如果修改没有冲突,如修改的都是不同的文件,则Git会自动新建一个提交,将共同祖先分支以及两个要合并的分支共同合并建立一个新的提交。此时Git会自行决定选取哪个提交作为最优的共同祖先。
但是如果两个不同分支都对同一个文件做了修改,在合并时就会引起冲突,因为Git不知道到底该对这个文件做如何操作。此时Git会先暂停下来,等待用户解决冲突。这种情况在平时也经常会遇到,如在本地对某个远程仓库做了修改,但是远程仓库在此之前已经在另一台电脑上做了push
操作,这时使用pull
操作就会自动抓取并合并到当前分支,如果存在冲突,pull
时就会提示哪个文件修改冲突,并等待用户解决。此时,可以使用git status
查看状态。
解决冲突后可以重新使用git add
将其标记为冲突已解决。
远程引用是指向远程仓库的指针,包括分支、标签等,可以通过git ls-remote <remotename>
查看远程引用的完整列表,或者通过git remote show <remote>
查看远程分支的更多信息。
远程跟踪则是指向远程分支状态的引用,只有当与远程仓库通信时,它们会自动移动。用户无法手动修改其状态。
可以使用git fetch
命令将远程仓库中的内容拉取到本地,同事远程跟踪会更新到新的远程分支状态。当本地与远程的工作出现分叉之后,合并到本地分支时,依然会考虑是否有冲突的问题,解决方式和其他冲突分支合并一样。
使用git push
将本地分支推送到远端:
等价于
Git会自动将test
名字展开为refs/heads/test:refs/heads/test
。
使用checkout
可以实现对分支的跟踪:
通常可以新建一个本地分支来跟踪拉取的远程分支:
也可以使用-u
或--set-upstream-to
选项来直接设置已有的本地分支来跟踪拉取的远程分支:
另外,可以使用git branch -vv
命令查看设置的所有跟踪分支。
可以使用git fetch
拉取分支后再使用git merge
合并到本地分支,也可以直接使用git pull
拉取并合并到本地分支。但是有时候git pull
会显得有些佛性,难以理解,最简单的方式是fetch
与merge
的组合。
删除远程分支可以使用:
或者直接将空分支推送到远端覆盖远端分支即可:
这个是个有趣的用法,自从有了变基,Github
就变成了Gayhub
(逃 )。
啊呸!当然不是这个原因。
变基是一种整合分支的方法,通常整合分支有两种方法:合并和变基。
合并(merge
)之前已经经常用到了,主要就是将一个分支合并到另一个上。而变基(rebase
)则是将一个分支里提交的修改在另一个分支上重放一边,也就是走别人的路,让别人说去吧。
一个基本的例子如下:
此时,Git会先找到这两个分支的分叉点(即最近共同祖先),然后从分叉点开始,将branch1
所经历的操作,给branch2
也体验一下。然后回到branch2
,进行一次快进合并:
其实就这个例子来看,变基和合并没有任何区别,但这样可以保证在向远程分支推送时保持提交历史的简洁。
另外,变基可以放到其他分支进行,并不一定非得依据分化之前的分支。可以从一个特性分支里再分出一个特性分支,然后跳过前面的特性分支,将后者与主分支进行变基,可以使用--onto
选项。
即取出branch2
分支,找到branch1
和branch2
的分离点,然后在master
分支上重放其共同祖先之后的修改。
然后就可以将变基后的分支快进合并到master
分支上:
剩下的也可以将branch1
合并到master
中:
然后快进合并master
分支:
之后就可以删除无用的分支了。
因为人人都可以编辑,所以一旦分支中的对象提交发布到公共仓库,就千万不要对该分支进行变基,不然其他人不得不重新将手里的工作和你的提交进行整合,接下来你也要重新拉取他们的提交进行整合,引入太多不必要的麻烦。
总之用官方一句加粗的话说:
不要对在你的仓库外有副本的分支执行变基。
和Linux的alias
命令一样的意思,也是方便在git中快速操作。
设置别名后,通过 git co
即可实现git checkout
命令。
当不想提交现在的工作状态,又想切换到别的分支进行工作,可以先将当前状态出藏起来。储藏(Stash)可以获取工作目录的中间状态——也就是修改过的被追踪的文件和暂存的变更——并将它保存到一个未完结变更的堆栈中,随时可以重新应用。
使用git stash list
可以查看当前储藏的列表。
如果之后要恢复储藏的状态,可以使用:
Git则会默认恢复最近一次的储藏,如果想应用更早的储藏,则可以通过名字指定,如:
此时对文件的变更被重新应用,但是被暂存的文件没有重新被暂存。可以通过运行git stash apply
命令时带上一个--index
的选项来告诉命令重新应用被暂存的变更。apply
选项只尝试应用储藏的工作,但储藏的栈上仍然有该储藏。可以通过运行git stash drop
,加上希望移除的储藏的名字来移除该储藏,或者直接通过git stash pop
来重新应用储藏并在此之后快速删除栈上的储藏。
如果要取消之前所应用的储藏的修改,可以通过取消该储藏的补丁达到该效果:
如果没有指定储藏名称,则会自动选择最近的储藏:
在储藏一个工作状态后,继续在该分支上工作,最后还原储藏的时候可能会引起合并冲突,此时可以新建一个储藏分支简化工作。
此时Git会创建一个新的分支,检出储藏工作时的所处的提交,重新应用,如果成功,则丢弃储藏。
]]>关于Telegram(电报),直接引用维基上的介绍:
Telegram是一个跨平台的即时通信软件,它的客户端是自由及开放源代码软件,但是它的服务器是专有软件。用户可以相互交换加密与自毁消息,发送照片、影片等所有类型文件。官方提供手机版(Android、iOS、Windows Phone)、桌面版(Windows、macOS、Linux)和网页版等多种平台客户端;同时官方开放应用程序接口,因此拥有许多第三方的客户端可供选择,其中多款内置中文。
这个是俄国的社交服务VK的创始者杜洛夫兄弟的作品,Telegram Messenger LLP是独立的非营利公司,与VK也没啥关系,所有的宗旨只在于保证聊天和隐私安全。但不接受监管的软件通常过的不是太好,追求绝对的隐私安全,以至于其在各个国家遭受了被封锁的命运。其他国家暂且不论,因为不肯交出密钥,连俄罗斯媒体监管机构都请求法庭再全国范围内封锁该软件。抛开这些原因不谈,就技术方面,其良好的功能体验,开源的客户端以及开放的应用程序接口,已经领先于绝大多数同类APP了。
Tencent基本是属于放弃Linux用户的一类,后来新注册的微信号连网页版都无法使用,虽然嫌弃通常是相互的,但微信之类的产品用的人太多,粘性太大,有时候不得不用其交流,等网页版每次都要扫一下也是麻烦,所以直接使用Telegram的机器人来收发微信消息。
聚合社交平台这方面,EFB做的不错,而且也有了现成的Docker镜像(由royx提供),使得环境搭建更为简单。
另外,需要一台能访问外网的主机,主要是能访问TG(Telegram)服务器。
然后安装Docker:
安装好之后,就可以拉取镜像了:
主要方式是通过登陆网页版微信,然后将微信消息通过Bot发送及接受。首先需要配置TG Bot:
@botfather
机器人,然后发送指令:/newbot
bot
结尾/
开头的消息,所以需要设置隐私权限。向该机器人发送指令/setprivacy
,选择刚刚创建的机器人,点Disable
即可。 /setjoingroups
,选择enable。/setcommand
,输入以下内容:
|
|
搜索另外一个机器人@get_id_bot
,点击start
即可获得TG ID。
新建一个config.py
文件保存机器人信息,输入以下内容:
在其中输入之前所获得的token,以及将admin后的内容换成TG ID。其余xxx
的内容是语音识别API,想要的可以申请,没有的也无所谓。
然后新建一个tgdata.db
文件,为空即可。
指定配置文件和数据文件的地址,启动容器:
然后通过docker logs
查看容器输出内容,应该可以看到一个二维码,用微信扫一扫即可登录。
登陆成功即可正常使用机器人收发微信消息,默认情况下,所有的微信消息以及公众号消息,全都是通过那个机器人发送的,看起来会比较乱。
如果需要单独跟某个人聊天,一种方法是在你创建的机器人中发送/chat 好友名
,然后机器人会给一段消息,回复那个消息就可以将消息发送给指定的联系人。但是略显麻烦,聊天不多的人可以这样。
另一种方法是单读创建一个TG群组,然后将名称命名为你要聊天的好友名,将机器人拉进来。然后向你所创建的机器人发送指令/link 好友名
,将与该好友的聊天绑定到你所创建的群组中,即可如微信一般发送以及接收消息,且可以发送TG的自定义贴纸表情。
此外,使用EFB工具也可以托管QQ消息,方法挺多,这里依然采用最简单的容器方法。
和接管微信消息一样,需要创建一个机器人获取Token,也可以就用微信机器人,不过为了方便管理,就直接另外创建一个机器人了。
然后直接使用EFB和酷Q的efb-qq-coolq-docker
项目中的配置,仓库在这.
然后修改两个配置文件:
|
|
和
|
|
执行docker-compose up -d
,然后打开ip:9801
完成登录操作。
但目前直接登录后,login可以成功,却无法获取到friends,借用blue-bird1
的解决方法,修改bot容器中的配置:
|
|
将第329行和第512行的调用赋值改为绝对赋值:
|
|
然后重启容器即可。
|
|
此时即可正常收发QQ消息。
]]>AROP(ADVANCED OFFICEPASSWORD RECOVERY)好像是比较主流的Office的破解工具,有收费版和免费版,区别在于密码长度是否超过4字节。
用了一个虚拟机跑了一下,字典查询没有,于是暴力破解,嗯,破解速度比较令人绝望,毕竟是CPU在跑。
将该Excel文件解压之后,可以发现里面包含DataSpace
、EncryptedPackage
以及EncryptionInfo
等文件,打开EncryptionInfo
文件,可以看到里面加密的一些信息:
其中,采用的是AES算法,有盐(salt,指随机的数据,加入到哈希的过程中,加大破解难度),hash算法是SHA512,spinCount=100000
经过了100000次的迭代操作,想直接逆向破解,实在太难。字典尝试无效,只能暴力破解,但是随着密码长度增加,以及字母数字、特殊字符的引入,破解难度指数增长。对于纯小写字母的6位密码,复杂度为$26^6 = 308915776$次,七位则超过了80亿次,指望这个靠CPU计算的软件,实际希望不大,跑了半天后放弃了。
Hashcat号称世界上最快的密码破解,世界上第一个和唯一的基于GPGPU规则引擎,免费多GPU(高达128个GPU),多哈希,多操作系统(Linux和Windows本地二进制文件),多平台(OpenCL和CUDA支持),多算法,资源利用率低,基于字典攻击,支持分布式破解等等。
嗯,暴力破解的话,只能考虑使用GPU跑,这时,开源的hashcat就是一个不错的选择。
使用hashcat破解office,先需要获取文件的hash值,网上有现成的工具office2join.py
,然后用python运行,参数加上该office文件即可。
可以看到,加密方式为office2013,将第一个冒号后面的字串复制到一个新文件中保存即可。
知道加密方式后,需要找到对应的破解模式,首先使用--help
看一下帮助:
可以看到office的加密模式有以下几种:
所以,office2013选择-m 9600
,然后开始破解:
其中,-a
表示破解模式,3
是暴力破解,--session test
是将该次破解进程命名,可有可无,但之后如果要中断再恢复,则可以使用进程名恢复。-o found.txt
是将找到的结果输出到指定文件中,hash.txt
是之前保存hash码的文件,最后是一串正则表达式,有?l?u?d?s
,分别表示小写字母、大写字母、数字、和特殊字符。该例子表示暴力破解七位小写字母的密码。
但由于不确定位数,先从简单的开始,破解从1到8位的小写字母:
其中,--increment-min 1 --increment-max 8
即如本身含义。
然后查看一下显卡运行情况:
如果中途有事,可以先暂停一下,然后跑完后再过来继续破解:
最后大概半天吧,跑出来结果如下:
冒号后面的就是密码。
]]>关于Docker容器的储存结构以及基本介绍,之前貌似有一篇文章已经说了一些了,这里不再赘述。
通过镜像构建容器很简单,docker run imagename
即可,由于是博客,可以把本地的blog目录挂在进去,并映射里面的端口,即加上-v /Blog:/Blog
和-p 4000:4000
,其他设置自己怎么喜欢怎么来。
个人是直接从Docker Hub官方仓库中的ubuntu:16.04镜像来启动容器的,可以使用docker pull
,也可以使用docker run
命令来启动容器。
然后是安装一些必要的软件:
首先安装必要的环境,然后安装npm的包管理工具nvm,然后配置git账号,并安装特定版本的node,在安装之前先确认之前可以运行的时候的node版本即可。剩下的就是安装hexo和gulp(博客资源压缩工具,优化用)。
为了hexo能够直接deploy,配置免密登录密钥并添加到github中。
拷贝/root/.ssh/id_rsa.pub
文件中内容到github的SSH-KEY中即可。
然后删掉博客中的node_modules/
文件夹和db.json
文件,重新安装。
之后便可以正常在本地访问博客了。
在之前环境配置好之后,退出容器,将该容器打包成镜像。
可加参数有-a authorname
添加作者信息,-m message
添加说明文字,如:
后面添加容器ID或者容器名都可以,然后添加你要上传到DockerHub的仓库名以及版本标签(TAG如果为空,默认为latest)
将之前的指令写入Dockerfile文件,然后建立镜像即可。
|
|
其中MAINTAINER
是作者名字,FROM
是使用的镜像来源,然后安装环境,和之前一样。最后使用Docker build
命令构建镜像。
需要说一点的是,通过Dockerfile构建的镜像不能直接使用ssh-keygen
命令生成免密密钥,因为每次构建镜像时都会执行一次生成指令,如果之后版本需要修改,Dockerfile中需要加入其他指令,那么原来可以免密的镜像,生成后会变的无法登陆。最明显的就是使用ssh的方式的hexo deploy和github 仓库的访问,故而将已经可以免密的.ssh/
文件夹直接拷贝进来。
|
|
使用-t
指令是指定之后要上传到Docker Hub的镜像仓库名。
然后等待一会,会显示构建完成,使用docker images
便可以查看之前直接在容器中构建的镜像和使用Dockerfile构建的镜像。通常使用Dockerfile构建镜像体积会更小,因为Docker的分层存储方式,由于在容器内通常会做很多多余的无用指令,所以直接commit构建的体积很容易变得臃肿。
之后就是上传镜像:
|
|
其实在镜像制作完成后,即可以使用镜像启动容器,然后使用博客环境了。上传镜像之后,可以在不同电脑上使用该博客环境,也不会有环境冲突的问题。但是每次都要挂载本地目录到容器中(因为博客目录体积较大,直接放入容器中体积太大,而且博客会更新,容器不能保存,只能重新制作镜像,使得效率低下)。也许是觉得在不同电脑上都要下载镜像启动容器显得麻烦,或者觉得每次都要手动generate、push和deploy显得麻烦,便开始打算使用Docker自动化部署。
首先去DockerHub创建一个仓库用来自动化部署,仓库创建需要绑定github账号,然后将博客的源文件仓库链接至该镜像仓库,如下图所示。
|
|
然后git add .
以及使用commit
和push
上传至git仓库即可触发。
其中,ADD
一个变化的值,保证之后的构建不使用缓存,不然即使仓库更新了,容器里的仓库也不会更新。
其实不用每次git clone
,git的优点就是差异性存储,所以可以之前依然可以使用缓存,节省时间,将后续操作设置成不使用缓存。
|
|
另一种方式,直接将当前仓库中的文件添加到容器中:
|
|
这种方式,每次修改文件后都将整个文件夹添加到容器中,文件夹比较大的话会花比较多的时间。另一方面,因为Docker层层有缓存,所以第一种方式也只有第一次较慢。
最后观察触发是否成功以及最后输出结果是否在预期内。
上述涉及到ADD
和COPY
区别,COPY只是简单的复制,ADD支持下载URL,并支持解压,并具有判断其ADD
的src
功能。因此,在没有特殊需求时,尽量使用COPY提高效率,上述使用ADD .git/
是为了让Docker Daemon判断git仓库是否更新了,如果更新了,则不使用缓存,这样,后续的git pull
才能真正获取更新并最终更新到网站上。
.dockerignore
文件来忽略一些容器中你不用的文件以提高速度。容器加载时会默认将当前目录下所有文件打包传给Docker Daemon,比如就是node_modules
文件夹。step by step
操作一番,不填坑也不会有印象,也并不会达到对容器熟悉的效果。不知道有没有人对音乐感兴趣,然而不为盈利,也非是为了悦人而作,也只是突然想写,写便罢。
拍子是音乐里最基础的节奏单位,经常听到的电音里面的那些”咚-咚-咚–”就是一拍一拍的。一定数量的拍可以构成节,如传统的 $\frac{4}{4}$ 下面的4表示4分音符为一拍,上面的4表示一小节拍4下。
另外,拍子的速度则是每分钟拍多少下,即beats per minute
(bpm)。时值则是音符持续时间长短,比如2分拍的时值就是4分拍的2倍。
钢琴一共88键,其实结构很简单,如果从数学的角度考虑,其实也都很容易记住。我画了一下:
恩,画得好累~虽然花了点时间,但实际上手绘就比较快了,钢琴一共88个键,左音低,右音高。其中,最右边的是小字5组C,即($C^5$),最左边的则是大字2组A,即($A^2$)。中间从小字组往左分别是大字组和大字一组,往右是小字一组到小字五组。每组为CDEFGAB七个白健,键盘中相邻两个键相差为半音,如B和C之间相差即为半音,而C和D之间,由于中间存在一个黑键,因此相差为一个全音。
黑键没有独立的名字,分别根据相邻的白健命名,如C和D之间的黑键称为C#或者D♭,其中sharp(#)表示升调,♭符号表示降调。
关于中央C,即最中间组的C,图中从大字二组到小字五组一共有九个组,中央组就是第五组了,也就是小字一组C,有的也称之为$C^5$。
音程,即两个音之间的高低关系,可以理解为两个音之间的距离。与两个音之间的半音数有关。
半音数和音程之间的关系,如下表所示:
半音数 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
音程 | 纯一度 | 小二度 | 大二度 | 小三度 | 大三度 | 纯四度 | 增四/减五度 | 纯五度 | 小六度 | 大六度 | 小七度 | 大七度 | 纯八度 |
嗯,可以简要从数学方面分析一下。
为此,我再画两个组,从C到A,重复两次:
虽然说规律一致,我还是一个一个分析吧:
表中,增四度和减五度之间的距离都是6个半音,即三个全音,因此增四度减五度都成为三全音。
音程转位就是从一个组的调转到下一个组的该调。如CDE转位后为EFGABC。从数列方面看,也有一些规律。
因为七个白键一组,所以从一个音会到下个组的本音,之间项数为8,距离为7。但转位之后,如从C-E与E-C,中E出现两次,所以最后一共有9个度数。故转位之后度数之和为9。而这一组白键中,黑键数量永远为5,因此转位之后,音程互补。对于大音程,转为之后成为小音程。增音程,转位之后成为减音程。
主观感受吧,大致是觉得听起来比较协调,就觉得是协和音程。
主要分为4类:
完全协和音程 | 纯一度、纯八度 |
协和音程 | 纯四度、纯五度 |
不完全协和音程 | 大/小三度、大/小六度 |
不协和音程 | 大/小二度、大/小七度、增四度、减五度 |
音调,记得初中物理里解释为声音频率的高低,其实大致也是这种感觉吧。直观的感觉是高音轻短细,低音中长粗。
图中琴键每七个音阶一组,即CDEFGAB
,调有大小之分。
以一个C大调为例,如表所示:
C大调 | C | D | E | F | G | A | B | C |
---|---|---|---|---|---|---|---|---|
距离 | 全音 | 全音 | 半音 | 全音 | 全音 | 全音 | 全音 | 半音 |
选取任意一组琴键,图中可以看到,一个大调包含了七个白健,五个黑键,除了中间的EF两个白健之间没有黑键,其余的从C开始到B,中间都有黑键。因此除了EF之间是相差半音,其余都是相差全音。另外,由于一组七个循环都是从白健开始,首位白健之间也是没有黑键的,因此如果从C回到C,最后B和C之间也是相差半音。大调特征即是如此,即三和七为半音,其余为全音,所谓的“全全半全全全半”。
以此方式,则D大调为:
D大调 | D | E | F# | G | A | B | C# | D |
---|---|---|---|---|---|---|---|---|
距离 | 全音 | 全音 | 半音 | 全音 | 全音 | 全音 | 全音 | 半音 |
表中F和C处为了保持半音和全音的距离,因此写作了F#和C#。虽然F#和G♭是一样的,但在这里,为了保持相邻两音度数为2,因此只能写作F#。如果写作G♭,则G♭和G之间只有一度音,少了F调,无法成为全音。C#的原因也是一样。(主要是写法不同)
另外,有个关于升号调和降号调的一个表格:
升号调 | G | D | A | E | B | F# | C# |
---|---|---|---|---|---|---|---|
降号调 | F | B♭ | E♭ | A♭ | D♭ | G♭ | C♭ |
以此方式,可以看一下F大调:
F大调 | F | G | A | B♭ | C | D | E | F |
---|---|---|---|---|---|---|---|---|
距离 | 全音 | 全音 | 半音 | 全音 | 全音 | 全音 | 全音 | 半音 |
以上述方式理解或者直接查升号调降号调表格,中可以看到,F属于降号调表示。
各音之间,稳定性是有差异的,最稳定的音为主音,如C大调主音为C,各音的稳定性如下:
最稳定 | 1 | 5 | 3 | 6 | 2 | 4 | 7 | 最不稳定 |
---|---|---|---|---|---|---|---|---|
稳定音 | 稳定音 | 稳定音 | 不稳定音 | 不稳定音 | 不稳定音 | 不稳定音 |
而各音会有倾向性,即不稳定的音听起来会倾向于进行到稳定音上。其中2级倾向于进行到1级,4级倾向于进行到3级,6级倾向于进行到5级,7级倾向于进行到1级。
和弦是一些音的结合,先看几个基本的三度叠置和弦:
大三度+小三度
的和弦称之为大三和弦,称之为$Cmaj$,简写为C。其中,C称之为根音,E和G相对于根音分别是3和5度(算上根音本身),因此称之为三音和五音。将三音或者五音、七音放在最下面时(即作为低音),构成和弦转位。
如CEG和弦:
和弦转位 | CEG | EGC | GCE |
---|---|---|---|
位置 | 原位 | 第一转位 | 第二转位 |
记法 | C | C/E | C/G |
同样的,如大七和弦:
和弦转位 | CEGB | EGBC | GBCE | BCGE |
---|---|---|---|---|
位置 | 原位 | 第一转位 | 第二转位 | 第三转位 |
记法 | CM7 | CM7/E | CM7/G | CM7/B |
看这名字,可以大致想一下,一组键包括7个白键,5个黑键,加起来是12个键,如果要五度循环,那么最小公倍数为60,因此应该是12组,从C开始算,最后才能回到C,完成一个循环。
纯五度协和程度仅次于纯一度和纯八度。五度循环圈如图所示:
图中字母可以看做和弦根音,也可以看作单个的音,也可以看作调式主音。如从C大调的所有纯五度音程为:C-G,G-D,D-A,A-E,E-B,F-C。与五度循环圈中位置一致。
外圈和内圈作等音转换,如C#等于D♭,圈里有三个半音。通过五度循环圈,可以很容易的写出和弦音。
调式七个音级使用罗马数字表示,每个调式和音级关系如下表:
C | D | E | F | G | A | B | C |
---|---|---|---|---|---|---|---|
I | II | III | IV | V | VI | VII | I |
主 | 上主 | 中 | 下属 | 属 | 下中 | 导 | 主 |
主要可以由琴键理解。C上方是D,因此D称之为上主音。C下方是B,称为导音。然后主音上方纯五度,即G处是属音。C下方纯五度为下属音,即F处。主音和属音中间的音称为中音,即E处。主音和下属音中间的称为下中音,即A处。
通常大调与小调区别,再直观感受上是,大调比较欢快、明朗,小调忧郁、悲伤。再音阶结构上区别如下:
C大调 | C | D | E | F | G | A | B | C |
---|---|---|---|---|---|---|---|---|
c小调 | C | D | E♭ | F | G | A♭ | B♭ | C |
I | II | III | IV | V | VI | VII | I | |
全 | 半 | 全 | 全 | 半 | 全 | 全 |
如表中所示,大调和小调的区别在III级音和VI、VII级处,其中最主要的区别在于三级音,也成为调式特性音。主音相同,三级音的特性是使小调悲伤的主要原因。
此外,音阶结构相对于大调的“全全半全全全半”,为“全半全全半全全”。
比较一下C大调和a小调:
C大调 | C | D | E | F | G | A | B | ||
---|---|---|---|---|---|---|---|---|---|
a小调 | A | B | C | D | E | F | G |
a小调再琴键上全是白健,构成音和C大调一样,只是主音不同。此时这两者称之为关系大小调:a小调是C大调的关系小调,C大调是a小调的关系大调。
通常构成音相同的大小调称为关系大小调,大调六级音为小调主音,小调三级音为大调主音。还是用数学方法证明一下:
小调音阶 | 全 | 半 | 全 | 全 | 半 | 全 | 全 | ||
---|---|---|---|---|---|---|---|---|---|
大调音阶 | 全 | 全 | 半 | 全 | 全 | 全 | 半 |
将大小调音阶对比,可以看出,在平移两度之后,小调音阶与大调音阶一致。从表中可以得到,大调的关系小调在其下方小三度处(相差2度)。
小调调号使用关系大调调号。画在五度循环圈里如下:
自然音程:大调(或者自然小调)中,任何两个音构成的音程都属于自然音程。
之前所分析的音程均为自然音程,共四大类,14种。
自然音程 | ||||
---|---|---|---|---|
纯音程 | 纯一度 | 纯四度 | 纯五度 | 纯八度 |
大音程 | 大二度 | 大三度 | 大六度 | 大七度 |
小音程 | 小二度 | 小三度 | 小六度 | 小七度 |
三全音 | 增四度 | 减五度 |
除了自然音程之外的,都属于变化音程,也有四类:
具体关系可由图表示:
图中可以看出,纯音程和大音程,在度数不变情况下,增加一个半音可得到增音程,再增加一个半音,可以得到倍增音程。如下表所示:
F-G | 大二度 |
---|---|
F-G# | 增二度 |
F-Gx | 倍增二度 |
减音程和倍减音程类似:对于纯音程和小音程,在度数不变情况下,减少一个半音可以得到减音程,在减少一个半音可以得到倍减音程。
G#-A | 小二度 |
---|---|
G#-A♭ | 减二度 |
G#-A♭♭ | 倍减二度 |
纯音程可以变为增音程、倍增音程和减音程、倍减音程。但由于纯一度的半音数为0,所以纯一度没有减音程和倍减音程。
小调与大调区别主要在于音阶,自然小调变体有和声小调和旋律小调。
I | II | III | IV | V | VI | VII | I | |
---|---|---|---|---|---|---|---|---|
a自然小调 | A | B | C | D | E | F | G | A |
a和声小调 | A | B | C | D | E | F | G# | A |
a旋律小调 | A | B | C | D | E | F# | G# | A |
大调与小调之间的关系:
I | II | III | IV | V | VI | VII | I | |
---|---|---|---|---|---|---|---|---|
C大调 | C | D | E | F | G | A | B | C |
c自然小调 | C | D | E♭ | F | G | A♭ | B♭ | C |
c和声小调 | C | D | E♭ | F | G | A♭ | B | C |
c旋律小调 | C | D | E♭ | F | G | A | B | C |
从表中可以看出,大调与同主音的主要区别在三级音,将大调三级音降半音,可以得到旋律小调,再将六级音降半音可以得到和声小调,再将七级音降半音就可以得到自然小调了。
]]>首先是下载LXD容器,如果是Ubuntu16.04里的apt软件仓库,最高应该是2.x的版本,如果要支持LXD容器内GPU的数据处理,至少版本为3.0.好在从16.04时候引进了另一个软件包管理工具,之前一篇文章有所介绍,即使用snap软件包管理工具。
查看版本:
可以看到已经到3.6了,直接下载就行snap install lxd
。
安装好后应该就可以直接使用了,第一部是初始化LXD的环境,使用lxd init
。如果出现permission denied
之类的问题,可以加sudo
,嫌麻烦可以将当前用户加入LXD组内:
然后注销重新登录就行了。
在初始化之前,需要安装几个工具,一个是ZFS,是LXD默认的后端存储工具,另一个是Bridge管理工具,LXD自身也带网桥创建功能,默认创建网桥会自动创建局域网私有地址并分配DHCP地址至虚拟网卡。
初始化过程如下:
所有提示注意一下是否创建网桥时候选择no就行,其余基本可以使用默认配置。如果不用管外网远程登录,可以直接全选默认。
然后拉取一个镜像,如:
拉取成功启动了就可以使用lxc list
看到容器了。使用lxc exec test -- ${command}
命令在容器内执行命令。如:
这时可以进入容器内的bash。
然后通过配置好第一个容器,将其作为模板,制作出多个虚拟主机。
在此之前,需要宿主机上安装显卡驱动和CUDA,具体过程不做赘述。
先关闭容器lxc stop test
,然后将显卡设备添加到容器中:
该命令是添加所有显卡,也可以手动指定显卡id。
然后启动容器,安装显卡驱动:
可以直接参考宿主机的显卡驱动,查看一下宿主机显卡驱动版本,可以使用nvidia-smi
或者sudo dpkg -l |grep nvidia
查看,然后回到容器,使用apt install nvidia-XXX-dev
安装。
如果安装成功,即可以使用nvidia命令查看显卡。
CUDA版本和TensorFlow版本由用户自己选择,默认不安装。
这个是最麻烦的,如果需要访问外网的话。目前个人方法如下:
先使用lxc创建一个网桥,网桥地址应该与本地电脑在一个网段,这样桥接后本地其他电脑才可以远程访问该容器。假如本地各电脑IP为192.168.1.xxx
,则:
其他可使用默认配置,具体各项参数见官方说明。
然后使用bridge管理工具将网桥连接至本地网卡,假如本地网卡为enp1s0,则:
添加之后可以使用brctl show
命令查看。
这时可能出现宿主机无法上网的问题,原因是访问网络时,数据包都默认转发到新建网桥地址,而不是默认网关地址,所以需要添加一条路由表:
可以解决本地宿主机上网问题。
关闭容器后再次使用lxd init
初始化容器环境,主要是为容器选择默认网桥,这时只用修改一项配置Would you like to configure LXD to use an existing bridge or host interface? (yes/no) [default=no]
,改为yes,然后输入新建网桥名lxd0
即可。
然后重新启动容器并进入bash,修改网络配置文件:
添加
重启网络服务
如果IP还不变,那就重启宿主机。
通常到上一步已经可以上网了,默认域名解析服务地址是网桥地址,你也可以改为自定义的DNS地址,如114.114.114.114
。最通常的方法是修改/etc/resolv.conf
文件中的nameserver
。但重启后会失效。以下是永久修改DNS的方法,通常在搭建过程中不需要用到。
修改/etc/resolvconf/resolv.conf.d
目录下的base,在里面修改DNS服务器地址即可。
另一个方法是修改DHCP配置文件,
可以看到,
去掉前面的#,将域名服务器改成自己的就可以了。
如果希望用户能远程访问容器,除了网络配置之外,还需要修改一下ssh配置。默认禁止root用户登录,容器创建默认用户也是root用户,里面有个ubuntu用户,未初始化。既然虚拟主机交给用户,即把root也给用户了,所以先设置允许root用户登录,如不需要可以让用户自行更改。
将其中的PermitRootLogin prohibit-password
改为PermitRootLogin yes
,以及ChallengeResponseAuthentication no
改为ChallengeResponseAuthentication yes
。
然后为root用户设置密码:
另外可以编辑ssh登录用户的欢迎信息,通过编辑/etc/update-motd.d/
目录下的00-header
和01-hepler-text
中的内容即可完成。
最后,重启ssh服务,
最后需要在主机上创建一个文件夹,用于各个容器与主机共享,文件传输之类,虽然主机lxc已经有pull和push方法从主机和容器之间拷贝文件,但共享目录会显得更为方便,即便在容器之间也可以相互访问。
其中,path
和source
的地址可以自己定义。
到这里,基本结束。
还是先说一下Docker容器的储存结构,容器镜像采用的是分层存储的方式,下面是一个Ubuntu16.04的镜像结构。
也可以在命令行观察,在拉取的时候也可以看到结果:
其中,每层表示的是与上一层的差异,而不是直接操作底层镜像,这样,当使用其他基于此镜像制作的镜像时,就不必整个拉取或者复制过来,因为它们很多的底层镜像是一样的。Docker镜像采用的是共享存储方式,当拉取一个镜像时,会首先获取所有层的信息,如果该镜像层本地已经有了,就不用下载,只需要下载所需要的镜像层。
在使用镜像建立容器时候,会在最上面一层镜像上建立一个可写层,即容器层。当在容器中所有的操作都会被保存在这个可写层,如果直接删除容器,则可写层就会被删除,即使利用相同镜像重新建立容器,之前的所有操作也不会被保存。镜像层都是只读的,基于此安全性,所有的容器都可以访问底层镜像,所以一次可以利用同一镜像建立多个容器。最后完成修改封装成新的容器的时候,也只是在原来的镜像层之上又加了一层而已。镜像的这种共享存储方式可以极大地提高资源利用效率,而差异存储也是文件管理的主流之选。
说到这个,想起来目前有个PWD(Play with Docker)的网站,可以直接在里面体验docker,地址在这。进去就可以创建一个Docker playground。
在电脑上配置CUDA或者TensorFlow啥的,经常因为各种版本不同导致一大堆问题,于是就想看看可不可以利用Docker去解决这个问题,每次直接打开封装好的镜像就行了,让Docker里的环境去使用GPU,不用去配环境,也不用在电脑上装啥别的软件。然后发现NVIDIA也在Docker上稍微封装了一下,弄了个Nvidia-docker命令,基本命令与docker命令一样,唯一的区别是普通的Docker无法使用GPU,所以Nvidia-docker
等效于命令docker --runtime=nvidia
。
要使用GPU,首先也要安装好显卡驱动,怎么安装这里不做赘述,通常安装成功是可以看到的。
当然,Docker肯定要先装好。
然后就是安装Nvidia封装的Docker来调用GPU了,具体可以参照NVIDIA/nvidia-docker页面。
如果之前有安装1.0版本的Nvidia-docker的,需要先卸载:
|
|
没有的话可以直接略过。然后添加仓库地址重定向到镜像源文件中,再更新软件源。
|
|
然后直接安装即可。
首先运行一个cuda的镜像,进入bash中,
这里将容器命名为cuda方便操作,需要选择runtime为nvidia,或者直接使用nvidia-docker
命令。然后输入nvidia-smi
就可以看到是否成功调用显卡了。
刚刚也说了,如果要使用GPU,需要在docker命令中加上–runtime=nvidia或者直接使用nvidia-docker
命令。这里就直接使用nvidia-docker
命令了,
查看容器内信息,
打开浏览器窗口,输入localhost:8888/?token=64e73aaba8febd5539fae22201c7b7cea1b8578cc1413850
,可以看到一个Jupyter的界面。
在里面可以编辑及运行python程序,或者使用终端操作:
|
|
一个容器运行需要制定规范、Runtime、管理和定义工具、镜像仓库、运行OS等环节。容器的Runtime是容器运行时的一些规范,主要任务是和操作系统的kernel协作来提供容器的运行环境,由OCI(Open Container Initiative,由Google,Docker、CoreOS、IBM、微软、红帽等于2015年联合发起的组织)维护。主要包括容器的文件系统包(Filesystem Bundle),容器的运行和生存周期Runtime and Lifecycle),容器配置文件(Container Configuration file),以及Linux的运行和配置文件(Linux Runtime, Linux Container Configuration)等。目前Linux上最原始的容器Runtime是LXC,即Linux Container,最初Docker也是用LXC作为Runtime,后来Docker基于libcontainer开发了自己的Runtime,即runC。谷歌也基于Docker的Runtime发布了Kubernetes,后来CoreOS开发了独立的rkt作为运行容器的Runtime。
而与容器相对的就是虚拟机了,目前虚拟机的Runtime如runV,看名字就知道是要与runC分庭抗礼的。此外Intel也弄了一个Clear containers的Runtime,也可以对接容器。基于Hyper runV和Clear containers,Openstack又新起了一个Kata Containers,目前已经可以在snap商店看到了,才出来没多久,地址在这。
这里使用的是一个开源的云存储方案OwnCloud来搭建私有云盘。
首先可以搜一下Dockerhub中的镜像,docker search owncloud
可以看到结果:
其中第一个就是官方的镜像了,直接docker pull owncloud:8.1
拉取就行。或者也可以直接docker run
,本地没有它会去Dockerhub下载。
|
|
其中,-d
表示后台运行,-p
用来映射端口。也可以直接用-it
前台打开tty直接操作。
这时候可以在浏览器中看到了,输入localhost
就可以看到登陆界面,大致是下面的样子:
其中数据保存在/var/www/html/data
目录中,默认是使用SQLite用于数据存储,但对于较大的或者使用桌面客户端同步文件时,并不推荐SQLite,可以考虑最流行的MySQL。其他数据库需要外部安装。
在运行时可以使用-v
选项来将本地磁盘挂载到容器中数据保存的位置,即/var/www/html/
中。
|
|
分的更细一点,可以添加三项,设置命令:
|
|
外部数据库配置有几种方法,第一个是使用Owncloud自己提供的OCC工具(OwnCloud Console)来配置,使用docker exec执行
:
另外的就是用Docker的工具了,Docker Stack或者Docker Compose来配置。首先需要编辑一个yml配置文件,如stack.yml
或compose.yml
,名字随便起,然后加入数据库配置:
最后运行docker stack deploy -c stack.yml owncloud (or docker-compose -f compose.yml up)
即可。
此时可以发现有两个容器正在运行:
然后在浏览器输入http://localhost:8080
可以看到登陆界面,登录信息填yml文件中的信息就行。
登陆成功就可以看到登陆界面了。然后就可以网页上传下载了,还可以生成分享链接。上传下载地址位于挂载的目录中。没有机子也可以去试试VPS,自己搭一个私有云盘用来平时备份下载。
]]>其实这已经是第二篇博文了,第一篇写了很多废话,因为再重装系统换上了Ubuntu18.04之后,发现怎么都没法配好博客环境。很多东西都在变化,系统升级了,nodejs升级了,hexo升级了,连next啥的都升级了,当初在里面乱改插件的,现在对于重建是已经近乎绝望的心态了。
但是喜欢挣扎,删了该博客目录,重新从以前备份的博客仓库克隆下来重新搭建,还是搭不好,之前写好的文章,也不小心随着那个博客目录涅槃了。后来尝试了使用Docker来重现当时的博客环境,花了些时间,但好在成功了,以后也不必再因为博客环境再花费太多时间了。
笔记本重装了Ubuntu18.04,首先是改了下基佬紫的登陆界面,毕竟这种颜色陪伴了我太多年,学校的校花貌似都是这种颜色,毕竟一直看着也很无聊,其实这种小事,网上教程挺多的。因为Ubuntu18.04拥抱了Gnome,里面的很多登陆或者开机都是以样式文件存储,是挺方便改的了,但目前只想改这个,其余的以后再说。
由于gnome的缘故,采用了css样式文件保存登陆界面样式,所以只需要简单修改/etc/alternatives/gdm3.css
或者/usr/share/gnome-shell/theme/.ubuntu.css
文件即可。这两个字虽然位置不同,实际上是一个文件,对其中一个的修改会立刻反映到另一个文件上,如果同时打开,则会有下面的警告:
主要就是修改#lockDialogGroup
的样式了,可以改成想要的颜色,也可以换成喜欢的图片,随便都行。
另外就是安装输入法了,还是习惯用的搜狗输入法,毕竟Linux下没几个好用的,安装完之后再语言管理栏添加以下语言和输入法就可以用了,没太多要说的。
记得取消Only Show那个选项,然后添加就行。
然后安装主题,还是用tweak工具,去gnome主题页面选择喜欢的下载下来应用就行了。
还有个小问题就是,安装了Ubuntu 18.04之后,截图工具有点问题了,每次截完图直接保存了,没有跳出复制还是保存的窗口,不过好在也不是什么大问题,以后用快捷键就可以解决。
回来发现站点统计也挂了,后来去不蒜子页面看了下,发现了这样一段话:
因七牛强制过期『dn-lbstatics.qbox.me』域名,与客服沟通无果,只能更换域名到『busuanzi.ibruce.info』!
那没办法了,只好将原来不蒜子插件里的js源地址改一下。
还有Vim手动编译以支持Python,Tmux手动编译,Powerline设置,等等,还是重复以往的工作,都挺无聊的,不过好在发现了一个有趣的网址,数字之门,可以免费提供各种云端镜像,有点像Docker,比如想学习Linux,各种化学分子软件,以及TensorFlow之类的软件,里面都有现成的镜像,不用自己配环境,适用于练习。
具体扩展部分可以参看Gnome的wiki页面,主要了解了一下它的透明效果:LookingGlass。可以使用JavaScript语言控制gnome-shell的界面。
Ubuntu 18.04 Gnome支持Alt F2快速启动命令,
然后输入lg即可打开界面。
其中第一个是Evaluator,就是脚本执行界面,里面可以直接运行JavaScript,可以使用tab补全。第二个是Windows,里面会显示当前电脑里的所有窗口,用鼠标点击可以看到。剩下一个就是它的扩展了,里面会列出当前系统所安装的所有扩展,有错的话也会显示出来。
另一个问题就是使用Google学术以及其他完全不相关的事了。目前ss安全性已经大不如前了,主要还是ssr,有两种方式安装。
|
|
配置
|
|
里面填上服务器的信息,然后使用ssr start
,再在系统或者浏览器中配置代理端口即可。
去项目页面安装
然后使用dpkg安装,再用apt修复一下,基本是可以打开的,和win版本的操作类似,可以添加订阅地址。
现在兴起的另一种加密方式,可见其项目页面,下载解压
|
|
编辑config.json文件,之后运行
|
|
直接运行程序,V2Ray默认会在当前文件夹寻找名为 config.json 的配置文件并运行。
或者移动到系统文件夹下运行:
|
|
不过我放弃了,不知道是不是系统版本原因,目前该项目问题也比较多,暂时求稳。
其他的vpn
主要如Windscribe,跨平台vpn,Linux版本下载地址
|
|
不过免费的速度都不怎么样,试过也放弃了。
如果有账号的话,直接安装openvpn就可以了:
|
|
另外,对于有IPv6的代理来说,可以设置浏览器的优先级。如Firefox设置IPv6优先:
输入about:config
,并找到以下两项做相应修改:
|
|
Ubuntu里新起的一种软件包管理方式,好像在16.04中开始引入。命令方式与apt类似,管理方式与docker容器类似,各个应用程序之间相对独立。与apt管理方式相比,s可以较好解决了应用之间的依赖问题便于管理,另一方面会占用较多磁盘空间,当apt方式无法下载所需的应用时,也可以选择使用snap方式下载。
最后说说被博客折腾得心力交瘁时候的救星,Docker是一个容器技术已经不想多说,主要是环境隔离且独立的特点,非常适用于各种项目的平台迁移。已经不用再在自己电脑上安装nodejs和nvm来惹这些麻烦了,毕竟是好事。
主要方式是通过努力回忆,记起当时的各个软件的版本,然后用同样的系统去重建一遍,基本可以成功。由于npm和各个插件的版本已经在package.json
和package-lock.json
文件里了,按照里面的版本,利用nvm安装,然后删掉node_modules
文件夹,使用npm重新安装即可跑起来。
关于Docker的镜像构建,可以通过Docker commit
和Dockerfile文件两种方式完成,前者可以直接从tty中构建,后者则可以实现自动部署。Docker教程网上太多,官方的也简明易上手,不知道有没有再写一篇关于Docker的笔记,而且网上也有很多已经写好了的镜像,是可以直接用的。
最后,使用-p
映射端口后,用-v
挂载博客目录,然后写了这篇文章来做个测试。
终于找到家了~
]]>之前已经说过,pad相当于一个接口,允许信息通过或者离开一个元素。Pad的功能(Capabilities或者简写为Cap)指定哪种信息可以通过pad。例如“分辨率为320x200像素,每秒30帧的RGB视频”,或“每采样音频16位,每秒44100采样率的5.1声道”,甚至压缩格式如mp3或h264等。
Pad可以支持多种功能(如一个video sink可以支持不同类型的RGB或者YUV格式的视频),而且功能也可以指定范围(如一个audio sink可以支持每秒1-48000个采样的采样率)。但从pad到pad的实际信息必须仅有一个明确指定的类型。两个相连的pad通过一个协商过程达到一个共同的类型,从而使其pad功能变得固定(仅有一种类型,不包含范围)。
要让两个元素连接在一起,他们的能力必须有一个公共子集,否则不可能相互理解。这也是能力的主要目标。
应用程序开发人员通常会通过将元素连接到一起来构建管道(如果使用playbin之类的全部元素,则程度较低)。在这种情况下需要知道元素的Pad Caps,或者至少知道当GStreamer拒绝连接两个协商错误的元素时它们的Caps是什么。
Pad是从pad模板创建的,pad模板表示了pad可能具有的所有功能。模板可用于创建一些相似的pad,并且允许早期拒绝元素之间的连接:如果其pad模板的功能没有公共子集(相交为空),则无需进一步协商。
pad模板可视为协商过程第一步。随着过程的演变,实际的Pads被实例化并且其能力被提炼,直到它们被固定(或者协商失败)。
一个能力的例子如下:
如代码中所示,该pad是一个接收端(sink),且一直可用。它支持两种媒体,包括整数格式的原始音频(audio/x-raw):带符号的16位小端 1和无符号8位。 方括号表示一个范围:例如,通道数从1到2不等。
video/x-raw表示这个信号源输出原始视频。它支持多种尺寸和帧率,以及一组YUV格式(大括号列表表示)。所有这些格式都表示图像平面的不同的填充和下采样。
可以使用gst-inspect-1.0
工具了解所有GStreamer元素的Caps。
一些元素查询底层硬件支持的格式,并相应地提供他们的pad功能(通常进入READY或更高状态时在这样做)。 因此,显示的能力可能因平台而异,也可能从一次执行到下一次执行就变了(即使这种情况很少)。
本次将会实例化两个元素(这次通过其工厂函数),显示其pad模板,连接它们并设置管道播放。 在每次状态改变时,sink元素的Pad的功能会被显示,所以可以观察到协商如何进行直到Pad Caps固定。
一个普通的有关pad caps的例子如下:
所需要的库只有gstreamer-1.0
。
print_field
, print_caps
和print_pad_templates
简单的以友好的格式显示了caps的结构,更多的GstCaps内部结构组织可以阅读GStreamer的关于Pad Caps的文档。
gst_element_get_static_pad
函数从给定的元素中检索命名的pad。这个pad是静态的,因为它总是存在于元素中。有关Pad可用性的更多信息,可以阅读有关Pads的GStreamer文档。
然后调用gst_pad_get_current_caps
来检索pad的当前caps,它可以是固定的或不固定的,具体取决于协商过程的状态。甚至可能不存在,这种情况下调用gst_pad_query_caps
来检索当前可接受的pad caps。当前可接受的caps将成为处于NULL状态的pad模板的caps,但可能会在以后的状态中更改,因为可能会查询实际的硬件功能。
然后将这些功能打印出来:
之前直接使用gst_element_factory_make
函数创建了元素,并跳过了关于工厂的讨论。GstElementFactory负责实例化由其工厂名称标识的特定类型的元素。
可以使用gst_element_factory_find
来创建一个类型为“videotestsrc”的工厂,然后通过gst_element_factory_create
使用它实例化多个“videotestsrc”元素。gst_element_factory_make
实际上是gst_element_factory_find
+gst_element_factory_create
的快捷方式。
Pad模板已经可以通过工厂来访问,所以一旦工厂被创建,它们就会被打印出来。
跳过管道的创建以及开始,转到State-Changed消息处理:
该段代码在每次Pipeline状态发生改变时简单的打印出当前的pad caps。可以在输出中看到初始caps(pad模板的caps)是如何逐步完善直至完全固定(包含无范围单一类型)。
这里将使用GTK+工具包构建媒体播放器,这些概念亦适用于其他工具包如QT。
关键是告诉GStreamer将视频输出到所选择的窗口。具体机制取决于操作系统(或者窗口系统),但GStreamer为平台独立性提供了一个抽象层。这种独立性来自GstVideoOverlay接口,它允许应用程序告诉视频接收器(sink)应该接收渲染的窗口的处理程序。
Gstreamer所使用的是GObject 接口。GObject的接口是元素可以实现的一组函数,包括GstVideoOverlay等。具体介绍如下:
A GObject interface (which GStreamer uses) is a set of functions that an element can implement. If it does, then it is said to support that particular interface. For example, video sinks usually create their own windows to display video, but, if they are also capable of rendering to an external window, they can choose to implement the GstVideoOverlay interface and provide functions to specify this external window. From the application developer point of view, if a certain interface is supported, you can use it and forget about which kind of element is implementing it. Moreover, if you are using playbin, it will automatically expose some of the interfaces supported by its internal elements: You can use your interface functions directly on playbin without knowing who is implementing them!
另一个问题是,GUI工具包通常只允许主(或应用)线程来操作图形“小部件”,而GStreamer通常会派生多个线程来处理不同的任务。从回调函数中调用GTK +函数通常会失败,因为回调函数在调用线程中执行,并不需要在主线程中。这个问题可以通过回调函数在GStreamer总线上发布消息来解决: 主线程接收消息并做出相应反应。
这里已经注册了一个handle_message函数,每次在总线上出现一条消息时都会调用这个函数,这迫使我们解析每条消息,看看我们是否对其感兴趣。本例中使用了一种不同的方法来为每种消息注册一个回调,所以解析更少,代码更少。
一个简单的基于playbin的带GUI的媒体播放器如下:
Required libraries: gstreamer-video-1.0 gtk+-3.0 gstreamer-1.0
所以此时编译需加上pkg-config --cflags --libs gstreamer-video-1.0 gtk+-3.0 gstreamer-1.0
参数获取所需的头文件和库文件。
如果提示找不到gtk+-3.0,则安装。sudo apt install build-essential libgtk-3-dev
提示未安装gstreamer-video-1.0,则安装。sudo apt install libgstreamer-plugins-base1.0-dev
该例将会打开一个GTK+窗口并显示一个伴有音频的电影。媒体来自于互联网,所以窗口可能需要几秒才能显示,具体取决于网速。该窗口有一些按钮来暂停、停止和播放电影,还有个滑块显示当前位置,可以拖动或者改变它。此外,关于流的信息显示在右边的一列上。
本例中,函数不再在使用之前定义,代码呈现的顺序也不总是和程序顺序相匹配。
首先需要注意的是,现在并不是与平台完全无关的了,因为我们需要为所使用的窗口系统包含适当的头文件。幸运的是,没有那么多支持的窗口系统,所以X11 for Linux,Win32 for Windows和Quartz for Mac OSX这三行足够了。本例主要有回调函数组成,这些回调函数将从GStreamer或GTK+中调用。所以先看一下主函数,其中将会用到所有的回调函数。
|
|
标准的GStreamer和playbin管道创建,以及GTK+初始化。
我们希望在流上出现新标签(元数据)时收到通知,为了简单起见,将处理来自相同回调函数tag_cb
的所有种类标签(视频、音频和文本)。
然后创建GUI:
所有的GTK+部件创建和信号注册都发生在这个函数中,它只包含GTK相关的函数调用,所以可以跳过它的定义。其所注册的信号传递用户命令,如下面在查看回调时所示。
其中,gst_bus_add_watch
函数用于注册用于接收所有的消息并发送给GStreamer总线。可以通过使用信号来达到更精细的粒度,这使得我们仅注册感兴趣的消息。
通过调用gst_bus_add_signal_watch
函数,我们指导总线在每次收到一个消息时发出一个信号。信号名称是message::detail
,其中‘detail’是触发信号发出的消息。例如,当总线接收到EOS消息时,将发出一个名为message::eos
的信号。
例中仅使用信号描述(detail)来注册所感兴趣的消息。如果我们注册了一个消息的信号,我们将收到每个消息的通知,如gst_bus_add_watch
函数所做的一样。
为了使“bus watches”工作(无论是gst_bus_add_watch
还是gst_bus_add_signal_watch
),必须运行GLib主循环。这种情况下,它隐藏在GTK+主循环中。
在将控制移交给GTK+之前,使用g_timeout_add_seconds
函数来注册另一个回调函数————超时,且每秒会被调用:用其从refresh_ui
函数刷新GUI。
在这之后,我们完成了建立并启动GTK+主循环。感兴趣的事件发生时,将从回调函数中重新获取控制权。每个回调函数都有不同的签名,具体取决于调用者。可以再信号的文档中查找签名(参数的含义和返回值)。
在应用程序生命周期的这一点上,我们知道GStreamer应该呈现视频的窗口句柄(无论是X11的XID,Window的HWND还是Quartz的NSView)。我们只需从窗口系统中检索它,并使用gst_video_overlay_set_window_handle
通过GstVideoOverlay接口将其传递给playbin。playbin将定位视频接收器并将处理程序传递给它,所以它不会创建自己的窗口并使用它。playbin和GstVideoOverlay将此过程简化了许多。
这三个回调函数是关于GUI的播放,暂停和停止按钮的,它们只需要将管道设置为相应的状态即可。值得注意的是,在STOP状态下,将管道状态设置为READY。可以将流水线一直带到NULL状态,但是会导致过渡慢一点,因为有些资源(如音频设备)需要重新释放重新获取。
gtk_main_quit
最终会在main中调用gtk_main_run
来终止,并在这种情况下完成整个程序。这里,在停止管道(只是为了整洁)后,当主窗口关闭时调用它。
|
|
当有数据流时(处于PAUSED和PLAYING状态),视频接收器负责刷新视频窗口的内容。但其他情况下不会这样,所以必须我们自己来做: 例中我们是使用一个黑色的矩形填充窗口。
|
|
通过GStreamer和GTK+的协作,可以非常容易地实现一个复杂的GUI元素,如一个搜索条(或者允许搜索的滑块)。如果滑块被拖动到新位置,则告诉GStreamer使用gst_element_seek_simple
查找该位置。 滑块已经设置,它的值代表秒。
值得注意的是,一些性能和响应可以通过不去响应所有的单个用户的搜索请求来获得。由于搜索操作需要花费一些时间,所以在允许另一个搜索操作之前,更好的办法是等待一会(如半秒钟)。否则,如果用户疯狂的拖拽滑动条,应用程序看起来可能也没有响应,因为在一个新的搜索操作在队列中之前将不会允许任何搜索。
|
|
该函数将移动滑块以反映媒体当前的位置。如果我们不处于PLAYING状态,那么在这里没有任何事情可做(位置和持续时间查询通常会失败)。
|
|
可以设置滑块的范围以防我们在不知情的情况下恢复clip的持续时间。
|
|
查询当前的管道位置,并根据滑块设置其位置。这将会触发一个value-changed
信号,我们可以通过其知道用户在拖动滑块。除非用户请求它们,否则我们不希望发生这种情况,所以在此操作期间,使用g_sinal_handler_block
和g_signal_handler_unblock
禁用value-changed
的信号发出。
该函数返回True将在之后保持其调用。如果返回FALSE,定时器将被删除。
|
|
这里是该例的重点。当媒体中发现新标签时,该函数将会从streaming线程中调用,即从一个应用程序线程(或主线程)之外的线程调用。我们这里希望做的是更新GTK+的部件来反映这个新的信息,但GTK+不允许主线程之外的其它线程的操作。
解决方法是让playbin在总线上发布消息并返回给调用线程。在适当时候,主线程会接收到这个消息并更新GTK。gst_element_post_message
函数使GStreamer元素将给定的消息发送到总线。gst_message_new_application
函数创建一个新的应用程序类型的消息。GStreamer消息有不同的类型,且这种特殊类型将保留给应用程序:它会通过不受GStreamer影响的总线。
类型列表可在GstMessageType文档中找到。
消息可以通过嵌入的GstStructure提供额外的信息,GstStructure是一个非常灵活的数据容器。这里使用gst_structure_new
创建一个新结构体,并将其命名为tags-changed,以避免在我们想发送其它应用程序消息时发生混淆。
然后,一旦在主线程中,总线将会收到这个消息并发送message::application
信号,该信号与application_cb
函数关联:
一旦确定它是标签变化(tag-changed)消息,则调用analyze_streams
函数。其基本上从流中恢复标签,并将其写入GUI中的文本小部件中。
虽然该例代码量较大,但所需的概念很少且很容易。
最后效果图如下:
GstQuery是GStreamer中用于查询element和pad信息的一种机制。此篇所用例中首先需要询问是否支持寻找(seek),因为有一些源,如live stream,并不支持跳转。本例在确定支持跳转后,一旦电影播放10s后,就是用seek函数跳转到一个不同的时间点。
在之前的例子中,一旦建立其了Pipeline并开始运行,主函数所做的事仅仅是坐等接收来自总线(bus)的ERROR或者EOS信息。这里将会修改这个函数来周期性的唤醒并查询Pipeline的位置,所以可以将其输出在屏幕上。有点类似于一个媒体播放器定期更新用户接口。
最终,在stream持续时间改变后就会重新查询和更新。
一个有关seeking时间点的示例如下:
|
|
该段代码会打开并显示一个伴有音频的一个电影,由于媒体来自于网络,所以窗口可能需要一会才能显示出来,具体取决于网速。并在电影十秒钟后跳转到一个新的位置。
首先建立一个可以传递给其他函数的含有所有信息的结构体:
|
|
这里由于信息处理代码会变得越来越大,因此将其移到了handle_message
函数中。
然后建立了一个包含单个元素(playbin)的Pipeline,然而playbin本身就是一个Pipeline,而且这种情况下,他是Pipeline中唯一的element,所以直接使用playbin。
这里跳过一些细节:该clip的URI通过URI属性给playbin,并将Pipeline设置为播放状态。
|
|
之前没有给gst_bus_timed_pop_filtered
函数提供超时参数,因此它在收到消息前不会返回。这里使用100ms的超时,所以如果在0.1s内没有收到任何消息,函数将返回NULL,并通过这个方法来更新UI。
需要注意的是,所有的超时时间必须指定为GstClockTime,所以都是以纳秒为单位的,表示不同时间单位的数字应该乘以宏如GST_SECOND或GST_MSECOND。也能使代码更具可读性。
如果收到消息,则通过handle_message
函数处理它。否则刷新用户接口(UI)。
|
|
如果Pipeline处于PLAYING状态,则刷新屏幕。在非PLAYING状态下我们不想做任何事,因为大多数查询都会失败。
这里的刷新率大约是每秒10次,对于我们的UI来说已经足够。同时将在屏幕上打印出当前媒体的位置以便了解管道查询。这涉及到几个步骤,之后再说,但是位置和持续时间是比较常见的查询,所以GStreamer提供了更容易的现成的备选方案:
|
|
其中,gst_element_query_position
函数隐藏了查询对象的管理并直接提供结果。
其中,gst_element_query_duration
用于函数查询流的长度。
这里使用GST_TIME_FORMAT
和GST_TIME_ARGS
宏来提供对GStreamer时间的对用户友好的表示。
|
|
现在在管道上调用gst_element_seek_simple
函数进行查找,这种方法的好处是隐藏了许多复杂的问题。
GST_FORMAT_TIME: 表示以时间单位指定目标位置,其他的查找格式使用不同的单位。
然后是一些GstSeekFlags,其中常见的一些如下:
handle_message
函数通过管道总线(Pipeline’s bus)处理接收到的所有信息。ERROR和EOS处理之前已经说过了,所以直接跳到感兴趣的部分:
该消息在流的持续时间变化时会发送给总线。这里简单地将当前持续时间标记为无效,所以稍后会被重新查询。
在PAUSED和PLAYING状态下,搜索和查询操作通常只会得到一个有效的回复,因为所有元素都有机会接收信息并进行自我配置。这里使用playing变量来跟踪管道是否处于PLAYING状态。如果刚刚进入了PLAYING状态,则执行第一次查询。然后询问Pipeline是否允许在此流上进行搜索:
这里gst_query_new_seeking
函数使用GST_FORMAT_TIME格式创建了一个新的“seeking”类型的查询对象。这表明我们有兴趣通过指定想要移动的新时间来寻找。也可以使用GST_FORMAT_BYTE格式,然后在源文件中查找特定的字节位置,不过通常不太实用。
然后gst_element_query
函数将查询对象传递给Pipeline,并将结果存储在同一个查询中,因此可以通过gst_query_parse_seeking
函数方便的检索。它提取出一个表示是否允许查询的布尔值和可查找的范围。
最后在完成查询后释放查询对象。
通过这些过程基本上可以建立一个媒体播放器,根据当前流的位置定期更新一个滑块,并允许通过滑块进行搜索或跳转。
本次尝试: 将Pipeline在其未完全建立起来时设置为Playing状态。虽然这并没有什么问题,如果不做任何动作,当数据到达Pipeline末端时将会由Pipeline产生一个error并停止,所以尝试会采取进一步的操作。尝试打开一个多路复用(muxed)的文件,即视频和音频存在一个容器文件中。负责打开该容器的元素称之为分离器(demuxers)。容器格式例如: MKV(Matroska), QT/MOV(Quick Time), Ogg或高级系统格式如ASF, WMV, WMA等。
如前所述,pad就是Gstreamer元素间互相通信的一个接口,也有人翻译为衬垫。数据通过sink pad流入,通过source pad流出。只包含source pad的称之为source元素,只包含sink pad的元素称为sink元素,两者兼有则称之为filter元素。如图所示:
如果一个容器嵌入多个流(例如一个视频和两个音频轨道),则分离器将分离它们并将其展示于不同的输出端口。通过这种方式,可以在流水线中创建不同的分支,处理不同类型的数据。
一个含有两个source pad和一个sink pad的分离器的例子如图所示:
一个动态的HelloWorld示例代码如下:
该示例代码仅播放音频,由于是在线媒体,所以连接速度会与网速有关。
首先定义了一个结构体:
简单情况下,通常可以使用一个局部变量(一个GstElement类型的基本指针)来表示所需要的信息。但大多数情况下(包括该例)是涉及到回调的,所以将其放在一个结构体中以便处理。
此处是一个添加pad的前置函数声明。
该段是创建Element的代码。其中:uridecodebin
通过将uri转化为原始音频或视频流来实例化所有的必要Element(source, 分离器,解码器),其所做的是playbin的一半。由于含有分离器,其source pads最初并不可用,而且我们需要随时将其连接起来。audioconvert
对于转换不同格式的音频很实用,由于解码器生成的格式可能和audio sink期望的不一样,所以为了确保其可以在任何平台上工作,使用audioconvert
进行转换。autoaudiosink
在音频中类似于视频中的autovideosink
,其将会把音频流送给声卡。
|
|
此处主要作用是将转换元素连接到sink,但是由于其不含source pads,所以没有将其连接到source上,仅仅是保持该分支(转换器+sink)为未连接状态,待后续处理。
|
|
这里通过设置文件uri属性方法来播放它们。
GSignals是GStreamer中的一个关键部分。当一些你感兴趣的事发生时,它允许你通过回调的方式获得通知。信号(Signals)由名称标识,且每个GObject都有自己的signals。
该行代码将一个”pad added”信号附加到我们的source(一个uridecoderbin元素)上。为此使用了g_signal_connect
函数,并提供要使用的回调函数pad_add_handler
和一个数据指针。GStreamer并未对数据指针做任何事,仅仅将它转发给回调函数,因此可以与其共享信息。在这种情况下,我们传递一个指向我们专门为此建立的一个结构体CustomData的指针。
GStreamer产生的信号可以通过其文档或者使用gst-inspect-1.0
工具查询。
至此已经准备好了,只需要将Pipeline设置为PLAYING状态并开始监听总线(bus)上感兴趣的Message(如ERROR或EOS)。
当source element有了足够的信息开始产生数据时,将会创建source pads,并触发“pad-added”信号。此时,回调函数就会被调用:
|
|
其中,
信号处理的第一个参数始终是触发它的对象。src是触发这个信号的GstElement。此例中,它只能是uridecodebin
,因为它是我们唯一附加的信号。
new_pad是刚刚添加到src element的GstPad, 通常是我们想要连接的pad。
data是我们附加到信号时提供的指针,例中用其传递CustomData指针。
|
|
从CustomData中提取转换元素,然后使用gst_element_get_static_pad
函数取回其sink pad。这是我们希望连接到new_pad的pad。之前涉及的简单例子中直接将元素连接到元素,并由GStreamer选择合适的pad。现在我们直接将这些pad连接起来。
|
|
uridecodebin
可以创建尽可能多的pad,而且每个pad都会调用这个回调函数。该行代码主要作用是阻止我们尝试连接到已经连接了的pad上。
|
|
现在将会检查这个新pad要输出的数据类型,因为我们仅对产生音频的pad有兴趣。之前已经创建了一个处理音频的Pipeline(一个autoaudioconver
连接到一个autoaudiosink
),例中我们将不能将其连接到产生视频的pad上。gst_pad_query_caps
函数查询或检索pad的功能(这是一种它所支持的数据,封装在GstCaps结构体中),一个pad可以提供许多功能(cap),因此GstCap可能包含多个GstStructure,且每个表示不同的功能。
由于此例中我们知道我们想要的pad只有一个能力(audio),所以使用gst_caps_get_structure
函数获取第一个GstStructure。
最后使用gst_structure_get_name
函数获取包含格式(实际是媒体类型)的主要描述的结构体名称。如果名称不是audio/x-raw
,这就不是解码的音频pad,也不是我们所感兴趣的。否则,尝试连接:
|
|
其中,gst_pad_link
函数尝试连接这两个pad。和gst_element_link
函数一样,连接必须指定有source到sink,且这两个pad必须属于同一个bin(或Pipeline)中的元素。
至此已经基本完成,当出现一个正确类型的pad时,它将会被连接到音频处理Pipeline的其余部分,并执行且继续直到遇到ERROR或者EOS。但关于GStreamer的状态还是需要重申一下。
之前,已经解释过GStreamer的状态了,一共四种,如下所示:
State | Description |
---|---|
NULL | the NULL state or initial state of an element. |
READY | the element is ready to go to PAUSED. |
PAUSED | the element is PAUSED, it is ready to accept and process data. Sink elements however only accept one buffer and then block. |
PLAYING | the element is PLAYING, the clock is running and the data is flowing. |
四种状态只能在相邻状态之间移动,不能直接从NULL
跳到PLAYING
,必须经过中间的READY
和PAUSED
状态。但是如果将管道设置为PLAYIING
状态,GStreamer将会为你进行中间转换。
|
|
此段代码的添加主要用于监听关于状态更改的总线消息(bus message),并将其打印在屏幕上以帮助了解这个转换。每个元素都将关于其当前状态的信息放在总线上,因此我们将其过滤出来,并只收听来自Pipeline的信息。
大多数应用程序只需要关心去PLAYING
来开始播放,然后PAUSED
来暂停,然后在程序退出时返回NULL
来释放所有资源。
基本代码如下:
|
|
编译时候可以通过pkg-config命令查询所需要的头文件和库文件,关于pkg的方法前面已有叙述,地址在此。
其基本流程图如下:
在初始化GStreamer后,首先需要创建元素,如上所示代码中的:
|
|
使用gst_element_factory_make
函数创建,该函数第一个参数是需要创建的元素类型,第二个参数是给这个元素实例的名称,如果为空,GStreamer会自动生成一个特有的名称。
此处创建了两个元素:videotestsrc
和autovideosink
。其中,videotestsrc
属于source元素,通常用来产生或提供数据,经常用来创建一个测试用的模型。该元素在debug模式下或者教程中用得较多,实际应用中鲜有所闻。autovideosink
属于sink元素,用于接受或者消费数据,将其接收到的图像展示在窗口中等。程序可以有多个video sink,这通常取决于操作系统。autovideosink
会自动选择并实例化最好的一个,所以不用担心实现细节,代码对于平台是比较独立的。
创建了Element后则需要创建Pipeline,如上所示代码中的:
|
|
所有元素在使用前必须包含进一个Pipeline,因为需要关心其时钟和Message功能。通常使用gst_pipeline_new
创建管道。
|
|
Pipeline也是一种bin,一种特殊的bin,是一种包含了其他元素的元素。因此所有对bin适用的方法对Pipeline也同样适用。这里通过gst_bin_add_many
函数添加多个元素到Pipeline,该函数接受多个元素,并添加到Pipeline中,以NULL结束。单个元素添加可使用gst_bin_add
函数。
然后就需要将这些元素连接起来,因为虽然添加进了管道,但只是说明了管道中元素的位置,并没有将各个元素连接起来,数据无法流动。这里通过gst_element_link
将各个元素连接起来,该函数第一个参数是源元素,第二个参数是链接的目标元素,连接必须遵照数据流动方向建立。
只有在同一个bin中的元素才能连接在一起,所以在连接之前必须先将其添加进Pipeline中。
如上代码中修改source的属性中的一段:
该行代码改变了videotestsrc
元素的pattern属性,控制了测试视频元素的输出类型。
绝大多数GStreamer元素都可以自定义其属性:可以通过修改名称属性来改变元素行为(可写属性),或者通过查询来获取元素内部状态(可读属性)。
通常使用g_object_get
函数获取属性,通过g_objece_set
函数设置属性。g_object_set
函数接受一个以NULL
结尾的属性名-属性值列表,所以可以一次性改变元素的属性。
Gstreamer元素都是一种特殊的GObject(GLib对象系统,提供属性设备的实例),所以属性处理方法都有一个带g_
的前缀。
所有元素的可用属性名和属性值可以通过gst-inspect工具获取。
剩余代码则是进行错误检测以增加程序的鲁棒性。如:
在播放时候通过gst_element_set_state
函数返回值来检测错误。
再如:
其中,gst_bus_timed_pop_filtered
函数等待执行结束并返回一个GstMessage。此处该函数在遇到错误或者到EOS状态时会返回,所以需要检测是什么原因导致函数返回的,因此通过下面的if语句对msg进行判断。
GstMessage是一个非常通用的结构,可以提供几乎任何类型的信息。而且,GStreamer为每种消息提供了一系列的解析函数。
通过使用宏定义函数GST_MESSAGE_TYPE
可以知道Message包含的错误,然后通过gst_message_parse_error
函数返回一个GLib Error的error结构体和一个字符串用于调试。
GStreamer总线(bus)是一个简单的系统,负责将由元素生成的GstMessages传递给应用程序的对象,以及应用程序线程。实际的媒体流是在另一个线程中完成的,而不是应用程序。
Message可以通过gst_bus_timed_pop_filtered()
函数和其兄弟姐妹同步获取,也可以通过signal异步获取。应用程序应该始终关注总线,去获取错误以及其他回放相关的问题。