diff --git a/src/devices/Interop/Unix/Libc/Interop.videodev2.cs b/src/devices/Interop/Unix/Libc/Interop.videodev2.cs index ecb02520dedd22932d7503db7d877cadec12bde3..dda11d71226d9878050c3ec35824f0d368201213 100644 --- a/src/devices/Interop/Unix/Libc/Interop.videodev2.cs +++ b/src/devices/Interop/Unix/Libc/Interop.videodev2.cs @@ -5,31 +5,10 @@ #pragma warning disable SA1300 // Element should begin with upper-case letter #pragma warning disable SA1307 // Field should begin with upper-case letter -internal enum VideoSettings : int -{ - VIDIOC_QUERYCAP = -2140645888, - VIDIOC_ENUM_FMT = -1069525502, - VIDIOC_CROPCAP = -1070836166, - VIDIOC_G_CROP = -1072409029, - VIDIOC_S_CROP = 1075074620, - VIDIOC_G_FMT = -1060350460, - VIDIOC_S_FMT = -1060350459, - VIDIOC_REQBUFS = -1072409080, - VIDIOC_QUERYBUF = -1069263351, - VIDIOC_STREAMON = 1074026002, - VIDIOC_STREAMOFF = 1074026003, - VIDIOC_QBUF = -1069263345, - VIDIOC_DQBUF = -1069263343, - VIDIOC_ENUM_FRAMESIZES = -1070836150, - VIDIOC_G_CTRL = -1073195493, - VIDIOC_S_CTRL = -1073195492, - VIDIOC_QUERYCTRL = -1069263324, -} - /// /// videodev2.h Request Definition /// -internal class RawVideoSettings +internal class V4l2Request { public static int VIDIOC_QUERYCAP = Interop._IOR('V', 0, typeof(v4l2_capability)); public static int VIDIOC_ENUM_FMT = Interop._IOWR('V', 2, typeof(v4l2_fmtdesc)); @@ -57,4 +36,4 @@ internal class RawVideoSettings public static int VIDIOC_S_PRIORITY = Interop._IOW('V', 68, typeof(uint)); public static int VIDIOC_ENUM_FRAMESIZES = Interop._IOWR('V', 74, typeof(v4l2_frmsizeenum)); public static int VIDIOC_PREPARE_BUF = Interop._IOWR('V', 93, typeof(v4l2_buffer)); -} \ No newline at end of file +} diff --git a/src/devices/Interop/Unix/Libc/Interop.videodev2.struct.cs b/src/devices/Interop/Unix/Libc/Interop.videodev2.struct.cs index 4caa6786bdbaaaeed6c586e6e3363bd7dd7a354f..2f82356787526f7ea573fbff4b3733eea6668b73 100644 --- a/src/devices/Interop/Unix/Libc/Interop.videodev2.struct.cs +++ b/src/devices/Interop/Unix/Libc/Interop.videodev2.struct.cs @@ -132,17 +132,35 @@ internal enum v4l2_colorspace : uint V4L2_COLORSPACE_RAW = 11, } -[StructLayout(LayoutKind.Sequential)] +[StructLayout(LayoutKind.Explicit)] internal struct v4l2_pix_format { + [FieldOffset(0)] public uint width; + [FieldOffset(4)] public uint height; + [FieldOffset(8)] public PixelFormat pixelformat; + [FieldOffset(12)] public v4l2_field field; + [FieldOffset(16)] public uint bytesperline; + [FieldOffset(20)] public uint sizeimage; + [FieldOffset(24)] public v4l2_colorspace colorspace; + [FieldOffset(28)] public uint priv; + [FieldOffset(32)] + public uint flags; + [FieldOffset(36)] + public uint ycbcr_enc; + [FieldOffset(36)] + public uint hsv_enc; + [FieldOffset(40)] + public uint quantization; + [FieldOffset(44)] + public uint xfer_func; } [StructLayout(LayoutKind.Sequential)] @@ -174,39 +192,33 @@ internal unsafe struct v4l2_window } [StructLayout(LayoutKind.Sequential)] -internal struct v4l2_vbi_format +internal unsafe 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 fixed int start[2]; + public fixed uint count[2]; public uint flags; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - public uint[] reserved; + public fixed uint reserved[2]; } [StructLayout(LayoutKind.Sequential)] -internal struct v4l2_sliced_vbi_format +internal unsafe struct v4l2_sliced_vbi_format { - public uint service_set; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] - public ushort[] service_lines; + public ushort service_set; + public fixed ushort service_lines[48]; public uint io_size; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - public uint[] reserved; + public fixed uint reserved[2]; } [StructLayout(LayoutKind.Sequential)] -internal struct v4l2_sdr_format +internal unsafe struct v4l2_sdr_format { public PixelFormat pixelformat; public uint buffersize; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)] - public byte[] reserved; + public fixed byte reserved[24]; } [StructLayout(LayoutKind.Sequential)] @@ -216,20 +228,26 @@ internal struct v4l2_meta_format public uint buffersize; } -[StructLayout(LayoutKind.Sequential)] -internal struct fmt +[StructLayout(LayoutKind.Explicit)] +internal unsafe struct fmt { + [FieldOffset(0)] public v4l2_pix_format pix; + [FieldOffset(0)] public v4l2_window win; + [FieldOffset(0)] public v4l2_vbi_format vbi; + [FieldOffset(0)] public v4l2_sliced_vbi_format sliced; + [FieldOffset(0)] public v4l2_sdr_format sdr; + [FieldOffset(0)] public v4l2_meta_format meta; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 200)] - public byte[] raw; + [FieldOffset(0)] + public fixed byte raw[200]; } -[StructLayout(LayoutKind.Sequential, Size = 204)] +[StructLayout(LayoutKind.Sequential)] internal struct v4l2_format { public v4l2_buf_type type; @@ -309,8 +327,8 @@ internal struct v4l2_buffer [StructLayout(LayoutKind.Sequential)] public struct timeval { - public uint tv_sec; - public uint tv_usec; + public nint tv_sec; + public nint tv_usec; } public timeval timestamp; @@ -325,7 +343,7 @@ internal struct v4l2_buffer [FieldOffset(0)] public uint offset; [FieldOffset(0)] - public uint userptr; + public nuint userptr; } public m_union m; @@ -335,16 +353,21 @@ internal struct v4l2_buffer public uint reserved; } -[StructLayout(LayoutKind.Sequential)] -internal struct v4l2_frmsizeenum +[StructLayout(LayoutKind.Explicit)] +internal unsafe struct v4l2_frmsizeenum { + [FieldOffset(0)] public uint index; + [FieldOffset(4)] public PixelFormat pixel_format; + [FieldOffset(8)] public v4l2_frmsizetypes type; + [FieldOffset(12)] public v4l2_frmsize_discrete discrete; + [FieldOffset(12)] public v4l2_frmsize_stepwise stepwise; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - public uint[] reserved; + [FieldOffset(36)] + public fixed uint reserved[2]; } [StructLayout(LayoutKind.Sequential)] diff --git a/src/devices/Media/VideoDevice/Devices/UnixVideoDevice.cs b/src/devices/Media/VideoDevice/Devices/UnixVideoDevice.cs index 03de73d7828277e7574c8b8e677cee3b54653b37..499baadb97611899c719bbdd3b7bb0e74890a131 100644 --- a/src/devices/Media/VideoDevice/Devices/UnixVideoDevice.cs +++ b/src/devices/Media/VideoDevice/Devices/UnixVideoDevice.cs @@ -111,14 +111,14 @@ namespace Iot.Device.Media { id = type }; - V4l2Struct(VideoSettings.VIDIOC_QUERYCTRL, ref query); + V4l2Struct(V4l2Request.VIDIOC_QUERYCTRL, ref query); // Get current value v4l2_control ctrl = new v4l2_control { id = type, }; - V4l2Struct(VideoSettings.VIDIOC_G_CTRL, ref ctrl); + V4l2Struct(V4l2Request.VIDIOC_G_CTRL, ref ctrl); return new VideoDeviceValue( type.ToString(), @@ -144,7 +144,7 @@ namespace Iot.Device.Media }; List result = new List(); - while (V4l2Struct(VideoSettings.VIDIOC_ENUM_FMT, ref fmtdesc) != -1) + while (V4l2Struct(V4l2Request.VIDIOC_ENUM_FMT, ref fmtdesc) != -1) { result.Add(fmtdesc.pixelformat); fmtdesc.index++; @@ -158,7 +158,7 @@ namespace Iot.Device.Media /// /// Pixel format. /// Supported resolution. - public override IEnumerable<(uint Width, uint Height)> GetPixelFormatResolutions(PixelFormat format) + public override IEnumerable GetPixelFormatResolutions(PixelFormat format) { Initialize(); @@ -168,10 +168,66 @@ namespace Iot.Device.Media pixel_format = format }; - List<(uint Width, uint Height)> result = new List<(uint Width, uint Height)>(); - while (V4l2Struct(VideoSettings.VIDIOC_ENUM_FRAMESIZES, ref size) != -1) + List result = new List(); + while (V4l2Struct(V4l2Request.VIDIOC_ENUM_FRAMESIZES, ref size) != -1) { - result.Add((size.discrete.width, size.discrete.height)); + Resolution resolution; + switch (size.type) + { + case v4l2_frmsizetypes.V4L2_FRMSIZE_TYPE_DISCRETE: + resolution = new Resolution + { + Type = ResolutionType.Discrete, + MinHeight = size.discrete.height, + MaxHeight = size.discrete.height, + StepHeight = 0, + MinWidth = size.discrete.width, + MaxWidth = size.discrete.width, + StepWidth = 0, + }; + break; + + case v4l2_frmsizetypes.V4L2_FRMSIZE_TYPE_CONTINUOUS: + resolution = new Resolution + { + Type = ResolutionType.Continuous, + MinHeight = size.stepwise.min_height, + MaxHeight = size.stepwise.max_height, + StepHeight = 1, + MinWidth = size.stepwise.min_width, + MaxWidth = size.stepwise.max_width, + StepWidth = 1, + }; + break; + + case v4l2_frmsizetypes.V4L2_FRMSIZE_TYPE_STEPWISE: + resolution = new Resolution + { + Type = ResolutionType.Stepwise, + MinHeight = size.stepwise.min_height, + MaxHeight = size.stepwise.max_height, + StepHeight = size.stepwise.step_height, + MinWidth = size.stepwise.min_width, + MaxWidth = size.stepwise.max_width, + StepWidth = size.stepwise.step_width, + }; + break; + + default: + resolution = new Resolution + { + Type = ResolutionType.Discrete, + MinHeight = 0, + MaxHeight = 0, + StepHeight = 0, + MinWidth = 0, + MaxWidth = 0, + StepWidth = 0, + }; + break; + } + + result.Add(resolution); size.index++; } @@ -184,12 +240,12 @@ namespace Iot.Device.Media { // Start data stream v4l2_buf_type type = v4l2_buf_type.V4L2_BUF_TYPE_VIDEO_CAPTURE; - Interop.ioctl(_deviceFileDescriptor, (int)VideoSettings.VIDIOC_STREAMON, new IntPtr(&type)); + Interop.ioctl(_deviceFileDescriptor, V4l2Request.VIDIOC_STREAMON, new IntPtr(&type)); byte[] dataBuffer = GetFrameData(buffers); // Close data stream - Interop.ioctl(_deviceFileDescriptor, (int)VideoSettings.VIDIOC_STREAMOFF, new IntPtr(&type)); + Interop.ioctl(_deviceFileDescriptor, V4l2Request.VIDIOC_STREAMOFF, new IntPtr(&type)); UnmappingFrameBuffers(buffers); @@ -205,7 +261,7 @@ namespace Iot.Device.Media type = v4l2_buf_type.V4L2_BUF_TYPE_VIDEO_CAPTURE, memory = v4l2_memory.V4L2_MEMORY_MMAP, }; - V4l2Struct(VideoSettings.VIDIOC_DQBUF, ref frame); + V4l2Struct(V4l2Request.VIDIOC_DQBUF, ref frame); // Get data from pointer IntPtr intptr = buffers[frame.index].Start; @@ -213,7 +269,7 @@ namespace Iot.Device.Media Marshal.Copy(source: intptr, destination: dataBuffer, startIndex: 0, length: (int)buffers[frame.index].Length); // Requeue the buffer - V4l2Struct(VideoSettings.VIDIOC_QBUF, ref frame); + V4l2Struct(V4l2Request.VIDIOC_QBUF, ref frame); return dataBuffer; } @@ -227,7 +283,7 @@ namespace Iot.Device.Media type = v4l2_buf_type.V4L2_BUF_TYPE_VIDEO_CAPTURE, memory = v4l2_memory.V4L2_MEMORY_MMAP }; - V4l2Struct(VideoSettings.VIDIOC_REQBUFS, ref req); + V4l2Struct(V4l2Request.VIDIOC_REQBUFS, ref req); // Mapping the applied buffer to user space V4l2FrameBuffer[] buffers = new V4l2FrameBuffer[BufferCount]; @@ -239,7 +295,7 @@ namespace Iot.Device.Media type = v4l2_buf_type.V4L2_BUF_TYPE_VIDEO_CAPTURE, memory = v4l2_memory.V4L2_MEMORY_MMAP }; - V4l2Struct(VideoSettings.VIDIOC_QUERYBUF, ref buffer); + V4l2Struct(V4l2Request.VIDIOC_QUERYBUF, ref buffer); buffers[i].Length = buffer.length; buffers[i].Start = Interop.mmap(IntPtr.Zero, (int)buffer.length, MemoryMappedProtections.PROT_READ | MemoryMappedProtections.PROT_WRITE, MemoryMappedFlags.MAP_SHARED, _deviceFileDescriptor, (int)buffer.m.offset); @@ -254,7 +310,7 @@ namespace Iot.Device.Media type = v4l2_buf_type.V4L2_BUF_TYPE_VIDEO_CAPTURE, memory = v4l2_memory.V4L2_MEMORY_MMAP }; - V4l2Struct(VideoSettings.VIDIOC_QBUF, ref buffer); + V4l2Struct(V4l2Request.VIDIOC_QBUF, ref buffer); } return buffers; @@ -278,7 +334,7 @@ namespace Iot.Device.Media } } }; - V4l2Struct(VideoSettings.VIDIOC_S_FMT, ref format); + V4l2Struct(V4l2Request.VIDIOC_S_FMT, ref format); // Set exposure type v4l2_control ctrl = new v4l2_control @@ -286,83 +342,83 @@ namespace Iot.Device.Media id = VideoDeviceValueType.ExposureType, value = (int)Settings.ExposureType }; - V4l2Struct(VideoSettings.VIDIOC_S_CTRL, ref ctrl); + V4l2Struct(V4l2Request.VIDIOC_S_CTRL, ref ctrl); // Set exposure time // If exposure type is auto, this field is invalid ctrl.id = VideoDeviceValueType.ExposureTime; ctrl.value = Settings.ExposureTime; - V4l2Struct(VideoSettings.VIDIOC_S_CTRL, ref ctrl); + V4l2Struct(V4l2Request.VIDIOC_S_CTRL, ref ctrl); // Set brightness ctrl.id = VideoDeviceValueType.Brightness; ctrl.value = Settings.Brightness; - V4l2Struct(VideoSettings.VIDIOC_S_CTRL, ref ctrl); + V4l2Struct(V4l2Request.VIDIOC_S_CTRL, ref ctrl); // Set contrast ctrl.id = VideoDeviceValueType.Contrast; ctrl.value = Settings.Contrast; - V4l2Struct(VideoSettings.VIDIOC_S_CTRL, ref ctrl); + V4l2Struct(V4l2Request.VIDIOC_S_CTRL, ref ctrl); // Set saturation ctrl.id = VideoDeviceValueType.Saturation; ctrl.value = Settings.Saturation; - V4l2Struct(VideoSettings.VIDIOC_S_CTRL, ref ctrl); + V4l2Struct(V4l2Request.VIDIOC_S_CTRL, ref ctrl); // Set sharpness ctrl.id = VideoDeviceValueType.Sharpness; ctrl.value = Settings.Sharpness; - V4l2Struct(VideoSettings.VIDIOC_S_CTRL, ref ctrl); + V4l2Struct(V4l2Request.VIDIOC_S_CTRL, ref ctrl); // Set gain ctrl.id = VideoDeviceValueType.Gain; ctrl.value = Settings.Gain; - V4l2Struct(VideoSettings.VIDIOC_S_CTRL, ref ctrl); + V4l2Struct(V4l2Request.VIDIOC_S_CTRL, ref ctrl); // Set gamma ctrl.id = VideoDeviceValueType.Gamma; ctrl.value = Settings.Gamma; - V4l2Struct(VideoSettings.VIDIOC_S_CTRL, ref ctrl); + V4l2Struct(V4l2Request.VIDIOC_S_CTRL, ref ctrl); // Set power line frequency ctrl.id = VideoDeviceValueType.PowerLineFrequency; ctrl.value = (int)Settings.PowerLineFrequency; - V4l2Struct(VideoSettings.VIDIOC_S_CTRL, ref ctrl); + V4l2Struct(V4l2Request.VIDIOC_S_CTRL, ref ctrl); // Set white balance effect ctrl.id = VideoDeviceValueType.WhiteBalanceEffect; ctrl.value = (int)Settings.WhiteBalanceEffect; - V4l2Struct(VideoSettings.VIDIOC_S_CTRL, ref ctrl); + V4l2Struct(V4l2Request.VIDIOC_S_CTRL, ref ctrl); // Set white balance temperature ctrl.id = VideoDeviceValueType.WhiteBalanceTemperature; ctrl.value = Settings.WhiteBalanceTemperature; - V4l2Struct(VideoSettings.VIDIOC_S_CTRL, ref ctrl); + V4l2Struct(V4l2Request.VIDIOC_S_CTRL, ref ctrl); // Set color effect ctrl.id = VideoDeviceValueType.ColorEffect; ctrl.value = (int)Settings.ColorEffect; - V4l2Struct(VideoSettings.VIDIOC_S_CTRL, ref ctrl); + V4l2Struct(V4l2Request.VIDIOC_S_CTRL, ref ctrl); // Set scene mode ctrl.id = VideoDeviceValueType.SceneMode; ctrl.value = (int)Settings.SceneMode; - V4l2Struct(VideoSettings.VIDIOC_S_CTRL, ref ctrl); + V4l2Struct(V4l2Request.VIDIOC_S_CTRL, ref ctrl); // Set rotate ctrl.id = VideoDeviceValueType.Rotate; ctrl.value = Settings.Rotate; - V4l2Struct(VideoSettings.VIDIOC_S_CTRL, ref ctrl); + V4l2Struct(V4l2Request.VIDIOC_S_CTRL, ref ctrl); // Set horizontal flip ctrl.id = VideoDeviceValueType.HorizontalFlip; ctrl.value = Settings.HorizontalFlip ? 1 : 0; - V4l2Struct(VideoSettings.VIDIOC_S_CTRL, ref ctrl); + V4l2Struct(V4l2Request.VIDIOC_S_CTRL, ref ctrl); // Set vertical flip ctrl.id = VideoDeviceValueType.VerticalFlip; ctrl.value = Settings.VerticalFlip ? 1 : 0; - V4l2Struct(VideoSettings.VIDIOC_S_CTRL, ref ctrl); + V4l2Struct(V4l2Request.VIDIOC_S_CTRL, ref ctrl); } private void FillVideoConnectionSettings() @@ -488,7 +544,7 @@ namespace Iot.Device.Media /// V4L2 request value /// The struct need to be read or set /// The ioctl result - private int V4l2Struct(VideoSettings request, ref T @struct) + private int V4l2Struct(int request, ref T @struct) where T : struct { IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(@struct)); diff --git a/src/devices/Media/VideoDevice/Resolution.cs b/src/devices/Media/VideoDevice/Resolution.cs new file mode 100644 index 0000000000000000000000000000000000000000..f3d60693540d020b467fe4b5227d59eaccfddb0d --- /dev/null +++ b/src/devices/Media/VideoDevice/Resolution.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Iot.Device.Media +{ + /// + /// The resolution of a video device. + /// + public class Resolution + { + /// + /// Resolution's type + /// + public ResolutionType Type { get; set; } + + /// + /// Resolution's minimum height + /// + public uint MinHeight { get; set; } + + /// + /// Resolution's maximum height + /// + public uint MaxHeight { get; set; } + + /// + /// Resolution's step for height + /// + public uint StepHeight { get; set; } + + /// + /// Resolution's minimum width + /// + public uint MinWidth { get; set; } + + /// + /// Resolution's maximum width + /// + public uint MaxWidth { get; set; } + + /// + /// Resolution's step for width + /// + public uint StepWidth { get; set; } + } +} diff --git a/src/devices/Media/VideoDevice/ResolutionType.cs b/src/devices/Media/VideoDevice/ResolutionType.cs new file mode 100644 index 0000000000000000000000000000000000000000..59338dcee8b71d1a6e4503da94881021a1554b11 --- /dev/null +++ b/src/devices/Media/VideoDevice/ResolutionType.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Iot.Device.Media +{ + /// + /// The resolution type of a video device. + /// + public enum ResolutionType : uint + { + /// + /// Discrete + /// + Discrete = v4l2_frmsizetypes.V4L2_FRMSIZE_TYPE_DISCRETE, + + /// + /// Continuous + /// + Continuous = v4l2_frmsizetypes.V4L2_FRMSIZE_TYPE_CONTINUOUS, + + /// + /// Stepwise + /// + Stepwise = v4l2_frmsizetypes.V4L2_FRMSIZE_TYPE_STEPWISE + } +} diff --git a/src/devices/Media/VideoDevice/VideoDevice.cs b/src/devices/Media/VideoDevice/VideoDevice.cs index 7e1ce88b62f217681678e4b0a3466319318df3cb..bf035a17d7d78dcd9fef8982b0185a27538bb764 100644 --- a/src/devices/Media/VideoDevice/VideoDevice.cs +++ b/src/devices/Media/VideoDevice/VideoDevice.cs @@ -75,7 +75,7 @@ namespace Iot.Device.Media /// /// Pixel format. /// Supported resolution. - public abstract IEnumerable<(uint Width, uint Height)> GetPixelFormatResolutions(PixelFormat format); + public abstract IEnumerable GetPixelFormatResolutions(PixelFormat format); /// public void Dispose() diff --git a/src/devices/Media/VideoDevice/samples/MjpegStream/MjpegStream.csproj b/src/devices/Media/VideoDevice/samples/MjpegStream/MjpegStream.csproj index 1d2b91c00e1c19182292c07e6cdbe95b066c07be..b79c705572685a9c2ba0c66b7387654bd65b58fb 100644 --- a/src/devices/Media/VideoDevice/samples/MjpegStream/MjpegStream.csproj +++ b/src/devices/Media/VideoDevice/samples/MjpegStream/MjpegStream.csproj @@ -1,7 +1,7 @@  Exe - netcoreapp3.1 + net5.0 diff --git a/src/devices/Media/VideoDevice/samples/Program.cs b/src/devices/Media/VideoDevice/samples/Program.cs index 0463a64f7e7c60502aa25a01952cb0d81bcb1e2b..4bfc471784bbdf0df8bbee9199875c4baaff1fe1 100644 --- a/src/devices/Media/VideoDevice/samples/Program.cs +++ b/src/devices/Media/VideoDevice/samples/Program.cs @@ -20,7 +20,7 @@ Console.WriteLine(); // Get the resolutions of the format foreach (var resolution in device.GetPixelFormatResolutions(PixelFormat.YUYV)) { - Console.Write($"{resolution.Width}x{resolution.Height} "); + Console.Write($"[{resolution.MinWidth}x{resolution.MinHeight}]->[{resolution.MaxWidth}x{resolution.MaxHeight}], Step [{resolution.StepWidth},{resolution.StepHeight}] "); } Console.WriteLine();