blob: 57766e2a3979fd60eb7b2ce1aaa76dd6d83be05f [file] [log] [blame]
#include "aos/vision/image/reader.h"
#include <errno.h>
#include <fcntl.h>
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include "aos/common/logging/logging.h"
#include "aos/common/time.h"
#define CLEAR(x) memset(&(x), 0, sizeof(x))
namespace camera {
struct Reader::Buffer {
void *start;
size_t length; // for munmap
};
aos::vision::CameraParams MakeCameraParams(int32_t width, int32_t height,
int32_t exposure, int32_t brightness,
int32_t gain, int32_t fps) {
aos::vision::CameraParams cam;
cam.set_width(width);
cam.set_height(height);
cam.set_exposure(exposure);
cam.set_brightness(brightness);
cam.set_gain(gain);
cam.set_fps(fps);
return cam;
}
Reader::Reader(const std::string &dev_name, ProcessCb process,
aos::vision::CameraParams params)
: dev_name_(dev_name), process_(std::move(process)), params_(params) {
struct stat st;
if (stat(dev_name.c_str(), &st) == -1) {
PLOG(FATAL, "Cannot identify '%s'", dev_name.c_str());
}
if (!S_ISCHR(st.st_mode)) {
PLOG(FATAL, "%s is no device\n", dev_name.c_str());
}
fd_ = open(dev_name.c_str(), O_RDWR /* required */ | O_NONBLOCK, 0);
if (fd_ == -1) {
PLOG(FATAL, "Cannot open '%s'", dev_name.c_str());
}
Init();
InitMMap();
LOG(INFO, "Bat Vision Successfully Initialized.\n");
}
void Reader::QueueBuffer(v4l2_buffer *buf) {
if (xioctl(fd_, VIDIOC_QBUF, buf) == -1) {
PLOG(WARNING,
"ioctl VIDIOC_QBUF(%d, %p)."
" losing buf #%" PRIu32 "\n",
fd_, &buf, buf->index);
} else {
// LOG(DEBUG, "put buf #%" PRIu32 " into driver's queue\n", buf->index);
++queued_;
}
}
void Reader::HandleFrame() {
v4l2_buffer buf;
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (xioctl(fd_, VIDIOC_DQBUF, &buf) == -1) {
if (errno != EAGAIN) {
PLOG(ERROR, "ioctl VIDIOC_DQBUF(%d, %p)", fd_, &buf);
}
return;
}
--queued_;
if (tick_id_ % 10 == 0) {
if (!SetCameraControl(V4L2_CID_EXPOSURE_AUTO, "V4L2_CID_EXPOSURE_AUTO",
V4L2_EXPOSURE_MANUAL)) {
LOG(FATAL, "Failed to set exposure\n");
}
if (!SetCameraControl(V4L2_CID_EXPOSURE_ABSOLUTE,
"V4L2_CID_EXPOSURE_ABSOLUTE", params_.exposure())) {
LOG(FATAL, "Failed to set exposure\n");
}
}
++tick_id_;
// Get a timestamp now as proxy for when the image was taken
// TODO(ben): the image should come with a timestamp, parker
// will know how to get it.
auto time = aos::monotonic_clock::now();
process_(aos::vision::DataRef(
reinterpret_cast<const char *>(buffers_[buf.index].start),
buf.bytesused),
time);
QueueBuffer(&buf);
}
void Reader::MMapBuffers() {
buffers_ = new Buffer[kNumBuffers];
v4l2_buffer buf;
for (unsigned int n = 0; n < kNumBuffers; ++n) {
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n;
if (xioctl(fd_, VIDIOC_QUERYBUF, &buf) == -1) {
PLOG(FATAL, "ioctl VIDIOC_QUERYBUF(%d, %p)", fd_, &buf);
}
buffers_[n].length = buf.length;
buffers_[n].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,
MAP_SHARED, fd_, buf.m.offset);
if (buffers_[n].start == MAP_FAILED) {
PLOG(FATAL,
"mmap(NULL, %zd, PROT_READ | PROT_WRITE, MAP_SHARED, %d, %jd)",
(size_t)buf.length, fd_, static_cast<intmax_t>(buf.m.offset));
}
}
}
void Reader::InitMMap() {
v4l2_requestbuffers req;
CLEAR(req);
req.count = kNumBuffers;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (xioctl(fd_, VIDIOC_REQBUFS, &req) == -1) {
if (EINVAL == errno) {
LOG(FATAL, "%s does not support memory mapping\n", dev_name_.c_str());
} else {
PLOG(FATAL, "ioctl VIDIOC_REQBUFS(%d, %p)\n", fd_, &req);
}
}
queued_ = kNumBuffers;
if (req.count != kNumBuffers) {
LOG(FATAL, "Insufficient buffer memory on %s\n", dev_name_.c_str());
}
}
// Sets one of the camera's user-control values.
// Prints the old and new values.
// Just prints a message if the camera doesn't support this control or value.
bool Reader::SetCameraControl(uint32_t id, const char *name, int value) {
struct v4l2_control getArg = {id, 0U};
int r = xioctl(fd_, VIDIOC_G_CTRL, &getArg);
if (r == 0) {
if (getArg.value == value) {
LOG(DEBUG, "Camera control %s was already %d\n", name, getArg.value);
return true;
}
} else if (errno == EINVAL) {
LOG(DEBUG, "Camera control %s is invalid\n", name);
errno = 0;
return false;
}
struct v4l2_control setArg = {id, value};
r = xioctl(fd_, VIDIOC_S_CTRL, &setArg);
if (r == 0) {
LOG(DEBUG, "Set camera control %s from %d to %d\n", name, getArg.value,
value);
return true;
}
LOG(DEBUG, "Couldn't set camera control %s to %d", name, value);
errno = 0;
return false;
}
void Reader::Init() {
v4l2_capability cap;
if (xioctl(fd_, VIDIOC_QUERYCAP, &cap) == -1) {
if (EINVAL == errno) {
LOG(FATAL, "%s is no V4L2 device\n", dev_name_.c_str());
} else {
PLOG(FATAL, "ioctl VIDIOC_QUERYCAP(%d, %p)", fd_, &cap);
}
}
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
LOG(FATAL, "%s is no video capture device\n", dev_name_.c_str());
}
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
LOG(FATAL, "%s does not support streaming i/o\n", dev_name_.c_str());
}
/* Select video input, video standard and tune here. */
v4l2_cropcap cropcap;
CLEAR(cropcap);
cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd_, VIDIOC_CROPCAP, &cropcap) == 0) {
v4l2_crop crop;
crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
crop.c = cropcap.defrect; /* reset to default */
if (xioctl(fd_, VIDIOC_S_CROP, &crop) == -1) {
switch (errno) {
case EINVAL:
/* Cropping not supported. */
break;
default:
/* Errors ignored. */
PLOG(WARNING, "xioctl VIDIOC_S_CROP");
break;
}
}
} else {
PLOG(WARNING, "xioctl VIDIOC_CROPCAP");
}
v4l2_format fmt;
CLEAR(fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = params_.width();
fmt.fmt.pix.height = params_.height();
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
// fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
// fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
if (xioctl(fd_, VIDIOC_S_FMT, &fmt) == -1) {
LOG(FATAL, "ioctl VIDIC_S_FMT(%d, %p) failed with %d: %s\n", fd_, &fmt,
errno, strerror(errno));
}
/* Note VIDIOC_S_FMT may change width and height. */
/* Buggy driver paranoia. */
unsigned int min = fmt.fmt.pix.width * 2;
if (fmt.fmt.pix.bytesperline < min) fmt.fmt.pix.bytesperline = min;
min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
if (fmt.fmt.pix.sizeimage < min) fmt.fmt.pix.sizeimage = min;
if (!SetCameraControl(V4L2_CID_EXPOSURE_AUTO, "V4L2_CID_EXPOSURE_AUTO",
V4L2_EXPOSURE_MANUAL)) {
LOG(FATAL, "Failed to set exposure\n");
}
if (!SetCameraControl(V4L2_CID_EXPOSURE_ABSOLUTE,
"V4L2_CID_EXPOSURE_ABSOLUTE", params_.exposure())) {
LOG(FATAL, "Failed to set exposure\n");
}
if (!SetCameraControl(V4L2_CID_BRIGHTNESS, "V4L2_CID_BRIGHTNESS",
params_.brightness())) {
LOG(FATAL, "Failed to set up camera\n");
}
if (!SetCameraControl(V4L2_CID_GAIN, "V4L2_CID_GAIN", params_.gain())) {
LOG(FATAL, "Failed to set up camera\n");
}
// set framerate
struct v4l2_streamparm *setfps;
setfps = (struct v4l2_streamparm *)calloc(1, sizeof(struct v4l2_streamparm));
memset(setfps, 0, sizeof(struct v4l2_streamparm));
setfps->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
setfps->parm.capture.timeperframe.numerator = 1;
setfps->parm.capture.timeperframe.denominator = params_.fps();
if (xioctl(fd_, VIDIOC_S_PARM, setfps) == -1) {
PLOG(FATAL, "ioctl VIDIOC_S_PARM(%d, %p)\n", fd_, setfps);
}
LOG(INFO, "framerate ended up at %d/%d\n",
setfps->parm.capture.timeperframe.numerator,
setfps->parm.capture.timeperframe.denominator);
}
aos::vision::ImageFormat Reader::get_format() {
struct v4l2_format fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd_, VIDIOC_G_FMT, &fmt) == -1) {
PLOG(FATAL, "ioctl VIDIC_G_FMT(%d, %p)\n", fd_, &fmt);
}
return aos::vision::ImageFormat{(int)fmt.fmt.pix.width,
(int)fmt.fmt.pix.height};
}
void Reader::Start() {
LOG(DEBUG, "queueing buffers for the first time\n");
v4l2_buffer buf;
for (unsigned int i = 0; i < kNumBuffers; ++i) {
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
QueueBuffer(&buf);
}
LOG(DEBUG, "done with first queue\n");
v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd_, VIDIOC_STREAMON, &type) == -1) {
PLOG(FATAL, "ioctl VIDIOC_STREAMON(%d, %p)\n", fd_, &type);
}
}
} // namespace camera