Possible Memory Leak with stageVideo?
I have an actionscript application the plays in a fullscreen browser. It loops through three H.264 movies, employing stageVideo. The stageVideo is working fine, however, over the course of several hours the application rapidly consumes a lot of memory (1.5GB approx). At that point memory consumption stabilizes (see attached graph -- the high point is where I re-started the app) While sometimes the application continues to play, at other times it hangs. If I use software rendering rather than the gpu accelerated stageVideo, the memory utilization remains constant.

*Note: The above graph is of the application running in Safari 32bit mode, with the normal (non-debugger) flash player.
Has anyone seen anything similar? If you have time, please read on an let me know if you spot any issues with my approach:
Here are the particulars:
Platform: Mac Mini, Intel Core 2 Duo (2.4 Ghz)
OS: Mac OSX 10.6.8
Installed memory:4GB
Video card: Nvidia 320M
Cuda driver version: 4.0.19
GPU driver version: 1.6.37.0 (256.02.25f01)
Browsers tested: Safari 5.05, Firefox 5.0.1, Google Chrome 12.0.742.122
Flash player version: 10.3.181.34
Movies: (2) are 10MB in size and (1) is 5 MB. They are encoded in mainline H.264. Their native resolution is 1024X768, and their bit rates vary between 2.5-4 Mbps
I have tested the swf on three different browsers listed above and get the same behavior.
I ran the application through the Flex Profiler to look for memory leaks in the application itself. After a few hours, according to the Flex Profiler the application memory consumption remains stable at around 40K. Analyzing memory snapshots taking at the start of the application and a few hours in shows that there are no unexpected loitering objects. Nonetheless, a look at the system memory shows that the memory being consumed by the kernel_task process has ballooned to nearly 1.4GB (see attached image of the output for "top" -- note processes are sorted by rsize of memory)

Note: While the flex profiler requires a debugger version of the flash player -- which can inflate real memory statistics (which are not displayed in the profiler itself) the above "top" output is from a normal (non-debugger) version of the player with the application running in a 32bit version of Safari. This output is not from the Flex Profiler experiment.
The above images show the result of using a 32bit (as that is Adobe's recommendation). Still, I have tested with 64bit browsers and I get the same memory consumption patterns. In those cases the output of "top" shows that both the kernel_task and the WebKitPlugin processes balloon to roughly 1.4GB each.
Now onto the actionscript:
I took 2 approaches each of which result in the same memory consumption patters:
1) re-using the VideoConnection and NetStream objects with each new movie
2) closing the VideoConnection and NetStream objects and setting them to null and then creating new objects with each movie
The code below shows the second approach.
My gut feeling is that whatever process handles the GPU decoding is allocating memory which is never reclaimed. I don't think I've done anything wrong with the code as evidenced by the result of the Flex Profiler output (unless the profiler is not yet able to analyze stageVideo events?!). If anyone could prove me wrong I would be overjoyed.
Thanks for your help and feedback!
- Josh
CODE:
package
{
import flash.display.*;
import flash.events.*;
import flash.net.*;
import flash.utils.*;
import flash.media.*;
import flash.geom.*;
public class stageVideoMemoryTest extends MovieClip
{
var duration:uint=0;
var reload:Boolean = false;
var timer4:Timer;
var movieArray:Array;
var urlArray:Array;
var curr:uint = 0;
var playmenow:String;
var orig_width:Number;
var orig_height:Number;
var aspect:Number;
var containerAspect:Number;
var netStream:NetStream;
var containerWidth:Number;
var containerHeight:Number;
var videoConnection:NetConnection;
var videoFinished:uint = 0;
var stageVideoAvailable:Boolean;
var stageVideo:StageVideo;
var videoWidth:uint;
var videoHeight:uint;
var videoX:uint = 0;
var videoY:uint = 0;
var v:Vector.<StageVideo>;
public function stageVideoMemoryTest()
{
trace("stageVideoMemoryTest has been instantiated");
curr = 0;
// screen size
containerWidth = 1080;
containerHeight = 1920;
this.blendMode = BlendMode.LAYER;
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
v = stage.stageVideos;
if ( v.length >= 1 ) {
stageVideoAvailable = true;
stage.color = 0x000000;
trace("StageVideo available. Number of instances: " + v.length);
} else {
stageVideoAvailable = false;
trace("StageVideo is unavailable.");
}
movieArray = new Array();
movieArray.push("http://localhost/movie1.mov");
movieArray.push("http://localhost/movie2.mov");
movieArray.push("http://localhost/movie3.mov");
playMovies();
// end Constructor
}
public function playMovies():void
{
trace("playMovies called");
try {
videoConnection = new NetConnection ();
videoConnection.connect(null);
netStream = new NetStream(videoConnection);
var metaListener:Object = new Object();
metaListener.onMetaData = onMetaData;
netStream.client = metaListener;
} catch(e:Error) {
trace("Unable to create netstream connection");
}
playmenow = movieArray[curr];
trace("We are going to play the following movie: " + playmenow);
stageVideo = v[0];
stageVideo.attachNetStream(netStream);
stageVideo.addEventListener(StageVideoEvent.RENDER_STATE, onStageVideoEvent);
stage.addEventListener(StageVideoAvailabilityEvent.STAGE_VIDEO_AVAILABILITY, onStageVideoState);
netStream.play(playmenow);
netStream.addEventListener(NetStatusEvent.NET_STATUS, netStreamStatus);
netStream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);
function asyncErrorHandler(event:AsyncErrorEvent):void
{
trace("asyncErrorHandler called: " + event.text);
}
// close playmovie
}
function onMetaData(data:Object):void
{
trace("onMetaData function called!!");
duration = data.duration;
netStream.bufferTime = 1;
// aspect ratio corrections
orig_width = data.width;
orig_height = data.height;
aspect = orig_width/orig_height;
containerAspect = containerWidth/containerHeight;
if ((aspect > containerAspect) && ((aspect-containerAspect)/containerAspect > 0.107)) {
// trace("film aspect is wider by more than 10%");
// the film has a wider aspect ration than the container by more than 10.7%. Let's take the container's width as the video's width (possible letter box)
videoWidth = containerWidth;
// now set the height based on the original ratio
videoHeight = videoWidth/aspect;
}
if ((aspect < containerAspect) && ((containerAspect-aspect)/aspect > 0.107)) {
// trace("film is heigher by more than 10%");
// the film has a heigher aspect ration than the container. Let's take the container's height as the video's width (possible pill box)
videoHeight = containerHeight;
// now set the height based on the original ratio
videoWidth = videoHeight*aspect;
}
if ( (aspect == containerAspect) || ( (aspect > containerAspect) && ( (aspect-containerAspect)/containerAspect <= 0.107) ) || ( (aspect < containerAspect) && ((containerAspect-aspect)/aspect <= 0.107) ) )
{
// trace("video and container have essentially the same aspect ratio");
// the video and container have the same aspect ratio, or are at least within 10% of eachother
videoWidth = containerWidth;
videoHeight = containerHeight;
}
if (containerWidth-videoWidth >0)
// container is wider, we need a pill box -> center video in x axis
{
// trace("we need to pill box this thing");
videoX = ((containerWidth-videoWidth)/2);
videoY = 0;
}
if (containerHeight-videoHeight > 0)
{
// trace("we need to letter box this thing");
// container is higher, we need a letter box -> center video in y axis
videoY = ((containerHeight-videoHeight)/2);
videoX = 0;
}
if (containerWidth == videoWidth && containerHeight == videoHeight) {
videoX = 0;
videoY = 0;
}
stageVideo.viewPort = new Rectangle(videoX,videoY,videoWidth,videoHeight);
trace("ViewPort params: width -> " + videoWidth + ", height -> " + videoHeight);
// close onMetaData
}
function onCuePoint(infoObject:Object):void
{
trace("cue point");
}
function onStageVideoState(event:StageVideoAvailabilityEvent):void
{
trace("===============>>>>>>>><<<<<<========================");
toggleStageVideo(event.availability == StageVideoAvailability.AVAILABLE);
trace("===============>>>>>>>><<<<<<========================");
}
function toggleStageVideo(on:Boolean):void
{
if (on) {
trace("StageVideo has become available.");
} else {
trace("StageVideo has become UNAVAILABLE.");
}
}
function onStageVideoEvent(event:StageVideoEvent) {
trace("===============>>>>>>>><<<<<<========================");
trace("STAGEVIDEO Event: " + event.status);
trace("===============>>>>>>>><<<<<<========================");
// end onStageVideoEvent
}
function netStreamStatus(e:NetStatusEvent):void
{
if(e.info.code == "NetStream.Play.Stop")
{
trace("----------------------------------------------------");
trace("!!!!!!!!!!!!!!! THE VIDEO IS DONE!!!! !!!!!!!!!!!!!");
trace("----------------------------------------------------");
curr++;
stageVideo.removeEventListener(StageVideoEvent.RENDER_STATE, onStageVideoEvent);
stage.removeEventListener(StageVideoAvailabilityEvent.STAGE_VIDEO_AVAILABILITY, onStageVideoState);
stageVideo.viewPort = new Rectangle(0,0,0,0);
netStream.close();
netStream = null;
videoConnection.close();
videoConnection = null;
stageVideo.attachNetStream(null);
stageVideo = null;
if (curr >= movieArray.length)
{
curr = 0;
}
trace ("Switching movie");
playMovies();
// end stop detected (NetStream.Play.Stop)
}
// end netstream status
}
// class
}
// package
}