简体   繁体   English

NetStream.appendBytes()或内存不足怎么播放大型视频?

[英]Out of memory with NetStream.appendBytes() or How to Play Large Video?

The following is a fully-functioning pure actionscript project for AIR 以下是AIR的功能齐全的纯ActionScript项目

When run, try opening a large FLV (I'm testing it with a 3GB file) 运行时,请尝试打开大型FLV(我正在使用3GB文件对其进行测试)

With DEBUG_UNUSED_BUFFER and DEBUG_APPEND_VIDEO set to false, it works fine- reads through the entire file without a problem. 将DEBUG_UNUSED_BUFFER和DEBUG_APPEND_VIDEO设置为false时,它可以正常读取整个文件,而不会出现问题。

However, with either of those set to true, it crashes with an OUT OF MEMORY error. 但是,如果将其中任何一个设置为true,它都将因内存不足错误而崩溃。

For practical purposes, I'm more interested in why appendBytes() fails, but for the sake of interest, the DEBUG_UNUSED_BUFFER only makes it to like 6% of the file while DEBUG_APPEND_VIDEO makes it to around 46% or so. 出于实用目的,我对appendBytes()为何失败的原因更感兴趣,但是出于兴趣的考虑,DEBUG_UNUSED_BUFFER仅使其占文件的6%,而DEBUG_APPEND_VIDEO使它占46%左右。

Question: How then are we supposed to play a large video?! 问题:那我们应该如何播放大型视频?

package
{
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.MouseEvent;
    import flash.events.ProgressEvent;
    import flash.events.TimerEvent;
    import flash.filesystem.File;
    import flash.filesystem.FileMode;
    import flash.filesystem.FileStream;
    import flash.net.FileFilter;
    import flash.net.NetConnection;
    import flash.net.NetStream;
    import flash.net.NetStreamAppendBytesAction;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;
    import flash.text.TextFormatAlign;
    import flash.utils.ByteArray;
    import flash.utils.Timer;

    public class MEMORY_TEST extends Sprite
    {
        //Set this to throttle data processing to once every DEBUG_THROTTLE_TIME milliseconds
        // 0 = no throttling at all
        // Note this this seems to make little difference, other than making it easier to see what's happening
        private static const DEBUG_THROTTLE_TIME:Number = 100;

        //Set this to write all bytes to an unused buffer.
        //THIS FAILS (at around 237912064 bytes)!!!!!
        private static const DEBUG_UNUSED_BUFFER:Boolean = false;

        //Set this to write the video data via appendBytes.
        //THIS FAILS (at around 1360003072 bytes)!!!!
        private static const DEBUG_APPEND_VIDEO:Boolean = true;

        /****************************************************************/
        /******* Nothing else to configure below this line **************/
        /****************************************************************/
        private var openButton:Sprite;
        private var statusTextField:TextField;

        private var inputFile:File = null;
        private var inputFileStream:FileStream = null;
        private var netStream:NetStream = null;
        private var netConnection:NetConnection = null;
        private var readBytes:ByteArray = null;
        private var totalBytesRead:Number = 0;

        private var throttleTimer:Timer = null;
        private var unusedBuffer:ByteArray = null;

        private static const READSIZE:uint = 2048;




        public function MEMORY_TEST()
        {
            this.addEventListener(Event.ADDED_TO_STAGE, onStage);
        }

        /*************************
         * 
         *  UI SETUP
         * 
         **************************/

        private function onStage(evt:Event) {
            this.removeEventListener(Event.ADDED_TO_STAGE, onStage);

            makeButtonAndStatus();
            updateStatus('Click the button to begin');
        }

        private function makeButtonAndStatus(buttonText:String = 'Open File') {
            var textField:TextField = new TextField();
            var fmt:TextFormat = new TextFormat();
            var padding:Number = 20;
            var halfPadding:Number = padding/2;

            //Button
            fmt.color = 0xFFFFFF;
            fmt.size = 24;
            fmt.font = "_sans";
            fmt.align = TextFormatAlign.LEFT;

            textField.autoSize = TextFieldAutoSize.LEFT;
            textField.multiline = false;
            textField.wordWrap = false;
            textField.defaultTextFormat = fmt;
            textField.text = buttonText;

            openButton = new Sprite();
            openButton.graphics.beginFill(0x0B8CC3);
            openButton.graphics.drawRoundRect(-halfPadding,-halfPadding,textField.width + padding, textField.height + padding, 20, 20);
            openButton.graphics.endFill();
            openButton.addChild(textField);

            openButton.buttonMode = true;
            openButton.useHandCursor = true;
            openButton.mouseChildren = false;

            openButton.addEventListener(MouseEvent.CLICK, selectFile);

            openButton.x = (stage.stageWidth - openButton.width)/2;
            openButton.y = (stage.stageHeight - openButton.height)/2;

            addChild(openButton);

            //Status
            statusTextField = new TextField();
            fmt = new TextFormat();

            fmt.color = 0xFF0000;
            fmt.size = 17;
            fmt.font = "_sans";
            fmt.align = TextFormatAlign.CENTER;

            statusTextField.defaultTextFormat = fmt;
            statusTextField.multiline = true;
            statusTextField.wordWrap = false;
            statusTextField.width = stage.stageWidth;
            statusTextField.text = '';

            statusTextField.x = 0;
            statusTextField.y = openButton.y + openButton.height + padding;
            statusTextField.mouseEnabled = false;

            addChild(statusTextField);
        }

        private function selectFile(evt:MouseEvent) {
            var videoFilter:FileFilter = new FileFilter("Videos", "*.flv");
            var inputFile:File = File.desktopDirectory;

            inputFile.addEventListener(Event.SELECT, fileSelected);
            inputFile.browseForOpen('Open', [videoFilter]);
        }

        private function fileSelected(evt:Event = null) {
            inputFile = evt.target as File;

            openButton.visible = false;

            startVideo();
            startFile();

            if(DEBUG_THROTTLE_TIME) {
                startTimer();
            }
        }

        private function updateStatus(statusText:String) {
            statusTextField.text = statusText;
            trace(statusText);
        }

        /*************************
        * 
        *   FILE & VIDEO OPERATIONS
        * 
        **************************/

        private function startVideo() {
            netConnection = new NetConnection();
            netConnection.connect(null);

            netStream = new NetStream(netConnection);

            netStream.client = {};

            // put the NetStream class into Data Generation mode
            netStream.play(null);

            // before appending new bytes, reset the position to the beginning
            netStream.appendBytesAction(NetStreamAppendBytesAction.RESET_BEGIN);

            updateStatus('Video Stream Started, Waiting for Bytes...');
        }

        private function startFile() {
            totalBytesRead = 0;
            readBytes = new ByteArray();
            if(DEBUG_UNUSED_BUFFER) {
                unusedBuffer = new ByteArray();
            }

            inputFileStream = new FileStream();
            inputFileStream.readAhead = READSIZE;
            inputFileStream.addEventListener(ProgressEvent.PROGRESS, fileReadProgress);
            inputFileStream.addEventListener(IOErrorEvent.IO_ERROR,ioError);
            inputFileStream.openAsync(inputFile, FileMode.READ);    
        }

        private function fileReadProgress(evt:ProgressEvent = null) {
            while(inputFileStream.bytesAvailable) {
                inputFileStream.readBytes(readBytes, readBytes.length, inputFileStream.bytesAvailable);
                if(!DEBUG_THROTTLE_TIME) {
                    processData();
                }
            }
        }

        private function processData(evt:TimerEvent = null) {
            var statusString:String;

            if(readBytes.length) {

                if(DEBUG_APPEND_VIDEO) {
                    //Here's where things get funky...
                    netStream.appendBytes(readBytes);
                }


                totalBytesRead += readBytes.length;

                statusString = 'bytes processed now: ' + readBytes.length.toString();
                statusString += '\n total bytes processed: ' + totalBytesRead.toString();
                statusString += '\n percentage: ' + Math.round((totalBytesRead / inputFile.size)  * 100).toString() + '%';

                if(DEBUG_UNUSED_BUFFER) {
                    //Here too....
                    unusedBuffer.writeBytes(readBytes);
                    statusString += '\n Unused Buffer size: ' + unusedBuffer.length.toString();
                }

                updateStatus(statusString);

                readBytes.length = 0;

                if(totalBytesRead == inputFile.size) {
                    fileReadComplete();
                }
            }

        }

        private function fileReadComplete(evt:Event = null) {
            closeAll();
            updateStatus('Finished Reading! Yay!');
        }

        private function ioError(evt:IOErrorEvent) {
            closeAll();
            updateStatus('IO ERROR!!!!');
        }

        /*************************
         * 
         *  TIMER OPERATIONS
         * 
         **************************/

        private function startTimer() {
            throttleTimer = new Timer(DEBUG_THROTTLE_TIME);
            throttleTimer.addEventListener(TimerEvent.TIMER, processData);
            throttleTimer.start();              
        }

        /*************************
         * 
         *  CLEANUP
         * 
         **************************/

        private function closeAll() {

            if(inputFile != null) {
                inputFile.cancel();
                inputFile = null;
            }

            if(inputFileStream != null) {
                inputFileStream.removeEventListener(ProgressEvent.PROGRESS, fileReadProgress);
                inputFileStream.removeEventListener(IOErrorEvent.IO_ERROR,ioError);
                inputFileStream.close();
                inputFileStream = null;
            } 

            if(readBytes != null) {
                readBytes.clear();
                readBytes = null;
            }

            if(unusedBuffer != null) {
                unusedBuffer.clear();
                unusedBuffer = null;
            }

            if(throttleTimer != null) {
                throttleTimer.removeEventListener(TimerEvent.TIMER, processData);
                throttleTimer.stop();
                throttleTimer = null;
            }

            if(netConnection != null) {
                netConnection.close();
                netConnection = null;
            }

            if(netStream != null) {
                netStream.close();
                netStream = null;

            }

            openButton.visible = true;
        }
    }
}

UPDATE: 更新:

NetStream.seek() will flush the content appended by appendBytes()... in other words, it seems appendBytes() will just keep adding whatever data you throw at it, which makes sense. NetStream.seek()将刷新由appendBytes()追加的内容...换句话说,似乎appendBytes()只会继续添加您向其抛出的任何数据,这是有道理的。 However- the core of this question still stands... 但是-这个问题的核心仍然存在...

In theory, I guess calling seek() at every 10 seconds worth of keyframes would work... That is really kindof weird, not at all what "seek" typically is used for, and it would require a whole bunch of manual calculations to make it work right since in Data Generation Mode using seek to continue playing would require calling appendBytesAction(NetStreamAppendBytesAction.RESET_SEEK), and that in turn requires that the next call to appendBytes() needs to begin on the next byte location for an FLV tag (which hopefully exists in the metadata). 从理论上讲,我猜每隔10秒钟就可以调用一次seek()来工作……这确实有点怪异,完全没有通常使用的“ seek”,并且需要大量的手动计算才能完成。使其正常工作,因为在数据生成模式下使用寻求继续播放将需要调用appendBytesAction(NetStreamAppendBytesAction.RESET_SEEK),进而又要求下一次对appendBytes()的调用必须从FLV标签的下一个字节位置开始(希望存在于元数据中)。

Is this the right solution? 这是正确的解决方案吗? Adobe Team, is this what you had in mind? Adobe团队,这是您的主意吗? Any sample code?! 任何示例代码?

Help! 救命! :) :)

I'm pretty sure that once data leaves the playout buffer of the NetStream that the memory is released. 我非常确定,一旦数据离开NetStream的播放缓冲区,就释放了内存。 In your sample you are not attaching the NetStream to a Video object, so I'm wondering if the NetStream is actually playing (and therefore releasing) the bytes you push in. 在您的示例中,您没有将NetStream附加到Video对象,所以我想知道NetStream是否实际上在播放(并因此释放)您推入的字节。

Try adding a trace on a timer and check netStream.bufferLength. 尝试在计时器上添加跟踪,然后检查netStream.bufferLength。 This should be an ever changing value if the video is actually playing out. 如果视频实际上正在播放,则该值应该是一个不断变化的值。 If bufferLength just goes forever, the bytes are never playing and never released. 如果bufferLength永远消失了,则字节永远不会播放,永远不会释放。 If you find that happening, try attaching the NetStream to a Video so that the content actually plays and run the same test on bufferLength. 如果发现这种情况,请尝试将NetStream附加到Video,以使内容实际播放并在bufferLength上运行相同的测试。

I would also recommend using the Flash Builder Profile or Adobe Scout to watch the memory usage. 我还建议您使用Flash Builder配置文件或Adobe Scout来查看内存使用情况。 With NetStream the memory usages should go up and down as bytes are played out and released. 使用NetStream时,随着字节的播放和释放,内存使用量应会上升和下降。

My other thought is that you may be reading bytes in too quickly. 我的另一个想法是,您可能读取字节的速度太快。 You push bytes basically as fast as they are loaded. 您推入字节的速度基本上与加载时一样快。 NetStream cannot play them out that quickly and so the bytes get stuck in memory until it's time to play that data. NetStream不能这么快地播放它们,因此这些字节卡在内存中,直到需要播放该数据为止。 You could read out the data in chunks; 您可以分块读取数据; the video should be a set of discrete FLV tags that you can read one by one. 视频应该是一组离散的FLV标签,您可以一次阅读它们。 You can figure out how long the chunk is (there should be a property of the chunk that tells it's length, or you could figure it out via the timestamps) and then only load more data when you have to. 您可以计算出块的长度(应该有一个块的属性来说明其长度,或者可以通过时间戳记出该块的大小),然后仅在需要时才加载更多数据。

Lastly, I believe that AIR is still only 32bit. 最后,我相信AIR仍然只有32位。 At least that's what some googling is telling me. 至少那是一些谷歌搜索告诉我的。 So that means that it can only get a limited amount of memory from the OS.. So I'm betting that you're hitting the ceiling and crashing the process. 因此,这意味着它只能从OS中获得有限的内存。.因此,我敢打赌,您正在达到顶峰,并且使进程崩溃。

Try this: Check out System.totalMemory to see how much memory AIR is using before it crashes. 尝试以下操作:检查System.totalMemory,以查看AIR在崩溃之前正在使用多少内存。 In theory it should be the same between thetwo use cases, but I bet NetStream is throwing away a lot more bytes sooner. 从理论上讲,这两个用例之间应该相同,但是我敢打赌NetStream会更快地丢弃更多的字节。

I suspect the underlying problem is having multiple copies of a 3GB file in memory. 我怀疑潜在的问题是内存中有3GB文件的多个副本。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM