From ef5462e1fcd513050fdb181a33dc8ae8331ade6d Mon Sep 17 00:00:00 2001 From: Kouji Matsui Date: Mon, 15 Jan 2024 22:36:40 +0900 Subject: [PATCH] Cleaned up some descriptors when stop capture. --- FlashCap.Core/Devices/DirectShowDevice.cs | 26 +- FlashCap.Core/Devices/V4L2Device.cs | 433 ++++++++++-------- .../NativeMethods_V4L2_Interop_Utilities.cs | 1 + 3 files changed, 244 insertions(+), 216 deletions(-) diff --git a/FlashCap.Core/Devices/DirectShowDevice.cs b/FlashCap.Core/Devices/DirectShowDevice.cs index 324c601..a983c87 100644 --- a/FlashCap.Core/Devices/DirectShowDevice.cs +++ b/FlashCap.Core/Devices/DirectShowDevice.cs @@ -96,20 +96,20 @@ protected override Task OnInitializeAsync( return this.workingContext!.InvokeAsync(() => { - if (NativeMethods_DirectShow.EnumerateDeviceMoniker( - NativeMethods_DirectShow.CLSID_VideoInputDeviceCategory). - Where(moniker => - moniker.GetPropertyBag() is { } pb && - pb.SafeReleaseBlock(pb => - pb.GetValue("DevicePath", default(string))?.Trim() is { } dp && - dp.Equals(devicePath))). - Collect(moniker => - moniker.BindToObject(null, null, in NativeMethods_DirectShow.IID_IBaseFilter, out var captureSource) == 0 ? - captureSource as NativeMethods_DirectShow.IBaseFilter : null). - FirstOrDefault() is { } captureSource) - { - try + if (NativeMethods_DirectShow.EnumerateDeviceMoniker( + NativeMethods_DirectShow.CLSID_VideoInputDeviceCategory). + Where(moniker => + moniker.GetPropertyBag() is { } pb && + pb.SafeReleaseBlock(pb => + pb.GetValue("DevicePath", default(string))?.Trim() is { } dp && + dp.Equals(devicePath))). + Collect(moniker => + moniker.BindToObject(null, null, in NativeMethods_DirectShow.IID_IBaseFilter, out var captureSource) == 0 ? + captureSource as NativeMethods_DirectShow.IBaseFilter : null). + FirstOrDefault() is { } captureSource) { + try + { if (captureSource.EnumeratePins(). Collect(pin => pin.GetPinInfo() is { } pinInfo && diff --git a/FlashCap.Core/Devices/V4L2Device.cs b/FlashCap.Core/Devices/V4L2Device.cs index 41617a9..721ae96 100644 --- a/FlashCap.Core/Devices/V4L2Device.cs +++ b/FlashCap.Core/Devices/V4L2Device.cs @@ -29,11 +29,12 @@ public sealed class V4L2Device : CaptureDevice private string devicePath; private TranscodeFormats transcodeFormat; private FrameProcessor frameProcessor; + private uint[] pix_fmts; private long frameIndex; - private readonly IntPtr[] pBuffers = new IntPtr[BufferCount]; - private readonly int[] bufferLength = new int[BufferCount]; + private IntPtr[] pBuffers = new IntPtr[BufferCount]; + private int[] bufferLength = new int[BufferCount]; private int fd; private IntPtr pBih; @@ -66,182 +67,273 @@ protected override unsafe Task OnInitializeAsync( $"FlashCap: Couldn't set video format [1]: DevicePath={this.devicePath}"); } - var pix_fmts = GetPixelFormats( + this.pix_fmts = GetPixelFormats( characteristics.PixelFormat); - if (pix_fmts.Length == 0) + if (this.pix_fmts.Length == 0) { throw new ArgumentException( $"FlashCap: Couldn't set video format [2]: DevicePath={this.devicePath}"); } - return Task.Factory.StartNew(() => + var pih = NativeMethods.AllocateMemory((IntPtr)sizeof(NativeMethods.BITMAPINFOHEADER)); + try { - if (open(this.devicePath, OPENBITS.O_RDWR) is { } fd && fd < 0) - { - var code = Marshal.GetLastWin32Error(); - throw new ArgumentException( - $"FlashCap: Couldn't open video device: Code={code}, DevicePath={this.devicePath}"); - } + var pBih = (NativeMethods.BITMAPINFOHEADER*)pih.ToPointer(); + + pBih->biSize = sizeof(NativeMethods.BITMAPINFOHEADER); + pBih->biCompression = compression; + pBih->biPlanes = 1; + pBih->biBitCount = bitCount; + pBih->biWidth = characteristics.Width; + pBih->biHeight = characteristics.Height; + pBih->biSizeImage = pBih->CalculateImageSize(); + + this.pBih = pih; + } + catch + { + NativeMethods.FreeMemory(pih); + throw; + } + + return TaskCompat.CompletedTask; + } + + ~V4L2Device() + { + if (this.abortwfd != -1) + { + write(this.abortwfd, new byte[] { 0x01 }, 1); + close(this.abortwfd); + this.abortwfd = -1; + } + + if (this.abortrfd != -1) + { + close(this.abortrfd); + this.abortrfd = -1; + } - try + if (this.pBuffers is { } pBuffers && + this.bufferLength is { } bufferLength) + { + for (var index = 0; index < pBuffers.Length; index++) { - var applied = false; - foreach (var pix_fmt in pix_fmts) - { - var fmt_pix = Interop.Create_v4l2_pix_format(); - fmt_pix.width = (uint)characteristics.Width; - fmt_pix.height = (uint)characteristics.Height; - fmt_pix.pixelformat = pix_fmt; - fmt_pix.field = (uint)v4l2_field.ANY; - - var format = Interop.Create_v4l2_format(); - format.type = (uint)v4l2_buf_type.VIDEO_CAPTURE; - format.fmt_pix = fmt_pix; - - if (ioctl(fd, Interop.VIDIOC_S_FMT, format) == 0) - { - applied = true; - break; - } - } - if (!applied) + if (pBuffers[index] != default && + bufferLength[index] != default) { - throw new ArgumentException( - $"FlashCap: Couldn't set video format [3]: DevicePath={this.devicePath}"); + munmap(pBuffers[index], (ulong)bufferLength[index]); + pBuffers[index] = default; + bufferLength[index] = default; } + } - var requestbuffers = Interop.Create_v4l2_requestbuffers(); - requestbuffers.count = BufferCount; // Flipping - requestbuffers.type = (uint)v4l2_buf_type.VIDEO_CAPTURE; - requestbuffers.memory = (uint)v4l2_memory.MMAP; + this.pBuffers = null!; + this.bufferLength = null!; + } + + if (this.fd != -1) + { + close(this.fd); + this.fd = -1; + } + + if (this.pBih != IntPtr.Zero) + { + NativeMethods.FreeMemory(this.pBih); + this.pBih = IntPtr.Zero; + } - if (ioctl(fd, Interop.VIDIOC_REQBUFS, requestbuffers) < 0) + this.IsRunning = false; + } + + protected override async Task OnDisposeAsync() + { + if (this.IsRunning) + { + await this.frameProcessor.DisposeAsync(). + ConfigureAwait(false); + + await this.InternalStopAsync(default). + ConfigureAwait(false); + } + } + + protected override Task OnStartAsync(CancellationToken ct) + { + if (!this.IsRunning) + { + return Task.Factory.StartNew(() => + { + if (open(this.devicePath, OPENBITS.O_RDWR) is { } fd && fd < 0) { var code = Marshal.GetLastWin32Error(); throw new ArgumentException( - $"FlashCap: Couldn't allocate video buffer: Code={code}, DevicePath={this.devicePath}"); + $"FlashCap: Couldn't open video device: Code={code}, DevicePath={this.devicePath}"); } - for (var index = 0; index < requestbuffers.count; index++) + try { - var buffer = Interop.Create_v4l2_buffer(); - buffer.type = (uint)v4l2_buf_type.VIDEO_CAPTURE; - buffer.memory = (uint)v4l2_memory.MMAP; - buffer.index = (uint)index; - - if (ioctl(fd, Interop.VIDIOC_QUERYBUF, buffer) < 0) + var applied = false; + foreach (var pix_fmt in this.pix_fmts) + { + var fmt_pix = Interop.Create_v4l2_pix_format(); + fmt_pix.width = (uint)this.Characteristics.Width; + fmt_pix.height = (uint)this.Characteristics.Height; + fmt_pix.pixelformat = pix_fmt; + fmt_pix.field = (uint)v4l2_field.ANY; + + var format = Interop.Create_v4l2_format(); + format.type = (uint)v4l2_buf_type.VIDEO_CAPTURE; + format.fmt_pix = fmt_pix; + + if (ioctl(fd, Interop.VIDIOC_S_FMT, format) == 0) + { + applied = true; + break; + } + } + if (!applied) { - var code = Marshal.GetLastWin32Error(); throw new ArgumentException( - $"FlashCap: Couldn't assign video buffer: Code={code}, DevicePath={this.devicePath}"); + $"FlashCap: Couldn't set video format [3]: DevicePath={this.devicePath}"); } - - if (mmap(IntPtr.Zero, buffer.length, PROT.READ, MAP.SHARED, - fd, buffer.m_offset) is { } pBuffer && - pBuffer == MAP_FAILED) + + var requestbuffers = Interop.Create_v4l2_requestbuffers(); + requestbuffers.count = BufferCount; // Flipping + requestbuffers.type = (uint)v4l2_buf_type.VIDEO_CAPTURE; + requestbuffers.memory = (uint)v4l2_memory.MMAP; + + if (ioctl(fd, Interop.VIDIOC_REQBUFS, requestbuffers) < 0) { var code = Marshal.GetLastWin32Error(); throw new ArgumentException( - $"FlashCap: Couldn't map video buffer: Code={code}, DevicePath={this.devicePath}"); + $"FlashCap: Couldn't allocate video buffer: Code={code}, DevicePath={this.devicePath}"); } - this.pBuffers[index] = pBuffer; - this.bufferLength[index] = (int)buffer.length; + for (var index = 0; index < requestbuffers.count; index++) + { + var buffer = Interop.Create_v4l2_buffer(); + buffer.type = (uint)v4l2_buf_type.VIDEO_CAPTURE; + buffer.memory = (uint)v4l2_memory.MMAP; + buffer.index = (uint)index; + + if (ioctl(fd, Interop.VIDIOC_QUERYBUF, buffer) < 0) + { + var code = Marshal.GetLastWin32Error(); + throw new ArgumentException( + $"FlashCap: Couldn't assign video buffer: Code={code}, DevicePath={this.devicePath}"); + } + + if (mmap(IntPtr.Zero, buffer.length, PROT.READ, MAP.SHARED, + fd, buffer.m_offset) is { } pBuffer && + pBuffer == MAP_FAILED) + { + var code = Marshal.GetLastWin32Error(); + throw new ArgumentException( + $"FlashCap: Couldn't map video buffer: Code={code}, DevicePath={this.devicePath}"); + } + + this.pBuffers[index] = pBuffer; + this.bufferLength[index] = (int)buffer.length; + + if (ioctl(fd, Interop.VIDIOC_QBUF, buffer) < 0) + { + var code = Marshal.GetLastWin32Error(); + throw new ArgumentException( + $"FlashCap: Couldn't enqueue video buffer: Code={code}, DevicePath={this.devicePath}"); + } + } - if (ioctl(fd, Interop.VIDIOC_QBUF, buffer) < 0) + var abortfds = new int[2]; + if (pipe(abortfds) < 0) { var code = Marshal.GetLastWin32Error(); throw new ArgumentException( - $"FlashCap: Couldn't enqueue video buffer: Code={code}, DevicePath={this.devicePath}"); + $"FlashCap: Couldn't open pipe: Code={code}, DevicePath={this.devicePath}"); } - } - var abortfds = new int[2]; - if (pipe(abortfds) < 0) - { - var code = Marshal.GetLastWin32Error(); - throw new ArgumentException( - $"FlashCap: Couldn't open pipe: Code={code}, DevicePath={this.devicePath}"); - } - this.abortrfd = abortfds[0]; - this.abortwfd = abortfds[1]; + if (ioctl( + fd, Interop.VIDIOC_STREAMON, + (int)v4l2_buf_type.VIDEO_CAPTURE) < 0) + { + var code = Marshal.GetLastWin32Error(); + throw new ArgumentException( + $"FlashCap: Couldn't start capture: Code={code}, DevicePath={this.devicePath}"); + } - var pih = NativeMethods.AllocateMemory((IntPtr)sizeof(NativeMethods.BITMAPINFOHEADER)); - try - { - var pBih = (NativeMethods.BITMAPINFOHEADER*)pih.ToPointer(); - - pBih->biSize = sizeof(NativeMethods.BITMAPINFOHEADER); - pBih->biCompression = compression; - pBih->biPlanes = 1; - pBih->biBitCount = bitCount; - pBih->biWidth = characteristics.Width; - pBih->biHeight = characteristics.Height; - pBih->biSizeImage = pBih->CalculateImageSize(); - + this.abortrfd = abortfds[0]; + this.abortwfd = abortfds[1]; this.fd = fd; - this.pBih = pih; - + + this.frameIndex = 0; + this.counter.Restart(); + + this.IsRunning = true; this.task = Task.Factory.StartNew( this.ThreadEntry, TaskCreationOptions.LongRunning); } catch { - NativeMethods.FreeMemory(pih); - throw; - } - } - catch - { - for (var index = 0; index < pBuffers.Length; index++) - { - if (this.pBuffers[index] != default && - this.bufferLength[index] != default) + for (var index = 0; index < pBuffers.Length; index++) { - munmap(this.pBuffers[index], (ulong)this.bufferLength[index]); - this.pBuffers[index] = default; - this.bufferLength[index] = default; + if (this.pBuffers[index] != default && + this.bufferLength[index] != default) + { + munmap(this.pBuffers[index], (ulong)this.bufferLength[index]); + this.pBuffers[index] = default; + this.bufferLength[index] = default; + } } + + close(fd); + throw; } - - close(fd); - throw; - } - }); - } - - ~V4L2Device() - { - if (this.abortwfd != -1) - { - write(this.abortwfd, new byte[] { 0x01 }, 1); - close(this.abortwfd); - this.abortwfd = -1; + }); } + + return TaskCompat.CompletedTask; } - protected override async Task OnDisposeAsync() + protected override async Task OnStopAsync(CancellationToken ct) { - if (this.abortwfd != -1) + if (this.IsRunning) { - await this.frameProcessor.DisposeAsync(). - ConfigureAwait(false); - - if (this.IsRunning) + if (ioctl( + this.fd, Interop.VIDIOC_STREAMOFF, + (int)v4l2_buf_type.VIDEO_CAPTURE) < 0) { - this.IsRunning = false; - ioctl( - this.fd, Interop.VIDIOC_STREAMOFF, - (int)v4l2_buf_type.VIDEO_CAPTURE); + var code = Marshal.GetLastWin32Error(); + throw new ArgumentException( + $"FlashCap: Couldn't stop capture: Code={code}, DevicePath={this.devicePath}"); } write(this.abortwfd, new byte[] { 0x01 }, 1); - await this.task. - ConfigureAwait(false); + var task = Interlocked.Exchange(ref this.task, null!); + await task.ConfigureAwait(false); close(this.abortwfd); this.abortwfd = -1; + + for (var index = 0; index < this.pBuffers.Length; index++) + { + if (this.pBuffers[index] != default && + this.bufferLength[index] != default) + { + munmap(this.pBuffers[index], (ulong)this.bufferLength[index]); + this.pBuffers[index] = default; + this.bufferLength[index] = default; + } + } + + close(this.abortrfd); + close(this.fd); + + this.abortrfd = -1; + this.fd = -1; + + this.IsRunning = false; } } @@ -274,7 +366,7 @@ static bool IsIgnore(int code) => try { - while (true) + while (this.IsRunning) { var pr = poll(fds, fds.Length, -1); if (pr < 0) @@ -315,19 +407,22 @@ static bool IsIgnore(int code) => $"FlashCap: Couldn't dequeue video buffer: Code={code}, DevicePath={this.devicePath}"); } - try - { - this.frameProcessor.OnFrameArrived( - this, - this.pBuffers[buffer.index], - (int)buffer.bytesused, - // buffer.timestamp is untrustworthy. - this.counter.ElapsedMicroseconds, - this.frameIndex++); - } - catch (Exception ex) + if ((buffer.flags & Interop.V4L2_BUF_FLAG_ERROR) != Interop.V4L2_BUF_FLAG_ERROR) { - Trace.WriteLine(ex); + try + { + this.frameProcessor.OnFrameArrived( + this, + this.pBuffers[buffer.index], + (int)buffer.bytesused, + // buffer.timestamp is untrustworthy. + this.counter.ElapsedMicroseconds, + this.frameIndex++); + } + catch (Exception ex) + { + Trace.WriteLine(ex); + } } if (ioctl(this.fd, Interop.VIDIOC_QBUF, buffer) < 0) @@ -349,74 +444,6 @@ static bool IsIgnore(int code) => { Trace.WriteLine(ex); } - finally - { - for (var index = 0; index < pBuffers.Length; index++) - { - if (this.pBuffers[index] != default && - this.bufferLength[index] != default) - { - munmap(this.pBuffers[index], (ulong)this.bufferLength[index]); - this.pBuffers[index] = default; - this.bufferLength[index] = default; - } - } - - close(this.abortrfd); - close(this.fd); - - NativeMethods.FreeMemory(this.pBih); - - this.abortrfd = -1; - this.fd = -1; - this.pBih = IntPtr.Zero; - } - } - - protected override Task OnStartAsync(CancellationToken ct) - { - if (!this.IsRunning) - { - return Task.Factory.StartNew(() => - { - this.frameIndex = 0; - this.counter.Restart(); - - if (ioctl( - this.fd, Interop.VIDIOC_STREAMON, - (int)v4l2_buf_type.VIDEO_CAPTURE) < 0) - { - var code = Marshal.GetLastWin32Error(); - throw new ArgumentException( - $"FlashCap: Couldn't start capture: Code={code}, DevicePath={this.devicePath}"); - } - this.IsRunning = true; - }); - } - - return TaskCompat.CompletedTask; - } - - protected override Task OnStopAsync(CancellationToken ct) - { - if (this.IsRunning) - { - return Task.Factory.StartNew(() => - { - this.IsRunning = false; - if (ioctl( - this.fd, Interop.VIDIOC_STREAMOFF, - (int)v4l2_buf_type.VIDEO_CAPTURE) < 0) - { - var code = Marshal.GetLastWin32Error(); - this.IsRunning = true; - throw new ArgumentException( - $"FlashCap: Couldn't stop capture: Code={code}, DevicePath={this.devicePath}"); - } - }); - } - - return TaskCompat.CompletedTask; } #if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP diff --git a/FlashCap.Core/Internal/V4L2/NativeMethods_V4L2_Interop_Utilities.cs b/FlashCap.Core/Internal/V4L2/NativeMethods_V4L2_Interop_Utilities.cs index 1e84669..bc2a84e 100644 --- a/FlashCap.Core/Internal/V4L2/NativeMethods_V4L2_Interop_Utilities.cs +++ b/FlashCap.Core/Internal/V4L2/NativeMethods_V4L2_Interop_Utilities.cs @@ -15,6 +15,7 @@ namespace FlashCap.Internal.V4L2 { partial class NativeMethods_V4L2_Interop { + public virtual uint V4L2_BUF_FLAG_ERROR => 0x40U; public virtual uint V4L2_PIX_FMT_YUY2 => 844715353U; public virtual uint V4L2_PIX_FMT_ARGB => 1111970369U;