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