blob: 95729dafec2a17e972ed09d44a6661dab3373088 [file] [log] [blame]
Parker Schuh44f86922017-01-03 23:59:50 -08001#include "aos/common/time.h"
2
3#include "aos/common/logging/logging.h"
4#include "aos/vision/image/reader.h"
5
6#define CLEAR(x) memset(&(x), 0, sizeof(x))
7
8namespace camera {
9
10struct Reader::Buffer {
11 void *start;
12 size_t length; // for munmap
13};
14
15Reader::Reader(const std::string &dev_name, ProcessCb process,
16 CameraParams params)
17 : dev_name_(dev_name), process_(std::move(process)), params_(params) {
18 struct stat st;
19 if (stat(dev_name.c_str(), &st) == -1) {
20 PLOG(FATAL, "Cannot identify '%s'", dev_name.c_str());
21 }
22 if (!S_ISCHR(st.st_mode)) {
23 PLOG(FATAL, "%s is no device\n", dev_name.c_str());
24 }
25
26 fd_ = open(dev_name.c_str(), O_RDWR /* required */ | O_NONBLOCK, 0);
27 if (fd_ == -1) {
28 PLOG(FATAL, "Cannot open '%s'", dev_name.c_str());
29 }
30
31 Init();
32}
33
34void Reader::QueueBuffer(v4l2_buffer *buf) {
35 if (xioctl(fd_, VIDIOC_QBUF, buf) == -1) {
36 PLOG(WARNING,
37 "ioctl VIDIOC_QBUF(%d, %p)."
38 " losing buf #%" PRIu32 "\n",
39 fd_, &buf, buf->index);
40 } else {
41 // LOG(DEBUG, "put buf #%" PRIu32 " into driver's queue\n", buf->index);
42 ++queued_;
43 }
44}
45
46void Reader::HandleFrame() {
47 v4l2_buffer buf;
48 CLEAR(buf);
49 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
50 buf.memory = V4L2_MEMORY_MMAP;
51
52 if (xioctl(fd_, VIDIOC_DQBUF, &buf) == -1) {
53 if (errno != EAGAIN) {
54 PLOG(ERROR, "ioctl VIDIOC_DQBUF(%d, %p)", fd_, &buf);
55 }
56 return;
57 }
58 --queued_;
59
60 // Get a timestamp now as proxy for when the image was taken
61 // TODO(ben): the image should come with a timestamp, parker
62 // will know how to get it.
63 auto time = aos::monotonic_clock::now();
64
65 process_(aos::vision::DataRef(
66 reinterpret_cast<const char *>(buffers_[buf.index].start),
67 buf.bytesused),
68 time);
69 QueueBuffer(&buf);
70}
71
72void Reader::MMapBuffers() {
73 buffers_ = new Buffer[kNumBuffers];
74 v4l2_buffer buf;
75 for (unsigned int n = 0; n < kNumBuffers; ++n) {
76 memset(&buf, 0, sizeof(buf));
77 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
78 buf.memory = V4L2_MEMORY_MMAP;
79 buf.index = n;
80 if (xioctl(fd_, VIDIOC_QUERYBUF, &buf) == -1) {
81 PLOG(FATAL, "ioctl VIDIOC_QUERYBUF(%d, %p)", fd_, &buf);
82 }
83 buffers_[n].length = buf.length;
84 buffers_[n].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,
85 MAP_SHARED, fd_, buf.m.offset);
86 if (buffers_[n].start == MAP_FAILED) {
87 PLOG(FATAL,
88 "mmap(NULL, %zd, PROT_READ | PROT_WRITE, MAP_SHARED, %d, %jd)",
89 (size_t)buf.length, fd_, static_cast<intmax_t>(buf.m.offset));
90 }
91 }
92}
93
94void Reader::InitMMap() {
95 v4l2_requestbuffers req;
96 CLEAR(req);
97 req.count = kNumBuffers;
98 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
99 req.memory = V4L2_MEMORY_MMAP;
100 if (xioctl(fd_, VIDIOC_REQBUFS, &req) == -1) {
101 if (EINVAL == errno) {
102 LOG(FATAL, "%s does not support memory mapping\n", dev_name_.c_str());
103 } else {
104 PLOG(FATAL, "ioctl VIDIOC_REQBUFS(%d, %p)\n", fd_, &req);
105 }
106 }
107 queued_ = kNumBuffers;
108 if (req.count < kNumBuffers) {
109 LOG(FATAL, "Insufficient buffer memory on %s\n", dev_name_.c_str());
110 }
111}
112
113// Sets one of the camera's user-control values.
114// Prints the old and new values.
115// Just prints a message if the camera doesn't support this control or value.
116bool Reader::SetCameraControl(uint32_t id, const char *name, int value) {
117 struct v4l2_control getArg = {id, 0U};
118 int r = xioctl(fd_, VIDIOC_G_CTRL, &getArg);
119 if (r == 0) {
120 if (getArg.value == value) {
121 LOG(DEBUG, "Camera control %s was already %d\n", name, getArg.value);
122 return true;
123 }
124 } else if (errno == EINVAL) {
125 LOG(DEBUG, "Camera control %s is invalid\n", name);
126 errno = 0;
127 return false;
128 }
129
130 struct v4l2_control setArg = {id, value};
131 r = xioctl(fd_, VIDIOC_S_CTRL, &setArg);
132 if (r == 0) {
133 LOG(DEBUG, "Set camera control %s from %d to %d\n", name, getArg.value,
134 value);
135 return true;
136 }
137
138 LOG(DEBUG, "Couldn't set camera control %s to %d", name, value);
139 errno = 0;
140 return false;
141}
142
143void Reader::Init() {
144 v4l2_capability cap;
145 if (xioctl(fd_, VIDIOC_QUERYCAP, &cap) == -1) {
146 if (EINVAL == errno) {
147 LOG(FATAL, "%s is no V4L2 device\n", dev_name_.c_str());
148 } else {
149 PLOG(FATAL, "ioctl VIDIOC_QUERYCAP(%d, %p)", fd_, &cap);
150 }
151 }
152 if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
153 LOG(FATAL, "%s is no video capture device\n", dev_name_.c_str());
154 }
155 if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
156 LOG(FATAL, "%s does not support streaming i/o\n", dev_name_.c_str());
157 }
158
159 /* Select video input, video standard and tune here. */
160
161 v4l2_cropcap cropcap;
162 CLEAR(cropcap);
163 cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
164 if (xioctl(fd_, VIDIOC_CROPCAP, &cropcap) == 0) {
165 v4l2_crop crop;
166 crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
167 crop.c = cropcap.defrect; /* reset to default */
168
169 if (xioctl(fd_, VIDIOC_S_CROP, &crop) == -1) {
170 switch (errno) {
171 case EINVAL:
172 /* Cropping not supported. */
173 break;
174 default:
175 /* Errors ignored. */
176 PLOG(WARNING, "xioctl VIDIOC_S_CROP");
177 break;
178 }
179 }
180 } else {
181 PLOG(WARNING, "xioctl VIDIOC_CROPCAP");
182 }
183
184 v4l2_format fmt;
185 CLEAR(fmt);
186 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
187 fmt.fmt.pix.width = params_.width;
188 fmt.fmt.pix.height = params_.height;
189 fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
190 fmt.fmt.pix.field = V4L2_FIELD_ANY;
191 // fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
192 // fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
193 if (xioctl(fd_, VIDIOC_S_FMT, &fmt) == -1) {
194 LOG(FATAL, "ioctl VIDIC_S_FMT(%d, %p) failed with %d: %s\n", fd_, &fmt,
195 errno, strerror(errno));
196 }
197 /* Note VIDIOC_S_FMT may change width and height. */
198
199 /* Buggy driver paranoia. */
200 unsigned int min = fmt.fmt.pix.width * 2;
201 if (fmt.fmt.pix.bytesperline < min) fmt.fmt.pix.bytesperline = min;
202 min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
203 if (fmt.fmt.pix.sizeimage < min) fmt.fmt.pix.sizeimage = min;
204
205 if (!SetCameraControl(V4L2_CID_EXPOSURE_AUTO, "V4L2_CID_EXPOSURE_AUTO",
206 V4L2_EXPOSURE_MANUAL)) {
207 LOG(FATAL, "Failed to set exposure\n");
208 }
209
210 if (!SetCameraControl(V4L2_CID_EXPOSURE_ABSOLUTE,
211 "V4L2_CID_EXPOSURE_ABSOLUTE", params_.exposure)) {
212 LOG(FATAL, "Failed to set exposure\n");
213 }
214
215 if (!SetCameraControl(V4L2_CID_BRIGHTNESS, "V4L2_CID_BRIGHTNESS",
216 params_.brightness)) {
217 LOG(FATAL, "Failed to set up camera\n");
218 }
219
220 if (!SetCameraControl(V4L2_CID_GAIN, "V4L2_CID_GAIN", params_.gain)) {
221 LOG(FATAL, "Failed to set up camera\n");
222 }
223
224 // #if 0
225 // set framerate
226 struct v4l2_streamparm *setfps;
227 setfps = (struct v4l2_streamparm *)calloc(1, sizeof(struct v4l2_streamparm));
228 memset(setfps, 0, sizeof(struct v4l2_streamparm));
229 setfps->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
230 setfps->parm.capture.timeperframe.numerator = 1;
231 setfps->parm.capture.timeperframe.denominator = params_.fps;
232 if (xioctl(fd_, VIDIOC_S_PARM, setfps) == -1) {
233 PLOG(FATAL, "ioctl VIDIOC_S_PARM(%d, %p)\n", fd_, setfps);
234 }
235 LOG(INFO, "framerate ended up at %d/%d\n",
236 setfps->parm.capture.timeperframe.numerator,
237 setfps->parm.capture.timeperframe.denominator);
238 // #endif
239
240 InitMMap();
241 LOG(INFO, "Bat Vision Successfully Initialized.\n");
242}
243
244aos::vision::ImageFormat Reader::get_format() {
245 struct v4l2_format fmt;
246 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
247 if (xioctl(fd_, VIDIOC_G_FMT, &fmt) == -1) {
248 PLOG(FATAL, "ioctl VIDIC_G_FMT(%d, %p)\n", fd_, &fmt);
249 }
250
251 return aos::vision::ImageFormat{(int)fmt.fmt.pix.width,
252 (int)fmt.fmt.pix.height};
253}
254
255void Reader::Start() {
256 LOG(DEBUG, "queueing buffers for the first time\n");
257 v4l2_buffer buf;
258 for (unsigned int i = 0; i < kNumBuffers; ++i) {
259 CLEAR(buf);
260 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
261 buf.memory = V4L2_MEMORY_MMAP;
262 buf.index = i;
263 QueueBuffer(&buf);
264 }
265 LOG(DEBUG, "done with first queue\n");
266
267 v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
268 if (xioctl(fd_, VIDIOC_STREAMON, &type) == -1) {
269 PLOG(FATAL, "ioctl VIDIOC_STREAMON(%d, %p)\n", fd_, &type);
270 }
271}
272
273} // namespace camera