Linux音视频开发之一:用V4L2采集webcam摄像头数据mjpeg 和yuv

Stella981
• 阅读 1053

网上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库。 Linux音视频开发之一:用V4L2采集webcam摄像头数据mjpeg 和yuv

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打开 Linux音视频开发之一:用V4L2采集webcam摄像头数据mjpeg 和yuv

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Easter79 Easter79
3年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这