HDMI输入直至Yolov5识别全流程代码

This commit is contained in:
zhangpeng
2025-04-28 14:48:28 +08:00
commit 31940162fc
112 changed files with 222134 additions and 0 deletions

1
hdmiIn/REDME.md Normal file
View File

@@ -0,0 +1 @@
./rk3588_hdmi_in_out-in_and_out 目录用来保存hdmi输入虚拟出video输出的代码

Binary file not shown.

View File

@@ -0,0 +1,226 @@
---
Language: Cpp
# BasedOnStyle: Google
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveMacros: None
AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: None
AlignConsecutiveDeclarations: None
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: true
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
QualifierAlignment: Leave
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: true
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
PackConstructorInitializers: NextLine
BasedOnStyle: ''
ConstructorInitializerAllOnOneLineOrOnePerLine: false
AllowAllConstructorInitializersOnNextLine: true
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^<.*\.h>'
Priority: 1
SortPriority: 0
CaseSensitive: false
- Regex: '^<.*'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 3
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '([-_](test|unittest))?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseLabels: true
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentRequires: false
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
LambdaBodyIndentation: Signature
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PenaltyIndentedWhitespace: 0
PointerAlignment: Left
PPIndentWidth: -1
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- 'c++'
- 'C++'
CanonicalDelimiter: ''
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
- ParseTestProto
- ParsePartialTestProto
CanonicalDelimiter: pb
BasedOnStyle: google
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
BeforeNonEmptyParentheses: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: Never
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
BitFieldColonSpacing: Both
Standard: Auto
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseCRLF: false
UseTab: Never
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
- NS_SWIFT_NAME
- CF_SWIFT_NAME
...

View File

@@ -0,0 +1,3 @@
cmake-build-*/
.idea
build

View File

@@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.21)
project(rk3588_hdmi_in)
set(CMAKE_CXX_STANDARD 17)
# 查找所需的库
find_package(OpenCV REQUIRED)
find_package(kaylordut REQUIRED)
# 明确列出所有源文件
set(SOURCES
main.cpp
src/fps_counter.cpp
src/hdmi_in.cpp
src/framebuffer.cpp
src/video_saver.cpp
src/virtual_device.cpp
)
# 创建可执行文件
add_executable(rk3588_hdmi_in ${SOURCES})
# 包含头文件
target_include_directories(rk3588_hdmi_in PRIVATE
include
${OpenCV_INCLUDE_DIRS}
)
# 链接库
target_link_libraries(rk3588_hdmi_in PRIVATE
${OpenCV_LIBS}
${kaylordut_LIBS}
)

View File

@@ -0,0 +1,18 @@
#ifndef FPS_COUNTER_H
#define FPS_COUNTER_H
#include <chrono>
#include <deque>
class FPSCounter {
public:
FPSCounter(size_t window_size = 10);
double update();
private:
size_t window_size_;
std::deque<std::chrono::steady_clock::time_point> time_points_;
std::deque<double> fps_values_;
};
#endif // FPS_COUNTER_H

View File

@@ -0,0 +1,25 @@
//
// Created by kaylor on 1/9/25.
//
#ifndef FRAMEBUFFER_H
#define FRAMEBUFFER_H
#include "linux/fb.h"
#include "opencv2/opencv.hpp"
#include "string"
class FrameBuffer {
public:
FrameBuffer(std::string device);
~FrameBuffer();
bool WriteFrameBuffer(const cv::Mat image);
private:
std::string device_;
int fd_;
char *fb_ptr_{nullptr};
fb_fix_screeninfo fix_screeninfo_;
fb_var_screeninfo var_screeninfo_;
};
#endif // FRAMEBUFFER_H

View File

@@ -0,0 +1,21 @@
//
// Created by kaylor on 1/8/25.
//
#ifndef HDMI_IN_H
#define HDMI_IN_H
#include "opencv2/opencv.hpp"
class HdmiIn {
public:
HdmiIn(std::string device_name);
~HdmiIn();
cv::Mat get_next_frame();
private:
cv::VideoCapture capture_;
std::string device_name_;
};
#endif // HDMI_IN_H

View File

@@ -0,0 +1,39 @@
#ifndef VIDEO_SAVER_H
#define VIDEO_SAVER_H
#include <opencv2/opencv.hpp>
#include <filesystem>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>
namespace fs = std::filesystem;
class VideoSaver {
public:
VideoSaver(int fps, int width, int height, size_t max_total_size_gb = 1);
~VideoSaver();
void write_frame(const cv::Mat& frame);
static void file_deleter();
static void stop_file_deleter();
private:
void start_new_file();
void check_and_cleanup_files();
int fps_;
int width_;
int height_;
int frame_count_;
size_t max_total_size_bytes_;
cv::VideoWriter writer_;
static std::mutex delete_mutex;
static std::condition_variable delete_cv;
static std::queue<std::string> files_to_delete_queue;
static bool stop_delete_thread;
};
#endif // VIDEO_SAVER_H

View File

@@ -0,0 +1,19 @@
#ifndef VIRTUAL_DEVICE_H
#define VIRTUAL_DEVICE_H
#include <opencv2/opencv.hpp>
#include <queue>
#include <mutex>
#include <condition_variable>
// 声明全局变量为 extern
extern std::queue<cv::Mat> frame_queue;
extern std::mutex queue_mutex;
extern std::condition_variable queue_cv;
extern bool stop_thread;
int init_virtual_device(const std::string& device_path, int width, int height);
void write_to_virtual_device(int fd, const cv::Mat& frame);
void virtual_device_writer(int fd);
#endif // VIRTUAL_DEVICE_H

View File

@@ -0,0 +1,111 @@
#include "fps_counter.h"
#include "video_saver.h"
#include "virtual_device.h"
#include "framebuffer.h"
#include "hdmi_in.h"
#include "kaylordut/log/logger.h"
#include <opencv2/opencv.hpp>
#include <thread>
#include <unistd.h>
#include <cstdlib> // 为了使用系统函数
#include <sys/stat.h> // 为了使用 access 函数
int main(int argc, char **argv) {
// 执行 framebuffer 设置命令
int result = system("sudo fbset -xres 1280 -yres 720");
if (result != 0) {
KAYLORDUT_LOG_ERROR("Failed to set framebuffer resolution");
return -1;
}
std::string virtual_device = "/dev/video10";
// 检查虚拟设备是否存在
if (access(virtual_device.c_str(), F_OK) == -1) {
// 如果不存在,创建虚拟设备
result = system("sudo modprobe v4l2loopback video_nr=10 card_label=\"HDMI_Capture\" exclusive_caps=1");
if (result != 0) {
KAYLORDUT_LOG_ERROR("Failed to load v4l2loopback module");
return -1;
}
}
std::stringstream ss;
for (int i = 0; i < argc; ++i) {
ss << argv[i] << " ";
}
KAYLORDUT_LOG_ERROR("Command: {}", ss.str());
std::string device = "/dev/video0";
HdmiIn hdmi_in(device);
std::string fb = "/dev/fb0";
FrameBuffer frame_buffer(fb);
int vd_fd = init_virtual_device(virtual_device, 1280, 720);
if (vd_fd == -1) {
KAYLORDUT_LOG_ERROR("Failed to initialize virtual device");
return -1;
}
cv::Mat frame;
FPSCounter fps_counter;
const std::chrono::milliseconds frame_duration(1000 / 25);
auto next_frame_time = std::chrono::steady_clock::now();
frame = hdmi_in.get_next_frame();
if (frame.empty()) {
KAYLORDUT_LOG_ERROR("Failed to get initial frame to determine video size");
return -1;
}
VideoSaver video_saver(25, frame.cols, frame.rows);
std::thread writer_thread(virtual_device_writer, vd_fd);
std::thread deleter_thread(VideoSaver::file_deleter);
while (true) {
auto start_time = std::chrono::steady_clock::now();
frame = hdmi_in.get_next_frame();
if (!frame.empty()) {
double current_fps = fps_counter.update();
std::string fps_text = cv::format("FPS: %.1f", current_fps);
cv::putText(frame, fps_text, cv::Point(20, 40), cv::FONT_HERSHEY_SIMPLEX, 1.2, CV_RGB(0, 255, 0), 2);
frame_buffer.WriteFrameBuffer(frame);
video_saver.write_frame(frame);
{
std::lock_guard<std::mutex> lock(queue_mutex);
frame_queue.push(frame.clone());
queue_cv.notify_one();
}
}
auto process_time = std::chrono::steady_clock::now() - start_time;
next_frame_time += frame_duration;
std::this_thread::sleep_until(next_frame_time);
if (process_time > frame_duration) {
// KAYLORDUT_LOG_ERROR("Frame processing took too long: {}ms", std::chrono::duration_cast<std::chrono::milliseconds>(process_time).count());
}
}
{
std::lock_guard<std::mutex> lock(queue_mutex);
stop_thread = true;
queue_cv.notify_one();
}
writer_thread.join();
VideoSaver::stop_file_deleter();
deleter_thread.join();
if (vd_fd != -1) {
close(vd_fd);
}
return 0;
}

View File

@@ -0,0 +1,9 @@
```bash
> v4l2-ctl -d /dev/video11 -V -D
#设置framebuffer缓冲区的显示尺寸和输入视频流的尺寸一致可以降低CPU占用
sudo fbset -xres 1280 -yres 720
#虚拟一个video10设备
sudo modprobe v4l2loopback video_nr=10 card_label="HDMI_Capture" exclusive_caps=1

View File

@@ -0,0 +1,25 @@
#include "fps_counter.h"
FPSCounter::FPSCounter(size_t window_size) : window_size_(window_size) {}
double FPSCounter::update() {
auto now = std::chrono::steady_clock::now();
if (!time_points_.empty()) {
double fps = 1.0 / std::chrono::duration<double>(now - time_points_.back()).count();
fps_values_.push_back(fps);
if (fps_values_.size() > window_size_) {
fps_values_.pop_front();
}
}
time_points_.push_back(now);
if (time_points_.size() > window_size_) {
time_points_.pop_front();
}
if (fps_values_.empty()) return 0.0;
double sum = 0.0;
for (double fps : fps_values_) {
sum += fps;
}
return sum / fps_values_.size();
}

View File

@@ -0,0 +1,81 @@
//
// Created by kaylor on 1/9/25.
//
#include "framebuffer.h"
#include <unistd.h>
#include "kaylordut/log/logger.h"
#include "sys/file.h"
#include "sys/ioctl.h"
#include "sys/mman.h"
FrameBuffer::FrameBuffer(std::string device) {
device_ = device;
fd_ = open(device_.c_str(), O_RDWR);
if (fd_ == -1) {
KAYLORDUT_LOG_ERROR("cannot open {}", device_);
exit(EXIT_FAILURE);
}
// 尝试独占锁定帧缓冲设备
if (flock(fd_, LOCK_EX | LOCK_NB) == -1) {
KAYLORDUT_LOG_ERROR("Error locking framebuffer device");
close(fd_);
exit(EXIT_FAILURE);
}
if (ioctl(fd_, FBIOGET_FSCREENINFO, &fix_screeninfo_)) {
KAYLORDUT_LOG_ERROR("cannot get fix screen info");
flock(fd_, LOCK_UN);
close(fd_);
exit(EXIT_FAILURE);
}
KAYLORDUT_LOG_ERROR("fix screen info: smem_len: {}, line_len: {}",
fix_screeninfo_.smem_len, fix_screeninfo_.line_length);
if (ioctl(fd_, FBIOGET_VSCREENINFO, &var_screeninfo_)) {
KAYLORDUT_LOG_ERROR("cannot var fix screen info");
flock(fd_, LOCK_UN);
close(fd_);
exit(EXIT_FAILURE);
}
KAYLORDUT_LOG_INFO("var screen info: {}x{}", var_screeninfo_.xres,
var_screeninfo_.yres);
fb_ptr_ = (char *)mmap(0, fix_screeninfo_.smem_len, PROT_READ | PROT_WRITE,
MAP_SHARED, fd_, 0);
if ((long)fb_ptr_ == -1) {
KAYLORDUT_LOG_ERROR("map mem error");
flock(fd_, LOCK_UN);
close(fd_);
exit(EXIT_FAILURE);
}
}
FrameBuffer::~FrameBuffer() {
if (fb_ptr_ != nullptr) {
munmap(fb_ptr_, fix_screeninfo_.smem_len);
if (fd_) {
flock(fd_, LOCK_UN);
close(fd_);
}
fb_ptr_ = nullptr;
}
}
bool FrameBuffer::WriteFrameBuffer(const cv::Mat image) {
cv::Mat result;
cv::resize(image, result,
cv::Size(var_screeninfo_.xres, var_screeninfo_.yres));
if (image.channels() != 4) {
// KAYLORDUT_TIME_COST_INFO("BGR2BGRA",
// cv::cvtColor(result, result, cv::COLOR_BGR2BGRA));
}
auto dst = fb_ptr_;
auto src = result.data;
for (int i = 0; i < result.rows; ++i) {
std::memcpy(dst, src, result.cols * result.elemSize());
dst += fix_screeninfo_.line_length;
src += result.cols * result.elemSize();
}
return true;
}

View File

@@ -0,0 +1,35 @@
//
// Created by kaylor on 1/8/25.
//
#include "hdmi_in.h"
#include "kaylordut/log/logger.h"
HdmiIn::HdmiIn(std::string device_name) {
device_name_ = device_name;
capture_ = cv::VideoCapture(
"v4l2src device=" + device_name_ +
" ! videoconvert ! video/x-raw, format=BGR ! appsink",
cv::CAP_GSTREAMER);
if (!capture_.isOpened()) {
KAYLORDUT_LOG_ERROR("cannot open {}", device_name_);
exit(EXIT_FAILURE);
}
}
HdmiIn::~HdmiIn() {
if (capture_.isOpened()) {
capture_.release();
}
}
cv::Mat HdmiIn::get_next_frame() {
cv::Mat frame;
capture_ >> frame;
if (frame.empty()) {
KAYLORDUT_LOG_WARN("cannot read a new frame");
// return cv::Mat();
}
return frame;
}

View File

@@ -0,0 +1,109 @@
#include "video_saver.h"
#include "kaylordut/log/logger.h"
std::mutex VideoSaver::delete_mutex;
std::condition_variable VideoSaver::delete_cv;
std::queue<std::string> VideoSaver::files_to_delete_queue;
bool VideoSaver::stop_delete_thread = false;
VideoSaver::VideoSaver(int fps, int width, int height, size_t max_total_size_gb)
: fps_(fps), width_(width), height_(height), frame_count_(0), max_total_size_bytes_(max_total_size_gb * 1024 * 1024 * 1024) {
fs::create_directory("./video_output");
start_new_file();
}
VideoSaver::~VideoSaver() {
if (writer_.isOpened()) {
writer_.release();
}
}
void VideoSaver::write_frame(const cv::Mat& frame) {
if (!writer_.isOpened()) {
KAYLORDUT_LOG_ERROR("Video writer is not opened!");
return;
}
writer_.write(frame);
frame_count_++;
if (frame_count_ >= fps_) {
writer_.release();
frame_count_ = 0;
start_new_file();
}
}
void VideoSaver::file_deleter() {
while (true) {
std::string file_path;
{
std::unique_lock<std::mutex> lock(delete_mutex);
delete_cv.wait(lock, [] { return !files_to_delete_queue.empty() || stop_delete_thread; });
if (stop_delete_thread && files_to_delete_queue.empty()) break;
file_path = files_to_delete_queue.front();
files_to_delete_queue.pop();
}
fs::remove(file_path);
KAYLORDUT_LOG_INFO("Deleted old file: {}", file_path);
}
}
void VideoSaver::stop_file_deleter() {
{
std::lock_guard<std::mutex> lock(delete_mutex);
stop_delete_thread = true;
delete_cv.notify_one();
}
}
void VideoSaver::start_new_file() {
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(std::localtime(&in_time_t), "%Y%m%d_%H%M%S");
std::string timestamp = ss.str();
std::string filename = "./video_output/video_" + timestamp + ".mp4";
int fourcc = cv::VideoWriter::fourcc('m', 'p', '4', 'v');
writer_.open(filename, fourcc, fps_, cv::Size(width_, height_));
if (!writer_.isOpened()) {
KAYLORDUT_LOG_ERROR("Could not open video file for writing: {}", filename);
return;
}
check_and_cleanup_files();
}
void VideoSaver::check_and_cleanup_files() {
std::vector<fs::path> files;
size_t total_size = 0;
for (const auto& entry : fs::directory_iterator("./video_output")) {
if (entry.is_regular_file() && entry.path().extension() == ".mp4") {
files.push_back(entry.path());
total_size += fs::file_size(entry.path());
}
}
std::sort(files.begin(), files.end(), [](const fs::path& a, const fs::path& b) {
return fs::last_write_time(a) < fs::last_write_time(b);
});
size_t files_to_delete = 0;
while (total_size > max_total_size_bytes_ && !files.empty() && files_to_delete < 5) {
total_size -= fs::file_size(files.front());
{
std::lock_guard<std::mutex> lock(delete_mutex);
files_to_delete_queue.push(files.front().string());
}
files.erase(files.begin());
files_to_delete++;
}
if (files_to_delete > 0) {
delete_cv.notify_one();
}
}

View File

@@ -0,0 +1,65 @@
#include "virtual_device.h"
#include <fcntl.h> // 包含 open、O_RDWR 等
#include <unistd.h> // 包含 close、write 等
#include <linux/videodev2.h> // 包含 Video4Linux2 相关定义
#include "kaylordut/log/logger.h" // 包含日志宏
#include <sys/ioctl.h> // 包含 ioctl 函数
// 定义全局变量
std::queue<cv::Mat> frame_queue;
std::mutex queue_mutex;
std::condition_variable queue_cv;
bool stop_thread = false;
int init_virtual_device(const std::string& device_path, int width, int height) {
int fd = open(device_path.c_str(), O_RDWR);
if (fd == -1) {
KAYLORDUT_LOG_ERROR("Failed to open virtual video device: {}", device_path);
return -1;
}
struct v4l2_format fmt = {};
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_BGR24;
fmt.fmt.pix.field = V4L2_FIELD_NONE;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
KAYLORDUT_LOG_ERROR("Failed to set video format");
close(fd);
return -1;
}
return fd;
}
void write_to_virtual_device(int fd, const cv::Mat& frame) {
if (fd == -1 || frame.empty()) return;
cv::Mat continuous_frame;
if (!frame.isContinuous()) {
continuous_frame = frame.clone();
} else {
continuous_frame = frame;
}
ssize_t written = write(fd, continuous_frame.data, continuous_frame.total() * continuous_frame.elemSize());
if (written == -1) {
KAYLORDUT_LOG_ERROR("Failed to write to virtual device");
}
}
void virtual_device_writer(int fd) {
while (true) {
cv::Mat frame;
{
std::unique_lock<std::mutex> lock(queue_mutex);
queue_cv.wait(lock, [] { return !frame_queue.empty() || stop_thread; });
if (stop_thread && frame_queue.empty()) break;
frame = frame_queue.front();
frame_queue.pop();
}
write_to_virtual_device(fd, frame);
}
}

Binary file not shown.