View Full Version : Multithreaded development (from view of a Source Filter)
jordanh
4th May 2013, 20:25
Although i spent the last months with playing around with avisynth Versions, i did not really get how to deal with the Multithreaded stuff.
First i encountered AVISynth MT Builds. It seemed to me that one can multithread basically any existing AVISynth Filter. So i expected to be able to enable multithreading before doing QTGMC, but i didnt really manage to see any change of speed or cpu usage
Second i learned that AVISynth 2.6 has a new API where Source Filters can add special support for multithreading. The problem was, that i wasnt able to build a sample with 2.6 my own(linkage errors) and also i didnt find a simple Multithreaded Source Plugin example.
Third i encountered, that a filter for 2.5 is upward compatible but 2.6 is no down compatible...
So, what is the most modern way of creating your source Filter?
Thanks,
Harry
A quick review of the current AvisynthMT modes :-
Mode 1: Unprotected access to a single instance of the filter. All the threads may enter the GetFrame routine concurrently. Code has to be fully thread safe and re-entrant.
Mode 2: Unprotected multiplexed access to thread count instances of the filter. A threads can enter the GetFrame routine of the next free instance of the filter. Code only has to be instance safe. Each filter instance only sees a non-contiguous proportion of the GetFrame calls.
Mode 3: Protected access to a single instance of the filter. Only 1 thread may enter the GetFrame routine concurrently, the lock is released while the routine executes it's child->GetFrame() calls. Code has to be instance safe, re-entrant, and the code up to the child->GetFrame() call thread safe. The filter instance sees all of the GetFrame calls.
Mode 4: Protected multiplexed access to thread count instances of the filter. Only 1 thread may enter the GetFrame routine of the next free instance of the filter, the lock is released while the routine executes it's child->GetFrame() calls. Code can be mostly slack, statics and globals must be thread safe or read only. Each filter instance only sees a non-contiguous proportion of the GetFrame calls.
Mode 5 and 6: Protected access to a single instance of the filter. Only 1 thread may execute the GetFrame routine. When the routine executes a child->GetFrame() call, a mini-distributor prefetches a number of frames from the remaining graph. The thread count is based on the number, N, of threads concurrently waiting for any mode 5 filter lock. Mode 5 runs N pre-fetch threads, mode 6 runs N+1 pre-fetch threads.
By definition a source filter does no child->GetFrame() calls, the frame data comes from some external source, e.g. disk or network.
Thus modes 3, 5, and 6 are identical, i.e. access to the GetFrame routine is restricted to a single thread at any one time.
Mode 4 is practically useless for source filters, you have N instances of the filter but only 1 can ever execute at a time.
Mode 2 also has N instances of the filter but all can executing in parallel. For input formats with no inter-frame state this can work fine, e.g. raw uncompressed data direct from the disk, I frame only formats with instance safe codecs and very low random access overhead. For formats with inter-frame dependencies each thread ends up duplicating the same work as the other threads, e.g.with h264 each instance must decode the I frame and all the referenced P and B frames for each subsequent B frame.
Mode 1 of course means the filter has to be re-entrant and thread safe. All the threads can enter the GetFrame routine together. The filter author may choose to provide their own mutex protection. If they do they should avail themselves of the thread resource for all thread that they cause to be suspended. This assumes that some elements of the source frame generation process are usefully parallelisable.
The other threading technology available in Avisynth is pipe-lining. This model has worker threads between filter instances pre-fetching frames for that part of the graph. This can lead to concurrent access like mode 1 if multiple worker threads are configured.
When running heavy filters like QTGMC usually the source filter threading is not the problem.
jordanh
6th May 2013, 17:37
Ah, again you make things more clear. Thank you very much, IanB. I will try to play around with the Mode 1with my CarbonSource Filter.
What i still dont get is if there is any connection between MT and 2.6, will they be merged at some point in time, or are they already?
The pipe-lining stuff with the pre-fetching of frames is also new and interesting to me, could you please point me a direction where to get informations about this?
Basically i feel that (as i dont use avisynth's Directshow interface but the framework only), i also could do the Multithreadnig on my own, by just splicing the pictures and having multiple instances, but i dont know if this is the best way...
All the best,
Harry
Groucho2004
6th May 2013, 17:54
i also could do the Multithreadnig on my own, by just splicing the pictures and having multiple instances, but i dont know if this is the best way...
You could have a look at the source of tritical's filters (http://web.missouri.edu/~kes25c/), some of them are multithreaded (nnedi3, dfttest).
TheFluff
6th May 2013, 18:06
Avisynth-MT is a fork of the official Avisynth project. It is unlikely to be merged with the official version since it is a terrible hack and suffers from severe stability problems because people don't get thread safety and keep using non-reentrant plugins in a threaded environment because "it only crashes occasionally". If you write your plugins in a generally thread-safe way and make sure that GetFrame is reentrant (can be surprisingly hard with source filters, depending on how you're doing the source file access) they will be compatible with Avisynth-MT's multithreading, but they will not be multithreaded in regular Avisynth. Generally I think it's still best to implement multithreading yourself inside your own plugin, since Avisynth-MT is a terrible idea in many ways. I'm not familiar with the pipelining IanB mentions though.
However, as IanB says, generally the source filter isn't the bottleneck, so if writing a threaded version is hard feel free to skip it.
Or you could try VapourSynth, which deals with the multithreading stuff in a much cleaner way.
Some quick archaeology found these threads :- ThreadRequest : yet another plugin for multithread processing (http://forum.doom9.org/showthread.php?t=154886)
Sora's avs multi-process/multi-thread plugin package (2012-02-20) (http://forum.doom9.org/showthread.php?t=164073)
Opinions needed for a multi-threading library (http://forum.doom9.org/showthread.php?t=163959)
Multi-threading: Roundup of options (http://forum.doom9.org/showthread.php?t=161656)
All filter authors should aim for mode 1 compliance if possible. For source filters in particular it is almost a given as nothing inside Avisynth can really help very much with the decoding of formats with inter-frame dependencies. If you want to do multi-threaded decoding then doing it yourself with your own mutex management of incoming GetFrame() requests gives you the most control. And by only assuming the resources of threads your mutex management suspends allows you to seamlessly integrate into any upper level thread management scheme.
E.g. on an 8 core machine ideally you would have 8 Avisynth threads running and ideally all 8 would be doing something constructive. With modes 3, 4, 5 or 6 the threads just queue sequentially waiting for each single frame to be decoded. This may be acceptable with heavy processing scripts but for light processing scripts we may want something more. If we just blindly allocate another 8 independent threads in the decoder that makes 16 threads globally all trying to run on only 8 cores. We get contention. A smarter model might be to have the 8 independent threads in the decoder but have them suspended. As each Avisynth thread call the GetFrame routine we suspend it and ready 1 more of our worker threads. So as more Avisynth threads wait, the decoding of the current frame can become faster because more worker threads are available. As a frame completes decoding we suspend 1 of our worker threads and ready the Avisynth thread that was waiting for that frame. Decoding continues with the remaining worker threads. So the more Avisynth waits for frames the more worker threads the decoder has available.
Just to add from practice- even mode 2 works well with I frame codecs. I use intermediate codecs and some of them are not not natively threaded, but with mode 2 I get multithreaded decoding. This is actually nice thing to have.
...
Mode 2: ... For input formats with no inter-frame state this can work fine, e.g. raw uncompressed data direct from the disk, I frame only formats with instance safe codec's and very low random access overhead. ...Note the instance safe bit, some builds of the Cedocide DV codec have had issues reported.
I never had an issue, use mainly Canopus codecs: lossless, HQ, HQX and also ProRes, DNxHD.
jordanh
7th May 2013, 22:15
Hey, i didnt expect that much response... thanks to all of you.
Until now i was under the impression, that AVISynth would call some kind of special function or float framenumbers or similar from my source filter in order to Multithread. i
In my special case with the CarbonSource Plugin, i first have to call some frames from Carbon, and then pull a few frames from AVISynth. So i always fill like 10 frames in one step and then request a few (depends on output framerate) frames from AVISynth for sending them to the upstream in Carbon again.. When i call getFrame() on the env (end of filterchain), all the frames that i can get are already loaded into the source plugin.
As the AVISynth CarbonSource Plugin does read only on the frames from Carbon, i should be able to do multithreaded frame gathering from the env (just as much as it needs depending on output framerate and available input frames in the buffer).
I try to summarize and generalize my understanding of multithreading options:
-) all MT() ,SetMTMode and similar functions are dedicated to AVISynth MT, but:
-) AVISynth MT is generally a bad idea
-) there are filters in above links that enable similar function than AVISynth MT, but i did not find any stable considered and multi-purpose
-) Generally best Performance is definitely reached by multithreading with your own program that invokes the AVISynth script and calls the getFrame() functions. The Source filter has to support Mode one Multithreading. The problem with that is, that you either have to create multiple environments or all filters in your chain are thread-safe.
-) If one needs to do multithreading using filters, its good to have a look at above links
That is great information for me. I think i see the way for my plugin now clearly.
Thanks,
Harry
There is also MP_Pipeline: http://forum.doom9.org/showthread.php?t=163281
I guess a lot depends on the Carbon API for "call some frames from Carbon".
Can you tell us a little about the environment.
Do you pull the frames from the API like inside AVISource or do you collect the frames from a callback like inside DirectShowSource?
Is the environment multithreading aware, i.e. can 8 threads each ask for 8 different frames in parallel?
Is that style an efficient use of the API?
You say "fill like 10 frames in one step". Does this mean grabbing a set of frames all at once with all cpu cores available to the Carbon decoder is a good design?
You imply that Carbon might eventually be calling processed frames back from Avisynth. How do you envision doing this? Is this simply through the VfW AVIFile interface or do you want to use the Avisynth C or C++ API?
One future idea for improving AvisynthMT is to allow filters that use more than 1 input frame to pre-request the other frames. There is no code written for this yet, if you have specific requirements now is a good time to discuss them.
I envisage a mythical SomeFilter that combines a 3 frame window of 2 input clips into an output frame would flow something like this. :-PVideoFrame __stdcall SomeFilter::GetFrame(int n, IScriptEnvironment* env) {
child->SetCacheHints(CACHE_PREFETCH_FRAME, n-1);
child2->SetCacheHints(CACHE_PREFETCH_FRAME, n-1);
child->SetCacheHints(CACHE_PREFETCH_FRAME, n+1);
child2->SetCacheHints(CACHE_PREFETCH_FRAME, n+1);
child->SetCacheHints(CACHE_PREFETCH_FRAME, n);
child2->SetCacheHints(CACHE_PREFETCH_FRAME, n);
child->SetCacheHints(CACHE_PREFETCH_GO, (int)env);
child2->SetCacheHints(CACHE_PREFETCH_GO, (int)env);
PVideoFrame frameOut = env->NewVideoFrame(vi);
PVideoFrame frameprevA = child->GetFrame(n-1, env);
PVideoFrame frameprevB = child2->GetFrame(n-1, env);
/* munge left frames ... */
PVideoFrame framenextA = child->GetFrame(n+1, env);
PVideoFrame framenextB = child2->GetFrame(n+1, env);
/* munge right frames ... */
PVideoFrame framecurrA = child->GetFrame(n, env);
PVideoFrame framecurrB = child2->GetFrame(n, env);
/* munge centre frames */
/* combine left, centre and right frames ... */
return frameOut;
}The idea being SomeFilter declare to the cache it will need these 6 frames. The cache requests any frames not already cached on worker threads. By the time the child->GetFrame calls happen the required frames are in cache or at least partially generated.
jordanh
8th May 2013, 00:54
Yeah, you are right.. basically all depends on the Carbon API. Please give me one or 2 weeks more, then i have a documentation ready. Its already implemented and works OK, but needs some care before first release.
Of course there is a huge number of Carbon internal reasons why i buffer things like i do it. Carbon is extremely fast on decoding. Also it has a very good and easy internal logic when and what to transform. Just like in Avisynth, Carbonfilters can implement multithreading or not. The most Carbon Plugins seem to be single threads, but mostly the Exporter is the huge break or the up-downconversion. You can get a fullHD mpeg stream decoded in up to 7x realtime when you "null render" and do not filter it.
So far i can give a little overview:
You may understand that i cannot give too much information about Carbon API Stuff because thats NDA covered. All the Carbon related information below is or was generally available, published by Rhozet/Harmonic.
AVISynth API:
Pull-Pull model: You need to pull a frame from env, that calls a frame from your source plugin.
Carbon API:
Push-Push model :you get the decoded frame delivered into one public callback method, you can at one time only recieve or deliver frames, you need to do it in ascending order.
Hybrid Requirements:
support fps change, size change, interlacing change (did i forget something?)
Hybrid Usage:
In your CarbonCoder Application, Load the "source video filter" "Avisynth Filter". If you would use it as "target video filter" instead of "source", carbon will already have done all the resizing, deinterlacing and fps conversion when the frames arrive at your avs script.
At the Avisynth Filter configuration, point the path to your avs script. Then choose any input file and output settings.
In your avs script, explicitly load the CarbonPlugin.dll in the first line, and use CarbonSource() as the Source Filter for the Video stream you want to have from Carbon.
Mixed Processing is no problem: you can let AVISynth do only deinterlacing and Carbon the Framerate conversion.
Hybrid Implementation:
For optimal performance, we must have direct memory access from the AVISynth Source Plugin CarbonSource() to the Carbon Avisynth Filter Plugin. Both are win32 dll's, and as we loaddll "avisynth.dll", the memory is basically free for us to read. In the Carbon Part,include CarbonSource.h. After loaddll and invoke of environment, tell carboncoder what format will be delivered back from the returned clip in the avs script. In the CarbonSource() part, use a env->SetVar to exchange its own instance address. In the Carbon Part, use env->GetVar to get the instance Address of the Source Plugin. So we have access to all public functions of our source filter object. Unfortunately we must always memcpy on both sides,
From there it is mostly a matter of how things work in Carbon.
I hope this is not too intrusive, but i already lose about 1/3 speed by just pulling frames through avisynth, with an "empty script", it would be a huge bottleneck to do IPC or Filebased feeding of avisynth.
I dont think that this special kind of API usage needs to have special support, but i really wished i could swich avisynth to push mode... this should open avisynth for all kinds of live sources, shouldnt it?
good night!
jordanh
8th May 2013, 23:50
@IanB: you work on the MT project?
thanks for sharing the idea of a pre-caching "theoretical after Source" plugin.
I dont know too much of the child stuff and this appearently mighty setcachehints function, as i have a push source, i dont need to implement any of them at this time... When i had a quick overview of all above mentioned links for multithreading strategies, i understand that the method you explained will have the same limitations like all the others and it cannot be compatible to all existing filters.
One question: am i right that you generally want to keep any Source Plugins requirement to only need to deliver frames only in ascending order?
The reason why i am asking is, that from my understanding of your prototype code, the Source Plugin is not asked in monotonic order. Is that right?
...if so, it either means incompatibility to slow seeking source media like CD and/or the need for a standard how to manage the needed buffer in the source plugin.
Of course all above are just assumtions :-)
Your question first :- Yes frames can be requested in any order. Consider script usage of Trim(), SelectEvery(), SelectOdd(), SelectEven(), Reverse(), etc, all these effect the request order. Also consider the calling host application, e.g. an mpeg2 encoder might conceivably ask for frames in encode order, i.e. IPbbPbb... (0,3,1,2,6,4,5,...). And then there are the AvisynthMT influences.
There are filter that can mitigate this like the 'hack' ChangeFPS(Last, Last) and the plugin RequestLinear(). DirectShowSource can suffer quite badly from non-linear access. The plugin DSS2() has some tweaks to mitigate this, I believe it maintains a 10? frame LRU to service requests and always pulls frames in order for upto 20? frames into it's LRU. A seek beyond 20? frames or backwards results in a 2? frame preroll.
Children are the input clips from which a filter gets the frames that it will process to produce the output frame.
The class GenericVideoFilter : public IClip is the simplest filter. It calls a single input clip (child) and just passes Audio and Video straight through, most filters use this as a template to provide API "glue", so for an Audio filter you only need to write the GetAudio code, for a Video filter you only need to write the GetVideo code.
Source filters are unique because they do not have any children (input clips). Basically they env->NewVideoFrame(vi); and fill in the image contents.
The SetCacheHints method is currently only used to influence the caches of a filters input clips, basically you can turn it off, CACHE_NOTHING, or put it into windows mode, CACHE_RANGE. As part of the Avisynth 2.6 API changes it's usage is being extended. For 2.6.0 the cache still works the same way as in 2.5. The 2.6.0 cache does actually ask the child input clip performance questions to assert the interface design but the information is presently not used. A future version may tailor the behaviour based on these or future defined queries.
These are the currently defined queries, they may change, they will almost certainly be extended. :- CACHE_GETCHILD_CACHE_MODE=200, // Cache ask Child for desired video cache mode.
CACHE_GETCHILD_CACHE_SIZE=201, // Cache ask Child for desired video cache size.
CACHE_GETCHILD_AUDIO_MODE=202, // Cache ask Child for desired audio cache mode.
CACHE_GETCHILD_AUDIO_SIZE=203, // Cache ask Child for desired audio cache size.
CACHE_GETCHILD_COST=220, // Cache ask Child for estimated processing cost.
CACHE_COST_ZERO=221, // Child response of zero cost (ptr arithmetic only).
CACHE_COST_UNIT=222, // Child response of unit cost (less than or equal 1 full frame blit).
CACHE_COST_LOW=223, // Child response of light cost. (Fast)
CACHE_COST_MED=224, // Child response of medium cost. (Real time)
CACHE_COST_HI=225, // Child response of heavy cost. (Slow)
CACHE_GETCHILD_THREAD_MODE=240, // Cache ask Child for thread safetyness.
CACHE_THREAD_UNSAFE=241, // Only 1 thread allowed for all instances. 2.5 filters default!
CACHE_THREAD_CLASS=242, // Only 1 thread allowed for each instance. 2.6 filters default!
CACHE_THREAD_SAFE=243, // Allow all threads in any instance.
CACHE_THREAD_OWN=244, // Safe but limit to 1 thread, internally threaded.
CACHE_GETCHILD_ACCESS_COST=260, // Cache ask Child for preferred access pattern.
CACHE_ACCESS_RAND=261, // Filter is access order agnostic.
CACHE_ACCESS_SEQ0=262, // Filter prefers sequential access (low cost)
CACHE_ACCESS_SEQ1=263, // Filter needs sequential access (high cost)
I hope this is not too intrusive, but i already lose about 1/3 speed by just pulling frames through avisynth, with an "empty script", it would be a huge bottleneck to do IPC or Filebased feeding of avisynth.
I dont think that this special kind of API usage needs to have special support, but i really wished i could switch avisynth to push mode... this should open avisynth for all kinds of live sources, shouldnt it?Yes you always seem to get stuck with an input frame blit and another output frame blit as you cross API boundaries.
I take it this is costing you some speed. Have you identified any other bottlenecks?
Worker thread piping can help sometimes when bridging push and pull models. e.g. at the bottom a thread keeps draining the input stream into a fair number of buffers, it stop when all the buffers are full. At the top another thread keeps pulling frames from Avisynth and pushing them into the output stream, it stops when the output stream is full or all the input buffers in the source are empty.
jordanh
11th May 2013, 18:49
IanB, sorry but i really struggle following if you are talking of AVISynth MT or classic.
The CACHE_GETCHILD_THREAD_MODE and similar, i have never seen them in the avisynth docs... is that a new thing you just implement? ...Sure thats interesting!
Thanks for all the explainations, everyone.
I take it this is costing you some speed. Have you identified any other bottlenecks?
At the moment not. I need to dig a little deeper into this.
By now i am measuring this way (including example values):
1) take the time that carbon needs without any filter: 1 minute
2) do the same using a special "empty" filter: 1 minute
3) take the time that carbon needs with the avisynth plugin filter, but disable all avisynth processing (copy the frames only): 1 min. 3 sec
4) take the time of carbon and avisynth in action, but with a special avs script that basically only loads the source and does not filtering: 1 min 20 sec
By the way, basically the framebuffer that holds the incoming pictures can be configured to hold only one frame, that brings the whole chain back to where it comes from: deliver one sample then fetch one sample.... Thats how most filters work in carbon.
Worker thread piping can help sometimes when bridging push and pull models. e.g. at the bottom a thread keeps draining the input stream into a fair number of buffers, it stop when all the buffers are full. At the top another thread keeps pulling frames from Avisynth and pushing them into the output stream, it stops when the output stream is full or all the input buffers in the source are empty.
I think in my special case it doesnt help to thread incoming and outgoing. As i mentioned before, the routine for frame gathering and the one for sending the processed frame to the upstream can only run seperately. it is not possible that both can run at the same time. Imagine it like the carbon engine was single threaded (AFAIK internally its not, but it looks like).
To implement what you mention would also mean the need to synchronize the carbon frame delivery.
I think the better and more easy way will be to just have multiple env's at the cost of memory. I hope to have more or less linear speed improvement when doing this:
1) create 4 similar avisynth environments, each having its own instance of my avisynth CarbonSource() plugin in the avs script
2) set the read buffer of the 4 instances of CarbonSource() to a single buffer (the one and only buffer)
3) Buffer about 10 input frames, stop buffering
4) create 4 threads that let the 4 environments process each 1 frame (actually at this spot, we measure in time units e.g. 40ms, not in frames, because of possible frame rate change)
5) buffer next 4 frames...
The problem is that i need to harden the functionality before i go to threading...
Harry
IanB, sorry but i really struggle following if you are talking of AVISynth MT or classic.
The CACHE_GETCHILD_THREAD_MODE and similar, I have never seen them in the Avisynth docs... is that a new thing you just implement?Yes this is all new and reserved for a future version, they are just place holder so we will not need to change the API again when they are implemented. As I said the Cache currently does make all the queries, it validates the answers, but does not act on the responses. About 95% of the "MT" code is in the "classic" code base, the missing 5% is Distributor(), the 5 mode cache code and the [GS]etMTMode() verbs.
The CACHE_GETCHILD_* model will be that user filters optionally implement the IClip::SetCacheHints method. The parent cache at start up makes the queries. If the filter has an opinion on the query it responds appropriately. The parent cache reconfigures itself to accommodate the opinion.
At the moment not. I need to dig a little deeper into this.
By now i am measuring this way (including example values):
1) take the time that carbon needs without any filter: 1 minute
2) do the same using a special "empty" filter: 1 minute
3) take the time that carbon needs with the avisynth plugin filter, but disable all avisynth processing (copy the frames only): 1 min. 3 sec
4) take the time of carbon and avisynth in action, but with a special avs script that basically only loads the source and does not filtering: 1 min 20 secI take it in 2. the filter effectively just returns the pointer to input buffer as it's output, much like a zero cost Avisynth filter would. So cost nothing.
In 3. I assume the filter malloc's (whatever) a new output buffer and just blits the input buffer contents into it, much like an Avisynth Crop(..., Align=True) will do with an unaligned left value. So costs 3 seconds.
In 4. I assume the script is a simple "Return CarbonSource(...)" and you lose an extra 17 seconds somewhere currently unknown.
I think the better and more easy way will be to just have multiple env's at the cost of memory. I hope to have more or less linear speed improvement when doing this:
1) create 4 similar avisynth environments, each having its own instance of my avisynth CarbonSource() plugin in the avs script
2) set the read buffer of the 4 instances of CarbonSource() to a single buffer (the one and only buffer)
3) Buffer about 10 input frames, stop buffering
4) create 4 threads that let the 4 environments process each 1 frame (actually at this spot, we measure in time units e.g. 40ms, not in frames, because of possible frame rate change)
5) buffer next 4 frames...This appear like you want manually implement a SetMTMode(2, 4). With mode 2, each "spread" of a filter share a common cache. If you have temporal filters that process say 3 input frames, prev, curr & next into an output frame the mode 2 cache only calls for each frame once. If the chains are fully divorced then no commonality is possible.
jordanh
12th May 2013, 10:54
Thanks, you help me a lot while putting the puzzle together :-)
Basically i think that i got what you want to tell me, except one: The SetMTMode() stuff is dedicated to AVISynth MT, isnt it? ...i definitely have to give that a try before implementing by own threading.
On the other hand the outcome of this whole discussion is, that i need to do a lot of benchmarking to find the right spot for threading.
If the chains are fully divorced then no commonality is possible.
I am not sure if you understood how things work for me... Actually i can definitely share the same input buffer on multiple environments. It is hard to get, but all i do at CarbonSource() depends on the special ability to be able to modify the living instance of my CarbonSource() Filter thats Instance was created by AVISynth when it loads an avs script.
It runs like this:
1) my Carboncoder Filter creates the env using c++
2) env creates the instance of the CarbonSource() Filter when an avs script containing CarbonSource() is loaded
3) CarbonSource() Filter sets a uservariable "INSTANCEADDRESS" in env to expose its own instance address
4) From there i can set the input buffer memory of the SourceFilter to whatever i like.
But, just like mentioned, it doesnt matter. First i need to benchmark and find out where speed is really lost.
Meanwhile i had a break through regarding input compatibility, i am realeasing the first Beta (or even RC1) in the next week.
jordanh
12th May 2013, 18:35
It seems that i just cannot have multiple seperate avisynth environments using the c++ api at a time. At least not while my SourcePlugin uses env->setVar()...
Can it be that
1) generally, a single process can only run a single avisynth environemnt (would it help to load the dll mutliple times?)
2) for a threadsafe source filter, using env->setVar() is not allowed?
Below is the code part of interest, Upper is the calling Application (the Carbon Part) and bottom is the AVISynth Source Plugin part.
It produces the "setvar failed." error when i do either of
-) use setmtmode(2,4)
-) call the CreateNewEnv() function from below 2x in order to create 2 seperate environments.
Thanks for any clue... especially on the setVar in MT environment stuff.
//calling application that imports avisynth.dll and creates script enviroment
IScriptEnvironment* Scaler::CreateNewEnv(){
//creates new avisynth script environment and returns it
if (!avsdll){
avsdll = LoadLibrary("avisynth.dll");//load dll only once
}
IScriptEnvironment* outputenv;
try{
CreateEnv = (IScriptEnvironment *(__stdcall *)(int))GetProcAddress(avsdll, "CreateScriptEnvironment"); //get function pointer in external dll
if(!CreateEnv){
if (debug==true){
sprintf(debugmsg,"Avisynth Filter error, cannot initialize avisynth.dll, version or installation problem");
debug_log_callback( debugmsg);
}
throw std::runtime_error("Avisynth Filter error, cannot initialize avisynth.dll, version or installation problem");
}
//do the avisynth work
outputenv= CreateEnv(AVISYNTH_INTERFACE_VERSION);
//first we set our input info as variables, they are read from the plugin when initializing //hardcoded
outputenv->SetVar("EXTERNAL_INPUT_HEIGHT", (int)m_InputVideoInfo.height);
outputenv->SetVar("EXTERNAL_INPUT_WIDTH", (int)m_InputVideoInfo.width);
outputenv->SetVar("EXTERNAL_INPUT_FPSNUM", (int)m_InputVideoInfo.frameRateNumerator);
outputenv->SetVar("EXTERNAL_INPUT_FPSDEN", (int)m_InputVideoInfo.frameRateDenominator);
outputenv->SetVar("EXTERNAL_INPUT_FIELDORDER", (int)m_InputVideoInfo.interlaceMode);
}
catch (AvisynthError e) {
m_errorMessage = e.msg;
debug_log_callback( (char*)e.msg);
m_errorMessage = "Avisynth script error.";
outputenv = 0;//for avisynth 2.5, do not delete the environment. in newer versions a delete env function is provided
throw PluginException(RPI_RESULT_INTERNAL_ERROR,m_errorMessage.c_str());
}
return outputenv;
}
//AVISynth Source Filter CarbonSource()
AVSValue __cdecl Create_CarbonSource(AVSValue args, void* user_data, IScriptEnvironment* env) {
//called by avisynth.dll. creates an instance of our SourceFilter Class
//returns new instance see AvisynthPluginInit2 AddFunction for parameter description
CarbonSource *theOneAndOnly = new CarbonSource(env);
if(env->SetVar("INSTANCEADDRESS", AVSValue((int)theOneAndOnly))) {
//env->ThrowError ("SetVar ok.");
} else {
//here is the error when using setmtmode(2,4)
env->ThrowError("SetVar fails. Possible reason is incompatible avisynth version. Must support at least interface version 2");
}
return theOneAndOnly;
}
Yes, the SetMTMode stuff is only in the experimental MT versions. In post 2 I discussed the various modes and implied that the way the current MT works is not all that helpful to intraframe dependant source filters. It can be very useful with complex and intensive scripts, but that does not seem to be your current area of interest.
Each IScriptEnvironment is completely independent, they have their own variables, their own graph, their own cache, etc ... . What is done in one has no effect in any others. A typical abuse of this is when one script calls AviSource() on a second script. I call it an abuse, but it works and it is supported. Typically it is used with poorly written scripts that use global variables whose names clash between the inner and outer scripts.
In your code :-outputenv= CreateEnv(AVISYNTH_INTERFACE_VERSION);you should test the result before using it. It can return 0 if the version is wrong or a bad .avsi script or bad plugin is in the Avisynth auto load plugin directory.
Depending on how you structure core code you may find it easier to use SetGlobalVar(). SetVar() variables are only visible in the local scope, e.g. script functions have a new scope, so if CarbonSource was called in a wrapper function the variables would not be present in that scope.
The boolean return value of env->SetVar() tells whether a new variable was created or an existing variable had its value updated. If you use mode 2, the graph builder creates ThreadCount copies of each filter, so it is expected that the first instance created will get a true return and the subsequent instances will get a false return. Here is the setvar code from core/avisynth.cpp :- bool Set(const char* name, const AVSValue& val) {
for (Variable* v = &variables; v; v = v->next)
if (!lstrcmpi(name, v->name)) {
v->val = val;
return false; // Updated value of existing variable
}
variables.next = new Variable(name, variables.next);
variables.next->val = val;
return true; // Create a new variable
}
From what you have described so far, you possibly might be better doing "new CarbonSource(outputenv)" in the load routine with a matching "outputenv->SetGlobalVar("INSTANCEADDRESS",AVSValue((int)theOneAndOnly));". Then in the script create routine do a env->GetVar() to find theOneAndOnly and interface to it.
Also have you considered the possibility of multiple avisynth scripts in a single Carbon workflow, i.e. (Carbon stuff)->(Avisynth stuff)->(More Carbon stuff)->(More Avisynth stuff)->(Final Carbon stuff). This would be a valid use of multiple IScriptEnvironments.
Something I should have pointed out earlier, multi-threading in Avisynth is of no advantage unless the script is doing some parallelisable work.
This will just spin 4 cpus in 4 DirectShow instances doing the same speed as 1 wouldSetMtMode(2, 4)
DirectShowSource("blah....")
This will occupy 4 cpus giving a useful speed increaseSetMtMode(3, 4)
DirectShowSource("blah....")
ChangeFPS(Last, Last, True)
SetMTMode(2)
QtGmc(....)
This will overlap disk IOSetMtMode(2, 2)
RawSource("blah....")
There is nothing to do in parallelMpeg2Source("blah...")
SelectEvery(5, 0,1,2,3) # Zero cost filter
SeparateFields() # Zero cost filter
real.finder
14th May 2013, 15:36
What about SEt's build (http://forum.doom9.org/showthread.php?t=148782)?
I used it for a long time without problems
and recently I use SetMTMode with SoraThread to increase speed
some thing like:
SetMemoryMax(1500)
src="D:\dvd\VTS_01_1.d2v"
DGDecode_MPEG2Source(src).ThreadRequest(10, 5)
SetMTMode(2)
ColorMatrix(d2v=src, threads=0)
Trim(2848, 20649) ++ Trim(20650, 38811) ++ Trim(40910, 41359)
SoraThread()
SetMTMode(6)
animeivtc(mode=1)
SoraThread()
SetMTMode(2)
DeHaloH
SoraThread()
daa3
SoraThread()
Tweak(sat=1.02,cont=1.02)
SMDegrain(lsb=true, lsb_out=true)
SoraThread()
SmoothGrad()
DitherPost()
SoraThread()
FastLineDarkenMOD(20)
SoraThread()
LSFmod(edgemode=2)
note: the cpu is core i7 3.4g with 32g ram, with core 2 with 4g ram I must put one SoraThread in good place in script Otherwise it get Crash
and with mp_pipeline:
MP_Pipeline("""
### platform: win32
SetMemoryMax(1500)
src="D:\dvd\VTS_01_1.d2v"
DGDecode_MPEG2Source(src).ThreadRequest(10, 5)
SetMTMode(2)
ColorMatrix(d2v=src, threads=0)
Trim(2848, 20649) ++ Trim(20650, 38811) ++ Trim(40910, 41359)
SoraThread()
SetMTMode(6)
animeivtc(mode=1)
SoraThread()
### lock threads to cores
### prefetch: 16, 12
### ###
### platform: win32
SetMemoryMax(2000)
SetMTMode(2)
DeHaloH
SoraThread()
daa3
SoraThread()
Tweak(sat=1.02,cont=1.02)
SMDegrain(lsb=true,lsb_out=true)
SoraThread()
SmoothGrad()
DitherPost()
SoraThread()
FastLineDarkenMOD(20)
SoraThread()
LSFmod(edgemode=2)
### lock threads to cores
### prefetch: 16, 12
### ###
""")
SetMTMode(2)
AssumeFrameBased
And here hybrid 32 and 64 script for more speed http://forum.doom9.org/showthread.php?p=1607063#post1607063
kolak
14th May 2013, 15:42
This will overlap disk IOSetMtMode(2, 2)
RawSource("blah....")
I found this working very well for scaling
SetMtMode(2)
avisource()
spline36resize()
Not only that it will make non-threaded codecs decoding way faster, but overall speed is very good and it will use many cores close to 100%.
It's also faster and more "compliant" with some apps than:
SetMtMode(3)
avisource()
SetMtMOde(2)
spline36resize()
real.finder
14th May 2013, 16:05
I found this working very well for scaling
SetMtMode(2)
avisource()
spline36resize()
Not only that it will make non-threaded codecs decoding way faster, but overall speed is very good and it will use many cores close to 100%.
It's also faster and more "compliant" with some apps than:
SetMtMode(3)
avisource()
SetMtMOde(2)
spline36resize()
if you use SetMtMode with a Source Filter that has MT support of it own (like ffms2), The results will be reversed (it make the process slower)
so it better to put SetMtMode after the Source Filter, and use ThreadRequest with a Source Filter that not has MT support of it own (like DGDecode) like I did above
kolak
14th May 2013, 19:43
Tanks for the tip :)
For AllKeyFrameOnly .avi files with AviSource mode 2 can be quite successful, just like RawSource or any other source filter that just simplistically does seek the disk, read a chunk, decode/unpack the chunk, return an image.
For content that has a high out of order overhead, mode 3 with a ChangeFPS(Last, Last, True) sequential guard is a solution. And including a thread pipe can be even better.
Do not ever use how busy all your cores are as a measure of goodness. The only useful measure is encoding FPS. Think hard about my first example above with 4 mode 2 DirectShowSources's. Doing 4 or 8 lots of identical work just heats the room.
kolak
14th May 2013, 23:17
Do not ever use how busy all your cores are as a measure of goodness. The only useful measure is encoding FPS. Think hard about my first example above with 4 mode 2 DirectShowSources's. Doing 4 or 8 lots of identical work just heats the room.
I'm aware of this :)
setmtmode2 does not only use lot of CPU in my case, but it's fast- 300fps on converting HD->SD on 12 threads machine.
jordanh
18th May 2013, 01:04
OK, sorry for the late delay. The last answers helped me to get it so far that at least MT Mode 3 and 5 are working. But i have a lot of errors.
Basically i fear that my code is somehow not re-entrant and instance safe.
The errors that i have go from - stuck at fram 67 and fills RAM forever (not my code) to still frame access, even if i dont request frames anymore (maybe delayed threads?)
In the end i have big trouble to support MT. I thought i should only look out for finding a way that
a) multiple instances of my source plugin can be created without problem
b) threads may enter the GetRoutine concurrently without problems.
Now, as i provide the source Frame Buffer from a "deque" which is just an array of char*, and Getframe does read only, i should have compatibillity for even Mode 1.
But in the end i just got stuck.
Can anybody please tell me whats a good and easy example for mode1?
..Also its possible, that my calling Application, Carboncoder, where i do the env->Getframes calls from, does the errors.
Is there any documentation about applications using MT env-GetFrame?
My application works quite well with this script, but not with any lower mode
---------
LoadPlugin("C:\CarbonSource.dll")
SetMTMode(5, 8)
Video = CarbonSource()
Video = ConvertToYV12(Video,true)
SetMtMode(2)
Video = AssumeTFF(Video)
Video= QTGMC( Video,Preset="Slow", EdiThreads=1)
Video = Distributor(Video)
Video = ConvertToYUY2(Video)#return the colorspace thats needed
return Video
________
I will post the final Versin 1.0 of the software in attachment as a special thread...
jordanh
18th May 2013, 02:26
An example for what happens with this script (MT Mode 1)
----------------------------------
LoadPlugin("C:\CarbonSource.dll")
SetMTMode(1, 8)
Video = CarbonSource()
Video = ConvertToYV12(Video,true)
SetMtMode(2)
Video = AssumeTFF(Video)
Video= QTGMC( Video,Preset="Slow", EdiThreads=1)
Video = Distributor(Video)
Video = ConvertToYUY2(Video)#return the colorspace thats needed
return Video
---------------------------------
The log shows that for each frame that i request from the env, a lot of framenumbers are requested. Below is only the log for the first part, but in attachment the full log until program breakdown. I dont know exactly where it breaks by now.
The first line is the log entry of the calling application that does the getframe, all others are from my AVISynth Source Plugin CarbonSource(). I only request frame 0 and avisynth asks my source plugin for those frames. As i have frame 0 to 25 in my buffer, it shouldnt be a problem, but it is.
356305419038010: Processing Frame from Buffer. Requesting Frame Num: 0 from Avisynth duration of buffer= 1.00000 seconds, msPerOutputFrame = 0.02000 seconds
Line 3: 356305421504249: CarbonSource GetFrame called for Framenum 2
Line 4: 356305421659533: CarbonSource GetFrame called for Framenum 3
Line 16: 356305772939996: CarbonSource GetFrame called for Framenum 4
Line 21: 356306044187046: CarbonSource GetFrame called for Framenum 0
Line 26: 356306085315725: CarbonSource GetFrame called for Framenum 5
Line 28: 356306097610777: CarbonSource GetFrame called for Framenum 1
Line 34: 356306279170519: CarbonSource GetFrame called for Framenum 4
Line 39: 356306640330218: CarbonSource GetFrame called for Framenum 3
If you download the log, please know that this log congains both, the messages from the calling application and the log of the avisynth source plugin. Look for the same entrys above to find the getframe calls from the host and look out for what happens next within CarbonSource...
All log entries where a newline is below, is from the host which calls the env->getframe function.
I think i need to add something special for mode 1 that i dont know yet.
Also the application that calls Getframe could add support for MT. Also i dont know how to do that... Should i avoid the Distributor() Filter at the end of script?
A quick list of issues :- memfill vi with zero before setting values.
Move PVideoFrame destinationFrame; into GetFrame as a local variable.
Same for char message [500];
Same for long numOfLastFrameInBuffer;
Maybe you need a mutex with the code in the other routines for std::deque<char*>* framebuffer_deque; and numOfLastFrameInBuffer. Another thread should not change these while you are calculating with them.
Consider env->BitBlt() for copying the image. It is designed to copy rectangular blocks of image data.
Loose the env->SetVar("INSTANCEADDRESS", how can it mean something useful if you have multiple instances.
::GetVersion() should return AVISYNTH_INTERFACE_VERSION not your version.
You have a class variable race with this :- destinationFrame = env->NewVideoFrame(vi);
pdst = destinationFrame->GetWritePtr();
How do signal to the other code to update (*framebuffer_deque)[frame_to_serve_bufferindex] and stop it changing while you are copying it.
A better structure might be :-...
PVideoFrame destinationFrame = env->NewVideoFrame(vi);
unsigned char* pdst = destinationFrame->GetWritePtr();
const int dPitch = destinationFrame->GetPitch();
const int dHeight = destinationFrame->GetHeight();
const int dRowSize = destinationFrame->GetRowSize();
const unsigned char* psrc = framebuffer_handler->GetBufferAndLock(n);
const int sPitch = framebuffer_handler->GetPitch();
env->BitBlt(pdst, dPitch, psrc, sPitch, dRowSize, dHeight);
framebuffer_handler->UnLock(n);
return destinationFrame;
...
Ah huh! Attached Files avisynth_CarbonSource.txt is approved!
Add the GetCurrentThreadId() and the ((int*)this) pointer value to your log messages.
It helps a lot to know which thread and to which instance it is doing what and when.
Subtract the time at the start (saved in a static) from ::now(). Huge numbers obfuscate information.
A brief precis of the out of scope code running here might help. Yes I understand NDA but a hint of how the data flows in and out and the information your code passes up and downs it's logic paths shouldn't be telling to much out of school.
I can see some "magic" occurred and you got 25 video frames. I can see MT avisynth fetching frames all over. I am not sure how this starts at the top of the Avisynth graph.
I can see you are probably trying mode 2 with your filter because the same frames are being requested more than once. This is why I have been going on about mode 1 or mode 3 for source filters. Ideally a source filter delivers a frame once and the Avisynth cache serves it subsequent times, well yes the CacheMT code is from 2.5.5 and hasn't tracked any fixes or tuning so may not always get this right, I am currently working on some cache tuning for 2.6alpha5 but that is for a different issue.
jordanh
24th May 2013, 01:26
Hey there, i think i was able to correct the problems you pointed out. The current version is in attachment and i will post a current log for mt mode 2,8 in the next thread
Thanks again a lot for the tipps, IanB and all others.
Ian, i think i was able to correct the problems you pointed out. You are right when asking for how the other side works... so here we go.
The functional model of the carbonsource should be clear by now:
*) it can only work fast enough with direct memory access between avisynth and carbon. You can see that the address of the framebuffer is shared by using setvar...
So, its a very special case, same application invokes, feeds and reads from AVISynth.
Now, your question was about the main application.
The main application has been implemented in form of a win32 c++ dll Video Filter Plugin for Carboncoder. Basically the implementation of such a plugin is similar to avisynth plugins, except the synchronous deliver-fetch mechanism. I can only either have the "in" channel or the "out" channel at the same time opened.
At initialisation time, i need to create the avisynth environment without actually having a frame, but the videoinfo about it.
By the way, the env->GetVar("INSTANCEADDRESS").AsInt(); stuff is used for this initialisation only. I need to find out the pitch/stride that i need to deliver AVISynth on input side. Shouldnt be a problem for MT stuff, as the input resolution will not change within the lifetime of an env.
Of course there are hundred other rules and restrictions to obey.
I tried to create a view of how i initialize and fill buffers as well as get frames on the Hosts Side.
A lifetime of the dll can be very long and hard but is limited to a single instance with no real MT support on the interface side. The same instance of the plugin can be used for multiple sessions, thats fundamental here..
This has support for framerateconversion and all other possible changes.
//prototype code, at the start of a sequence, carbon tells us the input format specification, we remember them in heap for later use
sub setInputFormat(VideoInfoObject object){
m_rpiInputVideoInfo = object.copy();
}
//thats production code, fully avisynth related. used from later initialisation callback before importing the userscript
IScriptEnvironment* Scaler::CreateNewEnv(){
//this only creates and returns an empty environment wiht some variables set. the avisynth carbonsource plugin is invoked later, when importing the userscript
avsdll = LoadLibrary("avisynth.dll");//todo: do this and the next stuff only once
IScriptEnvironment* outputenv;
try{
CreateEnv = (IScriptEnvironment *(__stdcall *)(int))GetProcAddress(avsdll, "CreateScriptEnvironment");
if(!CreateEnv){
if (debug==true){
sprintf(debugmsg,"Avisynth Filter error, cannot initialize avisynth.dll, version or installation problem");
debug_log_callback( debugmsg);
}
throw std::runtime_error("Avisynth Filter error, cannot initialize avisynth.dll, version or installation problem");
}
//do the avisynth work
outputenv= CreateEnv(AVISYNTH_INTERFACE_VERSION);
//first we set our input info as variables, there are read from the plugin when initializing //hardcoded
outputenv->SetVar("EXTERNAL_INPUT_HEIGHT", (int)m_rpiInputVideoInfo.height);
outputenv->SetVar("EXTERNAL_INPUT_WIDTH", (int)m_rpiInputVideoInfo.width);
outputenv->SetVar("EXTERNAL_INPUT_FPSNUM", (int)m_rpiInputVideoInfo.frameRateNumerator);
outputenv->SetVar("EXTERNAL_INPUT_FPSDEN", (int)m_rpiInputVideoInfo.frameRateDenominator);
outputenv->SetVar("EXTERNAL_INPUT_FIELDORDER", (int)m_rpiInputVideoInfo.interlaceMode);
outputenv->SetVar("EXTERNAL_INPUT_DEBUG", (bool)debug);
//inform the avisynth env about the address of the framebuffer
outputenv->SetVar("EXTERNAL_INPUT_FRAMEBUFFER_ADDRESS", (int)&framebuffer_deque);
}
catch (AvisynthError e) {
m_errorMessage = e.msg;
debug_log_callback( (char*)e.msg);
m_errorMessage = "Avisynth script error.";
throw PluginException(RPI_RESULT_INTERNAL_ERROR,m_errorMessage.c_str());
delete outputenv;
}
return outputenv;
}
//Invoke of environemnt. We are within a initial callback function that wants to know what we are going to deliver on upstream. Will be called on every format change (new sequence).
try{
//invokes avisynth script only once per runtime
// in case of we get called multiple times in the same runtime, cleanup... TODO: when updating to avisynth 2.6, use the new deletescriptenvironment stuff
Video = 0;
env = 0;
FreeLibrary(avsdll);
//load avisynth dll
avsdll = LoadLibrary("avisynth.dll");
Video =0;
Video = new PClip();
env= CreateNewEnv();
AVSValue args_userscript[1] = { m_args.c_str() };//the user of Carboncoder just enters the path to a script in the Video Filter configuration. No more inputs there.
*Video = env->Invoke("Import",AVSValue(args_userscript,1)).AsClip();
avisynthOutputVideoInfo = (*Video)->GetVideoInfo();
//benchmark only, delete for release
externallogstarttime = env->GetVar("EXTERNAL_INPUT_LOGSTART").AsInt();
int memoryAdressOfInstance = env->GetVar("INSTANCEADDRESS").AsInt();
avisynthSourcePlugin = (CarbonSource*)memoryAdressOfInstance; // key part of this whole integration... we get the address of our own avisynth source plugin
//analyse the Pitch that avisynth likes. Take huge care about Pitch!
// outputinfo
VideoInfo analyseinfo = (*Video)->GetVideoInfo();
PVideoFrame analyseframe=env->NewVideoFrame(avisynthOutputVideoInfo);
m_outputstride = analyseframe->GetPitch();//thats what we were doing all this stuff for
// inputinfo
analyseinfo = avisynthSourcePlugin->GetVideoInfo();//this is the ONLY part of this program where "INSTANCEADDRESS" is used
analyseframe = env->NewVideoFrame(analyseinfo);
m_inputstride = analyseframe->GetPitch();//thats what we were doing all this stuff for
//clear possible old buffers
for (unsigned int i=0;i<framebuffer_deque.size();i++){//clear framebuffer
framebuffer_deque.pop_back();
}
framebuffer_deque.clear();
if (debug==true){
sprintf(debugmsg,"Analyzed input and output pitch/stride from current AVISynth environment %d, %d\n",m_inputstride,m_outputstride);
debug_log_callback(debugmsg);
sprintf(debugmsg,"Size of queue after analyse (should be 0)%d\n",framebuffer_deque.size());
debug_log_callback(debugmsg);
sprintf(debugmsg,"Successfully loaded avisynth and initialized%s\n",args_userscript->AsString());
debug_log_callback(debugmsg);
}
signal_startsequence=FALSE;//KEY part of the function. as this function is called multiple times before a conversion starts, we implement this signal
}
catch (AvisynthError e) {
m_errorMessage = e.msg;
debug_log_callback( (char*)e.msg);
//m_errorMessage = "Avisynth script error.";
throw PluginException(RPI_RESULT_INTERNAL_ERROR,m_errorMessage.c_str());
env =0;
}
//prototype of the function that gets frames from carbon and pushes them into the buffer for AVISynth CarbonSource() plugin
sub recieveDecodedFrameFromCarbon(CarbonVideoFrame newFrame){
//callback that delivers new frames
while ((sharedFramebufferForAvisynth.frameCount() * ms_per_inputframe) > sharedFramebufferForAvisynth.duration ){
//before pushing new frames into buffer, delete frames that we dont need anymore
sharedFramebufferForAvisynth.pop_Front(); //Deletes one frame from buffer, index 0 = oldest frame
sharedFramebufferForAvisynth.duration -= durationOfOneInputFrame;
}
while (sharedFramebufferForAvisynth.duration < max_input_buffer_duration_millisecs)
//buffer frame from carbon
//the pitch of the input frame for avisynth comes from the initialisation part, we dont ask avisynth for every frame, (we are being initialized newly when the resolution changes or so)
char* pdst = malloc(Input_Height * m_inputstride);
env->BitBlt(pdst, m_inputstride, newFrame->buffer, newFrame->stride, dRowSize, dHeight);
sharedFramebufferForAvisynth.push_Back (newFrame);
sharedFramebufferForAvisynth.duration -= durationOfOneInputFrame;
tellCarbonToKeepDeliveringFrames();//mandatory
return;//only after returning, carbon will call this callback function again
}
//when the code comes here, the buffer is full
tellCarbonToStopDeliveringFrames();//mandatory
tellCarbonToStartFetchingFrames();//mandatory
}
sub deliverFramesToCarbon(CarbonVideoFrame upstreamFrame){
//kicks off the avisynth processing of a frame
while (sharedFramebufferForAvisynth.duration > min_input_buffer_duration_millisecs || signal_flush_end == true){
//drag new frames from the bottom of the avs scriot (env) and copy it to carbon delivery buffer
try{//avisynth operation starts
//pull a frame from returned clip of imported avs script, the AVISynth CarbonSource plugin knows the address of sharedFramebufferForAvisynth and reads(read only) frames from it
AvisynthOutputFrame = (*Video)->GetFrame(m_OutputFrameCounter, env);//pull the next frame from AVISynth
m_resultUnit->stride = (int)AvisynthOutputFrame->GetPitch();//mandatory to do this for every frame
readPointer = (char*)AvisynthOutputFrame->GetReadPtr();
if (debug==true){
sprintf(debugmsg,"AVISynth output frame properties: height: %d, width: %d, stride(rowsize in bytes): %d\n", m_resultUnit->height,m_resultUnit->width,m_resultUnit->stride);
debug_log_callback(debugmsg);
}
m_OutputFrameCounter++;
}
catch(AvisynthError e){
...
}
//after avisynth operation copy the newly processed frame into outputbuffer
for (int i=0; i<m_resultUnit->height;i++){//todo: port to bitblt
unsigned char* _in = (unsigned char*) (in_ptr + (i *m_resultUnit->stride));
unsigned char* _out = (unsigned char*)(out_ptr + (i *m_outputstride));
memcpy(_out,_in,m_resultUnit->width*2);
}
upstreamFrame = m_resultUnit;
tellCarbonToStartFetchingFrames();//mandatory
}//while bufferduration > minbufferdur
//when the code comes here, the buffer is nearly empty or we are out of frames (end)
tellCarbonToStopFetchingFrames();//mandatory
tellCarbonToStartDeliveringFrames();//mandatory
}
jordanh
24th May 2013, 02:13
i was wrong.. it seems that all mt modes are fine now with the source plugins, but i seem to have some intialisation and destroying problems.
...need further testing ;-)
I am starting to get a feel for how Carbon interacts with the code you have posted.
But this fragment does not make a lot of sense. Is this a bad copy and paste? You have a while loop but the at the end is a return
You malloc pdst and fill it with content but do not use it.
You push newFrame but it seems to be an ephemeral object. while (sharedFramebufferForAvisynth.duration < max_input_buffer_duration_millisecs)
//buffer frame from carbon
//the pitch of the input frame for avisynth comes from the initialisation part, we dont ask
//avisynth for every frame, (we are being initialized newly when the resolution changes or so)
char* pdst = malloc(Input_Height * m_inputstride);
env->BitBlt(pdst, m_inputstride, newFrame->buffer, newFrame->stride, dRowSize, dHeight);
sharedFramebufferForAvisynth.push_Back (newFrame);
sharedFramebufferForAvisynth.duration -= durationOfOneInputFrame;
tellCarbonToKeepDeliveringFrames();//mandatory
return;//only after returning, carbon will call this callback function again
}
I take it each of these :- tellCarbonToStartDeliveringFrames(); means deliver (at least) 1 frame.
tellCarbonToKeepDeliveringFrames(); means quick give me 1 more frame.
tellCarbonToStopDeliveringFrames(); means stop I have enough.
tellCarbonToStartFetchingFrames(); means start asking for frames.
tellCarbonToStopFetchingFrames(); means stop asking for frames.
There is no KeepFetching because StartFetching is autonomous.
You apparently have a similar problem to the VirtualDub plugin wrapper in your design. The LoadVirtualDubPlugin has to be told the amount of preroll a VirtualDub Plugin needs. This is to allow for the skew between input frame number and output frame number with VirtualDub plugins.
For you an Avisynth script can need an arbitrary spread of input frames from the source filter to build an output frame. If the frame number is greater than what you have in your set of buffers, I guess you could increase the number of buffers and do a tellCarbonToStartDeliveringFrames(). But if the frame number is one that you have discarded then you appear stuck. I guess you need to be told this by the script author or set restrictions on what a script is allowed to ask the source filter for.
jordanh
24th May 2013, 10:11
You have a while loop but the at the end is a return
Yeah, i could also take an if instead of while. There is no way for deliveringor fetching more than one frame per run of each method... So a while is really useless.
You malloc pdst and fill it with content but do not use it.
You push newFrame but it seems to be an ephemeral object.[/list]
There are typos in the code, here is a corrected version:
if (sharedFramebufferForAvisynth.duration < max_input_buffer_duration_millisecs)
//buffer frame from carbon
//carbon calls this callback function and hands over the pointer to newframe, this contains the buffers of one frame (2 fields)
char* pdst = malloc(Input_Height * m_inputstride);
env->BitBlt(pdst, m_inputstride, newFrame->buffer, newFrame->stride, dRowSize, dHeight);
sharedFramebufferForAvisynth.push_Back (pdst);
sharedFramebufferForAvisynth.duration -= durationOfOneInputFrame;
tellCarbonToKeepDeliveringFrames();//mandatory
return;//only after returning, carbon will call this callback function again
}
I take it each of these :- tellCarbonToStartDeliveringFrames(); means deliver (at least) 1 frame.
tellCarbonToKeepDeliveringFrames(); means quick give me 1 more frame.
tellCarbonToStopDeliveringFrames(); means stop I have enough.
tellCarbonToStartFetchingFrames(); means start asking for frames.
tellCarbonToStopFetchingFrames(); means stop asking for frames.
There is no KeepFetching because StartFetching is autonomous.
Keep and start is exactly the same. Replace all "Keep" by Start. Also it is basically not 2 different things to tell carbon to stop one and start another action. You just tell carbon at the end of your delivery or fetching function that you either want him to call the same function again (deliver or fetch), or to switch to the opposite function (stop deliver, start fetch, or vice versa)
I just thought it may colorize my explainations to use stop and start functions...
Basically it is like this:
1) At the beginning, Carbon automatically callbacks my deliverframe function in order to hand me over me one frame
2) then i decide if i like to have more frames, or carbon should stop delivery and start fetching
3) if i signal to stop delivery and start fetching, carbon will call the my fetch function and give me the pointer for an emtpy frame (env->newframe similar) that i need to fill up
4) then i decide if i still have frames to fetch for carbon, or he should stop pulling frames and start pushing again.
5) we get a "no nore frames to deliver" (NULL unit) from carbon at the end of transcoding, then we must signal carbon to fetch again and flush our buffers
5) final end is reached when carbon delivers only NULL units and i signal carbon that i dont have anything more to deliver by setting the unit it wants to fetch to NULL. one full NULL units round means end of processing
You apparently have a similar problem to the VirtualDub plugin wrapper in your design. The LoadVirtualDubPlugin has to be told the amount of preroll a VirtualDub Plugin needs. This is to allow for the skew between input frame number and output frame number with VirtualDub plugins.
For you an Avisynth script can need an arbitrary spread of input frames from the source filter to build an output frame. If the frame number is greater than what you have in your set of buffers, I guess you could increase the number of buffers and do a tellCarbonToStartDeliveringFrames(). But if the frame number is one that you have discarded then you appear stuck. I guess you need to be told this by the script author or set restrictions on what a script is allowed to ask the source filter for.
Hm, i guess i could size the buffer automatically, but as i cannot keep too much frames in memory (32bit), it wouldnt make too much sense. I have 2 choices what to do when non existent frames are requested: just serve last frame from buffer, or throw an error. For ease of use reasons, i decided the first one.
I built in a user setting for the buffersize, the user can choose between 1 and 3 seconds input frame buffer. That enables a bit flexibility... But in the end i can only keep up to ... let say +/- 40 frames in the buffer, then the heap is full.
There is definitely a restriction on the stuff you can do in an avs script with that integraion... But its not too tight.
You know, most stuff can and should be done with carbon itself. So e.g. there is no need for a trim() function in your script, because you can use Carbon to do the trim.
Basically i concentrate on format and framerate conversion. The plugin targets to replace carbons internal scaling, deinterlacing, fps conversion and similar filters.
In attachment the current CarbonSource() plugin... From my point of view, it is fully compatible to all MT modes now... but still at some Modes like 1, after about 200 processed frames, i end up in some destroyed heap and another carbon filter in the upstream tries to access some memory thats not allocated. I believe my problem is in the upstream somewhere, but i have problems to figure out whats caused by MT and what from my code...
Might I suggest to avoid double copying of the video data that you only use PVideoFrames to buffer it.
In your recieveDecodedFrameFromCarbon you env->NewVideoFrame buffers as required and add them to your sharedFramebufferForAvisynth which you rework as std::deque<PVideoFrame> instead of std::deque<char*>. PVideoFrames are reference counted smart pointer and the contained buffer will exist for as long as something points to it. It also greatly simplifies your GetFrame routine which just become index calculation and range validation and a return of an existing PVideoFrame.
You will also need to build a template VideoInfo to use at this level, which you might as well just pass up a pointer to the source instances, simplifying that as well.
Not sure if it is a logic error or I do not understand how it works, but I have assumed if Carbon feeds in data we always want to keep it. In your code you ignored the copy if it was to full, easy to flip back that was the right case.
// prototype of the function that gets frames from carbon
// and pushes them into the buffer for AVISynth CarbonSource() plugin
sub recieveDecodedFrameFromCarbon(CarbonVideoFrame newFrame) {
//callback that delivers new frames
// before pushing new frames into buffer,
// delete frames that we dont need anymore
while ((sharedFramebufferForAvisynth.frameCount() * ms_per_inputframe)
> sharedFramebufferForAvisynth.duration ) {
// Deletes one frame from buffer, index 0 = oldest frame.
sharedFramebufferForAvisynth.pop_Front();
sharedFramebufferForAvisynth.duration -= durationOfOneInputFrame;
}
// buffer frame from carbon
// carbon calls this callback function and hands over the pointer
// to newframe, this contains the buffers of one frame (2 fields)
PVideoFrame pvframe = env->NewVideoFrame(vi);
BYTE* pdst = pvframe->GetWritePtr();
const int pitch = pvframe->GetPitch();
const int rowsize = pvframe->GetRowSize();
const int height = pvframe->GetHeight();
env->BitBlt(pdst, pitch, newFrame->buffer, newFrame->stride, rowsize, height);
sharedFramebufferForAvisynth.push_Back (pvframe);
sharedFramebufferForAvisynth.duration += durationOfOneInputFrame;
if (sharedFramebufferForAvisynth.duration < max_input_buffer_duration_millisecs) {
tellCarbonToKeepDeliveringFrames();//mandatory
//only after returning, carbon will call this callback function again
} else {
//when the code comes here, the buffer is full
tellCarbonToStopDeliveringFrames();//mandatory
tellCarbonToStartFetchingFrames();//mandatory
}
}
The GetFrame routine becomes greatly simplified. Just calculate the buffer index and return the PVideoFrame.
To implement returning a blank frame, just env->NewVideoFrame a class global PVideoFrame and fill in the appropriate pixel data. When ever you need it just return it. To release the buffer in your destructor just assign 0 to it.
PVideoFrame __stdcall CarbonSource::GetFrame(int n, IScriptEnvironment* env) {
// called by the calling application, the framebuffer_deque is
// entirely managed by the caller, we only access it for reading...
char message [500]; // logging
if (framebuffer_deque->size() < 1) {
sprintf(message, ("Error, Buffer is empty but frame was requested "));
debug_log_callback(message);
throw std::runtime_error("Error, Buffer is empty but frame was requested");
// todo: serve empty frame?
// return TheBlankFrame; // A preinitialised PVideoFrame
}
//Key part, Get the absolute Num of last frame in deque.
long numOfLastFrameInBuffer = env->GetVar("EXTERNAL_INPUT_LASTFRAMEINBUFFER").AsInt();
// calculate the index of n within the deque
// have fun while thinking about this ;-)
// the -1 is because deque.size is 1 based and all other numbers are zero based.
int frame_to_serve_bufferindex = framebuffer_deque->size()-1 - (numOfLastFrameInBuffer - n) ;
// compatibility mode: support programs that request impossible frames
// again +1 because deque.size is 1 based and other number not.
if (frame_to_serve_bufferindex < 0 || frame_to_serve_bufferindex > framebuffer_deque->size()-1) {
sprintf(message, "Fatal Error: requested Framenumber does not exist in buffer. "
"Framebuffer size: %d, index requested: %d",
framebuffer_deque->size(), frame_to_serve_bufferindex);
debug_log_callback(message);
frame_to_serve_bufferindex = 0;
}
if (debug==true){
sprintf(message, "CarbonSource GetFrame bufferindex of requested frame %d",
frame_to_serve_bufferindex);
debug_log_callback(message);
}
return (*framebuffer_deque)[frame_to_serve_bufferindex];
}
Had a thought about a better model for the data flow between the "Source" module and the main Carbon environment.
env->AddFunction allow you to pass an optional user data element to the function create routine. If you design an appropriate comms structure you can easily pass any useful information between the 2 layers. This would be much tidier than a set of script variables.
I envision something a bit like this :- try {
//load avisynth dll
avsdll = LoadLibrary("avisynth.dll");
// Create and initialise an IScriptEnvironment
env= CreateNewEnv();
// Create a comms struct
communication = new Communication;
memset(comunication, 0, sizeof(Communication));
// Initialise the comms struct
communication->vi.height = m_rpiInputVideoInfo.height;
communication->vi.width = m_rpiInputVideoInfo.width;
communication->vi.SetFPS(m_rpiInputVideoInfo.frameRateNumerator,
m_rpiInputVideoInfo.frameRateDenominator);
// hardcoded number of frames (output duration)
// todo: find out if there is a number for endless frame delivery (-1?)
// No just a big number!
communication->vi.num_frames = 900000000;
communication->input_video_fieldorder = m_rpiInputVideoInfo.interlaceMode;
// Add "CarbonSource", pass pointer to comms struct as user data.
env->AddFunction("CarbonSource", "", Create_CarbonSource, communication);
// the user of Carboncoder just enters the path to a script
// in the Video Filter configuration. No more inputs there.
AVSValue args_userscript[1] = { m_args.c_str() };
Video = env->Invoke("Import", AVSValue(args_userscript, 1)).AsClip();
avisynthOutputVideoInfo = Video->GetVideoInfo();
//clear possible old buffers
for (unsigned int i=0; i<framebuffer_deque.size(); i++) {//clear framebuffer
framebuffer_deque.pop_back();
}
framebuffer_deque.clear();
// KEY part of the function. as this function is called multiple times
// before a conversion starts, we implement this signal
signal_startsequence=FALSE;
}
catch (AvisynthError e) {
m_errorMessage = e.msg;
debug_log_callback( (char*)e.msg );
//m_errorMessage = "Avisynth script error.";
throw PluginException(RPI_RESULT_INTERNAL_ERROR, m_errorMessage.c_str());
env =0;
}
jordanh
2nd June 2013, 19:13
Might I suggest to avoid double copying of the video data that you only use PVideoFrames to buffer it.
Oh yes... thanks for that suggestion :-)
Not sure if it is a logic error or I do not understand how it works, but I have assumed if Carbon feeds in data we always want to keep it. In your code you ignored the copy if it was to full, easy to flip back that was the right case.
You are completely right. I just didnt expect anybody to think so hard about everything ;-)
So the production code just looks very similar to your suggestion.
env->AddFunction allow you to pass an optional user data element to the function create routine. If you design an appropriate comms structure you can easily pass any useful information between the 2 layers. This would be much tidier than a set of script variables.
Thats a great idea, but i dont know how to use the function after adding? - i thought about sharing dynamic per frame data with this method (number of last frame in buffer)
but i dont know how to use the function after adding? - i thought about sharing dynamic per frame data with this method (number of last frame in buffer)
Assuming the model is Carbon Coder loads your code, your code load avisynth.dll, creates a IScriptEnvironment, adds "CarbonSource" verb, loads the users Avisynth script, buffers some seconds worth of frames, then tells Carbon To Start Fetching Frames, etc, etc, ....
The loaded users Avisynth script starts with a "CarbonSource()" statement. You do not need to LoadDLL a plugin for "CarbonSource" now because it is part of your main code from the 'adds "CarbonSource" verb' step.
The thought about the user data pointer, was the comms structure could provide all the information, static and dynamic, that "CarbonSource()" needs to function, i.e. the VideoInfo, the framebuffer_deque, number of last frame in buffer, .... All the stuff currently in script variables.
Your thoughts ???
jordanh
2nd June 2013, 23:17
Hey IanB, thanks again for the brain and time you invest here!
Unfortunately i dont know how addfunction really works.
In my head it would be great if it works like this
//CarbonPlugin Avisynthsource.cpp
//after loading the userscript avs file that already includes CarbonSource():
env->AddFunction(getCarbonComm);
void __cdecl getCarbonComm(CarbonComm cc) {
cc=this->cc;
}
then the other side just using this as some kind of function
//AVISynth Source Filter CarbonSource.cpp
CarbonComm currentcomm;
env->execute("getCarbonComm",(void*)currentcomm);
..but i know meanwhile it doesnt work that way.
One of the biggest problems is the per-frame flexible data: numberoflastframeinbuffer. When i find a solution how to hand this over in a cleaner way than globalvariables, all other problem spots should be killed too
By the way, a small sidequestion for source filter related stuff: is there something special about the frameragenum 60000 (changefps(60000,1001))? when i do this framerateconversion, and then do getframe, only the first call for frame0 actually ends up in my own source filter. All other calls then just return the last (so the first) frame. Also at the one frame i get from the env, no filtering was done...
try{
Video->getFrame(env,n);//returns always the first frame without filters
}
catch(AVISynthError e){
//code never comes here
}
typedef AVSValue (__cdecl *ApplyFunc)(AVSValue args, void* user_data, IScriptEnvironment* env);
virtual void __stdcall AddFunction(const char* name, const char* params, ApplyFunc apply, void* user_data);
The AddFunction call adds a handler for a script function verb of "name", with argument list specified by "params". The callback function "apply" of type ApplyFunc is called whenever the script parser want to evaluate a script expression containing "name".
When the call back happens, the "apply" function is called with the parsed script arguments as an AVSValue array, the value of the optional "user_data" parameter provided with the original AddFunction call and a pointer to the current IScriptEnvironment. The answer is returned as an AVSValue, the type can be any AVSValue type, i.e. PClip, Integer, Float, String or Void.
You need to add any additional script function names before you invoke any user scripts.
=================================================================
You seem to be hung up on getting the per-frame flexible data: numberoflastframeinbuffer inside your CarbonSource code.
If you really need to do this as a separate .dll Avisynth plugin then stuffing the address of something into a script variable, AVSValue, is an easy way to do it. Adding your own extra script function gets you nothing extra, you will still be stuffing the answer into an AVSValue. Same result, different path.
My suggestion from a few posts back is put the code for "CarbonSource()" into the main Carbon Coder plugin which does :- Load avisynth.dll.
Create the IScriptEnvironment
Do the Carbon setup stuff.
Create a comms struct for all the juicy static and dynamic data you need to pass up and down.
AddFunction("CarbonSource", /* no args */ "", Create_CarbonSource, &communication);
Fill in the static data numbers.
Import the user script, "CarbonSource" is part of the script vocabulary.
Do some more Carbon set up.
Fill in the numberoflastframeinbuffer and whatever else.
Release the hounds .....
Fold up the IScriptEnvironment
Release other resources
Close up shop.
=================================================================
something special about the frameragenum 60000 (changefps(60000,1001))?My crystal ball is failing me, I don't understand frameragenum.
As a guess you are seeing something to do with the cache. With a 24fps source, applying a ChangeFps(60000,1001) should result in 2 GetFrame call to the source and 3 returns from the cache for each 5 frame output group.
Gavino
3rd June 2013, 09:56
With a 24fps source, applying a ChangeFps(60000,1001) should result in 1 GetFrame call to the source and 4 returns from the cache for each 5 frame output group.
Wouldn't it be 2 calls to the source and 3 returns from the cache in this case?
(2 original frames and 3 repeated ones for every 5 output, giving 24 original frames in 60 output)
jordanh
4th June 2013, 00:59
I guess Gavino is right...
Sorry, IanB.. i of course meant frameratenum. Just wanted to check if there is something about the number 60.000. Actually the question was what could be the difference between 60.000/1001(dont work) and 60.000/1000(works) It seems there isn't something special about that number, so i have to look on my side;-)
I am not sure if this is related to using the SetGlobalVar and on the other side the GetVar.asInt... maybe its just about signed and unsigned... But most common framerates work, so just a minor bug.
Its absolutely not like i am stuck. Everything seems to work fine.
My problem understanding your suggestion was, that handing over the pointer to a comm object was my first approach. But i felt this is too intrusive, so i changed to changed to env->SetVar.
The problem was the direction of passing the "instance" address. First i passed it from the avisynth plugin to the calling carboncoder program. Now i see you suggested doing the other way.
This should enable threadsafety, but dont support multiple instances of CarbonSource() in a single environment. (what for...)
Dont you feel thats too intrusive?
Sorry I just couldn't undo the 1 char typo. Frame rates internally are always stored as a rational pair, you should always write your algorithms based on these. At the script level there is the option of using a single precision IEEE float representation. The Avisynth ...FPS() functions have some limited intelligence in trying to notice numbers near 23.976, 29.97 and 59.94 and assume them back to 24000, 30000 & 60000 over 1001 for novice users. If the FPS matters then always use the numerator/denominator rational pair flavour.
Dont you feel thats too intrusive?
I don't know. You are only able to share restricted views of your project, so I am guessing about how things interact in places where a decision might matter. My current mental picture of your project leads me towards a single .dll approach with the details as above, but I don't have the whole picture so other consideration may lead to different conclusion. If you can explain your constraints without violating your NDA I am happy to try to help you here. If you need to discuss something privately send me a PM. I would like to keep as much of the discussion here as possible, this thread is becoming quite a good general reference.
jordanh
6th June 2013, 17:11
Ok, i feel we can close the intrusiveness topic. Both sides have a c++ api and thats it. By asking about this, i was only reffering to avisynth side. As i seem to understand from you too, it is just a natural thing to share memory and objects between dll's.
As i just ported to 2.6 alpha 4, i didnt have enough time to test MT, but on first peek it seemed to work at least very different than before. Also i wasnt able to get QTGMC running by now, must play with dll versions.
If you allow, a last question to the framerate topic (otherwise iths worth another thread).
The issue is, that clip->getFrame only causes the source filter to deliver the frames neccessary for one output frame (e.g. 2 times when coming from 25/1 and going to 60000/1001).
When i do the next clip->getframe, my source filter's getFrame routine is not called anymore. clip->getFrame returns always the pointer to the same Framebuffer.
60000/1001 dont work
120000/1001 works
120000/2002 dont work
So, the question is, what could cause getframe to return always the same frame, even if the Framenumber "n" thats passed to getframe is correct (0,1,2...)
In the next days, i do some MT testing and post the results here :-)
Thanks,
Harry
P.S. the log in attachment shows the start of a transcode process. somewhere at the top you find the 60.000 and 1001, they are passed correctly to the source plugin.
Later, after the buffer was filled, you see the first time getFrame called.
567857802: Processing Frame from Buffer. Requesting Frame Num: 0 from Avisynth duration of buffer= 1.00000 seconds, msPerOutputFrame = 0.01668 seconds
the next log entry is from the Avisynth plugin, not from the host.
637579014: CarbonSource GetFrame called...
As its MT mode 2, its called concurrently by one thread only. You also seee, that this is the last log entry from carbonsource, so the routine is never again called by the env.
jordanh
6th June 2013, 20:30
Uh, one new thing:
the framerate problem i have above is also dependent on input framerate. PAL dont work, NTSC does...
Also when i call getframe directly on the living instance of the avisynth CarbonSource source filter, it responds the correct frame. Even when the returned_clip->getFrame at the same time still delivers only the first frame buffer.
jordanh
7th June 2013, 02:12
OK, i got the fps problem. Very (!) interesting and informative.
After a bit of testing and thinking, trying to find out hot to get a debug view on the avisynth code. As i did not manage yet to get the needed knowledge about Assembler to port the current avisynth to visual studio 2010, i cannot debug directly.
In the end i did this:
Copy the changefps filter (fps.cpp, fps.h) from current avisynth source code and port it to a standalone filter.
So i created my own version of the changefps and were able to debug it from there. I recognized, that the first framenumber n of the getFrame routine in the fps filter, got the maximum number of frames +1 asked. Thats a problem. it should start at frame 0.
So the solution for my problem, and also a general advice for source filters:
// In your source filter, at initialisation (building the vi), take a high number if you want to deliver endless frames. But dont take it too high.
vi.num_frames = 900000000; //does not work, number too high
vi.num_frames = 4320000; //48 hours @25 fps, this should work in most cases, when you dont have ridiculous framerates over 1000fps or so
Now, i felt up in front that this is connected to initialisation and some datatype (signed, unsigend, int, double..)...
Before i add this to the avisynth.org c++ wiki, can anyone tell me, if i am right, that the problem is just that
1) the max. framenumber in avisynth is a 32bit signed int, so it is decimal: 2.147.483.648
2) we need to keep the number below this value. As far below as neccessary to be able to feed enough frames into avisynth and in the end, with all framerate conversions, the number of delivered frames may not be more than signed int (32bit)
Thanks!
jordanh
7th June 2013, 02:32
Now i am close before release. I also did some MT tests and it showed to be very well.
How could i possibly test if my Source Filter does all MT Modes correctly at all possible video properties and so on?
Yep frame count is signed int32.
900,000,000 * 60,000 / (1,001 * 25) = 2,157,842,157 [0x809E0EED -> -2,137,125,139] which is greater than 2,147,483,647
vBulletin® v3.8.11, Copyright ©2000-2025, vBulletin Solutions Inc.