rockstardev / csharpWebCam

Versatile WebCam C# library
94 stars 55 forks source link

Hang on stop grabbing during WPF form closing #4

Open antonio-petricca opened 9 years ago

antonio-petricca commented 9 years ago

Here is the scenario:

1 - I wrote a simple simple WPF form application 2 - At startup I start grabbing the webcam getting frames and displaying them insie a picture box 3 - At close time, inside the WPF form close event I call the webcam capture stop method 4 - The stop capture method never ends! 5 - If I call the stop capture method inside the Dispose method it works!

Could you help me please?

Gakk commented 8 years ago

I am also having this problem...

Stephanowicz commented 7 years ago

As far as I can see it happens in webcamlib.cpp @ void CameraMethods::StopCamera() ``g_pMediaControl->StopWhenReady(); or g_pMediaControl->Stop();

This is calling virtual HRESULT STDMETHODCALLTYPE StopWhenReady( void) = 0; in control.h

Dunno if this only happens in combination with wpf...?

So any help would be really appreciated!

Cheers, Stephan

Stephanowicz commented 7 years ago

O.k. - after ~6 hours digging and trying different suggestions I think the solution regarding WPF is to seperate threads for updating the imagesource in the window as suggested here: [http://stackoverflow.com/a/33285565] In short you need to define a bitmap in the main window class that is used as storage for the incoming frames. Then define a dispatcher timer with very short interval (~10 ms) wich updates the source of the image in your window with this bitmap ... Still needs thorough testing, but @ moment it looks quite promising! ;)

rockstardev commented 7 years ago

@Stephanowicz If you guys figure out solution to problem feel free to update README.md and send me pull request - I'll gladly merge.

nilsonsrjunior commented 7 years ago

@Stephanowicz can you share your implementation ? thanks in advance.

Stephanowicz commented 7 years ago

Well, did You take a look at http://stackoverflow.com/a/33285565 ?

nilsonsrjunior commented 7 years ago

Yeah.. i tried adding a DispatcherTimer but it still crashes on stop so i figured i might have done something wrong

Stephanowicz commented 7 years ago

O.k., I managed it as described in the thread with a Dispatcher timer. The timer is enabled each time a new Frame is available. I tried to extract it from my prog:

` in XAML <Image x:Name="_camImage" ...

in CS using Touchless.Vision.Camera;

    DispatcherTimer camTimer = new DispatcherTimer();
    BitmapSource _camImageSource;
    private CameraFrameSource _frameSource;

    public MainWindow()
    {
        ...
        camTimer.Tick += new EventHandler(camTimer_Tick);
        camTimer.Interval = new TimeSpan(0, 0, 0, 0, 5); // --> timer gets once started when image available
        ...
    }

    private void camTimer_Tick(object sender, EventArgs e)
    {
        if (_camImageSource != null && _frameSource != null)
            _camImage.Source = _camImageSource;
        camTimer.IsEnabled = false;
    }

    #region cam

    private void btnCamStart_Click(object sender, RoutedEventArgs e)
    {
        if (_frameSource != null && _frameSource.Camera == comboBoxCameras.SelectedItem)
            return;

        thrashOldCamera();
        startCapturing();
    }
    private void btnCamStop_Click(object sender, RoutedEventArgs e)
    {
        thrashOldCamera();
    }
    private void btnCamSettings_Click(object sender, RoutedEventArgs e)
    {
        if (_frameSource != null && _frameSource.Camera != null)
        {
            _frameSource.Camera.ShowPropertiesDialog();
        }
    }
    private void btnCamListRefresh_Click(object sender, RoutedEventArgs e)
    {
        comboBoxCameras.Items.Clear();
        CameraService.ClearCameraList();
        foreach (Camera cam in CameraService.AvailableCameras)
            comboBoxCameras.Items.Add(cam);

        if (comboBoxCameras.Items.Count > 0)
            comboBoxCameras.SelectedIndex = 0;
        if (_frameSource != null && comboBoxCameras.Items.Count > 0 && comboBoxCameras.Items.Contains(_frameSource.Camera.ToString()))
            comboBoxCameras.SelectedItem = (Camera)_frameSource.Camera;

    }

    private Camera CurrentCamera
    {
        get
        {
            return comboBoxCameras.SelectedItem as Camera;
        }
    }
    private void startCapturing()
    {
        try
        {
            Camera c = (Camera)comboBoxCameras.SelectedItem;
            setFrameSource(new CameraFrameSource(c));
            _frameSource.Camera.CaptureWidth = 640;
            _frameSource.Camera.CaptureHeight = 480;
            _frameSource.Camera.Fps = 25;
            _frameSource.NewFrame += OnImageCaptured;
            _frameSource.StartFrameCapture();
        }
        catch (Exception ex)
        {
            comboBoxCameras.Text = "Select A Camera";
            MessageBox.Show(ex.Message);
        }
    }

    public void OnImageCaptured(Touchless.Vision.Contracts.IFrameSource frameSource, Touchless.Vision.Contracts.Frame frame, double fps)
    {
        _camImageSource = BitmapConversion.ToWpfBitmap(frame.Image);
        camTimer.IsEnabled = true;
    }
    private void setFrameSource(CameraFrameSource cameraFrameSource)
    {
        if (_frameSource == cameraFrameSource)
            return;

        _frameSource = cameraFrameSource;
    }
    private void thrashOldCamera()
    {

        // Trash the old camera
        if (_frameSource != null)
        {
            _frameSource.NewFrame -= OnImageCaptured;
            Dispatcher.Invoke(() => {
                _camImage.Source = null;
                _camImage.InvalidateVisual();
            });
            using (Dispatcher.DisableProcessing())
            {
                _frameSource.StopFrameCapture();
                setFrameSource(null);
                Thread.Sleep(10);
            }
        }
    }
    #endregion cam

`

nilsonsrjunior commented 7 years ago

@Stephanowicz thanks for sharing it. Well, i tried your implementation but it still crashes when _frameSource.StopFrameCapture(); or _frameSource.Camera.Dispose(); is executed (i tried both).

The exception:

InnerException: null Message: Referência de objeto não definida para uma instância de um objeto. StackTracke: em Touchless.Vision.Camera.CameraFrameSource.OnImageCaptured(Object sender, CameraEventArgs e) em System.EventHandler1.Invoke(Object sender, TEventArgs e) em Touchless.Vision.Camera.Camera.ImageCaptured(Bitmap bitmap) em Touchless.Vision.Camera.Camera.CaptureCallbackProc(Int32 dataSize, Byte[] data) em WebCamLib.CameraMethods.CaptureCallbackDelegate.Invoke(Int32 dwSize, Byte[] abData) em WebCamLib.SampleGrabberCB.BufferCB(SampleGrabberCB* , Double SampleTime, Byte* pBuffer, Int32 BufferLen)

Any ideas ?

Stephanowicz commented 7 years ago

Sorry, not really...

The idea of the workaround is to put the image into a local resource in onImageCaptured : _camImageSource = BitmapConversion.ToWpfBitmap(frame.Image); and then in an other thread (thedispatcherTimer thread) to update the wpf image with this local resource: _camImage.Source = _camImageSource; So You have 3 resources - the display image, the temporary image and the frame.image

If You set up everything like this, it ought to work...

nilsonsrjunior commented 7 years ago

@Stephanowicz I really appreciate the effort in trying to help me.

I'll just leave my code below so you can take a look, but i've done pretty much what you did.

` private readonly UltrasoundSettings _ultrasoundSettings; private CameraFrameSource _frameSource; private BitmapSource _camImageSource; private static bool _running; private DispatcherTimer _videoTimer = new DispatcherTimer();

    public Ultrassom()
    {
        InitializeComponent();

        _ultrasoundSettings = SimpleIoc.Default.GetInstance<UltrasoundSettings>();

        _videoTimer.Tick += new EventHandler(VideoTimer_Tick);
        _videoTimer.Interval = new TimeSpan(0, 0, 0, 0, 5); // --> timer gets once started when image available
    }

    #region Video
    private void buttonStartCapture_Click(object sender, RoutedEventArgs e)
    {
        Cnv.Children.Clear();

        StartCapturing();
    }

    private void buttonCaptureImage_Click(object sender, RoutedEventArgs e)
    {
        DeleteOldVideo();
    }

    private void StartCapturing()
    {
        if (!_running)
        {
            try
            {
                var video = CameraService.AvailableCameras.FirstOrDefault(x => x.Name == _ultrasoundSettings.Device);
                SetFrameSource(new CameraFrameSource(video));
                _frameSource.NewFrame += OnImageCaptured;
                _frameSource.StartFrameCapture();
                _running = true;
            }
            catch (Exception exception)
            {
                _running = false;
                MessageBox.Show(
                      string.Format(Messages.MSG_BOX_ERR_FalhaNaConexaoPortaComunicacao, exception.Message),
                      Messages.MSG_BOX_TITLE, MessageBoxButton.OK, MessageBoxImage.Stop);
            }
        }
    }

    private void DeleteOldVideo()
    {
        if (_running && _frameSource != null)
        {
            _frameSource.NewFrame -= OnImageCaptured;
            _running = false;

            Dispatcher.Invoke(() =>
            {
                CameraImage.Source = null;
                //CameraImage.Source = _camImageSource.ToBitmapSource();
                //CameraImage.InvalidateVisual();
            });
            using (Dispatcher.DisableProcessing())
            {
                try
                {
                    _frameSource.StopFrameCapture();
                }
                catch (Exception)
                {

                }
                finally
                {
                    SetFrameSource(null);
                    Thread.Sleep(10);
                }
            }
        }
    }

    public void OnImageCaptured(Touchless.Vision.Contracts.IFrameSource frameSource, Touchless.Vision.Contracts.Frame frame, double fps)
    {
        _camImageSource = frame.Image.ToWpfBitmap();
        _videoTimer.IsEnabled = true;
    }

    private void VideoTimer_Tick(object sender, EventArgs e)
    {
        if (_camImageSource != null && _frameSource != null)
            CameraImage.Source = _camImageSource;

        _videoTimer.IsEnabled = false;
    }

    private void SetFrameSource(CameraFrameSource videoFrameSource)
    {
        if (_frameSource == videoFrameSource)
            return;

        _frameSource = videoFrameSource;
    }
    #endregion`
nilsonsrjunior commented 7 years ago

@Stephanowicz i found the problem... it was my mistake. I first found this project on CodeProject and i assumed it was the same version as the one here in github, so i updated the lib and it started working.

So once again: thanks for your time & help and i'm sorry for my fault.

Stephanowicz commented 7 years ago

Good to hear that it is working now!

Cheers, Stephan