diff --git a/SeeShark.Example.Ascii/Program.cs b/SeeShark.Example.Ascii/Program.cs index e4b7e4b..9a6de10 100644 --- a/SeeShark.Example.Ascii/Program.cs +++ b/SeeShark.Example.Ascii/Program.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; +using System.Linq; using System.Text; using SeeShark.Decode; using SeeShark.Device; @@ -44,7 +45,7 @@ static void Main(string[] args) manager = new CameraManager(); - string devicePath; + CameraInfo device; if (args.Length < 1) { /// Select an available camera device. @@ -60,21 +61,41 @@ static void Main(string[] args) Console.Out.Flush(); if (int.TryParse(Console.ReadLine(), out int index) && index < manager.Devices.Count && index >= 0) { - devicePath = manager.Devices[index].Path; + device = manager.Devices[index]; break; } } } else { - devicePath = args[0]; + device = manager.Devices.First((ci) => ci.Path == args[0]); + } + + /// Select video input options for the given device path. + VideoInputOptions? vios = null; + if (device.AvailableVideoInputOptions != null) + { + while (true) + { + Console.WriteLine("\nVideo input options available:"); + for (int i = 0; i < device.AvailableVideoInputOptions.Length; i++) + Console.WriteLine($"| #{i}: {device.AvailableVideoInputOptions[i]}"); + + Console.Write("\nChoose an input option by index: "); + Console.Out.Flush(); + if (int.TryParse(Console.ReadLine(), out int index) && index < device.AvailableVideoInputOptions.Length && index >= 0) + { + vios = device.AvailableVideoInputOptions[index]; + break; + } + } } /// You can get a from either a string /// representing the device path, or a . // Unfortunately, she saw the manager - karen = manager.GetDevice(devicePath); + karen = manager.GetDevice(device, vios); /// Attach our method to the camera's frame event handler, /// so that we can process every coming frame the way we want. diff --git a/SeeShark/Device/CameraManager.cs b/SeeShark/Device/CameraManager.cs index a2a84cb..dbc54ca 100644 --- a/SeeShark/Device/CameraManager.cs +++ b/SeeShark/Device/CameraManager.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.InteropServices; -using DirectShowLib; +using SeeShark.Utils; namespace SeeShark.Device { @@ -51,24 +51,18 @@ protected override CameraInfo[] EnumerateDevices() { // FFmpeg doesn't implement avdevice_list_input_sources() for the DShow input format yet. // See first SeeShark issue: https://github.com/vignetteapp/SeeShark/issues/1 - if (InputFormat == DeviceInputFormat.DShow) - { - var dsDevices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice); - var devices = new CameraInfo[dsDevices.Length]; - for (int i = 0; i < dsDevices.Length; i++) - { - var dsDevice = dsDevices[i]; - devices[i] = new CameraInfo - { - Name = dsDevice.Name, - Path = $"video={dsDevice.Name}", - }; - } - return devices; - } - else + + // Supported formats won't use the default method to allow better video input options handling. + switch (InputFormat) { - return base.EnumerateDevices(); + case DeviceInputFormat.DShow: + return DShowUtils.EnumerateDevices(); + case DeviceInputFormat.V4l2: + CameraInfo[] devices = base.EnumerateDevices(); + V4l2Utils.FillDeviceOptions(devices); + return devices; + default: + return base.EnumerateDevices(); } } } diff --git a/SeeShark/Device/VideoDeviceInfo.cs b/SeeShark/Device/VideoDeviceInfo.cs index 30758a1..57761a9 100644 --- a/SeeShark/Device/VideoDeviceInfo.cs +++ b/SeeShark/Device/VideoDeviceInfo.cs @@ -14,11 +14,15 @@ public class VideoDeviceInfo : IEquatable /// /// Name of the camera. Can be null. /// - public string? Name { get; init; } + public string? Name { get; internal set; } /// /// Path of the camera device. It can be anything from a file on the system (on Linux for instance) or a UUID (on Windows for example). /// - public string Path { get; init; } = ""; + public string Path { get; internal set; } = ""; + /// + /// Available sets of video input options for this device. + /// + public VideoInputOptions[]? AvailableVideoInputOptions { get; internal set; } public bool Equals(VideoDeviceInfo? other) => Path == other?.Path; public override bool Equals(object? obj) => obj is VideoDeviceInfo info && Equals(info); diff --git a/SeeShark/Device/VideoDeviceManager.cs b/SeeShark/Device/VideoDeviceManager.cs index 0f0d433..7bb41ec 100644 --- a/SeeShark/Device/VideoDeviceManager.cs +++ b/SeeShark/Device/VideoDeviceManager.cs @@ -94,14 +94,14 @@ protected virtual TDeviceInfo[] EnumerateDevices() AVDeviceInfoList* avDeviceInfoList = null; ffmpeg.avdevice_list_input_sources(AvInputFormat, null, null, &avDeviceInfoList).ThrowExceptionIfError(); int nDevices = avDeviceInfoList->nb_devices; - var avDevices = avDeviceInfoList->devices; + AVDeviceInfo** avDevices = avDeviceInfoList->devices; - var devices = new TDeviceInfo[nDevices]; + TDeviceInfo[] devices = new TDeviceInfo[nDevices]; for (int i = 0; i < nDevices; i++) { - var avDevice = avDevices[i]; - var name = new string((sbyte*)avDevice->device_description); - var path = new string((sbyte*)avDevice->device_name); + AVDeviceInfo* avDevice = avDevices[i]; + string name = new string((sbyte*)avDevice->device_description); + string path = new string((sbyte*)avDevice->device_name); if (path == null) throw new InvalidOperationException($"Device at index {i} doesn't have a path!"); @@ -122,15 +122,15 @@ protected virtual TDeviceInfo[] EnumerateDevices() /// public void SyncDevices() { - var newDevices = EnumerateDevices().ToImmutableList(); + ImmutableList newDevices = EnumerateDevices().ToImmutableList(); if (Devices.SequenceEqual(newDevices)) return; - foreach (var device in newDevices.Except(Devices)) + foreach (TDeviceInfo device in newDevices.Except(Devices)) OnNewDevice?.Invoke(device); - foreach (var device in Devices.Except(newDevices)) + foreach (TDeviceInfo device in Devices.Except(newDevices)) OnLostDevice?.Invoke(device); Devices = newDevices; diff --git a/SeeShark/Interop/Libc/FileOpenFlags.cs b/SeeShark/Interop/Libc/FileOpenFlags.cs new file mode 100644 index 0000000..274c214 --- /dev/null +++ b/SeeShark/Interop/Libc/FileOpenFlags.cs @@ -0,0 +1,14 @@ +// Copyright (c) The Vignette Authors +// This file is part of SeeShark. +// SeeShark is licensed under the BSD 3-Clause License. See LICENSE for details. + +namespace SeeShark.Interop.Libc +{ + internal enum FileOpenFlags + { + O_RDONLY = 0x00, + O_RDWR = 0x02, + O_NONBLOCK = 0x800, + O_SYNC = 0x101000 + } +} diff --git a/SeeShark/Interop/Libc/Ioctl.cs b/SeeShark/Interop/Libc/Ioctl.cs new file mode 100644 index 0000000..24ead31 --- /dev/null +++ b/SeeShark/Interop/Libc/Ioctl.cs @@ -0,0 +1,40 @@ +// Copyright (c) The Vignette Authors +// This file is part of SeeShark. +// SeeShark is licensed under the BSD 3-Clause License. See LICENSE for details. + +using System; +using System.Runtime.InteropServices; + +namespace SeeShark.Interop.Libc +{ + internal partial class Ioctl + { + const int ioc_nrbits = 8; + const int ioc_typebits = 8; + const int ioc_sizebits = 14; + // const int ioc_dirbits = 2; + + // const int ioc_nrmask = (1 << ioc_nrbits) - 1; + // const int ioc_typemask = (1 << ioc_typebits) - 1; + // const int ioc_sizemask = (1 << ioc_sizebits) - 1; + // const int ioc_dirmask = (1 << ioc_dirbits) - 1; + + const int ioc_nrshift = 0; + const int ioc_typeshift = ioc_nrshift + ioc_nrbits; + const int ioc_sizeshift = ioc_typeshift + ioc_typebits; + const int ioc_dirshift = ioc_sizeshift + ioc_sizebits; + + const int ioc_none = 0; + const int ioc_write = 1; + const int ioc_read = 2; + + internal static int IOC(int dir, int type, int nr, int size) + => dir << ioc_dirshift | type << ioc_typeshift | nr << ioc_nrshift | size << ioc_sizeshift; + + internal static int IO(int type, int nr) => IOC(ioc_none, type, nr, 0); + internal static int IOR(int type, int nr, Type size) => IOC(ioc_read, type, nr, IOC_TYPECHECK(size)); + internal static int IOW(int type, int nr, Type size) => IOC(ioc_write, type, nr, IOC_TYPECHECK(size)); + internal static int IOWR(int type, int nr, Type size) => IOC(ioc_read | ioc_write, type, nr, IOC_TYPECHECK(size)); + internal static int IOC_TYPECHECK(Type t) => Marshal.SizeOf(t); + } +} diff --git a/SeeShark/Interop/Libc/Libc.cs b/SeeShark/Interop/Libc/Libc.cs new file mode 100644 index 0000000..eb43c59 --- /dev/null +++ b/SeeShark/Interop/Libc/Libc.cs @@ -0,0 +1,44 @@ +// Copyright (c) The Vignette Authors +// This file is part of SeeShark. +// SeeShark is licensed under the BSD 3-Clause License. See LICENSE for details. + +using System; +using System.Runtime.InteropServices; + +namespace SeeShark.Interop.Libc +{ + internal class Libc + { + private const string libc_library = "libc"; + private const string explain_library = "explain"; + + [DllImport(libc_library, SetLastError = true)] + internal static extern int open([MarshalAs(UnmanagedType.LPStr)] string pathname, FileOpenFlags flags); + + [DllImport(libc_library)] + internal static extern int close(int fd); + + [DllImport(libc_library, SetLastError = true)] + internal static extern int read(int fd, IntPtr buf, int count); + + [DllImport(libc_library, SetLastError = true)] + internal static extern int write(int fd, IntPtr buf, int count); + + #region ioctl + [DllImport(libc_library, SetLastError = true)] + internal static extern int ioctl(int fd, int request, IntPtr argp); + + [DllImport(explain_library, SetLastError = true)] + internal static extern unsafe sbyte* explain_ioctl(int fd, int request, IntPtr argp); + + [DllImport(explain_library, SetLastError = true)] + internal static extern unsafe sbyte* explain_errno_ioctl(int errno, int fd, int request, IntPtr argp); + #endregion + + [DllImport(libc_library, SetLastError = true)] + internal static extern IntPtr mmap(IntPtr addr, int length, MemoryMappedProtections prot, MemoryMappedFlags flags, int fd, int offset); + + [DllImport(libc_library)] + internal static extern int munmap(IntPtr addr, int length); + } +} diff --git a/SeeShark/Interop/Libc/MemoryMappedFlags.cs b/SeeShark/Interop/Libc/MemoryMappedFlags.cs new file mode 100644 index 0000000..09523de --- /dev/null +++ b/SeeShark/Interop/Libc/MemoryMappedFlags.cs @@ -0,0 +1,16 @@ +// Copyright (c) The Vignette Authors +// This file is part of SeeShark. +// SeeShark is licensed under the BSD 3-Clause License. See LICENSE for details. + +using System; + +namespace SeeShark.Interop.Libc +{ + [Flags] + internal enum MemoryMappedFlags + { + MAP_SHARED = 0x01, + MAP_PRIVATE = 0x02, + MAP_FIXED = 0x10 + } +} diff --git a/SeeShark/Interop/Libc/MemoryMappedProtections.cs b/SeeShark/Interop/Libc/MemoryMappedProtections.cs new file mode 100644 index 0000000..3924447 --- /dev/null +++ b/SeeShark/Interop/Libc/MemoryMappedProtections.cs @@ -0,0 +1,17 @@ +// Copyright (c) The Vignette Authors +// This file is part of SeeShark. +// SeeShark is licensed under the BSD 3-Clause License. See LICENSE for details. + +using System; + +namespace SeeShark.Interop.Libc +{ + [Flags] + internal enum MemoryMappedProtections + { + PROT_NONE = 0x0, + PROT_READ = 0x1, + PROT_WRITE = 0x2, + PROT_EXEC = 0x4 + } +} diff --git a/SeeShark/Interop/Libc/RawVideoSettings.cs b/SeeShark/Interop/Libc/RawVideoSettings.cs new file mode 100644 index 0000000..caa5f29 --- /dev/null +++ b/SeeShark/Interop/Libc/RawVideoSettings.cs @@ -0,0 +1,80 @@ +// Copyright (c) The Vignette Authors +// This file is part of SeeShark. +// SeeShark is licensed under the BSD 3-Clause License. See LICENSE for details. + +using System; + +namespace SeeShark.Interop.Libc +{ + /// + /// videodev2.h Request Definition + /// + internal static class RawVideoSettings + { + public static readonly int VIDIOC_QUERYCAP = Ioctl.IOR('V', 0, typeof(v4l2_capability)); + public static readonly int VIDIOC_ENUM_FMT = Ioctl.IOWR('V', 2, typeof(v4l2_fmtdesc)); + public static readonly int VIDIOC_G_FMT = Ioctl.IOWR('V', 4, typeof(v4l2_format)); + public static readonly int VIDIOC_S_FMT = Ioctl.IOWR('V', 5, typeof(v4l2_format)); + public static readonly int VIDIOC_REQBUFS = Ioctl.IOWR('V', 8, typeof(v4l2_requestbuffers)); + public static readonly int VIDIOC_QUERYBUF = Ioctl.IOWR('V', 9, typeof(v4l2_buffer)); + public static readonly int VIDIOC_OVERLAY = Ioctl.IOW('V', 14, typeof(int)); + public static readonly int VIDIOC_QBUF = Ioctl.IOWR('V', 15, typeof(v4l2_buffer)); + public static readonly int VIDIOC_DQBUF = Ioctl.IOWR('V', 17, typeof(v4l2_buffer)); + public static readonly int VIDIOC_STREAMON = Ioctl.IOW('V', 18, typeof(int)); + public static readonly int VIDIOC_STREAMOFF = Ioctl.IOW('V', 19, typeof(int)); + public static readonly int VIDIOC_G_PARM = Ioctl.IOWR('V', 21, typeof(v4l2_streamparm)); + public static readonly int VIDIOC_S_PARM = Ioctl.IOWR('V', 22, typeof(v4l2_streamparm)); + public static readonly int VIDIOC_G_CTRL = Ioctl.IOWR('V', 27, typeof(v4l2_control)); + public static readonly int VIDIOC_S_CTRL = Ioctl.IOWR('V', 28, typeof(v4l2_control)); + public static readonly int VIDIOC_QUERYCTRL = Ioctl.IOWR('V', 36, typeof(v4l2_queryctrl)); + public static readonly int VIDIOC_G_INPUT = Ioctl.IOR('V', 38, typeof(int)); + public static readonly int VIDIOC_S_INPUT = Ioctl.IOWR('V', 39, typeof(int)); + public static readonly int VIDIOC_G_OUTPUT = Ioctl.IOR('V', 46, typeof(int)); + public static readonly int VIDIOC_S_OUTPUT = Ioctl.IOWR('V', 47, typeof(int)); + public static readonly int VIDIOC_CROPCAP = Ioctl.IOWR('V', 58, typeof(v4l2_cropcap)); + public static readonly int VIDIOC_G_CROP = Ioctl.IOWR('V', 59, typeof(v4l2_crop)); + public static readonly int VIDIOC_S_CROP = Ioctl.IOW('V', 60, typeof(v4l2_crop)); + public static readonly int VIDIOC_TRY_FMT = Ioctl.IOWR('V', 64, typeof(v4l2_format)); + public static readonly int VIDIOC_G_PRIORITY = Ioctl.IOR('V', 67, typeof(uint)); + public static readonly int VIDIOC_S_PRIORITY = Ioctl.IOW('V', 68, typeof(uint)); + public static readonly int VIDIOC_ENUM_FRAMESIZES = Ioctl.IOWR('V', 74, typeof(v4l2_frmsizeenum)); + public static readonly int VIDIOC_ENUM_FRAMEINTERVALS = Ioctl.IOWR('V', 75, typeof(v4l2_frmivalenum)); + public static readonly int VIDIOC_PREPARE_BUF = Ioctl.IOWR('V', 93, typeof(v4l2_buffer)); + + public static void PrintConstants() + { + Console.WriteLine($" internal enum VideoSettings : int"); + Console.WriteLine($" {{"); + Console.WriteLine($" {nameof(VIDIOC_QUERYCAP)} = {VIDIOC_QUERYCAP},"); + Console.WriteLine($" {nameof(VIDIOC_ENUM_FMT)} = {VIDIOC_ENUM_FMT},"); + Console.WriteLine($" {nameof(VIDIOC_G_FMT)} = {VIDIOC_G_FMT},"); + Console.WriteLine($" {nameof(VIDIOC_S_FMT)} = {VIDIOC_S_FMT},"); + Console.WriteLine($" {nameof(VIDIOC_REQBUFS)} = {VIDIOC_REQBUFS},"); + Console.WriteLine($" {nameof(VIDIOC_QUERYBUF)} = {VIDIOC_QUERYBUF},"); + Console.WriteLine($" {nameof(VIDIOC_OVERLAY)} = {VIDIOC_OVERLAY},"); + Console.WriteLine($" {nameof(VIDIOC_QBUF)} = {VIDIOC_QBUF},"); + Console.WriteLine($" {nameof(VIDIOC_DQBUF)} = {VIDIOC_DQBUF},"); + Console.WriteLine($" {nameof(VIDIOC_STREAMON)} = {VIDIOC_STREAMON},"); + Console.WriteLine($" {nameof(VIDIOC_STREAMOFF)} = {VIDIOC_STREAMOFF},"); + Console.WriteLine($" {nameof(VIDIOC_G_PARM)} = {VIDIOC_G_PARM},"); + Console.WriteLine($" {nameof(VIDIOC_S_PARM)} = {VIDIOC_S_PARM},"); + Console.WriteLine($" {nameof(VIDIOC_G_CTRL)} = {VIDIOC_G_CTRL},"); + Console.WriteLine($" {nameof(VIDIOC_S_CTRL)} = {VIDIOC_S_CTRL},"); + Console.WriteLine($" {nameof(VIDIOC_QUERYCTRL)} = {VIDIOC_QUERYCTRL},"); + Console.WriteLine($" {nameof(VIDIOC_G_INPUT)} = {VIDIOC_G_INPUT},"); + Console.WriteLine($" {nameof(VIDIOC_S_INPUT)} = {VIDIOC_S_INPUT},"); + Console.WriteLine($" {nameof(VIDIOC_G_OUTPUT)} = {VIDIOC_G_OUTPUT},"); + Console.WriteLine($" {nameof(VIDIOC_S_OUTPUT)} = {VIDIOC_S_OUTPUT},"); + Console.WriteLine($" {nameof(VIDIOC_CROPCAP)} = {VIDIOC_CROPCAP},"); + Console.WriteLine($" {nameof(VIDIOC_G_CROP)} = {VIDIOC_G_CROP},"); + Console.WriteLine($" {nameof(VIDIOC_S_CROP)} = {VIDIOC_S_CROP},"); + Console.WriteLine($" {nameof(VIDIOC_TRY_FMT)} = {VIDIOC_TRY_FMT},"); + Console.WriteLine($" {nameof(VIDIOC_G_PRIORITY)} = {VIDIOC_G_PRIORITY},"); + Console.WriteLine($" {nameof(VIDIOC_S_PRIORITY)} = {VIDIOC_S_PRIORITY},"); + Console.WriteLine($" {nameof(VIDIOC_ENUM_FRAMESIZES)} = {VIDIOC_ENUM_FRAMESIZES},"); + Console.WriteLine($" {nameof(VIDIOC_ENUM_FRAMEINTERVALS)} = {VIDIOC_ENUM_FRAMEINTERVALS},"); + Console.WriteLine($" {nameof(VIDIOC_PREPARE_BUF)} = {VIDIOC_PREPARE_BUF},"); + Console.WriteLine($" }}"); + } + } +} diff --git a/SeeShark/Interop/Libc/V4l2InputFormat.cs b/SeeShark/Interop/Libc/V4l2InputFormat.cs new file mode 100644 index 0000000..b7a9a2e --- /dev/null +++ b/SeeShark/Interop/Libc/V4l2InputFormat.cs @@ -0,0 +1,1012 @@ +// Copyright (c) The Vignette Authors +// This file is part of SeeShark. +// SeeShark is licensed under the BSD 3-Clause License. See LICENSE for details. + +namespace SeeShark.Interop.Libc +{ + /// + /// The pixel format or codec of a video device. + /// + internal enum V4l2InputFormat : uint + { + /// + /// RGB332 + /// + RGB332 = 826427218, + + /// + /// RGB444 + /// + RGB444 = 875836498, + + /// + /// ARGB444 + /// + ARGB444 = 842093121, + + /// + /// XRGB444 + /// + XRGB444 = 842093144, + + /// + /// RGBA444 + /// + RGBA444 = 842088786, + + /// + /// RGBX444 + /// + RGBX444 = 842094674, + + /// + /// ABGR444 + /// + ABGR444 = 842089025, + + /// + /// XBGR444 + /// + XBGR444 = 842089048, + + /// + /// BGRA444 + /// + BGRA444 = 842088775, + + /// + /// BGRX444 + /// + BGRX444 = 842094658, + + /// + /// RGB555 + /// + RGB555 = 1329743698, + + /// + /// ARGB555 + /// + ARGB555 = 892424769, + + /// + /// XRGB555 + /// + XRGB555 = 892424792, + + /// + /// RGBA555 + /// + RGBA555 = 892420434, + + /// + /// RGBX555 + /// + RGBX555 = 892426322, + + /// + /// ABGR555 + /// + ABGR555 = 892420673, + + /// + /// XBGR555 + /// + XBGR555 = 892420696, + + /// + /// BGRA555 + /// + BGRA555 = 892420418, + + /// + /// BGRX555 + /// + BGRX555 = 892426306, + + /// + /// RGB565 + /// + RGB565 = 1346520914, + + /// + /// RGB555X + /// + RGB555X = 1363298130, + + /// + /// ARGB555X + /// + ARGB555X = 3039908417, + + /// + /// XRGB555X + /// + XRGB555X = 3039908440, + + /// + /// RGB565X + /// + RGB565X = 1380075346, + + /// + /// BGR666 + /// + BGR666 = 1213351746, + + /// + /// BGR24 + /// + BGR24 = 861030210, + + /// + /// RGB24 + /// + RGB24 = 859981650, + + /// + /// BGR32 + /// + BGR32 = 877807426, + + /// + /// ABGR32 + /// + ABGR32 = 875713089, + + /// + /// XBGR32 + /// + XBGR32 = 875713112, + + /// + /// BGRA32 + /// + BGRA32 = 875708754, + + /// + /// BGRX32 + /// + BGRX32 = 875714642, + + /// + /// RGB32 + /// + RGB32 = 876758866, + + /// + /// RGBA32 + /// + RGBA32 = 875708993, + + /// + /// RGBX32 + /// + RGBX32 = 875709016, + + /// + /// ARGB32 + /// + ARGB32 = 875708738, + + /// + /// XRGB32 + /// + XRGB32 = 875714626, + + /// + /// GREY + /// + GREY = 1497715271, + + /// + /// Y4 + /// + Y4 = 540291161, + + /// + /// Y6 + /// + Y6 = 540422233, + + /// + /// Y10 + /// + Y10 = 540029273, + + /// + /// Y12 + /// + Y12 = 540160345, + + /// + /// Y16 + /// + Y16 = 540422489, + + /// + /// Y16_BE + /// + Y16_BE = 2687906137, + + /// + /// Y10BPACK + /// + Y10BPACK = 1110454617, + + /// + /// Y10P + /// + Y10P = 1345335641, + + /// + /// PAL8 + /// + PAL8 = 944521552, + + /// + /// UV8 + /// + UV8 = 540563029, + + /// + /// YUYV + /// + YUYV = 1448695129, + + /// + /// YYUV + /// + YYUV = 1448434009, + + /// + /// YVYU + /// + YVYU = 1431918169, + + /// + /// UYVY + /// + UYVY = 1498831189, + + /// + /// VYUY + /// + VYUY = 1498765654, + + /// + /// Y41P + /// + Y41P = 1345401945, + + /// + /// YUV444 + /// + YUV444 = 875836505, + + /// + /// YUV555 + /// + YUV555 = 1331058009, + + /// + /// YUV565 + /// + YUV565 = 1347835225, + + /// + /// YUV32 + /// + YUV32 = 878073177, + + /// + /// AYUV32 + /// + AYUV32 = 1448433985, + + /// + /// XYUV32 + /// + XYUV32 = 1448434008, + + /// + /// VUYA32 + /// + VUYA32 = 1096373590, + + /// + /// VUYX32 + /// + VUYX32 = 1482249558, + + /// + /// HI240 + /// + HI240 = 875710792, + + /// + /// HM12 + /// + HM12 = 842091848, + + /// + /// M420 + /// + M420 = 808596557, + + /// + /// NV12 + /// + NV12 = 842094158, + + /// + /// NV21 + /// + NV21 = 825382478, + + /// + /// NV16 + /// + NV16 = 909203022, + + /// + /// NV61 + /// + NV61 = 825644622, + + /// + /// NV24 + /// + NV24 = 875714126, + + /// + /// NV42 + /// + NV42 = 842290766, + + /// + /// NV12M + /// + NV12M = 842091854, + + /// + /// NV21M + /// + NV21M = 825380174, + + /// + /// NV16M + /// + NV16M = 909200718, + + /// + /// NV61M + /// + NV61M = 825642318, + + /// + /// NV12MT + /// + NV12MT = 842091860, + + /// + /// NV12MT_16X16 + /// + NV12MT_16X16 = 842091862, + + /// + /// YUV410 + /// + YUV410 = 961959257, + + /// + /// YVU410 + /// + YVU410 = 961893977, + + /// + /// YUV411P + /// + YUV411P = 1345401140, + + /// + /// YUV420 + /// + YUV420 = 842093913, + + /// + /// YVU420 + /// + YVU420 = 842094169, + + /// + /// YUV422P + /// + YUV422P = 1345466932, + + /// + /// YUV420M + /// + YUV420M = 842091865, + + /// + /// YVU420M + /// + YVU420M = 825380185, + + /// + /// YUV422M + /// + YUV422M = 909200729, + + /// + /// YVU422M + /// + YVU422M = 825642329, + + /// + /// YUV444M + /// + YUV444M = 875711833, + + /// + /// YVU444M + /// + YVU444M = 842288473, + + /// + /// SBGGR8 + /// + SBGGR8 = 825770306, + + /// + /// SGBRG8 + /// + SGBRG8 = 1196573255, + + /// + /// SGRBG8 + /// + SGRBG8 = 1195528775, + + /// + /// SRGGB8 + /// + SRGGB8 = 1111967570, + + /// + /// SBGGR10 + /// + SBGGR10 = 808535874, + + /// + /// SGBRG10 + /// + SGBRG10 = 808534599, + + /// + /// SGRBG10 + /// + SGRBG10 = 808534338, + + /// + /// SRGGB10 + /// + SRGGB10 = 808535890, + + /// + /// SBGGR10P + /// + SBGGR10P = 1094795888, + + /// + /// SGBRG10P + /// + SGBRG10P = 1094797168, + + /// + /// SGRBG10P + /// + SGRBG10P = 1094805360, + + /// + /// SRGGB10P + /// + SRGGB10P = 1094799984, + + /// + /// SBGGR10ALAW8 + /// + SBGGR10ALAW8 = 943800929, + + /// + /// SGBRG10ALAW8 + /// + SGBRG10ALAW8 = 943802209, + + /// + /// SGRBG10ALAW8 + /// + SGRBG10ALAW8 = 943810401, + + /// + /// SRGGB10ALAW8 + /// + SRGGB10ALAW8 = 943805025, + + /// + /// SBGGR10DPCM8 + /// + SBGGR10DPCM8 = 943800930, + + /// + /// SGBRG10DPCM8 + /// + SGBRG10DPCM8 = 943802210, + + /// + /// SGRBG10DPCM8 + /// + SGRBG10DPCM8 = 808535106, + + /// + /// SRGGB10DPCM8 + /// + SRGGB10DPCM8 = 943805026, + + /// + /// SBGGR12 + /// + SBGGR12 = 842090306, + + /// + /// SGBRG12 + /// + SGBRG12 = 842089031, + + /// + /// SGRBG12 + /// + SGRBG12 = 842088770, + + /// + /// SRGGB12 + /// + SRGGB12 = 842090322, + + /// + /// SBGGR12P + /// + SBGGR12P = 1128481392, + + /// + /// SGBRG12P + /// + SGBRG12P = 1128482672, + + /// + /// SGRBG12P + /// + SGRBG12P = 1128490864, + + /// + /// SRGGB12P + /// + SRGGB12P = 1128485488, + + /// + /// SBGGR14P + /// + SBGGR14P = 1162166896, + + /// + /// SGBRG14P + /// + SGBRG14P = 1162168176, + + /// + /// SGRBG14P + /// + SGRBG14P = 1162176368, + + /// + /// SRGGB14P + /// + SRGGB14P = 1162170992, + + /// + /// SBGGR16 + /// + SBGGR16 = 844257602, + + /// + /// SGBRG16 + /// + SGBRG16 = 909197895, + + /// + /// SGRBG16 + /// + SGRBG16 = 909201991, + + /// + /// SRGGB16 + /// + SRGGB16 = 909199186, + + /// + /// HSV24 + /// + HSV24 = 861295432, + + /// + /// HSV32 + /// + HSV32 = 878072648, + + /// + /// MJPEG + /// + MJPEG = 1196444237, + + /// + /// JPEG + /// + JPEG = 1195724874, + + /// + /// DV + /// + DV = 1685288548, + + /// + /// MPEG + /// + MPEG = 1195724877, + + /// + /// H264 + /// + H264 = 875967048, + + /// + /// H264_NO_SC + /// + H264_NO_SC = 826496577, + + /// + /// H264_MVC + /// + H264_MVC = 875967053, + + /// + /// H263 + /// + H263 = 859189832, + + /// + /// MPEG1 + /// + MPEG1 = 826757197, + + /// + /// MPEG2 + /// + MPEG2 = 843534413, + + /// + /// MPEG2_SLICE + /// + MPEG2_SLICE = 1395803981, + + /// + /// MPEG4 + /// + MPEG4 = 877088845, + + /// + /// XVID + /// + XVID = 1145656920, + + /// + /// VC1_ANNEX_G + /// + VC1_ANNEX_G = 1194410838, + + /// + /// VC1_ANNEX_L + /// + VC1_ANNEX_L = 1278296918, + + /// + /// VP8 + /// + VP8 = 808996950, + + /// + /// VP9 + /// + VP9 = 809062486, + + /// + /// HEVC + /// + HEVC = 1129727304, + + /// + /// FWHT + /// + FWHT = 1414027078, + + /// + /// FWHT_STATELESS + /// + FWHT_STATELESS = 1213679187, + + /// + /// CPIA1 + /// + CPIA1 = 1095323715, + + /// + /// WNVA + /// + WNVA = 1096175191, + + /// + /// SN9C10X + /// + SN9C10X = 808532307, + + /// + /// SN9C20X_I420 + /// + SN9C20X_I420 = 808597843, + + /// + /// PWC1 + /// + PWC1 = 826496848, + + /// + /// PWC2 + /// + PWC2 = 843274064, + + /// + /// ET61X251 + /// + ET61X251 = 892483141, + + /// + /// SPCA501 + /// + SPCA501 = 825242963, + + /// + /// SPCA505 + /// + SPCA505 = 892351827, + + /// + /// SPCA508 + /// + SPCA508 = 942683475, + + /// + /// SPCA561 + /// + SPCA561 = 825636179, + + /// + /// PAC207 + /// + PAC207 = 925905488, + + /// + /// MR97310A + /// + MR97310A = 808530765, + + /// + /// JL2005BCD + /// + JL2005BCD = 808602698, + + /// + /// SN9C2028 + /// + SN9C2028 = 1481527123, + + /// + /// SQ905C + /// + SQ905C = 1127559225, + + /// + /// PJPG + /// + PJPG = 1196444240, + + /// + /// OV511 + /// + OV511 = 825308495, + + /// + /// OV518 + /// + OV518 = 942749007, + + /// + /// STV0680 + /// + STV0680 = 808990291, + + /// + /// TM6000 + /// + TM6000 = 808865108, + + /// + /// CIT_YYVYUY + /// + CIT_YYVYUY = 1448364355, + + /// + /// KONICA420 + /// + KONICA420 = 1229868875, + + /// + /// JPGL + /// + JPGL = 1279742026, + + /// + /// SE401 + /// + SE401 = 825242707, + + /// + /// S5C_UYVY_JPG + /// + S5C_UYVY_JPG = 1229141331, + + /// + /// Y8I + /// + Y8I = 541669465, + + /// + /// Y12I + /// + Y12I = 1228026201, + + /// + /// Z16 + /// + Z16 = 540422490, + + /// + /// MT21C + /// + MT21C = 825381965, + + /// + /// INZI + /// + INZI = 1230655049, + + /// + /// SUNXI_TILED_NV12 + /// + SUNXI_TILED_NV12 = 842093651, + + /// + /// CNF4 + /// + CNF4 = 877022787, + + /// + /// IPU3_SBGGR10 + /// + IPU3_SBGGR10 = 1647538281, + + /// + /// IPU3_SGBRG10 + /// + IPU3_SGBRG10 = 1731424361, + + /// + /// IPU3_SGRBG10 + /// + IPU3_SGRBG10 = 1194553449, + + /// + /// IPU3_SRGGB10 + /// + IPU3_SRGGB10 = 1915973737, + + /// + /// CU8 + /// + CU8 = 942691651, + + /// + /// CU16LE + /// + CU16LE = 909202755, + + /// + /// CS8 + /// + CS8 = 942691139, + + /// + /// CS14LE + /// + CS14LE = 875647811, + + /// + /// RU12LE + /// + RU12LE = 842093906, + + /// + /// PCU16BE + /// + PCU16BE = 909198160, + + /// + /// PCU18BE + /// + PCU18BE = 942752592, + + /// + /// PCU20BE + /// + PCU20BE = 808600400, + + /// + /// DELTA_TD16 + /// + DELTA_TD16 = 909198420, + + /// + /// DELTA_TD08 + /// + DELTA_TD08 = 942687316, + + /// + /// TU16 + /// + TU16 = 909202772, + + /// + /// TU08 + /// + TU08 = 942691668, + + /// + /// VSP1_HGO + /// + VSP1_HGO = 1213223766, + + /// + /// VSP1_HGT + /// + VSP1_HGT = 1414550358, + + /// + /// UVC + /// + UVC = 1212372565, + + /// + /// D4XX + /// + D4XX = 1482175556, + } +} diff --git a/SeeShark/Interop/Libc/VideoDeviceValueType.cs b/SeeShark/Interop/Libc/VideoDeviceValueType.cs new file mode 100644 index 0000000..c1d9454 --- /dev/null +++ b/SeeShark/Interop/Libc/VideoDeviceValueType.cs @@ -0,0 +1,93 @@ +#pragma warning disable IDE0073 +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace SeeShark.Interop.Libc +{ + /// + /// The type of a video device's control. + /// + public enum VideoDeviceValueType : uint + { + /// + /// Exposure Type + /// + ExposureType = 10094849, + + /// + /// Exposure Time + /// + ExposureTime = 10094850, + + /// + /// Sharpness + /// + Sharpness = 9963803, + + /// + /// Contrast + /// + Contrast = 9963777, + + /// + /// Brightness + /// + Brightness = 9963776, + + /// + /// Saturation + /// + Saturation = 9963778, + + /// + /// Gamma + /// + Gamma = 9963792, + + /// + /// Gain + /// + Gain = 9963795, + + /// + /// Rotate + /// + Rotate = 9963810, + + /// + /// Horizontal Flip + /// + HorizontalFlip = 9963796, + + /// + /// Vertical Flip + /// + VerticalFlip = 9963797, + + /// + /// Power Line Frequency + /// + PowerLineFrequency = 9963800, + + /// + /// White Balance Temperature + /// + WhiteBalanceTemperature = 9963802, + + /// + /// Color Effect + /// + ColorEffect = 9963807, + + /// + /// White Balance Effect + /// + WhiteBalanceEffect = 10094868, + + /// + /// Scene Mode + /// + SceneMode = 10094874, + } +} diff --git a/SeeShark/Interop/Libc/VideoSettings.cs b/SeeShark/Interop/Libc/VideoSettings.cs new file mode 100644 index 0000000..4605673 --- /dev/null +++ b/SeeShark/Interop/Libc/VideoSettings.cs @@ -0,0 +1,39 @@ +// Copyright (c) The Vignette Authors +// This file is part of SeeShark. +// SeeShark is licensed under the BSD 3-Clause License. See LICENSE for details. + +namespace SeeShark.Interop.Libc +{ + internal enum VideoSettings : int + { + VIDIOC_QUERYCAP = -2140645888, + VIDIOC_ENUM_FMT = -1069525502, + VIDIOC_G_FMT = -1041213948, // previously -1060350460? + VIDIOC_S_FMT = -1041213947, // previously -1060350459? + VIDIOC_REQBUFS = -1072409080, + VIDIOC_QUERYBUF = -1069263351, + VIDIOC_OVERLAY = 1074025998, + VIDIOC_QBUF = -1069263345, + VIDIOC_DQBUF = -1069263343, + VIDIOC_STREAMON = 1074026002, + VIDIOC_STREAMOFF = 1074026003, + VIDIOC_G_PARM = -1060350443, + VIDIOC_S_PARM = -1060350442, + VIDIOC_G_CTRL = -1073195493, + VIDIOC_S_CTRL = -1073195492, + VIDIOC_QUERYCTRL = -1069263324, + VIDIOC_G_INPUT = -2147199450, + VIDIOC_S_INPUT = -1073457625, + VIDIOC_G_OUTPUT = -2147199442, + VIDIOC_S_OUTPUT = -1073457617, + VIDIOC_CROPCAP = -1070836166, + VIDIOC_G_CROP = -1072409029, + VIDIOC_S_CROP = 1075074620, + VIDIOC_TRY_FMT = -1041213888, + VIDIOC_G_PRIORITY = -2147199421, + VIDIOC_S_PRIORITY = 1074026052, + VIDIOC_ENUM_FRAMESIZES = -1070836150, + VIDIOC_ENUM_FRAMEINTERVALS = -1070311861, // -1069787573 doesn't work + VIDIOC_PREPARE_BUF = -1069263267, + } +} diff --git a/SeeShark/Interop/Libc/Videodev2.struct.cs b/SeeShark/Interop/Libc/Videodev2.struct.cs new file mode 100644 index 0000000..05b5a2b --- /dev/null +++ b/SeeShark/Interop/Libc/Videodev2.struct.cs @@ -0,0 +1,443 @@ +// Copyright (c) The Vignette Authors +// This file is part of SeeShark. +// SeeShark is licensed under the BSD 3-Clause License. See LICENSE for details. + +using System; +using System.Runtime.InteropServices; + +namespace SeeShark.Interop.Libc +{ + // internal struct V4l2FrameBuffer + // { + // public IntPtr Start; + // public uint Length; + // } + +#pragma warning disable IDE1006 + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_capability + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] + public string driver; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string card; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string bus_info; + public uint version; + public uint capabilities; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public uint[] reserved; + } + + internal enum v4l2_ctrl_type : uint + { + V4L2_CTRL_TYPE_INTEGER = 1, + V4L2_CTRL_TYPE_BOOLEAN = 2, + V4L2_CTRL_TYPE_MENU = 3, + V4L2_CTRL_TYPE_BUTTON = 4, + V4L2_CTRL_TYPE_INTEGER64 = 5, + V4L2_CTRL_TYPE_CTRL_CLASS = 6, + V4L2_CTRL_TYPE_STRING = 7, + V4L2_CTRL_TYPE_BITMASK = 8, + V4L2_CTRL_TYPE_INTEGER_MENU = 9, + V4L2_CTRL_COMPOUND_TYPES = 0x0100, + V4L2_CTRL_TYPE_U8 = 0x0100, + V4L2_CTRL_TYPE_U16 = 0x0101, + V4L2_CTRL_TYPE_U32 = 0x0102, + }; + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_queryctrl + { + public VideoDeviceValueType id; + public v4l2_ctrl_type type; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string name; + public int minimum; + public int maximum; + public int step; + public int default_value; + public uint flags; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public uint[] reserved; + }; + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_control + { + public VideoDeviceValueType id; + public int value; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_fmtdesc + { + public uint index; + public v4l2_buf_type type; + public uint flags; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string description; + public V4l2InputFormat pixelformat; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public uint[] reserved; + } + + internal enum v4l2_buf_type : uint + { + V4L2_BUF_TYPE_VIDEO_CAPTURE = 1, + V4L2_BUF_TYPE_VIDEO_OUTPUT = 2, + V4L2_BUF_TYPE_VIDEO_OVERLAY = 3, + V4L2_BUF_TYPE_VBI_CAPTURE = 4, + V4L2_BUF_TYPE_VBI_OUTPUT = 5, + V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6, + V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7, + V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE = 10, + V4L2_BUF_TYPE_SDR_CAPTURE = 11, + V4L2_BUF_TYPE_SDR_OUTPUT = 12, + V4L2_BUF_TYPE_META_CAPTURE = 13, + V4L2_BUF_TYPE_META_OUTPUT = 14, + V4L2_BUF_TYPE_PRIVATE = 0x80, + } + + internal enum v4l2_field : uint + { + V4L2_FIELD_ANY = 0, + V4L2_FIELD_NONE = 1, + V4L2_FIELD_TOP = 2, + V4L2_FIELD_BOTTOM = 3, + V4L2_FIELD_INTERLACED = 4, + V4L2_FIELD_SEQ_TB = 5, + V4L2_FIELD_SEQ_BT = 6, + V4L2_FIELD_ALTERNATE = 7, + V4L2_FIELD_INTERLACED_TB = 8, + V4L2_FIELD_INTERLACED_BT = 9, + } + + internal enum v4l2_colorspace : uint + { + V4L2_COLORSPACE_DEFAULT = 0, + V4L2_COLORSPACE_SMPTE170M = 1, + V4L2_COLORSPACE_SMPTE240M = 2, + V4L2_COLORSPACE_REC709 = 3, + V4L2_COLORSPACE_BT878 = 4, + V4L2_COLORSPACE_470_SYSTEM_M = 5, + V4L2_COLORSPACE_470_SYSTEM_BG = 6, + V4L2_COLORSPACE_JPEG = 7, + V4L2_COLORSPACE_SRGB = 8, + V4L2_COLORSPACE_ADOBERGB = 9, + V4L2_COLORSPACE_BT2020 = 10, + V4L2_COLORSPACE_RAW = 11, + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_pix_format + { + public uint width; + public uint height; + public V4l2InputFormat pixelformat; + public v4l2_field field; + public uint bytesperline; + public uint sizeimage; + public v4l2_colorspace colorspace; + public uint priv; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_rect + { + public int left; + public int top; + public uint width; + public uint height; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct v4l2_clip + { + public v4l2_rect c; + public v4l2_clip* next; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct v4l2_window + { + public v4l2_rect w; + public v4l2_field field; + public uint chromakey; + public v4l2_clip* clips; + public uint clipcount; + public void* bitmap; + public byte global_alpha; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_vbi_format + { + public uint sampling_rate; + public uint offset; + public uint samples_per_line; + public uint sample_format; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public uint[] start; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public uint[] count; + public uint flags; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public uint[] reserved; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_sliced_vbi_format + { + public uint service_set; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] + public ushort[] service_lines; + public uint io_size; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public uint[] reserved; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_sdr_format + { + public V4l2InputFormat pixelformat; + public uint buffersize; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)] + public byte[] reserved; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_meta_format + { + public uint dataformat; + public uint buffersize; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct fmt + { + public v4l2_pix_format pix; + public v4l2_window win; + public v4l2_vbi_format vbi; + public v4l2_sliced_vbi_format sliced; + public v4l2_sdr_format sdr; + public v4l2_meta_format meta; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 200)] + public byte[] raw; + } + + [StructLayout(LayoutKind.Sequential, Size = 204)] + internal struct v4l2_format + { + public v4l2_buf_type type; + public fmt fmt; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_captureparm + { + public uint capability; + public uint capturemode; + public v4l2_fract timeperframe; + public uint extendedmode; + public uint readbuffers; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public uint[] reserved; + } + + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_outputparm + { + public uint capability; + public uint outputmode; + public v4l2_fract timeperframe; + public uint extendedmode; + public uint writebuffers; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public uint[] reserved; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_streamparm + { + public v4l2_buf_type type; + [StructLayout(LayoutKind.Explicit)] + internal struct parm_union + { + [FieldOffset(0)] + public v4l2_captureparm capture; + [FieldOffset(0)] + public v4l2_outputparm output; + [FieldOffset(0)] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 200)] + public byte[] raw; + } + public parm_union parm; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_fract + { + public uint numerator; + public uint denominator; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_cropcap + { + public v4l2_buf_type type; + public v4l2_rect bounds; + public v4l2_rect defrect; + public v4l2_fract pixelaspect; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_crop + { + public v4l2_buf_type type; + public v4l2_rect c; + } + + internal enum v4l2_memory : uint + { + V4L2_MEMORY_MMAP = 1, + V4L2_MEMORY_USERPTR = 2, + V4L2_MEMORY_OVERLAY = 3, + V4L2_MEMORY_DMABUF = 4, + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_requestbuffers + { + public uint count; + public v4l2_buf_type type; + public v4l2_memory memory; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public uint[] reserved; + } + + [StructLayout(LayoutKind.Sequential)] + public struct v4l2_timeval + { + public uint tv_sec; + public uint tv_usec; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_timecode + { + public uint type; + public uint flags; + public byte frames; + public byte seconds; + public byte minutes; + public byte hours; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] userbits; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_buffer + { + public uint index; + public v4l2_buf_type type; + public uint bytesused; + public uint flags; + public v4l2_field field; + + [StructLayout(LayoutKind.Sequential)] + public struct timeval + { + public uint tv_sec; + public uint tv_usec; + } + public timeval timestamp; + + public v4l2_timecode timecode; + public uint sequence; + public v4l2_memory memory; + + [StructLayout(LayoutKind.Explicit)] + public struct m_union + { + [FieldOffset(0)] + public uint offset; + [FieldOffset(0)] + public uint userptr; + } + public m_union m; + + public uint length; + public uint input; + public uint reserved; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_frmsizeenum + { + public uint index; + public V4l2InputFormat pixel_format; + public v4l2_frmsizetypes type; + public v4l2_frmsize_discrete discrete; + public v4l2_frmsize_stepwise stepwise; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public uint[] reserved; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_frmsize_discrete + { + public uint width; + public uint height; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_frmsize_stepwise + { + public uint min_width; + public uint max_width; + public uint step_width; + public uint min_height; + public uint max_height; + public uint step_height; + }; + + internal enum v4l2_frmsizetypes : uint + { + V4L2_FRMSIZE_TYPE_DISCRETE = 1, + V4L2_FRMSIZE_TYPE_CONTINUOUS = 2, + V4L2_FRMSIZE_TYPE_STEPWISE = 3, + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_frmivalenum + { + public uint index; + public V4l2InputFormat pixel_format; + public uint width; + public uint height; + public v4l2_frmivaltypes type; + public v4l2_fract discrete; + public v4l2_frmival_stepwise stepwise; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public uint[] reserved; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct v4l2_frmival_stepwise + { + public v4l2_fract min; + public v4l2_fract max; + public v4l2_fract step; + } + + internal enum v4l2_frmivaltypes : uint + { + V4L2_FRMIVAL_TYPE_DISCRETE = 1, + V4L2_FRMIVAL_TYPE_CONTINUOUS = 2, + V4L2_FRMIVAL_TYPE_STEPWISE = 3, + } +} diff --git a/SeeShark/Utils/DShowUtils.cs b/SeeShark/Utils/DShowUtils.cs new file mode 100644 index 0000000..7943fff --- /dev/null +++ b/SeeShark/Utils/DShowUtils.cs @@ -0,0 +1,198 @@ +// Copyright (c) The Vignette Authors +// This file is part of SeeShark. +// SeeShark is licensed under the BSD 3-Clause License. See LICENSE for details. + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using DirectShowLib; +using FFmpeg.AutoGen; +using SeeShark.Device; +using SeeShark.Utils.PrivateFFmpeg; + +namespace SeeShark.Utils +{ + internal static class DShowUtils + { + /// + /// Type of compression for a bitmap image. + /// See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wmf/4e588f70-bd92-4a6f-b77f-35d0feaf7a57 + /// + private enum BitmapCompression : int + { + Rgb = 0x00, + Rle8 = 0x01, + Rle4 = 0x02, + Bitfields = 0x03, + Jpeg = 0x04, + Png = 0x05, + Cmyk = 0x0B, + Cmykrle8 = 0x0C, + Cmykrle4 = 0x0D, + } + + public static CameraInfo[] EnumerateDevices() + { + DsDevice[] dsDevices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice); + CameraInfo[] devices = new CameraInfo[dsDevices.Length]; + for (int i = 0; i < dsDevices.Length; i++) + { + DsDevice dsDevice = dsDevices[i]; + devices[i] = new CameraInfo + { + Name = dsDevice.Name, + Path = $"video={dsDevice.Name}", + AvailableVideoInputOptions = getAvailableOptions(dsDevice).ToArray(), + }; + } + return devices; + } + + /// + /// Get available video input options of a DirectShow device. + /// Inspired from https://github.com/eldhosekpaul18/WebCam-Capture-Opencvsharp/blob/master/Camera%20Configuration/Camera.cs + /// + private unsafe static List getAvailableOptions(DsDevice dsDevice) + { + List options = new List(); + + try + { + uint bitCount = 0; + + IFilterGraph2 filterGraph = (IFilterGraph2)new FilterGraph(); + filterGraph.AddSourceFilterForMoniker(dsDevice.Mon, null, dsDevice.Name, out IBaseFilter sourceFilter); + IPin rawPin = DsFindPin.ByCategory(sourceFilter, PinCategory.Capture, 0); + + VideoInfoHeader v = new VideoInfoHeader(); + rawPin.EnumMediaTypes(out IEnumMediaTypes mediaTypeEnum); + + AMMediaType[] mediaTypes = new AMMediaType[1]; + IntPtr fetched = IntPtr.Zero; + mediaTypeEnum.Next(1, mediaTypes, fetched); + + while (mediaTypes[0] != null) + { + Marshal.PtrToStructure(mediaTypes[0].formatPtr, v); + + if (v.BmiHeader.Size != 0 && v.BmiHeader.BitCount != 0) + { + if (v.BmiHeader.BitCount > bitCount) + bitCount = (uint)v.BmiHeader.BitCount; + + // Part of code inspired from dshow_get_format_info in dshow.c + // https://github.com/FFmpeg/FFmpeg/blob/a64e250680fbc7296eff714b81b54b1c0e2d185f/libavdevice/dshow.c#L692-L759 + PixelFormat pixelFormat = dshowPixelFormat(v.BmiHeader.Compression, bitCount); + + AVCodecID codecId; + if (pixelFormat == PixelFormat.None) + { + AVCodecTag*[] tags = new[] + { + ffmpeg.avformat_get_riff_video_tags(), + null, + }; + + fixed (AVCodecTag** tagsPtr = tags) + { + codecId = ffmpeg.av_codec_get_id(tagsPtr, bitCount); + } + } + else + { + codecId = AVCodecID.AV_CODEC_ID_RAWVIDEO; + } + + var vio = new VideoInputOptions + { + VideoSize = (v.BmiHeader.Width, v.BmiHeader.Height), + // https://docs.microsoft.com/en-us/windows/win32/directshow/configure-the-video-output-format + // "frames per second = 10,000,000 / frame duration" + Framerate = ffmpeg.av_d2q((double)10_000_000L / v.AvgTimePerFrame, (int)10_000_000L), + }; + + if ((codecId != AVCodecID.AV_CODEC_ID_NONE) && (codecId != AVCodecID.AV_CODEC_ID_RAWVIDEO)) + { + AVCodec* codec = ffmpeg.avcodec_find_decoder(codecId); + vio.InputFormat = new string((sbyte*)codec->name); + vio.IsRaw = true; + } + else if (pixelFormat == PixelFormat.None) + { + // https://learn.microsoft.com/en-us/windows/win32/directshow/h-264-video-types: + if (mediaTypes[0].subType.Equals(MediaSubType.Video.H264) + || mediaTypes[0].subType.Equals(MediaSubType.Video.h264) + || mediaTypes[0].subType.Equals(MediaSubType.Video.X264) + || mediaTypes[0].subType.Equals(MediaSubType.Video.x264) + || mediaTypes[0].subType.Equals(MediaSubType.Video.Avc1) + || mediaTypes[0].subType.Equals(MediaSubType.Video.avc1)) + { + vio.InputFormat = "h264"; + vio.IsRaw = true; + } + else if (Equals(mediaTypes[0].subType, MediaSubType.MJPG)) + { + vio.InputFormat = "mjpeg"; + vio.IsRaw = true; + } + else + { + // TODO: remove? maybe? idk + Console.Error.WriteLine($"Warning: could not handle media type {mediaTypes[0].subType}"); + } + } + + if (pixelFormat != PixelFormat.None) + vio.InputFormat = pixelFormat.ToString().ToLower(); + + options.Add(vio); + } + mediaTypeEnum.Next(1, mediaTypes, fetched); + } + } + catch (Exception) + { + } + + return options; + } + + /// + /// Ported from libavdevice/dshow.c - dshow_pixfmt. + /// See https://github.com/FFmpeg/FFmpeg/blob/a64e250680fbc7296eff714b81b54b1c0e2d185f/libavdevice/dshow.c#L59-L80 + /// + private static PixelFormat dshowPixelFormat(int compression, uint bitCount) + { + if (compression == (int)BitmapCompression.Bitfields || compression == (int)BitmapCompression.Rgb) + { + // Caution: There's something going on with BE vs LE pixel formats that I don't fully understand. + // I'm using little endian variants of the missing pixel formats until I find a better solution. + // https://github.com/FFmpeg/FFmpeg/blob/a64e250680fbc7296eff714b81b54b1c0e2d185f/libavutil/pixfmt.h#L373-L377 + + // 1-8 are untested + switch (bitCount) + { + case 1: + return PixelFormat.Monowhite; + case 4: + return PixelFormat.Rgb4; + case 8: + return PixelFormat.Rgb8; + case 16: + // This pixel format was originally RGB555. + // https://github.com/FFmpeg/FFmpeg/blob/a64e250680fbc7296eff714b81b54b1c0e2d185f/libavutil/pixfmt.h#L394 + return PixelFormat.Rgb555Le; + case 24: + return PixelFormat.Bgr24; + case 32: + // This pixel format was originally 0RGB32. + // https://github.com/FFmpeg/FFmpeg/blob/a64e250680fbc7296eff714b81b54b1c0e2d185f/libavutil/pixfmt.h#L383 + return PixelFormat.Bgrx; + } + } + + // All others + return PixelFormatTag.FindRawPixelFormat(compression); + } + } +} diff --git a/SeeShark/Utils/PrivateFFmpeg/PixelFormatTag.cs b/SeeShark/Utils/PrivateFFmpeg/PixelFormatTag.cs new file mode 100644 index 0000000..fc5a98c --- /dev/null +++ b/SeeShark/Utils/PrivateFFmpeg/PixelFormatTag.cs @@ -0,0 +1,315 @@ +// Copyright (c) The Vignette Authors +// This file is part of SeeShark. +// SeeShark is licensed under the BSD 3-Clause License. See LICENSE for details. + +namespace SeeShark.Utils.PrivateFFmpeg +{ + /// + /// https://github.com/FFmpeg/FFmpeg/blob/a64e250680fbc7296eff714b81b54b1c0e2d185f/libavcodec/raw.h#L32-L35 + /// + internal class PixelFormatTag + { + public PixelFormat PixelFormat { get; set; } + public int FourCC { get; set; } + + public PixelFormatTag(PixelFormat pixelFormat, int fourcc) + { + PixelFormat = pixelFormat; + FourCC = fourcc; + } + +#pragma warning disable CS0618 + /// + /// https://github.com/FFmpeg/FFmpeg/blob/a64e250680fbc7296eff714b81b54b1c0e2d185f/libavcodec/raw.c#L31-L298 + /// + public static PixelFormatTag[] RawPixelFormatTags = new PixelFormatTag[] + { + new PixelFormatTag(PixelFormat.Yuv420P, mkTag('I', '4', '2', '0')), // Planar formats + new PixelFormatTag(PixelFormat.Yuv420P, mkTag('I', 'Y', 'U', 'V')), + new PixelFormatTag(PixelFormat.Yuv420P, mkTag('y', 'v', '1', '2')), + new PixelFormatTag(PixelFormat.Yuv420P, mkTag('Y', 'V', '1', '2')), + new PixelFormatTag(PixelFormat.Yuv410P, mkTag('Y', 'U', 'V', '9')), + new PixelFormatTag(PixelFormat.Yuv410P, mkTag('Y', 'V', 'U', '9')), + new PixelFormatTag(PixelFormat.Yuv411P, mkTag('Y', '4', '1', 'B')), + new PixelFormatTag(PixelFormat.Yuv422P, mkTag('Y', '4', '2', 'B')), + new PixelFormatTag(PixelFormat.Yuv422P, mkTag('P', '4', '2', '2')), + new PixelFormatTag(PixelFormat.Yuv422P, mkTag('Y', 'V', '1', '6')), + + // yuvjXXX formats are deprecated hacks specific to libav*, they are identical to yuvXXX + new PixelFormatTag(PixelFormat.Yuvj420P, mkTag('I', '4', '2', '0')), // Planar formats + new PixelFormatTag(PixelFormat.Yuvj420P, mkTag('I', 'Y', 'U', 'V')), + new PixelFormatTag(PixelFormat.Yuvj420P, mkTag('Y', 'V', '1', '2')), + new PixelFormatTag(PixelFormat.Yuvj422P, mkTag('Y', '4', '2', 'B')), + new PixelFormatTag(PixelFormat.Yuvj422P, mkTag('P', '4', '2', '2')), + new PixelFormatTag(PixelFormat.Gray8, mkTag('Y', '8', '0', '0')), + new PixelFormatTag(PixelFormat.Gray8, mkTag('Y', '8', ' ', ' ')), + + new PixelFormatTag(PixelFormat.Yuyv422, mkTag('Y', 'U', 'Y', '2')), // Packed formats + new PixelFormatTag(PixelFormat.Yuyv422, mkTag('Y', '4', '2', '2')), + new PixelFormatTag(PixelFormat.Yuyv422, mkTag('V', '4', '2', '2')), + new PixelFormatTag(PixelFormat.Yuyv422, mkTag('V', 'Y', 'U', 'Y')), + new PixelFormatTag(PixelFormat.Yuyv422, mkTag('Y', 'U', 'N', 'V')), + new PixelFormatTag(PixelFormat.Yuyv422, mkTag('Y', 'U', 'Y', 'V')), + new PixelFormatTag(PixelFormat.Yvyu422, mkTag('Y', 'V', 'Y', 'U')), // Philips + new PixelFormatTag(PixelFormat.Uyvy422, mkTag('U', 'Y', 'V', 'Y')), + new PixelFormatTag(PixelFormat.Uyvy422, mkTag('H', 'D', 'Y', 'C')), + new PixelFormatTag(PixelFormat.Uyvy422, mkTag('U', 'Y', 'N', 'V')), + new PixelFormatTag(PixelFormat.Uyvy422, mkTag('U', 'Y', 'N', 'Y')), + new PixelFormatTag(PixelFormat.Uyvy422, mkTag('u', 'y', 'v', '1')), + new PixelFormatTag(PixelFormat.Uyvy422, mkTag('2', 'V', 'u', '1')), + new PixelFormatTag(PixelFormat.Uyvy422, mkTag('A', 'V', 'R', 'n')), // Avid AVI Codec 1:1 + new PixelFormatTag(PixelFormat.Uyvy422, mkTag('A', 'V', '1', 'x')), // Avid 1:1x + new PixelFormatTag(PixelFormat.Uyvy422, mkTag('A', 'V', 'u', 'p')), + new PixelFormatTag(PixelFormat.Uyvy422, mkTag('V', 'D', 'T', 'Z')), // SoftLab-NSK VideoTizer + new PixelFormatTag(PixelFormat.Uyvy422, mkTag('a', 'u', 'v', '2')), + new PixelFormatTag(PixelFormat.Uyvy422, mkTag('c', 'y', 'u', 'v')), // CYUV is also Creative YUV + new PixelFormatTag(PixelFormat.Uyyvyy411, mkTag('Y', '4', '1', '1')), + new PixelFormatTag(PixelFormat.Gray8, mkTag('G', 'R', 'E', 'Y')), + new PixelFormatTag(PixelFormat.Nv12, mkTag('N', 'V', '1', '2')), + new PixelFormatTag(PixelFormat.Nv21, mkTag('N', 'V', '2', '1')), + + // nut + new PixelFormatTag(PixelFormat.Rgb555Le, mkTag('R', 'G', 'B', 15)), + new PixelFormatTag(PixelFormat.Bgr555Le, mkTag('B', 'G', 'R', 15)), + new PixelFormatTag(PixelFormat.Rgb565Le, mkTag('R', 'G', 'B', 16)), + new PixelFormatTag(PixelFormat.Bgr565Le, mkTag('B', 'G', 'R', 16)), + new PixelFormatTag(PixelFormat.Rgb555Be, mkTag(15, 'B', 'G', 'R')), + new PixelFormatTag(PixelFormat.Bgr555Be, mkTag(15, 'R', 'G', 'B')), + new PixelFormatTag(PixelFormat.Rgb565Be, mkTag(16, 'B', 'G', 'R')), + new PixelFormatTag(PixelFormat.Bgr565Be, mkTag(16, 'R', 'G', 'B')), + new PixelFormatTag(PixelFormat.Rgb444Le, mkTag('R', 'G', 'B', 12)), + new PixelFormatTag(PixelFormat.Bgr444Le, mkTag('B', 'G', 'R', 12)), + new PixelFormatTag(PixelFormat.Rgb444Be, mkTag(12, 'B', 'G', 'R')), + new PixelFormatTag(PixelFormat.Bgr444Be, mkTag(12, 'R', 'G', 'B')), + new PixelFormatTag(PixelFormat.Rgba64Le, mkTag('R', 'B', 'A', 64)), + new PixelFormatTag(PixelFormat.Bgra64Le, mkTag('B', 'R', 'A', 64)), + new PixelFormatTag(PixelFormat.Rgba64Be, mkTag(64, 'R', 'B', 'A')), + new PixelFormatTag(PixelFormat.Bgra64Be, mkTag(64, 'B', 'R', 'A')), + new PixelFormatTag(PixelFormat.Rgba, mkTag('R', 'G', 'B', 'A')), + new PixelFormatTag(PixelFormat.Rgbx, mkTag('R', 'G', 'B', 0)), + new PixelFormatTag(PixelFormat.Bgra, mkTag('B', 'G', 'R', 'A')), + new PixelFormatTag(PixelFormat.Bgrx, mkTag('B', 'G', 'R', 0)), + new PixelFormatTag(PixelFormat.Abgr, mkTag('A', 'B', 'G', 'R')), + new PixelFormatTag(PixelFormat.Xbgr, mkTag(0, 'B', 'G', 'R')), + new PixelFormatTag(PixelFormat.Argb, mkTag('A', 'R', 'G', 'B')), + new PixelFormatTag(PixelFormat.Xrgb, mkTag(0, 'R', 'G', 'B')), + new PixelFormatTag(PixelFormat.Rgb24, mkTag('R', 'G', 'B', 24)), + new PixelFormatTag(PixelFormat.Bgr24, mkTag('B', 'G', 'R', 24)), + new PixelFormatTag(PixelFormat.Yuv411P, mkTag('4', '1', '1', 'P')), + new PixelFormatTag(PixelFormat.Yuv422P, mkTag('4', '2', '2', 'P')), + new PixelFormatTag(PixelFormat.Yuvj422P, mkTag('4', '2', '2', 'P')), + new PixelFormatTag(PixelFormat.Yuv440P, mkTag('4', '4', '0', 'P')), + new PixelFormatTag(PixelFormat.Yuvj440P, mkTag('4', '4', '0', 'P')), + new PixelFormatTag(PixelFormat.Yuv444P, mkTag('4', '4', '4', 'P')), + new PixelFormatTag(PixelFormat.Yuvj444P, mkTag('4', '4', '4', 'P')), + new PixelFormatTag(PixelFormat.Monowhite, mkTag('B', '1', 'W', '0')), + new PixelFormatTag(PixelFormat.Monoblack, mkTag('B', '0', 'W', '1')), + new PixelFormatTag(PixelFormat.Bgr8, mkTag('B', 'G', 'R', 8)), + new PixelFormatTag(PixelFormat.Rgb8, mkTag('R', 'G', 'B', 8)), + new PixelFormatTag(PixelFormat.Bgr4, mkTag('B', 'G', 'R', 4)), + new PixelFormatTag(PixelFormat.Rgb4, mkTag('R', 'G', 'B', 4)), + new PixelFormatTag(PixelFormat.Rgb4Byte,mkTag('B', '4', 'B', 'Y')), + new PixelFormatTag(PixelFormat.Bgr4Byte,mkTag('R', '4', 'B', 'Y')), + new PixelFormatTag(PixelFormat.Rgb48Le, mkTag('R', 'G', 'B', 48)), + new PixelFormatTag(PixelFormat.Rgb48Be, mkTag(48, 'R', 'G', 'B')), + new PixelFormatTag(PixelFormat.Bgr48Le, mkTag('B', 'G', 'R', 48)), + new PixelFormatTag(PixelFormat.Bgr48Be, mkTag(48, 'B', 'G', 'R')), + new PixelFormatTag(PixelFormat.Gray9Le, mkTag('Y', '1', 0, 9)), + new PixelFormatTag(PixelFormat.Gray9Be, mkTag(9, 0, '1', 'Y')), + new PixelFormatTag(PixelFormat.Gray10Le, mkTag('Y', '1', 0, 10)), + new PixelFormatTag(PixelFormat.Gray10Be, mkTag(10, 0, '1', 'Y')), + new PixelFormatTag(PixelFormat.Gray12Le, mkTag('Y', '1', 0, 12)), + new PixelFormatTag(PixelFormat.Gray12Be, mkTag(12, 0, '1', 'Y')), + new PixelFormatTag(PixelFormat.Gray14Le, mkTag('Y', '1', 0, 14)), + new PixelFormatTag(PixelFormat.Gray14Be, mkTag(14, 0, '1', 'Y')), + new PixelFormatTag(PixelFormat.Gray16Le, mkTag('Y', '1', 0, 16)), + new PixelFormatTag(PixelFormat.Gray16Be, mkTag(16, 0, '1', 'Y')), + new PixelFormatTag(PixelFormat.Yuv420P9Le, mkTag('Y', '3', 11, 9)), + new PixelFormatTag(PixelFormat.Yuv420P9Be, mkTag(9, 11, '3', 'Y')), + new PixelFormatTag(PixelFormat.Yuv422P9Le, mkTag('Y', '3', 10, 9)), + new PixelFormatTag(PixelFormat.Yuv422P9Be, mkTag(9, 10, '3', 'Y')), + new PixelFormatTag(PixelFormat.Yuv444P9Le, mkTag('Y', '3', 0, 9)), + new PixelFormatTag(PixelFormat.Yuv444P9Be, mkTag(9, 0, '3', 'Y')), + new PixelFormatTag(PixelFormat.Yuv420P10Le, mkTag('Y', '3', 11, 10)), + new PixelFormatTag(PixelFormat.Yuv420P10Be, mkTag(10, 11, '3', 'Y')), + new PixelFormatTag(PixelFormat.Yuv422P10Le, mkTag('Y', '3', 10, 10)), + new PixelFormatTag(PixelFormat.Yuv422P10Be, mkTag(10, 10, '3', 'Y')), + new PixelFormatTag(PixelFormat.Yuv444P10Le, mkTag('Y', '3', 0, 10)), + new PixelFormatTag(PixelFormat.Yuv444P10Be, mkTag(10, 0, '3', 'Y')), + new PixelFormatTag(PixelFormat.Yuv420P12Le, mkTag('Y', '3', 11, 12)), + new PixelFormatTag(PixelFormat.Yuv420P12Be, mkTag(12, 11, '3', 'Y')), + new PixelFormatTag(PixelFormat.Yuv422P12Le, mkTag('Y', '3', 10, 12)), + new PixelFormatTag(PixelFormat.Yuv422P12Be, mkTag(12, 10, '3', 'Y')), + new PixelFormatTag(PixelFormat.Yuv444P12Le, mkTag('Y', '3', 0, 12)), + new PixelFormatTag(PixelFormat.Yuv444P12Be, mkTag(12, 0, '3', 'Y')), + new PixelFormatTag(PixelFormat.Yuv420P14Le, mkTag('Y', '3', 11, 14)), + new PixelFormatTag(PixelFormat.Yuv420P14Be, mkTag(14, 11, '3', 'Y')), + new PixelFormatTag(PixelFormat.Yuv422P14Le, mkTag('Y', '3', 10, 14)), + new PixelFormatTag(PixelFormat.Yuv422P14Be, mkTag(14, 10, '3', 'Y')), + new PixelFormatTag(PixelFormat.Yuv444P14Le, mkTag('Y', '3', 0, 14)), + new PixelFormatTag(PixelFormat.Yuv444P14Be, mkTag(14, 0, '3', 'Y')), + new PixelFormatTag(PixelFormat.Yuv420P16Le, mkTag('Y', '3', 11, 16)), + new PixelFormatTag(PixelFormat.Yuv420P16Be, mkTag(16, 11, '3', 'Y')), + new PixelFormatTag(PixelFormat.Yuv422P16Le, mkTag('Y', '3', 10, 16)), + new PixelFormatTag(PixelFormat.Yuv422P16Be, mkTag(16, 10, '3', 'Y')), + new PixelFormatTag(PixelFormat.Yuv444P16Le, mkTag('Y', '3', 0, 16)), + new PixelFormatTag(PixelFormat.Yuv444P16Be, mkTag(16, 0, '3', 'Y')), + new PixelFormatTag(PixelFormat.Yuva420P, mkTag('Y', '4', 11, 8)), + new PixelFormatTag(PixelFormat.Yuva422P, mkTag('Y', '4', 10, 8)), + new PixelFormatTag(PixelFormat.Yuva444P, mkTag('Y', '4', 0, 8)), + new PixelFormatTag(PixelFormat.Ya8, mkTag('Y', '2', 0, 8)), + new PixelFormatTag(PixelFormat.Pal8, mkTag('P', 'A', 'L', 8)), + + new PixelFormatTag(PixelFormat.Yuva420P9Le, mkTag('Y', '4', 11, 9)), + new PixelFormatTag(PixelFormat.Yuva420P9Be, mkTag(9, 11, '4', 'Y')), + new PixelFormatTag(PixelFormat.Yuva422P9Le, mkTag('Y', '4', 10, 9)), + new PixelFormatTag(PixelFormat.Yuva422P9Be, mkTag(9, 10, '4', 'Y')), + new PixelFormatTag(PixelFormat.Yuva444P9Le, mkTag('Y', '4', 0, 9)), + new PixelFormatTag(PixelFormat.Yuva444P9Be, mkTag(9, 0, '4', 'Y')), + new PixelFormatTag(PixelFormat.Yuva420P10Le, mkTag('Y', '4', 11, 10)), + new PixelFormatTag(PixelFormat.Yuva420P10Be, mkTag(10, 11, '4', 'Y')), + new PixelFormatTag(PixelFormat.Yuva422P10Le, mkTag('Y', '4', 10, 10)), + new PixelFormatTag(PixelFormat.Yuva422P10Be, mkTag(10, 10, '4', 'Y')), + new PixelFormatTag(PixelFormat.Yuva444P10Le, mkTag('Y', '4', 0, 10)), + new PixelFormatTag(PixelFormat.Yuva444P10Be, mkTag(10, 0, '4', 'Y')), + new PixelFormatTag(PixelFormat.Yuva422P12Le, mkTag('Y', '4', 10, 12)), + new PixelFormatTag(PixelFormat.Yuva422P12Be, mkTag(12, 10, '4', 'Y')), + new PixelFormatTag(PixelFormat.Yuva444P12Le, mkTag('Y', '4', 0, 12)), + new PixelFormatTag(PixelFormat.Yuva444P12Be, mkTag(12, 0, '4', 'Y')), + new PixelFormatTag(PixelFormat.Yuva420P16Le, mkTag('Y', '4', 11, 16)), + new PixelFormatTag(PixelFormat.Yuva420P16Be, mkTag(16, 11, '4', 'Y')), + new PixelFormatTag(PixelFormat.Yuva422P16Le, mkTag('Y', '4', 10, 16)), + new PixelFormatTag(PixelFormat.Yuva422P16Be, mkTag(16, 10, '4', 'Y')), + new PixelFormatTag(PixelFormat.Yuva444P16Le, mkTag('Y', '4', 0, 16)), + new PixelFormatTag(PixelFormat.Yuva444P16Be, mkTag(16, 0, '4', 'Y')), + + new PixelFormatTag(PixelFormat.Gbrp, mkTag('G', '3', 00, 8)), + new PixelFormatTag(PixelFormat.Gbrp9Le, mkTag('G', '3', 00, 9)), + new PixelFormatTag(PixelFormat.Gbrp9Be, mkTag(9, 00, '3', 'G')), + new PixelFormatTag(PixelFormat.Gbrp10Le, mkTag('G', '3', 00, 10)), + new PixelFormatTag(PixelFormat.Gbrp10Be, mkTag(10, 00, '3', 'G')), + new PixelFormatTag(PixelFormat.Gbrp12Le, mkTag('G', '3', 00, 12)), + new PixelFormatTag(PixelFormat.Gbrp12Be, mkTag(12, 00, '3', 'G')), + new PixelFormatTag(PixelFormat.Gbrp14Le, mkTag('G', '3', 00, 14)), + new PixelFormatTag(PixelFormat.Gbrp14Be, mkTag(14, 00, '3', 'G')), + new PixelFormatTag(PixelFormat.Gbrp16Le, mkTag('G', '3', 00, 16)), + new PixelFormatTag(PixelFormat.Gbrp16Be, mkTag(16, 00, '3', 'G')), + + new PixelFormatTag(PixelFormat.Gbrap, mkTag('G', '4', 00, 8)), + new PixelFormatTag(PixelFormat.Gbrap10Le, mkTag('G', '4', 00, 10)), + new PixelFormatTag(PixelFormat.Gbrap10Be, mkTag(10, 00, '4', 'G')), + new PixelFormatTag(PixelFormat.Gbrap12Le, mkTag('G', '4', 00, 12)), + new PixelFormatTag(PixelFormat.Gbrap12Be, mkTag(12, 00, '4', 'G')), + new PixelFormatTag(PixelFormat.Gbrap16Le, mkTag('G', '4', 00, 16)), + new PixelFormatTag(PixelFormat.Gbrap16Be, mkTag(16, 00, '4', 'G')), + + new PixelFormatTag(PixelFormat.Xyz12Le, mkTag('X', 'Y', 'Z', 36)), + new PixelFormatTag(PixelFormat.Xyz12Be, mkTag(36, 'Z', 'Y', 'X')), + + new PixelFormatTag(PixelFormat.BayerBggr8, mkTag(0xBA, 'B', 'G', 8)), + new PixelFormatTag(PixelFormat.BayerBggr16Le, mkTag(0xBA, 'B', 'G', 16)), + new PixelFormatTag(PixelFormat.BayerBggr16Be, mkTag(16, 'G', 'B', 0xBA)), + new PixelFormatTag(PixelFormat.BayerRggb8, mkTag(0xBA, 'R', 'G', 8)), + new PixelFormatTag(PixelFormat.BayerRggb16Le, mkTag(0xBA, 'R', 'G', 16)), + new PixelFormatTag(PixelFormat.BayerRggb16Be, mkTag(16, 'G', 'R', 0xBA)), + new PixelFormatTag(PixelFormat.BayerGbrg8, mkTag(0xBA, 'G', 'B', 8)), + new PixelFormatTag(PixelFormat.BayerGbrg16Le, mkTag(0xBA, 'G', 'B', 16)), + new PixelFormatTag(PixelFormat.BayerGbrg16Be, mkTag(16, 'B', 'G', 0xBA)), + new PixelFormatTag(PixelFormat.BayerGrbg8, mkTag(0xBA, 'G', 'R', 8)), + new PixelFormatTag(PixelFormat.BayerGrbg16Le, mkTag(0xBA, 'G', 'R', 16)), + new PixelFormatTag(PixelFormat.BayerGrbg16Be, mkTag(16, 'R', 'G', 0xBA)), + + // quicktime + new PixelFormatTag(PixelFormat.Yuv420P, mkTag('R', '4', '2', '0')), // Radius DV YUV PAL + new PixelFormatTag(PixelFormat.Yuv411P, mkTag('R', '4', '1', '1')), // Radius DV YUV NTSC + new PixelFormatTag(PixelFormat.Uyvy422, mkTag('2', 'v', 'u', 'y')), + new PixelFormatTag(PixelFormat.Uyvy422, mkTag('2', 'V', 'u', 'y')), + new PixelFormatTag(PixelFormat.Uyvy422, mkTag('A', 'V', 'U', 'I')), // FIXME merge both fields + new PixelFormatTag(PixelFormat.Uyvy422, mkTag('b', 'x', 'y', 'v')), + new PixelFormatTag(PixelFormat.Yuyv422, mkTag('y', 'u', 'v', '2')), + new PixelFormatTag(PixelFormat.Yuyv422, mkTag('y', 'u', 'v', 's')), + new PixelFormatTag(PixelFormat.Yuyv422, mkTag('D', 'V', 'O', 'O')), // Digital Voodoo SD 8 Bit + new PixelFormatTag(PixelFormat.Rgb555Le, mkTag('L', '5', '5', '5')), + new PixelFormatTag(PixelFormat.Rgb565Le, mkTag('L', '5', '6', '5')), + new PixelFormatTag(PixelFormat.Rgb565Be, mkTag('B', '5', '6', '5')), + new PixelFormatTag(PixelFormat.Bgr24, mkTag('2', '4', 'B', 'G')), + new PixelFormatTag(PixelFormat.Bgr24, mkTag('b', 'x', 'b', 'g')), + new PixelFormatTag(PixelFormat.Bgra, mkTag('B', 'G', 'R', 'A')), + new PixelFormatTag(PixelFormat.Rgba, mkTag('R', 'G', 'B', 'A')), + new PixelFormatTag(PixelFormat.Rgb24, mkTag('b', 'x', 'r', 'g')), + new PixelFormatTag(PixelFormat.Abgr, mkTag('A', 'B', 'G', 'R')), + new PixelFormatTag(PixelFormat.Gray16Be, mkTag('b', '1', '6', 'g')), + new PixelFormatTag(PixelFormat.Rgb48Be, mkTag('b', '4', '8', 'r')), + new PixelFormatTag(PixelFormat.Rgba64Be, mkTag('b', '6', '4', 'a')), + new PixelFormatTag(PixelFormat.BayerRggb16Be, mkTag('B', 'G', 'G', 'R')), + + // vlc + new PixelFormatTag(PixelFormat.Yuv410P, mkTag('I', '4', '1', '0')), + new PixelFormatTag(PixelFormat.Yuv411P, mkTag('I', '4', '1', '1')), + new PixelFormatTag(PixelFormat.Yuv422P, mkTag('I', '4', '2', '2')), + new PixelFormatTag(PixelFormat.Yuv440P, mkTag('I', '4', '4', '0')), + new PixelFormatTag(PixelFormat.Yuv444P, mkTag('I', '4', '4', '4')), + new PixelFormatTag(PixelFormat.Yuvj420P, mkTag('J', '4', '2', '0')), + new PixelFormatTag(PixelFormat.Yuvj422P, mkTag('J', '4', '2', '2')), + new PixelFormatTag(PixelFormat.Yuvj440P, mkTag('J', '4', '4', '0')), + new PixelFormatTag(PixelFormat.Yuvj444P, mkTag('J', '4', '4', '4')), + new PixelFormatTag(PixelFormat.Yuva444P, mkTag('Y', 'U', 'V', 'A')), + new PixelFormatTag(PixelFormat.Yuva420P, mkTag('I', '4', '0', 'A')), + new PixelFormatTag(PixelFormat.Yuva422P, mkTag('I', '4', '2', 'A')), + new PixelFormatTag(PixelFormat.Rgb8, mkTag('R', 'G', 'B', '2')), + new PixelFormatTag(PixelFormat.Rgb555Le, mkTag('R', 'V', '1', '5')), + new PixelFormatTag(PixelFormat.Rgb565Le, mkTag('R', 'V', '1', '6')), + new PixelFormatTag(PixelFormat.Bgr24, mkTag('R', 'V', '2', '4')), + new PixelFormatTag(PixelFormat.Bgrx, mkTag('R', 'V', '3', '2')), + new PixelFormatTag(PixelFormat.Rgba, mkTag('A', 'V', '3', '2')), + new PixelFormatTag(PixelFormat.Yuv420P9Le, mkTag('I', '0', '9', 'L')), + new PixelFormatTag(PixelFormat.Yuv420P9Be, mkTag('I', '0', '9', 'B')), + new PixelFormatTag(PixelFormat.Yuv422P9Le, mkTag('I', '2', '9', 'L')), + new PixelFormatTag(PixelFormat.Yuv422P9Be, mkTag('I', '2', '9', 'B')), + new PixelFormatTag(PixelFormat.Yuv444P9Le, mkTag('I', '4', '9', 'L')), + new PixelFormatTag(PixelFormat.Yuv444P9Be, mkTag('I', '4', '9', 'B')), + new PixelFormatTag(PixelFormat.Yuv420P10Le, mkTag('I', '0', 'A', 'L')), + new PixelFormatTag(PixelFormat.Yuv420P10Be, mkTag('I', '0', 'A', 'B')), + new PixelFormatTag(PixelFormat.Yuv422P10Le, mkTag('I', '2', 'A', 'L')), + new PixelFormatTag(PixelFormat.Yuv422P10Be, mkTag('I', '2', 'A', 'B')), + new PixelFormatTag(PixelFormat.Yuv444P10Le, mkTag('I', '4', 'A', 'L')), + new PixelFormatTag(PixelFormat.Yuv444P10Be, mkTag('I', '4', 'A', 'B')), + new PixelFormatTag(PixelFormat.Yuv420P12Le, mkTag('I', '0', 'C', 'L')), + new PixelFormatTag(PixelFormat.Yuv420P12Be, mkTag('I', '0', 'C', 'B')), + new PixelFormatTag(PixelFormat.Yuv422P12Le, mkTag('I', '2', 'C', 'L')), + new PixelFormatTag(PixelFormat.Yuv422P12Be, mkTag('I', '2', 'C', 'B')), + new PixelFormatTag(PixelFormat.Yuv444P12Le, mkTag('I', '4', 'C', 'L')), + new PixelFormatTag(PixelFormat.Yuv444P12Be, mkTag('I', '4', 'C', 'B')), + new PixelFormatTag(PixelFormat.Yuv420P16Le, mkTag('I', '0', 'F', 'L')), + new PixelFormatTag(PixelFormat.Yuv420P16Be, mkTag('I', '0', 'F', 'B')), + new PixelFormatTag(PixelFormat.Yuv444P16Le, mkTag('I', '4', 'F', 'L')), + new PixelFormatTag(PixelFormat.Yuv444P16Be, mkTag('I', '4', 'F', 'B')), + + // special + new PixelFormatTag(PixelFormat.Rgb565Le, mkTag(3, 0, 0, 0)), // flipped RGB565LE + new PixelFormatTag(PixelFormat.Yuv444P, mkTag('Y', 'V', '2', '4')), // YUV444P, swapped UV + + new PixelFormatTag(PixelFormat.None, 0), + }; +#pragma warning restore CS0618 + + /// + /// https://github.com/FFmpeg/FFmpeg/blob/a64e250680fbc7296eff714b81b54b1c0e2d185f/libavutil/macros.h#L55 + /// + private static int mkTag(int a, int b, int c, int d) => a | (b << 8) | (c << 16) | (d << 24); + + /// + /// https://github.com/FFmpeg/FFmpeg/blob/a64e250680fbc7296eff714b81b54b1c0e2d185f/libavcodec/raw.c#L341-L369 + /// + public static PixelFormat FindRawPixelFormat(int fourcc) + { + foreach (PixelFormatTag tag in RawPixelFormatTags) + { + if (tag.FourCC == fourcc) + return tag.PixelFormat; + } + + return PixelFormat.None; + } + } +} diff --git a/SeeShark/Utils/V4l2Utils.cs b/SeeShark/Utils/V4l2Utils.cs new file mode 100644 index 0000000..19a9811 --- /dev/null +++ b/SeeShark/Utils/V4l2Utils.cs @@ -0,0 +1,133 @@ +// Copyright (c) The Vignette Authors +// This file is part of SeeShark. +// SeeShark is licensed under the BSD 3-Clause License. See LICENSE for details. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using FFmpeg.AutoGen; +using SeeShark.Device; +using SeeShark.Interop.Libc; + +namespace SeeShark.Utils +{ + internal static class V4l2Utils + { + public static void FillDeviceOptions(CameraInfo[] devices) + { + foreach (CameraInfo device in devices) + device.AvailableVideoInputOptions = getAvailableOptions(device).ToArray(); + } + + /// + /// Get available video input options of a V4l2 device. + /// Inspired from https://github.com/ZhangGaoxing/v4l2.net + /// + private unsafe static List getAvailableOptions(CameraInfo device) + { + List options = new List(); + + int deviceFd = Libc.open(device.Path, FileOpenFlags.O_RDWR); + if (deviceFd < 0) + throw new IOException($"Error {Marshal.GetLastWin32Error()}: Can not open video device {device}"); + + v4l2_fmtdesc fmtdesc = new v4l2_fmtdesc + { + index = 0, + type = v4l2_buf_type.V4L2_BUF_TYPE_VIDEO_CAPTURE + }; + + List supportedInputFormats = new List(); + while (v4l2Struct(deviceFd, VideoSettings.VIDIOC_ENUM_FMT, ref fmtdesc) != -1) + { + supportedInputFormats.Add(fmtdesc.pixelformat); + fmtdesc.index++; + } + + foreach (V4l2InputFormat inputFormat in supportedInputFormats) + { + v4l2_frmsizeenum frmsize = new v4l2_frmsizeenum + { + index = 0, + pixel_format = inputFormat, + }; + + while (v4l2Struct(deviceFd, VideoSettings.VIDIOC_ENUM_FRAMESIZES, ref frmsize) != -1) + { + if (frmsize.type == v4l2_frmsizetypes.V4L2_FRMSIZE_TYPE_DISCRETE) + { + fillFrameIntervalOptions(options, deviceFd, inputFormat, frmsize.discrete.width, frmsize.discrete.height); + } + else + { + for (uint width = frmsize.stepwise.min_width; width < frmsize.stepwise.max_width; width += frmsize.stepwise.step_width) + { + for (uint height = frmsize.stepwise.min_height; height < frmsize.stepwise.max_height; height += frmsize.stepwise.step_height) + fillFrameIntervalOptions(options, deviceFd, inputFormat, width, height); + } + } + frmsize.index++; + } + } + + Libc.close(deviceFd); + return options; + } + + private static void fillFrameIntervalOptions(List options, int deviceFd, V4l2InputFormat pixelFormat, uint width, uint height) + { + v4l2_frmivalenum frmival = new v4l2_frmivalenum + { + index = 0, + pixel_format = pixelFormat, + width = width, + height = height, + }; + + while (v4l2Struct(deviceFd, VideoSettings.VIDIOC_ENUM_FRAMEINTERVALS, ref frmival) != -1) + { + if (frmival.type == v4l2_frmivaltypes.V4L2_FRMIVAL_TYPE_DISCRETE) + { + options.Add(new VideoInputOptions + { + InputFormat = pixelFormat.ToString(), + VideoSize = ((int)width, (int)height), + Framerate = new AVRational + { + num = (int)frmival.discrete.denominator, + den = (int)frmival.discrete.numerator, + }, + }); + } + frmival.index++; + } + } + + /// + /// Get and set v4l2 struct. + /// + /// V4L2 struct + /// V4L2 request value + /// The struct need to be read or set + /// The ioctl result + private static unsafe int v4l2Struct(int deviceFd, VideoSettings request, ref T @struct) + where T : struct + { + IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(@struct)); + Marshal.StructureToPtr(@struct, ptr, true); + + int result = Libc.ioctl(deviceFd, (int)request, ptr); + // if (result < 0) + // { + // int errno = Marshal.GetLastPInvokeError(); + // Console.Error.WriteLine($"Error: {errno}"); + // sbyte* explanation = Libc.explain_errno_ioctl(errno, deviceFd, (int)request, ptr); + // Console.Error.WriteLine($"- {new string(explanation)}"); + // } + + @struct = Marshal.PtrToStructure(ptr); + return result; + } + } +} diff --git a/SeeShark/VideoInputOptions.cs b/SeeShark/VideoInputOptions.cs index 5f5c54c..9b5598f 100644 --- a/SeeShark/VideoInputOptions.cs +++ b/SeeShark/VideoInputOptions.cs @@ -14,9 +14,11 @@ namespace SeeShark /// /// /// Some examples of input options are: - /// https://ffmpeg.org/ffmpeg-devices.html#video4linux2_002c-v4l2 - /// https://ffmpeg.org/ffmpeg-devices.html#dshow - /// https://ffmpeg.org/ffmpeg-devices.html#avfoundation + /// + /// https://ffmpeg.org/ffmpeg-devices.html#video4linux2_002c-v4l2 + /// https://ffmpeg.org/ffmpeg-devices.html#dshow + /// https://ffmpeg.org/ffmpeg-devices.html#avfoundation + /// /// public class VideoInputOptions { @@ -43,13 +45,19 @@ public class VideoInputOptions public AVRational? Framerate { get; set; } /// /// To request a specific input format for the video stream. + /// If the video stream is raw, it is the name of its pixel format, otherwise it is the name of its codec. /// public string? InputFormat { get; set; } + /// + /// Used on Windows only - tells us if the video stream is raw or not. + /// If the video stream is raw, it is a pixel format, otherwise it is a codec. + /// + public bool IsRaw { get; set; } /// /// Combines all properties into a dictionary of options that FFmpeg can use. /// - public virtual IDictionary ToAVDictOptions(DeviceInputFormat? inputFormat = null) + public virtual IDictionary ToAVDictOptions(DeviceInputFormat deviceFormat) { Dictionary dict = new(); @@ -58,14 +66,29 @@ public virtual IDictionary ToAVDictOptions(DeviceInputFormat? in (int width, int height) = VideoSize.Value; dict.Add("video_size", $"{width}x{height}"); } + if (Framerate != null) dict.Add("framerate", $"{Framerate.Value.num}/{Framerate.Value.den}"); + if (InputFormat != null) - dict.Add("input_format", InputFormat); + { + string key = "input_format"; + if (deviceFormat == DeviceInputFormat.DShow) + key = IsRaw ? "pixel_format" : "vcodec"; + + // I have no idea why there is an inconsistency but here we are... + string inputFormat = InputFormat switch + { + "YUYV" => "yuv422p", + "YUV420" => "yuv420p", + _ => InputFormat.ToLower(), + }; + dict.Add(key, inputFormat); + } if (VideoPosition != null) { - switch (inputFormat) + switch (deviceFormat) { case DeviceInputFormat.X11Grab: { @@ -81,7 +104,19 @@ public virtual IDictionary ToAVDictOptions(DeviceInputFormat? in } } } + return dict; } + + public override string ToString() + { + string s = $"{InputFormat} {VideoSize}"; + if (Framerate != null) + { + double fps = ffmpeg.av_q2d(Framerate.Value); + s += $" - {fps:0.000} fps"; + } + return s; + } } }