记一次从AviSynth到VapourSynth的迁移(2):感受不同的API 下

距离写上一篇博文已经过去几天了(事实上那篇博文在更早的几天就写了草稿)。这几天学到了不少东西,硬塞到上一篇博文中好像不太合适;而且上一篇博文就是在思路不太清时写,有点混乱。总之,干脆新写一篇。

为了加强理解、尽量全面,可能会有些重复的话。

srcpdstp的理解

约定俗成的变量命名,src已经习惯,是输入,而dsp则是结果。

关于srcpdstp,通常这样定义

1
2
const T* srcp = reinterpret_cast<const T*>(vsapi->getReadPtr(src, plane));
T* VS_RESTRICT dstp = reinterpret_cast<T*>(vsapi->getWritePtr(dst, plane));
  1. getReadPtr()getWritePte()均是uint8_t *型,为满足不同位深的输入输出,也就是能够给不同类型的变量赋值,用reinterpret_cast做类型转换,转换为模版中的T
  2. 需要注意到,srcpdstp已经包含了平面信息,或者说代表了特定平面,在滤镜核心处理函数中,用for循环遍历各个平面,for循环内就会基于srcpdstp,对单个平面做处理。
  3. 注意srcpdstp是指针,这个在稍后会继续说。

stride的理解

avs api和vs api的一大区别,就是隐藏在习惯变量名不同(pitchstride)之下的单位问题。

从Doc上看,avs api获取的pitch和vs api获取的stride都是以字节为单位。在使用中,vs框架下要除以变量所占的字节,即

1
int dst_stride = vsapi->getStride(dst, plane) / sizeof(T);

但我读过的几份avs滤镜代码,都是直接用的,没有除sizeof

突然明白,我忘了一个事情,**sizeof(uint8_t) = 1啊**,所以不需要除sizeof,是不是以字节为单位,都是一样的啊。

疑问的解释

我一直在纠结下面的avs与vs代码会不会有区别。

1
2
3
4
5
6
// avs
srcp += src_pitch;
dstp += target_width;
// vs
srcp += src_stride;
dstp += target_width; // dstp += dst_stride;

但其实这个问题是不存在的。对于avs,基于全程8bit的假设,sizeof = 1,虽然src_pitch是以字节为单位,但数值上与target_width是同级的;对于vs,src_stride是除过sizof的,与target_width单位一致。

所以无论vs怎么写,与avs都是一致的,且srcpdstp数值都是同级的。

stridedstp的共同作用

类似dstp += dst_stride;这样的语句,实现了指针的移动,让继续处理下一帧?是处理同列中个下个像素。

dstp[y + x * dst_stride]应该是二维平面上的指针移动。这话也没有错,但有种误导的感觉,其实和上面是一个意思,只不过上面单独处理列,这个是行和列同时处理。

详情在另一篇博文里写了。

VapourSynth数据结构的猜测

(有人说编程是算法+数据结构,现在终于能理解到这句话了)

简单地说,是像一维数组那样横着排的?对dstp加上一个width就到下一帧?

但这样想,上面那样的dstp[y + x * dst_stride]又说不通了。

void函数做了什么

这个问题看上去很蠢,但也许是我上半年写Python留下的后遗症。总以为函数后要return个什么东西,仿佛把封装好的函数当成一个计算黑箱。这样理解也谈不上错吧,但却让我在一开始读vs滤镜代码时一头雾水。

经过了一个void函数,函数内的全局变量(或者是生存期大过该函数的变量)已经被改变。简单地说,就是“酒肉穿肠过,该变的都变了”的意思。

vsapi的理解

可以将滤镜分为两类:一类是resize型滤镜,输入输出的尺寸不一致;一类是非resize型滤镜,输入输出的尺寸一致。

对于后者,在初始化函数FilterInit()中,只需原样写下vi即可

1
2
3
4
5
static void VS_CC FilterInit(VSMap* in, VSMap* out, void** instanceData, VSNode* node, VSCore* core, const VSAPI* vsapi)
{
FilterData* d = static_cast<FilterData*>(*instanceData);
vsapi->setVideoInfo(d->vi, 1, node);
}

对于前者,要在初始化中将尺寸改写为输出尺寸

1
2
3
4
5
6
7
8
static void VS_CC FilterInit(VSMap* in, VSMap* out, void** instanceData, VSNode* node, VSCore* core, const VSAPI* vsapi)
{
FilterData* d = static_cast<FilterData*>(*intanceData);
VSVideoInfo dst_vi = (VSVideoInfo)* d->vi;
dst_vi.width = d->output_width;
dst_vi.height = d->output_height;
vsapi->setVideoInfo(&dst_vi, 1, node); // 注意d->vi是指针,这里要取地址
}

这样就保证了输出的dst是resize后的尺寸,否则照上面那样写,(与输入源不一样的尺寸)会报错。

写法不是唯一的,也可以在FilterCreate()函数里写,只是我还不太会…

遇事不决用double

emmm困扰了我一整天的颜色问题,把函数内部处理全变成double,直到输出时再改成所需的类型,颜色终于正常了。

现在毕竟还在起步阶段,先把能正常运行的程序写出来,再考虑提升性能的事情。

关于编译与调用

要保持编译时用的头文件与VapourSynth SDK文件夹中的头文件一致,否则无法调用。

(所以在配置编译文件时需要动点脑子)

C++的基础

进一步理解C++的类型转换

编译相关

两次编译的结果,hash不会一样的,但二进制文件大小一样。

零散的知识点

递归函数(调用了自身的函数)没法内联。

静态函数(用static关键字声明)不可在文件外被调用。