diff --git a/Application.cpp b/Application.cpp index f16e045..8d13bfc 100644 --- a/Application.cpp +++ b/Application.cpp @@ -2,6 +2,7 @@ #include "BoostLog.h" #include "Configuration.h" #include "H264Palyer.h" +#include "Settings.h" #include "VideoFrameProvider.h" #include #include @@ -9,10 +10,31 @@ #include #include +std::ostream &operator<<(std::ostream &os, const ImageFormat &format) { + switch (format) { + case ImageFormat::Jpeg: + os << "Jpeg"; + break; + case ImageFormat::YUV: + os << "YUV"; + break; + default: + os << "Unknown"; + break; + } + return os; +} + Application::Application(int &argc, char **argv) : m_app(std::make_shared(argc, argv)), m_videoFrameProvider(new VideoFrameProvider()), m_player(std::make_shared()), m_devices(new DeviceListModel(this)), m_collector(new DataCollection(this)) { + using namespace Amass; + + m_settings = Singleton::instance(); + m_settings->load(); + m_collector->setImageFormat(m_settings->imageFormat(), m_settings->imageQuality()); + QFont font; font.setPointSize(16); font.setFamily("微软雅黑"); diff --git a/Application.h b/Application.h index c2b41a4..814b273 100644 --- a/Application.h +++ b/Application.h @@ -12,6 +12,7 @@ class QGuiApplication; class VideoFrameProvider; +class Settings; class H264Palyer; class Application : public QObject { @@ -102,6 +103,7 @@ protected: private: std::shared_ptr m_app; VideoFrameProvider *m_videoFrameProvider = nullptr; + std::shared_ptr m_settings; std::shared_ptr m_player; std::weak_ptr m_device; DeviceListModel *m_devices = nullptr; diff --git a/CMakeLists.txt b/CMakeLists.txt index 9eed951..48ce31b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,7 @@ add_executable(AntiClipSettings DeviceListModel.h DeviceListModel.cpp H264Palyer.h H264Palyer.cpp VideoFrameProvider.h VideoFrameProvider.cpp + Settings.h Settings.cpp ) if (Qt6_FOUND) @@ -92,10 +93,12 @@ add_subdirectory(${Projects_ROOT}/Kylin/Encrypt Encrypt) target_include_directories(AntiClipSettings PRIVATE ${FFmpeg_INCLUDE_DIR} PRIVATE ${CMAKE_CURRENT_BINARY_DIR} + PRIVATE ${Libraries_ROOT}/libjpeg-turbo64/include ) target_link_directories(AntiClipSettings PRIVATE ${FFmpeg_LIB_DIR} + PRIVATE ${Libraries_ROOT}/libjpeg-turbo64/lib ) target_link_libraries(AntiClipSettings @@ -111,6 +114,7 @@ target_link_libraries(AntiClipSettings PRIVATE avformat PRIVATE Universal PRIVATE Encrypt + PRIVATE turbojpeg-static PRIVATE Ws2_32 ) diff --git a/DataCollection.cpp b/DataCollection.cpp index d59727b..6df478a 100644 --- a/DataCollection.cpp +++ b/DataCollection.cpp @@ -7,6 +7,7 @@ #include #include #include +#include DataCollection::DataCollection(QObject *parent) : QObject{parent} { m_manager = new QNetworkAccessManager(this); @@ -24,6 +25,15 @@ void DataCollection::stop() { emit enabledChanged(); } +void DataCollection::setImageFormat(ImageFormat format, int quality) { + if (m_format != format) { + m_format = format; + } + if (m_quality != quality) { + m_quality = quality; + } +} + QString DataCollection::path() const { return m_path; } @@ -78,13 +88,81 @@ void DataCollection::onCaptureFinished() { void DataCollection::onDataGetFinished() { auto reply = dynamic_cast(sender()); auto data = reply->readAll(); - LOG(info) << "error: " << reply->error() << ", capture data size: " << data.size(); - + if (m_format == ImageFormat::Jpeg) { + m_filename.replace("yuv", "jpg"); + } + LOG(info) << "filename: " << m_filename.toStdString() << ", error: " << reply->error() + << ", capture data size: " << data.size(); QFile file(m_path + "/" + m_filename); file.open(QIODevice::WriteOnly); - file.write(data); + if (m_format == ImageFormat::Jpeg) { + auto jpegBuffer = + encodeNv21ToJpeg(reinterpret_cast(data.data()), ImageWidth, ImageHeight, m_quality); + file.write(reinterpret_cast(jpegBuffer.data()), jpegBuffer.size()); + } else { + file.write(data); + } file.close(); if (m_enabled) { QTimer::singleShot(0, this, [this]() { start(); }); } } + +static void convertNV21ToYUV420(uint8_t *nv21, uint8_t *yuv420, int width, int height) { + int frameSize = width * height; + int chromaSize = frameSize / 4; + + memcpy(yuv420, nv21, frameSize); + + uint8_t *u = yuv420 + frameSize; + uint8_t *v = u + chromaSize; + + uint8_t *uv = nv21 + frameSize; + + for (int i = 0; i < chromaSize; i++) { + u[i] = uv[2 * i + 1]; + v[i] = uv[2 * i]; + } +} + +std::vector DataCollection::encodeNv21ToJpeg(uint8_t *nv21, int width, int height, int quality) { + tjhandle handle = tjInitCompress(); + if (handle == nullptr) { + fprintf(stderr, "Failed to initialize turbojpeg compressor\n"); + return {}; + } + + unsigned long jpeg_size = 0; + uint8_t *jpeg_buffer = nullptr; + + int yuv_size = tjBufSizeYUV2(width, 4, height, TJSAMP_420); + uint8_t *yuv_buffer = (uint8_t *)malloc(yuv_size); + if (yuv_buffer == nullptr) { + fprintf(stderr, "Failed to allocate YUV buffer\n"); + tjDestroy(handle); + return {}; + } + + // Convert NV21 to YUV420 + convertNV21ToYUV420(nv21, yuv_buffer, width, height); + + // Compress YUV to JPEG + int retval = tjCompressFromYUV(handle, yuv_buffer, width, 4, height, TJSAMP_420, &jpeg_buffer, &jpeg_size, quality, + TJFLAG_FASTDCT); + if (retval != 0) { + fprintf(stderr, "Failed to compress to JPEG: %s\n", tjGetErrorStr()); + free(yuv_buffer); + tjDestroy(handle); + return {}; + } + + // Copy the JPEG buffer into a std::vector + std::vector jpeg_vector(jpeg_buffer, jpeg_buffer + jpeg_size); + + // Free the JPEG buffer allocated by libjpeg-turbo + tjFree(jpeg_buffer); + free(yuv_buffer); + tjDestroy(handle); + + return jpeg_vector; +} diff --git a/DataCollection.h b/DataCollection.h index 031df27..67f38c9 100644 --- a/DataCollection.h +++ b/DataCollection.h @@ -1,6 +1,7 @@ #ifndef DATACOLLECTION_H #define DATACOLLECTION_H +#include "DataStructure.h" #include #include @@ -17,6 +18,7 @@ public: explicit DataCollection(QObject *parent = nullptr); Q_INVOKABLE void start(const QString &address); Q_INVOKABLE void stop(); + void setImageFormat(ImageFormat format, int quality); QString path() const; void setPath(const QString &path); @@ -31,6 +33,7 @@ protected: void start(); void onCaptureFinished(); void onDataGetFinished(); + static std::vector encodeNv21ToJpeg(uint8_t *nv21, int width, int height, int quality); private: QNetworkAccessManager *m_manager = nullptr; @@ -38,6 +41,9 @@ private: QString m_address; QString m_path; QString m_filename; + + ImageFormat m_format = ImageFormat::Jpeg; + int m_quality = 100; }; #endif // DATACOLLECTION_H diff --git a/DataStructure.h b/DataStructure.h index 8b500a9..0056698 100644 --- a/DataStructure.h +++ b/DataStructure.h @@ -4,6 +4,15 @@ #include #include +constexpr int ImageWidth = 576; +constexpr int ImageHeight = 320; +enum class ImageFormat : int { + Jpeg = 0, + YUV, // nv21 576x320 + None, // 新增枚举需要放在这上面 +}; +std::ostream &operator<<(std::ostream &os, const ImageFormat &format); + struct NetworkInfomation { Q_GADGET QML_NAMED_ELEMENT(networkInfomation) diff --git a/Settings.cpp b/Settings.cpp new file mode 100644 index 0000000..9ac3de3 --- /dev/null +++ b/Settings.cpp @@ -0,0 +1,50 @@ +#include "Settings.h" +#include "BoostLog.h" +#include +#include +#include + +constexpr auto SettingsFilePath = "settings.xml"; + +Settings::Settings() { + if (!std::filesystem::exists(SettingsFilePath)) { + save(); + } +} + +void Settings::save() { + boost::property_tree::ptree ptree; + ptree.put("Application.DataCollection.ImageFormat", static_cast(m_imageFormat)); + + std::ostringstream oss; + for (int i = 0; i < static_cast(ImageFormat::None); i++) { + oss << i << ":" << static_cast(i) << " "; + } + ptree.put("Application.DataCollection.ImageFormat.", oss.str()); + + ptree.put("Application.DataCollection.ImageQuality", m_imageQuality); + ptree.put("Application.DataCollection.ImageQuality.", "0-100,仅对jpg有效"); + + boost::property_tree::xml_writer_settings settings('\t', 1); + boost::property_tree::write_xml(SettingsFilePath, ptree, std::locale(), settings); +} + +void Settings::load() { + boost::property_tree::ptree ptree; + try { + boost::property_tree::read_xml(SettingsFilePath, ptree); + m_imageFormat = static_cast(ptree.get("Application.DataCollection.ImageFormat")); + m_imageQuality = ptree.get("Application.DataCollection.ImageQuality"); + } catch (...) { + LOG(error) << "parse " << SettingsFilePath << " failed."; + } + LOG(info) << "image format: " << m_imageFormat << ", quality: " << m_imageQuality; +} + +ImageFormat Settings::imageFormat() const { + return m_imageFormat; +} + +int Settings::imageQuality() const { + return m_imageQuality; +} diff --git a/Settings.h b/Settings.h new file mode 100644 index 0000000..3a9e27f --- /dev/null +++ b/Settings.h @@ -0,0 +1,24 @@ +#ifndef SETTINGS_H +#define SETTINGS_H + +#include "DataStructure.h" +#include "Singleton.h" + +class Settings { + friend class Amass::Singleton; + +public: + void save(); + void load(); + ImageFormat imageFormat() const; + int imageQuality() const; + +protected: + Settings(); + +private: + ImageFormat m_imageFormat = ImageFormat::Jpeg; // 0: jpg 1: yuv + int m_imageQuality = 100; +}; + +#endif // SETTINGS_H