airsdk / Adobe-Runtime-Support

Report, track and discuss issues in Adobe AIR. Monitored by Adobe - and HARMAN - and maintained by the AIR community.
203 stars 11 forks source link

[Linux] FLV video playback via VideoTexture cause crash #2296

Open itlancer opened 1 year ago

itlancer commented 1 year ago

Problem Description

FLV video playback via VideoTexture cause application crash for Linux devices after ~1 second of playback. The same using Starling or Stage3D directly.

It has been tested with multiple AIR versions, even with latest AIR 50.0.1.3 with multiple Linux Ubuntu 16 and 22.04.1 LTS devices (VM and real) with different AIR applications. Same issue in all cases. There is no such issues with Windows/macOS/Android/iOS devices. There is no such issue using Video. Cannot test with StageVideo cause stage.stageVideos return empty array for Linux right now.

Related issues (not the same): https://github.com/airsdk/Adobe-Runtime-Support/issues/1984 https://github.com/airsdk/Adobe-Runtime-Support/issues/2277 https://github.com/airsdk/Adobe-Runtime-Support/issues/2159 https://github.com/airsdk/Adobe-Runtime-Support/issues/2125 https://github.com/airsdk/Adobe-Runtime-Support/issues/1159 https://github.com/airsdk/Adobe-Runtime-Support/issues/180 https://github.com/airsdk/Adobe-Runtime-Support/issues/155 https://github.com/airsdk/Adobe-Runtime-Support/issues/139 https://github.com/airsdk/Adobe-Runtime-Support/issues/93 https://github.com/airsdk/Adobe-Runtime-Support/issues/82 https://github.com/airsdk/Adobe-Runtime-Support/issues/16

Steps to Reproduce

Launch code below with any Linux x86_64 device. Application start video playback. Application example with sources and example of video attached. linux_videotexture_netstream_flv_crash.zip

package {
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.display3D.Context3DProfile;
    import flash.events.NetStatusEvent;
    import flash.display3D.Context3DProgramType;
    import flash.display3D.Context3D;
    import flash.display3D.IndexBuffer3D;
    import flash.geom.Matrix3D;
    import flash.net.NetConnection;
    import flash.net.NetStream;
    import flash.display3D.textures.VideoTexture;
    import flash.events.SecurityErrorEvent;
    import flash.display3D.VertexBuffer3D;
    import flash.display3D.Context3DTextureFormat;
    import com.adobe.utils.AGALMiniAssembler;
    import flash.display3D.Program3D;
    import flash.display3D.Context3DVertexBufferFormat;

    public class LinuxVideoTextureNetStreamFLVCrash extends Sprite {
        private var context3D:Context3D;
        private var indexbuffer:IndexBuffer3D;
        private var matrix3D:Matrix3D = new Matrix3D();
        private var nc:NetConnection;
        private var ns:NetStream;
        private var videoTexture:VideoTexture;

        public function LinuxVideoTextureNetStreamFLVCrash() {
            addEventListener(Event.ADDED_TO_STAGE, init);
        }

        private function init(e:Event):void {
            removeEventListener(Event.ADDED_TO_STAGE, init);

            if (stage.stage3Ds.length > 0){
                stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, contextCreated, false, 0, true);
                stage.stage3Ds[0].requestContext3DMatchingProfiles(Vector.<String>([Context3DProfile.BASELINE, Context3DProfile.BASELINE_CONSTRAINED, Context3DProfile.BASELINE_EXTENDED, Context3DProfile.STANDARD, Context3DProfile.STANDARD_CONSTRAINED]));
            } else {
                trace("Error: there is no Stage3D available")
            }
        }

        private function contextCreated(event:Event):void {
            context3D = stage.stage3Ds[0].context3D;
            context3D.enableErrorChecking = true;
            context3D.configureBackBuffer(1280, 800, 4, false, false, true);
            trace(context3D.driverInfo);

            var vertices:Vector.<Number> = Vector.<Number>([
                0.5, -1, 0, 1, 0,
                0.5, 0.5, 0, 1, 1,
                -1, 0.5, 0, 0, 1,
                -1,-1, 0, 0, 0
            ]);

            // create the buffer to upload the vertices
            var vertexbuffer:VertexBuffer3D = context3D.createVertexBuffer(4, 5);
            // upload the vertices
            vertexbuffer.uploadFromVector(vertices, 0, 4);
            // create the buffer to upload the indices
            indexbuffer = context3D.createIndexBuffer(6);
            // upload the indices
            indexbuffer.uploadFromVector(Vector.<uint>([0, 1, 2, 2, 3, 0]), 0, 6);

            // create the mini assembler
            var vertexShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
            // assemble the vertex shader
            vertexShaderAssembler.assemble(Context3DProgramType.VERTEX, "m44 op, va0, vc0\n" + "mov v0, va1");

            var fragmentShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
            // assemble the fragment shader
            fragmentShaderAssembler.assemble(Context3DProgramType.FRAGMENT, "tex ft1, v0, fs0 <2d,linear, nomip>\n" + "mov oc, ft1");
            // create the shader program
            var program:Program3D = context3D.createProgram();
            // upload the vertex and fragment shaders
            program.upload(vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode);

            // set the vertex buffer
            context3D.setVertexBufferAt(0, vertexbuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
            context3D.setVertexBufferAt(1, vertexbuffer, 3, Context3DVertexBufferFormat.FLOAT_2);

            videoTexture = context3D.createVideoTexture();
            context3D.setTextureAt(0, videoTexture);

            context3D.setProgram(program);
            matrix3D.appendScale(1, -1, 1);
            context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, matrix3D, true);

            nc = new NetConnection();
            nc.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
            nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
            nc.connect(null);
        }

        private function netStatusHandler(event:NetStatusEvent):void {
            trace(event.info.code);
            switch (event.info.code){
                case "NetConnection.Connect.Success":
                    ns = new NetStream(nc);
                    ns.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
                    ns.client = {onMetaData:getMeta, onPlayStatus:onPlayStatus};
                    videoTexture.attachNetStream(ns);
                    videoTexture.addEventListener(Event.TEXTURE_READY, renderState);
                    ns.play("video.flv");
                break;
                case "NetStream.Play.StreamNotFound":
                    trace("Stream not found");
                break;
            }
        }

        //Metadata handler
        private function getMeta(mdata:Object):void {
            trace("metadata");
        }

        //Seek video to begin after complete
        private function onPlayStatus(infoObject:Object):void {
            ns.seek(0);
        }

        private function securityErrorHandler(event:SecurityErrorEvent):void {
            trace("securityErrorHandler:", event.text);
        }

        private function renderState(e:Event):void {
            videoTexture.removeEventListener(Event.TEXTURE_READY, renderState);
            trace("renderState");
            render();
            addEventListener(Event.ENTER_FRAME, render);
        }

        private function render(event:Event = null):void {
            context3D.clear(0, 0, 0, 1);
            context3D.drawTriangles(indexbuffer);
            context3D.present();
        }
    }
}

Actual Result: Application crash after ~1 second of video playback. No errors or logs about crash generated. Context3D::enableErrorChecking = true didn't help.

Expected Result: Application play video without crashes.

Known Workarounds

none *Use Video.

BwackNinja commented 1 year ago

Crash is happening when the runtime calls gdk_flush() which in turn calls XSync for all displays. For a simple flv video texture test it works without crashing if you change that function to a no-op.

Tested by creating a file gdkflush.c with the contents void gdk_flush(void) { } compiling with gcc -shared -fPIC -o gdkflush.so gdkflush.c and running with LD_PRELOAD=./gdkflush.so yourprogram