Browse Source

vcap: Initial implementation

This UPM module captures a still frame from a Linux V4L device, such
as a USB webcam, and and then allows you to save it as a JPEG image
into a file.

The camera and driver in use must support streaming, mmap-able buffers
and must provide data in YUYV format.  This should encompass most
video cameras out there.  It has been tested with a few off the shelf
USB cameras without any problems.

Signed-off-by: Jon Trulson <jtrulson@ics.com>
Signed-off-by: Mihai Tudor Panu <mihai.tudor.panu@intel.com>
Jon Trulson 8 years ago
parent
commit
4f6be750c7

+ 1
- 0
examples/c++/CMakeLists.txt View File

@@ -264,6 +264,7 @@ if (BACNET_FOUND)
264 264
   include_directories(${PROJECT_SOURCE_DIR}/src/bacnetmstp)
265 265
   add_example (e50hx)
266 266
 endif()
267
+add_example (vcap)
267 268
 
268 269
 # These are special cases where you specify example binary, source file and module(s)
269 270
 include_directories (${PROJECT_SOURCE_DIR}/src)

+ 70
- 0
examples/c++/vcap.cxx View File

@@ -0,0 +1,70 @@
1
+/*
2
+ * Author: Jon Trulson <jtrulson@ics.com>
3
+ * Copyright (c) 2016 Intel Corporation.
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining
6
+ * a copy of this software and associated documentation files (the
7
+ * "Software"), to deal in the Software without restriction, including
8
+ * without limitation the rights to use, copy, modify, merge, publish,
9
+ * distribute, sublicense, and/or sell copies of the Software, and to
10
+ * permit persons to whom the Software is furnished to do so, subject to
11
+ * the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be
14
+ * included in all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ */
24
+
25
+#include <unistd.h>
26
+#include <iostream>
27
+
28
+#include "vcap.hpp"
29
+
30
+using namespace std;
31
+
32
+int main(int argc, char **argv)
33
+{
34
+//! [Interesting]
35
+
36
+  string defaultDev = "/dev/video0";
37
+
38
+  // if an argument was specified, use it as the device instead
39
+  if (argc > 1)
40
+    defaultDev = string(argv[1]);
41
+
42
+  cout << "Using device " << defaultDev << endl;
43
+  cout << "Initializing..." << endl;
44
+
45
+  // Instantiate an VCAP instance, using the specified video device
46
+  upm::VCAP *sensor = new upm::VCAP(defaultDev);
47
+
48
+  // enable some debug/verbose output
49
+  sensor->setDebug(true);
50
+
51
+  // This is just a hint.  The kernel can change this to a lower
52
+  // resolution that the hardware supports.  Use getWidth() and
53
+  // getHeight() methods to see what the kernel actually chose if you
54
+  // care.
55
+  sensor->setResolution(1920, 1080);
56
+
57
+  // capture an image
58
+  sensor->captureImage();
59
+
60
+  // convert and save it as a jpeg
61
+  sensor->saveImage("video-img1.jpg");
62
+
63
+  cout << "Exiting..." << endl;
64
+
65
+  delete sensor;
66
+
67
+//! [Interesting]
68
+
69
+  return 0;
70
+}

+ 1
- 0
examples/java/CMakeLists.txt View File

@@ -120,6 +120,7 @@ endif()
120 120
 if (BACNET_FOUND)
121 121
   add_example(E50HX_Example e50hx)
122 122
 endif()
123
+add_example(VCAP_Example vcap)
123 124
 
124 125
 add_example_with_path(Jhd1313m1_lcdSample lcd i2clcd)
125 126
 add_example_with_path(Jhd1313m1Sample lcd i2clcd)

+ 60
- 0
examples/java/VCAP_Example.java View File

@@ -0,0 +1,60 @@
1
+/*
2
+ * Author: Jon Trulson <jtrulson@ics.com>
3
+ * Copyright (c) 2016 Intel Corporation.
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining
6
+ * a copy of this software and associated documentation files (the
7
+ * "Software"), to deal in the Software without restriction, including
8
+ * without limitation the rights to use, copy, modify, merge, publish,
9
+ * distribute, sublicense, and/or sell copies of the Software, and to
10
+ * permit persons to whom the Software is furnished to do so, subject to
11
+ * the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be
14
+ * included in all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ */
24
+
25
+import upm_vcap.VCAP;
26
+
27
+public class VCAP_Example 
28
+{
29
+    private static String defaultDev = "/dev/video0";
30
+
31
+    public static void main(String[] args) throws InterruptedException
32
+    {
33
+// ! [Interesting]
34
+        if (args.length > 0)
35
+            defaultDev = args[0];
36
+
37
+        System.out.println("Using device " + defaultDev);
38
+        System.out.println("Initializing...");
39
+
40
+        // Instantiate an VCAP instance, using the specified video device
41
+        VCAP sensor = new VCAP(defaultDev);
42
+        
43
+        // enable some debug/verbose output
44
+        sensor.setDebug(true);
45
+
46
+        // This is just a hint.  The kernel can change this to a lower
47
+        // resolution that the hardware supports.  Use getWidth() and
48
+        // getHeight() methods to see what the kernel actually chose if you
49
+        // care.
50
+        sensor.setResolution(1920, 1080);
51
+
52
+        // capture an image
53
+        sensor.captureImage();
54
+        
55
+        // convert and save it as a jpeg
56
+        sensor.saveImage("video-img1.jpg");
57
+
58
+// ! [Interesting]
59
+    }
60
+}

+ 69
- 0
examples/javascript/vcap.js View File

@@ -0,0 +1,69 @@
1
+/*jslint node:true, vars:true, bitwise:true, unparam:true */
2
+/*jshint unused:true */
3
+
4
+/*
5
+ * Author: Jon Trulson <jtrulson@ics.com>
6
+ * Copyright (c) 2016 Intel Corporation.
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining
9
+ * a copy of this software and associated documentation files (the
10
+ * "Software"), to deal in the Software without restriction, including
11
+ * without limitation the rights to use, copy, modify, merge, publish,
12
+ * distribute, sublicense, and/or sell copies of the Software, and to
13
+ * permit persons to whom the Software is furnished to do so, subject to
14
+ * the following conditions:
15
+ *
16
+ * The above copyright notice and this permission notice shall be
17
+ * included in all copies or substantial portions of the Software.
18
+ *
19
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ */
27
+
28
+
29
+var sensorObj = require('jsupm_vcap');
30
+
31
+
32
+/************** Main code **************/
33
+
34
+var defaultDev = "/dev/video0";
35
+
36
+// if an argument was specified, use it as the device instead
37
+if (process.argv.length > 2)
38
+{
39
+    defaultDev = process.argv[2];
40
+}
41
+
42
+console.log("Using device " + defaultDev);
43
+console.log("Initializing...");
44
+
45
+// Instantiate an VCAP instance, using the specified video device
46
+var sensor = new sensorObj.VCAP(defaultDev);
47
+
48
+// enable some debug/verbose output
49
+sensor.setDebug(true);
50
+
51
+// This is just a hint.  The kernel can change this to a lower
52
+// resolution that the hardware supports.  Use getWidth() and
53
+// getHeight() methods to see what the kernel actually chose if you
54
+// care.
55
+sensor.setResolution(1920, 1080);
56
+
57
+// capture an image
58
+sensor.captureImage();
59
+
60
+// convert and save it as a jpeg
61
+sensor.saveImage("video-img1.jpg");
62
+
63
+// make sure we clean up
64
+sensor = null;
65
+sensorObj.cleanUp();
66
+sensorObj = null;
67
+console.log("Exiting...");
68
+process.exit(0);
69
+

+ 67
- 0
examples/python/vcap.py View File

@@ -0,0 +1,67 @@
1
+#!/usr/bin/python
2
+# Author: Jon Trulson <jtrulson@ics.com>
3
+# Copyright (c) 2016 Intel Corporation.
4
+#
5
+# Permission is hereby granted, free of charge, to any person obtaining
6
+# a copy of this software and associated documentation files (the
7
+# "Software"), to deal in the Software without restriction, including
8
+# without limitation the rights to use, copy, modify, merge, publish,
9
+# distribute, sublicense, and/or sell copies of the Software, and to
10
+# permit persons to whom the Software is furnished to do so, subject to
11
+# the following conditions:
12
+#
13
+# The above copyright notice and this permission notice shall be
14
+# included in all copies or substantial portions of the Software.
15
+#
16
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+import time, sys, signal, atexit
25
+import pyupm_vcap as sensorObj
26
+
27
+## Exit handlers ##
28
+# This function stops python from printing a stacktrace when you hit control-C
29
+def SIGINTHandler(signum, frame):
30
+	raise SystemExit
31
+
32
+# This function lets you run code on exit
33
+def exitHandler():
34
+	print "Exiting..."
35
+	sys.exit(0)
36
+
37
+# Register exit handlers
38
+atexit.register(exitHandler)
39
+signal.signal(signal.SIGINT, SIGINTHandler)
40
+
41
+defaultDev = "/dev/video0"
42
+
43
+# if an argument was specified, use it as the device instead
44
+if (len(sys.argv) > 1):
45
+        defaultDev = sys.argv[1]
46
+
47
+print "Using device", defaultDev
48
+print "Initializing..."
49
+
50
+# Instantiate an VCAP instance, using the specified video device
51
+sensor = sensorObj.VCAP(defaultDev)
52
+
53
+# enable some debug/verbose output
54
+sensor.setDebug(True);
55
+
56
+# This is just a hint.  The kernel can change this to a lower
57
+# resolution that the hardware supports.  Use getWidth() and
58
+# getHeight() methods to see what the kernel actually chose if you
59
+# care.
60
+sensor.setResolution(1920, 1080);
61
+
62
+# capture an image
63
+sensor.captureImage();
64
+
65
+# convert and save it as a jpeg
66
+sensor.saveImage("video-img1.jpg");
67
+

+ 6
- 0
src/upm.h View File

@@ -221,6 +221,12 @@
221 221
  * @ingroup bycat
222 222
  */
223 223
 
224
+/**
225
+ * @brief Provide video or video camera access
226
+ * @defgroup video Video
227
+ * @ingroup bycat
228
+ */
229
+
224 230
 /**
225 231
  * @brief Provide WiFi, Bluetooth, RF communication
226 232
  * @defgroup wifi Wireless Communication

+ 18
- 0
src/vcap/CMakeLists.txt View File

@@ -0,0 +1,18 @@
1
+set (libname "vcap")
2
+set (libdescription "upm Video Frame Capture and image save utility")
3
+set (module_src ${libname}.cxx)
4
+set (module_h ${libname}.hpp)
5
+set (reqlibname "jpeg")
6
+upm_module_init()
7
+target_link_libraries(${libname} jpeg)
8
+if (BUILDSWIG)
9
+  if (BUILDSWIGNODE)
10
+    swig_link_libraries (jsupm_${libname} jpeg ${MRAA_LIBRARIES} ${NODE_LIBRARIES})
11
+  endif()
12
+  if (BUILDSWIGPYTHON)
13
+    swig_link_libraries (pyupm_${libname} jpeg ${PYTHON_LIBRARIES} ${MRAA_LIBRARIES})
14
+  endif()
15
+  if (BUILDSWIGJAVA)
16
+    swig_link_libraries (javaupm_${libname} jpeg ${MRAAJAVA_LDFLAGS} ${JAVA_LDFLAGS})
17
+  endif()
18
+endif()

+ 19
- 0
src/vcap/javaupm_vcap.i View File

@@ -0,0 +1,19 @@
1
+%module javaupm_vcap
2
+%include "../upm.i"
3
+%include "std_string.i"
4
+
5
+%include "vcap.hpp"
6
+%{
7
+    #include "vcap.hpp"
8
+%}
9
+
10
+%pragma(java) jniclasscode=%{
11
+    static {
12
+        try {
13
+            System.loadLibrary("javaupm_vcap");
14
+        } catch (UnsatisfiedLinkError e) {
15
+            System.err.println("Native code library failed to load. \n" + e);
16
+            System.exit(1);
17
+        }
18
+    }
19
+%}

+ 10
- 0
src/vcap/jsupm_vcap.i View File

@@ -0,0 +1,10 @@
1
+%module jsupm_vcap
2
+%include "../upm.i"
3
+%include "std_string.i"
4
+
5
+%include "vcap.hpp"
6
+%{
7
+    #include "vcap.hpp"
8
+%}
9
+
10
+

+ 14
- 0
src/vcap/pyupm_vcap.i View File

@@ -0,0 +1,14 @@
1
+// Include doxygen-generated documentation
2
+%include "pyupm_doxy2swig.i"
3
+%module pyupm_vcap
4
+%include "../upm.i"
5
+%include "std_string.i"
6
+
7
+%feature("autodoc", "3");
8
+
9
+%include "vcap.hpp"
10
+%{
11
+    #include "vcap.hpp"
12
+%}
13
+
14
+

+ 524
- 0
src/vcap/vcap.cxx View File

@@ -0,0 +1,524 @@
1
+/*
2
+ * Author: Jon Trulson <jtrulson@ics.com>
3
+ * Copyright (c) 2016 Intel Corporation.
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining
6
+ * a copy of this software and associated documentation files (the
7
+ * "Software"), to deal in the Software without restriction, including
8
+ * without limitation the rights to use, copy, modify, merge, publish,
9
+ * distribute, sublicense, and/or sell copies of the Software, and to
10
+ * permit persons to whom the Software is furnished to do so, subject to
11
+ * the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be
14
+ * included in all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ */
24
+
25
+#include <iostream>
26
+#include <stdexcept>
27
+#include <unistd.h>
28
+#include <stdio.h>
29
+#include <math.h>
30
+#include <string.h>
31
+
32
+#include "vcap.hpp"
33
+
34
+using namespace upm;
35
+using namespace std;
36
+
37
+#define CLAMP(_val, _min, _max) \
38
+  (((_val) < (_min)) ? (_min) : (((_val) > (_max)) ? (_max) : (_val)))
39
+
40
+VCAP::VCAP(string videoDev) :
41
+  m_buffer(0), m_fd(-1)
42
+{
43
+  memset(&m_caps, 0, sizeof(struct v4l2_capability));
44
+  memset(&m_format, 0, sizeof(struct v4l2_format));
45
+  
46
+  m_debugging = false;
47
+  m_bufferLen = 0;
48
+  m_videoDevice = videoDev;
49
+  setJPGQuality(VCAP_DEFAULT_JPEG_QUALITY);
50
+
51
+  // try to open the video device, and set a default format.
52
+  if (!initVideoDevice())
53
+    throw std::runtime_error(std::string(__FUNCTION__) +
54
+                             ": initVideoDevice() failed");
55
+
56
+  m_height = 0;
57
+  m_width = 0;
58
+  m_imageCaptured = false;
59
+}
60
+
61
+VCAP::~VCAP()
62
+{
63
+  releaseBuffer();
64
+
65
+  if (m_fd >= 0)
66
+    close(m_fd);
67
+
68
+  m_fd = -1;
69
+}
70
+
71
+bool VCAP::initVideoDevice()
72
+{
73
+  if (m_videoDevice.empty())
74
+    return false;
75
+
76
+  if ((m_fd = open(m_videoDevice.c_str(), O_RDWR)) < 0)
77
+    {
78
+      cerr << __FUNCTION__ << ": open failed: " << strerror(errno) << endl;
79
+      return false;
80
+    }
81
+  
82
+  if (!checkCapabilities())
83
+    {
84
+      close(m_fd);
85
+      m_fd = -1;
86
+      return false;
87
+    }
88
+
89
+  return true;
90
+}
91
+
92
+// This seems... odd, but appears to be necessary.
93
+// Ignore error and retry if the ioctl fails due to EINTR
94
+int VCAP::xioctl(int fd, int request, void* argp)
95
+{
96
+  int r;
97
+  
98
+  do {
99
+    r = ioctl(fd, request, argp);
100
+  }
101
+  while (r == -1 && errno == EINTR);
102
+  
103
+  return r;
104
+}
105
+
106
+bool VCAP::checkCapabilities()
107
+{
108
+  if (xioctl(m_fd, VIDIOC_QUERYCAP, &m_caps) < 0)
109
+    {
110
+      cerr << __FUNCTION__ << ": ioctl(VIDIOC_QUERYCAP) failed: "
111
+           << strerror(errno) << endl;
112
+      return false;
113
+    }
114
+  
115
+  if (m_debugging)
116
+    {
117
+      cerr << "Driver: " << m_caps.driver << endl;
118
+      cerr << "Device: "  << m_caps.card << endl;
119
+      cerr << "Caps  : 0x" << std::hex << m_caps.capabilities << std::dec
120
+           << endl;
121
+    }
122
+
123
+  // see if capturing is supported
124
+  if (!(m_caps.capabilities & V4L2_CAP_VIDEO_CAPTURE))
125
+    {
126
+      cerr << __FUNCTION__ << ": Device does not support video capture"
127
+           << endl;
128
+      return false;
129
+    }
130
+
131
+  if (!(m_caps.capabilities & V4L2_CAP_STREAMING))
132
+    {
133
+      cerr << __FUNCTION__ << ": Device does not support streaming I/O"
134
+           << endl;
135
+      return false;
136
+    }
137
+
138
+  return true;
139
+}
140
+
141
+bool VCAP::setResolution(int width, int height)
142
+{
143
+  // in case we already created one
144
+  releaseBuffer();
145
+
146
+  m_width = width;
147
+  m_height = height;
148
+
149
+  m_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
150
+  // initialize with the current format
151
+  if (xioctl(m_fd, VIDIOC_G_FMT, &m_format) < 0)
152
+    {
153
+      cerr << __FUNCTION__ << ": ioctl(VIDIOC_G_FMT) failed: "
154
+           << strerror(errno) << endl;
155
+      return false;
156
+    }
157
+
158
+  // make our changes...
159
+  m_format.fmt.pix.width = m_width;
160
+  m_format.fmt.pix.height = m_height;
161
+  m_format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
162
+  m_format.fmt.pix.field = V4L2_FIELD_ANY;
163
+        
164
+  if (xioctl(m_fd, VIDIOC_S_FMT, &m_format) < 0)
165
+    {
166
+      cerr << __FUNCTION__ << ": ioctl(VIDIOC_S_FMT) failed: "
167
+           << strerror(errno) << endl;
168
+
169
+      // If it's just busy, then this still might work, so don't fail here
170
+      if (errno != EBUSY)
171
+        return false;
172
+    }
173
+
174
+  // Now retrieve the driver's selected format and check it -
175
+  // specifically, the width and height might change, causing
176
+  // coredumps if we don't adjust them accordingly.
177
+
178
+  if (xioctl(m_fd, VIDIOC_G_FMT, &m_format) < 0)
179
+    {
180
+      cerr << __FUNCTION__ << ": ioctl(VIDIOC_G_FMT) failed: "
181
+           << strerror(errno) << endl;
182
+      return false;
183
+    }
184
+
185
+  // G_FMT will have adjusted these if neccessary, so verify
186
+  if (m_format.fmt.pix.width != m_width)
187
+    {
188
+      if (m_debugging)
189
+        cerr << __FUNCTION__ << ": Warning: Selected width "
190
+             << std::to_string(m_width)
191
+             << " adjusted by driver to "
192
+             << std::to_string(m_format.fmt.pix.width)
193
+             << endl;
194
+
195
+      m_width = m_format.fmt.pix.width;
196
+    }
197
+  
198
+  if (m_format.fmt.pix.height != m_height)
199
+    {
200
+      if (m_debugging)
201
+        cerr << __FUNCTION__ << ": Warning: Selected height "
202
+             << std::to_string(m_height)
203
+             << " adjusted by driver to "
204
+             << std::to_string(m_format.fmt.pix.height)
205
+             << endl;
206
+
207
+      m_height = m_format.fmt.pix.height;
208
+    }
209
+
210
+  // now alloc the buffers here
211
+  if (!allocBuffer())
212
+    return false;
213
+
214
+  return true;
215
+}
216
+ 
217
+bool VCAP::allocBuffer()
218
+{
219
+  struct v4l2_requestbuffers rb;
220
+  memset(&rb, 0, sizeof(rb));
221
+
222
+  // we just want one buffer, and we only support mmap().
223
+  rb.count = 1;
224
+  rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
225
+  rb.memory = V4L2_MEMORY_MMAP;
226
+ 
227
+  if (xioctl(m_fd, VIDIOC_REQBUFS, &rb) < 0)
228
+    {
229
+      if (errno == EINVAL)
230
+        {
231
+          cerr << __FUNCTION__ << ": Capture device does not support mmapped "
232
+               << "buffers"
233
+               << endl;
234
+        }
235
+      cerr << __FUNCTION__ << ": ioctl(VIDIOC_REQBUFS) failed: "
236
+           << strerror(errno) << endl;
237
+      
238
+      return false;
239
+    }
240
+ 
241
+  // get the buffer and mmap it
242
+  struct v4l2_buffer mbuf;
243
+  memset(&mbuf, 0, sizeof(mbuf));
244
+
245
+  mbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
246
+  mbuf.memory = V4L2_MEMORY_MMAP;
247
+  mbuf.index = 0;
248
+
249
+  if (xioctl(m_fd, VIDIOC_QUERYBUF, &mbuf) < 0)
250
+    {
251
+      cerr << __FUNCTION__ << ": ioctl(VIDIOC_QUERYBUF) failed: "
252
+           << strerror(errno) << endl;
253
+      return false;
254
+    }
255
+ 
256
+  // map it
257
+  m_buffer = (unsigned char *)mmap(NULL, mbuf.length, 
258
+                                   PROT_READ | PROT_WRITE, MAP_SHARED, 
259
+                                   m_fd, mbuf.m.offset);
260
+
261
+  if (m_buffer == MAP_FAILED)
262
+    {
263
+      cerr << __FUNCTION__ << ": mmap() failed: "
264
+           << strerror(errno) << endl;
265
+      return false;
266
+    }
267
+
268
+  // we'll need this when unmapping
269
+  m_bufferLen = mbuf.length;
270
+  
271
+  return true;
272
+}
273
+
274
+void VCAP::releaseBuffer()
275
+{
276
+  // first unmap any buffers
277
+  if (m_buffer)
278
+    munmap(m_buffer, m_bufferLen);
279
+
280
+  m_buffer = 0;
281
+  m_bufferLen = 0;
282
+
283
+  // then, tell the kernel driver to free any allocated buffer(s)...
284
+  struct v4l2_requestbuffers rb;
285
+  memset(&rb, 0, sizeof(rb));
286
+
287
+  rb.count = 0;
288
+  rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
289
+  rb.memory = V4L2_MEMORY_MMAP;
290
+ 
291
+  if (xioctl(m_fd, VIDIOC_REQBUFS, &rb) < 0)
292
+    {
293
+      cerr << __FUNCTION__ << ": ioctl(VIDIOC_REQBUFS) failed while freeing: "
294
+           << strerror(errno) << endl;
295
+    }
296
+
297
+  // reset captured flag
298
+  m_imageCaptured = false;
299
+}
300
+
301
+
302
+bool VCAP::YUYV2JPEG(FILE *file)
303
+{
304
+  struct jpeg_compress_struct jpgInfo;
305
+  struct jpeg_error_mgr jerr;
306
+  JSAMPROW row_pointer[1];
307
+  unsigned char *row_buffer = NULL;
308
+  unsigned char *yuyv = NULL;
309
+  int z;
310
+
311
+  row_buffer = (unsigned char *)calloc(m_width * 3, 1);
312
+  if (!row_buffer)
313
+    {
314
+      cerr << __FUNCTION__ << ": allocation of line buffer failed."
315
+           << endl;
316
+      return false;
317
+    }
318
+
319
+  yuyv = m_buffer;
320
+
321
+  jpgInfo.err = jpeg_std_error(&jerr);
322
+  jpeg_create_compress(&jpgInfo);
323
+  jpeg_stdio_dest(&jpgInfo, file);
324
+
325
+  jpgInfo.image_width = m_width;
326
+  jpgInfo.image_height = m_height;
327
+
328
+  // components R, G, B
329
+  jpgInfo.input_components = 3;
330
+  jpgInfo.in_color_space = JCS_RGB;
331
+
332
+  jpeg_set_defaults(&jpgInfo);
333
+  jpeg_set_quality(&jpgInfo, m_jpgQuality, TRUE);
334
+
335
+  jpeg_start_compress(&jpgInfo, TRUE);
336
+
337
+  z = 0;
338
+
339
+  while (jpgInfo.next_scanline < jpgInfo.image_height)
340
+    {
341
+      int x;
342
+      unsigned char *ptr = row_buffer;
343
+      
344
+      for (x = 0; x < m_width; x++)
345
+        {
346
+          int r, g, b;
347
+          int y, u, v;
348
+          
349
+          if (!z)
350
+            y = yuyv[0] << 8;
351
+          else
352
+            y = yuyv[2] << 8;
353
+          u = yuyv[1] - 128;
354
+          v = yuyv[3] - 128;
355
+          
356
+          r = (y + (359 * v)) >> 8;
357
+          g = (y - (88 * u) - (183 * v)) >> 8;
358
+          b = (y + (454 * u)) >> 8;
359
+          
360
+          *(ptr++) = CLAMP(r, 0, 255);
361
+          *(ptr++) = CLAMP(g, 0, 255);
362
+          *(ptr++) = CLAMP(b, 0, 255);
363
+          
364
+          if (z++)
365
+            {
366
+              z = 0;
367
+              yuyv += 4;
368
+            }
369
+        }
370
+      
371
+      row_pointer[0] = row_buffer;
372
+      jpeg_write_scanlines(&jpgInfo, row_pointer, 1);
373
+    }
374
+
375
+  jpeg_finish_compress(&jpgInfo);
376
+  jpeg_destroy_compress(&jpgInfo);
377
+
378
+  free(row_buffer);
379
+
380
+  return true;
381
+}
382
+
383
+bool VCAP::saveImage(string filename)
384
+{
385
+  // check m_buffer to make sure we have an actual buffer... If not,
386
+  // we throw here.
387
+  if (!m_buffer)
388
+    {
389
+      throw std::runtime_error(std::string(__FUNCTION__) +
390
+                               ": no buffer.  Call setResolution() first");
391
+    }
392
+
393
+  // if we haven't done at least one capture yet...
394
+  if (!m_imageCaptured)
395
+    {
396
+      throw std::runtime_error(std::string(__FUNCTION__) +
397
+                               ": No data, call captureImage() first");
398
+    }
399
+
400
+  FILE *file;
401
+  if ((file = fopen(filename.c_str(), "wb")) == NULL)
402
+    {
403
+      cerr << __FUNCTION__ << ": fopen() failed: "
404
+           << strerror(errno) << endl;
405
+      return false;
406
+    }
407
+  
408
+  YUYV2JPEG(file);
409
+  fclose(file);
410
+
411
+  if (m_debugging)
412
+    cerr << __FUNCTION__ << ": Saved image to " << filename << endl;
413
+
414
+  return true;
415
+}
416
+
417
+bool VCAP::captureImage()
418
+{
419
+  // first, make sure a resolution was specified.  If not, set the
420
+  // default
421
+  if (m_width == 0 || m_height == 0)
422
+    {
423
+      if (!setResolution(VCAP_DEFAULT_WIDTH, VCAP_DEFAULT_HEIGHT))
424
+        throw std::runtime_error(std::string(__FUNCTION__) +
425
+                                 ": setResolution() failed");
426
+    }
427
+
428
+  // we basically just call doCaptureImage() twice - once to grab and
429
+  // discard the first frame (which is usually a remnent of a previous
430
+  // capture), and another to grab the real frame we are interesed in.
431
+
432
+  if (!doCaptureImage())
433
+    {
434
+      cerr << __FUNCTION__ << ": capture of first frame failed"
435
+           << endl;
436
+    }
437
+
438
+  return doCaptureImage();
439
+}
440
+
441
+
442
+// the real workhorse
443
+bool VCAP::doCaptureImage()
444
+{
445
+  struct v4l2_buffer buf = {0};
446
+  buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
447
+  buf.memory = V4L2_MEMORY_MMAP;
448
+  buf.index = 0;
449
+
450
+  // queue our buffer
451
+  if (xioctl(m_fd, VIDIOC_QBUF, &buf) < 0)
452
+    {
453
+      cerr << __FUNCTION__ << ": ioctl(VIDIOC_QBUF) failed: "
454
+           << strerror(errno) << endl;
455
+
456
+      return false;
457
+    }
458
+  
459
+  // enable streaming
460
+  if (xioctl(m_fd, VIDIOC_STREAMON, &buf.type) < 0)
461
+    {
462
+      cerr << __FUNCTION__ << ": ioctl(VIDIOC_STREAMON) failed: "
463
+           << strerror(errno) << endl;
464
+
465
+      return false;
466
+    }
467
+
468
+  // use select to wait for a complete frame.
469
+  fd_set fds;
470
+
471
+  FD_ZERO(&fds);
472
+  FD_SET(m_fd, &fds);
473
+
474
+  struct timeval tv;
475
+  memset(&tv, 0, sizeof(tv));
476
+
477
+  // 5 seconds should be more than enough
478
+  tv.tv_sec = 5;
479
+
480
+  int rv;
481
+  if ((rv = select(m_fd + 1, &fds, NULL, NULL, &tv)) < 0)
482
+    {
483
+      cerr << __FUNCTION__ << ": select() failed: "
484
+           << strerror(errno) << endl;
485
+      return false;
486
+    }
487
+
488
+  if (!rv)
489
+    {
490
+      // timed out
491
+      cerr << __FUNCTION__ << ": select() timed out waiting for frame"
492
+           << endl;
493
+
494
+      return false;
495
+    }
496
+
497
+  // de-queue the buffer, we're now free to access it via the mmapped
498
+  // ptr (m_buffer)
499
+  if (xioctl(m_fd, VIDIOC_DQBUF, &buf) < 0)
500
+    {
501
+      cerr << __FUNCTION__ << ": ioctl(VIDIOC_DQBUF) failed: "
502
+           << strerror(errno) << endl;
503
+
504
+      return false;
505
+    }
506
+
507
+  // turn off streaming
508
+  if (xioctl(m_fd, VIDIOC_STREAMOFF, &buf.type) < 0)
509
+    {
510
+      cerr << __FUNCTION__ << ": ioctl(VIDIOC_STREAMOFF) failed: "
511
+           << strerror(errno) << endl;
512
+
513
+      return false;
514
+    }
515
+    
516
+  m_imageCaptured = true;
517
+
518
+  return true;
519
+}
520
+
521
+ void VCAP::setJPGQuality(unsigned int qual)
522
+ {
523
+   m_jpgQuality = CLAMP(qual, 0, 100);
524
+ }

+ 214
- 0
src/vcap/vcap.hpp View File

@@ -0,0 +1,214 @@
1
+/*
2
+ * Author: Jon Trulson <jtrulson@ics.com>
3
+ * Copyright (c) 2016 Intel Corporation.
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining
6
+ * a copy of this software and associated documentation files (the
7
+ * "Software"), to deal in the Software without restriction, including
8
+ * without limitation the rights to use, copy, modify, merge, publish,
9
+ * distribute, sublicense, and/or sell copies of the Software, and to
10
+ * permit persons to whom the Software is furnished to do so, subject to
11
+ * the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be
14
+ * included in all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ */
24
+#pragma once
25
+
26
+#include <string>
27
+#include <iostream>
28
+
29
+#include <errno.h>
30
+#include <fcntl.h>
31
+#include <sys/ioctl.h>
32
+#include <sys/mman.h>
33
+#include <jpeglib.h>
34
+#include <linux/videodev2.h>
35
+
36
+#define VCAP_DEFAULT_VIDEODEV "/dev/video0"
37
+#define VCAP_DEFAULT_OUTPUTFILE "vcap.jpg"
38
+#define VCAP_DEFAULT_WIDTH 640
39
+#define VCAP_DEFAULT_HEIGHT 480
40
+#define VCAP_DEFAULT_JPEG_QUALITY 99
41
+
42
+namespace upm {
43
+    /**
44
+     * @brief Take a snapshot from a video camera and save as a JPEG
45
+     * @defgroup vcap libupm-vcap
46
+     * @ingroup video
47
+     */
48
+
49
+    /**
50
+     * @library vcap
51
+     * @sensor vcap
52
+     * @comname Video Capture
53
+     * @type video
54
+     *
55
+     * @brief API for the Video Capture driver
56
+     *
57
+     * This UPM module captures a still frame from a Linux V4L device,
58
+     * such as a USB webcam, and and then allows you to save it as a
59
+     * JPEG image into a file.
60
+     *
61
+     * The camera and driver in use must support streaming, mmap-able
62
+     * buffers and must provide data in YUYV format.  This should
63
+     * encompass most video cameras out there.  It has been tested
64
+     * with a few off the shelf cameras without any problems.
65
+     *
66
+     * @snippet vcap.cxx Interesting
67
+     */
68
+
69
+  class VCAP {
70
+  public:
71
+
72
+    /**
73
+     * VCAP object constructor
74
+     *
75
+     * @param videoDev The path to the video device, default is /dev/video0.
76
+     */
77
+    VCAP(std::string videoDev=VCAP_DEFAULT_VIDEODEV);
78
+
79
+    /**
80
+     * VCAP object destructor
81
+     */
82
+    ~VCAP();
83
+
84
+    /**
85
+     * Set the desired resolution of the output image.  Note, this is
86
+     * a hint to the underlying video driver.  The video driver is
87
+     * free to lower the specified resolution if the hardware cannot
88
+     * support it.  You can use getHeight() and getWidth() after
89
+     * calling this method to see what the video driver chose.
90
+     *
91
+     * @param width The desired width of the image.
92
+     * @param width The desired height of the image.
93
+     * @return true if the operation succeeded, false otherwise.
94
+     */
95
+    bool setResolution(int width, int height);
96
+
97
+    /**
98
+     * Capture an image from the camera.
99
+     *
100
+     * @return true if the operation succeeded, false otherwise.
101
+     */
102
+    bool captureImage();
103
+
104
+    /**
105
+     * Save the captured image (created with captureImage()) to a file
106
+     * in JPEG format.  The file will be overwritten if it already
107
+     * exists.
108
+     *
109
+     * @param filename The name of the file in which to store the image.
110
+     * @return true if the operation succeeded, false otherwise.
111
+     */
112
+    bool saveImage(std::string filename=VCAP_DEFAULT_OUTPUTFILE);
113
+
114
+    /**
115
+     * Return the current width of the image.  You can use this method
116
+     * to determine if the video driver downgraded it after a call to
117
+     * setResolution().
118
+     *
119
+     * @return true Current width of capture.
120
+     */
121
+    int getWidth() const
122
+    {
123
+      return m_width;
124
+    };
125
+
126
+    /**
127
+     * Return the current height of the image.  You can use this method
128
+     * to determine if the video driver downgraded it after a call to
129
+     * setResolution().
130
+     *
131
+     * @return true Current height of capture.
132
+     */
133
+    int getHeight() const
134
+    {
135
+      return m_height;
136
+    };
137
+
138
+    /**
139
+     * Set the JPEG quality.
140
+     *
141
+     * @param quality A number between 0-100, with higher numbers
142
+     * meaning higher quality. Numbers less than 0 will be clamped to
143
+     * 0, numbers higher than 100 will be clamped to 100.
144
+     */
145
+    void setJPGQuality(unsigned int quality);
146
+
147
+    /**
148
+     * Get the current JPEG quality setting.
149
+     *
150
+     * @return the current JPEG quality setting.
151
+     */
152
+    int getJPGQuality() const
153
+    {
154
+      return m_jpgQuality;
155
+    };
156
+
157
+    /**
158
+     * Enable or disable debugging output.
159
+     *
160
+     * @param enable true to enable debugging, false otherwise
161
+     */
162
+    void setDebug(bool enable)
163
+    {
164
+      m_debugging = enable;
165
+    };
166
+    
167
+  protected:
168
+    // open the device and check that it meats minimum requirements
169
+    bool initVideoDevice();
170
+
171
+    // make sure device is streamable, supports mmap and capture
172
+    bool checkCapabilities();
173
+
174
+    // read the mmapped buffer in YUYV format and create a jpeg image
175
+    bool YUYV2JPEG(FILE *file);
176
+
177
+    // buffer management
178
+    bool allocBuffer();
179
+    void releaseBuffer();
180
+
181
+    // does the actual capture
182
+    bool doCaptureImage();
183
+    
184
+  private:
185
+    // internal ioctl
186
+    int xioctl(int fd, int request, void* argp);
187
+               
188
+    std::string m_videoDevice;
189
+    
190
+    // our file descriptor to the video device
191
+    int m_fd;
192
+
193
+    // v4l info
194
+    struct v4l2_capability m_caps;
195
+    struct v4l2_format m_format;
196
+
197
+    // our mmaped buffer
198
+    unsigned char *m_buffer;
199
+    size_t m_bufferLen;
200
+
201
+    // the resolution and quality
202
+    int m_width;
203
+    int m_height;
204
+    int m_jpgQuality;
205
+
206
+    // at least one image captured with current settings?
207
+    bool m_imageCaptured;
208
+
209
+    // are we debugging?
210
+    bool m_debugging;
211
+  };
212
+}
213
+
214
+