GCC Code Coverage Report


Directory: avs_core/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 0.0% 0 / 0 / 328
Functions: 0.0% 0 / 0 / 35
Branches: 0.0% 0 / 0 / 676

core/FilterGraph.cpp
Line Branch Exec Source
1 #include "avs/config.h"
2 #include "FilterGraph.h"
3 #include "DeviceManager.h"
4 #include "InternalEnvironment.h"
5 #include "strings.h"
6
7 #ifdef AVS_WINDOWS
8 #include <avs/win.h>
9 #else
10 #include <avs/posix.h>
11 #endif
12
13 #include <map>
14 #include <iostream>
15 #include <string>
16 #include <sstream>
17 #include <iomanip>
18
19 #include <avs/filesystem.h>
20
21 static AVSValue DeepCopyValue(std::vector<std::unique_ptr<AVSValue[]>>& arrays, const AVSValue& src) {
22 if (src.IsArray()) {
23 AVSValue* copy = new AVSValue[src.ArraySize()];
24 for (int i = 0; i < src.ArraySize(); ++i) {
25 copy[i] = src[i]; // NEW_AVSVALUE is already doing deep copy
26 // copy[i] = DeepCopyValue(arrays, src[i]);
27 }
28 arrays.emplace_back(std::unique_ptr<AVSValue[]>(copy));
29 return AVSValue(copy, src.ArraySize());
30 }
31 return src;
32 }
33
34 FilterGraphNode::FilterGraphNode(PClip child, const char* name,
35 const AVSValue& last_, const AVSValue& args_, const char* const* argnames_,
36 IScriptEnvironment* env)
37 : Env(env)
38 , child(child)
39 , name(name)
40 , memory(new GraphMemoryNode())
41 {
42 if (last_.Defined()) {
43 std::vector<AVSValue> argstmp;
44 argstmp.push_back(last_);
45 if (argnames_) {
46 argnames.push_back(std::string());
47 }
48 for (int i = 0; i < args_.ArraySize(); ++i) {
49 argstmp.push_back(args_[i]);
50 }
51 args = DeepCopyValue(arrays, AVSValue(argstmp.data(), (int)argstmp.size()));
52 }
53 else {
54 args = DeepCopyValue(arrays, args_.IsArray() ? args_ : AVSValue(args_, 1));
55 }
56
57 if (argnames_) {
58 for (int i = 0; i < args_.ArraySize(); ++i) {
59 argnames.push_back(argnames_[i] ? std::string(argnames_[i]) : std::string());
60 }
61 }
62
63 Env->ManageCache(MC_RegisterGraphNode, this);
64 }
65
66 FilterGraphNode::~FilterGraphNode()
67 {
68 Env->ManageCache(MC_UnRegisterGraphNode, this);
69 }
70
71 struct ScopedGraphNode {
72 FilterGraphNode*& target;
73 FilterGraphNode* prev;
74 ScopedGraphNode(FilterGraphNode*& target, FilterGraphNode* node) : target(target) {
75 prev = target;
76 target = node;
77 }
78 ~ScopedGraphNode() {
79 target = prev;
80 }
81 };
82
83 PVideoFrame __stdcall FilterGraphNode::GetFrame(int n, IScriptEnvironment* env_)
84 {
85 InternalEnvironment* env = GetAndRevealCamouflagedEnv(env_);
86
87 ScopedGraphNode scope(env->GetCurrentGraphNode(), this);
88 return child->GetFrame(n, env);
89 }
90
91 void GraphMemoryNode::OnAllocate(size_t bytes, Device* dev)
92 {
93 auto it = memory.find(dev);
94 if (it == memory.end()) {
95 memory[dev] = MemoryInfo();
96 it = memory.find(dev);
97 }
98 it->second.numAllocation++;
99 it->second.totalBytes += bytes;
100 }
101
102 void GraphMemoryNode::OnFree(size_t bytes, Device* dev)
103 {
104 auto it = memory.find(dev);
105 if (it == memory.end()) {
106 printf("Unexpected behavior ...\n");
107 return;
108 }
109 it->second.numAllocation--;
110 it->second.totalBytes -= bytes;
111 }
112
113 class FilterGraph
114 {
115 protected:
116 IScriptEnvironment * env;
117
118 struct NodeInfo {
119 bool isFunction;
120 int number;
121 std::string name;
122 std::string args;
123 std::vector<void*> refNodes;
124
125 int cacheSize;
126 int cacheCapacity;
127 std::map<Device*, GraphMemoryNode::MemoryInfo> memory;
128
129 NodeInfo() { }
130 NodeInfo(int number) : number(number) { }
131 };
132
133 std::map<void*, NodeInfo> nodeMap;
134
135 int DoClip(IClip* pclip) {
136 if (nodeMap.find(pclip) == nodeMap.end()) {
137 nodeMap.insert(std::make_pair(pclip, (int)nodeMap.size()));
138 FilterGraphNode* node = dynamic_cast<FilterGraphNode*>(pclip);
139 if (node != nullptr) {
140 NodeInfo& info = nodeMap[node];
141 info.isFunction = false;
142 info.name = node->name;
143 info.args = "(" + DoArray(info, nullptr, node->argnames.data(), node->args) + ")";
144 info.cacheSize = node->SetCacheHints(CACHE_GET_SIZE, 0);
145 info.cacheCapacity = node->SetCacheHints(CACHE_GET_CAPACITY, 0);
146 info.memory = node->memory->memory;
147 }
148 OutClip(nodeMap[node]);
149 }
150 return nodeMap[pclip].number;
151 }
152
153 int DoFunc(IFunction* pfunc) {
154 if (nodeMap.find(pfunc) == nodeMap.end()) {
155 nodeMap.insert(std::make_pair(pfunc, (int)nodeMap.size()));
156 NodeInfo& info = nodeMap[pfunc];
157 info.isFunction = true;
158 auto captures = pfunc->GetCaptures();
159 info.name = pfunc->ToString(env);
160 info.args = "[" + DoArray(info, captures.var_names, nullptr, AVSValue(captures.var_data, captures.count)) + "]";
161 info.cacheSize = 0;
162 info.cacheCapacity = 0;
163 OutFunc(info);
164 }
165 return nodeMap[pfunc].number;
166 }
167
168 std::string DoArray(NodeInfo& info, const char** argnames_c, std::string* argnames_s, const AVSValue& arr) {
169 std::stringstream ss;
170 int breakpos = 0;
171 int maxlen = 60;
172
173 for (int i = 0; i < arr.ArraySize(); ++i) {
174 if (i != 0) {
175 ss << ",";
176 }
177 if (argnames_c && argnames_c[i]) {
178 ss << argnames_c[i] << "=";
179 }
180 if (argnames_s && argnames_s[i].size() > 0) {
181 ss << argnames_s[i] << "=";
182 }
183 const AVSValue& v = arr[i];
184 if (!v.Defined()) {
185 ss << "default";
186 }
187 else if (v.IsClip()) {
188 IClip* pclip = (IClip*)(void*)v.AsClip();
189 int clipnum = DoClip(pclip);
190 ss << "clip" << (clipnum + 1);
191 info.refNodes.push_back(pclip);
192 }
193 else if (v.IsFunction()) {
194 IFunction* pfunc = (IFunction*)(void*)v.AsFunction();
195 int funcnum = DoFunc(pfunc);
196 ss << "func" << (funcnum + 1);
197 info.refNodes.push_back(pfunc);
198 }
199 else if (v.IsArray()) {
200 ss << OutArray("(" + DoArray(info, nullptr, nullptr, v) + ")");
201 }
202 else if (v.IsBool()) {
203 ss << (v.AsBool() ? "True" : "False");
204 }
205 else if (v.IsInt()) {
206 ss << v.AsInt();
207 }
208 else if (v.IsFloat()) {
209 ss << std::setprecision(8) << v.AsFloat();
210 }
211 else if (v.IsString()) {
212 ss << "\"" << v.AsString() << "\"";
213 }
214 else {
215 ss << "<error>";
216 }
217 if ((int)ss.tellp() - breakpos > maxlen) {
218 ss << "\n";
219 breakpos = (int)ss.tellp();
220 }
221 }
222 return ss.str();
223 }
224
225 virtual void OutClip(const NodeInfo& info) = 0;
226 virtual void OutFunc(const NodeInfo& info) = 0;
227 virtual std::string OutArray(const std::string& args) = 0;
228
229 public:
230 int Construct(FilterGraphNode* root, IScriptEnvironment* env_)
231 {
232 env = env_;
233 nodeMap.clear();
234 return DoClip(root);
235 }
236 void Construct(const std::vector<FilterGraphNode*>& roots, IScriptEnvironment* env_)
237 {
238 env = env_;
239 nodeMap.clear();
240 for (auto node : roots) {
241 if (node != nullptr) {
242 DoClip(node);
243 }
244 }
245 }
246 };
247
248 static void ReplaceAll(std::string& str, const std::string& from, const std::string& to) {
249 size_t start_pos = 0;
250 while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
251 str.replace(start_pos, from.length(), to);
252 start_pos += to.length();
253 }
254 }
255
256 class AvsScriptFilterGraph : private FilterGraph
257 {
258 std::stringstream ss;
259
260 protected:
261 virtual void OutClip(const NodeInfo& info) {
262 int num = info.number + 1;
263 if (info.name.size() == 0) {
264 ss << "clip" << num << ": Failed to get information" << std::endl;
265 }
266 else {
267 auto args = info.args;
268 ReplaceAll(args, "\n", "");
269 ss << "clip" << num << " = " << info.name << args << std::endl;
270 }
271 }
272 virtual void OutFunc(const NodeInfo& info) {
273 int num = info.number + 1;
274 auto args = info.args;
275 ReplaceAll(args, "\n", "");
276 ss << "func" << num << " = function" << args << "(){ " << info.name << " }" << std::endl;
277 }
278
279 int nextArrayNumber = 0;
280 virtual std::string OutArray(const std::string& args) {
281 std::string name = std::string("array") + std::to_string(++nextArrayNumber);
282 ss << name;
283 ss << " = ArrayCreate" << args << std::endl;
284 return name;
285 }
286 public:
287
288 void Construct(FilterGraphNode* root, IScriptEnvironment* env) {
289 int last = FilterGraph::Construct(root, env);
290 ss << "return clip" << (last + 1) << std::endl;
291 }
292
293 std::string GetOutput() {
294 return ss.str();
295 }
296 };
297
298 class DotFilterGraph : private FilterGraph
299 {
300 bool enableArgs;
301 bool enableMemory;
302 std::stringstream ss;
303
304 protected:
305
306 void printfcomma(size_t n) {
307 if (n < 1000) {
308 ss << n;
309 return;
310 }
311 printfcomma(n / 1000);
312 ss << ',' << std::setfill('0') << std::setw(3) << (n % 1000);
313 }
314
315 virtual void OutClip(const NodeInfo& info) {
316 int num = info.number + 1;
317 ss << "clip" << num;
318 if (info.name.size() == 0) {
319 ss << " [label = \"...\"];" << std::endl;
320 }
321 else {
322 if (enableArgs) {
323 std::string label = info.name + info.args;
324 ReplaceAll(label, "\\", "\\\\");
325 ReplaceAll(label, "\"", "\\\"");
326 ReplaceAll(label, "\n", "\\n");
327 ss << " [label = \"" << label << "\"];" << std::endl;
328 }
329 else {
330 if (info.cacheCapacity != 0) {
331 ss << " [label = \"" << info.name << "(caching " << info.cacheSize << " frames with capacity " << info.cacheCapacity << ")";
332 }
333 else {
334 ss << " [label = \"" << info.name;
335 }
336 if (enableMemory) {
337 for (auto entry : info.memory) {
338 ss << "\\n" << entry.first->GetName() << ": " << entry.second.numAllocation << " frames, ";
339 printfcomma(entry.second.totalBytes);
340 ss << " bytes";
341 }
342 }
343 ss << "\"];" << std::endl;;
344 }
345 }
346 for (void* pclip : info.refNodes) {
347 auto& node = nodeMap[pclip];
348 int refnum = node.number + 1;
349 if (node.isFunction) {
350 ss << "func" << refnum << " -> " << "clip" << num << ";" << std::endl;
351 }
352 else {
353 ss << "clip" << refnum << " -> " << "clip" << num << ";" << std::endl;
354 }
355 }
356 }
357 virtual void OutFunc(const NodeInfo& info) {
358 int num = info.number + 1;
359 ss << "func" << num;
360 if (enableArgs) {
361 std::string label = info.name + "\n" + info.args;
362 ReplaceAll(label, "\\", "\\\\");
363 ReplaceAll(label, "\"", "\\\"");
364 ReplaceAll(label, "\n", "\\n");
365 ss << " [label = \"" << label << "\"];" << std::endl;
366 }
367 else {
368 ss << " [label = \"" << info.name << "\"];" << std::endl;
369 }
370 for (void* pclip : info.refNodes) {
371 auto& node = nodeMap[pclip];
372 int refnum = node.number + 1;
373 if (node.isFunction) {
374 ss << "func" << refnum << " -> " << "func" << num << ";" << std::endl;
375 }
376 else {
377 ss << "clip" << refnum << " -> " << "func" << num << ";" << std::endl;
378 }
379 }
380 }
381
382 int nextArrayNumber = 0;
383 virtual std::string OutArray(const std::string& args) {
384 std::string name = std::string("array") + std::to_string(++nextArrayNumber);
385 //ss << name;
386 //ss << " = ArrayCreate" << args << std::endl;
387 return name;
388 }
389 public:
390
391 void Construct(FilterGraphNode* root, bool enableArgs, bool enableMemory, IScriptEnvironment* env) {
392 this->enableArgs = enableArgs;
393 this->enableMemory = enableMemory;
394 ss << "digraph avs_filter_graph {" << std::endl;
395 ss << "node [ shape = box ];" << std::endl;
396 int last = FilterGraph::Construct(root, env);
397 ss << "GOAL;" << std::endl;
398 ss << "clip" << (last + 1) << " -> GOAL" << std::endl;
399 ss << "}" << std::endl;
400 }
401
402 void Construct(const std::vector<FilterGraphNode*>& roots, bool enableArgs, bool enableMemory, IScriptEnvironment* env) {
403 this->enableArgs = enableArgs;
404 this->enableMemory = enableMemory;
405 ss << "digraph avs_filter_graph {" << std::endl;
406 ss << "node [ shape = box ];" << std::endl;
407 FilterGraph::Construct(roots, env);
408 ss << "}" << std::endl;
409 }
410
411 std::string GetOutput() {
412 return ss.str();
413 }
414 };
415
416 // Helper: open a file whose path is encoded as UTF-8.
417 // On Windows convert to wide string and use _wfopen; on POSIX just call fopen.
418 static FILE* OpenFileUtf8(const std::string& path_utf8, const char* mode) {
419 #ifdef AVS_WINDOWS
420 if (path_utf8.empty() || mode == nullptr) return nullptr;
421
422 // Convert UTF-8 path to wide string using helper from strings.h
423 std::wstring wpath = Utf8ToWideChar(path_utf8.c_str()).get();
424 if (wpath.empty()) return nullptr;
425
426 // Convert mode to wide string. Mode is usually ASCII but convert via Utf8 helper for consistency.
427 std::wstring wmode = Utf8ToWideChar(mode).get();
428 if (wmode.empty()) return nullptr;
429
430 return _wfopen(wpath.c_str(), wmode.c_str());
431 #else
432 return fopen(path_utf8.c_str(), mode);
433 #endif
434 }
435
436 void DoDumpGraph(const std::vector<FilterGraphNode*>& roots, const char* path_utf8, IScriptEnvironment* env)
437 {
438 DotFilterGraph graph;
439 graph.Construct(roots, true, true, env);
440 std::string ret = graph.GetOutput();
441
442 FILE* fp = OpenFileUtf8(std::string(path_utf8), "w");
443 if (fp == nullptr) {
444 env->ThrowError("Could not open output file ...");
445 }
446 fwrite(ret.data(), ret.size(), 1, fp);
447 fclose(fp);
448 }
449
450 static void DoDumpGraph(PClip clip, int mode, const char* path_utf8, IScriptEnvironment* env)
451 {
452 FilterGraphNode* root = dynamic_cast<FilterGraphNode*>((IClip*)(void*)clip);
453
454 std::string ret;
455
456 if (mode == 0) {
457 AvsScriptFilterGraph graph;
458 graph.Construct(root, env);
459 ret = graph.GetOutput();
460 }
461 else if (mode == 1 || mode == 2) {
462 DotFilterGraph graph;
463 graph.Construct(root, mode == 1, true, env);
464 ret = graph.GetOutput();
465 }
466 else {
467 env->ThrowError("Unknown mode (%d)", mode);
468 }
469
470 FILE* fp = OpenFileUtf8(std::string(path_utf8), "w");
471 if (fp == nullptr) {
472 env->ThrowError("Could not open output file ...");
473 }
474 fwrite(ret.data(), ret.size(), 1, fp);
475 fclose(fp);
476 }
477
478 class DelayedDump : public GenericVideoFilter
479 {
480 std::string outpath_utf8;
481 int mode;
482 int nframes;
483 bool repeat;
484 std::vector<bool> fired;
485 public:
486 DelayedDump(PClip clip, const std::string& outpath_utf8, int mode, int nframes, bool repeat)
487 : GenericVideoFilter(clip)
488 , outpath_utf8(outpath_utf8)
489 , mode(mode)
490 , nframes(nframes)
491 , repeat(repeat)
492 {
493 if (repeat) {
494 fired.resize((clip->GetVideoInfo().num_frames + nframes - 1) / nframes);
495 }
496 else {
497 fired.resize(1);
498 }
499 }
500
501 PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env)
502 {
503 if (repeat) {
504 int slot = std::max(0, std::min(n / nframes, (int)fired.size() - 1));
505 if (fired[slot] == false) {
506 fired[slot] = true;
507 // Build path by inserting "-<n>" before extension (if any), preserve UTF-8
508 std::string basename = outpath_utf8;
509 size_t pos = basename.find_last_of('.');
510 std::string path;
511 if (pos != std::string::npos) {
512 path = basename.substr(0, pos) + "-" + std::to_string(n) + basename.substr(pos);
513 } else {
514 path = basename + "-" + std::to_string(n);
515 }
516 DoDumpGraph(child, mode, path.c_str(), env);
517 }
518 }
519 else {
520 if (n == nframes && fired[0] == false) {
521 fired[0] = true;
522 DoDumpGraph(child, mode, outpath_utf8.c_str(), env);
523 }
524 }
525 return child->GetFrame(n, env);
526 }
527 };
528
529 static AVSValue DumpFilterGraph(AVSValue args, void* user_data, IScriptEnvironment* env) {
530 PClip clip = args[0].AsClip();
531 FilterGraphNode* root = dynamic_cast<FilterGraphNode*>((IClip*)(void*)clip);
532 if (root == nullptr) {
533 env->ThrowError("clip is not a FilterChainNode. Ensure you have enabled the chain analysis by SetGraphAnalysis(true).");
534 }
535
536 int mode = args[2].AsInt(0);
537 const char* path = args[1].AsString("");
538 int nframes = args[3].AsInt(-1);
539 bool repeat = args[4].AsBool(false);
540
541 const bool utf8 = args[5].AsBool(false);
542 // DumpFiltergraph can also support yet forced UTF-8 mode in ANSI-only processes.
543
544 std::string path_utf8;
545 #ifdef AVS_WINDOWS
546 // internally use UTF-8 path
547 if (!utf8)
548 path_utf8 = AnsiToUtf8(path).get();
549 else
550 path_utf8 = path;
551 #else
552 path_utf8 = path;
553 #endif
554
555 if (nframes >= 0) {
556 return new DelayedDump(clip, GetFullPathNameWrapUtf8(path_utf8), mode, nframes, repeat);
557 }
558
559 // For immediate dumps, normalize and let DoDumpGraph handle UTF-8 aware opening.
560 DoDumpGraph(clip, mode, path_utf8.c_str(), env);
561
562 return clip;
563 }
564
565 static AVSValue __cdecl SetGraphAnalysis(AVSValue args, void* user_data, IScriptEnvironment* env_) {
566 InternalEnvironment* env = GetAndRevealCamouflagedEnv(env_);
567 env->SetGraphAnalysis(args[0].AsBool());
568 return AVSValue();
569 }
570
571 extern const AVSFunction FilterGraph_filters[] = {
572 { "SetGraphAnalysis", BUILTIN_FUNC_PREFIX, "b", SetGraphAnalysis, nullptr },
573 { "DumpFilterGraph", BUILTIN_FUNC_PREFIX, "c[outfile]s[mode]i[nframes]i[repeat]b[utf8]b", DumpFilterGraph, nullptr },
574 { 0 }
575 };
576