First import

Program can read from /dev/video0 and dump the frame to the disk
This commit is contained in:
King_DuckZ 2021-10-31 16:43:18 +01:00
commit 2f4587e40a
6 changed files with 476 additions and 0 deletions

16
meson.build Normal file
View 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
View 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
View 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
View 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
View 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
View 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;
}