From 7c3dbcedfe16ece8930336d3276ce4e6c9d11a5b Mon Sep 17 00:00:00 2001 From: amass <168062547@qq.com> Date: Wed, 2 Oct 2024 03:01:37 +0800 Subject: [PATCH] add linux. --- Analyser/VideoPlayer.cpp | 24 +++-- CMakeLists.txt | 5 +- OtaUpdate/Widget.cpp | 9 +- Peripheral/CdcUpdater.cpp | 7 +- Peripheral/DeviceDiscovery.cpp | 180 +++++++++++++++++++++++++++++++-- Peripheral/DeviceDiscovery.h | 12 ++- UnitTest/CMakeLists.txt | 20 ++++ UnitTest/LinuxDeviceEnums.cpp | 17 ++++ UnitTest/main.cpp | 2 + 9 files changed, 251 insertions(+), 25 deletions(-) create mode 100644 UnitTest/CMakeLists.txt create mode 100644 UnitTest/LinuxDeviceEnums.cpp create mode 100644 UnitTest/main.cpp diff --git a/Analyser/VideoPlayer.cpp b/Analyser/VideoPlayer.cpp index 4769e5d..53be363 100644 --- a/Analyser/VideoPlayer.cpp +++ b/Analyser/VideoPlayer.cpp @@ -29,27 +29,39 @@ VideoPlayer::~VideoPlayer() { bool VideoPlayer::open(const std::string &deviceName) { bool ret = true; m_formatContext = avformat_alloc_context(); - auto format = av_find_input_format("dshow"); +#ifdef WIN32 + constexpr auto format = "dshow"; + std::ostringstream oss; + oss << "video=" << deviceName; + auto device = oss.str(); +#else + constexpr auto format = "v4l2"; + auto &device = deviceName; +#endif + auto inputFormat = av_find_input_format(format); AVDictionary *dictionary = nullptr; // ffmpeg -f dshow -list_options true -i video="UVC Camera" av_dict_set(&dictionary, "video_size", "800*600", 0); - std::ostringstream oss; - oss << "video=" << deviceName; - auto device = oss.str(); - int status = avformat_open_input(&m_formatContext, "video=UVC Camera", format, &dictionary); + int status = avformat_open_input(&m_formatContext, device.c_str(), inputFormat, &dictionary); if (status != 0) { char message[256] = {0}; av_make_error_string(message, sizeof(message), status); LOG(error) << "open device[" << device << "] failed: " << status << "," << message; ret = false; } else { + av_dump_format(m_formatContext, 0, device.c_str(), 0); + // for (unsigned int i = 0; i < m_formatContext->nb_streams; i++) { + // AVStream *stream = m_formatContext->streams[i]; + // if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + // std::cout << "当前分辨率: " << stream->codecpar->width << "x" << stream->codecpar->height << std::endl; + // } + // } m_exit = false; m_thread = std::thread(&VideoPlayer::run, this); } av_dict_free(&dictionary); - return ret; } diff --git a/CMakeLists.txt b/CMakeLists.txt index 9cb4a13..d9883ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,5 @@ FetchContent_MakeAvailable(Kylin) add_subdirectory(Peripheral) add_subdirectory(Database) add_subdirectory(Analyser) -if(WIN32) - add_subdirectory(OtaUpdate) -endif() +add_subdirectory(OtaUpdate) +add_subdirectory(UnitTest) diff --git a/OtaUpdate/Widget.cpp b/OtaUpdate/Widget.cpp index 8e6ad13..07c1b5b 100644 --- a/OtaUpdate/Widget.cpp +++ b/OtaUpdate/Widget.cpp @@ -60,7 +60,12 @@ Widget::~Widget() { void Widget::start() { m_progressBar->setValue(0); auto filePath = m_fileEditor->text(); - if (!std::filesystem::exists(Amass::StringUtility::UTF8ToGBK(filePath.toStdString()))) { +#ifdef Q_OS_WIN + auto path = Amass::StringUtility::UTF8ToGBK(filePath.toStdString()); +#else + auto path = filePath.toStdString(); +#endif + if (!std::filesystem::exists(path)) { QMessageBox::warning(this, "升级", "升级文件不存在!"); return; } @@ -72,7 +77,7 @@ void Widget::start() { std::error_code error; setMessage("尝试发现设备......"); - auto device = discovery->find("UVC Camera", error); + auto device = discovery->find(DeviceDiscovery::DeviceName, error); if (!device) { QMessageBox::warning(this, "升级", "未检测到模组,请尝试重新插入模组!"); return; diff --git a/Peripheral/CdcUpdater.cpp b/Peripheral/CdcUpdater.cpp index 839c83b..f7a361e 100644 --- a/Peripheral/CdcUpdater.cpp +++ b/Peripheral/CdcUpdater.cpp @@ -99,9 +99,10 @@ std::optional CdcUpdater::searchDevice() { std::optional ret = std::nullopt; const auto serialPortInfos = QSerialPortInfo::availablePorts(); for (const QSerialPortInfo &portInfo : serialPortInfos) { - LOG(info) << "portName:" << portInfo.portName().toStdString() - << ", vendorIdentifier: " << portInfo.vendorIdentifier() - << ", productIdentifier: " << portInfo.productIdentifier(); + if ((portInfo.vendorIdentifier() == 0) && (portInfo.productIdentifier() == 0)) continue; + LOG(info) << "portName:" << portInfo.portName().toStdString() << ", vendorIdentifier: 0x" << std::hex + << portInfo.vendorIdentifier() << ", productIdentifier: 0x" << std::hex + << portInfo.productIdentifier(); if (portInfo.vendorIdentifier() == 0xffff) { ret = portInfo; break; diff --git a/Peripheral/DeviceDiscovery.cpp b/Peripheral/DeviceDiscovery.cpp index b7fb216..3d16106 100644 --- a/Peripheral/DeviceDiscovery.cpp +++ b/Peripheral/DeviceDiscovery.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #ifdef Q_OS_WIN #include #include @@ -13,17 +14,9 @@ #include #include #include +#include #endif -struct DeviceDiscovery::Device { - ~Device(); -#ifdef Q_OS_WIN - Device(IMFMediaSource *source); - IMFMediaSource *source = nullptr; - IMFSourceReader *reader = nullptr; -#endif -}; - template void SafeRelease(T **ppT) { if (*ppT) { @@ -210,7 +203,8 @@ std::vector DeviceDiscovery::devices() { struct v4l2_capability cap; if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == 0) { - if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) { + if ((strstr(reinterpret_cast(cap.card), DeviceName) != nullptr) && + (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { ret.push_back(entry.path().string()); } } @@ -219,6 +213,172 @@ std::vector DeviceDiscovery::devices() { } return ret; } + +static std::string find_video_device_by_name(const std::string &targetName) { + std::string ret; + const std::string base_path = "/sys/class/video4linux"; + for (const auto &entry : std::filesystem::directory_iterator(base_path)) { + if (!std::filesystem::is_directory(entry.path())) continue; + + std::string interface; + std::ifstream ifs(entry.path().string() + "/device/interface"); + if (ifs.is_open()) { + std::getline(ifs, interface); + } + if (interface != targetName) continue; + + ifs = std::ifstream(entry.path().string() + "/index"); + std::string index; + if (ifs.is_open()) { + std::getline(ifs, index); + } + if (index != "0") continue; + + ret = "/dev/" + entry.path().filename().string(); + break; + } + return ret; +} + +std::shared_ptr DeviceDiscovery::find(const std::string &deviceName, std::error_code &error) { + auto ret = std::make_shared(); + ret->name = find_video_device_by_name(deviceName); + return ret; +} + +void DeviceDiscovery::enterOtaMode(const std::shared_ptr &device, std::error_code &error) { + auto resolutions = deviceResolutions(device); + LOG(info) << "device resolutions:"; + for (auto &[w, h] : resolutions) { + LOG(info) << "\t" << w << "*" << h; + } + int32_t otaSpecificHeight = -1; + for (auto &[width, height] : resolutions) { + if (width == OtaSpecificWidth) { + otaSpecificHeight = height; + break; + } + } + if (otaSpecificHeight <= 0) { + LOG(error) << "cannot find ota specific resolution."; + return; + } else { + LOG(info) << "found ota specific resolution: " << OtaSpecificWidth << "x" << otaSpecificHeight; + } + int fd = open(device->name.c_str(), O_RDWR); + if (fd <= 0) { + LOG(error) << "Failed to open device " << device->name; + } else { + struct v4l2_format format; + memset(&format, 0, sizeof(format)); + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + format.fmt.pix.width = OtaSpecificWidth; + format.fmt.pix.height = otaSpecificHeight; + format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; + format.fmt.pix.field = V4L2_FIELD_INTERLACED; + + if (ioctl(fd, VIDIOC_S_FMT, &format) == -1) { + perror("Setting Pixel Format"); + return; + } +#define CLEAR(x) memset(&(x), 0, sizeof(x)) + struct v4l2_requestbuffers req; + CLEAR(req); + req.count = 1; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + + if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) { + perror("Requesting Buffer"); + return; + } + + struct v4l2_buffer buf; + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = 0; + + if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) { + perror("Querying Buffer"); + return; + } + + struct buffer { + void *start; + size_t length; + }; + + struct buffer buffer; + buffer.length = buf.length; + buffer.start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); + + if (buffer.start == MAP_FAILED) { + perror("Mapping Buffer"); + return; + } + + if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) { + perror("Queue Buffer"); + return; + } + + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) { + perror("Start Capture"); + return; + } + + if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1) { + perror("Stop Capture"); + return; + } + + if (munmap(buffer.start, buffer.length) == -1) { + perror("Unmapping Buffer"); + return; + } + + close(fd); + } +} + +DeviceDiscovery::Resolutions DeviceDiscovery::deviceResolutions(const std::shared_ptr &source) { + Resolutions ret; + int fd = open(source->name.c_str(), O_RDWR); + if (fd <= 0) { + LOG(error) << "Failed to open device " << source->name; + } else { + struct v4l2_fmtdesc fmt; + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.index = 0; + + while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) == 0) { + std::cout << "Pixel Format: " << fmt.description << std::endl; + struct v4l2_frmsizeenum frmsize; + frmsize.pixel_format = fmt.pixelformat; + frmsize.index = 0; + + while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0) { + if (frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) { + ret.emplace_back(frmsize.discrete.width, frmsize.discrete.height); + } else if (frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE) { + for (int width = frmsize.stepwise.min_width; width <= frmsize.stepwise.max_width; + width += frmsize.stepwise.step_width) { + for (int height = frmsize.stepwise.min_height; height <= frmsize.stepwise.max_height; + height += frmsize.stepwise.step_height) { + ret.emplace_back(width, height); + } + } + } + frmsize.index++; + } + fmt.index++; + } + close(fd); + } + return ret; +} #endif DeviceDiscovery::Device::~Device() { diff --git a/Peripheral/DeviceDiscovery.h b/Peripheral/DeviceDiscovery.h index 566d80e..dda61cb 100644 --- a/Peripheral/DeviceDiscovery.h +++ b/Peripheral/DeviceDiscovery.h @@ -10,7 +10,17 @@ class DeviceDiscovery { constexpr static int32_t OtaSpecificWidth = 96; public: - struct Device; + constexpr static auto DeviceName = "UVC Camera"; + struct Device { + ~Device(); +#ifdef Q_OS_WIN + Device(IMFMediaSource *source); + IMFMediaSource *source = nullptr; + IMFSourceReader *reader = nullptr; +#else + std::string name; +#endif + }; using Resolution = std::pair; using Resolutions = std::vector; diff --git a/UnitTest/CMakeLists.txt b/UnitTest/CMakeLists.txt new file mode 100644 index 0000000..8f56a06 --- /dev/null +++ b/UnitTest/CMakeLists.txt @@ -0,0 +1,20 @@ +find_package(Boost REQUIRED COMPONENTS unit_test_framework) + +# --detect_memory_leak=0 --run_test=MarkdownParserTest,ProcessUtilityTest +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable(UnitTest main.cpp + LinuxDeviceEnums.cpp +) + +target_compile_definitions(UnitTest + PUBLIC LOG_FILTER_LEVEL=2 +) + +target_link_libraries(UnitTest + PRIVATE Boost::unit_test_framework + PRIVATE DataStructure + PRIVATE Peripheral + PRIVATE Universal +) diff --git a/UnitTest/LinuxDeviceEnums.cpp b/UnitTest/LinuxDeviceEnums.cpp new file mode 100644 index 0000000..911f579 --- /dev/null +++ b/UnitTest/LinuxDeviceEnums.cpp @@ -0,0 +1,17 @@ +#include "BoostLog.h" +#include "DeviceDiscovery.h" +#include + +BOOST_AUTO_TEST_CASE(EnumDevice) { + std::error_code error; + DeviceDiscovery discovery; + auto device = discovery.find("UVC Camera", error); + LOG(info) << device->name; + + auto devices = discovery.devices(); + for (int i = 0; i < devices.size(); i++) { + LOG(info) << "device[" << i << "] " << devices.at(i); + } + + discovery.enterOtaMode(device, error); +} diff --git a/UnitTest/main.cpp b/UnitTest/main.cpp new file mode 100644 index 0000000..4cd512b --- /dev/null +++ b/UnitTest/main.cpp @@ -0,0 +1,2 @@ +#define BOOST_TEST_MODULE KylinLibraryTest +#include "boost/test/included/unit_test.hpp"