簡體   English   中英

NetStream.appendBytes()或內存不足怎么播放大型視頻?

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

以下是AIR的功能齊全的純ActionScript項目

運行時,請嘗試打開大型FLV(我正在使用3GB文件對其進行測試)

將DEBUG_UNUSED_BUFFER和DEBUG_APPEND_VIDEO設置為false時,它可以正常讀取整個文件,而不會出現問題。

但是,如果將其中任何一個設置為true,它都將因內存不足錯誤而崩潰。

出於實用目的,我對appendBytes()為何失敗的原因更感興趣,但是出於興趣的考慮,DEBUG_UNUSED_BUFFER僅使其占文件的6%,而DEBUG_APPEND_VIDEO使它占46%左右。

問題:那我們應該如何播放大型視頻?

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;
        }
    }
}

更新:

NetStream.seek()將刷新由appendBytes()追加的內容...換句話說,似乎appendBytes()只會繼續添加您向其拋出的任何數據,這是有道理的。 但是-這個問題的核心仍然存在...

從理論上講,我猜每隔10秒鍾就可以調用一次seek()來工作……這確實有點怪異,完全沒有通常使用的“ seek”,並且需要大量的手動計算才能完成。使其正常工作,因為在數據生成模式下使用尋求繼續播放將需要調用appendBytesAction(NetStreamAppendBytesAction.RESET_SEEK),進而又要求下一次對appendBytes()的調用必須從FLV標簽的下一個字節位置開始(希望存在於元數據中)。

這是正確的解決方案嗎? Adobe團隊,這是您的主意嗎? 任何示例代碼?

救命! :)

我非常確定,一旦數據離開NetStream的播放緩沖區,就釋放了內存。 在您的示例中,您沒有將NetStream附加到Video對象,所以我想知道NetStream是否實際上在播放(並因此釋放)您推入的字節。

嘗試在計時器上添加跟蹤,然后檢查netStream.bufferLength。 如果視頻實際上正在播放,則該值應該是一個不斷變化的值。 如果bufferLength永遠消失了,則字節永遠不會播放,永遠不會釋放。 如果發現這種情況,請嘗試將NetStream附加到Video,以使內容實際播放並在bufferLength上運行相同的測試。

我還建議您使用Flash Builder配置文件或Adobe Scout來查看內存使用情況。 使用NetStream時,隨着字節的播放和釋放,內存使用量應會上升和下降。

我的另一個想法是,您可能讀取字節的速度太快。 您推入字節的速度基本上與加載時一樣快。 NetStream不能這么快地播放它們,因此這些字節卡在內存中,直到需要播放該數據為止。 您可以分塊讀取數據; 視頻應該是一組離散的FLV標簽,您可以一次閱讀它們。 您可以計算出塊的長度(應該有一個塊的屬性來說明其長度,或者可以通過時間戳記出該塊的大小),然后僅在需要時才加載更多數據。

最后,我相信AIR仍然只有32位。 至少那是一些谷歌搜索告訴我的。 因此,這意味着它只能從OS中獲得有限的內存。.因此,我敢打賭,您正在達到頂峰,並且使進程崩潰。

嘗試以下操作:檢查System.totalMemory,以查看AIR在崩潰之前正在使用多少內存。 從理論上講,這兩個用例之間應該相同,但是我敢打賭NetStream會更快地丟棄更多的字節。

我懷疑潛在的問題是內存中有3GB文件的多個副本。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM