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..7f070ef 100644 --- a/FlashCap.Core/Devices/V4L2Device.cs +++ b/FlashCap.Core/Devices/V4L2Device.cs @@ -29,6 +29,7 @@ public sealed class V4L2Device : CaptureDevice private string devicePath; private TranscodeFormats transcodeFormat; private FrameProcessor frameProcessor; + private uint[] pix_fmts; private long frameIndex; @@ -66,148 +67,36 @@ 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}"); - } - - try - { - 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) - { - throw new ArgumentException( - $"FlashCap: Couldn't set video format [3]: DevicePath={this.devicePath}"); - } - - 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 allocate video buffer: Code={code}, DevicePath={this.devicePath}"); - } - - 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}"); - } - } - - 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]; - - 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.fd = fd; - this.pBih = pih; + 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; + } - 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) - { - munmap(this.pBuffers[index], (ulong)this.bufferLength[index]); - this.pBuffers[index] = default; - this.bufferLength[index] = default; - } - } - - close(fd); - throw; - } - }); + return TaskCompat.CompletedTask; } ~V4L2Device() @@ -237,8 +126,8 @@ await this.frameProcessor.DisposeAsync(). 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; @@ -274,7 +163,7 @@ static bool IsIgnore(int code) => try { - while (true) + while (this.IsRunning) { var pr = poll(fds, fds.Length, -1); if (pr < 0) @@ -379,44 +268,154 @@ protected override Task OnStartAsync(CancellationToken ct) { return Task.Factory.StartNew(() => { - this.frameIndex = 0; - this.counter.Restart(); - - if (ioctl( - this.fd, Interop.VIDIOC_STREAMON, - (int)v4l2_buf_type.VIDEO_CAPTURE) < 0) + if (open(this.devicePath, OPENBITS.O_RDWR) is { } fd && fd < 0) { var code = Marshal.GetLastWin32Error(); throw new ArgumentException( - $"FlashCap: Couldn't start capture: Code={code}, DevicePath={this.devicePath}"); + $"FlashCap: Couldn't open video device: Code={code}, DevicePath={this.devicePath}"); + } + + try + { + 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) + { + throw new ArgumentException( + $"FlashCap: Couldn't set video format [3]: DevicePath={this.devicePath}"); + } + + 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 allocate video buffer: Code={code}, DevicePath={this.devicePath}"); + } + + 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}"); + } + } + + 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}"); + } + + 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}"); + } + + this.abortrfd = abortfds[0]; + this.abortwfd = abortfds[1]; + this.fd = fd; + + this.frameIndex = 0; + this.counter.Restart(); + + this.IsRunning = true; + this.task = Task.Factory.StartNew( + this.ThreadEntry, TaskCreationOptions.LongRunning); + } + catch + { + 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(fd); + throw; } - this.IsRunning = true; }); } return TaskCompat.CompletedTask; } - protected override Task OnStopAsync(CancellationToken ct) + protected override async Task OnStopAsync(CancellationToken ct) { if (this.IsRunning) { - return Task.Factory.StartNew(() => + if (ioctl( + this.fd, Interop.VIDIOC_STREAMOFF, + (int)v4l2_buf_type.VIDEO_CAPTURE) < 0) { - 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}"); - } - }); - } + var code = Marshal.GetLastWin32Error(); + this.IsRunning = true; + throw new ArgumentException( + $"FlashCap: Couldn't stop capture: Code={code}, DevicePath={this.devicePath}"); + } - return TaskCompat.CompletedTask; + this.IsRunning = false; + var task = Interlocked.Exchange(ref this.task, null!); + await task.ConfigureAwait(false); + } } #if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP