网上v4l2介绍的文章文多,api的使用我就不再说了,只在这里贴出我的C++封装类。
源码直达:https://gitee.com/noevilme/libwebcam
webcam_v4l2.h
/** * Copyright(C)2020 NoevilMe. All rights reserved. * File : webcam_v4l2.h * Author : NoevilMe <surpass168@live.com> * Date : 2020-05-21 23:00:54 * Last Modified Date: 2020-07-25 21:56:28 * Last Modified By : NoevilMe <surpass168@live.com> */ #ifndef __WEBCAM_V4L2_H_ #define __WEBCAM_V4L2_H_ #include "log.h" #include <functional> #include <map> #include <memory> #include <string> #include <linux/videodev2.h> namespace noevil { namespace webcam { enum class WebcamFormat { kFmtNone, kFmtMJPG, // Motion-JPEG kFmtYUYV // YUYV422 }; struct V4l2BufUnit { int index = 0; uint32_t length = 0; uint32_t offset = 0; void *start = nullptr; uint32_t bytes = 0; }; struct V4l2BufStat { // for preparation int count = 0; // mapped count uint32_t type = 0; // enum v4l2_buf_type struct V4l2BufUnit *buffer = nullptr; // tmp struct v4l2_buffer buf; }; class V4l2BufStatDeleter { public: void operator()(V4l2BufStat *stat); }; struct V4l2Ctrl { struct v4l2_queryctrl queryctrl; struct v4l2_control control; }; class WebcamV4l2 { public: WebcamV4l2(); WebcamV4l2(const char *name); WebcamV4l2(int id); ~WebcamV4l2(); bool IsOpen(); // get error message if any interface returns false std::string GetError() const; bool Open(bool force = false); bool Open(const char *name); bool Close(); // settings bool Init(); bool SetPixFormat(WebcamFormat fmt, uint32_t width, uint32_t height); bool SetFps(uint8_t fps); // sync mode bool Start(); bool Stop(); // block, select and grab // @timeout milliseconds bool Grab(std::string &out, uint32_t timeout = 100); // nullptr to discard bool Grab(std::string *out, uint32_t timeout = 100); // non-block, work with eventloop bool Retrieve(std::string &img); // nullptr to discard bool Retrieve(std::string *img); // with work callback void SetFrameCallback(const std::function<void(const char *const, uint32_t)> &cb) { frame_cb_ = cb; } // block bool Grab(uint32_t timeout = 100); // non-block bool Retrieve(bool discard = false); // query util bool GetControl(); bool SetExposure(); int fd() const { return cam_fd_; } private: bool IsV4l2VideoDevice(); bool QueryCapability(); bool SetInput(const char *name = nullptr); bool SetMMap(); bool FreeMMap(); bool StreamOn(); bool StreamOff(); std::string EnumerateMenu(uint32_t id, int32_t index_min, int32_t index_max); static std::string FormatErrno(); static std::string PixFormatName(uint32_t format); // TODO: bool SetExtControl(); bool ShowCtrlMenu(struct v4l2_queryctrl *queryctrl); bool ShowCtrlInt(struct v4l2_queryctrl *queryctrl); bool ShowControl(struct v4l2_queryctrl *queryctrl); void Release(); bool GrabFrame(std::string &img, uint32_t timeout = 100); private: bool working_; int cam_fd_; uint32_t capabilities_; uint32_t format_; std::string error_; std::string dev_name_; std::shared_ptr<spdlog::logger> logger_; std::map<decltype(V4l2Ctrl::queryctrl.id), V4l2Ctrl> ctrl_; std::unique_ptr<V4l2BufStat, V4l2BufStatDeleter> buf_stat_; std::function<void(const char *const, uint32_t)> frame_cb_; }; } // namespace webcam } // namespace noevil #endif /* __WEBCAM_V4L2_H_ */
webcam_v4l2.cxx
/** * Copyright(C)2020 NoevilMe. All rights reserved. * File : webcam_v4l2.cxx * Author : NoevilMe <surpass168@live.com> * Date : 2020-05-21 23:02:05 * Last Modified Date: 2020-07-26 09:33:07 * Last Modified By : NoevilMe <surpass168@live.com> */ #include "webcam_v4l2.h" #include "spdlog/fmt/bundled/core.h" #include "spdlog/fmt/bundled/format.h" #include "string_util.hpp" #include <iostream> #include <stdexcept> #include <utility> #include <vector> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/select.h> // The SCALE macro converts a value (sv) from one range (sf -> sr) #define SCALE(df, dr, sf, sr, sv) (((sv - sf) * (dr - df) / (sr - sf)) + df) namespace noevil { namespace webcam { static constexpr auto QBUF_SIZE = 5; static constexpr auto VIDEO_DEV_PREFIX = "/dev/video"; static constexpr auto LOGGER_NAME = "webcam-v4l2"; void V4l2BufStatDeleter::operator()(V4l2BufStat *stat) { if (!stat->buffer) return; for (int i = 0; i < stat->count; ++i) { munmap(stat->buffer[i].start, stat->buffer[i].length); } delete[] stat->buffer; stat->buffer = nullptr; } WebcamV4l2::WebcamV4l2() : working_(false), cam_fd_(-1), capabilities_(0), format_(0), logger_(util::GetLogger(LOGGER_NAME)) {} WebcamV4l2::WebcamV4l2(int id) : working_(false), cam_fd_(-1), capabilities_(0), format_(0), dev_name_(VIDEO_DEV_PREFIX + std::to_string(id)), logger_(util::GetLogger(LOGGER_NAME)) {} WebcamV4l2::WebcamV4l2(const char *name) : working_(false), cam_fd_(-1), capabilities_(0), format_(0), dev_name_(name), logger_(util::GetLogger(LOGGER_NAME)) {} WebcamV4l2::~WebcamV4l2() { Release(); } std::string WebcamV4l2::GetError() const { return error_; } std::string WebcamV4l2::FormatErrno() { return fmt::format("{} - {}", errno, strerror(errno)); } bool WebcamV4l2::Open(bool force) { logger_->debug("check {} open", dev_name_); if (IsOpen()) { if (force) { close(cam_fd_); cam_fd_ = -1; } else { logger_->debug("{} is open", dev_name_); return true; } } logger_->debug("check {} stat", dev_name_); struct stat st; if (-1 == stat(dev_name_.data(), &st)) { error_ = FormatErrno(); logger_->error("can not identify {}, {}", dev_name_, error_); return false; } logger_->debug("check {} type", dev_name_); // check if it's device if (!S_ISCHR(st.st_mode)) { error_ = dev_name_ + " is not a device"; logger_->error(error_); return false; } logger_->debug("openning {} ", dev_name_); cam_fd_ = open(dev_name_.data(), O_RDWR | O_NONBLOCK); if (cam_fd_ == -1) { error_ = FormatErrno(); logger_->error("open {} failure: {}", dev_name_, error_); return false; } logger_->debug("open {} success", dev_name_); return true; } bool WebcamV4l2::Open(const char *name) { if (!name) { return false; } dev_name_ = name; return Open(); } bool WebcamV4l2::IsOpen() { return cam_fd_ != -1; } bool WebcamV4l2::Init() { logger_->debug("initializing {}", dev_name_); if (!IsOpen()) { error_ = "webcam is not open"; logger_->error(error_); return false; } if (!QueryCapability()) { return false; } if (!IsV4l2VideoDevice()) { error_ = fmt::format("{} is not a video device", dev_name_); logger_->error(error_); return false; } if (!SetInput()) { return false; } return true; } bool WebcamV4l2::GrabFrame(std::string &img, uint32_t timeout) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = timeout * 1000; fd_set fds; FD_ZERO(&fds); FD_SET(cam_fd_, &fds); int r = select(cam_fd_ + 1, &fds, nullptr, nullptr, &tv); if (-1 == r) { error_ = fmt::format("select failure, {}", FormatErrno()); logger_->error(error_); return false; } if (!r) { error_ = fmt::format("select {} ms timeout", timeout); logger_->error(error_); return false; } auto buf_ptr = &buf_stat_->buf; memset(buf_ptr, 0, sizeof(*buf_ptr)); buf_ptr->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf_ptr->memory = V4L2_MEMORY_MMAP; if (ioctl(cam_fd_, VIDIOC_DQBUF, buf_ptr) == -1) { error_ = fmt::format("VIDIOC_DQBUF failure, {}", FormatErrno()); logger_->error(error_); return false; } auto index = buf_ptr->index; buf_stat_->buffer[index].bytes = buf_ptr->bytesused; img.assign((char *)buf_stat_->buffer[index].start, buf_stat_->buffer[index].bytes); if (ioctl(cam_fd_, VIDIOC_QBUF, buf_ptr) == -1) { error_ = fmt::format("VIDIOC_QBUF failure, {}", FormatErrno()); logger_->error(error_); return false; } return true; } bool WebcamV4l2::Close() { if (IsOpen()) { close(cam_fd_); cam_fd_ = -1; } return true; } bool WebcamV4l2::SetInput(const char *name) { uint32_t match_index = 0; struct v4l2_input cam_input; cam_input.index = 0; while (ioctl(cam_fd_, VIDIOC_ENUMINPUT, &cam_input) == 0) { logger_->debug("enumerate input {} name: {}, type: {}", cam_input.index, cam_input.name, cam_input.type); if (name && strncasecmp((char *)cam_input.name, name, 32) == 0) { match_index = cam_input.index; } ++cam_input.index; } if (cam_input.index == 0) { error_ = fmt::format("no input on {}", dev_name_); logger_->error(error_); return false; } cam_input.index = match_index; if (ioctl(cam_fd_, VIDIOC_ENUMINPUT, &cam_input) == -1) { error_ = fmt::format("query input {} failure", match_index); logger_->error(error_); return false; } logger_->debug("try to set input index: {}, name: {}, type: {}", cam_input.index, cam_input.name, cam_input.type); if (ioctl(cam_fd_, VIDIOC_S_INPUT, &cam_input) == -1) { error_ = fmt::format("set input {} failure: {}", cam_input.index, strerror(errno)); logger_->error(error_); return false; } logger_->debug("set input success"); return true; } bool WebcamV4l2::QueryCapability() { if (!IsOpen()) { error_ = fmt::format("{} is not open", dev_name_); logger_->error(error_); return false; } struct v4l2_capability cam_cap; if (ioctl(cam_fd_, VIDIOC_QUERYCAP, &cam_cap) == -1) { error_ = FormatErrno(); logger_->error("query capibility failure: {}", error_); return false; } logger_->info("capabilities: 0x{:X}", cam_cap.capabilities); logger_->info("card name : {}", cam_cap.card); logger_->info("driver name : {}", cam_cap.driver); logger_->info("version : {}", cam_cap.version); logger_->info("bus info : {}", cam_cap.bus_info); capabilities_ = cam_cap.capabilities; return true; } bool WebcamV4l2::IsV4l2VideoDevice() { // Judge if the device is a camera device return (capabilities_ & V4L2_CAP_VIDEO_CAPTURE) != 0; } std::string WebcamV4l2::PixFormatName(uint32_t format) { char buf[20] = {0}; sprintf(buf, "[0x%08X] '%c%c%c%c'", format, format >> 0, format >> 8, format >> 16, format >> 24); return buf; } bool WebcamV4l2::SetPixFormat(WebcamFormat fmt, uint32_t width, uint32_t height) { if (!IsV4l2VideoDevice()) { error_ = fmt::format("{} is not a video device", dev_name_); logger_->error(error_); return false; } uint32_t pix_format = 0; struct v4l2_fmtdesc fmt_desc; fmt_desc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt_desc.index = 0; while (ioctl(cam_fd_, VIDIOC_ENUM_FMT, &fmt_desc) == 0) { logger_->info("enumerate format: {}, {}", PixFormatName(fmt_desc.pixelformat), fmt_desc.description); if (fmt == WebcamFormat::kFmtMJPG && fmt_desc.pixelformat == V4L2_PIX_FMT_MJPEG) { pix_format = V4L2_PIX_FMT_MJPEG; } if (fmt == WebcamFormat::kFmtYUYV && fmt_desc.pixelformat == V4L2_PIX_FMT_YUYV) { pix_format = V4L2_PIX_FMT_YUYV; } struct v4l2_frmsizeenum frmsize; frmsize.pixel_format = fmt_desc.pixelformat; frmsize.index = 0; while (ioctl(cam_fd_, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0) { logger_->debug("frame size: {}x{}", frmsize.discrete.width, frmsize.discrete.height); struct v4l2_frmivalenum frmival; memset(&frmival, 0, sizeof(frmival)); frmival.pixel_format = frmsize.pixel_format; frmival.width = frmsize.discrete.width; frmival.height = frmsize.discrete.height; frmival.type = V4L2_FRMIVAL_TYPE_DISCRETE; frmival.index = 0; while (ioctl(cam_fd_, VIDIOC_ENUM_FRAMEINTERVALS, &frmival) == 0) { logger_->debug("frame interval: {:0.3f}s ({} fps)", (double)frmival.discrete.numerator / frmival.discrete.denominator, frmival.discrete.denominator); frmival.index++; } ++frmsize.index; } ++fmt_desc.index; } if (fmt_desc.index == 0) { error_ = "no format is supported"; logger_->error(error_); return false; } // no match, select the 1st format if (pix_format == 0) { fmt_desc.index = 0; if (ioctl(cam_fd_, VIDIOC_ENUM_FMT, &fmt_desc) == 0) { pix_format = fmt_desc.pixelformat; } else { error_ = fmt::format("get index 0 format failure, {}", FormatErrno()); logger_->error(error_); return false; } } logger_->debug("try format {}, {}x{}", PixFormatName(pix_format), width, height); struct v4l2_format v4l2_fmt; v4l2_fmt.type = V4L2_CAP_VIDEO_CAPTURE; v4l2_fmt.fmt.pix.width = width; v4l2_fmt.fmt.pix.height = height; v4l2_fmt.fmt.pix.pixelformat = pix_format; v4l2_fmt.fmt.pix.field = V4L2_FIELD_ANY; if (ioctl(cam_fd_, VIDIOC_TRY_FMT, &v4l2_fmt) == -1) { error_ = fmt::format("try format {}, {}x{} error, {}", PixFormatName(pix_format), width, height, FormatErrno()); logger_->error(error_); return false; } if (v4l2_fmt.fmt.pix.pixelformat != pix_format) { error_ = fmt::format("format {} is not supported, run as {}", PixFormatName(pix_format), PixFormatName(v4l2_fmt.fmt.pix.pixelformat)); logger_->error(error_); return false; } if (v4l2_fmt.fmt.pix.width != width || v4l2_fmt.fmt.pix.height != height) { logger_->info("adjust resolution from {}x{} to {}x{}", width, height, v4l2_fmt.fmt.pix.width, v4l2_fmt.fmt.pix.height); } if (ioctl(cam_fd_, VIDIOC_S_FMT, &v4l2_fmt) == -1) { error_ = fmt::format("set pixel format failure, {}", FormatErrno()); logger_->error(error_); return false; } format_ = pix_format; logger_->info("enable pixel format {} {}x{}", PixFormatName(format_), v4l2_fmt.fmt.pix.width, v4l2_fmt.fmt.pix.height); return true; } void WebcamV4l2::Release() { Stop(); Close(); capabilities_ = 0; format_ = 0; } bool WebcamV4l2::SetMMap() { if (buf_stat_) { return true; } if ((capabilities_ & V4L2_CAP_STREAMING) == 0) { error_ = fmt::format("set mmap failure, {} is not a streaming device", dev_name_); logger_->error(error_); return false; } std::unique_ptr<V4l2BufStat, V4l2BufStatDeleter> buf_stat( new V4l2BufStat, V4l2BufStatDeleter()); // request struct v4l2_requestbuffers req; memset(&req, 0, sizeof(req)); req.count = QBUF_SIZE; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; if (ioctl(cam_fd_, VIDIOC_REQBUFS, &req) == -1) { error_ = fmt::format("VIDIOC_REQBUFS failure, {}", FormatErrno()); logger_->error(error_); return false; } logger_->debug("mmap information:"); logger_->debug("buffer for {} frames", req.count); if (req.count < 2) { error_ = "Insufficient buffer memory"; logger_->error(error_); return false; } buf_stat->type = req.type; buf_stat->buffer = new V4l2BufUnit[req.count]; buf_stat->count = 0; // query and map for (uint32_t i = 0; i < req.count; ++i) { struct v4l2_buffer &buf = buf_stat->buf; memset(&buf, 0, sizeof(struct v4l2_buffer)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; if (ioctl(cam_fd_, VIDIOC_QUERYBUF, &buf) == -1) { error_ = fmt::format("query buffer {} failure, {}", i, strerror(errno)); logger_->error(error_); return false; } auto &unit = buf_stat->buffer[i]; unit.index = i; unit.length = buf.length; unit.offset = buf.m.offset; unit.start = mmap(nullptr, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, cam_fd_, buf.m.offset); if (unit.start == MAP_FAILED) { error_ = fmt::format("map buffer {} failure, {}", i, FormatErrno()); logger_->error(error_); return false; } buf_stat->count = i + 1; } // put in queue for (uint32_t i = 0; i < req.count; ++i) { struct v4l2_buffer &buf = buf_stat->buf; memset(&buf, 0, sizeof(struct v4l2_buffer)); buf.index = i; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (ioctl(cam_fd_, VIDIOC_QBUF, &buf) == -1) { error_ = fmt::format("unable to queue buffer, {}", FormatErrno()); logger_->error(error_); return false; } } buf_stat_ = std::move(buf_stat); return true; } bool WebcamV4l2::FreeMMap() { if (buf_stat_) { buf_stat_.reset(); } return true; } bool WebcamV4l2::StreamOn() { if (!buf_stat_) { error_ = "mmap is not ready"; logger_->error(error_); return false; } enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(cam_fd_, VIDIOC_STREAMON, &type) == -1) { error_ = fmt::format("streamon failure, {}", FormatErrno()); logger_->error(error_); return false; } return true; } bool WebcamV4l2::StreamOff() { if (!buf_stat_) { error_ = "mmap is not ready"; logger_->error(error_); return false; } enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(cam_fd_, VIDIOC_STREAMOFF, &type) == -1) { error_ = fmt::format("streamoff failure, {}", FormatErrno()); logger_->error(error_); return false; } return true; } bool WebcamV4l2::SetFps(uint8_t fps) { if (!fps) { return false; } struct v4l2_streamparm parm; memset(&parm, 0, sizeof(struct v4l2_streamparm)); parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(cam_fd_, VIDIOC_G_PARM, &parm) == -1) { error_ = fmt::format("VIDIOC_G_PARM failure, {}", FormatErrno()); logger_->error(error_); return false; } logger_->info("current fps {}", parm.parm.capture.timeperframe.denominator); if (parm.parm.capture.timeperframe.denominator == fps) { return true; } logger_->info("try to set fps {}", fps); struct v4l2_streamparm setfps; memset(&setfps, 0, sizeof(setfps)); setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; setfps.parm.capture.timeperframe.numerator = 1; setfps.parm.capture.timeperframe.denominator = fps; if (ioctl(cam_fd_, VIDIOC_S_PARM, &setfps) == -1) { /* Not fatal - just warn about it */ error_ = fmt::format("set fps failure, {}", FormatErrno()); logger_->warn(error_); return false; } if (ioctl(cam_fd_, VIDIOC_G_PARM, &parm) == -1) { error_ = fmt::format("VIDIOC_G_PARM failure, {}", FormatErrno()); logger_->error(error_); return false; } logger_->info("current fps {}", parm.parm.capture.timeperframe.denominator); return true; } bool WebcamV4l2::GetControl() { struct v4l2_queryctrl queryctrl; memset(&queryctrl, 0, sizeof(queryctrl)); queryctrl.id = V4L2_CTRL_FLAG_NEXT_CTRL; while (0 == ioctl(cam_fd_, VIDIOC_QUERYCTRL, &queryctrl)) { ShowControl(&queryctrl); queryctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL; } return true; } bool WebcamV4l2::SetExtControl() { // might be same as SetControl struct v4l2_query_ext_ctrl query_ext_ctrl; memset(&query_ext_ctrl, 0, sizeof(query_ext_ctrl)); query_ext_ctrl.id = V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND; while (0 == ioctl(cam_fd_, VIDIOC_QUERY_EXT_CTRL, &query_ext_ctrl)) { if (!(query_ext_ctrl.flags & V4L2_CTRL_FLAG_DISABLED)) { logger_->info("Ext control {}", query_ext_ctrl.name); if (query_ext_ctrl.type == V4L2_CTRL_TYPE_MENU) { logger_->info("{}", EnumerateMenu(query_ext_ctrl.id, query_ext_ctrl.minimum, query_ext_ctrl.maximum)); } } query_ext_ctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND; } return true; } std::string WebcamV4l2::EnumerateMenu(uint32_t id, int32_t index_min, int32_t index_max) { struct v4l2_querymenu querymenu; memset(&querymenu, 0, sizeof(querymenu)); querymenu.id = id; std::vector<std::string> menu_names; for (int32_t m = index_min; m <= index_max; ++m) { querymenu.index = m; if (0 == ioctl(cam_fd_, VIDIOC_QUERYMENU, &querymenu)) { menu_names.push_back((char *)querymenu.name); } } if (menu_names.empty()) { return std::string(); } else { return string_util::join(menu_names, " | "); } } bool WebcamV4l2::ShowCtrlMenu(struct v4l2_queryctrl *queryctrl) { std::string options = EnumerateMenu(queryctrl->id, queryctrl->minimum, queryctrl->maximum); struct v4l2_control control; memset(&control, 0, sizeof(control)); control.id = queryctrl->id; if (ioctl(cam_fd_, VIDIOC_G_CTRL, &control) == -1) { error_ = fmt::format("read value of control {} failure, {}", queryctrl->name, strerror(errno)); logger_->error(error_); return false; } struct v4l2_querymenu querymenu; memset(&querymenu, 0, sizeof(querymenu)); querymenu.id = queryctrl->id; querymenu.index = control.value; if (-1 == ioctl(cam_fd_, VIDIOC_QUERYMENU, &querymenu)) { error_ = fmt::format("read menu item {} value of control {} failure, {}", control.value, queryctrl->name, strerror(errno)); logger_->error(error_); return false; } logger_->info("queryctrl menu id: 0x{:X}, name: {}, menu: {}, options: {}", queryctrl->id, queryctrl->name, querymenu.name, options); return true; } bool WebcamV4l2::ShowCtrlInt(struct v4l2_queryctrl *queryctrl) { struct v4l2_control control; memset(&control, 0, sizeof(control)); control.id = queryctrl->id; if (ioctl(cam_fd_, VIDIOC_G_CTRL, &control) == -1) { logger_->error("read value of control {} failure, {}", queryctrl->name, strerror(errno)); return false; } if (queryctrl->maximum - queryctrl->minimum <= 10) { logger_->info("queryctrl int id: 0x{:X}, name: {:<32}, value:{:<12}, " "flags: {:<2} " "(default: {:<4}, [{:<6}:{:<6}:{:<2}])", queryctrl->id, queryctrl->name, control.value, queryctrl->flags, queryctrl->default_value, queryctrl->minimum, queryctrl->maximum, queryctrl->step); } else { logger_->info("queryctrl int id: 0x{:X}, name: {:<32}, value:{:<5} - " "{:>3}%, flags: " "{:<2} (default: {:<4}, [{:<6}:{:<6}:{:<2}])", queryctrl->id, queryctrl->name, control.value, SCALE(0, 100, queryctrl->minimum, queryctrl->maximum, control.value), queryctrl->flags, queryctrl->default_value, queryctrl->minimum, queryctrl->maximum, queryctrl->step); } V4l2Ctrl ctrl{.queryctrl = *queryctrl, .control = control}; ctrl_[control.id] = ctrl; return true; } bool WebcamV4l2::ShowControl(struct v4l2_queryctrl *queryctrl) { if (!queryctrl) { return false; } if (queryctrl->flags & V4L2_CTRL_FLAG_DISABLED) { logger_->info( "queryctrl id: 0x{:X}, name: {:<32}, DISABLED, flags: {:<2} ", queryctrl->id, queryctrl->name, queryctrl->flags); return false; } switch (queryctrl->type) { case V4L2_CTRL_TYPE_INTEGER: if (!ShowCtrlInt(queryctrl)) { return false; } break; case V4L2_CTRL_TYPE_BOOLEAN: { struct v4l2_control control; memset(&control, 0, sizeof(control)); control.id = queryctrl->id; if (ioctl(cam_fd_, VIDIOC_G_CTRL, &control) == -1) { error_ = fmt::format("read value of control {} failure, {}", queryctrl->name, strerror(errno)); logger_->error(error_); return false; } logger_->info("queryctrl bool id: 0x{:X}, name: {:<32}, value:{:<12}, " "flags: {:<2} " "(default: {})", queryctrl->id, queryctrl->name, control.value ? "True" : "False", queryctrl->flags, queryctrl->default_value ? "True" : "False"); V4l2Ctrl ctrl{.queryctrl = *queryctrl, .control = control}; ctrl_[control.id] = ctrl; } break; case V4L2_CTRL_TYPE_MENU: if (!ShowCtrlMenu(queryctrl)) { return false; } break; case V4L2_CTRL_TYPE_BUTTON: logger_->info("queryctrl btn id: 0x{:X}, name: {:<32} - [Button]", queryctrl->id, queryctrl->name); break; default: logger_->info("queryctrl deft id: 0x{:X}, name: {:<32} N/A [Unknown " "Control Type]", queryctrl->id, queryctrl->name); break; } return true; } bool WebcamV4l2::SetExposure() { int ret; struct v4l2_control ctrl; //得到曝光模式 ctrl.id = V4L2_CID_EXPOSURE_AUTO; if (ioctl(cam_fd_, VIDIOC_G_CTRL, &ctrl) == -1) { printf("Get exposure auto Type failed\n"); return false; } printf("\nGet Exposure Auto Type:[%d]\n", ctrl.value); // ctrl.id = V4L2_CID_ROTATE; // ctrl.value = 90; // if (ioctl(cam_fd_, VIDIOC_S_CTRL, &ctrl) == -1) { // printf("Set rotate failed\n"); // return false; //} // printf("\nSet rotate:[%d]\n", ctrl.value); // struct v4l2_control ctrl; // ctrl.id = V4L2_CID_EXPOSURE_AUTO; // if (ioctl(cam_fd_, VIDIOC_G_CTRL, &ctrl) == -1) { // logger_->warn("get exposure failure, {}", strerror(errno)); //} // logger_->info("exposure {}", ctrl.value); return false; } // sync mode bool WebcamV4l2::Grab(std::string &out, uint32_t timeout) { if (!working_) { error_ = "not started"; logger_->error(error_); return false; } if (!buf_stat_) { error_ = "v4l2 buffers are not ready"; logger_->error(error_); return false; } if (!GrabFrame(out, timeout)) { error_ = fmt::format("grab frame failure, {}", error_); logger_->error(error_); return false; } return true; } bool WebcamV4l2::Grab(std::string *out, uint32_t timeout) { if (!working_) { error_ = "not started"; logger_->error(error_); return false; } if (!buf_stat_) { error_ = "v4l2 buffers are not ready"; logger_->error(error_); return false; } struct timeval tv; tv.tv_sec = 0; tv.tv_usec = timeout * 1000; fd_set fds; FD_ZERO(&fds); FD_SET(cam_fd_, &fds); int r = select(cam_fd_ + 1, &fds, nullptr, nullptr, &tv); if (-1 == r) { error_ = fmt::format("select failure, {}", FormatErrno()); logger_->error(error_); return false; } if (!r) { error_ = fmt::format("select {} ms timeout", timeout); logger_->error(error_); return false; } auto buf_ptr = &buf_stat_->buf; memset(buf_ptr, 0, sizeof(*buf_ptr)); buf_ptr->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf_ptr->memory = V4L2_MEMORY_MMAP; if (ioctl(cam_fd_, VIDIOC_DQBUF, buf_ptr) == -1) { logger_->error("grab VIDIOC_DQBUF failure, {}", FormatErrno()); return false; } if (out) { (*out).assign((char *)buf_stat_->buffer[buf_ptr->index].start, buf_ptr->bytesused); } if (ioctl(cam_fd_, VIDIOC_QBUF, buf_ptr) == -1) { logger_->error("grab VIDIOC_QBUF failure, {}", FormatErrno()); return false; } return true; } // block bool WebcamV4l2::Grab(uint32_t timeout) { if (!frame_cb_) { error_ = "frame callback is null"; logger_->error(error_); return false; } if (!working_) { error_ = "stream is not started"; logger_->error(error_); return false; } if (!buf_stat_) { error_ = "v4l2 buffers are not ready"; logger_->error(error_); return false; } struct timeval tv; tv.tv_sec = 0; tv.tv_usec = timeout * 1000; fd_set fds; FD_ZERO(&fds); FD_SET(cam_fd_, &fds); int r = select(cam_fd_ + 1, &fds, nullptr, nullptr, &tv); if (-1 == r) { error_ = fmt::format("select failure, {}", FormatErrno()); logger_->error(error_); return false; } if (!r) { error_ = fmt::format("select {} ms timeout", timeout); logger_->error(error_); return false; } auto buf_ptr = &buf_stat_->buf; memset(buf_ptr, 0, sizeof(*buf_ptr)); buf_ptr->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf_ptr->memory = V4L2_MEMORY_MMAP; if (ioctl(cam_fd_, VIDIOC_DQBUF, buf_ptr) == -1) { logger_->error("VIDIOC_DQBUF failure"); return false; } frame_cb_((const char *)buf_stat_->buffer[buf_ptr->index].start, buf_ptr->bytesused); if (ioctl(cam_fd_, VIDIOC_QBUF, buf_ptr) == -1) { logger_->error("VIDIOC_QBUF failure"); return false; } return true; } // non-block bool WebcamV4l2::Retrieve(bool discard) { if (!frame_cb_) { error_ = "frame callback is null"; logger_->error(error_); return false; } if (!working_) { error_ = "stream is not started"; logger_->error(error_); return false; } if (!buf_stat_) { error_ = "v4l2 buffers are not ready"; logger_->error(error_); return false; } struct v4l2_buffer buf; memset(&buf, 0, sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (ioctl(cam_fd_, VIDIOC_DQBUF, &buf) == -1) { logger_->error("retrieve VIDIOC_DQBUF failure, {}", FormatErrno()); return false; } if (!discard) { frame_cb_((const char *)buf_stat_->buffer[buf.index].start, buf.bytesused); } if (ioctl(cam_fd_, VIDIOC_QBUF, &buf) == -1) { logger_->error("retrieve VIDIOC_QBUF failure, {}", FormatErrno()); return false; } return true; } bool WebcamV4l2::Retrieve(std::string &img) { if (!working_) { error_ = "stream is not started"; logger_->error(error_); return false; } if (!buf_stat_) { error_ = "v4l2 buffers are not ready"; logger_->error(error_); return false; } struct v4l2_buffer buf; memset(&buf, 0, sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (ioctl(cam_fd_, VIDIOC_DQBUF, &buf) == -1) { logger_->error("retrieve VIDIOC_DQBUF failure, {}", FormatErrno()); return false; } img.assign((char *)buf_stat_->buffer[buf.index].start, buf.bytesused); if (ioctl(cam_fd_, VIDIOC_QBUF, &buf) == -1) { logger_->error("retrieve VIDIOC_QBUF failure, {}", FormatErrno()); return false; } return true; } bool WebcamV4l2::Retrieve(std::string *img) { if (img) { return Retrieve(*img); } else { // discard frame quickly if (!working_) { error_ = "stream is not started"; logger_->error(error_); return false; } if (!buf_stat_) { error_ = "v4l2 buffers are not ready"; logger_->error(error_); return false; } struct v4l2_buffer buf; memset(&buf, 0, sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (ioctl(cam_fd_, VIDIOC_DQBUF, &buf) == -1) { logger_->error("retrieve VIDIOC_DQBUF failure, {}", FormatErrno()); return false; } if (ioctl(cam_fd_, VIDIOC_QBUF, &buf) == -1) { logger_->error("retrieve VIDIOC_QBUF failure, {}", FormatErrno()); return false; } return true; } } bool WebcamV4l2::Start() { if (working_) { return true; } if (!SetMMap()) { return false; } working_ = StreamOn(); logger_->info("start working {}", working_); return working_; } bool WebcamV4l2::Stop() { if (!working_) { return false; } StreamOff(); FreeMMap(); working_ = false; logger_->info("stop working"); return true; } } // namespace webcam } // namespace noevil
1、获取jpeg图像
#include "jpeg_transform.h" #include "webcam_v4l2.h" #include <iostream> #include <string> using namespace noevil::webcam; bool WriteFile(const std::string &path, const std::string &content) { int fd = open(path.data(), O_RDWR | O_CREAT, 00664); if (fd == -1) { throw std::runtime_error( fmt::format("Failed to open {}, {}", path, strerror(errno))); } int writesize = write(fd, content.data(), content.length()); close(fd); return true; } int main(int argc, char **argv) { setlocale(LC_ALL, ""); noevil::util::Init("cam.log"); noevil::util::SetLevel(spdlog::level::trace); noevil::webcam::WebcamV4l2 cam(argv[1]); if (!cam.Open()) { return 1; } if (!cam.Init()) { std::cout << "init failure, " << cam.GetError() << std::endl; return 1; } if (!cam.SetPixFormat(noevil::webcam::WebcamFormat::kFmtMJPG, 1920, 1080)) { std::cout << "set format failure, " << cam.GetError() << std::endl; return 1; } if (!cam.Start()) { std::cout << "start failure, " << cam.GetError() << std::endl; return 1; } std::unique_ptr<JpegTransform> transform( new JpegTransform(JpegTransform::JpegTransformOp::kTransRot90)); for (int i = 0; i < 100; ++i) { std::string frm; if (cam.Grab(frm)) { std::string name = std::to_string(i) + ".jpg"; WriteFile(name, frm); std::string rotate; transform->Transform(frm, rotate); WriteFile(std::to_string(i) + "_90.jpg", rotate); } } cam.Stop(); return 0; }
我附带了一个图像旋转的包装类,需要libturbojpeg0-dev库。
2、获取yuv图像
noevil::webcam::WebcamV4l2 cam(argv[1]); if (!cam.Open()) { return 1; } if (!cam.Init()) { std::cout << "init failure, " << cam.GetError() << std::endl; return 1; } if (!cam.SetPixFormat(noevil::webcam::WebcamFormat::kFmtYUYV, 1280, 720)) { std::cout << "set format failure, " << cam.GetError() << std::endl; return 1; } if (!cam.Start()) { std::cout << "start failure, " << cam.GetError() << std::endl; return 1; } for (int i = 0; i < 100; ++i) { std::string frm; if (cam.Grab(frm)) { std::string name = std::to_string(i) + ".yuv"; WriteFile(name, frm); } } cam.Stop();
3、获取yuv视频
noevil::webcam::WebcamV4l2 cam(argv[1]); if (!cam.Open()) { return 1; } if (!cam.Init()) { std::cout << "init failure, " << cam.GetError() << std::endl; return 1; } if (!cam.SetPixFormat(noevil::webcam::WebcamFormat::kFmtYUYV, 1280, 720)) { std::cout << "set format failure, " << cam.GetError() << std::endl; return 1; } if (!cam.Start()) { std::cout << "start failure, " << cam.GetError() << std::endl; return 1; } FILE *fp = fopen("video_yuv422.yuv", "wb+"); auto cb=[&](const char* const data, uint32_t size) { fwrite(data, 1, size, fp); }; cam.SetFrameCallback(cb); // discard for (int i = 0; i < 100; ++i) { cam.Grab(nullptr, 200); } for (int i = 0; i < 100; ++i) { cam.Grab(200); } cam.Stop(); fclose(fp);
摄像头启动的前面一些帧光线太暗,直接跳过,后面的帧直接存储成yuv, 用pyuv打开
广告