1
votes

I'm aware there are a lot of questions on this topic, and I've looked through most of them as well as Googling to help me solve this problem, to no avail.

What I'd like to do is post the relevant sections of my code that produce bitmaps and render bitmaps onto PictureBoxes in my UI, and I would like to know if anyone can spot what is specifically causing this error, and can suggest how to avoid or bypass it.

I'll start with the relevant bits (3) in my VideoRenderer class:

  1. The timer event that continously calls MoveFrameToBitmap while video is running:

    private void TimerTick(object sender, EventArgs e)
    {
        if (frameTransport.IsNewFrameAvailable())
        {
            if (frameTransport.GetFrame())
            {
                if (MoveFrameToBitmap())
                {
                    double msSinceLastFrame = (Int32)DateTime.Now.Subtract(lastFrameTimestamp).TotalMilliseconds;
                    fps = 1000 / msSinceLastFrame;
                    lastFrameTimestamp = DateTime.Now;
                }
            }
            else
            {
                if (frameTransport.channelKeyBufferBufidMismatch)
                {
                    needsRestart = true;
                }
            }
        }
    }
    
  2. MoveFrameToBitmap, which marshals in a video frame from FrameTransport, creates a bitmap if successful, clones it and queues the frame:

    internal bool MoveFrameToBitmap()
    {
        bool result = false;
    
        try
        {
            if (frameTransport.bitmapDataSize == 0)
            {
                return false;
            }
    
            bool ResolutionHasChanged = ((videoWidth != frameTransport.width) | (videoHeight != frameTransport.height));
    
            videoHeight = frameTransport.height;
            videoWidth = frameTransport.width;
    
            Bitmap bitmap = new System.Drawing.Bitmap(videoWidth, videoHeight);
            Rectangle rectangle = new System.Drawing.Rectangle(0, 0, videoWidth, videoHeight);
            BitmapData bitmapData = new System.Drawing.Imaging.BitmapData();
            bitmapData.Width = videoWidth;
            bitmapData.Height = videoHeight;
            bitmapData.PixelFormat = PixelFormat.Format24bppRgb;
    
            bitmap.LockBits(rectangle, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb, bitmapData);
    
            Marshal.Copy(frameTransport.bitmapData, 0, bitmapData.Scan0, frameTransport.bitmapDataSize);
    
            lock (frameQueueLock)
            {
                if (frameQueue.Count == 0)
                {
                    frameQueue.Enqueue(bitmap.Clone());
                }
            }
    
            bitmap.UnlockBits(bitmapData);
    
            if (ResolutionHasChanged) skypeRef.events.FireOnVideoResolutionChanged(this, new RootEvents.OnVideoResolutionChangedArgs(videoWidth, videoHeight));
    
            bitmap.Dispose();
    
            result = true;
        }
        catch (Exception) { }
    
        GC.Collect();
        return result;
    }
    
  3. The property that exposes the queued frame, which can be safely accessed even when a frame is not currently queued:

    public Bitmap QueuedFrame
    {
        get
        {
            try
            {
                lock (frameQueueLock)
                {
                    return frameQueue.Dequeue() as Bitmap;
                }
            }
            catch (Exception)
            {
                return null;
            }
        }
    }
    

That's all for VideoRenderer. Now I'll show the relevant property of the static MyVideo class, which encapsulates, controls and returns frames from two video renderers. Here is the property which exposes the queued frame of the first renderer (every call to videoPreviewRenderer is locked with VPR_Lock):

    public static Bitmap QueuedVideoPreviewFrame
    {
        get
        {
            lock (VPR_Lock) { return videoPreviewRenderer.QueuedFrame; }
        }
    }

The property for the second renderer is the same, except with its own lock object.

Moving on, here is the thread and its call in my UI that accesses the two queued frame properties in MyVideo and renders frames to the two PictureBoxes:

ThreadStart renderVCONF3501VideoThreadStart = new ThreadStart(new Action(() =>
    {
        while (MyAccount.IsLoggedIn)
        {
            if (MyVideo.VideoPreviewIsRendering)
            {
                if (MyVideo.VideoPreviewRenderer.NeedsRestart)
                {
                    MyVideo.VideoPreviewRenderer.Stop();
                    MyVideo.VideoPreviewRenderer.Start();
                    MyVideo.VideoPreviewRenderer.NeedsRestart = false;
                }
                else
                {
                    try
                    {
                        Bitmap newVideoPreviewFrame = MyVideo.QueuedVideoPreviewFrame;

                        if (newVideoPreviewFrame != null)
                        {
                            lock (VCONF3501_VPI_Lock)
                            {
                                VCONF3501_VideoPreview.Image = newVideoPreviewFrame;
                            }
                        }
                    }
                    catch (Exception) { continue; }
                }
            }
            else
            {
                lock (VCONF3501_VPI_Lock)
                {
                    VCONF3501_VideoPreview.Image = null;
                }
            }

            if (MyVideo.LiveSessionParticipantVideoIsRendering)
            {
                if (MyVideo.LiveSessionParticipantVideoRenderer.NeedsRestart)
                {
                    MyVideo.LiveSessionParticipantVideoRenderer.Stop();
                    MyVideo.LiveSessionParticipantVideoRenderer.Start();
                    MyVideo.LiveSessionParticipantVideoRenderer.NeedsRestart = false;
                }
                else
                {
                    try
                    {
                        Bitmap newLiveSessionParticipantVideoFrame = MyVideo.QueuedLiveSessionParticipantVideoFrame;

                        if (newLiveSessionParticipantVideoFrame != null)
                        {
                            lock (VCONF3501_LSPVI_Lock)
                            {
                                VCONF3501_Video.Image = newLiveSessionParticipantVideoFrame;
                            }
                        }
                    }
                    catch (Exception) { continue; }
                }
            }
            else
            {
                lock (VCONF3501_LSPVI_Lock)
                {
                    VCONF3501_Video.Image = null;
                }
            }

            GC.Collect();
        }
    }));

new Thread(renderVCONF3501VideoThreadStart).Start();

The GC.Collect() calls are to force bitmap memory release, as there was a memory leak (and still might be one--the cloned bitmaps aren't being manually disposed of and I'm not sure where to, at this point).

Where is the InvalidOperationException in System.Drawing, which causes a red cross to be drawn to the PictureBox coming from, what am I doing wrong in terms of locking and access, and how can I avoid/bypass this error?

I am trying to bypass it with the catch exception and continue logic in the thread, and that I have confirmed works . . . sometimes. At other times, the failed draw attempt seems to complete too far and draws the red cross anyway, and after that point, the PictureBox is thoroughly unresponsive and new frames cannot be drawn to it, even when the video is still running fine.

Perhaps there is a way to refresh the PictureBox so that it accepts new frames?

1

1 Answers

0
votes

I was having a problem with the red cross, then I find this and it help me, I hope it helps you too:

WinForms controls and the red X