1 // 2 // Copyright (c) 2010-2021 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 using System; 8 using System.Runtime.InteropServices; 9 10 using Antmicro.Renode.Logging; 11 using Antmicro.Renode.Exceptions; 12 using Antmicro.Renode.Utilities; 13 using Antmicro.Renode.Debugging; 14 15 namespace Antmicro.Renode 16 { 17 public static class VideoCapturer 18 { Start(string device, IEmulationElement loggingParent)19 public static bool Start(string device, IEmulationElement loggingParent) 20 { 21 #if !PLATFORM_LINUX 22 throw new RecoverableException("Video capture is supported on Linux only!"); 23 #else 24 25 // stop any previous capturer 26 Stop(); 27 28 VideoCapturer.loggingParent = loggingParent; 29 30 loggingParent.Log(LogLevel.Debug, "Opening device: {0}...", device); 31 fd = LibCWrapper.Open(device, O_RDWR); 32 33 if(fd == -1) 34 { 35 loggingParent.Log(LogLevel.Error, "Couldn't open device: {0}", device); 36 return false; 37 } 38 39 if(!CheckImageFormat()) 40 { 41 loggingParent.Log(LogLevel.Error, "Device does not support JPEG output: {0}", device); 42 LibCWrapper.Close(fd); 43 return false; 44 } 45 46 started = true; 47 return RequestBuffer(); 48 #endif 49 } 50 Stop()51 public static void Stop() 52 { 53 #if !PLATFORM_LINUX 54 throw new RecoverableException("Video capture is supported on Linux only!"); 55 #else 56 if(started) 57 { 58 started = false; 59 FreeBuffer(); 60 LibCWrapper.Close(fd); 61 } 62 #endif 63 } 64 GrabSingleFrame()65 public static byte[] GrabSingleFrame() 66 { 67 #if !PLATFORM_LINUX 68 throw new RecoverableException("Video capture is supported on Linux only!"); 69 #else 70 loggingParent.Log(LogLevel.Debug, "Grabbing a frame..."); 71 72 var framebuffer = Marshal.AllocHGlobal(FRAME_BUFFER_SIZE); 73 74 var buf = Marshal.AllocHGlobal(V4L2_BUFFER_SIZE); 75 76 // buf.index = 0; 77 Marshal.WriteInt32(buf, 0x0, 0); 78 // buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 79 Marshal.WriteInt32(buf, 0x4, V4L2_BUF_TYPE_VIDEO_CAPTURE); 80 // buf.memory = V4L2_MEMORY_USERPTR; 81 Marshal.WriteInt32(buf, 0x3c, V4L2_MEMORY_USERPTR); 82 // buf.m.userptr = PTR; 83 Marshal.WriteInt64(buf, 0x40, framebuffer.ToInt64()); 84 // buf.length = LENGTH; 85 Marshal.WriteInt32(buf, 0x48, FRAME_BUFFER_SIZE); 86 87 if(!DoIoctl(IoctlCode.VIDIOC_QBUF, buf) 88 || !DoIoctl(IoctlCode.VIDIOC_STREAMON, IntPtr.Add(buf, 0x4)) 89 || !DoIoctl(IoctlCode.VIDIOC_DQBUF, buf)) 90 { 91 return null; 92 } 93 94 DoIoctl(IoctlCode.VIDIOC_STREAMOFF, IntPtr.Add(buf, 0x4)); 95 96 // var butesUsed = buf.bytesused; 97 var bytesUsed = Marshal.ReadInt32(buf, 0x8); 98 99 var frame = new byte[bytesUsed]; 100 Marshal.Copy(framebuffer, frame, 0, bytesUsed); 101 102 Marshal.FreeHGlobal(buf); 103 Marshal.FreeHGlobal(framebuffer); 104 105 return frame; 106 #endif 107 } 108 SetImageSize(int width, int height)109 public static Tuple<int, int> SetImageSize(int width, int height) 110 { 111 #if !PLATFORM_LINUX 112 throw new RecoverableException("Video capture is supported on Linux only!"); 113 #else 114 if(!FreeBuffer()) 115 { 116 return null; 117 } 118 119 var fmt = Marshal.AllocHGlobal(V4L2_FORMAT_SIZE); 120 121 // fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE 122 Marshal.WriteInt32(fmt, 0x0, V4L2_BUF_TYPE_VIDEO_CAPTURE); 123 // fmt.fmt.pix.width = width 124 Marshal.WriteInt32(fmt, 0x8, width); 125 // fmt.fmt.pix.height = height 126 Marshal.WriteInt32(fmt, 0xc, height); 127 // fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG 128 Marshal.WriteInt32(fmt, 0x10, V4L2_PIX_FMT_JPEG); 129 // fmt.fmt.pix.field = V4L2_FIELD_NONE 130 Marshal.WriteInt32(fmt, 0x14, V4L2_FIELD_NONE); 131 132 var result = DoIoctl(IoctlCode.VIDIOC_S_FMT, fmt); 133 134 var finalWidth = Marshal.ReadInt32(fmt, 0x8); 135 var finalHeight = Marshal.ReadInt32(fmt, 0xc); 136 137 Marshal.FreeHGlobal(fmt); 138 139 if(!result) 140 { 141 return null; 142 } 143 144 if(!RequestBuffer()) 145 { 146 return null; 147 } 148 149 return Tuple.Create(finalWidth, finalHeight); 150 #endif 151 } 152 CheckImageFormat()153 private static bool CheckImageFormat() 154 { 155 var fmt = Marshal.AllocHGlobal(V4L2_FORMAT_SIZE); 156 157 // fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE 158 Marshal.WriteInt32(fmt, 0x0, V4L2_BUF_TYPE_VIDEO_CAPTURE); 159 160 var result = DoIoctl(IoctlCode.VIDIOC_G_FMT, fmt); 161 if(!result) 162 { 163 return false; 164 } 165 166 var format = Marshal.ReadInt32(fmt, 0x10); 167 Marshal.FreeHGlobal(fmt); 168 169 return format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPG; 170 } 171 RequestBuffer()172 private static bool RequestBuffer() 173 { 174 DebugHelper.Assert(!bufferAllocated); 175 176 loggingParent.Log(LogLevel.Debug, "Requesting video IO buffer..."); 177 178 // INIT 179 var req = Marshal.AllocHGlobal(V4L2_REQUESTBUFFERS_SIZE); 180 181 // req.count = 1; 182 Marshal.WriteInt32(req, 0x0, 1); 183 // req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 184 Marshal.WriteInt32(req, 0x4, V4L2_BUF_TYPE_VIDEO_CAPTURE); 185 // req.memory = V4L2_MEMORY_USERPTR; 186 Marshal.WriteInt32(req, 0x8, V4L2_MEMORY_USERPTR); 187 188 var result = DoIoctl(IoctlCode.VIDIOC_REQBUF, req); 189 190 Marshal.FreeHGlobal(req); 191 192 bufferAllocated = true; 193 194 return result; 195 } 196 FreeBuffer()197 private static bool FreeBuffer() 198 { 199 if(!bufferAllocated) 200 { 201 return true; 202 } 203 204 loggingParent.Log(LogLevel.Debug, "Freeing video IO buffer..."); 205 206 // INIT 207 var req = Marshal.AllocHGlobal(V4L2_REQUESTBUFFERS_SIZE); 208 209 // req.count = 0; 210 Marshal.WriteInt32(req, 0x0, 0); 211 // req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 212 Marshal.WriteInt32(req, 0x4, V4L2_BUF_TYPE_VIDEO_CAPTURE); 213 // req.memory = V4L2_MEMORY_USERPTR; 214 Marshal.WriteInt32(req, 0x8, V4L2_MEMORY_USERPTR); 215 216 var result = DoIoctl(IoctlCode.VIDIOC_REQBUF, req); 217 218 Marshal.FreeHGlobal(req); 219 220 bufferAllocated = false; 221 222 return result; 223 } 224 DoIoctl(IoctlCode code, IntPtr data)225 private static bool DoIoctl(IoctlCode code, IntPtr data) 226 { 227 int err; 228 if((err = LibCWrapper.Ioctl(fd, (int)code, data)) < 0) 229 { 230 var lastErrorCode = Marshal.GetLastWin32Error(); 231 var lastErrorMessage = LibCWrapper.Strerror(lastErrorCode); 232 233 loggingParent.Log(LogLevel.Error, "There was an error when executing the {0} ioctl: {1} (0x{2:X})", Enum.GetName(typeof(IoctlCode), code), lastErrorMessage, lastErrorCode); 234 LibCWrapper.Close(fd); 235 return false; 236 } 237 238 return true; 239 } 240 241 private static int fd; 242 private static bool started; 243 private static bool bufferAllocated; 244 private static IEmulationElement loggingParent; 245 246 private const int O_RDWR = 2; 247 248 private const int V4L2_BUFFER_SIZE = 0x58; 249 private const int V4L2_REQUESTBUFFERS_SIZE = 20; 250 private const int V4L2_BUF_TYPE_VIDEO_CAPTURE = 0x1; 251 private const int V4L2_MEMORY_USERPTR = 0x2; 252 253 private const int V4L2_FIELD_NONE = 0x1; 254 private const int V4L2_FORMAT_SIZE = 208; 255 256 // size of the buffer is set arbitrarily to 6MB 257 private const int FRAME_BUFFER_SIZE = 6 * 1024 * 1024; 258 259 private const int V4L2_PIX_FMT_JPEG = 0x47504a4d; 260 private const int V4L2_PIX_FMT_MJPG = 0x4745504a; 261 262 private enum IoctlCode : uint 263 { 264 VIDIOC_REQBUF = 0xc0145608, 265 VIDIOC_QBUF = 0xc058560f, 266 VIDIOC_DQBUF = 0xc0585611, 267 VIDIOC_STREAMON = 0x40045612, 268 VIDIOC_STREAMOFF = 0x40045613, 269 VIDIOC_S_FMT = 0xc0d05605, 270 VIDIOC_G_FMT = 0xc0d05604, 271 } 272 } 273 } 274