汗……版本差距……这我倒没想过…………
"changefps不对帧的内容作改动只是靠那个公式改变了返回的帧数n"这句话我说的不准确,应该是"changefps不对帧的内容作改动,而只是靠那个公式改变了返回的帧的选取"。
------------------------------------------------
我先来说说avs滤镜的构造吧,以changefps为例:
首先:
AVSFunction Fps_filters[] = {
{ "AssumeFPS", "ci[]i[sync_audio]b", AssumeFPS::Create }, // dst framerate, sync audio?
{ "AssumeFPS", "cf[sync_audio]b", AssumeFPS::CreateFloat }, // dst framerate, sync audio?
{ "AssumeFPS", "cc[sync_audio]b", AssumeFPS::CreateFromClip }, // clip with dst framerate, sync audio?
{ "ChangeFPS", "ci[]i[linear]b", ChangeFPS::Create }, // dst framerate <<<这里是changefps滤镜的参数表和调用,相当于一个声明了,第一个参数是滤镜名,第二个参数是参数表(一个英文字母代表一个参数,c代表clip,i代表int,b代表bool,方括号中为接下来那个参数的参数名,标定了参数名的参数可以在avs中用 参数名=参数 的形式写出来而不一定需要按参数的固定顺序写),第三个参数是调用的接口
{ "ChangeFPS", "cf[linear]b", ChangeFPS::CreateFloat },// dst framerate
{ "ConvertFPS", "ci[]i[zone]i[vbi]i", ConvertFPS::Create }, // dst framerate, zone lines, vbi lines
{ "ConvertFPS", "cf[zone]i[vbi]i", ConvertFPS::CreateFloat }, // dst framerate, zone lines, vbi lines
{ 0 }
};
接下来是接口的定义:
AVSValue __cdecl ChangeFPS::Create(AVSValue args, void*, IScriptEnvironment* env)
{
return new ChangeFPS( args[0].AsClip(), args[1].AsInt(), args[2].AsInt(1), args[3].AsBool(true) ); //<<<调用ChangeFPS的构造函数并返回对象,args是avs中的参数组成的数组,通过AsXXX()函数转换成相应的数据类型
}
接下来类的构造函数:
ChangeFPS::ChangeFPS(PClip _child, int new_numerator, int new_denominator, bool _linear)
: GenericVideoFilter(_child), linear(_linear) ///注意GenericVideoFilter是IClip的子类,大多数的视频滤镜都是继承这个类而不直接继承IClip类,GenericVideoFilter(_child)是用_child拷贝构造PClip GenericVideoFilter::child对象,所以这行父类的构造完成后,child就是指向上一个滤镜输出的clip的指针(注意_child的值就是前面接口里的args[0].AsClip(),也就是avs滤镜的第一个参数,或者写成clip.filter)
{
a = __int64(vi.fps_numerator) * new_denominator; //vi是VideoInfo GenericVideoFilter::vi,保存环境结构
b = __int64(vi.fps_denominator) * new_numerator;
vi.SetFPS(new_numerator, new_denominator);
vi.num_frames = int((vi.num_frames * b + (a >> 1)) / a);
lastframe = -1;
}
然后再看GetFrame:
PVideoFrame __stdcall ChangeFPS::GetFrame(int n, IScriptEnvironment* env)
{
int getframe = int(((__int64)n * a + (b>>1)) / b); // Which frame to get next? //计算changefps的输出clip的第n帧对应于输入的第几帧
if (linear) { //2.53加入的linear参数,暂时略过...
if ((lastframe < (getframe-1)) && (getframe - lastframe < 10)) { // Do not decode more than 10 frames
while (lastframe < (getframe-1)) {
lastframe++;
PVideoFrame p = child->GetFrame(lastframe, env); // If MSVC optimizes this I'll kill it ;)
}
}
}
lastframe = getframe;
return child->GetFrame(getframe , env ); //把前一个滤镜的输出(也就是当前滤镜的输入)的第getframe帧的指针作为当前调用的返回值
}
以上changefps解读完成
------------------------------------------------
avs的视频滤镜读帧就是这样一个滤镜调用前一个滤镜的GetFrame函数来完成的(我不敢确定是直接调用还是间接调用,但原理是不会错的),如果要改变vi一般在滤镜的构造函数里完成
------------------------------------------------
然后再看avisource:
avisource::GetFrame那个do..while循环内部是先找到之前最近的一个keyframe,然后看上次输出的那个帧是不是在这个keyframe和当前被请求帧之间;如果是就从上次请求帧开始,不是就从之前最近的一个keyframe开始,逐渐往后读帧(就是那个for循环),遇到null帧就跳过继续往后读,直到读到当前被请求的帧;如果当前被请求的帧能正常解码就返回的当前帧;如果当前被请求帧是null帧,则返回之前最近一个能正常解码出来的帧( if ((!dropped_frame) && frame && (error == ICERR_OK)) last_frame = frame;这句在取到的frame是null frame时是不会给last_frame赋值的 ),而不是你所说的“第一個可用的 Key frame”(又或许你这里说的keyframe就是指非null frame?);也就是说:avisource输出时已经用null frame之前最近的一个可读取帧替代了null frame输出
例如源文件是[1][2][3][4][N][6]
avisource读取的输出就是[1][2][3][4][4][6],当然虽然这里已经不存在null帧了,但是原null帧所占的那一个位置还在,如果是顺序播放的话和avi的null帧的作用是一样的,所以之后的滤镜也不存在什么“是否是null frame”的判定了,更不可能因为源文件中存在null frame而不同步
如果changefps(原来帧率的1/2),那么changefps的输出就会是[1][3][4]
selectevery(2,0)的结果也会一样,是[1][3][4],selectevery(2,1)输出就是[2][4][6]