public static async Task Run(WebSocket webSocket)
        {
            var scaleNom  = 1;
            var scaleDen  = 1;
            var width     = (1280 * scaleNom / scaleDen) | 0;
            var height    = (720 * scaleNom / scaleDen) | 0;
            var frameSpec = new FrameSpec(width, height, 1);

            using (var jpegCompressor = new JpegCompressor())
            {
                const int workerCount = 2;

                var receiveBuffer = new byte[1024 * 4];

                var stats = new FrameStats();

                var workers = Enumerable.Range(0, workerCount)
                              .Select(index => new FrameWorker(frameSpec, "background-small.jpg", jpegCompressor, stats, webSocket))
                              .ToArray();

                var requests = Enumerable.Repeat(FrameRequest.Completed, workerCount)
                               .ToArray();

                int workerIndex = 0;

                // Ready to start
                await webSocket.SendJsonAsync("READY", frameSpec);

                double lastFrameTimeMS = -1;
                int    lastFrameId     = -1;

                // Start render loop
                while (!webSocket.CloseStatus.HasValue) // && lastFrameTimeMS < 250)
                {
                    var message = await webSocket.ReceiveJsonAsync(default, receiveBuffer);
 public FrameWorker(FrameSpec spec, string backgroundPath, JpegCompressor jpegCompressor, FrameStats stats, WebSocket webSocket)
 {
     _spec               = spec;
     _stats              = stats;
     _background         = backgroundPath == null ? null : Image.FromFile(backgroundPath);
     _jpegCompressor     = jpegCompressor;
     _webSocket          = webSocket;
     _cancellationSource = new CancellationTokenSource();
     _cancellationToken  = _cancellationSource.Token;
     _thread             = new Thread(ThreadEntry)
     {
         Priority = ThreadPriority.AboveNormal
     };
     _thread.Start();
 }
        public JpegFrame(JpegCompressor compressor, int width, int height, TJSubsamplingOptions subSampling)
        {
            _compressor = compressor;

            checked
            {
                Width         = width;
                Height        = height;
                SubSampling   = subSampling;
                MaxBufferSize = (int)JpegLibrary.tjBufSize(width, height, (int)subSampling);
            }

            var bufferHandle = JpegLibrary.tjAlloc(MaxBufferSize);

            if (bufferHandle == IntPtr.Zero)
            {
                throw new OutOfMemoryException(
                          $"Failed to allocate TurboJPEG buffer of size {width}x{height}, subSampling {subSampling}");
            }

            SetResourceHandle(bufferHandle);
        }
 protected override void Free(IntPtr handle)
 {
     _compressor = null;
     JpegLibrary.tjFree(handle);
 }