commit 2f4587e40ae71c175c0fe50bf9a984191bf6815b Author: King_DuckZ Date: Sun Oct 31 16:43:18 2021 +0100 First import Program can read from /dev/video0 and dump the frame to the disk diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..47704f8 --- /dev/null +++ b/meson.build @@ -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, +) diff --git a/src/camera.cpp b/src/camera.cpp new file mode 100644 index 0000000..8ac0eea --- /dev/null +++ b/src/camera.cpp @@ -0,0 +1,265 @@ +#include "camera.hpp" +#if !defined(NDEBUG) +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mgs { +typedef std::unique_ptr 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(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& Camera::read_frame (std::vector& 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 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 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(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(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 diff --git a/src/camera.hpp b/src/camera.hpp new file mode 100644 index 0000000..745da10 --- /dev/null +++ b/src/camera.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include "file_handle.hpp" +#include +#include +#include +#include +#include + +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& read_frame (std::vector& 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 supported_frame_sizes() const; + +private: + struct LocalData; + + void start_opening_device(); + void allocate_buffers(); + void activate_streaming (bool activate); + + std::unique_ptr 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 diff --git a/src/file_handle.cpp b/src/file_handle.cpp new file mode 100644 index 0000000..537fb9a --- /dev/null +++ b/src/file_handle.cpp @@ -0,0 +1,37 @@ +#include "file_handle.hpp" + +#if !defined(NDEBUG) +# include +#endif +#include +#include +//#include + +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(fd) << '\n'; +#endif + close(fd); +} + +FileHandle FileHandle::open (const char* path, int mode) { + return {::open(path, O_RDWR)}; +} + +} //namespace mgs diff --git a/src/file_handle.hpp b/src/file_handle.hpp new file mode 100644 index 0000000..bb5ad2b --- /dev/null +++ b/src/file_handle.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +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 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..60a1b22 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,55 @@ +#include "camera.hpp" +#if !defined(NDEBUG) +# include +#endif +#include +#include +#include + +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((value.format >> 0 * CHAR_BIT) & 0xFF) << + static_cast((value.format >> 1 * CHAR_BIT) & 0xFF) << + static_cast((value.format >> 2 * CHAR_BIT) & 0xFF) << + static_cast((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 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; +}