『漫游』酷论坛>『影音数码技术学习交流』>[求助]写AVS压RMVB产生 ..

weilai@2004-12-27 23:30

我是直接看 changefps 源碼
因為談論的就是這個 filter 而不是 avisource

avisource 的確提供 changefps 前身資訊
但幾乎都是資訊定位處理 及 數據空間宣告
沒有frame運算解碼的動作 (我只看名稱及參數※如果作者有規劃過的話應該可以這麼看)

P.S. 也就是 changefps 得到的資料數據還是原始檔案,avisource 只是告訴它其如何看

今天自己還真的是打死不認輸,所以這個討論哪個時候有空再說吧
免得錯了一小步(太急著回答)卻漸行越遠
引用

adamhj@2004-12-27 23:46

因为你说getframe(n)会跳过null frame(不知道有没有理解错,我看繁体还是有困难……),所以我才把avisource带出来的,事实上null frame在source提取的时候就已经滤除了,你可以去看看avisource:getframe()函数,判定null frame的代码就在那里~
恩~要熄灯了,明天继续~hoho~
引用

weilai@2004-12-28 11:19

^^閒來無聊又來聊聊了

看了一下 source.cpp 的 AVISource
adamhj 兄說的 avisource::getframe 可以就字面上分析出實際是一個
就 keyframe 來定位第一個 frame 的式子
P.S. 是一個傳回 PVideoFrame類別型態的 VideoFrame pointer
沒有對整個 frame stream 有持續性的作業(前面講到了"幾乎都是資訊定位處理 及 數據空間宣告")
與 ChangeFPS::GetFrame 性質不同
所以沒有達到濾除 null frame 的功用

P.S.
. source.cpp 下半部倒是有一推frame運算只是與 AVISource 無關
. Avisynth 的frame codec解碼工作是由外部來做,Avisynth只做到 frame為單位 的地步,所以才說Avisynth 是 frame server
引用

adamhj@2004-12-28 12:47

source.cpp中大部分都是些使用比较少的avs源,avisource的主要代码都在avi_source.cpp中。
avs滤镜对clip视频部分的处理主要都在GetFrame(int n, IScriptEnvironment* env)中,其中n为帧数,env为环境结构,每个视频处理滤镜都通过调用前一个滤镜的GetFrame获取视频然后通过自己重载的GetFrame函数向下一个滤镜输出处理后的视频(ps:在IClip类中GetFrame函数被声明为纯虚函数,所以凡是IClip类的子类都必须重载该函数),changefps不对帧的内容作改动只是靠那个公式改变了返回的帧数n,avisource的GetFrame如下:

PVideoFrame AVISource::GetFrame(int n, IScriptEnvironment* env) {

n = min(max(n, 0), vi.num_frames-1);
dropped_frame=false; //注意这个变量,比较奇怪
if (n != last_frame_no) {
// find the last keyframe
int keyframe = pvideo->NearestKeyFrame(n);
// maybe we don't need to go back that far
if (last_frame_no < n && last_frame_no >= keyframe)
keyframe = last_frame_no+1;
if (keyframe < 0) keyframe = 0;
bool not_found_yet=false;
do {
for (int i = keyframe; i <= n; ++i) {
PVideoFrame frame = env->NewVideoFrame(vi, -4);
LRESULT error = DecompressFrame(i, i != n, frame->GetWritePtr()); //这里才是真正解码帧的地方,DecompressFrame的声明如下:
//LRESULT AVISource:: DecompressFrame(int n, bool preroll, BYTE* buf)
//我没仔细看这个函数,不知道这个preroll是做什么的

// we don't want dropped frames to throw an error
// Experiment to remove ALL error reporting, so it will always return last valid frame. 这段注释是源程序里的,从此可见一斑
if (error != ICERR_OK && !dropped_frame) {
// env->ThrowError("AVISource: failed to decompress frame %d (error %d)", i, error); env->ThrowError就是平时调用滤镜出问题时那个黑底红字的错误信息的调用,但不知道这里为啥却被注掉了..
}
last_frame_no = i;
if ((!dropped_frame) && frame && (error == ICERR_OK)) last_frame = frame; // Better safe than sorry 取得最近的一个可读帧。注意此处dropped_frame必定为false,但如果这个dropped_frame为true会出现什么情况呢?放在这个函数里看仿佛一旦遇到null frame会导致最终出错,我猜想是作者考虑过要在读取帧的时候记录下null frame信息才预留了这么个变量,不知道是不是这样
}
if (!last_frame) { // Last keyframe was not valid.
not_found_yet=true;
int key_pre=keyframe;
keyframe = pvideo->NearestKeyFrame(keyframe-1);
if (keyframe == key_pre) {
env->ThrowError("AVISource: could not find valid keyframe for frame %d.", n);
}
}

} while(not_found_yet);//这层循环我不知道是什么意思,一次没找出需要的帧来在继续找么?
}
return last_frame;
}
引用

weilai@2004-12-28 15:43

原來是因為我看的是 2.50 adamhj兄看的是 2.55 所以才會有
"source.cpp中大部分都是些使用比较少的avs源,avisource的主要代码都在avi_source.cpp中"
這一句的差距

不過先不管版本問題
基本上 Ver 2.50 之 source.cpp 的AVISource::GetFrame源碼與 Ver 2.55 avi_source.cpp 內的 AVISource::GetFrame 是一樣的(其實是一模一樣)

這個程序重點在 return last_frame;
會得到一個AviSynth內部使用之指向開頭 frame 的 frame指標(實際結構當然不是這麼單純,而是 class PVideoFrame)

LRESULT error = DecompressFrame(i, i != n, frame->GetWritePtr());
LRESULT 是一個MS VC++ window通訊用訊息類別
靠著 DecompressFrame 得到一個 ERR常數
(在AVISource::DecompressFrame 句中雖然使用了 ICDecompress ※此函式解壓縮某一個frame,只是這個動作讓人感覺只用來啟動 解碼器,藉由 Graph filter Manager 自動建構出一個 decodec filter 流程)
也就是 error,這個常數用來給下面兩個 if 判斷句用的 (用來使 last_frame 得到第一個可用的 Key frame ※至於第一個if 的內涵 env->ThrowError 式子被"註"掉大概是作者認為有重複或MS有優化過沒此必要,只是為什麼不是將整個 if "註"掉這我也不清楚...)

正常情況下一定會找到第一個可用的 Key frame
如此 not_found_yet 就是 false
就可以脫離 do{ ..... } while(not_found_yet); 回圈了
最後 PVideoFrame AVISource::GetFrame(int n, IScriptEnvironment* env)
就達到 "得到一個指向第一個 Key frame 的 frame指標" 之目的

至於
"changefps不对帧的内容作改动只是靠那个公式改变了返回的帧数n"
這點不瞭
n 只是個計數值不會有回返的作用

不過講到這
child->GetFrame(getframe , env ); 到底是做了什麼動作(內容我找不到^^||)
P.S. child 是一個 PClip 類別吧,但卻不見 GetFrame 的實際運作蹤影
因此一直沒法對 changefps 可濾掉 null 下個定論
平常都是寫VB,看這個專案我都是以"常理"來解題,所以卡在這裡的話就用大膽推測 :
child->GetFrame 就字面上是依序獲得frame排序出一個串列[Frame]
所以當 null 不做解碼,但 n 仍然遞增的情形下
會回應之前講的:

[1][2][3][N][5] 還原就變成
[1 Frame][2 Frame][3 Frame][][5 Frame] 相當於 [1 Frame][2 Frame][3 Frame][5 Frame]
P.S. [] 實際上是沒有的"東西",因為根本沒有資料可運算出來"東西"

之情形 也就是 去Null frame
引用

adamhj@2004-12-28 17:36

汗……版本差距……这我倒没想过…………

"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]
引用

weilai@2004-12-29 08:28

想不到又是版本差異造成的...
2.50

ChangeFPS::ChangeFPS(PClip _child, int new_numerator, int new_denominator)
: GenericVideoFilter(_child)
{
a = __int64(vi.fps_numerator) * new_denominator;
b = __int64(vi.fps_denominator) * new_numerator;
vi.SetFPS(new_numerator, new_denominator);
vi.num_frames = int((vi.num_frames * b + (a >> 1)) / a);
}


PVideoFrame __stdcall ChangeFPS::GetFrame(int n, IScriptEnvironment* env)
{
return child->GetFrame( int((n * a + (b>>1)) / b), env );
}


bool __stdcall ChangeFPS::GetParity(int n)
{
return child->GetParity( int((n * a + (b>>1)) / b) );
}


AVSValue __cdecl ChangeFPS::Create(AVSValue args, void*, IScriptEnvironment* env)
{
return new ChangeFPS( args[0].AsClip(), args[1].AsInt(), args[2].AsInt(1) );
}


AVSValue __cdecl ChangeFPS::CreateFloat(AVSValue args, void*, IScriptEnvironment* env)
{
double n = args[1].AsFloat();
int d = 1;
while (n < 16777216 && d < 16777216) { n*=2; d*=2; }
return new ChangeFPS(args[0].AsClip(), int(n+0.5), d);
}

可看出
PVideoFrame __stdcall ChangeFPS::GetFrame(int n, IScriptEnvironment* env)
{
return child->GetFrame( int((n * a + (b>>1)) / b), env );
}
短短幾行
與 2.55 最後
return child->GetFrame(getframe , env );

差距上可看出 getframe 的確可接受返回
但 int((n * a + (b>>1)) / b) 就不行了 (至於技術上可否,有可能C++不太熟...)

與 adamhj 兄切磋很有收穫
上文有空會仔細看一下
或許真的不是我想的 ^^||
引用

adamhj@2004-12-29 10:25

还是差不多的

--------------
int getframe = int(((__int64)n * a + (b>>1)) / b);
lastframe = getframe;
return child->GetFrame(getframe , env );
--------------



--------------
return child->GetFrame( int((n * a + (b>>1)) / b), env );
--------------

作用几乎是一样的,上面的就是把下面的拆开了,加了2个中间变量(那两变量是用在linear那段里的), 如果真的要说有啥差别,就是2.55用了个(__int64)来强制转换n成64位整型。
引用

adamhj

nemoon@2008-07-10 16:18

adamhj

你能否从总体上说说avisynth是如何工作的?
引用

不败的魔术师@2008-07-10 16:23

楼上你..
引用

不败的魔术师

nemoon@2008-07-10 16:27

呵呵 怎么啦?我最近在学习avisynth的代码,想多了解一些,谢谢大家了。
引用

softworm@2008-07-10 16:35

漫游的搜索不是坏了么,4年前的帖子也挖得出来?
引用

nemoon@2008-07-10 16:36

呵呵 我启动了人肉搜索引擎
可以搜
引用

fangfang@2008-07-10 20:17

好强大,支持!
引用

nemoon@2008-07-11 08:51

adamhj 你还在吗?
引用

«1234»共4页

| TOP