0

I am working on a CCTV project which employs ONVIF. I use a Winform sample, which is provided by "ONVIF Device Manager" project, to obtain video frames from a camera. (You can find it here). I found that the sample put a CopyMemory() block code inside UI thread by using dispatcher.BeginInvoke(). I would slow down main UI thread because this block is repeated to display images in a PictureBox.

void InitPlayback(VideoBuffer videoBuffer, bool isInitial)
    {
        //....

        var renderingTask = Task.Factory.StartNew(delegate
        {
            var statistics = PlaybackStatistics.Start(Restart, isInitial);
            using (videoBuffer.Lock())
            {
                try
                {
                    //start rendering loop
                    while (!cancellationToken.IsCancellationRequested)
                    {
                        using (var processingEvent = new ManualResetEventSlim(false))
                        {
                            var dispOp = disp.BeginInvoke((MethodInvoker)delegate
                            {
                                using (Disposable.Create(() => processingEvent.Set()))
                                {
                                    if (!cancellationToken.IsCancellationRequested)
                                    {
                                        //update statisitc info
                                        statistics.Update(videoBuffer);

                                        //render farme to screen
                                        //DrawFrame(bitmap, videoBuffer, statistics);
                                        DrawFrame(videoBuffer, statistics);
                                    }
                                }
                            });
                            processingEvent.Wait(cancellationToken);
                        }
                        cancellationToken.WaitHandle.WaitOne(renderinterval);
                    }
                }
                catch (OperationCanceledException error) { } catch (Exception error) { } finally { }
            }
        }, cancellationToken);
    }

    [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
    public static extern void CopyMemory(IntPtr dest, IntPtr src, int count);
    private void DrawFrame(VideoBuffer videoBuffer, PlaybackStatistics statistics)
    {
        Bitmap bmp = img as Bitmap;
        BitmapData bd = null;
        try
        {
            bd = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);//bgra32

            using (var md = videoBuffer.Lock())
            {

                CopyMemory(bd.Scan0, md.value.scan0Ptr, videoBuff.stride * videoBuff.height);

                //bitmap.WritePixels(
                //    new Int32Rect(0, 0, videoBuffer.width, videoBuffer.height),
                //    md.value.scan0Ptr, videoBuffer.size, videoBuffer.stride,
                //    0, 0
                //);
            }

        }
        catch (Exception err)
        {
            //errBox.Text = err.Message;
            Debug.Print("DrawFrame:: " + err.Message);
        }
        finally
        {
            bmp.UnlockBits(bd);
        }
        imageBox.Image = bmp;
        // var dispOp = disp.BeginInvoke((MethodInvoker)delegate {imageBox.Image = bmp;}); =>>Bitmap is already locked
    }

I tried to exclude the CopyMemory() statement outside of UI thread by calling BeginInvoke() after UnlockBits() bitmap. But, an error is raised "Bitmap is already locked". There has one question which was posted, I have followed the answer of that question but another error occurs "Invalid parameter" while redrawing imageBox. I guess if we lock at bitmap lock(bmp) {CopyMemory();...} the imageBox cannot get information of bitmap associated with it.

Any help is highly appreciated.

Update Proposed Solution

        private void DrawFrame(PlaybackStatistics statistics)
    {
        Bitmap bmp = new Bitmap(videoBuff.width, videoBuff.height);//img as Bitmap;
        //...
        imageBox.Invoke((MethodInvoker)delegate
        {
            Image bmTemp = imageBox.Image;
            imageBox.Image = bmp;
            if (bmTemp != null)
            {
                bmTemp.Dispose();
            }

        });
    }
John Pekl
  • 75
  • 7
  • 1
    I would probably using a [BackgroundWorker](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.backgroundworker?view=netframework-4.7.2) for this, because you just offloading the image processing from the UI thread. – Jeroen van Langen Mar 07 '19 at 13:42
  • You should also separate the read of the video and the display on the form instead of asisgning the bmp to the uage of the imageBox, raise a refresh event which will be taken over by your imageBox OnPaint event to draw the current image. – Laurent Lequenne Mar 07 '19 at 13:57
  • @LaurentLequenne, raising a refresh event will causes this [issue](https://stackoverflow.com/questions/55034455/c-sharp-picturebox-sizemode-zoom-does-not-redraw-when-assigning-new-images). – John Pekl Mar 07 '19 at 14:18
  • I guess that you bmp could be locked by acquiring a new image from the video...So you have to create a new image each time, and clone the result to the global image that will be redrawn in the onpaint event. – Laurent Lequenne Mar 07 '19 at 15:34
  • Have you ever though about using free softwares like VLC to play your ONVIF streams? If the camera you're using is ONVIF conformant, I would suggest you to look for its streaming link and then open this link with VLC instead of loading every frame in pictureboxes – LoukMouk Mar 07 '19 at 19:01
  • @LoukMo, VLC is working fine but I want to get frames in a video to further process it with object tracking. – John Pekl Mar 08 '19 at 00:22
  • You can use the native `Marshal.Copy` instead of importing that `CopyMemory` function from kernel32, by the way. – Nyerguds Mar 11 '19 at 18:34
  • 1
    By the way... _never_ do `imageBox.Image.Dispose()`. You need to store the contents of `imageBox.Image` in a separate variable, _then_ change the contents of `imageBox.Image` to something else, and _only then_ dispose the image, from the separate variable. Otherwise there is a moment at which an active UI element will have a disposed object linked to it, and you'll get an `ObjectDisposedException` when it tries to repaint the UI. – Nyerguds Mar 11 '19 at 18:39

1 Answers1

0

You get the error "Bitmap is already locked" because of the following line:

Bitmap bmp = img as Bitmap;

It seems that img is declared globally, and it is being used both by your thread, and the UI thread ath the same time. When a Bitmap object is being displayed in UI, it is being Locked by UI thread for painting. The Lock method in your thread conflicts with this operation in UI thread.

To get better performance, I recommend you to generate a Bitmap for each frame you get in your thread. Then BeginInvoke it to display the prepared image. In UI thread you should care for disposing the Bitmap when replacing in PictureBox property.

M.Mahdipour
  • 592
  • 1
  • 7
  • 21
  • Thanks, I already your suggested solution in mind.I expect that we could handle it using "lock" or something similar to handle the global _img_ variable. – John Pekl Mar 09 '19 at 01:48
  • You could keep using the one `img` bitmap internally for updating, but make a clone whenever you display it, using the `new Bitmap(bitmap)` constructor. And, yes, for memory reasons, you definitely need to dispose the clone each time you replace it. – Nyerguds Mar 11 '19 at 18:36