Log in

View Full Version : Streaming Using Avisynth


SMurf
15th August 2012, 02:42
Hello,

This may be the wrong tool for doing this, but I have a need to set up a MPC playlist of videos interspersed with slideshows featuring an audio track. This will run full screen on a display in a waiting room. The images used in the slideshows will change regularly, so I don't think it's a good idea to try to produce a single large video from the playlist every time.

I installed Avisynth and created a simple user-defined function to load an image, scale/center it to the screen size and show it for three seconds:-

function SlideImage(string image)
{
scrW = 1920
scrH = 1200
duration = 3

img = ImageSource(image, end=1, fps=1)
img = ((Float(scrW) / img.width) < (Float(scrH) / img.height)) ? Spline64Resize(img, scrW, Round((Float(scrW) / img.width) * img.height)) : Spline64Resize(img, Round((Float(scrH) / img.height) * img.width), scrH)
img = AddBorders(img, (scrW - img.width) / 2, (scrH - img.height) / 2, (scrW - img.width) / 2, (scrH - img.height) / 2)
img = AddBorders(img, 0, 0, scrW - img.width, scrH - img.height)
return Loop(img, duration)
}

I called this function a few times in the script, splicing the result into a clip variable called "video" and finished off with:-

return AudioDub(ChangeFPS(video, 25), DirectShowSource("bg.ogg"))

I had to change the frame rate to 25 fps because otherwise MPC displays nothing (but still plays the audio). Might be something to do with the VMR renderer, I don't know.

Although this works, it seems to lag and consume a ridiculous amount of memory. I believe this is because Avisynth is storing each frame in memory to allow seeking. Given the size of each frame this isn't practical! :o

I would prefer it if Avisynth produced non-indexed (not seekable) output, discarding each rendered frame. Is there a way to do this, or am I using the wrong software?

gyth
15th August 2012, 18:47
I'm not sure what the problem is.
How many images are you trying to load?

The end=1 is telling it to look for a file that likely doesn't exist.

Although this works, it seems to lag and consume a ridiculous amount of memory.
The cache will go to 25% of available memory, but should max at 512MB. Is it bigger than that?

You can lower the cache size with SetMemoryMax.
But if the cache size is too small that can cause problems too.

Try adding SetMemoryMax(32) to the script.

I rewrote your script a bit, but I don't think any of the changes will help your problem.
function SlideImage(string image, int "srcW", int "srcH", float "duration")
{
final_W = default(srcW, 1920)
final_H = default(srcH, 1200)
duration = default(duration, 3.0)

img = ImageSource(image, end = 0, fps = 1.0 / duration)
w = img.width
h = img.height

tmp_H = h * final_W / w
letter = final_H - tmp_H
top = letter / 2
bottom = letter - top
letterbox = img.Spline64Resize(final_W, tmp_H)\
.AddBorders(0, top, 0, bottom)

tmp_W = w * final_H / h
pillar = final_W - tmp_W
left = pillar / 2
right = pillar - left
pillarbox = img.Spline64Resize(tmp_W, final_H)\
.AddBorders(left, 0, right, 0)


((w * final_H) > (h * final_W)) ? \
letterbox : pillarbox
}

SMurf
15th August 2012, 21:55
The slideshow is 40 images at the moment.

Just watching mpc-hc.exe in Task Manager, using either my or your version memory usage peaks at 558 MB, appearing pretty stable at 368 MB. Audio does lag in either case.

If I add SetMemory(32), usage goes down to 68 MB, but performance is the same.

I don't have much horsepower at my disposal (Intel Atom 1.6GHz, 2GB RAM) but if I play the .ogg on its own and run the slideshow full screen in IrfanView at the same time, CPU usage is negligible. Based upon this, I believe it's possible to get this working lag-free and in sync with the playlist. Perhaps I should use a simpler resize filter?

IanB
16th August 2012, 00:18
Each instance of the ImageSource() filter maintains a buffer for holding the decoded image. e.g. for a RGB24 8 mega-pixel image this will be 24 Megabytes.

If you splice a number of single picture ImageSource() calls together, every instance allocates a buffer as it loads it's image. The buffer is only freed when the entire script exits. So for 40 of 8 mega-pixel images this is 960Mb. These buffers will exceed any SetMemoryMax() limit.

Using ImageSource() in multi-picture mode avoids the problem. As each new picture is loaded into the same single buffer. Of course all the images have to be the same size, orientation and format.

Others have worked around the problem by using ScriptClip() to dynamically load and release single instances of ImageSource() plus a resizer. ScriptClip() compiles a segment of script, runs it and then releases all the resources on a per frame basis.

If you can solve the memory usage problem, processing 1920x1200 images is still a fair challenge horsepower wise. Using a simpler Spline16Resize() or BicubicResize() will reduce load quite a bit. Your logic of processing the images as single frame clips and using loop to clone that frame is a very sound idea, the ChangeFPS() filter works in much the same way.

SMurf
16th August 2012, 23:13
I guess it doesn't help that:-

Most of the images are sourced from a 7 megapixel camera;
During testing, I had Firefox running in the background. :o

What I've decided to do is pre-scale them on demand using a batch file and NConvert. All the AVS script has to do now is add borders if required. The upshot of this is that I can get NConvert to output sequentially-numbered images and make one call to ImageSource.

A new challenge I face is that given a image sequence, each individual frame must be looped <duration> seconds. My function now looks like:-

function SlideImage(string image, int "start", int "end")
{
start = Default(start, 0)
end = Default(end, 1)
duration = 3

# do AddBorders after I figure looping out
return ImageSource(image, start, end - 1, 1).ApplyRange(0, end - start, "Loop", duration)
}

Given the call SlideImage("D:\nconvert\%02d.bmp", 1, 40), this should produce a clip consisting of the files 01.bmp to 40.bmp running at 1 fps where each frame is looped 3 times, but it doesn't seem to be working. :(

I think I'm gradually getting the hang of this. :)

SMurf
17th August 2012, 03:13
Ah ha, cracked it! :D

# for consistency
global clipW = 1920
global clipH = 1200

function SlideImage(string image, int "start", int "end", float "fps", int "duration")
{
start = Default(start, 0)
end = Default(end, 0)
fps = Default(fps, 2)
duration = Default(duration, 3)

out = ImageSource(image, start, end, 1)
Assert(out.Width() <= clipW, "Image too wide")
Assert(out.Height() <= clipH, "Image too high")
left = (clipW - out.Width()) / 2
right = clipW - left
top = (clipH - out.Height()) / 2
bottom = clipH - top
(out.Width() < clipW) ? out.AddBorders(left=left, right=right) : Nop()
(out.Height() < clipH) ? out.AddBorders(top=top, bottom=bottom) : Nop()
return out.ChangeFPS(fps * duration).AssumeFPS(fps)
}

Import("playlist.avs") # populates audio
video = SlideImage("%02d.bmp", 1, 40)
AudioFrameCount = (FrameRate(video) * audio.AudioLength()) / audio.AudioRate()
return AudioDub(video.Loop(Ceil(AudioFrameCount / video.FrameCount())).Trim(0, -Ceil(AudioFrameCount)), audio)