First import
Program can read from /dev/video0 and dump the frame to the disk
This commit is contained in:
commit
2f4587e40a
6 changed files with 476 additions and 0 deletions
16
meson.build
Normal file
16
meson.build
Normal file
|
@ -0,0 +1,16 @@
|
|||
project('magicstore', 'cpp',
|
||||
version: '0.1.0',
|
||||
default_options: ['buildtype=debug', 'cpp_std=gnu++17', 'c_std=c11', 'build_testing=true'],
|
||||
)
|
||||
|
||||
libopencv_dep = dependency('opencv4')
|
||||
|
||||
executable(meson.project_name(),
|
||||
'src/main.cpp',
|
||||
'src/camera.cpp',
|
||||
'src/file_handle.cpp',
|
||||
dependencies: [
|
||||
libopencv_dep,
|
||||
],
|
||||
install: true,
|
||||
)
|
265
src/camera.cpp
Normal file
265
src/camera.cpp
Normal file
|
@ -0,0 +1,265 @@
|
|||
#include "camera.hpp"
|
||||
#if !defined(NDEBUG)
|
||||
# include <iostream>
|
||||
#endif
|
||||
#include <stdexcept>
|
||||
#include <linux/v4l2-common.h>
|
||||
#include <linux/v4l2-controls.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <cstring>
|
||||
#include <sys/mman.h>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
namespace mgs {
|
||||
typedef std::unique_ptr<FileHandle, FileHandle::Deleter> UniqueFile;
|
||||
|
||||
struct Camera::LocalData {
|
||||
explicit LocalData (FileHandle fh) :
|
||||
buffer_info{},
|
||||
video_path(),
|
||||
input_buffer(nullptr),
|
||||
frames_read(0),
|
||||
file(fh)
|
||||
{ }
|
||||
|
||||
v4l2_buffer buffer_info;
|
||||
std::string video_path;
|
||||
const char* input_buffer;
|
||||
std::size_t frames_read;
|
||||
UniqueFile file;
|
||||
};
|
||||
|
||||
namespace {
|
||||
typedef decltype(v4l2_format::type) V4l2FormatTypeType;
|
||||
|
||||
constexpr auto DefaultPixelFormat = V4L2_PIX_FMT_RGB24; //V4L2_PIX_FMT_MJPEG;
|
||||
constexpr auto DefaultPixelField = V4L2_FIELD_NONE;
|
||||
|
||||
v4l2_format fetch_image_format (
|
||||
int file_handle,
|
||||
V4l2FormatTypeType type,
|
||||
const std::string& path
|
||||
) {
|
||||
v4l2_format img_format;
|
||||
img_format.type = type;
|
||||
if (ioctl(file_handle, VIDIOC_G_FMT, &img_format))
|
||||
throw std::runtime_error("Unable to get format from device \"" + path + "\"");
|
||||
return img_format;
|
||||
}
|
||||
|
||||
} //unnamed namespace
|
||||
|
||||
FrameFormat::FrameFormat (std::uint32_t x, std::uint32_t y, std::uint32_t fmt) :
|
||||
width(x),
|
||||
height(y),
|
||||
format(fmt)
|
||||
{ }
|
||||
|
||||
bool FrameFormat::operator== (const FrameFormat& o) const noexcept {
|
||||
return width == o.width and height == o.height and format == o.format;
|
||||
}
|
||||
|
||||
bool FrameFormat::operator< (const FrameFormat& o) const noexcept {
|
||||
return (o.format == format ?
|
||||
(o.width == width ? height < o.height : width < o.width) :
|
||||
format < o.format
|
||||
);
|
||||
}
|
||||
|
||||
bool FrameFormat::operator!=(const FrameFormat& o) const noexcept {
|
||||
return not this->operator==(o);
|
||||
}
|
||||
|
||||
Camera::Camera (std::string path, const FrameFormat& frame_size, bool start_now) :
|
||||
//1. Open the device
|
||||
m_local(std::make_unique<LocalData>(FileHandle::open(path.c_str(), FileHandle::Mode_RDWR)))
|
||||
{
|
||||
m_local->video_path = std::move(path);
|
||||
|
||||
start_opening_device();
|
||||
|
||||
if (FrameFormat{} != frame_size)
|
||||
this->set_frame_size(frame_size);
|
||||
|
||||
if (start_now)
|
||||
this->allocate_buffers();
|
||||
}
|
||||
|
||||
Camera::~Camera() noexcept = default;
|
||||
|
||||
FileHandle Camera::file_handle() const {
|
||||
return m_local->file.get();
|
||||
}
|
||||
|
||||
void Camera::start_streaming() {
|
||||
allocate_buffers();
|
||||
}
|
||||
|
||||
std::vector<char>& Camera::read_frame (std::vector<char>& out_buf) {
|
||||
const char* const buffer = m_local->input_buffer;
|
||||
v4l2_buffer& buffer_info = m_local->buffer_info;
|
||||
|
||||
// Queue the buffer
|
||||
if (ioctl(file_handle(), VIDIOC_QBUF, &buffer_info) < 0)
|
||||
throw std::runtime_error("Could not queue buffer, VIDIOC_QBUF");
|
||||
|
||||
// Dequeue the buffer
|
||||
if (ioctl(file_handle(), VIDIOC_DQBUF, &buffer_info) < 0)
|
||||
throw std::runtime_error("Could not dequeue the buffer, VIDIOC_DQBUF");
|
||||
|
||||
// Frames get written after dequeuing the buffer
|
||||
++m_local->frames_read;
|
||||
out_buf.resize(buffer_info.bytesused);
|
||||
std::copy(buffer, buffer + buffer_info.bytesused, out_buf.begin());
|
||||
return out_buf;
|
||||
}
|
||||
|
||||
FrameFormat Camera::frame_size() const {
|
||||
v4l2_format img_format = fetch_image_format(file_handle(), V4L2_BUF_TYPE_VIDEO_CAPTURE, video_path());
|
||||
return {img_format.fmt.pix.width, img_format.fmt.pix.height, img_format.fmt.pix.pixelformat};
|
||||
}
|
||||
|
||||
void Camera::set_frame_size (const FrameFormat& new_size) {
|
||||
#if !defined(NDEBUG)
|
||||
std::cout << "Setting frame size to " << new_size.width << 'x' << new_size.height << '\n';
|
||||
#endif
|
||||
|
||||
v4l2_format img_format = fetch_image_format(
|
||||
file_handle(),
|
||||
V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
||||
video_path()
|
||||
);
|
||||
|
||||
img_format.fmt.pix.pixelformat = new_size.format;
|
||||
img_format.fmt.pix.field = DefaultPixelField;
|
||||
img_format.fmt.pix.width = new_size.width;
|
||||
img_format.fmt.pix.height = new_size.height;
|
||||
if (ioctl(file_handle(), VIDIOC_S_FMT, &img_format) < 0)
|
||||
throw std::runtime_error("Device could not set format, VIDIOC_S_FMT");
|
||||
}
|
||||
|
||||
unsigned int Camera::stride() const {
|
||||
const v4l2_format img_format = fetch_image_format(
|
||||
file_handle(),
|
||||
V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
||||
video_path()
|
||||
);
|
||||
return img_format.fmt.pix.bytesperline;
|
||||
}
|
||||
|
||||
const std::string& Camera::video_path() const {
|
||||
return m_local->video_path;
|
||||
}
|
||||
|
||||
std::vector<FrameFormat> Camera::supported_frame_sizes() const {
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
struct v4l2_fmtdesc fmt;
|
||||
struct v4l2_frmsizeenum frmsize;
|
||||
std::vector<FrameFormat> retval;
|
||||
|
||||
fmt.index = 0;
|
||||
fmt.type = type;
|
||||
while (ioctl(file_handle(), VIDIOC_ENUM_FMT, &fmt) >= 0) {
|
||||
frmsize.pixel_format = fmt.pixelformat;
|
||||
frmsize.index = 0;
|
||||
while (ioctl(file_handle(), VIDIOC_ENUM_FRAMESIZES, &frmsize) >= 0) {
|
||||
if (frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
|
||||
retval.emplace_back(frmsize.discrete.width, frmsize.discrete.height, frmsize.pixel_format);
|
||||
}
|
||||
else if (frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE) {
|
||||
retval.emplace_back(frmsize.stepwise.max_width, frmsize.stepwise.max_height, frmsize.pixel_format);
|
||||
}
|
||||
frmsize.index++;
|
||||
}
|
||||
fmt.index++;
|
||||
}
|
||||
|
||||
//std::sort(retval.begin(), retval.end());
|
||||
//auto it_end = std::unique(retval.begin(), retval.end());
|
||||
//retval.erase(it_end, retval.end());
|
||||
return retval;
|
||||
}
|
||||
|
||||
void Camera::start_opening_device() {
|
||||
#if !defined(NDEBUG)
|
||||
std::cout << "Opened \"" << video_path() << "\" as handle " << static_cast<int>(m_local->file.get()) << '\n';
|
||||
#endif
|
||||
if (not m_local->file)
|
||||
throw std::runtime_error{"Opening \"" + video_path() + "\" failed"};
|
||||
|
||||
//from https://gist.github.com/mike168m/6dd4eb42b2ec906e064d
|
||||
//2. Ask the device if it can capture frames
|
||||
v4l2_capability cap;
|
||||
if (ioctl(file_handle(), VIDIOC_QUERYCAP, &cap) < 0)
|
||||
throw std::runtime_error("Failed to get device capabilities, VIDIOC_QUERYCAP");
|
||||
|
||||
//3. Set Image format
|
||||
v4l2_format img_format = fetch_image_format(file_handle(), V4L2_BUF_TYPE_VIDEO_CAPTURE, video_path());
|
||||
|
||||
img_format.fmt.pix.pixelformat = DefaultPixelFormat;
|
||||
img_format.fmt.pix.field = DefaultPixelField;
|
||||
// tell the device you are using this format
|
||||
if (ioctl(file_handle(), VIDIOC_S_FMT, &img_format) < 0)
|
||||
throw std::runtime_error("Device could not set format, VIDIOC_S_FMT");
|
||||
}
|
||||
|
||||
void Camera::allocate_buffers() {
|
||||
activate_streaming(false);
|
||||
|
||||
//4. Request Buffers from the device
|
||||
v4l2_requestbuffers request_buf{};
|
||||
request_buf.count = 1; // one request buffer
|
||||
request_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // request a buffer wich we an use for capturing frames
|
||||
request_buf.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
if (ioctl(file_handle(), VIDIOC_REQBUFS, &request_buf) < 0)
|
||||
throw std::runtime_error("Could not request buffer from device, VIDIOC_REQBUFS");
|
||||
|
||||
// 5. Query the buffer to get raw data ie. ask for the requested buffer
|
||||
// and allocate memory for it
|
||||
v4l2_buffer query_buf {};
|
||||
query_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
query_buf.memory = V4L2_MEMORY_MMAP;
|
||||
query_buf.index = 0;
|
||||
if (ioctl(file_handle(), VIDIOC_QUERYBUF, &query_buf) < 0)
|
||||
throw std::runtime_error("Device did not return the buffer information, VIDIOC_QUERYBUF");
|
||||
|
||||
// use a pointer to point to the newly created buffer
|
||||
// mmap() will map the memory address of the device to
|
||||
// an address in memory
|
||||
m_local->input_buffer = static_cast<char*>(mmap(
|
||||
nullptr,
|
||||
query_buf.length,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
file_handle(),
|
||||
query_buf.m.offset
|
||||
));
|
||||
//std::memset(buffer, 0, query_buf.length);
|
||||
|
||||
// 6. Get a frame
|
||||
// Create a new buffer type so the device knows whichbuffer we are talking about
|
||||
v4l2_buffer& buffer_info = m_local->buffer_info;
|
||||
std::memset(&buffer_info, 0, sizeof(buffer_info));
|
||||
buffer_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buffer_info.memory = V4L2_MEMORY_MMAP;
|
||||
buffer_info.index = 0;
|
||||
|
||||
// Activate streaming
|
||||
activate_streaming(true);
|
||||
}
|
||||
|
||||
void Camera::activate_streaming (bool activate) {
|
||||
const int type = m_local->buffer_info.type;
|
||||
const auto value = (activate ? VIDIOC_STREAMON : VIDIOC_STREAMOFF);
|
||||
if (ioctl(file_handle(), value, &type) < 0) {
|
||||
if (activate)
|
||||
throw std::runtime_error("Could not start streaming, VIDIOC_STREAMON");
|
||||
else
|
||||
throw std::runtime_error("Could not stop streaming, VIDIOC_STREAMOFF");
|
||||
}
|
||||
}
|
||||
|
||||
} //namespace mgs
|
57
src/camera.hpp
Normal file
57
src/camera.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
#include "file_handle.hpp"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <climits>
|
||||
|
||||
namespace mgs {
|
||||
struct FrameFormat {
|
||||
FrameFormat() = default;
|
||||
FrameFormat(const FrameFormat&) = default;
|
||||
FrameFormat(FrameFormat&&) = default;
|
||||
FrameFormat (std::uint32_t x, std::uint32_t y, std::uint32_t fmt);
|
||||
~FrameFormat() noexcept = default;
|
||||
|
||||
FrameFormat& operator=(const FrameFormat&) = default;
|
||||
FrameFormat& operator=(FrameFormat&&) = default;
|
||||
bool operator== (const FrameFormat& o) const noexcept;
|
||||
bool operator< (const FrameFormat& o) const noexcept;
|
||||
bool operator!=(const FrameFormat& o) const noexcept;
|
||||
|
||||
std::uint32_t width{}, height{};
|
||||
std::uint32_t format{};
|
||||
};
|
||||
|
||||
constexpr std::uint32_t fourcc_to_int (const char fourcc[5]) noexcept;
|
||||
|
||||
class Camera {
|
||||
public:
|
||||
Camera (std::string path, const FrameFormat& frame_size, bool start_now);
|
||||
~Camera() noexcept;
|
||||
FileHandle file_handle() const;
|
||||
|
||||
void start_streaming();
|
||||
std::vector<char>& read_frame (std::vector<char>& out_buf);
|
||||
FrameFormat frame_size() const;
|
||||
void set_frame_size (const FrameFormat& new_size);
|
||||
unsigned int stride() const;
|
||||
const std::string& video_path() const;
|
||||
std::vector<FrameFormat> supported_frame_sizes() const;
|
||||
|
||||
private:
|
||||
struct LocalData;
|
||||
|
||||
void start_opening_device();
|
||||
void allocate_buffers();
|
||||
void activate_streaming (bool activate);
|
||||
|
||||
std::unique_ptr<LocalData> m_local;
|
||||
};
|
||||
|
||||
inline constexpr std::uint32_t fourcc_to_int (const char fourcc[5]) noexcept {
|
||||
return fourcc[0] | (fourcc[1] << CHAR_BIT) | (fourcc[2] << 2 * CHAR_BIT) | (fourcc[3] << 3 * CHAR_BIT);
|
||||
}
|
||||
} //namespace mgs
|
37
src/file_handle.cpp
Normal file
37
src/file_handle.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#include "file_handle.hpp"
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
# include <iostream>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
//#include <sys/stat.h>
|
||||
|
||||
namespace mgs {
|
||||
const int FileHandle::Mode_RDWR = O_RDWR;
|
||||
const int FileHandle::Mode_RDONLY = O_RDONLY;
|
||||
const int FileHandle::Mode_WRONLY = O_WRONLY;
|
||||
const int FileHandle::Mode_APPEND = O_APPEND;
|
||||
const int FileHandle::Mode_CREAT = O_CREAT;
|
||||
const int FileHandle::Mode_DIRECTORY = O_DIRECTORY;
|
||||
const int FileHandle::Mode_DSYNC = O_DSYNC;
|
||||
const int FileHandle::Mode_EXCL = O_EXCL;
|
||||
const int FileHandle::Mode_NOCTTY = O_NOCTTY;
|
||||
const int FileHandle::Mode_NOFOLLOW = O_NOFOLLOW;
|
||||
const int FileHandle::Mode_NONBLOCK = O_NONBLOCK;
|
||||
const int FileHandle::Mode_RSYNC = O_RSYNC;
|
||||
const int FileHandle::Mode_SYNC = O_SYNC;
|
||||
const int FileHandle::Mode_TRUNC = O_TRUNC;
|
||||
|
||||
void FileHandle::Deleter::operator() (pointer fd) {
|
||||
#if !defined(NDEBUG)
|
||||
std::cout << "Closing file " << static_cast<int>(fd) << '\n';
|
||||
#endif
|
||||
close(fd);
|
||||
}
|
||||
|
||||
FileHandle FileHandle::open (const char* path, int mode) {
|
||||
return {::open(path, O_RDWR)};
|
||||
}
|
||||
|
||||
} //namespace mgs
|
46
src/file_handle.hpp
Normal file
46
src/file_handle.hpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace mgs {
|
||||
class FileHandle {
|
||||
public:
|
||||
struct Deleter {
|
||||
typedef FileHandle pointer;
|
||||
void operator() (pointer fd);
|
||||
};
|
||||
|
||||
static const int Mode_RDWR, Mode_RDONLY, Mode_WRONLY, Mode_APPEND,
|
||||
Mode_CREAT, Mode_DIRECTORY, Mode_DSYNC, Mode_EXCL, Mode_NOCTTY,
|
||||
Mode_NOFOLLOW, Mode_NONBLOCK, Mode_RSYNC, Mode_SYNC, Mode_TRUNC;
|
||||
|
||||
FileHandle(std::nullptr_t = nullptr) : m_handle(0) {}
|
||||
FileHandle(int v) : m_handle(v) {}
|
||||
FileHandle(const FileHandle&) = default;
|
||||
FileHandle(FileHandle&&) = default;
|
||||
~FileHandle() noexcept = default;
|
||||
|
||||
FileHandle& operator= (const FileHandle&) = default;
|
||||
FileHandle& operator= (FileHandle&&) = default;
|
||||
|
||||
operator bool() const noexcept { return 0 != m_handle; }
|
||||
operator int() const noexcept { return m_handle; }
|
||||
bool operator!() const noexcept { return !m_handle; }
|
||||
|
||||
friend bool operator==(FileHandle a, FileHandle b) noexcept {
|
||||
return a.m_handle == b.m_handle;
|
||||
}
|
||||
|
||||
friend bool operator!=(FileHandle a, FileHandle b) noexcept {
|
||||
return a.m_handle != b.m_handle;
|
||||
}
|
||||
|
||||
friend bool operator== (FileHandle fh, std::nullptr_t) noexcept { return !fh; }
|
||||
friend bool operator!= (FileHandle fh, std::nullptr_t) noexcept { return !!fh; }
|
||||
|
||||
static FileHandle open (const char* path, int mode);
|
||||
|
||||
private:
|
||||
int m_handle;
|
||||
};
|
||||
} //namespace mgs
|
55
src/main.cpp
Normal file
55
src/main.cpp
Normal file
|
@ -0,0 +1,55 @@
|
|||
#include "camera.hpp"
|
||||
#if !defined(NDEBUG)
|
||||
# include <iostream>
|
||||
#endif
|
||||
#include <opencv2/flann.hpp>
|
||||
#include <climits>
|
||||
#include <fstream>
|
||||
|
||||
constexpr const auto MjpegFormat = mgs::fourcc_to_int("MJPG");
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
namespace mgs {
|
||||
std::ostream& operator<< (std::ostream& stream, const FrameFormat& value) {
|
||||
stream << value.width << 'x' << value.height << ' ' <<
|
||||
static_cast<char>((value.format >> 0 * CHAR_BIT) & 0xFF) <<
|
||||
static_cast<char>((value.format >> 1 * CHAR_BIT) & 0xFF) <<
|
||||
static_cast<char>((value.format >> 2 * CHAR_BIT) & 0xFF) <<
|
||||
static_cast<char>((value.format >> 3 * CHAR_BIT) & 0xFF);
|
||||
return stream;
|
||||
}
|
||||
} //namespace mgs
|
||||
#endif
|
||||
|
||||
int main() {
|
||||
try {
|
||||
mgs::Camera cam("/dev/video0", {}, false);
|
||||
cam.set_frame_size({1280, 720, MjpegFormat});
|
||||
std::cout << mgs::FrameFormat{1280, 720, MjpegFormat} << '\n';
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
const auto curr_frame_size = cam.frame_size();
|
||||
|
||||
for (const auto& size : cam.supported_frame_sizes()) {
|
||||
std::cout << size;
|
||||
if (curr_frame_size == size)
|
||||
std::cout << " *";
|
||||
std::cout << '\n';
|
||||
}
|
||||
#endif
|
||||
cam.start_streaming();
|
||||
|
||||
std::vector<char> my_frame;
|
||||
auto data = cam.read_frame(my_frame);
|
||||
|
||||
std::ofstream file_wr("temp.jpg", std::ios::binary | std::ios::out);
|
||||
file_wr.write(data.data(), data.size());
|
||||
}
|
||||
catch (const std::runtime_error& err) {
|
||||
std::cerr << "Unhandled exception: " << err.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Program done\n";
|
||||
return 0;
|
||||
}
|
Loading…
Reference in a new issue