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;
+ }
}
}