commit c7b4e178ec9b9ca9b997deb942643bfe51b1481e Author: Pushuang <490688307@qq.com> Date: Mon Sep 28 21:33:04 2026 +0800 整理代码 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7da2826 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +**/.build +**/build +**/.vscode +**/.vs + +# 运行时生成:配置目录与日志 +config/ +logs/ +*.log + +*.o +*.a +*.exe +*.ilk +*.pdb diff --git a/Application.cpp b/Application.cpp new file mode 100644 index 0000000..ffbb3ea --- /dev/null +++ b/Application.cpp @@ -0,0 +1,308 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Application.h" +#ifdef USING_NETWORK_SUBSYSTEM +#include "NetworkSubsystem.h" +#endif +#ifdef USING_GRAPHICS_SUBSYSTEM +#include "GraphicsSubsystem.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hardware/BoardInfo.h" +#include "hardware/GPIO.h" +#include "hardware/HardwareSubsystem.h" +#include "util/HexBinaryCodec.h" +#include "util/SystemInfo.h" +#include "util/Temperature.h" + +/* + ******************************************************************************* + * CONSTANTS + ******************************************************************************* + */ +static const std::string CONFIG_ROOT_PATH = "/customer/conf"; +static const std::string ROOTCFG_FILENAME = CONFIG_ROOT_PATH + "/config.json"; +static const std::string DEFAULT_ROOTCFG_FILENAME = CONFIG_ROOT_PATH + "/config_default.json"; +const std::string VERSION_FILE = "/customer/VERSION"; +const std::string IMAGE_VERSION_FILE = "/etc/IMAGE_VERSION"; + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +const std::string Application::ROOT_CONFIG = "config"; + +void Application::initialize(Poco::Util::Application& self) +{ + _exitRequst = false; + initConfigAll(); + + Poco::Util::Application::initialize(self); +} + +void Application::uninitialize() +{ + Poco::Util::Application::uninitialize(); +} + +int Application::main(const std::vector& args) +{ + while (!_exitRequst) + Poco::Thread::sleep(1000); + + return EXIT_OK; +} + +std::string Application::getConfigFilePath(const std::string& label) +{ + Poco::Path path(getConfig(Application::ROOT_CONFIG).getString("buss_params_path")); + if (path.isRelative()) + path = path.makeAbsolute(Poco::Path::current()); + + return path.toString() + label + ".json"; +} + +void Application::initConfigAll() +{ + config().add(new Poco::Util::JSONConfiguration(ROOTCFG_FILENAME), ROOT_CONFIG, PRIO_SYSTEM, true); +} + +void Application::initConfig(const std::string& label) +{ + int retry = 0; + while (retry++ < 3) + { + try + { + config().add(new Poco::Util::JSONConfiguration(getConfigFilePath(label)), label, PRIO_APPLICATION, true); + break; + } + catch (Poco::Exception& e) + { + logger().error("Load config file '%s' FAIL: %s, USE DEFAULT CONFIG FILE INSTEAD!", label, e.displayText()); + Poco::File dFile(getConfigFilePath(label + "_default")); + if (dFile.exists()) + { + dFile.copyTo(getConfigFilePath(label)); + } + else + { + logger().error("Load config file '%s' FAIL: %s, DEFAULT CONFIG FILE IS NOT EXIST", label, + e.displayText()); + } + } + } +} + +std::string Application::version() const +{ + try + { + Poco::FileInputStream istr(VERSION_FILE); + std::string version; + istr >> version; + return version; + } + catch (...) + { + return "0.0.0"; + } +} + +Poco::Util::AbstractConfiguration& Application::getConfig() const +{ + return config(); +} + +Poco::Util::AbstractConfiguration& Application::getConfig(const std::string& label) const +{ + return findConfig(label); +} + +void Application::saveConfig(const std::string& label) +{ + logger().information("Saving config..."); + std::string tmpFilePath = "/tmp/" + label + ".json.tmp"; + std::string filePath = getConfigFilePath(label); + + Poco::FileOutputStream fost(tmpFilePath); + dumpConfig(label, fost); + fost.flush(); + fost.close(); + + try + { + Poco::File(tmpFilePath).moveTo(filePath); + logger().information("Config saved successfully."); + } + catch (Poco::Exception& e) + { + logger().error("Save config file ERROR: %s", e.displayText()); + } +} + +void Application::loadConfig(const std::string& label, std::istream& istr) +{ + auto& config = findConfig(label); + config.load(istr); + saveConfig(label); + ConfigChanged.notifyAsync(this, label); +} + +void Application::loadConfig(const std::string& label, const std::string& str) +{ + std::istringstream istr(str); + loadConfig(label, istr); +} + +void Application::dumpConfig(const std::string& label, std::ostream& os) +{ + auto& config = findConfig(label); + config.save(os); +} + +void Application::configNotifyAsync(const std::string& label) +{ + ConfigChanged.notifyAsync(this, label); +} + +void Application::restoreFactorySettings() +{ + try + { + printf("Restore system factory settings...\r\n"); + system("/customer/reset-key -Pweb"); + } + catch (...) + { + } + + reboot(2000); +} + +void Application::reboot(uint32_t delay) +{ + _delayRebootThread.startFunc([=]() -> void { + logger().warning("Reboot after %u ms ...", delay); + Poco::Thread::sleep(delay); + + exit(); + ::sync(); + + if (system("reboot") != 0) + logger().error("System reboot command failed!"); + + ::reboot(RB_AUTOBOOT); + }); +} + +void Application::setSystemTime(uint32_t timestamp, int timezone) +{ + struct timeval tv; + + tv.tv_sec = timestamp; + tv.tv_usec = 0; + settimeofday(&tv, NULL); + + try + { + std::string cmd = ""; + if (timezone >= 0) + { + cmd = Poco::format("ln -snf /customer/zoneinfo/Etc/GMT-%d /etc/localtime", timezone); + } + else + { + cmd = Poco::format("ln -snf /customer/zoneinfo/Etc/GMT+%d /etc/localtime", -1 * timezone); + } + system(cmd.c_str()); + } + catch (const std::exception& e) + { + std::cerr << e.what() << '\n'; + } + system("hwclock -w -l"); +} + +void Application::exit() +{ + ApplicationExitNotify.notify(this); + _exitRequst = true; +} + +void Application::upgrade(const std::string& file) +{ +#if defined(FIRMWARE_UPGRADE_FROM_BACKUP_PARTITION) + Poco::File("/customer/DIRTY").createFile(); + logger().warning("Reboot to upgrade firmware ...."); +#else + + std::string specCharacters[] = { + "\\", " ", "\"", "'", "(", ")", "*", "!", "$", "%", "&", ">", "<", "|", "`", + }; + + for (auto& c : specCharacters) + file = Poco::replace(file, c, "\\" + c); + + Poco::Pipe inPipe, outPipe; + Poco::Process::Args args; + Poco::ProcessHandle handle = Poco::Process::launch("sh", args, &inPipe, &outPipe, 0); + + Poco::PipeOutputStream outputStream(inPipe); + std::string tmpdir = "/tmp/upgrade"; + outputStream << Poco::format("mkdir -p %s", tmpdir) << std::endl; + outputStream << Poco::format("tar xf %s -C %s", file, tmpdir) << std::endl; + outputStream << Poco::format("%s/upgrade.sh", tmpdir) << std::endl; + outputStream.flush(); + outputStream.close(); + + handle.wait(); + + Poco::PipeInputStream inputStream(outPipe); + std::string str; + Poco::StreamCopier::copyToString(inputStream, str); + logger().information("Firmware upgrade complete, reboot...."); +#endif + reboot(2000); +} diff --git a/Application.h b/Application.h new file mode 100644 index 0000000..15469d7 --- /dev/null +++ b/Application.h @@ -0,0 +1,115 @@ +#ifndef C7F1A9D3_4E28_4B60_9C15_8A3E6D2F0B94 +#define C7F1A9D3_4E28_4B60_9C15_8A3E6D2F0B94 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "util/JsonUtil.h" + + +/* + ******************************************************************************* + * CONSTANTS + ******************************************************************************* + */ +#define APPLICATION_VERSION application().version() + + +/* + ******************************************************************************* + * MACROS + ******************************************************************************* + */ +#define application() (*(Application::getInstance())) + + + +/* + ******************************************************************************* + * TYPEDEFS + ******************************************************************************* + */ +using ConfigurationPtr = Poco::Util::AbstractConfiguration::Ptr; +using Configuration = Poco::Util::AbstractConfiguration; + + +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Application: public Poco::Util::Application +{ +public: + struct SystemStatus + { + }; + +public: + static const std::string ROOT_CONFIG; + + Poco::BasicEvent ConfigChanged; + Poco::BasicEvent ApplicationExitNotify; + +protected: + void initialize(Poco::Util::Application& self) override; + void uninitialize() override; + int main(const std::vector& args) override; + + std::string getConfigFilePath(const std::string& label); + + template T& findConfig(const std::string& label) const + { + return *static_cast(config().find(label).get()); + } + + void initConfigAll(); + void initConfig(const std::string& label); + +public: + std::string version() const; + Poco::Util::AbstractConfiguration& getConfig() const; + Poco::Util::AbstractConfiguration& getConfig(const std::string& label) const; + + void saveConfig(const std::string& label); + + void loadConfig(const std::string& label, std::istream& istr); + + void loadConfig(const std::string& label, const std::string& istr); + + void dumpConfig(const std::string& label, std::ostream& ostr); + + void configNotifyAsync(const std::string& label); + + void restoreFactorySettings(); + + void reboot(uint32_t delay = 2000); + + void setSystemTime(uint32_t timestamp, int timezone = 8); + + void exit(); + + void upgrade(const std::string& file); + + static Application* getInstance() + { + Poco::Util::Application& app = instance(); + return static_cast(&app); + } + +private: + Poco::Thread _delayRebootThread; + std::atomic _exitRequst; +}; + +#endif diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..34088f1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,162 @@ +cmake_minimum_required(VERSION 3.16) +set(PROJNAME encoder) +include(config.cmake) +include(FetchContent) + +project(${PROJNAME}) + +set(ARCH arm) + +if (USING_HISIMPP5) + add_compile_definitions( + -DUSING_HISIMPP5 + ) +endif() + +add_compile_definitions( + -DFIRMWARE_UPGRADE_FROM_BACKUP_PARTITION +) + +execute_process( + COMMAND git log -1 --format=%h + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_COMMIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +add_compile_definitions(-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\") + +# 未定义或为空 +if (NOT DEFINED CMAKE_BUILD_TYPE OR NOT CMAKE_BUILD_TYPE) + set (CMAKE_BUILD_TYPE Debug) +endif() + +message("Project \"${GIT_COMMIT_HASH}\" build type: " ${CMAKE_BUILD_TYPE}) + +if (CMAKE_BUILD_TYPE MATCHES Debug) + add_compile_definitions(-DDEBUG) +endif() + +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + set(Poco_DIR $ENV{POCO_PATH}/lib/cmake/Poco) + link_libraries( + pthread + dl + m + atomic + ) + if (CMAKE_BUILD_TYPE MATCHES Debug) + # 定义Debug编译参数 + SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -Og -Wall -g3 -Wno-psabi -std=c++1z -mfpu=neon -mfloat-abi=softfp") # GDB调试 + # SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -Og -Wall -g3 -Wno-psabi -std=c++1z -mfpu=neon -mfloat-abi=softfp -fsanitize=address -fno-eliminate-unused-debug-types -fno-omit-frame-pointer") # 内存检测 + # SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -Wno-psabi") + # SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -Wno-psabi -rdynamic -funwind-tables -ffunction-sections") # 可以在应用里捕获SIGSEGV信号,然后用backtrace()函数把堆栈信息打印出来 + else() + # 定义Release编译参数 + SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall -Wno-psabi -mfpu=neon -mfloat-abi=softfp") + endif() + add_compile_definitions(-DOS_LINUX) +elseif (CMAKE_SYSTEM_NAME MATCHES "Windows") + set(Poco_DIR $ENV{POCO_PATH}/cmake) + # 强制Visual Studio使用UTF-8编码 + add_compile_options("$<$:/utf-8>") + add_compile_options("$<$:/utf-8>") + # 忽略部分警告 + add_compile_definitions(/wd"4828") + # 使用Unicode字符集 + add_compile_definitions(-DUNICODE -D_UNICODE) + if (USING_LVGL_PLATFORM OR USING_SOUI_PLATFORM) + # 设置为窗口程序 + set(GUI_EXECUTABLE WIN32) + add_compile_definitions(-DWINDOWS_GUI_APPLICATION) + endif() + link_directories($ENV{POCO_PATH}/lib) +endif() + +find_package(Poco REQUIRED COMPONENTS Foundation Net XML Encodings Util Zip Crypto) +include_directories( + . + ${Poco_LIBRARIES} + $ENV{JSONC_PATH}/include + $ENV{CRYPTOAUTHLIB_PATH}/include/cryptoauthlib + $ENV{OPENSSL_PATH}/include + $ENV{PAHO_MQTTC_PATH}/include +) +link_libraries( + ${Poco_LIBRARIES} + $ENV{CRYPTOAUTHLIB_PATH}/lib/libcryptoauth.a + $ENV{OPENSSL_PATH}/lib/libssl.a + $ENV{OPENSSL_PATH}/lib/libcrypto.a + #$ENV{PAHO_MQTTC_PATH}/lib/libpaho-mqtt3a.a + #$ENV{PAHO_MQTTC_PATH}/lib/libpaho-mqtt3as.a + $ENV{PAHO_MQTTC_PATH}/lib/libpaho-mqtt3c.a + #$ENV{PAHO_MQTTC_PATH}/lib/libpaho-mqtt3cs.a +) + +if (USING_HISIMPP5) + include_directories( + $ENV{MPP_PATH}/include + ) + + link_libraries( + $ENV{MPP_PATH}/lib/libmpi.a + $ENV{MPP_PATH}/lib/libdnvqe.a + $ENV{MPP_PATH}/lib/libupvqe.a + $ENV{MPP_PATH}/lib/libvqe_res.a + $ENV{MPP_PATH}/lib/libVoiceEngine.a + $ENV{MPP_PATH}/lib/libsecurec.so + $ENV{MPP_PATH}/lib/libhdmi.a + $ENV{MPP_PATH}/lib/libtde.a + ) +endif() + + +aux_source_directory(. MAIN_SOURCES) +add_executable(${PROJNAME} ${GUI_EXECUTABLE} ${MAIN_SOURCES}) + +# target_compile_options(${PROJNAME} PRIVATE +# -fsanitize=address,undefined +# -fsanitize=float-divide-by-zero +# -fsanitize=null +# -fsanitize=bounds-strict +# ) + +# target_link_options(${PROJNAME} PRIVATE +# -fsanitize=address,undefined +# ) + +if (CMAKE_SYSTEM_NAME MATCHES "Windows") + SET_PROPERTY (DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJNAME} ) +endif() + +if (USING_VIDEO_SUBSYSTEM) + add_compile_definitions(-DUSING_VIDEO_SUBSYSTEM) +endif() + +# 网络 +if (USING_NETWORK_SUBSYSTEM) + add_compile_definitions(-DUSING_NETWORK_SUBSYSTEM) +endif() + +# 板级设备 +if (USING_HARDWARE_SUBSYSTEM) + add_compile_definitions(-DUSING_HARDWARE_SUBSYSTEM) +endif() + +if (USING_VIDEO_SUBSYSTEM) + include(video/video.cmake) +endif() + +# 网络 +if (USING_NETWORK_SUBSYSTEM) + include(network/network.cmake) +endif() + +# 板级设备 +if (USING_HARDWARE_SUBSYSTEM) + include(hardware/hardware.cmake) +endif() + +# Util +include(util/util.cmake) + diff --git a/Logger.cpp b/Logger.cpp new file mode 100644 index 0000000..a0f107b --- /dev/null +++ b/Logger.cpp @@ -0,0 +1,6 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Logger.h" \ No newline at end of file diff --git a/Logger.h b/Logger.h new file mode 100644 index 0000000..cc179a6 --- /dev/null +++ b/Logger.h @@ -0,0 +1,16 @@ +#ifndef E4B8FD4A_9AEF_4A23_9C62_8D091F7306FE +#define E4B8FD4A_9AEF_4A23_9C62_8D091F7306FE +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Application.h" + +/* + ******************************************************************************* + * MACROS + ******************************************************************************* + */ +#define logger() (application().logger()) +#endif diff --git a/config.cmake b/config.cmake new file mode 100644 index 0000000..1a7aac3 --- /dev/null +++ b/config.cmake @@ -0,0 +1,32 @@ +message(BOARD..${BOARD}) +option(USING_HARDWARE_SUBSYSTEM "Using Hardware subsystem" ON) +option(USING_HISIMPP5 "Using Hisilicon MPP5" ON) + +# Video +option(USING_VIDEO_SUBSYSTEM "Using Video subsystem" ON) +option(USING_FFMPEG "Using ffmpeg library" ON) +option(USING_MEDIA_FRAMEWORK "Using Media-Framework" ON) +option(USING_ZLMEDIAKIT "Using ZLMediaKit" ON) +option(USING_ONVIF "Using ONVIF" ON) +option(USING_ITE_FRONTEND "Using ITE IT66021" ON) +option(USING_SEMTECH_DEVICE "Using SEMTECH device" OFF) +option(USING_MEDIASERVER "Using media-server" ON) +option(USING_LIBOPUS "Using libopus" ON) +option(USING_SHINE "Using shine MP3" ON) + +# Serialization +option(USING_PROTOBUF "Using Google's data interchange format" OFF) + +# Network +option(USING_NETWORK_SUBSYSTEM "Using Network subsystem" ON) +option(USING_TCPIP "Using TCP/IP protocol" ON) + +# Licence +option(USING_LICENCE_FROM_LOCAL_ENABLE "Using Licence form local enable" ON) + +set(CMAKE_C_COMPILER $ENV{GCC_PATH}/$ENV{GCC_PREFIX}gcc) +set(CMAKE_CXX_COMPILER $ENV{GCC_PATH}/$ENV{GCC_PREFIX}g++) + +set(CMAKE_BUILD_TYPE Release) +# set(CMAKE_BUILD_TYPE Debug) + diff --git a/hardware/BoardInfo.cpp b/hardware/BoardInfo.cpp new file mode 100644 index 0000000..05e476a --- /dev/null +++ b/hardware/BoardInfo.cpp @@ -0,0 +1,28 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "BoardInfo.h" + +namespace Hardware +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Board::Ptr BoardInfo::_pBoard = nullptr; + +Board::~Board() +{ +} + +std::string BoardInfo::serialNumber() +{ + if (_pBoard.isNull()) + return "NOT IMPLEMENTED"; + + return _pBoard->serialNumber(); +} +} diff --git a/hardware/BoardInfo.h b/hardware/BoardInfo.h new file mode 100644 index 0000000..3d8ef2d --- /dev/null +++ b/hardware/BoardInfo.h @@ -0,0 +1,45 @@ +#ifndef FD5BB51A_6CF1_4A59_B157_25E320495F52 +#define FD5BB51A_6CF1_4A59_B157_25E320495F52 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include + +namespace Hardware +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Board +{ +public: + using Ptr = Poco::SharedPtr; + +public: + ~Board(); + +public: + virtual std::string serialNumber() = 0; +}; + +class BoardInfo +{ +public: + static void registerBoard(Board::Ptr pBoard); + + static std::string serialNumber(); + +private: + static Board::Ptr _pBoard; +}; + +inline void BoardInfo::registerBoard(Board::Ptr pBoard) +{ + _pBoard = pBoard; +} +} +#endif diff --git a/hardware/CMakeLists.txt b/hardware/CMakeLists.txt new file mode 100644 index 0000000..c81662f --- /dev/null +++ b/hardware/CMakeLists.txt @@ -0,0 +1,29 @@ +set(TARGET hardware) + +aux_source_directory(. HARDWARE_SRCS) +aux_source_directory(user USER_SOURCES) + + +if (USING_HISIMPP5) + aux_source_directory(hisilicon HISI_SOURCES) + list(APPEND HARDWARE_SRCS ${HISI_SOURCES}) + include_directories($ENV{MPP_PATH}/include) +endif() + +if (USING_ITE_DEVICE) + message("Using ITE device ...") + aux_source_directory(ite ITE_SOURCES) + list(APPEND HARDWARE_SRCS ${ITE_SOURCES}) +endif() + +if (USING_SEMTECH_DEVICE) + message("Using SEMTECH device ...") + aux_source_directory(semtech SEMTECH_SOURCES) + list(APPEND HARDWARE_SRCS ${SEMTECH_SOURCES}) +endif() + +add_library(${TARGET} STATIC + ${HARDWARE_SRCS} + ${USER_SOURCES} +) + diff --git a/hardware/GPIO.cpp b/hardware/GPIO.cpp new file mode 100644 index 0000000..097127e --- /dev/null +++ b/hardware/GPIO.cpp @@ -0,0 +1,81 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "GPIO.h" +#include +#include +#include +#include +#include + +namespace Hardware +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ + +GPIO::GPIO(int pinno): _pinno(pinno) +{ + std::string s = std::to_string(_pinno); + writeFile("/sys/class/gpio/export", s.c_str(), s.length()); + _fd = ::open(Poco::format("/sys/class/gpio/gpio%d/value", _pinno).c_str(), O_RDWR); + if (_fd < 0) + throw Poco::InvalidAccessException("Open FAIL"); +} + +GPIO::~GPIO() +{ + ::close(_fd); + std::string s = std::to_string(_pinno); + writeFile("/sys/class/gpio/unexport", s.c_str(), s.length()); +} + +void GPIO::direction(Direction dir) +{ + switch (dir) + { + case Direction::OUT: + default: + writeDirection("out"); + break; + + case Direction::IN: + writeDirection("in"); + break; + } +} + +void GPIO::write(unsigned level) +{ + if (level) + ::write(_fd, "1", 1); + else + ::write(_fd, "0", 1); +} + +unsigned GPIO::read() +{ + char value; + ::read(_fd, &value, 1); + return value != '0'; +} + +void GPIO::writeFile(const std::string& path, const void* pData, int len) +{ + int fd = ::open(path.c_str(), O_WRONLY); + if (fd < 0) + throw Poco::InvalidAccessException("Open FAIL"); + + ::write(fd, pData, len); + ::close(fd); +} + +void GPIO::writeDirection(const std::string& value) +{ + writeFile(Poco::format("/sys/class/gpio/gpio%d/direction", _pinno), value.c_str(), value.length()); +} +} diff --git a/hardware/GPIO.h b/hardware/GPIO.h new file mode 100644 index 0000000..a6e80b3 --- /dev/null +++ b/hardware/GPIO.h @@ -0,0 +1,44 @@ +#ifndef C88B7A31_D4F3_4E43_B4CE_45003C99E915 +#define C88B7A31_D4F3_4E43_B4CE_45003C99E915 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include + +namespace Hardware +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class GPIO +{ +public: + enum class Direction + { + IN, + OUT, + }; + +public: + GPIO(int pinno); + ~GPIO(); + +public: + void direction(Direction dir); + void write(unsigned level); + unsigned read(); + +private: + void writeFile(const std::string& path, const void* pData, int len); + void writeDirection(const std::string& value); + +private: + int _fd; + int _pinno; +}; +} +#endif diff --git a/hardware/HardwareSubsystem.cpp b/hardware/HardwareSubsystem.cpp new file mode 100644 index 0000000..8420f00 --- /dev/null +++ b/hardware/HardwareSubsystem.cpp @@ -0,0 +1,35 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "HardwareSubsystem.h" +#include "BoardInfo.h" +#include "hisilicon/Board.h" +#include "Sensor.h" +#include "hisilicon/TemperatureSensor.h" + + +namespace Hardware +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +const char* HardwareSubsystem::name() const +{ + return "Hardware"; +} + +void HardwareSubsystem::initialize(Poco::Util::Application& app) +{ + BoardInfo::registerBoard(new Hisilicon::Board()); + SensorManager::registerSensor(Sensor::Type::TEMPERATURE, new Hisilicon::TemperatureSensor()); +} + +void HardwareSubsystem::uninitialize() +{ + stopAll(); +} +} diff --git a/hardware/HardwareSubsystem.h b/hardware/HardwareSubsystem.h new file mode 100644 index 0000000..b8d58b4 --- /dev/null +++ b/hardware/HardwareSubsystem.h @@ -0,0 +1,30 @@ +#ifndef CEE64BF6_3FB0_4B34_9E98_BCFE7C712DA1 +#define CEE64BF6_3FB0_4B34_9E98_BCFE7C712DA1 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Poco/Util/Application.h" +#include "Poco/Util/Subsystem.h" +#include "Application.h" +#include "util/Subsystem.h" + +namespace Hardware +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class HardwareSubsystem: public Util::Subsystem +{ +public: + virtual const char* name() const override; + +protected: + virtual void initialize(Poco::Util::Application& app) override; + virtual void uninitialize(); +}; +} +#endif diff --git a/hardware/SPI.cpp b/hardware/SPI.cpp new file mode 100644 index 0000000..f88d2e2 --- /dev/null +++ b/hardware/SPI.cpp @@ -0,0 +1,73 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "SPI.h" +#include +#include +#include +#include +#include +#include +#include + +namespace Hardware +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +const uint8_t SPI::MODE_0 = SPI_MODE_0; +const uint8_t SPI::MODE_1 = SPI_MODE_1; +const uint8_t SPI::MODE_2 = SPI_MODE_2; +const uint8_t SPI::MODE_3 = SPI_MODE_3; + +SPI::SPI(int bus, int cs) +{ + _fd = open(Poco::format("/dev/spidev%d.%d", bus, cs).c_str(), O_RDWR); + if (_fd < 0) + throw Poco::IOException("SPI: Open device FAIL"); +} + +SPI::~SPI() +{ + close(_fd); +} + +int SPI::setSpeed(uint32_t speed) +{ + return ioctl(_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); +} + +int SPI::setMode(uint8_t mode) +{ + return ioctl(_fd, SPI_IOC_WR_MODE, &mode); +} + +int SPI::setBitsPerWord(uint8_t bits) +{ + return ioctl(_fd, SPI_IOC_WR_BITS_PER_WORD, &bits); +} + +int SPI::setLSBFirst(bool enable) +{ + uint8_t lsb = enable ? 1 : 0; + return ioctl(_fd, SPI_IOC_WR_LSB_FIRST, &lsb); +} + +int SPI::writeThenRead(const uint8_t* tx, int txlen, uint8_t* rx, int rxlen) +{ + struct spi_ioc_transfer trans[2] = {{0}, {0}}; + + trans[0].tx_buf = (unsigned long)tx; + trans[0].rx_buf = 0; + trans[0].len = txlen; + + trans[1].tx_buf = 0; + trans[1].rx_buf = (unsigned long)rx; + trans[1].len = rxlen; + return ioctl(_fd, SPI_IOC_MESSAGE(2), trans); +} +} diff --git a/hardware/SPI.h b/hardware/SPI.h new file mode 100644 index 0000000..146827e --- /dev/null +++ b/hardware/SPI.h @@ -0,0 +1,41 @@ +#ifndef C9ED230E_05FF_4E41_87B9_664DF1FD835C +#define C9ED230E_05FF_4E41_87B9_664DF1FD835C +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include + +namespace Hardware +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class SPI +{ +public: + static const uint8_t MODE_0; + static const uint8_t MODE_1; + static const uint8_t MODE_2; + static const uint8_t MODE_3; + +public: + SPI(int bus, int cs); + ~SPI(); + +public: + int setSpeed(uint32_t speed); + int setMode(uint8_t mode); + int setBitsPerWord(uint8_t bits); + int setLSBFirst(bool enable); + + int writeThenRead(const uint8_t* tx, int txlen, uint8_t* rx, int rxlen); + +private: + int _fd; +}; +} +#endif diff --git a/hardware/Sensor.cpp b/hardware/Sensor.cpp new file mode 100644 index 0000000..16b7dd2 --- /dev/null +++ b/hardware/Sensor.cpp @@ -0,0 +1,47 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Sensor.h" + +namespace Hardware +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Sensor::Sensor(Type type): _type(type) +{ +} + +Sensor::~Sensor() +{ +} + +void Sensor::read(float& value) +{ +} + +void Sensor::read(float values[]) +{ +} + +Sensor::Type Sensor::type() +{ + return _type; +} + +SensorManager::SensorCollection SensorManager::_sensorCollection; + +Sensor& SensorManager::getDefaultSensor(Sensor::Type type) +{ + return *_sensorCollection[type][0]; +} + +void SensorManager::registerSensor(Sensor::Type type, Sensor::Ptr pSensor) +{ + _sensorCollection[type].push_back(pSensor); +} +} diff --git a/hardware/Sensor.h b/hardware/Sensor.h new file mode 100644 index 0000000..a742192 --- /dev/null +++ b/hardware/Sensor.h @@ -0,0 +1,57 @@ +#ifndef F49794A5_C3E3_4955_B9B2_6BAA267D3436 +#define F49794A5_C3E3_4955_B9B2_6BAA267D3436 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include +#include + +namespace Hardware +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Sensor +{ +public: + using Ptr = Poco::SharedPtr; + + enum Type + { + TEMPERATURE, + }; + +public: + Sensor(Type type); + virtual ~Sensor(); + +public: + virtual void read(float& value); + virtual void read(float values[]); + + Type type(); + +private: + Type _type; +}; + +class SensorManager +{ +public: + static Sensor& getDefaultSensor(Sensor::Type type); + static void registerSensor(Sensor::Type type, Sensor::Ptr pSensor); + +private: + using SensorList = std::vector; + using SensorCollection = std::map; + +private: + static SensorCollection _sensorCollection; +}; +} +#endif diff --git a/hardware/SerialPort.cpp b/hardware/SerialPort.cpp new file mode 100644 index 0000000..d4699b0 --- /dev/null +++ b/hardware/SerialPort.cpp @@ -0,0 +1,313 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "SerialPort.h" +#include +#include +#include +#include +#include +#include + +namespace Hardware +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +SerialPort::SerialPort(): _fd(-1) +{ +} + +SerialPort::~SerialPort() +{ + if (isOpen()) + close(); +} + +void SerialPort::open() +{ + if (isOpen()) + return; + + if (_path.empty()) + throw Poco::InvalidArgumentException("Empty path"); + + struct termios options = {0}; + options.c_cflag |= CLOCAL | getBaudRateFlag(_baudRate); + switch (_dataBits) + { + case 5: + options.c_cflag |= CS5; + break; + + case 6: + options.c_cflag |= CS6; + break; + + case 7: + options.c_cflag |= CS7; + break; + + case 8: + options.c_cflag |= CS8; + break; + + default: + throw Poco::InvalidArgumentException(Poco::format("Invalid data bits: %d", _dataBits)); + } + + switch (_stopBits) + { + case 1: + options.c_cflag &= ~CSTOPB; + break; + + case 2: + options.c_cflag |= CSTOPB; + break; + + default: + case 3: + case 4: + throw Poco::InvalidArgumentException(Poco::format("Invalid stop bits: %d", _stopBits)); + } + + switch (_parity) + { + default: + case Parity::NONE: + options.c_cflag &= ~PARENB; + options.c_iflag &= ~INPCK; + break; + + case Parity::ODD: + options.c_cflag |= PARENB; + options.c_cflag |= PARODD; + options.c_iflag |= INPCK; + options.c_iflag |= ISTRIP; + break; + + case Parity::EVEN: + options.c_cflag |= PARENB; + options.c_cflag &= ~PARODD; + options.c_iflag |= INPCK; + options.c_iflag |= ISTRIP; + break; + } + + options.c_cflag |= CREAD; + + switch (_flowControl) + { + default: + case FlowControl::NONE: + options.c_cflag &= ~CRTSCTS; + break; + + case FlowControl::RTSCTS: + options.c_cflag |= CRTSCTS; + break; + } + + _fd = ::open(_path.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); + if (_fd < 0) + throw Poco::InvalidAccessException(Poco::format("Open '%s' FAIL: %d", _path, _fd)); + + tcflush(_fd, TCIOFLUSH); + cfmakeraw(&options); + tcsetattr(_fd, TCSANOW, &options); +} + +void SerialPort::close() +{ + if (!isOpen()) + return; + + ::close(_fd); + _fd = -1; +} + +bool SerialPort::isOpen() +{ + return _fd != -1; +} + +int SerialPort::write(const void* pData, uint32_t size) +{ + if (!isOpen()) + return 0; + + return ::write(_fd, pData, size); +} + +int SerialPort::read(void* pBuffer, uint32_t length, uint32_t timeout) +{ + if (!isOpen()) + return 0; + + struct timeval tm; + tm.tv_sec = timeout / 1000; + tm.tv_usec = (timeout % 1000) * 1000; + + fd_set reads; + FD_ZERO(&reads); + FD_SET(_fd, &reads); + + int rc = ::select(_fd + 1, &reads, NULL, NULL, &tm); + if (rc < 0) + return 0; + + if (FD_ISSET(_fd, &reads)) + return ::read(_fd, pBuffer, length); + + return 0; +} + +SerialPort& SerialPort::setPath(const std::string& path) +{ + _path = path; + return *this; +} + +SerialPort& SerialPort::setBaudRate(uint32_t baudRate) +{ + _baudRate = baudRate; + return *this; +} + +SerialPort& SerialPort::setParity(Parity parity) +{ + _parity = parity; + return *this; +} + +SerialPort& SerialPort::setDataBits(uint32_t bits) +{ + _dataBits = bits; + return *this; +} + +SerialPort& SerialPort::setStopBits(uint32_t bits) +{ + _stopBits = bits; + return *this; +} + +SerialPort& SerialPort::setFlowControl(FlowControl flowControl) +{ + _flowControl = flowControl; + return *this; +} + +uint32_t SerialPort::getBaudRateFlag(uint32_t br) +{ + uint32_t flag; + + switch (br) + { + case 0: + flag = B0; + break; + case 50: + flag = B50; + break; + case 75: + flag = B75; + break; + case 110: + flag = B110; + break; + case 134: + flag = B134; + break; + case 150: + flag = B150; + break; + case 200: + flag = B200; + break; + case 300: + flag = B300; + break; + case 600: + flag = B600; + break; + case 1200: + flag = B1200; + break; + case 1800: + flag = B1800; + break; + case 2400: + flag = B2400; + break; + case 4800: + flag = B4800; + break; + case 9600: + flag = B9600; + break; + case 19200: + flag = B19200; + break; + case 38400: + flag = B38400; + break; + case 57600: + flag = B57600; + break; + case 115200: + flag = B115200; + break; + case 230400: + flag = B230400; + break; + case 460800: + flag = B460800; + break; + case 500000: + flag = B500000; + break; + case 576000: + flag = B576000; + break; + case 921600: + flag = B921600; + break; + case 1000000: + flag = B1000000; + break; + case 1152000: + flag = B1152000; + break; + case 1500000: + flag = B1500000; + break; + case 2000000: + flag = B2000000; + break; + case 2500000: + flag = B2500000; + break; + case 3000000: + flag = B3000000; + break; + case 3500000: + flag = B3500000; + break; + case 4000000: + flag = B4000000; + break; + + default: + flag = B9600; + break; + } + + return flag; +} +} diff --git a/hardware/SerialPort.h b/hardware/SerialPort.h new file mode 100644 index 0000000..58b1c9e --- /dev/null +++ b/hardware/SerialPort.h @@ -0,0 +1,101 @@ +#ifndef FA788814_8B75_424F_99DB_D04B4977DD8C +#define FA788814_8B75_424F_99DB_D04B4977DD8C +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include + +namespace Hardware +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class SerialPort +{ +public: + enum class Parity + { + NONE, + ODD, + EVEN, + }; + + enum class FlowControl + { + NONE, + RTSCTS, + }; + +public: + SerialPort(); + ~SerialPort(); + +public: + SerialPort& setPath(const std::string& path); + SerialPort& setBaudRate(uint32_t baudRate); + SerialPort& setParity(Parity parity); + SerialPort& setDataBits(uint32_t bits); + SerialPort& setStopBits(uint32_t bits); + SerialPort& setFlowControl(FlowControl flowControl); + void open(); + void close(); + bool isOpen(); + int write(const void* pData, uint32_t size); + int read(void* pBuffer, uint32_t length, uint32_t timeout); + + const std::string& getPath(); + uint32_t getBaudRate(); + Parity getParity(); + uint32_t getDataBits(); + uint32_t getStopBits(); + FlowControl getFlowControl(); + +protected: + uint32_t getBaudRateFlag(uint32_t baudRate); + +private: + std::string _path; + int _fd; + uint32_t _baudRate; + Parity _parity; + uint32_t _dataBits; + uint32_t _stopBits; + FlowControl _flowControl; +}; + +inline const std::string& SerialPort::getPath() +{ + return _path; +} + +inline uint32_t SerialPort::getBaudRate() +{ + return _baudRate; +} + +inline SerialPort::Parity SerialPort::getParity() +{ + return _parity; +} + +inline uint32_t SerialPort::getDataBits() +{ + return _dataBits; +} + +inline uint32_t SerialPort::getStopBits() +{ + return _stopBits; +} + +inline SerialPort::FlowControl SerialPort::getFlowControl() +{ + return _flowControl; +} +} +#endif diff --git a/hardware/hardware.cmake b/hardware/hardware.cmake new file mode 100644 index 0000000..849ce30 --- /dev/null +++ b/hardware/hardware.cmake @@ -0,0 +1,3 @@ +add_subdirectory(hardware) +include_directories(hardware) +target_link_libraries(${PROJNAME} PRIVATE hardware) diff --git a/hardware/hisilicon/Board.cpp b/hardware/hisilicon/Board.cpp new file mode 100644 index 0000000..e8e89a0 --- /dev/null +++ b/hardware/hisilicon/Board.cpp @@ -0,0 +1,42 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../BoardInfo.h" +#include "Board.h" +#include +#include +#include +#include + +namespace Hardware +{ +namespace Hisilicon +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Board::Board() +{ + hi_unique_id id; + hi_mpi_sys_get_unique_id(&id); + for (size_t i = 0; i < HI_UNIQUE_ID_NUM; i++) + { + std::cout << "#" << i << ": " << Poco::format("%08x", id.id[i]) << std::endl; + } + + Poco::MD5Engine md5; + md5.update(id.id, sizeof(id.id)); + _serialNumber = Poco::DigestEngine::digestToHex(md5.digest()); + std::cout << "Serial Number: " << _serialNumber << std::endl; +} + +std::string Hisilicon::Board::serialNumber() +{ + return _serialNumber; +} +} +} diff --git a/hardware/hisilicon/Board.h b/hardware/hisilicon/Board.h new file mode 100644 index 0000000..70f3f85 --- /dev/null +++ b/hardware/hisilicon/Board.h @@ -0,0 +1,33 @@ +#ifndef A6A48FAD_A41E_4AF4_A92C_A3CFE3D065B1 +#define A6A48FAD_A41E_4AF4_A92C_A3CFE3D065B1 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../BoardInfo.h" + +namespace Hardware +{ +namespace Hisilicon +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Board: public Hardware::Board +{ +public: + Board(); + ~Board(); + +public: + virtual std::string serialNumber() override; + +private: + std::string _serialNumber; +}; +} +} +#endif diff --git a/hardware/hisilicon/TemperatureSensor.cpp b/hardware/hisilicon/TemperatureSensor.cpp new file mode 100644 index 0000000..1b1a70b --- /dev/null +++ b/hardware/hisilicon/TemperatureSensor.cpp @@ -0,0 +1,51 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "TemperatureSensor.h" +#include "memmap.h" + +namespace Hardware +{ +namespace Hisilicon +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +#define TSENSOR_CTRL_REG_BASE 0x1102A000 + +TemperatureSensor::TemperatureSensor(): Sensor(Sensor::Type::TEMPERATURE) +{ + _pRegisters = getRegisters(); + + _pRegisters->ctrl0 |= (1 << 31) | (1 << 30); + + _pRegisters->ctrl1 |= (3 << 2); +} + +TemperatureSensor::~TemperatureSensor() +{ + _pRegisters->ctrl0 = 0; + releaseRegisters(_pRegisters); +} + +void TemperatureSensor::read(float& value) +{ + uint32_t rval = _pRegisters->ctrl2 & 0x3ff; + value = (float)(rval - 117) / 784 * 165 - 40; +} + +TemperatureSensor::Registers* TemperatureSensor::getRegisters() +{ + return (Registers*)memmap(TSENSOR_CTRL_REG_BASE, sizeof(Registers)); +} + +void TemperatureSensor::releaseRegisters(Registers* pRegisters) +{ + memunmap(pRegisters); +} +} +} diff --git a/hardware/hisilicon/TemperatureSensor.h b/hardware/hisilicon/TemperatureSensor.h new file mode 100644 index 0000000..a9371e8 --- /dev/null +++ b/hardware/hisilicon/TemperatureSensor.h @@ -0,0 +1,48 @@ +#ifndef CBEF59CE_2DB7_43B5_AFA8_524952A27AF9 +#define CBEF59CE_2DB7_43B5_AFA8_524952A27AF9 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../Sensor.h" + +namespace Hardware +{ +namespace Hisilicon +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class TemperatureSensor: public Sensor +{ +protected: + struct Registers + { + uint32_t ctrl0; + uint32_t ctrl1; + uint32_t ctrl2; + uint32_t ctrl3; + uint32_t ctrl4; + uint32_t ctrl5; + }; + +public: + TemperatureSensor(); + ~TemperatureSensor(); + +public: + virtual void read(float& value) override; + +protected: + Registers* getRegisters(); + void releaseRegisters(Registers* pRegisters); + +private: + Registers* _pRegisters; +}; +} +} +#endif diff --git a/hardware/hisilicon/memmap.c b/hardware/hisilicon/memmap.c new file mode 100644 index 0000000..db6e874 --- /dev/null +++ b/hardware/hisilicon/memmap.c @@ -0,0 +1,275 @@ +/************************************************************/ +/* file name : memmap.c */ +/* linux /dev/mem mmap support func */ +/* */ +/* */ +/* Copyright 2005 huawei com. */ +/* Author :zhouaidi(42136) */ +/* Create date: 2005-04-07 */ +/* Modify history */ +/* 2005-12-21: add memunmap function */ +/************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct tag_MMAP_Node +{ + unsigned long Start_P; + unsigned long Start_V; + unsigned long length; + unsigned long refcount; + struct tag_MMAP_Node * next; +}TMMAP_Node_t; + +TMMAP_Node_t * pTMMAPNode = NULL; + +#define PAGE_SIZE 0x1000 +#define PAGE_SIZE_MASK (~(0xfff)) + +static int fd = -1; +static const char dev[]="/dev/mem"; + +/* + ******************************************************************************* + * MACROS + ******************************************************************************* + */ +#define WRITE_LOG_DEBUG(fmt, ...) printf("%05d:[debug] "fmt"\r\n", __LINE__, ##__VA_ARGS__) +#define WRITE_LOG_INFO(fmt, ...) printf("%05d:[info ] "fmt"\r\n", __LINE__, ##__VA_ARGS__) +#define WRITE_LOG_ERROR(fmt, ...) printf("%05d:[error] "fmt"\r\n", __LINE__, ##__VA_ARGS__) + + +/* no need considering page_size of 4K */ +void * memmap(unsigned long phy_addr, unsigned long size) +{ + unsigned long phy_addr_in_page; + unsigned long page_diff; + + unsigned long size_in_page; + + TMMAP_Node_t * pTmp; + TMMAP_Node_t * pNew; + + void *addr=NULL; + + if(size == 0) + { + WRITE_LOG_ERROR("memmap():size can't be zero!\n"); + return NULL; + } + + /* check if the physical memory space have been mmaped */ + pTmp = pTMMAPNode; + while(pTmp != NULL) + { + if( (phy_addr >= pTmp->Start_P) && + ( (phy_addr + size) <= (pTmp->Start_P + pTmp->length) ) ) + { + pTmp->refcount++; /* referrence count increase by 1 */ + return (void *)(pTmp->Start_V + phy_addr - pTmp->Start_P); + } + + pTmp = pTmp->next; + } + + /* not mmaped yet */ + if(fd < 0) + { + /* dev not opened yet, so open it */ + fd = open (dev, O_RDWR | O_SYNC); + if (fd < 0) + { + WRITE_LOG_ERROR("memmap():open %s error!\n", dev); + return NULL; + } + } + + /* addr align in page_size(4K) */ + phy_addr_in_page = phy_addr & PAGE_SIZE_MASK; + page_diff = phy_addr - phy_addr_in_page; + + /* size in page_size */ + size_in_page =((size + page_diff - 1) & PAGE_SIZE_MASK) + PAGE_SIZE; + + addr = mmap((void *)0, size_in_page, PROT_READ|PROT_WRITE, MAP_SHARED, fd, phy_addr_in_page); + if (addr == MAP_FAILED) + { + WRITE_LOG_ERROR("memmap():mmap @ 0x%x error!\n", phy_addr_in_page); + return NULL; + } + + /* add this mmap to MMAP Node */ + pNew = (TMMAP_Node_t *)malloc(sizeof(TMMAP_Node_t)); + if(NULL == pNew) + { + WRITE_LOG_ERROR("memmap():malloc new node failed!\n"); + return NULL; + } + pNew->Start_P = phy_addr_in_page; + pNew->Start_V = (unsigned long)addr; + pNew->length = size_in_page; + pNew->refcount = 1; + pNew->next = NULL; + + if(pTMMAPNode == NULL) + { + pTMMAPNode = pNew; + } + else + { + pTmp = pTMMAPNode; + while(pTmp->next != NULL) + { + pTmp = pTmp->next; + } + + pTmp->next = pNew; + } + + return (void *)(addr+page_diff); +} + +/***************************************************************************** +Prototype : memunmap +Description : +Input : void * addr_mapped +Output : None +Return Value : On success, returns 0, on failure -1 +Calls : +Called By : + +History : +1.Date : 2005/12/21 +Author : Z42136 +Modification : Created function + + *****************************************************************************/ +int memunmap(void * addr_mapped) +{ + TMMAP_Node_t * pPre; + TMMAP_Node_t * pTmp; + + if(pTMMAPNode == NULL) + { + WRITE_LOG_ERROR("memunmap(): address have not been mmaped!\n"); + return -1; + } + + /* check if the physical memory space have been mmaped */ + pTmp = pTMMAPNode; + pPre = pTMMAPNode; + + do + { + if( ((unsigned long)addr_mapped >= pTmp->Start_V) && + ((unsigned long)addr_mapped <= (pTmp->Start_V + pTmp->length)) ) + { + pTmp->refcount--; /* referrence count decrease by 1 */ + if(0 == pTmp->refcount) + { + + // WRITE_LOG_INFO("memunmap(): map node will be remove!\n"); + + /* delete this map node from pMMAPNode */ + if(pTmp == pTMMAPNode) + { + pTMMAPNode = NULL; + } + else + { + pPre->next = pTmp->next; + } + + /* munmap */ + if(munmap((void *)pTmp->Start_V, pTmp->length) != 0 ) + { + WRITE_LOG_INFO("memunmap(): munmap failed!\n"); + } + + free(pTmp); + } + + return 0; + } + + pPre = pTmp; + pTmp = pTmp->next; + }while(pTmp != NULL); + + WRITE_LOG_ERROR("memunmap(): address have not been mmaped!\n"); + return -1; +} + +void * mmapfile(char* fn, unsigned long size) +{ + int fd; + void * pShm = NULL; + if (fn == NULL) + { + return NULL; + } + /* Open the file. */ + fd = open (fn, O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | O_SYNC); + + if (fd == -1) + { + WRITE_LOG_DEBUG("Open file %s error: %i.\n",fn, errno ); + return NULL; + } + + /* Create the memory-mapping. */ + pShm = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + close (fd); + if (pShm != NULL) + { + WRITE_LOG_DEBUG("Memory Map OK. Map File: %s, length:%#X, addr:0x%08lX", + fn, size, (unsigned long)pShm); + } + else + { + WRITE_LOG_DEBUG("Memory Map Failure. Map File %s, length:0x%08lX", + fn, size); + } + return pShm; +} + +void * mmapfile_r(char* fn, unsigned long size) +{ + int fd; + void * pShm = NULL; + if (fn == NULL) + { + return NULL; + } + /* Open the file. */ + fd = open (fn, O_RDONLY | O_SYNC); + + if (fd == -1) + { + WRITE_LOG_INFO("Open file %s error: %d.\n",fn, errno ); + return NULL; + } + + /* Create the memory-mapping. */ + pShm = mmap(0, size, PROT_READ, MAP_SHARED, fd, 0); + close (fd); + if (pShm != NULL) + { + WRITE_LOG_DEBUG("Memory Map OK. Map File: %s, length:%#X, addr:0x%08lX\n", + fn, size, (unsigned long)pShm); + } + else + { + WRITE_LOG_DEBUG("Memory Map Failure. Map File %s, length:0x%08lX\n", + fn, size); + } + return pShm; +} + diff --git a/hardware/hisilicon/memmap.h b/hardware/hisilicon/memmap.h new file mode 100644 index 0000000..5cfbc2a --- /dev/null +++ b/hardware/hisilicon/memmap.h @@ -0,0 +1,33 @@ + +/* file name : memmap.h */ +/* linux /dev/mem mmap support head file */ +/* */ +/* */ +/* Copyright 2005 huawei com. */ +/* Author :zhouaidi(42136) */ +/* Create date: 2005-04-07 */ +/* Modify history */ +/* */ + +#ifndef __MEM_MAP_H__ +#define __MEM_MAP_H__ + +#ifdef __cplusplus +#if __cplusplus +extern "C"{ +#endif +#endif /* __cplusplus */ + +extern void * memmap(unsigned long phy_addr, unsigned long size); +extern int memunmap(void * addr_mapped); +extern void * mmapfile(char* fn, unsigned int size); +extern void * mmapfile_r(char* fn, unsigned int size); + +#ifdef __cplusplus +#if __cplusplus +} +#endif +#endif /* __cplusplus */ + +#endif + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..e08086e --- /dev/null +++ b/main.cpp @@ -0,0 +1,75 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Application.h" +#ifdef USING_HARDWARE_SUBSYSTEM +#include "HardwareSubsystem.h" +#endif + +#ifdef USING_NETWORK_SUBSYSTEM +#include "NetworkSubsystem.h" +#endif + +#ifdef USING_GRAPHICS_SUBSYSTEM +#include "GraphicsSubsystem.h" +#endif + +#ifdef WINDOWS_GUI_APPLICATION +#include +#include +#endif + +#ifdef USING_VIDEO_SUBSYSTEM +#include "VideoSubsystem.h" +#endif + + +/* + ******************************************************************************* + * FUNCTIONS + ******************************************************************************* + */ +#ifdef WINDOWS_GUI_APPLICATION +int WINAPI _tWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, + _In_ INT nCmdShow) +#else +int main() +#endif +{ + Application app; + +#ifdef USING_HARDWARE_SUBSYSTEM + app.addSubsystem(new Hardware::HardwareSubsystem()); +#endif + +#ifdef USING_NETWORK_SUBSYSTEM + app.addSubsystem(new Network::NetworkSubsystem()); +#endif + +#ifdef USING_GRAPHICS_SUBSYSTEM + app.addSubsystem(new Graphics::GraphicsSubsystem()); +#endif + +#ifdef USING_VIDEO_SUBSYSTEM + app.addSubsystem(new Video::VideoSubsystem()); +#endif + + int rc = 0; + + try + { + rc = app.run(); + } + catch (const std::exception& e) + { + std::cout << e.what() << std::endl; + } + catch (...) + { + std::cout << "Unknown exception" << std::endl; + } + + return rc; +} diff --git a/network/CMakeLists.txt b/network/CMakeLists.txt new file mode 100644 index 0000000..0f905b1 --- /dev/null +++ b/network/CMakeLists.txt @@ -0,0 +1,6 @@ +set(target network) +aux_source_directory(. NETWORK_SOURCES) + +add_library(${target} STATIC + ${NETWORK_SOURCES} +) \ No newline at end of file diff --git a/network/HTTPServer.cpp b/network/HTTPServer.cpp new file mode 100644 index 0000000..3c30de8 --- /dev/null +++ b/network/HTTPServer.cpp @@ -0,0 +1,53 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "HTTPServer.h" + +using namespace Poco::Net; + +namespace Network +{ +class RouterRequestHandlerFactory: public HTTPRequestHandlerFactory +{ +public: + HTTPRequestHandler* createRequestHandler(const HTTPServerRequest& request) override + { + throw Poco::NotFoundException("Not found"); + } +}; + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +std::string HTTPServer::HOST = "host"; +std::string HTTPServer::PORT = "port"; + +HTTPServer::HTTPServer(): _pHTTPServer(nullptr) +{ + setParameter(HOST, std::string("::")); + setParameter(PORT, 80); +} + +HTTPServer::~HTTPServer() +{ +} + +void HTTPServer::start() +{ + Poco::Net::SocketAddress address(getParameter(HOST), getParameter(PORT)); + Poco::Net::ServerSocket socket(address); + Poco::Net::HTTPServerParams* pParams = new Poco::Net::HTTPServerParams(); + pParams->setKeepAlive(false); + _pHTTPServer = new Poco::Net::HTTPServer(new RouterRequestHandlerFactory, socket, pParams); + _pHTTPServer->start(); +} + +void HTTPServer::stop() +{ + _pHTTPServer->stopAll(); +} +} diff --git a/network/HTTPServer.h b/network/HTTPServer.h new file mode 100644 index 0000000..964d414 --- /dev/null +++ b/network/HTTPServer.h @@ -0,0 +1,37 @@ +#ifndef DC9BEC60_0C80_41F2_B1F7_289C63388DC1 +#define DC9BEC60_0C80_41F2_B1F7_289C63388DC1 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Poco/Net/HTTPServer.h" +#include "Poco/SharedPtr.h" +#include "util/Configuration.h" + +namespace Network +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class HTTPServer: public Util::Configuration +{ +public: + static std::string HOST; + + static std::string PORT; + +public: + HTTPServer(); + ~HTTPServer(); + + void start(); + void stop(); + +private: + Poco::SharedPtr _pHTTPServer; +}; +} +#endif diff --git a/network/NetworkSubsystem.cpp b/network/NetworkSubsystem.cpp new file mode 100644 index 0000000..dc0e2af --- /dev/null +++ b/network/NetworkSubsystem.cpp @@ -0,0 +1,40 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "NetworkSubsystem.h" +#include "Application.h" +#include "Logger.h" +#include "Poco/Exception.h" +#include "Poco/Net/DNS.h" + +namespace Network +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +NetworkSubsystem::NetworkSubsystem() +{ +} + +NetworkSubsystem::~NetworkSubsystem() +{ +} + +const char* NetworkSubsystem::name() const +{ + return "Network"; +} + +void NetworkSubsystem::initialize(Poco::Util::Application& app) +{ +} + +void NetworkSubsystem::uninitialize() +{ + stopAll(); +} +} diff --git a/network/NetworkSubsystem.h b/network/NetworkSubsystem.h new file mode 100644 index 0000000..0582ab0 --- /dev/null +++ b/network/NetworkSubsystem.h @@ -0,0 +1,47 @@ +#ifndef __NETWORK_SUBSYSTEM__H__ +#define __NETWORK_SUBSYSTEM__H__ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Poco/Util/Application.h" +#include "util/Subsystem.h" + +namespace Network +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class NetworkSubsystem: public Util::Subsystem +{ +public: + NetworkSubsystem(); + ~NetworkSubsystem(); + + virtual const char* name() const override; + +protected: + virtual void initialize(Poco::Util::Application& app); + virtual void uninitialize(); + +public: + struct ZLStreamPushMessage + { + std::string appname; + std::string streamid; + short type; + ZLStreamPushMessage(const std::string& appname, const std::string& streamid, short type) + : appname(appname), streamid(streamid), type(type) + { + } + }; + + Poco::BasicEvent ZLStreamPushMessageEvent; + +private: +}; +} +#endif diff --git a/network/TCPClient.cpp b/network/TCPClient.cpp new file mode 100644 index 0000000..c5d6212 --- /dev/null +++ b/network/TCPClient.cpp @@ -0,0 +1,148 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "TCPClient.h" +#include + + +namespace Network +{ + +TCPClient::TCPClient(std::string host, int port, Poco::Timespan timeout, uint32_t sz) + : Poco::Task("TCPClient"), _address(host, port), _timeout(timeout), _bufsz(sz), _pBuffer(new uint8_t[sz]) +{ +} + +TCPClient::~TCPClient() +{ +} + +int TCPClient::send(const uint8_t* data, uint32_t len) +{ + try + { + int rc = _socket.sendBytes(data, len); + return rc; + } + catch (const std::exception& e) + { + } + + return 0; +} + +void TCPClient::start() +{ + if (!_thread.isRunning()) + _thread.start(*this); +} + +void TCPClient::stop() +{ + if (_thread.isRunning()) + { + cancel(); + _thread.join(); + } +} + +void TCPClient::setStateChangedCallback(const StateChangedCallback& callback) +{ + _stateChanged = std::move(callback); +} + +void TCPClient::setDataIncomingCallback(const DataIncomingCallback& callback) +{ + _dataIncoming = std::move(callback); +} + +bool TCPClient::connect() +{ + try + { + _socket.connect(_address, _timeout); + _socket.setReceiveTimeout(Poco::Timespan(1000)); + setState(State::CONNECTED, "Connected"); + return true; + } + catch (Poco::TimeoutException& e) + { + setState(State::CONNECT_TIMEOUT, e.displayText()); + } + catch (Poco::Net::ConnectionRefusedException& e) + { + setState(State::CONNECT_REFUSED, e.displayText()); + } + catch (Poco::Exception& e) + { + setState(State::CONNECT_REFUSED, e.displayText()); + } + + return false; +} + +void TCPClient::runTask() +{ + while (!isCancelled()) + { + while (!isCancelled()) + { + setState(State::CONNECTING, "Connecting"); + if (connect()) + break; + + Poco::Thread::sleep(1000); + } + + while (!isCancelled()) + { + try + { + int rc = _socket.receiveBytes(_pBuffer, _bufsz); + if (rc <= 0) + { + setState(State::DISCONNECTED, "Connection lost"); + _socket.close(); + break; + } + + receive(_pBuffer, rc); + } + catch (Poco::TimeoutException& e) + { + } + catch (Poco::Exception& e) + { + setState(State::DISCONNECTED, "Connection lost"); + _socket.close(); + break; + } + } + + Poco::Thread::sleep(1000); + } + + try + { + _socket.close(); + } + catch (...) + { + } +} + +void TCPClient::setState(State state, const std::string& desc) +{ + if (_stateChanged) + _stateChanged(state, desc); +} + +void TCPClient::receive(const uint8_t* data, uint32_t len) +{ + if (_dataIncoming) + _dataIncoming(data, len); +} + +} diff --git a/network/TCPClient.h b/network/TCPClient.h new file mode 100644 index 0000000..778384c --- /dev/null +++ b/network/TCPClient.h @@ -0,0 +1,65 @@ +#ifndef __TCP_CLIENT_H__ +#define __TCP_CLIENT_H__ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include +#include +#include + + +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +namespace Network +{ + +class TCPClient: public Poco::Task +{ +public: + enum class State + { + DISCONNECTED, + CONNECT_TIMEOUT, + CONNECT_REFUSED, + CONNECTING, + CONNECTED, + }; + + using StateChangedCallback = std::function; + using DataIncomingCallback = std::function; + +public: + TCPClient(std::string host, int port, Poco::Timespan timeout, uint32_t sz = 10240); + ~TCPClient(); + + int send(const uint8_t* data, uint32_t len); + void start(); + void stop(); + void setStateChangedCallback(const StateChangedCallback& callback); + void setDataIncomingCallback(const DataIncomingCallback& callback); + +protected: + bool connect(); + void receive(const uint8_t* data, uint32_t len); + virtual void runTask() override; + void setState(State state, const std::string& desc); + +private: + Poco::Thread _thread; + Poco::Net::StreamSocket _socket; + Poco::Net::SocketAddress _address; + Poco::Timespan _timeout; + uint32_t _bufsz; + Poco::SharedPtr _pBuffer; + StateChangedCallback _stateChanged; + DataIncomingCallback _dataIncoming; +}; + +} +#endif diff --git a/network/network.cmake b/network/network.cmake new file mode 100644 index 0000000..5b23d19 --- /dev/null +++ b/network/network.cmake @@ -0,0 +1,3 @@ +add_subdirectory(network) +include_directories(network) +target_link_libraries(${PROJNAME} PRIVATE network) \ No newline at end of file diff --git a/util/AESUtil.h b/util/AESUtil.h new file mode 100644 index 0000000..8afb0ed --- /dev/null +++ b/util/AESUtil.h @@ -0,0 +1,57 @@ +#ifndef A89D0C3C_4F05_4631_A720_D8B2CEA5E99A +#define A89D0C3C_4F05_4631_A720_D8B2CEA5E99A +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Logger.h" +namespace Util +{ +class AESUtil +{ +public: + static std::string encryptAES(const std::string& plaintext, const std::string& key, const std::string& iv) + { + try + { + Poco::Crypto::CipherKey cipherKey("aes-256-cbc", key, iv); + Poco::Crypto::CipherFactory& factory = Poco::Crypto::CipherFactory::defaultFactory(); + std::unique_ptr pCipher(factory.createCipher(cipherKey)); + + std::string ciphertext = pCipher->encryptString(plaintext, Poco::Crypto::Cipher::ENC_BASE64_NO_LF); + return ciphertext; + } + catch (const Poco::Exception& e) + { + logger().error("encryptAES error: %s", std::string(e.what())); + return ""; + } + } + + static std::string decryptAES(const std::string& ciphertext, const std::string& key, const std::string& iv) + { + try + { + Poco::Crypto::CipherKey cipherKey("aes-256-cbc", key, iv); + Poco::Crypto::CipherFactory& factory = Poco::Crypto::CipherFactory::defaultFactory(); + std::unique_ptr pCipher(factory.createCipher(cipherKey)); + + std::string plaintext = pCipher->decryptString(ciphertext, Poco::Crypto::Cipher::ENC_BASE64_NO_LF); + return plaintext; + } + catch (const Poco::Exception& e) + { + logger().error("decryptAES error: %s", std::string(e.what())); + return ""; + } + } +}; +} +#endif diff --git a/util/Base64Util.h b/util/Base64Util.h new file mode 100644 index 0000000..0f59f1b --- /dev/null +++ b/util/Base64Util.h @@ -0,0 +1,35 @@ +#ifndef CFCB972C_1202_49D2_AAD4_9066BA17F94F +#define CFCB972C_1202_49D2_AAD4_9066BA17F94F +#include +#include +#include +#include "Poco/Base64Decoder.h" +#include "Poco/Base64Encoder.h" +#include "Poco/StreamCopier.h" + +namespace Util +{ +class Base64Util +{ +private: +public: + static std::string base64Encode(const std::string& input) + { + std::ostringstream oss; + Poco::Base64Encoder encoder(oss, 0); + encoder.write(input.data(), input.size()); + encoder.close(); + return oss.str(); + } + + static std::string base64Decode(const std::string& input) + { + std::istringstream iss(input); + Poco::Base64Decoder decoder(iss); + std::ostringstream oss; + Poco::StreamCopier::copyStream(decoder, oss); + return oss.str(); + } +}; +} +#endif diff --git a/util/Broadcast.cpp b/util/Broadcast.cpp new file mode 100644 index 0000000..b814904 --- /dev/null +++ b/util/Broadcast.cpp @@ -0,0 +1,144 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Broadcast.h" +#include +#include + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +class BroadcastObserver +{ +public: + BroadcastObserver(BroadcastReceiver& receiver, IntentFilter::Ptr pFilter): _pReceiver(&receiver), _pFilter(pFilter) + { + } + + void broadcast(Intent::Ptr pIntent) + { + if (_pFilter->match(pIntent)) + _pReceiver->onReceive(pIntent); + } + + bool equals(const BroadcastObserver& observer) + { + return _pReceiver == observer._pReceiver; + } + + bool isBoundTo(BroadcastReceiver& receiver) + { + return _pReceiver == &receiver; + } + +private: + BroadcastReceiver* _pReceiver; + IntentFilter::Ptr _pFilter; +}; + +class BroadcastCore +{ +public: + void sendBroadcast(Intent::Ptr pIntent) + { + Poco::ScopedLockWithUnlock lock(_mutex); + BroadcastObserverList list(_broadcastObservers); + lock.unlock(); + for (auto& p : list) + p->broadcast(pIntent); + } + + void registerReceiver(BroadcastReceiver& receiver, IntentFilter::Ptr pFilter) + { + Poco::Mutex::ScopedLock lock(_mutex); + for (auto& obs : _broadcastObservers) + { + if (obs->isBoundTo(receiver)) + return; + } + + _broadcastObservers.push_back(new BroadcastObserver(receiver, pFilter)); + } + + void unregisterReceiver(BroadcastReceiver& receiver) + { + Poco::Mutex::ScopedLock lock(_mutex); + for (BroadcastObserverList::iterator it = _broadcastObservers.begin(); it != _broadcastObservers.end();) + { + if ((*it)->isBoundTo(receiver)) + it = _broadcastObservers.erase(it); + else + ++it; + } + } + +private: + using BroadcastObserverPtr = Poco::SharedPtr; + using BroadcastObserverList = std::vector; + + BroadcastObserverList _broadcastObservers; + mutable Poco::Mutex _mutex; +}; + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Intent& Intent::putExtra(const std::string& key, const char* value) +{ + return putExtra(key, std::string(value)); +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +void IntentFilter::addAction(const std::string& action) +{ + _actions.push_back(action); +} + +bool IntentFilter::match(Intent::Ptr pIntent) const +{ + const std::string& target = pIntent->getAction(); + return std::find(_actions.begin(), _actions.end(), target) != _actions.end(); +} + +/* + ******************************************************************************* + * LOCAL VARIABLES + ******************************************************************************* + */ +namespace +{ +static BroadcastCore core; +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +void BroadcastSender::sendBroadcast(Intent::Ptr pIntent) +{ + core.sendBroadcast(pIntent); +} + +void BroadcastReceiverRegistry::registerReceiver(BroadcastReceiver& receiver, IntentFilter::Ptr pFilter) +{ + core.registerReceiver(receiver, pFilter); +} + +void BroadcastReceiverRegistry::unregisterReceiver(BroadcastReceiver& receiver) +{ + core.unregisterReceiver(receiver); +} +} diff --git a/util/Broadcast.h b/util/Broadcast.h new file mode 100644 index 0000000..e7582ac --- /dev/null +++ b/util/Broadcast.h @@ -0,0 +1,117 @@ +#ifndef C81E1B2C_54AF_4E7D_800B_F21E0DACFD8B +#define C81E1B2C_54AF_4E7D_800B_F21E0DACFD8B +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include +#include +#include +#include + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Intent +{ +public: + using Ptr = Poco::SharedPtr; + + template Intent& putExtra(const std::string& key, const T& value); + Intent& putExtra(const std::string& key, const char* value); + + template const T getExtra(const std::string& key) const; + + const std::string& getAction() const; + void setAction(const std::string& action); + +private: + using Value = Poco::Any; + using Values = std::unordered_map; + Values _values; + + std::string _action; +}; + +template inline Intent& Intent::putExtra(const std::string& key, const T& value) +{ + _values[key] = value; + return *this; +} + +template inline const T Intent::getExtra(const std::string& key) const +{ + if (_values.find(key) == _values.end()) + throw Poco::NotFoundException(Poco::format("Intent: Can't find value: %s", key), -ENODATA); + + return Poco::AnyCast(_values.at(key)); +} + +inline const std::string& Intent::getAction() const +{ + return _action; +} + +inline void Intent::setAction(const std::string& action) +{ + _action = action; +} + +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class IntentFilter +{ +public: + using Ptr = Poco::SharedPtr; + + void addAction(const std::string& action); + bool match(Intent::Ptr pIntent) const; + +private: + std::vector _actions; +}; + +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class BroadcastReceiver +{ +public: + virtual void onReceive(Intent::Ptr pIntent) = 0; +}; + +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class BroadcastSender +{ +public: + void sendBroadcast(Intent::Ptr pIntent); +}; + +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class BroadcastReceiverRegistry +{ +public: + void registerReceiver(BroadcastReceiver& receiver, IntentFilter::Ptr pFilter); + void unregisterReceiver(BroadcastReceiver& receiver); +}; +} +#endif diff --git a/util/BuilderSet.cpp b/util/BuilderSet.cpp new file mode 100644 index 0000000..927e370 --- /dev/null +++ b/util/BuilderSet.cpp @@ -0,0 +1,15 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "BuilderSet.h" + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +} diff --git a/util/BuilderSet.h b/util/BuilderSet.h new file mode 100644 index 0000000..dd0c14f --- /dev/null +++ b/util/BuilderSet.h @@ -0,0 +1,63 @@ +#ifndef BE7ED734_3D5C_4600_89C3_4082EB263BE0 +#define BE7ED734_3D5C_4600_89C3_4082EB263BE0 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +template class BuilderSet +{ +private: + class IBuilder + { + public: + using Ptr = Poco::SharedPtr; + virtual ~IBuilder() + { + } + + virtual T* build(TArgs... args) = 0; + }; + + template class Builder: public IBuilder + { + public: + C* build(TArgs... args) override + { + C* p = new C(args...); + return p; + } + }; + + using Builders = std::map; + +public: + static T* build(TArgs... args) + { + TKey key = TSelector::select(args...); + return _builders[key]->build(args...); + } + + template static void registerClass(TKey key) + { + _builders[key] = new Builder(); + } + +private: + static Builders _builders; +}; + +template +typename BuilderSet::Builders BuilderSet::_builders; +} +#endif diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt new file mode 100644 index 0000000..95b0a2c --- /dev/null +++ b/util/CMakeLists.txt @@ -0,0 +1,5 @@ +set(target util) + +aux_source_directory(. UTIL_SOURCES) +add_library(${target} STATIC ${UTIL_SOURCES} ) + diff --git a/util/ClassAssistant.cpp b/util/ClassAssistant.cpp new file mode 100644 index 0000000..d56f762 --- /dev/null +++ b/util/ClassAssistant.cpp @@ -0,0 +1,28 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "ClassAssistant.h" + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +std::string Util::ClassAssistant::demangle(const std::type_info& ti) +{ + int status; + char* demangled = abi::__cxa_demangle(ti.name(), nullptr, nullptr, &status); + if (status == 0) + { + std::string result(demangled); + free(demangled); + return result; + } + + return ti.name(); +} +} diff --git a/util/ClassAssistant.h b/util/ClassAssistant.h new file mode 100644 index 0000000..10eb1ea --- /dev/null +++ b/util/ClassAssistant.h @@ -0,0 +1,68 @@ +#ifndef AD5404F9_7A8A_45FF_8ABE_66F7E9E53212 +#define AD5404F9_7A8A_45FF_8ABE_66F7E9E53212 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include +#include + +/* + ******************************************************************************* + * MACROS + ******************************************************************************* + */ +#define PROPERTY_DECLARE(name, type, defaultValue, setAccessibility, getAccessibility) \ +private: \ + type _##name = defaultValue; \ + setAccessibility: \ + void set##name(const type& value) \ + { \ + _##name = value; \ + } \ + getAccessibility: \ + type get##name() \ + { \ + return _##name; \ + } + +#define READONLY_PROPERTY_DECLARE(name, type) \ +private: \ + type _##name; \ +\ +public: \ + const type& name() \ + { \ + return _##name; \ + } + +namespace Util +{ +/* + ******************************************************************************* + * FUNCTIONS + ******************************************************************************* + */ +class ClassAssistant +{ +public: + template static std::string demangle(); + + template static std::string demangle(const T& object); + + static std::string demangle(const std::type_info& ti); +}; + +template std::string ClassAssistant::demangle() +{ + return demangle(typeid(T)); +} + +template inline std::string ClassAssistant::demangle(const T& object) +{ + return demangle(typeid(object)); +} +} +#endif diff --git a/util/Configurable.cpp b/util/Configurable.cpp new file mode 100644 index 0000000..d069bbf --- /dev/null +++ b/util/Configurable.cpp @@ -0,0 +1,26 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Configurable.h" + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +void Configurable::commit() +{ + ParameterNames names; + config().getChangedParameters(names); + if (names.size()) + { + onConfigChanged(names); + for (auto& name : names) + config().confirmParameterChanged(name); + } +} +} diff --git a/util/Configurable.h b/util/Configurable.h new file mode 100644 index 0000000..3946961 --- /dev/null +++ b/util/Configurable.h @@ -0,0 +1,55 @@ +#ifndef E6D074E1_FEDB_4EFB_902B_95FF6BD2EB09 +#define E6D074E1_FEDB_4EFB_902B_95FF6BD2EB09 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Configuration.h" +#include + +/* + ******************************************************************************* + * MACROS + ******************************************************************************* + */ +#define CONFIGURABLE_DECLARE_PARAMETER(name, type) \ + type get##name() \ + { \ + return config().getParameter(#name); \ + } \ + void set##name(const type value) \ + { \ + config().setParameter(#name, value); \ + } \ + const std::string _##name = (#name); + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Configurable +{ +public: + using ParameterNames = Configuration::ParameterNames; + +public: + void commit(); + Configuration& config(); + +protected: + virtual void onConfigChanged(ParameterNames& names) = 0; + +private: + Configuration _config; +}; + +inline Configuration& Configurable::config() +{ + return _config; +} +} +#endif diff --git a/util/Configuration.cpp b/util/Configuration.cpp new file mode 100644 index 0000000..de2e773 --- /dev/null +++ b/util/Configuration.cpp @@ -0,0 +1,33 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Configuration.h" + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +bool Configuration::ParameterNames::exists(std::string name) const +{ + return find(name) != end(); +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Configuration::~Configuration() +{ +} + +void Configuration::copyTo(Configuration& other) +{ + other._parameters = _parameters; +} +} diff --git a/util/Configuration.h b/util/Configuration.h new file mode 100644 index 0000000..06ff2c8 --- /dev/null +++ b/util/Configuration.h @@ -0,0 +1,140 @@ +#ifndef __CONFIG_CONFIGURATION_H__ +#define __CONFIG_CONFIGURATION_H__ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include +#include +#include "Poco/Any.h" +#include "Poco/Exception.h" +#include "Poco/Format.h" + + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Configuration +{ +private: + class Parameter + { + public: + template T getValue() + { + return Poco::AnyCast(_value); + } + + void setValue(const Poco::Any& value) + { + this->_value = value; + } + + void setChanged(bool changed) + { + this->_changed = changed; + } + + bool getChanged() + { + return _changed; + } + + bool empty() + { + return _value.empty(); + } + + Poco::Any& value() + { + return _value; + } + + private: + Poco::Any _value; + bool _changed; + }; + +public: + class ParameterNames: public std::unordered_set + { + public: + bool exists(std::string name) const; + }; + +public: + virtual ~Configuration(); + + template Configuration& setParameter(const std::string& name, const T& value) + { + Parameter& parameter = _parameters[name]; + if (parameter.empty() || parameter.getValue() != value) + { + parameter.setChanged(true); + parameter.setValue(value); + } + + return *this; + } + + template T getParameter(const std::string& name) + { + return findParameter(name).getValue(); + } + + bool isParameterChanged(const std::string& name) + { + return findParameter(name).getChanged(); + } + + void confirmParameterChanged(const std::string& name) + { + findParameter(name).setChanged(false); + } + + void getChangedParameters(ParameterNames& names) + { + names.clear(); + for (auto& p : _parameters) + { + if (p.second.getChanged()) + names.insert(p.first); + } + } + + const std::string& name() const + { + return _name; + } + + void copyTo(Configuration& other); + +protected: + void setName(std::string& name) + { + _name = name; + } + + Parameter& findParameter(const std::string& name) + { + if (_parameters.find(name) == _parameters.end()) + throw Poco::NotFoundException(Poco::format("Can't find parameter: %s", name), -ENODATA); + + return _parameters[name]; + } + +private: + using Parameters = std::map; + + Parameters _parameters; + std::string _name; +}; + +} +#endif diff --git a/util/DateTimeUtils.h b/util/DateTimeUtils.h new file mode 100644 index 0000000..45a554f --- /dev/null +++ b/util/DateTimeUtils.h @@ -0,0 +1,467 @@ +#ifndef A283B06E_3ED5_4D78_A2B5_4B87495A80A6 +#define A283B06E_3ED5_4D78_A2B5_4B87495A80A6 +#include +#include +#include +#include +#include "Poco/DateTime.h" +#include "Poco/DateTimeFormat.h" +#include "Poco/DateTimeFormatter.h" +#include "Poco/DateTimeParser.h" +#include "Poco/Exception.h" +#include "Poco/LocalDateTime.h" +#include "Poco/Timespan.h" +#include "Poco/Timestamp.h" +namespace Util +{ +class DateTimeUtils +{ +public: + static Poco::DateTime now() + { + return Poco::DateTime(); + } + + static Poco::LocalDateTime localNow() + { + return Poco::LocalDateTime(); + } + + static std::string nowStr(const std::string& formatStr = "%Y-%m-%d %H:%M:%S") + { + return format(Poco::DateTime(), formatStr); + } + + static std::string localNowStr(const std::string& formatStr = "%Y-%m-%d %H:%M:%S") + { + return Poco::DateTimeFormatter::format(Poco::LocalDateTime(), formatStr); + } + + static Poco::DateTime parse(const std::string& str, const std::string& formatStr = "") + { + auto dt = Poco::DateTime(1970, 1, 1); + if (str.empty()) + { + return dt; + } + int timeZoneDifferential = 0; + if (!formatStr.empty()) + { + if (Poco::DateTimeParser::tryParse(formatStr, str, dt, timeZoneDifferential)) + { + return dt; + } + } + else + { + if (Poco::DateTimeParser::tryParse(Poco::DateTimeFormat::ISO8601_FORMAT, str, dt, timeZoneDifferential) || + Poco::DateTimeParser::tryParse("%Y-%m-%d %H:%M:%S", str, dt, timeZoneDifferential) || + Poco::DateTimeParser::tryParse("%Y/%m/%d %H:%M:%S", str, dt, timeZoneDifferential) || + Poco::DateTimeParser::tryParse("%Y%m%d%H%M%S", str, dt, timeZoneDifferential)) + { + return dt; + } + } + return dt; + } + + static std::string format(const Poco::DateTime& dt, const std::string& formatStr = "%Y-%m-%d %H:%M:%S") + { + return Poco::DateTimeFormatter::format(dt, formatStr); + } + + static std::string toISO8601(const Poco::DateTime& dt) + { + return Poco::DateTimeFormatter::format(dt, Poco::DateTimeFormat::ISO8601_FORMAT); + } + + static std::string toRFC1123(const Poco::DateTime& dt) + { + return Poco::DateTimeFormatter::format(dt, Poco::DateTimeFormat::RFC1123_FORMAT); + } + + static Poco::DateTime addDays(const Poco::DateTime& dt, int days) + { + return dt + Poco::Timespan(days, 0, 0, 0, 0); + } + + static Poco::DateTime addHours(const Poco::DateTime& dt, int hours) + { + return dt + Poco::Timespan(0, hours, 0, 0, 0); + } + + static Poco::DateTime addMinutes(const Poco::DateTime& dt, int minutes) + { + return dt + Poco::Timespan(0, 0, minutes, 0, 0); + } + + static Poco::DateTime addSeconds(const Poco::DateTime& dt, int seconds) + { + return dt + Poco::Timespan(0, 0, 0, seconds, 0); + } + + static Poco::Timespan diff(const Poco::DateTime& dt1, const Poco::DateTime& dt2) + { + Poco::Timestamp ts1 = dt1.timestamp(); + Poco::Timestamp ts2 = dt2.timestamp(); + + Poco::Timestamp::TimeDiff diffMicroseconds = 0; + + if (ts1 > ts2) + { + diffMicroseconds = ts1 - ts2; + } + else + { + diffMicroseconds = ts2 - ts1; + } + + return Poco::Timespan(diffMicroseconds); + } + + static long diffSeconds(const Poco::DateTime& dt1, const Poco::DateTime& dt2) + { + Poco::Timestamp ts1 = dt1.timestamp(); + Poco::Timestamp ts2 = dt2.timestamp(); + + Poco::Timestamp::TimeDiff diffMicroseconds = 0; + + if (ts1 > ts2) + { + diffMicroseconds = ts1 - ts2; + } + else + { + diffMicroseconds = ts2 - ts1; + } + + Poco::Timespan ts(diffMicroseconds); + return ts.totalSeconds(); + } + + static long long diffMilliseconds(const Poco::DateTime& dt1, const Poco::DateTime& dt2) + { + Poco::Timestamp ts1 = dt1.timestamp(); + Poco::Timestamp ts2 = dt2.timestamp(); + + Poco::Timestamp::TimeDiff diffMicroseconds = 0; + + if (ts1 > ts2) + { + diffMicroseconds = ts1 - ts2; + } + else + { + diffMicroseconds = ts2 - ts1; + } + + return diffMicroseconds / 1000; + } + + static long long diffMicroseconds(const Poco::DateTime& dt1, const Poco::DateTime& dt2) + { + Poco::Timestamp ts1 = dt1.timestamp(); + Poco::Timestamp ts2 = dt2.timestamp(); + + Poco::Timestamp::TimeDiff diffMicroseconds = 0; + + if (ts1 > ts2) + { + diffMicroseconds = ts1 - ts2; + } + else + { + diffMicroseconds = ts2 - ts1; + } + + return diffMicroseconds; + } + + static void diffComponents(const Poco::DateTime& dt1, const Poco::DateTime& dt2, int& days, int& hours, + int& minutes, int& seconds) + { + Poco::Timespan ts = diff(dt1, dt2); + days = ts.days(); + hours = ts.hours(); + minutes = ts.minutes(); + seconds = ts.seconds(); + } + + static Poco::Timespan calculateDifference(const Poco::DateTime& dt1, const Poco::DateTime& dt2) + { + Poco::Timestamp::TimeVal us1 = dt1.timestamp().epochMicroseconds(); + Poco::Timestamp::TimeVal us2 = dt2.timestamp().epochMicroseconds(); + + Poco::Timestamp::TimeVal diff = 0; + if (us1 > us2) + { + diff = us1 - us2; + } + else + { + diff = us2 - us1; + } + + return Poco::Timespan(diff); + } + + static bool isBefore(const Poco::DateTime& dt1, const Poco::DateTime& dt2) + { + Poco::Timestamp ts1 = dt1.timestamp(); + Poco::Timestamp ts2 = dt2.timestamp(); + return ts1 < ts2; + } + + static bool isAfter(const Poco::DateTime& dt1, const Poco::DateTime& dt2) + { + Poco::Timestamp ts1 = dt1.timestamp(); + Poco::Timestamp ts2 = dt2.timestamp(); + return ts1 > ts2; + } + + static bool isEqual(const Poco::DateTime& dt1, const Poco::DateTime& dt2) + { + Poco::Timestamp ts1 = dt1.timestamp(); + Poco::Timestamp ts2 = dt2.timestamp(); + return ts1 == ts2; + } + + static bool isLeapYear(int year) + { + return Poco::DateTime::isLeapYear(year); + } + + static int daysInMonth(int year, int month) + { + if (month < 1 || month > 12) + { + return 0; + } + + Poco::DateTime nextMonth; + if (month == 12) + { + nextMonth = Poco::DateTime(year + 1, 1, 1); + } + else + { + nextMonth = Poco::DateTime(year, month + 1, 1); + } + + Poco::DateTime lastDay = nextMonth - Poco::Timespan(1, 0, 0, 0, 0); + return lastDay.day(); + } + + static std::string getWeekdayName(int dayOfWeek) + { + static const std::string weekdays[] = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"}; + + if (dayOfWeek >= 0 && dayOfWeek <= 6) + { + return weekdays[dayOfWeek]; + } + return "未知"; + } + + static time_t getTimestamp() + { + Poco::Timestamp ts; + return ts.epochTime(); + } + + static long long getTimestampMs() + { + Poco::Timestamp ts; + return ts.epochMicroseconds() / 1000; + } + + static long long getTimestampUs() + { + Poco::Timestamp ts; + return ts.epochMicroseconds(); + } + + static Poco::DateTime fromTimestamp(time_t timestamp) + { + Poco::Timestamp ts(static_cast(timestamp) * 1000000); + return Poco::DateTime(ts); + } + + static Poco::DateTime fromTimestampMs(long long timestampMs) + { + Poco::Timestamp ts(static_cast(timestampMs) * 1000); + return Poco::DateTime(ts); + } + + static Poco::DateTime fromTimestampUs(long long timestampUs) + { + Poco::Timestamp ts(static_cast(timestampUs)); + return Poco::DateTime(ts); + } + + static Poco::Timestamp toTimestamp(const Poco::DateTime& dt) + { + return dt.timestamp(); + } + + static Poco::DateTime toDateTime(const Poco::Timestamp& ts) + { + return Poco::DateTime(ts); + } + + static Poco::DateTime getStartOfDay(const Poco::DateTime& dt) + { + return Poco::DateTime(dt.year(), dt.month(), dt.day()); + } + + static Poco::DateTime getEndOfDay(const Poco::DateTime& dt) + { + return Poco::DateTime(dt.year(), dt.month(), dt.day(), 23, 59, 59, 999999); + } + + static int getAge(const Poco::DateTime& birthDate) + { + Poco::DateTime now = DateTimeUtils::now(); + + int age = now.year() - birthDate.year(); + + if (now.month() < birthDate.month() || (now.month() == birthDate.month() && now.day() < birthDate.day())) + { + age--; + } + + return age; + } + + static bool isSameDay(const Poco::DateTime& dt1, const Poco::DateTime& dt2) + { + return dt1.year() == dt2.year() && dt1.month() == dt2.month() && dt1.day() == dt2.day(); + } + + static std::string formatTimespan(const Poco::Timespan& ts) + { + std::string result; + + if (ts.days() > 0) + { + result += std::to_string(ts.days()) + "天"; + } + + if (ts.hours() > 0) + { + result += std::to_string(ts.hours()) + "小时"; + } + + if (ts.minutes() > 0) + { + result += std::to_string(ts.minutes()) + "分"; + } + + if (ts.seconds() > 0) + { + result += std::to_string(ts.seconds()) + "秒"; + } + + if (result.empty()) + { + result = "0秒"; + } + + return result; + } + + static std::string formatTimeDifference(const Poco::DateTime& dt1, const Poco::DateTime& dt2) + { + Poco::Timespan diff = calculateDifference(dt1, dt2); + return formatTimespan(diff); + } + + static std::string formatTimespanWithMs(const Poco::Timespan& ts) + { + std::string result = formatTimespan(ts); + + int milliseconds = ts.milliseconds(); + if (milliseconds > 0) + { + result += std::to_string(milliseconds) + "毫秒"; + } + + return result; + } + + static int getWeekOfYear(const Poco::DateTime& dt) + { + Poco::DateTime firstDay(dt.year(), 1, 1); + + Poco::Timespan diff = dt - firstDay; + int dayOfYear = diff.days() + 1; + + int firstDayWeek = firstDay.dayOfWeek(); + int week = (dayOfYear + firstDayWeek - 1) / 7 + 1; + + return week; + } + + static Poco::DateTime getFirstDayOfMonth(const Poco::DateTime& dt) + { + return Poco::DateTime(dt.year(), dt.month(), 1); + } + + static Poco::DateTime getLastDayOfMonth(const Poco::DateTime& dt) + { + int lastDay = daysInMonth(dt.year(), dt.month()); + return Poco::DateTime(dt.year(), dt.month(), lastDay); + } + + static Poco::DateTime getFirstDayOfWeek(const Poco::DateTime& dt) + { + int dayOfWeek = dt.dayOfWeek(); + int daysToMonday = (dayOfWeek == 0) ? 6 : (dayOfWeek - 1); + return addDays(dt, -daysToMonday); + } + + static Poco::DateTime getLastDayOfWeek(const Poco::DateTime& dt) + { + int dayOfWeek = dt.dayOfWeek(); + int daysToSunday = (dayOfWeek == 0) ? 0 : (7 - dayOfWeek); + return addDays(dt, daysToSunday); + } + + static long long getTime(const Poco::DateTime& dt) + { + Poco::Timestamp ts = dt.timestamp(); + return ts.epochMicroseconds() / 1000; + } + + static time_t getTimeSeconds(const Poco::DateTime& dt) + { + Poco::Timestamp ts = dt.timestamp(); + return ts.epochTime(); + } + + static Poco::DateTime fromTime(long long milliseconds) + { + Poco::Timestamp ts(static_cast(milliseconds) * 1000); + return Poco::DateTime(ts); + } + + static Poco::DateTime fromTimeSeconds(time_t seconds) + { + Poco::Timestamp ts(static_cast(seconds) * 1000000); + return Poco::DateTime(ts); + } + + static long long currentTimeMillis() + { + Poco::Timestamp ts; + return ts.epochMicroseconds() / 1000; + } + + static time_t currentTimeSeconds() + { + Poco::Timestamp ts; + return ts.epochTime(); + } +}; +} +#endif diff --git a/util/Device.cpp b/util/Device.cpp new file mode 100644 index 0000000..3900379 --- /dev/null +++ b/util/Device.cpp @@ -0,0 +1,104 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Device.h" +#include +#include +#include +#include +#include +#include "Poco/Exception.h" +#include "Poco/Format.h" +#include + + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Device::Device(std::string path, Mode mode): _path(path), _mode(mode), _fd(-1) +{ +} + +Device::~Device() +{ + close(); +} + +void Device::open() +{ + int flags; + + switch (_mode) + { + default: + case Mode::RD_ONLY: + flags = O_RDONLY; + break; + + case Mode::WR_ONLY: + flags = O_WRONLY; + break; + + case Mode::RDWR: + flags = O_RDWR; + break; + } + + _fd = ::open(_path.c_str(), flags); + if (_fd < 0) + throw Poco::InvalidAccessException(Poco::format("Access %s error: %d", _path, _fd), _fd); +} + +int Device::write(uint8_t* data, int size) +{ + return ::write(_fd, data, size); +} + +int Device::read(uint8_t* buffer, int count) +{ + return ::read(_fd, buffer, count); +} + +void Device::close() +{ + if (_fd >= 0) + ::close(_fd); +} + +int Device::fcntl(int cmd) +{ + return ::fcntl(_fd, cmd); +} + +int Device::fcntl(int cmd, long arg) +{ + return ::fcntl(_fd, cmd, arg); +} + +int Device::fcntl(int cmd, long arg, void* lock) +{ + return ::fcntl(_fd, cmd, arg, lock); +} + +int Device::poll(unsigned int& revents, int timeout) +{ + struct pollfd pollfd; + int rc; + + pollfd.fd = _fd; + pollfd.events = POLLIN; + + rc = ::poll(&pollfd, 1, timeout); + if (rc > 0) + revents = pollfd.revents; + + return rc; +} + +} diff --git a/util/Device.h b/util/Device.h new file mode 100644 index 0000000..07cedbb --- /dev/null +++ b/util/Device.h @@ -0,0 +1,47 @@ +#ifndef C5ADE9DB_6B16_4E4F_8C58_0101E9832DE0 +#define C5ADE9DB_6B16_4E4F_8C58_0101E9832DE0 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Device +{ +public: + enum Mode + { + RD_ONLY, + WR_ONLY, + RDWR, + }; + +public: + Device(std::string path, Mode mode = Mode::RD_ONLY); + ~Device(); + + virtual void open(); + virtual int write(uint8_t* data, int size); + virtual int read(uint8_t* buffer, int count); + virtual void close(); + virtual int fcntl(int cmd); + virtual int fcntl(int cmd, long arg); + virtual int fcntl(int cmd, long arg, void* lock); + virtual int poll(unsigned int& revents, int timeout); + +private: + std::string _path; + Mode _mode; + int _fd; +}; +} +#endif diff --git a/util/FactoryManager.cpp b/util/FactoryManager.cpp new file mode 100644 index 0000000..f6ad031 --- /dev/null +++ b/util/FactoryManager.cpp @@ -0,0 +1,15 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "FactoryManager.h" + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +} diff --git a/util/FactoryManager.h b/util/FactoryManager.h new file mode 100644 index 0000000..56d1d7a --- /dev/null +++ b/util/FactoryManager.h @@ -0,0 +1,79 @@ +#ifndef BE13A370_B199_43EC_A4E6_E2D4FD197D63 +#define BE13A370_B199_43EC_A4E6_E2D4FD197D63 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "ClassAssistant.h" +#include +#include +#include + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +template class SimpleSelector +{ +public: + static T select(T val) + { + return val; + } +}; + +template class FactoryManager +{ +private: + class IFactory + { + public: + using Ptr = Poco::SharedPtr; + virtual ~IFactory() + { + } + + public: + virtual T* create(TArgs... args) = 0; + }; + + template class Factory: public IFactory + { + public: + C* create(TArgs... args) override + { + return new C(args...); + } + }; + + using Factories = std::map; + +public: + static T* create(TArgs... args) + { + TKey key = TSelector::select(args...); + if (_factories.find(key) == _factories.end()) + throw Poco::NotFoundException( + Poco::format("FactoryManager: Factory '%s' does not exists.", Util::ClassAssistant::demangle()), + -EINVAL); + + return _factories[key]->create(args...); + } + + template static void registerClass(TKey key) + { + _factories[key] = new Factory(); + } + +private: + static Factories _factories; +}; + +template +typename FactoryManager::Factories FactoryManager::_factories; +} +#endif diff --git a/util/FileUtil.h b/util/FileUtil.h new file mode 100644 index 0000000..e0ba982 --- /dev/null +++ b/util/FileUtil.h @@ -0,0 +1,71 @@ +#ifndef B2D3FCFE_BBC5_4EFD_9AA7_7EEB5C58E2AE +#define B2D3FCFE_BBC5_4EFD_9AA7_7EEB5C58E2AE +#include +#include +#include +#include +#include "Logger.h" +namespace Util +{ +class FileUtil +{ +private: +public: + static std::string readFile(const std::string& fileFullName) + { + std::string content; + Poco::File file(fileFullName); + if (file.exists()) + { + try + { + Poco::FileInputStream fis(fileFullName); + Poco::StreamCopier::copyToString(fis, content); + fis.close(); + } + catch (Poco::Exception& e) + { + logger().error("readFile read error: %s", std::string(e.what())); + } + } + return content; + } + + static bool writeFile(const std::string& fileFullName, const std::string& content) + { + try + { + Poco::FileOutputStream fos(fileFullName, std::ios::trunc); + fos << content; + fos.close(); + + int fd = ::open(fileFullName.c_str(), O_RDWR); + if (fd != -1) + { + ::fsync(fd); + ::close(fd); + } + + return true; + } + catch (Poco::Exception& e) + { + logger().error("writeFile file error: %s", std::string(e.what())); + } + return false; + } + + static void delFile(const std::string& fileFullName) + { + if (!fileFullName.empty()) + { + if (Poco::File(fileFullName).exists()) + { + Poco::File(fileFullName).remove(); + logger().debug("删除文件: %s", fileFullName); + } + } + } +}; +} +#endif diff --git a/util/FrpUtil.h b/util/FrpUtil.h new file mode 100644 index 0000000..bec55b1 --- /dev/null +++ b/util/FrpUtil.h @@ -0,0 +1,125 @@ +#ifndef FRP_UTIL_H +#define FRP_UTIL_H + +#include +#include +#include +#include +#include +#include +#include "Application.h" +#include "Poco/JSON/Object.h" +#include "util/SystemInfo.h" + +namespace Util +{ +class FrpUtil +{ +private: +public: + static constexpr const char* frpcConfigPathName = "/customer/frp/frpc.toml"; + static constexpr const char* frpcPathName = "/customer/frp/frpc"; + +public: + static int getFrpcPid() + { + return Util::SystemInfo::getProcessPID(frpcPathName); + } + + static void startFrpc(std::string serialNum, Poco::JSON::Object::Ptr service_ports, bool forceRestart) + { + int http_local_port = service_ports->get("http_port"); + int http_remote_port = service_ports->get("http_port_nat_port"); + int http_api_local_port = service_ports->get("http_api_port"); + int http_api_remote_port = service_ports->get("http_api_port_nat_port"); + int rtmp_local_port = service_ports->get("rtmp_port"); + int rtmp_remote_port = service_ports->get("rtmp_port_nat_port"); + int rtsp_local_port = service_ports->get("rtsp_port"); + int rtsp_remote_port = service_ports->get("rtsp_port_nat_port"); + int srt_local_port = service_ports->get("srt_port"); + int srt_remote_port = service_ports->get("srt_port_nat_port"); + int nat_frp_enable = service_ports->get("nat_frp_enable"); + try + { + int pid = getFrpcPid(); + if (nat_frp_enable == 0) + { + if (pid > 0) + { + stopFrpc(pid); + } + return; + } + + if (pid > 0) + { + if (forceRestart) + { + stopFrpc(pid); + } + else + { + return; + } + } + + pid_t child = fork(); + if (child < 0) + { + logger().information("frpc fork failed"); + return; + } + if (child > 0) + { + logger().information("frpc daemon starting..."); + return; + } + + setsid(); + + for (int i = 3; i < 1024; ++i) + { + close(i); + } + + int fd = open("/dev/null", O_RDWR); + if (fd >= 0) + { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); + } + + setenv("FRP_SERIAL_NUM", serialNum.c_str(), 1); + setenv("FRP_HTTP_LOCAL_PORT", std::to_string(http_local_port).c_str(), 1); + setenv("FRP_HTTP_REMOTE_PORT", std::to_string(http_remote_port).c_str(), 1); + setenv("FRP_HTTP_API_LOCAL_PORT", std::to_string(http_api_local_port).c_str(), 1); + setenv("FRP_HTTP_API_REMOTE_PORT", std::to_string(http_api_remote_port).c_str(), 1); + setenv("FRP_RTMP_LOCAL_PORT", std::to_string(rtmp_local_port).c_str(), 1); + setenv("FRP_RTMP_REMOTE_PORT", std::to_string(rtmp_remote_port).c_str(), 1); + setenv("FRP_RTSP_LOCAL_PORT", std::to_string(rtsp_local_port).c_str(), 1); + setenv("FRP_RTSP_REMOTE_PORT", std::to_string(rtsp_remote_port).c_str(), 1); + setenv("FRP_SRT_LOCAL_PORT", std::to_string(srt_local_port).c_str(), 1); + setenv("FRP_SRT_REMOTE_PORT", std::to_string(srt_remote_port).c_str(), 1); + + execlp(frpcPathName, frpcPathName, "-c", frpcConfigPathName, (char*)NULL); + exit(EXIT_FAILURE); + } + catch (const std::exception& e) + { + logger().information("doStartFrpc,error: %s", std::string(e.what())); + } + } + + static void stopFrpc(int pid) + { + if (pid > 0) + { + Util::SystemInfo::executeCommand("kill -9 " + std::to_string(pid)); + logger().information("frpc stoped,pid=%d........", pid); + } + } +}; +} +#endif diff --git a/util/HTTPClient.h b/util/HTTPClient.h new file mode 100644 index 0000000..2f05f5e --- /dev/null +++ b/util/HTTPClient.h @@ -0,0 +1,386 @@ +#ifndef C99FBF1C_5805_40A3_963F_5BC1CA998C5C +#define C99FBF1C_5805_40A3_963F_5BC1CA998C5C + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Logger.h" + +namespace Util +{ +class HTTPClient +{ +public: + struct HttpResponse + { + int statusCode; + std::string statusMessage; + std::string content; + std::string contentType; + std::map headers; + bool success; + + HttpResponse(): statusCode(0), success(false) + { + } + }; + + struct ProxyConfig + { + std::string host; + int port; + bool enabled; + + ProxyConfig(): port(0), enabled(false) + { + } + ProxyConfig(const std::string& h, int p): host(h), port(p), enabled(true) + { + } + }; + + struct RequestConfig + { + int timeout; + std::string userAgent; + std::map headers; + bool followRedirects; + ProxyConfig proxy; + + RequestConfig(): timeout(30), userAgent("HTTPClient/1.0"), followRedirects(true) + { + } + }; + + static HttpResponse get(const std::string& url, const RequestConfig& config = RequestConfig()) + { + return executeRequest("GET", url, "", config); + } + + static bool downloadFile(const std::string& url, const std::string& localFilePath, + const RequestConfig& config = RequestConfig()) + { + try + { + Poco::URI uri(url); + auto session = createSession(uri, config.proxy); + session->setTimeout(Poco::Timespan(config.timeout, 0)); + + std::string path = getPathAndQuery(uri); + Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, path); + setupRequest(request, config); + + session->sendRequest(request); + + Poco::Net::HTTPResponse response; + std::istream& responseStream = session->receiveResponse(response); + + if (response.getStatus() != Poco::Net::HTTPResponse::HTTP_OK) + { + std::cerr << "Download failed: HTTP " << response.getStatus() << std::endl; + return false; + } + + std::ofstream file(localFilePath, std::ios::binary); + if (!file.is_open()) + { + std::cerr << "Cannot open file: " << localFilePath << std::endl; + return false; + } + + Poco::StreamCopier::copyStream(responseStream, file); + file.close(); + + return true; + } + catch (Poco::Exception& e) + { + std::cerr << "Download exception: " << e.displayText() << std::endl; + return false; + } + } + + static HttpResponse postJson(const std::string& url, const std::string& jsonData, + const RequestConfig& config = RequestConfig()) + { + RequestConfig jsonConfig = config; + jsonConfig.headers["Content-Type"] = "application/json"; + return executeRequest("POST", url, jsonData, jsonConfig); + } + + static HttpResponse postForm(const std::string& url, const std::map& formData, + const RequestConfig& config = RequestConfig()) + { + std::string formString = buildFormData(formData); + + RequestConfig formConfig = config; + formConfig.headers["Content-Type"] = "application/x-www-form-urlencoded"; + + return executeRequest("POST", url, formString, formConfig); + } + + static HttpResponse postFormWithPoco(const std::string& url, const std::map& formData, + const RequestConfig& config = RequestConfig()) + { + try + { + Poco::URI uri(url); + auto session = createSession(uri, config.proxy); + session->setTimeout(Poco::Timespan(config.timeout, 0)); + + std::string path = getPathAndQuery(uri); + + Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, path); + setupRequest(request, config); + request.setContentType("application/x-www-form-urlencoded"); + + Poco::Net::HTMLForm form; + for (const auto& field : formData) + { + form.set(field.first, field.second); + } + + std::ostream& requestStream = session->sendRequest(request); + form.write(requestStream); + + Poco::Net::HTTPResponse response; + std::istream& responseStream = session->receiveResponse(response); + + HttpResponse result; + result.statusCode = response.getStatus(); + result.statusMessage = response.getReason(); + result.contentType = response.getContentType(); + result.success = (result.statusCode == Poco::Net::HTTPResponse::HTTP_OK); + + Poco::Net::NameValueCollection::ConstIterator it = response.begin(); + while (it != response.end()) + { + result.headers[it->first] = it->second; + ++it; + } + + Poco::StreamCopier::copyToString(responseStream, result.content); + + return result; + } + catch (Poco::Exception& e) + { + std::cerr << "POST form exception: " << e.displayText() << std::endl; + HttpResponse result; + result.success = false; + result.statusMessage = e.displayText(); + return result; + } + } + + static HttpResponse post(const std::string& url, const std::string& data, + const std::string& contentType = "text/plain", + const RequestConfig& config = RequestConfig()) + { + RequestConfig postConfig = config; + postConfig.headers["Content-Type"] = contentType; + return executeRequest("POST", url, data, postConfig); + } + + static HttpResponse put(const std::string& url, const std::string& data = "", + const RequestConfig& config = RequestConfig()) + { + return executeRequest("PUT", url, data, config); + } + + static HttpResponse deleteRequest(const std::string& url, const RequestConfig& config = RequestConfig()) + { + return executeRequest("DELETE", url, "", config); + } + + static RequestConfig createProxyConfig(const std::string& proxyHost, int proxyPort, + const RequestConfig& baseConfig = RequestConfig()) + { + RequestConfig config = baseConfig; + config.proxy = ProxyConfig(proxyHost, proxyPort); + return config; + } + + static HttpResponse executeRequest(const std::string& method, const std::string& url, const std::string& data, + const RequestConfig& config) + { + HttpResponse result; + + try + { + Poco::URI uri(url); + auto session = createSession(uri, config.proxy); + session->setTimeout(Poco::Timespan(config.timeout, 0)); + + std::string path = getPathAndQuery(uri); + Poco::Net::HTTPRequest request(method, path); + setupRequest(request, config); + + if (!data.empty()) + { + request.setContentLength(data.length()); + std::ostream& requestStream = session->sendRequest(request); + requestStream << data; + } + else + { + session->sendRequest(request); + } + + Poco::Net::HTTPResponse response; + std::istream& responseStream = session->receiveResponse(response); + + result.statusCode = response.getStatus(); + result.statusMessage = response.getReason(); + result.contentType = response.getContentType(); + result.success = (result.statusCode == Poco::Net::HTTPResponse::HTTP_OK); + + Poco::Net::NameValueCollection::ConstIterator it = response.begin(); + while (it != response.end()) + { + result.headers[it->first] = it->second; + ++it; + } + + Poco::StreamCopier::copyToString(responseStream, result.content); + + if (config.followRedirects && (result.statusCode == Poco::Net::HTTPResponse::HTTP_MOVED_PERMANENTLY || + result.statusCode == Poco::Net::HTTPResponse::HTTP_FOUND || + result.statusCode == Poco::Net::HTTPResponse::HTTP_SEE_OTHER)) + { + auto locationIt = result.headers.find("Location"); + if (locationIt != result.headers.end()) + { + std::cout << "Redirecting to: " << locationIt->second << std::endl; + return executeRequest(method, locationIt->second, data, config); + } + } + } + catch (Poco::TimeoutException& e) + { + std::cerr << "Request timeout: " << e.displayText() << std::endl; + result.success = false; + result.statusMessage = "Timeout"; + } + catch (Poco::Exception& e) + { + std::cerr << "HTTP exception: " << e.displayText() << std::endl; + result.success = false; + result.statusMessage = e.displayText(); + } + catch (std::exception& e) + { + std::cerr << "Standard exception: " << e.what() << std::endl; + result.success = false; + result.statusMessage = e.what(); + } + + return result; + } + +private: + static std::string urlEncode(const std::string& value) + { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (char c : value) + { + if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') + { + escaped << c; + } + else + { + escaped << '%' << std::setw(2) << int(static_cast(c)); + } + } + + return escaped.str(); + } + + static std::string buildFormData(const std::map& formData) + { + std::ostringstream formStream; + bool first = true; + + for (const auto& field : formData) + { + if (!first) + { + formStream << "&"; + } + formStream << urlEncode(field.first) << "=" << urlEncode(field.second); + first = false; + } + + return formStream.str(); + } + + static std::unique_ptr createSession(const Poco::URI& uri, const ProxyConfig& proxy) + { + std::unique_ptr session; + logger().debug("HTTPClient url: %s", uri.toString()); + if (proxy.enabled) + { + session = std::make_unique(proxy.host, proxy.port); + session->setProxy(uri.getHost(), uri.getPort()); + } + else + { + if (uri.getScheme() == "https") + { + logger().warning("HTTPClient 暂时不支持https,自动调整为使用http连接: %s", uri.toString()); + + session = std::make_unique(uri.getHost(), uri.getPort()); + } + else + { + session = std::make_unique(uri.getHost(), uri.getPort()); + } + } + + return session; + } + + static void setupRequest(Poco::Net::HTTPRequest& request, const RequestConfig& config) + { + request.setVersion(Poco::Net::HTTPMessage::HTTP_1_1); + request.set("User-Agent", config.userAgent); + request.set("Accept", "*/*"); + request.set("Connection", "close"); + + for (const auto& header : config.headers) + { + request.set(header.first, header.second); + } + } + + static std::string getPathAndQuery(const Poco::URI& uri) + { + std::string path = uri.getPathAndQuery(); + if (path.empty()) + { + path = "/"; + } + return path; + } +}; +} +#endif diff --git a/util/HandlesManager.cpp b/util/HandlesManager.cpp new file mode 100644 index 0000000..e69de29 diff --git a/util/HandlesManager.h b/util/HandlesManager.h new file mode 100644 index 0000000..421f684 --- /dev/null +++ b/util/HandlesManager.h @@ -0,0 +1,67 @@ +#ifndef F0270054_CDC4_4A91_B09F_73A4C75D9356 +#define F0270054_CDC4_4A91_B09F_73A4C75D9356 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +template class HandlesManager +{ +public: + using HandleCollection = std::set; + +public: + HandlesManager(unsigned count): HandlesManager(0, count) + { + } + + HandlesManager(unsigned begin, unsigned count) + { + for (T h = begin; h < (begin + count); h++) + _freeHandles.insert(h); + } + + HandlesManager(HandleCollection& c) + { + _freeHandles.insert(c); + } + + T allocHandle() + { + Poco::FastMutex::ScopedLock lock(_lock); + + if (_freeHandles.size() == 0) + throw Poco::NotFoundException("Collection is empty", -ENODATA); + + T h = *_freeHandles.begin(); + _freeHandles.erase(h); + + return h; + } + + void freeHandle(T handle) + { + Poco::FastMutex::ScopedLock lock(_lock); + + if (_freeHandles.count(handle) != 0) + throw Poco::NotFoundException("Double free", -EINVAL); + + _freeHandles.insert(handle); + } + +private: + std::set _freeHandles; + Poco::FastMutex _lock; +}; +} +#endif diff --git a/util/HexBinaryCodec.cpp b/util/HexBinaryCodec.cpp new file mode 100755 index 0000000..855a1a7 --- /dev/null +++ b/util/HexBinaryCodec.cpp @@ -0,0 +1,29 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "HexBinaryCodec.h" +#include +#include + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +std::string HexBinaryCodec::encode(const void* data, unsigned size, bool uppercase) +{ + std::ostringstream ostr; + Poco::HexBinaryEncoder encoder(ostr); + encoder.rdbuf()->setUppercase(uppercase); + uint8_t* pBytes = (uint8_t*)data; + for (unsigned i = 0; i < size; i++) + encoder << pBytes[i]; + encoder.close(); + + return ostr.str(); +} +} diff --git a/util/HexBinaryCodec.h b/util/HexBinaryCodec.h new file mode 100755 index 0000000..7ad6c18 --- /dev/null +++ b/util/HexBinaryCodec.h @@ -0,0 +1,23 @@ +#ifndef E955D6BE_6497_479F_8D49_2A73C17E05E9 +#define E955D6BE_6497_479F_8D49_2A73C17E05E9 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class HexBinaryCodec +{ +public: + static std::string encode(const void* data, unsigned size, bool uppercase = true); +}; +} +#endif diff --git a/util/JsonUtil.h b/util/JsonUtil.h new file mode 100644 index 0000000..167673a --- /dev/null +++ b/util/JsonUtil.h @@ -0,0 +1,257 @@ +#ifndef JSONUTIL_H +#define JSONUTIL_H + +#include +#include +#include +#include +#include "util/xpack/json.h" + +#include +#include +#include +namespace Util +{ +class JsonUtil +{ +private: +public: + template static std::string encode(T t) + { + return xpack::json::encode(t); + } + + template static T decode(std::string jsonStr) + { + T t; + xpack::json::decode(jsonStr, t); + return t; + } + + static std::string jsonToString(const Poco::JSON::Object::Ptr& jsonObject) + { + std::ostringstream oss; + Poco::JSON::Stringifier::stringify(jsonObject, oss); + return oss.str(); + } + + static Poco::JSON::Object::Ptr stringToJson(const std::string& jsonString) + { + Poco::JSON::Parser parser; + Poco::Dynamic::Var result = parser.parse(jsonString); + return result.extract(); + } + + static Poco::JSON::Array::Ptr stringToJsonArray(const std::string& jsonArrayString) + { + Poco::JSON::Parser parser; + Poco::Dynamic::Var result = parser.parse(jsonArrayString); + return result.extract(); + } + + static Poco::Dynamic::Var getNestedValue(Poco::JSON::Object::Ptr json, const std::string& path) + { + if (path.empty()) + return json; + + std::regex arrayRegex(R"((\w+)((?:\[\d+\])+))"); + std::istringstream pathStream(path); + std::string segment; + Poco::Dynamic::Var currentVar = json; + + while (std::getline(pathStream, segment, '.')) + { + std::smatch match; + + if (std::regex_match(segment, match, arrayRegex)) + { + std::string arrayKey = match[1]; + std::string indicesStr = match[2]; + + std::vector indices; + std::regex indexRegex(R"(\[(\d+)\])"); + std::sregex_iterator it(indicesStr.begin(), indicesStr.end(), indexRegex); + std::sregex_iterator end; + + while (it != end) + { + indices.push_back(static_cast(std::stoi((*it)[1]))); + ++it; + } + + Poco::JSON::Object::Ptr obj = currentVar.extract(); + if (!obj->has(arrayKey)) + { + throw std::runtime_error("getNestedValue Key not found: " + arrayKey); + } + + Poco::JSON::Array::Ptr array = obj->getArray(arrayKey); + if (!array) + { + throw std::runtime_error("getNestedValue Expected an array at key: " + arrayKey); + } + + for (std::size_t i = 0; i < indices.size(); i++) + { + std::size_t index = indices[i]; + + if (index >= array->size()) + { + throw std::out_of_range("getNestedValue Array index out of bounds: " + std::to_string(index)); + } + + currentVar = array->get(index); + + if (i < indices.size() - 1) + { + array = currentVar.extract(); + if (!array) + { + throw std::runtime_error("getNestedValue Expected an array at index: " + + std::to_string(index)); + } + } + } + } + else + { + Poco::JSON::Object::Ptr obj = currentVar.extract(); + if (!obj->has(segment)) + { + throw std::runtime_error("getNestedValue Key not found: " + segment); + } + currentVar = obj->get(segment); + } + } + return currentVar; + } + + static Poco::JSON::Object::Ptr getNestedValue(Poco::JSON::Object::Ptr json, const std::vector& paths) + { + Poco::JSON::Object::Ptr results = new Poco::JSON::Object(); + for (const auto& path : paths) + { + results->set(path, getNestedValue(json, path)); + } + return results; + } + + static void setNestedValue(Poco::JSON::Object::Ptr jsonObject, const std::string& path, + const Poco::Dynamic::Var& value) + { + if (!jsonObject) + { + throw std::invalid_argument("setNestedValue JSON object is null."); + } + + std::regex arrayRegex(R"((\w+)((?:\[\d+\])+))"); + std::istringstream pathStream(path); + std::string segment; + Poco::JSON::Object::Ptr currentObject = jsonObject; + + while (std::getline(pathStream, segment, '.')) + { + std::smatch match; + + if (std::regex_match(segment, match, arrayRegex)) + { + std::string arrayKey = match[1]; + std::string indicesStr = match[2]; + + std::vector indices; + std::regex indexRegex(R"(\[(\d+)\])"); + std::sregex_iterator it(indicesStr.begin(), indicesStr.end(), indexRegex); + std::sregex_iterator end; + + while (it != end) + { + indices.push_back(static_cast(std::stoi((*it)[1]))); + ++it; + } + + Poco::JSON::Array::Ptr array; + if (!currentObject->has(arrayKey)) + { + array = new Poco::JSON::Array(); + currentObject->set(arrayKey, array); + } + else + { + array = currentObject->getArray(arrayKey); + if (!array) + { + throw std::runtime_error("setNestedValue Expected an array at key: " + arrayKey); + } + } + + for (std::size_t i = 0; i < indices.size() - 1; i++) + { + while (array->size() <= indices[i]) + { + Poco::JSON::Array::Ptr nestedArray = new Poco::JSON::Array(); + array->add(nestedArray); + } + + Poco::JSON::Array::Ptr nextArray = array->getArray(indices[i]); + if (!nextArray) + { + throw std::runtime_error("setNestedValue Expected an array at index: " + + std::to_string(indices[i])); + } + array = nextArray; + } + + std::size_t lastIndex = indices.back(); + while (array->size() <= lastIndex) + { + Poco::JSON::Object::Ptr nestedObject = new Poco::JSON::Object(); + array->add(nestedObject); + } + + if (pathStream.peek() == EOF) + { + array->set(lastIndex, value); + return; + } + + currentObject = array->getObject(lastIndex); + if (!currentObject) + { + throw std::runtime_error("setNestedValue Expected an object at index: " + + std::to_string(lastIndex)); + } + } + else + { + if (pathStream.peek() == EOF) + { + currentObject->set(segment, value); + return; + } + + if (!currentObject->has(segment)) + { + Poco::JSON::Object::Ptr nestedObject = new Poco::JSON::Object(); + currentObject->set(segment, nestedObject); + } + + currentObject = currentObject->getObject(segment); + if (!currentObject) + { + throw std::runtime_error("setNestedValue Expected an object at key: " + segment); + } + } + } + } + + static void setNestedValue(Poco::JSON::Object::Ptr jsonObject, + const std::map& pathValues) + { + for (const auto& kv : pathValues) + { + setNestedValue(jsonObject, kv.first, kv.second); + } + } +}; +} +#endif diff --git a/util/Math.cpp b/util/Math.cpp new file mode 100644 index 0000000..9481783 --- /dev/null +++ b/util/Math.cpp @@ -0,0 +1,25 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Math.h" + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +unsigned Math::alignup(unsigned num, unsigned align) +{ + return (((num) + (align)-1) & ~((align)-1)); +} + +unsigned Math::aligndown(unsigned num, unsigned align) +{ + return ((num) & ~((align)-1)); +} + +} diff --git a/util/Math.h b/util/Math.h new file mode 100644 index 0000000..0e719bb --- /dev/null +++ b/util/Math.h @@ -0,0 +1,37 @@ +#ifndef D6937363_A08A_40BF_B387_36E8AC570ECF +#define D6937363_A08A_40BF_B387_36E8AC570ECF +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Math +{ +public: + static unsigned alignup(unsigned num, unsigned align); + static unsigned aligndown(unsigned num, unsigned align); + + template static T max(T a, T b) + { + return a > b ? a : b; + } + template static T min(T a, T b) + { + return a < b ? a : b; + } + + template static T clamp(T value, T minVal, T maxVal) + { + return max(minVal, min(maxVal, value)); + } +}; +} +#endif diff --git a/util/Md5Util.h b/util/Md5Util.h new file mode 100644 index 0000000..cf0b783 --- /dev/null +++ b/util/Md5Util.h @@ -0,0 +1,37 @@ +#ifndef B6E698FD_68E6_4CB8_9BBA_BFE96C7501B0 +#define B6E698FD_68E6_4CB8_9BBA_BFE96C7501B0 +#include +#include +#include +#include +#include + +namespace Util +{ +class Md5Util +{ +public: + static std::string md5(const std::string& data) + { + Poco::MD5Engine md5Engine; + + Poco::DigestOutputStream dos(md5Engine); + dos << data; + dos.close(); + + const Poco::DigestEngine::Digest& digest = md5Engine.digest(); + + return Poco::DigestEngine::digestToHex(digest); + } + + static std::string uuid() + { + Poco::UUIDGenerator& generator = Poco::UUIDGenerator::defaultGenerator(); + + Poco::UUID uuid = generator.createRandom(); + + return uuid.toString(); + } +}; +} +#endif diff --git a/util/ParallelRunner.cpp b/util/ParallelRunner.cpp new file mode 100644 index 0000000..b9de38c --- /dev/null +++ b/util/ParallelRunner.cpp @@ -0,0 +1,20 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "ParallelRunner.h" + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +void ParallelRunner::join() +{ + for (auto& t : _threads) + t->join(); +} +} diff --git a/util/ParallelRunner.h b/util/ParallelRunner.h new file mode 100644 index 0000000..63aa3c5 --- /dev/null +++ b/util/ParallelRunner.h @@ -0,0 +1,35 @@ +#ifndef DE82057F_5520_4849_8108_E9A31AAC1878 +#define DE82057F_5520_4849_8108_E9A31AAC1878 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include +#include + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class ParallelRunner +{ +public: + template void run(Func func, Args... args) + { + Poco::Thread* pThread = new Poco::Thread(); + pThread->startFunc([=]() -> void { func(args...); }); + _threads.push_back(pThread); + } + + void join(); + +private: + std::vector> _threads; +}; +} +#endif diff --git a/util/PocoConfigurationCopier.cpp b/util/PocoConfigurationCopier.cpp new file mode 100644 index 0000000..b0cd538 --- /dev/null +++ b/util/PocoConfigurationCopier.cpp @@ -0,0 +1,44 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "PocoConfigurationCopier.h" +#include + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +void PocoConfigurationCopier::copy(Poco::Util::AbstractConfiguration::Ptr& from, + Poco::Util::AbstractConfiguration::Ptr& to) +{ + bool usingJSON = false; + + if (to.isNull()) + { + to = new Poco::Util::JSONConfiguration(); + usingJSON = true; + } + else + usingJSON = !to.cast().isNull(); + + Poco::AutoPtr pJConfig = from.cast(); + if (pJConfig && usingJSON) + { + std::stringstream ss; + pJConfig->save(ss); + to.cast()->load(ss); + } + else + { + Poco::Util::AbstractConfiguration::Keys keys; + from->keys(keys); + for (const auto& key : keys) + to->setString(key, from->getString(key)); + } +} +} diff --git a/util/PocoConfigurationCopier.h b/util/PocoConfigurationCopier.h new file mode 100644 index 0000000..1cbe6e7 --- /dev/null +++ b/util/PocoConfigurationCopier.h @@ -0,0 +1,23 @@ +#ifndef B2336806_10E0_4BFA_9CC6_0995436A32EB +#define B2336806_10E0_4BFA_9CC6_0995436A32EB +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class PocoConfigurationCopier +{ +public: + static void copy(Poco::Util::AbstractConfiguration::Ptr& from, Poco::Util::AbstractConfiguration::Ptr& to); +}; +} +#endif diff --git a/util/Service.cpp b/util/Service.cpp new file mode 100644 index 0000000..b1f6d9f --- /dev/null +++ b/util/Service.cpp @@ -0,0 +1,54 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Service.h" +#include + + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +namespace Util +{ + +void Service::start() +{ + if (isRunning()) + return; + + _pThread = new Poco::Thread(); + try + { + _pThread->start(*this); + } + catch (Poco::Exception& e) + { + std::cout << " --> Service: Poco::Exception: " << e.displayText() << "(" << e.what() << ")" << std::endl; + } + catch (std::exception& e) + { + std::cout << " --> Service: std::exception: " << e.what() << std::endl; + } +} + +void Service::stop() +{ + if (isRunning()) + { + cancel(); + _pThread->join(); + _pThread.reset(); + reset(); + } +} + +bool Service::isRunning() const +{ + return !_pThread.isNull() && _pThread->isRunning(); +} + +} diff --git a/util/Service.h b/util/Service.h new file mode 100644 index 0000000..6b07617 --- /dev/null +++ b/util/Service.h @@ -0,0 +1,31 @@ +#ifndef F3A063B4_C5E0_40B0_AFDA_B9DC936E9E82 +#define F3A063B4_C5E0_40B0_AFDA_B9DC936E9E82 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Service: public Poco::Task +{ +public: + using Poco::Task::Task; + + virtual void start(); + virtual void stop(); + bool isRunning() const; + +private: + Poco::SharedPtr _pThread; +}; +} +#endif diff --git a/util/SimpleFactory.cpp b/util/SimpleFactory.cpp new file mode 100644 index 0000000..9011091 --- /dev/null +++ b/util/SimpleFactory.cpp @@ -0,0 +1,15 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "SimpleFactory.h" + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +} diff --git a/util/SimpleFactory.h b/util/SimpleFactory.h new file mode 100644 index 0000000..22aca92 --- /dev/null +++ b/util/SimpleFactory.h @@ -0,0 +1,20 @@ +#ifndef FF4EF0DA_D455_437D_A5ED_BA7A7994B9CB +#define FF4EF0DA_D455_437D_A5ED_BA7A7994B9CB + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +template class SimpleFactory +{ +public: + static T* create(TArgs... args) + { + return new T(args...); + } +}; +} +#endif diff --git a/util/Subsystem.cpp b/util/Subsystem.cpp new file mode 100644 index 0000000..5aa4d8a --- /dev/null +++ b/util/Subsystem.cpp @@ -0,0 +1,35 @@ +/* + ******************************************************************************* + * INCLUDES + ******************************************************************************* + */ +#include "Subsystem.h" + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Subsystem::Subsystem(int maxCapacity): _threadPool(2, 16, 60, 0), _taskManager(_threadPool) +{ +} + +void Subsystem::run(Service* pService) +{ + _taskManager.start(pService); +} + +void Subsystem::stopAll() +{ + _taskManager.cancelAll(); + _taskManager.joinAll(); +} + +void Subsystem::uninitialize() +{ + stopAll(); +} + +} diff --git a/util/Subsystem.h b/util/Subsystem.h new file mode 100644 index 0000000..3e54fcf --- /dev/null +++ b/util/Subsystem.h @@ -0,0 +1,39 @@ +#ifndef DC436124_02CE_49A1_88B1_1A95453FA546 +#define DC436124_02CE_49A1_88B1_1A95453FA546 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Service.h" +#include +#include +#include +#include +#include + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Subsystem: public Poco::Util::Subsystem +{ +public: + Subsystem(int maxCapacity = 16); + +protected: + void run(Service* pService); + + void stopAll(); + + virtual void uninitialize() override; + +private: + Poco::ThreadPool _threadPool; + Poco::TaskManager _taskManager; +}; +} +#endif diff --git a/util/SystemInfo.cpp b/util/SystemInfo.cpp new file mode 100644 index 0000000..7b631c7 --- /dev/null +++ b/util/SystemInfo.cpp @@ -0,0 +1,829 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "SystemInfo.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Application.h" +#include "Logger.h" +#include "Poco/Thread.h" +#include "linux-system-usage.hpp" + +using namespace get_system_usage_linux; + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +unsigned SystemInfo::cpuUsage() +{ + CPU_stats t1 = read_cpu_data(); + Poco::Thread::sleep(100); + CPU_stats t2 = read_cpu_data(); + + return (unsigned)(100.0f * get_cpu_usage(t1, t2)); +} + +SystemInfo::MemoryInfo SystemInfo::readMemoryInfo() +{ + auto memory_data = read_memory_data(); + + MemoryInfo info; + info.total = memory_data.total_memory; + info.free = memory_data.free_memory; + info.usage = 100.0f * memory_data.get_memory_usage(); + info.available = memory_data.available_memory; + + return info; +} + +std::map SystemInfo::getStorageInfo(bool returnSpace) +{ + std::ifstream mounts("/proc/mounts"); + std::string line; + std::map storageMap; + + if (!mounts.is_open()) + { + logger().error("Failed to open /proc/mounts"); + return storageMap; + } + while (std::getline(mounts, line)) + { + std::istringstream iss(line); + std::string device, path; + iss >> device >> path; + std::string deviceType = device.find("/dev/sd") == 0 ? "disk" : ""; + deviceType = !deviceType.empty() ? deviceType : (device.find("/dev/mmcblk") == 0 ? "mmcblk" : ""); + deviceType = !deviceType.empty() ? deviceType : (path.find("/mnt/storages/nfs") == 0 ? "nfs" : ""); + deviceType = !deviceType.empty() ? deviceType : (path.find("/mnt/storages/cifs") == 0 ? "cifs" : ""); + if (!deviceType.empty()) + { + try + { + if (!mountPointEnable(path)) + { + logger().error("%s Mount point is not available", path); + continue; + } + StorageInfo storageInfo; + storageInfo.device = device; + storageInfo.path = path; + storageInfo.status = 1; + storageInfo.type = deviceType; + + if (returnSpace) + { + struct statvfs stat; + int ret_code = statvfs(path.c_str(), &stat); + if (ret_code != 0) + { + int err = errno; + logger().error("Failed to get disk usage information for: %s", strerror(err)); + } + else + { + unsigned long long total = stat.f_blocks * stat.f_frsize; + unsigned long long free = stat.f_bfree * stat.f_frsize; + unsigned long long available = stat.f_bavail * stat.f_frsize; + unsigned long long used = total - free; + storageInfo.total = total; + storageInfo.used = used; + storageInfo.available = available; + storageInfo.free = free; + } + } + storageMap[device] = storageInfo; + } + catch (const std::exception& e) + { + logger().error("SystemInfo::getStorageInfo erorr: %s", std::string(e.what())); + } + } + } + + auto blocks = getBlockDevices(); + for (const auto& block : blocks) + { + if (block.empty() || block.back() != '1') + { + continue; + } + + auto it = storageMap.find(block); + if (it == storageMap.end()) + { + StorageInfo storageInfo; + storageInfo.status = 2; + storageInfo.device = block; + storageMap[block] = storageInfo; + } + } + return storageMap; +} + +bool isBlockDevice(const std::string& path) +{ + struct stat statbuf; + if (stat(path.c_str(), &statbuf) != 0) + { + return false; + } + return S_ISBLK(statbuf.st_mode); +} + +std::vector SystemInfo::getBlockDevices() +{ + std::vector blockDevices; + std::ifstream file("/proc/partitions"); + std::string line; + + while (std::getline(file, line)) + { + std::istringstream iss(line); + std::string major, minor, blocks, name; + if (!(iss >> major >> minor >> blocks >> name)) + { + continue; + } + + std::string devicePath = "/dev/" + name; + if (devicePath.find("/dev/sd") == 0 && isBlockDevice(devicePath)) + { + blockDevices.push_back(devicePath); + } + } + + return blockDevices; +} + +std::vector SystemInfo::getFileInfoList(std::string parentPath) +{ + std::vector fileInfoList; + Poco::Path path(parentPath); + if (!path.isDirectory()) + { + logger().error("Error: %s is not a directory.", parentPath); + return fileInfoList; + } + try + { + Poco::DirectoryIterator it(parentPath); + Poco::DirectoryIterator end; + + while (it != end) + { + const Poco::Path& p = it.path(); + Util::SystemInfo::FileInfo fileInfo; + fileInfo.name = p.getFileName(); + fileInfo.parentPath = parentPath; + fileInfo.type = it->isDirectory() ? 2 : (it->isFile() ? 1 : 0); + if (it->isDirectory()) + { + } + else if (it->isFile()) + { + Poco::File file(p.toString()); + fileInfo.size = file.getSize(); + fileInfo.lastModifyTime = file.getLastModified().epochTime(); + } + ++it; + fileInfoList.push_back(fileInfo); + } + } + catch (Poco::Exception& e) + { + logger().error("An error occurred: %s", e.displayText()); + } + return fileInfoList; +} + +unsigned long long Util::SystemInfo::getPathFreeSpace(const std::string& path) +{ + struct statvfs stat; + if (statvfs(path.c_str(), &stat) != 0) + { + logger().error("Failed to get filesystem stats for %s", path); + return 0; + } + unsigned long long free = stat.f_bfree * stat.f_frsize; + + return free; +} + +unsigned long long Util::SystemInfo::checkSizeAndDelEarliestFile(const std::string& basePath, + const unsigned int limitMB, + const std::string deletePath, + const std::string& deleteExtName, + const std::set exculdePathOrFiles) +{ + int i = 10; + unsigned long long freeBytes = 0; + while (i-- > 0) + { + freeBytes = getPathFreeSpace(basePath); + unsigned long long limitBytes = static_cast(limitMB) * 1024 * 1024; + logger().information(" freeBytes=%s, limitBytes=%s", std::to_string(freeBytes), std::to_string(limitBytes)); + + if (freeBytes < limitBytes) + { + logger().information(" Space is less than=%s. Deleting earliest file...", std::to_string(limitMB)); + + try + { + std::string findCmd = "find " + deletePath + " -type f -name \"*" + deleteExtName + + "\" -exec stat -c '%mY %n' {} + | sort -n | head -n 1 | cut -d' ' -f2-"; + logger().information("Execute command: %s", findCmd); + std::string oldestFile; + std::array buffer; + std::unique_ptr pipe(popen(findCmd.c_str(), "r"), pclose); + if (!pipe) + { + throw std::runtime_error("popen() failed!"); + } + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) + { + oldestFile += buffer.data(); + } + + if (!oldestFile.empty() && oldestFile[oldestFile.size() - 1] == '\n') + { + oldestFile.erase(oldestFile.size() - 1); + } + + if (!oldestFile.empty()) + { + bool isExecuteDelete = true; + + for (std::string name : exculdePathOrFiles) + { + if (oldestFile.find(name) == 0) + { + isExecuteDelete = false; + break; + } + } + if (isExecuteDelete) + { + try + { + Poco::File file(oldestFile); + + if (file.exists()) + { + file.remove(); + logger().information("File deleted successfully: %s", oldestFile); + } + else + { + logger().information("File does not exist: %s", oldestFile); + } + } + catch (const Poco::Exception& ex) + { + logger().information("Failed to delete file: : %s, error:", oldestFile, ex.displayText()); + } + } + else + { + logger().information("deleted old file -> exculde path or file:%s", oldestFile); + } + } + else + { + logger().information("deleted old file -> No .mp4 files found"); + } + } + catch (const std::exception& e) + { + logger().error("deleted old file -> Error: ", e.what()); + } + } + else + { + break; + } + } + if (freeBytes <= 0) + { + freeBytes = getPathFreeSpace(basePath); + } + return freeBytes; +} + +bool Util::SystemInfo::mountPointEnable(const std::string& localPath) +{ + return Util::SystemInfo::executeCommand("ls " + localPath, 5); +} + +bool Util::SystemInfo::mountCifs(std::string ip, std::string remotePath, std::string localPath, std::string username, + std::string password, std::string vers, std::string charset) +{ + Util::SystemInfo::umount(localPath); + Util::SystemInfo::executeCommand("rm -rf " + localPath); + Util::SystemInfo::executeCommand("mkdir -p " + localPath); + + std::string _charset = charset.empty() ? "" : ",iocharset=" + charset; + std::string mountCmd = Poco::format("mount -t cifs -o username=%s,password=%s,vers=%s%s //%s%s %s", username, + password, vers, _charset, ip, remotePath, localPath); + return Util::SystemInfo::executeCommand(mountCmd); +} +bool Util::SystemInfo::mountNfs(std::string ip, std::string remotePath, std::string localPath, std::string vers) +{ + Util::SystemInfo::umount(localPath); + Util::SystemInfo::executeCommand("rm -rf " + localPath); + Util::SystemInfo::executeCommand("mkdir -p " + localPath); + + std::string mountCmd = Poco::format("mount -t nfs -o nolock,vers=%s %s:%s %s", vers, ip, remotePath, localPath); + return Util::SystemInfo::executeCommand(mountCmd, 5); +} +bool Util::SystemInfo::umount(std::string localPath) +{ + return Util::SystemInfo::executeCommand("umount -f " + localPath); +} + +int Util::SystemInfo::getProcessPID(const std::string& processName) +{ + try + { + DIR* dir = opendir("/proc"); + if (!dir) + { + return 0; + } + + std::string targetName = processName; + size_t lastSlash = targetName.find_last_of('/'); + if (lastSlash != std::string::npos) + { + targetName = targetName.substr(lastSlash + 1); + } + + struct dirent* entry; + int pid = 0; + + while ((entry = readdir(dir))) + { + if (entry->d_type != DT_DIR || !isdigit(entry->d_name[0])) + { + continue; + } + + std::string commPath = std::string("/proc/") + entry->d_name + "/comm"; + std::ifstream commFile(commPath); + if (commFile) + { + std::string commName; + if (std::getline(commFile, commName)) + { + commName.erase(std::remove(commName.begin(), commName.end(), '\n'), commName.end()); + if (commName == targetName) + { + pid = std::stoi(entry->d_name); + break; + } + continue; + } + } + + std::string cmdlinePath = std::string("/proc/") + entry->d_name + "/cmdline"; + std::ifstream cmdlineFile(cmdlinePath); + if (cmdlineFile) + { + std::string cmdline; + if (std::getline(cmdlineFile, cmdline)) + { + size_t nullPos = cmdline.find('\0'); + if (nullPos != std::string::npos) + { + cmdline.resize(nullPos); + } + + lastSlash = cmdline.find_last_of('/'); + std::string exeName = (lastSlash != std::string::npos) ? cmdline.substr(lastSlash + 1) : cmdline; + if (exeName == targetName) + { + pid = std::stoi(entry->d_name); + break; + } + } + } + } + closedir(dir); + return pid; + } + catch (const std::exception& e) + { + logger().error("getProcessPID Error: %s", std::string(e.what())); + } + return 0; +} + +std::vector Util::SystemInfo::getNetworkInterfaceNames() +{ + std::vector interfaceNames; + try + { + Poco::Net::NetworkInterface::List interfaces = Poco::Net::NetworkInterface::list(false, false); + for (const auto& interface : interfaces) + { + if (!interface.isLoopback() + + && !interface.name().empty()) + { + interfaceNames.push_back(interface.name()); + } + } + } + catch (const Poco::Exception& e) + { + logger().error("getNetworkInterfaceNames Error: %s", std::string(e.what())); + } + return interfaceNames; +} + +Util::SystemInfo::NetworkInterfaceInfo Util::SystemInfo::getNetWorkInterfaceInfo(const std::string& interfaceName, + bool returnGateway) +{ + Util::SystemInfo::NetworkInterfaceInfo info; + try + { + auto interface = Poco::Net::NetworkInterface::forName(interfaceName); + if (interface.name() == interfaceName) + { + info.name = interface.name(); + info.displayName = interface.displayName(); + info.ipAddress = interface.address().toString(); + info.subnetMask = interface.subnetMask().toString(); + info.broadcastAddress = interface.broadcastAddress().toString(); + auto macAddress = interface.macAddress(); + info.macAddress = Poco::format("%02X:%02X:%02X:%02X:%02X:%02X", (unsigned)macAddress[0], + (unsigned)macAddress[1], (unsigned)macAddress[2], (unsigned)macAddress[3], + (unsigned)macAddress[4], (unsigned)macAddress[5]); + info.isUp = interface.isUp(); + + if (returnGateway) + { + info.gateway = gatewayAddress(interfaceName).toString(); + } + info.isInitialized = true; + } + } + catch (const Poco::Exception& e) + { + logger().error("Error getNetWorkInterfaceInfo info: %s", std::string(e.what())); + } + return info; +} + +Poco::Net::IPAddress Util::SystemInfo::localAddress(const std::string& interfaceName) +{ + try + { + return Poco::Net::NetworkInterface::forName(interfaceName).address(); + } + catch (...) + { + return Poco::Net::IPAddress("0.0.0.0"); + } +} +std::map Util::SystemInfo::localAddresses() +{ + std::map ipMap; + try + { + Poco::Net::NetworkInterface::List interfaces = Poco::Net::NetworkInterface::list(); + + for (const auto& interface : interfaces) + { + if (interface.isUp() && !interface.isLoopback() && !interface.address().isWildcard() && + interface.address().isUnicast() && interface.address().family() == Poco::Net::AddressFamily::IPv4) + { + ipMap[interface.name()] = interface.address(); + } + } + } + catch (const Poco::Exception& e) + { + logger().error("localAddresses Error: %s", std::string(e.what())); + } + return ipMap; +} + +std::vector Util::SystemInfo::localAddressesList() +{ + std::vector ipList; + try + { + Poco::Net::NetworkInterface::List interfaces = Poco::Net::NetworkInterface::list(); + + for (const auto& interface : interfaces) + { + if (interface.isUp() && !interface.isLoopback()) + { + Poco::Net::NetworkInterface::AddressList addresses = interface.addressList(); + + for (const auto& addressInfo : addresses) + { + const Poco::Net::IPAddress& ip = addressInfo.get<0>(); + + if (!ip.isWildcard() && ip.isUnicast() && ip.family() == Poco::Net::AddressFamily::IPv4) + { + ipList.push_back(ip.toString()); + } + } + } + } + } + catch (const Poco::Exception& e) + { + logger().error("localAddresses Error: %s", std::string(e.what())); + } + return ipList; +} + +std::string Util::SystemInfo::localAddressOfAvailable() +{ + std::map ipMap = localAddresses(); + std::string hostIp = "0.0.0.0"; + if (!ipMap.empty()) + { + hostIp = ipMap.begin()->second.toString(); + } + return hostIp; +} + +std::string Util::SystemInfo::getFirstWirelessInterfaceName() +{ + auto interfaces = getNetworkInterfaceNames(); + for (auto& name : interfaces) + { + std::vector wirelessPatterns = {"wlan", "wi-fi", "wireless", "wifi", "ath", "ra", "wlp", "wlan"}; + for (const auto& pattern : wirelessPatterns) + { + if (Poco::toLower(name).find(pattern) != std::string::npos) + { + return name; + } + } + } + return ""; +} + +Poco::Net::IPAddress Util::SystemInfo::subnetMask(const std::string& interfaceName) +{ + try + { + return Poco::Net::NetworkInterface::forName(interfaceName).subnetMask(); + } + catch (...) + { + return Poco::Net::IPAddress("0.0.0.0"); + } +} + +Poco::Net::NetworkInterface::MACAddress Util::SystemInfo::macAddress(const std::string& interfaceName) +{ + try + { + return Poco::Net::NetworkInterface::forName(interfaceName).macAddress(); + } + catch (...) + { + return Poco::Net::NetworkInterface::MACAddress(); + } +} + +Poco::Net::NetworkInterface::MACAddress Util::SystemInfo::getFirstMacAddress() +{ + auto interfaces = getNetworkInterfaceNames(); + try + { + for (const auto& name : interfaces) + { + return Poco::Net::NetworkInterface::forName(name).macAddress(); + } + return Poco::Net::NetworkInterface::MACAddress(); + } + catch (...) + { + return Poco::Net::NetworkInterface::MACAddress(); + } +} + +bool Util::SystemInfo::netInterfaceIsExist(const std::string& interfaceName) +{ + if (interfaceName.empty()) + { + return false; + } + std::vector interfaceNames = Util::SystemInfo::getNetworkInterfaceNames(); + for (const auto& name : interfaceNames) + { + if (interfaceName == name) + { + return true; + } + } + return false; +} + +bool Util::SystemInfo::netInterfaceIsAvailable(const std::string& interfaceName) +{ + std::vector interfaceNames; + try + { + Poco::Net::NetworkInterface::List interfaces = Poco::Net::NetworkInterface::list(false, false); + for (const auto& interface : interfaces) + { + if (!interface.isLoopback() && interface.isUp() && !interface.name().empty()) + { + if (interfaceName == interface.name()) + { + return true; + } + } + } + } + catch (const Poco::Exception& e) + { + logger().error("netInterfaceIsAvailable Error: %s", std::string(e.what())); + } + return false; +} + +Poco::Net::IPAddress Util::SystemInfo::gatewayAddress(const std::string& interfaceName) +{ +#define BUFLEN 20480 + Poco::Net::IPAddress invalid("0.0.0.0"); + + int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) + { + logger().error("NETLINK socket error"); + return invalid; + } + + char req[BUFLEN] = {0}; + struct nlmsghdr* nh = (struct nlmsghdr*)req; + nh->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + nh->nlmsg_type = RTM_GETROUTE; + nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + struct rtmsg* rtm = (struct rtmsg*)NLMSG_DATA(nh); + rtm->rtm_family = AF_INET; + rtm->rtm_table = RT_TABLE_MAIN; + struct sockaddr_nl nladdr = {0}; + nladdr.nl_family = AF_NETLINK; + if (bind(fd, (struct sockaddr*)&nladdr, sizeof(nladdr)) < 0) + { + logger().error("NETLINK bind error"); + close(fd); + return invalid; + } + if (send(fd, req, sizeof(req), 0) < 0) + { + logger().error("NETLINK send error"); + close(fd); + return invalid; + } + + char buf[BUFLEN] = {0}; + struct iovec iov = {buf, sizeof(buf)}; + struct sockaddr_nl sa; + struct msghdr msg = {&sa, sizeof(sa), &iov, 1, NULL, 0, 0}; + int len = recvmsg(fd, &msg, 0); + if (len < 0) + { + logger().error("NETLINK recvmsg error"); + close(fd); + return invalid; + } + + struct nlmsghdr* nhp; + for (nhp = (struct nlmsghdr*)buf; NLMSG_OK(nhp, len); nhp = NLMSG_NEXT(nhp, len)) + { + struct rtmsg* rtp = (struct rtmsg*)NLMSG_DATA(nhp); + if (rtp->rtm_table != RT_TABLE_MAIN || rtp->rtm_family != AF_INET) + { + continue; + } + struct rtattr* attr = (struct rtattr*)RTM_RTA(rtp); + int attrlen = RTM_PAYLOAD(nhp); + for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen)) + { + if (attr->rta_type != RTA_GATEWAY) + { + continue; + } + + uint32_t gateway = *(uint32_t*)RTA_DATA(attr); + char gwstr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &gateway, gwstr, sizeof(gwstr)); + close(fd); + + return Poco::Net::IPAddress(std::string(gwstr)); + } + } + close(fd); + + return invalid; +} + +bool Util::SystemInfo::executeCommand(const std::string& command, int timeout_sec, std::string& output) +{ + if (timeout_sec <= 0) + { + std::unique_ptr pipe(popen(command.c_str(), "r"), pclose); + if (!pipe) + { + logger().error("Failed to execute command: %s", command); + return false; + } + + char buffer[128]; + while (fgets(buffer, sizeof(buffer), pipe.get()) != nullptr) + { + output += buffer; + } + + int status = pclose(pipe.release()); + int ret = WEXITSTATUS(status); + if (ret != 0) + { + logger().error("Command failed: %s, exit code: %d, error: %s", command, ret, output); + return false; + } + logger().information("Command succeeded: %s, exit code: %d", command, ret); + return true; + } + + output.clear(); + std::string timeoutCommand = "timeout -s KILL " + std::to_string(timeout_sec) + " " + command; + timeoutCommand += " 2>/dev/null || timeout -s KILL -t " + std::to_string(timeout_sec) + " " + command; + + std::unique_ptr pipe(popen(timeoutCommand.c_str(), "r"), pclose); + if (!pipe) + { + logger().error("2Failed to execute command: %s", timeoutCommand); + return false; + } + + char buffer[128]; + while (fgets(buffer, sizeof(buffer), pipe.get()) != nullptr) + { + output += buffer; + } + + int status = pclose(pipe.release()); + int ret = WEXITSTATUS(status); + + if (ret != 0) + { + if (ret == 124 || output.find("time") != std::string::npos) + { + logger().error("2Command timeout: %s", timeoutCommand); + } + else + { + logger().error("2Command failed: %s, exit code: %d, error: %s", timeoutCommand, ret, output); + } + return false; + } + + logger().information("2Command succeeded: %s, exit code: %d", timeoutCommand, ret); + return true; +} +} diff --git a/util/SystemInfo.h b/util/SystemInfo.h new file mode 100644 index 0000000..506f876 --- /dev/null +++ b/util/SystemInfo.h @@ -0,0 +1,140 @@ +#ifndef C42159C1_B275_444E_86D0_9A26B781F354 +#define C42159C1_B275_444E_86D0_9A26B781F354 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include +#include +#include +#include "util/xpack/json.h" +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class SystemInfo +{ +public: + struct MemoryInfo + { + uint32_t total; + uint32_t free; + uint32_t usage; + uint32_t available; + }; + + struct StorageInfo + { + std::string type = ""; + int status = 0; + std::string device = ""; + std::string path = ""; + unsigned long long total = 0; + unsigned long long used = 0; + unsigned long long available = 0; + unsigned long long free = 0; + StorageInfo() = default; + + XPACK(O(type, status, device, path, total, used, available, free)); + }; + + struct FileInfo + { + std::string name; + std::string parentPath = ""; + int type; + off_t size; + time_t lastModifyTime; + XPACK(O(name, parentPath, type, size, lastModifyTime)); + }; + + struct NetworkInterfaceInfo + { + std::string name; + std::string displayName; + std::string ipAddress = "0.0.0.0"; + std::string subnetMask = "0.0.0.0"; + std::string broadcastAddress; + std::string gateway = "0.0.0.0"; + std::string macAddress; + bool isUp; + + bool isInitialized; + + NetworkInterfaceInfo() = default; + XPACK(O(name, displayName, ipAddress, subnetMask, broadcastAddress, gateway, macAddress, isUp)); + }; + +public: + static unsigned cpuUsage(); + static MemoryInfo readMemoryInfo(); + + static std::map getStorageInfo(bool returnSpace = true); + + static std::vector getBlockDevices(); + + static std::vector getFileInfoList(std::string parentPath); + + static unsigned long long getPathFreeSpace(const std::string& path); + + static unsigned long long checkSizeAndDelEarliestFile(const std::string& basePath, const unsigned int limitMB, + const std::string deletePath, + const std::string& deleteExtName, + const std::set exculdePathOrFiles); + + static bool mountPointEnable(const std::string& localPath); + + static bool mountCifs(std::string ip, std::string remotePath, std::string localPath, std::string username, + std::string password, std::string vers = "2.0", std::string charset = ""); + + static bool mountNfs(std::string ip, std::string remotePath, std::string localPath, std::string vers = "4"); + static bool umount(std::string localPath); + + static int getProcessPID(const std::string& processName); + + static std::vector getNetworkInterfaceNames(); + + static NetworkInterfaceInfo getNetWorkInterfaceInfo(const std::string& interfaceName, bool returnGateway = false); + + static bool netInterfaceIsExist(const std::string& interfaceName); + + static bool netInterfaceIsAvailable(const std::string& interfaceName); + + static Poco::Net::IPAddress localAddress(const std::string& interfaceName); + + static std::map localAddresses(); + + static std::vector localAddressesList(); + + static std::string localAddressOfAvailable(); + + static std::string getFirstWirelessInterfaceName(); + + static Poco::Net::IPAddress subnetMask(const std::string& interfaceName); + + static Poco::Net::NetworkInterface::MACAddress macAddress(const std::string& interfaceName); + + static Poco::Net::NetworkInterface::MACAddress getFirstMacAddress(); + + static Poco::Net::IPAddress gatewayAddress(const std::string& interfaceName); + + static bool executeCommand(const std::string& command) + { + return executeCommand(command, 0); + } + + static bool executeCommand(const std::string& command, int timeout_sec) + { + std::string output; + return executeCommand(command, timeout_sec, output); + } + + static bool executeCommand(const std::string& command, int timeout_sec, std::string& output); +}; +} +#endif diff --git a/util/Task.cpp b/util/Task.cpp new file mode 100644 index 0000000..192270b --- /dev/null +++ b/util/Task.cpp @@ -0,0 +1,62 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Task.h" +#include "Logger.h" + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Task::Task(const std::string& name): _name(name) +{ +} + +void Task::run(std::function& isCancelled)> func) +{ + cancel(); + + Poco::FastMutex::ScopedLock lock(_lock); + _pThread = new Poco::Thread(); + _isCancelled = false; + + _pThread->startFunc( + [this, func]() -> void + { + try + { + func(_isCancelled); + } + catch (Poco::Exception& e) + { + logger().error("Task(%s): %s", name(), e.displayText()); + } + catch (std::exception& e) + { + logger().error("Task(%s): %s", name(), std::string(e.what())); + } + }); +} + +void Task::cancel() +{ + if (isRunning()) + { + Poco::FastMutex::ScopedLock lock(_lock); + _isCancelled = true; + _pThread->join(); + _pThread.reset(); + } +} + +bool Task::isRunning() +{ + Poco::FastMutex::ScopedLock lock(_lock); + return !_pThread.isNull() && _pThread->isRunning(); +} +} diff --git a/util/Task.h b/util/Task.h new file mode 100644 index 0000000..df747c9 --- /dev/null +++ b/util/Task.h @@ -0,0 +1,93 @@ +#ifndef EBDC071F_8F58_4C5D_ACB9_7C534616BEC5 +#define EBDC071F_8F58_4C5D_ACB9_7C534616BEC5 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Task +{ +public: + Task(const std::string& name = "Anonymous"); + +public: + void run(std::function& isCancelled)> func); + + template static void run2(Func&& func, Args&&... args) + { + run2WithError(std::forward(func), nullptr, std::forward(args)...); + } + + template + static void run2WithError(Func&& func, std::function onError, Args&&... args) + { + auto boundFunc = std::bind(std::forward(func), std::forward(args)...); + + std::thread( + [boundFunc, onError]() mutable + { + try + { + boundFunc(); + } + catch (const std::exception& e) + { + if (onError) + { + onError(std::current_exception()); + } + else + { + std::cerr << "Util::Task::run2 exception: " << e.what() << std::endl; + } + } + catch (...) + { + if (onError) + { + onError(std::current_exception()); + } + else + { + std::cerr << "Util::Task::run2 exception!" << std::endl; + } + } + }) + .detach(); + } + + void cancel(); + + bool isRunning(); + + const std::string& name() const; + +private: + const std::string _name; + std::atomic _isCancelled; + Poco::FastMutex _lock; + Poco::SharedPtr _pThread; +}; + +inline const std::string& Task::name() const +{ + return _name; +} +} +#endif diff --git a/util/Temperature.cpp b/util/Temperature.cpp new file mode 100644 index 0000000..209c04f --- /dev/null +++ b/util/Temperature.cpp @@ -0,0 +1,38 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Temperature.h" +#include "Device.h" +#include "Application.h" + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ + +float Temperature::read() +{ + Device device("/sys/devices/virtual/mstar/msys/TEMP_R"); + + try + { + device.open(); + char str[64]; + device.read((uint8_t*)str, sizeof(str)); + int temp; + sscanf(str, "Temperature %d", &temp); + device.close(); + return temp; + } + catch (const std::exception& e) + { + } + + return 0.0f; +} +} diff --git a/util/Temperature.h b/util/Temperature.h new file mode 100644 index 0000000..4e19f93 --- /dev/null +++ b/util/Temperature.h @@ -0,0 +1,22 @@ +#ifndef AE8A4A92_D487_4675_9A6A_0B266E47C520 +#define AE8A4A92_D487_4675_9A6A_0B266E47C520 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ + +namespace Util +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Temperature +{ +public: + static float read(); +}; +} +#endif diff --git a/util/WifiUtil.h b/util/WifiUtil.h new file mode 100644 index 0000000..d9d1beb --- /dev/null +++ b/util/WifiUtil.h @@ -0,0 +1,169 @@ +#ifndef WIFI_UTIL_H +#define WIFI_UTIL_H + +#include +#include +#include +#include +#include +#include "Application.h" +#include "util/SystemInfo.h" + +namespace Util +{ +class WifiUtil +{ +private: +public: + static constexpr const char* frpcConfigPathName = "/customer/frp/frpc.toml"; + static constexpr const char* frpcPathName = "/customer/frp/frpc"; + +public: + static Poco::JSON::Object::Ptr wifiStaStatus(const std::string& interfaceName) + { + Poco::JSON::Object::Ptr statusMap = new Poco::JSON::Object; + try + { + std::string retStr; + bool retFlag = Util::SystemInfo::executeCommand("wpa_cli -i " + interfaceName + " status", 10, retStr); + if (retFlag) + { + Poco::StringTokenizer lines(retStr, "\n", Poco::StringTokenizer::TOK_TRIM); + for (const auto& line : lines) + { + size_t eqPos = line.find('='); + if (eqPos != std::string::npos) + { + std::string key = line.substr(0, eqPos); + std::string value = line.substr(eqPos + 1); + statusMap->set(key, value); + } + } + } + } + catch (const std::exception& e) + { + logger().error("wifiStaStatus failed: %s", std::string(e.what())); + } + return statusMap; + } + + static bool wifiStaScan(const std::string& interfaceName, bool wifiEnable = false) + { + try + { + return Util::SystemInfo::executeCommand("/customer/customer-network.sh " + interfaceName + " scan"); + } + catch (const std::exception& e) + { + logger().error("wifiStaScan failed: %s", std::string(e.what())); + } + return false; + } + + static Poco::JSON::Array::Ptr wifiStaScanResults(const std::string& interfaceName) + { + Poco::JSON::Array::Ptr jsonArray = new Poco::JSON::Array; + std::string retStr; + bool retFlag = Util::SystemInfo::executeCommand("wpa_cli -i " + interfaceName + " scan_results", 10, retStr); + if (!retFlag) + { + return jsonArray; + } + + Poco::StringTokenizer lines(retStr, "\n", Poco::StringTokenizer::TOK_TRIM); + + for (size_t i = 1; i < lines.count(); ++i) + { + Poco::StringTokenizer tokens(lines[i], "\t", Poco::StringTokenizer::TOK_TRIM); + if (tokens.count() < 4) + continue; + + Poco::JSON::Object::Ptr netObj = new Poco::JSON::Object; + netObj->set("bssid", tokens[0]); + netObj->set("frequency", std::stoi(tokens[1])); + netObj->set("signal_level", std::stoi(tokens[2])); + + if (tokens.count() == 4 && tokens[3].find('[') == std::string::npos) + { + netObj->set("flags", "OPEN"); + netObj->set("ssid", convertHexEscape(tokens[3])); + } + else + { + netObj->set("flags", tokens[3]); + netObj->set("ssid", tokens.count() > 4 ? convertHexEscape(tokens[4]) : ""); + } + + jsonArray->add(netObj); + } + return jsonArray; + } + + static bool wifiStaConnect(const std::string& interfaceName, int status) + { + if (status == 1) + { + return Util::SystemInfo::executeCommand("/customer/customer-network.sh " + interfaceName + " up"); + } + else if (status == 0) + { + return Util::SystemInfo::executeCommand("/customer/customer-network.sh " + interfaceName + " down"); + } + return false; + } + + static Poco::JSON::Array::Ptr wifiGetApClients(const std::string& interfaceName) + { + Poco::JSON::Array::Ptr jsonArray = new Poco::JSON::Array; + std::string retStr; + + bool retFlag = Util::SystemInfo::executeCommand( + "ip neigh show dev " + interfaceName + " nud reachable | awk '{print $1, $3}'", 10, retStr); + if (!retFlag) + { + return jsonArray; + } + + Poco::StringTokenizer lines(retStr, "\n", Poco::StringTokenizer::TOK_TRIM); + + for (size_t i = 0; i < lines.count(); ++i) + { + if (lines[i].empty()) + continue; + Poco::StringTokenizer tokens(lines[i], " ", Poco::StringTokenizer::TOK_TRIM); + if (tokens.count() < 2) + { + continue; + } + std::string ip = tokens[0], mac = tokens[1]; + + Poco::JSON::Object::Ptr netObj = new Poco::JSON::Object; + netObj->set("ip", ip); + netObj->set("mac", mac); + jsonArray->add(netObj); + } + return jsonArray; + } + + static std::string convertHexEscape(const std::string& input) + { + std::stringstream ss; + for (size_t i = 0; i < input.length();) + { + if (input.substr(i, 2) == "\\x" && i + 3 < input.length()) + { + ss << static_cast(std::stoi(input.substr(i + 2, 2), nullptr, 16)); + i += 4; + } + else + { + ss << input[i]; + i++; + } + } + return ss.str(); + } +}; +} +#endif diff --git a/util/linux-system-usage.hpp b/util/linux-system-usage.hpp new file mode 100644 index 0000000..374aded --- /dev/null +++ b/util/linux-system-usage.hpp @@ -0,0 +1,203 @@ +/* --------------------------------------------------------------------- + * Luiz Carlos doleron @ 2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero Public License for more details. + * ---------------------------------------------------------------------- + */ + +#ifndef _GET_CPU_USAGE_LINUX_ +#define _GET_CPU_USAGE_LINUX_ + +#include +#include +#include +#include + +namespace get_system_usage_linux +{ + + struct CPU_stats + { + // see http://www.linuxhowtos.org/manpages/5/proc.htm + int user; + int nice; + int system; + int idle; + int iowait; + int irq; + int softirq; + int steal; + int guest; + int guest_nice; + + int get_total_idle() + const { + return idle + iowait; + } + + int get_total_active() + const { + return user + nice + system + irq + softirq + steal + guest + guest_nice; + } + + }; + + struct Memory_stats + { + int total_memory; + int available_memory; + int free_memory; + int total_swap; + int free_swap; + + float get_memory_usage() { + const float result = static_cast(total_memory - available_memory) / total_memory; + return result; + } + + float get_swap_usage() { + const float result = static_cast(total_swap - free_swap) / total_swap; + return result; + } + + }; + + inline CPU_stats read_cpu_data() + { + CPU_stats result; + std::ifstream proc_stat("/proc/stat"); + + if (proc_stat.good()) + { + std::string line; + getline(proc_stat, line); + + unsigned int *stats_p = (unsigned int *)&result; + std::stringstream iss(line); + std::string cpu; + iss >> cpu; + while (iss >> *stats_p) + { + stats_p++; + }; + } + + proc_stat.close(); + + return result; + } + + inline int get_val(const std::string &target, const std::string &content) { + int result = -1; + std::size_t start = content.find(target); + if (start != std::string::npos) { + int begin = start + target.length(); + std::size_t end = content.find("kB", start); + std::string substr = content.substr(begin, end - begin); + result = std::stoi(substr); + } + return result; + } + + inline Memory_stats read_memory_data() + { + Memory_stats result; + std::ifstream proc_meminfo("/proc/meminfo"); + + if (proc_meminfo.good()) + { + std::string content((std::istreambuf_iterator(proc_meminfo)), + std::istreambuf_iterator()); + + result.total_memory = get_val("MemTotal:", content); + result.free_memory = get_val("MemFree:", content); + result.total_swap = get_val("SwapTotal:", content); + result.free_swap = get_val("SwapFree:", content); + result.available_memory = get_val("MemAvailable:", content); + + } + + proc_meminfo.close(); + + return result; + } + + inline float get_cpu_usage(const CPU_stats &first, const CPU_stats &second) { + const float active_time = static_cast(second.get_total_active() - first.get_total_active()); + const float idle_time = static_cast(second.get_total_idle() - first.get_total_idle()); + const float total_time = active_time + idle_time; + return active_time / total_time; + } + + inline float get_disk_usage(const std::string & disk) { + struct statvfs diskData; + + statvfs(disk.c_str(), &diskData); + + auto total = diskData.f_blocks; + auto free = diskData.f_bfree; + auto diff = total - free; + + float result = static_cast(diff) / total; + + return result; + } + + //see https://unix.stackexchange.com/questions/304845/discrepancy-between-number-of-cores-and-thermal-zones-in-sys-class-thermal/342023 + + inline int find_thermalzone_index() { + int result = 0; + bool stop = false; + // 20 must stop anyway + for (int i = 0; !stop && i < 20; ++i) { + std::ifstream thermal_file("/sys/class/thermal/thermal_zone" + std::to_string(i) + "/type"); + + if (thermal_file.good()) + { + std::string line; + getline(thermal_file, line); + + if (line.compare("x86_pkg_temp") == 0) { + result = i; + stop = true; + } + + } else { + stop = true; + } + + thermal_file.close(); + } + return result; + } + + inline int get_thermalzone_temperature(int thermal_index) { + int result = -1; + std::ifstream thermal_file("/sys/class/thermal/thermal_zone" + std::to_string(thermal_index) + "/temp"); + + if (thermal_file.good()) + { + std::string line; + getline(thermal_file, line); + + std::stringstream iss(line); + iss >> result; + } else { + throw std::invalid_argument(std::to_string(thermal_index) + " doesn't refer to a valid thermal zone."); + } + + thermal_file.close(); + + return result; + } + +} + +#endif \ No newline at end of file diff --git a/util/structs/ParamStruct.h b/util/structs/ParamStruct.h new file mode 100644 index 0000000..c19cc10 --- /dev/null +++ b/util/structs/ParamStruct.h @@ -0,0 +1,711 @@ +#ifndef PARAMSTRUCT_H +#define PARAMSTRUCT_H + +#include +#include +#include "Application.h" +#include "util/xpack/json.h" + +namespace Util +{ +namespace Structs +{ +struct Channel_stream +{ + std::string sn; + int enable = 0; + std::string name; + std::string appname; + std::string streamid; + int bitrate = 8192; + std::string bitrate_ctl; + std::string codec; + int encoded_size_mode = 0; + std::string encoded_size; + int fps = 30; + int gopmode = 0; + int goptxt = 30; + int video_flip_mirror_enable = 0; + int video_clipping_enable = 0; + int video_clipping_left = 0; + int video_clipping_right = 0; + int video_clipping_width = 100; + int video_clipping_height = 100; + int video_rotate = 0; + std::string pkey; + std::string rtmp_url; + std::string rtsp_url; + std::string flv_url; + std::string hls_url; + std::string webrtc_url; + std::string ts_url; + std::string srt_url; + std::string h5_url; + int multicast_enable = 1; + std::string multicast_ip_port; + int push_enable = 1; + std::string push_url; + int push_connected = 0; + Channel_stream() = default; + + XPACK(O(enable, sn, name, appname, streamid, bitrate, bitrate_ctl, codec, encoded_size_mode, encoded_size, fps, + gopmode, goptxt, video_flip_mirror_enable, video_clipping_enable, video_clipping_left, video_clipping_right, + video_clipping_width, video_clipping_height, video_rotate, pkey, rtmp_url, rtsp_url, flv_url, hls_url, + webrtc_url, ts_url, srt_url, multicast_enable, multicast_ip_port, push_enable, push_url), + C(c_push_connected, F(0), push_connected)) + + template + bool c_push_connected_decode(Decoder& de, Channel_stream& t, const char* name, int& data, const xpack::Extend* ext) + { + (void)t; + bool ret = false; + try + { + std::string raw; + ret = de.decode(name, raw, ext); + if (ret) + { + sscanf(raw.c_str(), "%i", &data); + } + } + catch (const std::exception& e) + { // TODO:如果原则本身就是整数,会转换失败,怎么办??? + ret = de.decode(name, data, ext); + } + return ret; + } + + template + bool c_push_connected_encode(Encoder& en, const Channel_stream& t, const char* name, const int& data, + const xpack::Extend* ext) + { + (void)t; + + bool ret = en.encode(name, data, ext); + return ret; + } +}; + +struct Channel_input_type_mix_layer +{ + int chn_id = 0; + int stream_id = 0; + int audio_status = 0; + float pos_x = 0.0f; + float pos_y = 0.0f; + float pos_w = 0.3333f; + float pos_h = 0.3333f; + int z_index = 0; + Channel_input_type_mix_layer() = default; + XPACK(O(chn_id, stream_id, audio_status, pos_x, pos_y, pos_w, pos_h, z_index)) +}; + +struct Channel_input_type_mix +{ + std::string layout_id; + std::vector layers; + Channel_input_type_mix() = default; + XPACK(O(layout_id, layers)) +}; + +struct Channel_input_type_ndi +{ + std::string ndirecv_name; + Channel_input_type_ndi() = default; + XPACK(O(ndirecv_name)) +}; + +struct Channel_input_type_net +{ + int stream_mode = 0; + int min_delay = 500; + std::string path; + int rtp_type = 0; + std::string appname; + std::string streamid; + std::string push_remote_ip; + std::string pull_path; + int proxy_status; + + int ipc_onvif = 0; + std::string ipc_ip; + int ipc_port = 8000; + int ipc_web_port = 80; + std::string ipc_username = "admin"; + std::string ipc_password; + std::string ipc_ptz_xaddr; + std::string ipc_ptz_profile_token; + Channel_input_type_net() = default; + XPACK(O(stream_mode, min_delay, path, rtp_type, pull_path, proxy_status, appname, streamid, push_remote_ip, + ipc_onvif, ipc_ip, ipc_port, ipc_web_port, ipc_username, ipc_password, ipc_ptz_xaddr, + ipc_ptz_profile_token)) +}; + +struct Channel_input_type_usb +{ + std::string alsa; + int channel = -1; + std::string lnk; + Channel_input_type_usb() = default; + XPACK(O(alsa, channel, lnk)) +}; + +struct Channel_audio_set +{ + std::vector audio_src = {0}; + int audio_channel = 0; + int digital_volume_gain = 0; + int pts_offset = 0; + int encoder_bitrate = 128; + int encoder_methods = 0; + int sampling_rate = -1; + int collected_audio_frame = 0; + Channel_audio_set() = default; + XPACK(O(audio_src, audio_channel, digital_volume_gain, pts_offset, encoder_bitrate, encoder_methods, sampling_rate, + collected_audio_frame)) +}; + +struct Channel_input_type_digimage_point +{ + float x = 0; + float y = 0; + Channel_input_type_digimage_point() = default; + XPACK(O(x, y)) +}; + +struct Channel_input_type_digimage +{ + int auto_update = 0; + int tolerance = 0; + int src_fg_channel = 0; + std::string src_bg_type; + std::string src_bg_image; + int src_bg_channel = 0; + std::vector point; + Channel_input_type_digimage() = default; + XPACK(O(auto_update, tolerance, src_fg_channel, src_bg_type, src_bg_image, src_bg_channel, point)) +}; + +struct Channel_input_type_carousel_file +{ + std::string filename; + int video_len = 0; + Channel_input_type_carousel_file() = default; + XPACK(O(filename, video_len)) +}; + +struct Channel_input_type_carousel +{ + int decodeA = 0; + int decodeV = 0; + int playing_file_index = -1; + long playing_file_position = 0; + std::vector files; + Channel_input_type_carousel() = default; + XPACK(O(decodeA, decodeV, files), C(c_playing_file_index, F(0), playing_file_index)) + + template + bool c_playing_file_index_decode(Decoder& de, Channel_input_type_carousel& t, const char* name, int& data, + const xpack::Extend* ext) + { + (void)t; + bool ret = false; + try + { + std::string raw; + ret = de.decode(name, raw, ext); + if (ret) + { + sscanf(raw.c_str(), "%i", &data); + } + } + catch (const std::exception& e) + { // TODO:如果原则本身就是整数,会转换失败,怎么办??? + ret = de.decode(name, data, ext); + } + return ret; + } + + template + bool c_playing_file_index_encode(Encoder& en, const Channel_input_type_carousel& t, const char* name, + const int& data, const xpack::Extend* ext) + { + (void)t; + + bool ret = en.encode(name, data, ext); + return ret; + } +}; + +struct Channel_channel +{ + int enable = 0; + std::string name = "HDMI"; + std::string input_type = "HDMI"; + int input_video_framerate = 0; + std::string input_video_size; + int input_video_collected_frame = 0; + int input_video_lost_frame = 0; + Channel_audio_set audio_set; + std::vector streams; + Channel_input_type_digimage input_type_digimage; + Channel_input_type_carousel input_type_carousel; + Channel_input_type_mix input_type_mix; + Channel_input_type_ndi input_type_ndi; + Channel_input_type_net input_type_net; + Channel_input_type_usb input_type_usb; + Channel_channel() = default; + XPACK(O(enable, name, input_type, input_video_framerate, input_video_size, input_video_collected_frame, + input_video_lost_frame, audio_set, streams, input_type_digimage, input_type_carousel, input_type_mix, + input_type_ndi, input_type_net, input_type_usb)) +}; + +struct Channel_params +{ + std::vector channels; + Channel_params() = default; + XPACK(O(channels)) +}; + +struct Record_record +{ + int enable = 0; + int flv = 0; + int mp4 = 1; + int mp4_max_second = 600; + int status = 0; + + std::string start_time; + + Record_record() = default; + XPACK(O(enable, flv, mp4, mp4_max_second, status, start_time)) +}; + +struct Record_params +{ + std::string base_path; + int loop_storage_enable = 0; + int loop_storage_min_size = 1024; + std::vector> records; + + Record_params() = default; + XPACK(O(base_path, loop_storage_enable, loop_storage_min_size, records)) +}; + +struct Gb28181_chn +{ + int enable = 0; + std::string gb_chn_id; + std::string gb_chn_name; + int stream_state = 0; + Gb28181_chn() = default; + XPACK(O(enable, gb_chn_id, gb_chn_name, stream_state)) +}; + +struct Gb28181_params +{ + int enable = 0; + std::string spec_model = "H.264/H.265 Encoder"; + std::string manufacture = "Encoder"; + int local_sip_port = 5061; + int con_state = 0; + std::string auth_id; + std::string auth_passwd; + int heartbeat_cycle = 30; + int heartbeat_timeout_cnt = 3; + int registration_period = 86400; + std::string sip_server_addr; + std::string sip_server_domain; + std::string sip_server_id; + int sip_server_port = 5060; + std::vector> gb28181_chns; + Gb28181_params() = default; + XPACK(O(enable, spec_model, manufacture, local_sip_port, con_state, auth_id, auth_passwd, heartbeat_cycle, + heartbeat_timeout_cnt, registration_period, sip_server_addr, sip_server_domain, sip_server_id, + sip_server_port, gb28181_chns)) +}; + +struct Loopout_audio +{ + int enable = 1; + int chn_id = 0; + + int audio_volume_gain; + Loopout_audio() = default; + XPACK(O(enable, chn_id, audio_volume_gain)) +}; +struct Loopout_video +{ + int enable = 1; + int low_latency = 0; + std::string encoded_size; + int rotate = 0; + int mirror = 0; + int chn_id = 0; + int stream_id = 0; + std::string type; + int ui = 0; + int vid = 0; + std::string matrix; + int luma = 64; + int contrast = 64; + int saturation = 64; + int hue = 64; + int audio_volume_gain; + Loopout_video() = default; + XPACK(O(enable, low_latency, encoded_size, rotate, mirror, chn_id, stream_id, type, ui, vid, matrix, luma, contrast, + saturation, hue, audio_volume_gain)) +}; + +struct Loopout_params +{ + Loopout_audio audio; + Loopout_video hdmi; + Loopout_video sdi; + Loopout_params() = default; + XPACK(O(hdmi, sdi)) +}; + +struct Mix_lay_def_layer +{ + int chn_id = -1; + int stream_id = 0; + int audio_status = 0; + int lock = 0; + float pos_x = 0.0f; + float pos_y = 0.0f; + float pos_w = 0.3333f; + float pos_h = 0.3333f; + int z_index = 0; + Mix_lay_def_layer() = default; + XPACK(O(chn_id, stream_id, audio_status, lock, pos_x, pos_y, pos_w, pos_h, z_index)) +}; + +struct Mix_lay_def_layout +{ + std::string layout_id; + std::string layout_name; + int enable = 1; + std::vector layers; + Mix_lay_def_layout() = default; + XPACK(O(layout_id, layout_name, layers)) +}; + +struct Mix_lay_def +{ + std::vector layouts; + Mix_lay_def() = default; + XPACK(O(layouts)) +}; + +struct Osd_osd +{ + int enable = 0; + int chn_id = 0; + int stream_id = 0; + std::string type = "pic"; + std::string content; + std::string font; + std::string color; + std::string bgcolor; + float alpha = 0; + float scale = 0; + int speed = 0; + float x = 0; + float y = 0; + int z = 0; + float w = 0; + float h = 0; + Osd_osd() = default; + XPACK(O(enable, chn_id, stream_id, type, content, font, color, bgcolor, alpha, scale, speed, x, y, z, w, h)) +}; + +struct Osd_resource +{ + std::string path; + std::string time; + std::string type; + Osd_resource() = default; + XPACK(O(path, time, type)) +}; + +struct Osd_params +{ + std::vector>> osds; + std::vector resources; + Osd_params() = default; + XPACK(O(osds, resources)) +}; + +struct Push_url +{ + int enable = 0; + std::string des; + std::string url; + int stream_state = 0; + + Push_url() = default; + XPACK(O(enable, des, url, stream_state)) +}; + +struct Push_params +{ + int srcA = -1; + int srcV = 0; + int srcV_stream = 0; + int autorun = 0; + std::string start_day; + std::string start_time; + std::string stop_day; + std::string stop_time; + std::vector urls; + Push_params() = default; + XPACK(O(srcA, srcV, srcV_stream, autorun, start_day, start_time, stop_day, stop_time, urls)) +}; + +struct Status_params +{ + std::string version; + std::string image_version; + std::string serial_num; + float cpu_usage = 0; + float cpu_junc_tmp = 0; + float memory_free = 0; + float memory_total = 0; + std::string runtime; + std::string device_time; + int net_state_up = 0; + int net_state_dw = 0; + std::string fireware_download_url; + std::string fireware_upgrade_last_log; + Status_params() = default; + XPACK(O(version, serial_num, cpu_usage, cpu_junc_tmp, memory_free, memory_total, runtime, device_time, net_state_up, + net_state_dw, fireware_download_url, fireware_upgrade_last_log)) +}; + +struct System_http +{ + std::string user_name; + std::string user_password; + std::string language = "zh"; + int api_auth = 0; + System_http() = default; + XPACK(O(user_name, user_password, language, api_auth)) +}; + +struct System_service_ports +{ + int nat_port_enable = 0; + std::string nat_port_host = ""; + int http_api_port = 22112; + int http_api_port_nat_port = 22112; + int http_port = 80; + int http_port_nat_port = 80; + int rtmp_port = 1935; + int rtmp_port_nat_port = 1935; + int rtsp_port = 554; + int rtsp_port_nat_port = 554; + int srt_port = 9000; + int srt_port_nat_port = 9000; + int nat_frp_enable = 0; + int nat_frp_pid = 0; + System_service_ports() = default; + XPACK(O(nat_port_enable, nat_port_host, http_api_port, http_api_port_nat_port, http_port, http_port_nat_port, + rtmp_port, rtmp_port_nat_port, rtsp_port, rtsp_port_nat_port, srt_port, srt_port_nat_port, nat_frp_enable, + nat_frp_pid)) +}; + +struct System_share_storage_cifs +{ + int enable = 0; + std::string ip; + std::string path; + std::string username; + std::string password; + std::string local_path = "/mnt/storages/cifs"; + std::string vers = "2.0"; + System_share_storage_cifs() = default; + XPACK(O(enable, ip, path, username, password, local_path, vers)) +}; + +struct System_share_storage_nfs +{ + int enable = 0; + std::string ip; + std::string path; + std::string local_path = "/mnt/stroages/nfs"; + std::string vers = "4"; + System_share_storage_nfs() = default; + XPACK(O(enable, ip, path, local_path, vers)) +}; + +struct System_share_storage +{ + System_share_storage_cifs cifs; + System_share_storage_nfs nfs; + System_share_storage() = default; + XPACK(O(cifs, nfs)) +}; + +struct Network_set_config +{ + int enable = 0; + int dhcp_enable = 0; + std::string host_ip; + std::string mac_addr; + std::string netmask; + std::string gateway; + std::string dns1; + std::string dns2; + std::string ssid; + std::string ssid_pwd; + std::string ssid_mode = "STA"; + Network_set_config() = default; + XPACK(O(enable, dhcp_enable, host_ip, mac_addr, netmask, gateway, dns1, dns2, ssid, ssid_pwd, ssid_mode)) +}; + +struct Network_set +{ + Network_set_config eth0; + Network_set_config wlan0; + Network_set() = default; + XPACK(O(eth0, wlan0)) +}; + +struct Time_set +{ + int ntp_enable = 1; + std::string ntp_server = "ntp.ntsc.ac.cn"; + int ntp_server_port = 123; + int ntp_idle = 60; + std::string time_zone = "8"; + Time_set() = default; + XPACK(O(ntp_enable, ntp_server, ntp_server_port, ntp_idle, time_zone)) +}; + +struct Schedule_restart +{ + int restart_enable = 0; + std::string restart_time = "06:10"; + Schedule_restart() = default; + XPACK(O(restart_enable, restart_time)) +}; + +struct System_serialtrans +{ + std::string type = "232"; + int work_mode = 0; + int baudrate = 9600; + std::string remote_addr; + int remote_port = 10000; + int listen_port = 10000; + System_serialtrans() = default; + XPACK(O(type, work_mode, baudrate, remote_addr, remote_port, listen_port)) +}; + +struct System_nattraversal_frp +{ + int enable = 0; + std::string remote_addr; + int remote_port = 10001; + int listen_port = 10001; + System_nattraversal_frp() = default; + XPACK(O(enable, remote_addr, remote_port, listen_port)) +}; + +struct System_nattraversal_rtty +{ + int enable = 0; + std::string name; + std::string remote_addr; + std::string id = "dfdfdsfdfdsfds"; + std::string token; + System_nattraversal_rtty() = default; + XPACK(O(enable, name, remote_addr, id, token)) +}; + +struct System_nattraversal +{ + System_nattraversal_frp frp; + System_nattraversal_rtty rtty; + XPACK(O(frp, rtty)) +}; + +struct System_moresettings +{ + std::string logo_title = "H.265/H.264 Encoder"; + std::string logo_image; + std::string menu_location = "left"; + std::string h5play_title = ""; + int screen_turn_time = 5; + System_moresettings() = default; + XPACK(O(logo_title, logo_image, menu_location, h5play_title, screen_turn_time)) +}; + +struct System_params +{ + System_http http; + Network_set network_set; + Time_set time_set; + Schedule_restart schedule_restart; + std::vector serialtrans; + System_nattraversal nattraversal; + System_moresettings moresettings; + System_service_ports service_ports; + System_share_storage share_storage; + System_params() = default; + XPACK(O(http, network_set, time_set, schedule_restart, serialtrans, nattraversal, moresettings, service_ports, + share_storage)) +}; + +struct Onvif_chn +{ + uint32_t enable = 1; + + Onvif_chn() = default; + XPACK(O(enable)) +}; + +struct Onvif_params +{ + int auth_enable = 0; + std::string auth_username = "admin"; + std::string auth_password = "admin"; + int time_sync_enable = 0; + uint32_t enable = 1; + std::string device_name = "H.264/H.265 Encoder"; + std::string spec_model = "H.264/H.265 Encoder"; + std::string manufacture = "DaweiVision"; + std::string firmware_version; + std::string serial_number; + std::string hardware_id = "1.0"; + std::string device_type = "Encoder"; + std::string ip; + std::string mac; + uint web_service_bind_port = 8000; + std::string location_country = "China"; + std::string location_city = "Chengdu"; + std::vector> chns; + + Onvif_params() = default; + XPACK(O(auth_enable, auth_username, auth_password, time_sync_enable, enable, device_name, spec_model, manufacture, + firmware_version, serial_number, hardware_id, device_type, ip, mac, web_service_bind_port, location_country, + location_city, chns)) +}; + +struct Diskinfo +{ + int status = 0; + std::string device = ""; + std::string path = ""; + unsigned long long total = 0; + unsigned long long used = 0; + unsigned long long available = 0; + unsigned long long free = 0; + + Diskinfo() = default; + XPACK(O(status, device, path, total, used, available, free)) +}; +} +} +#endif diff --git a/util/util.cmake b/util/util.cmake new file mode 100644 index 0000000..fb748d7 --- /dev/null +++ b/util/util.cmake @@ -0,0 +1,3 @@ +add_subdirectory(util) +include_directories(util) +target_link_libraries(${PROJNAME} PRIVATE util) \ No newline at end of file diff --git a/util/xpack/LICENSE b/util/xpack/LICENSE new file mode 100755 index 0000000..29f81d8 --- /dev/null +++ b/util/xpack/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/util/xpack/README-bson.md b/util/xpack/README-bson.md new file mode 100755 index 0000000..d0fdaaf --- /dev/null +++ b/util/xpack/README-bson.md @@ -0,0 +1,84 @@ +* 用于在C++结构体和bson之间互相转换 +* 这个库可以配合[mongoxclient](https://github.com/xyz347/mongoxclient)使用 + +# 目录 +- [encoder](#encoder) +- [decoder](#decoder) +- [bson types](#bsontypes) +- [builder](#builder) +- [重要说明](#重要说明) + +## encoder +用于将结构体转成bson格式。范例: +```C++ +struct Test { + int64_t id; + std::string name; + std::string email; + XPACK(A(id, "bson:_id"), O(name, email)); +}; + +Test t; +std::string data = xpack::bson::encode(t); // data就是t转成bson的数据 +``` + +## decoder +用于将bson数据转成结构体 +用于将结构体转成bson格式。范例: +```C++ +struct Test { + int64_t id; + std::string name; + std::string email; + XPACK(A(id, "bson:_id"), O(name, email)); +}; + +std::string data; +// data = xxxx; data存储了相应的bson数据 +Test t; +xpack::bson::decode(data, t); +// xpack::bson::decode 还有另一个参数方式:static void decode(const uint8_t* data, size_t len, T &val) +``` + +## bsontypes +- date time: `bson_date_time_t` +- timestamp: `bson_timestamp_t` +- binary: `bson_binary_t` +- regex: `bson_regex_t` +- oid: `bson_oid_t` +- decimal128: `bson_decimal128_t` + +前4种是xbson额外定义的,后面两种是定义在libbson内的,为了统一,前4种也是定义在全局命名空间。 + +## builder +**需要C++11或以上版本支持**。用json的格式来构造bson数据。查询接口中的query,一般只需要指定很少几个字段,这个场景用结构体表达不是很方便。用builder就会简单很多。基本用法是: +1. 定义一个BsonBuilder: + - 参数是一个json格式的字符串 + - 里面的双引号用单引号替代 + - 变量用问号作为占位符,问号后面可以加一个单词用来作为助记符,比如"?"和"?uid"是等效的 + - key也可以是变量,key作为变量,问号不用加引号 + - BsonBuilder是线程安全的,建议定义为static变量,这样json字符串只需解析一次 +范例: +```C++ +static xpack::BsonBuilder bd("{'_id':?, ?:?}"); // 带占位符的builder +static xpack::BsonBuilder empty("{}"); // 空bson +static xpack::BsonBuilder student("{'type':'student'}"); // 不带占位符的builder +``` + +2. 调用Encode函数来构造bson数据,Encode的参数对应的是各个占位符,因此参数个数应该和占位符一致 +比如对于上述的BsonBuilder +```C++ +std::string data1 = bd.Encode(12345, "type", "master"); +std::string data2 = empty.Encode(); +std::string data3 = student.Encode(); +// 这些bson的数据可以用在mongoxclient的各个接口中 +``` + +3. 其他的一些API + - EncodeAsJson 和Encode类似,但是输出的是json格式的数据,可以用于调试等场景(所以这个其实也是个JsonBuilder) + - Error 如果构造BsonBuilder的json字符串有误,这个接口可以返回错误信息 + - static std::string En(const std::string&fmt, Args... args)。这个是把构造BsonBuilder和Encode合在一起了,不是很建议使用,建议用于调试,因为每次都要构造一次BsonBuilder + + +## 重要说明 +- 依赖于[libbson](https://github.com/mongodb/libbson/tree/1.0.0),需自行安装 diff --git a/util/xpack/README-en.md b/util/xpack/README-en.md new file mode 100755 index 0000000..36ad955 --- /dev/null +++ b/util/xpack/README-en.md @@ -0,0 +1,407 @@ +xpack +==== +* Used to convert between C++ structure and json/xml, bson is supported in [xbson](https://github.com/xyz347/xbson). +* Only header files, no need to compile library files, so there is no Makefile. +* Support MySQL, depends on `libmysqlclient-dev`, need to install by yourself. **not fully tested** +* Support Sqlite, depends on [libsqlite3](https://cppget.org/libsqlite3), need to install it yourself. **not fully tested** +* Support yaml, depend on [yaml-cpp](https://github.com/jbeder/yaml-cpp), need to install it yourself. **not fully tested** +* For details, please refer to the example + +------ +* [Quick start](#quick-start) +* [Container Support](#container-support) +* [FLAG](#flag) +* [Alias](#alias) +* [Bitfield](#bitfield) +* [Inherit](#inherit) +* [enum](#enum) +* [Custom codec](#custom-codec) +* [union](#union) +* [Schema indeterminate type](#schema-indeterminate-type) +* [Define macro outside the structure](#define-macro-outside-the-structure) +* [Array](#array) +* [Format indentation](#format-indentation) +* [XML array](#xml-array) +* [CDATA](#cdata) +* [Qt support](#qt-support) +* [Important note](#important-note) + +Quick start +---- +- Use macro `XPACK`(or `XPACK_OUT` if you don't want to change the structure) include structure members +- Use xpack::json::encode to convert structure to json +- Use xpack::json::decode to convert json to structure + +```C++ +#include +#include "xpack/json.h" // include this header file for json or "xpack/xml.h" for xml + +using namespace std; + +struct User { + int id; + string name; + XPACK(O(id, name)); // Add XPACK at the end of structure, up to 99 'O' can be added, and each 'O' can contain up to 99 members +}; + +int main(int argc, char *argv[]) { + User u; + string data = "{\"id\":12345, \"name\":\"xpack\"}"; + + xpack::json::decode(data, u); // json to structure + cout< +- map // only for json +- unordered_map (need C++11) +- shared_ptr (need C++11) + +FLAG +---- +In the macro `XPACK`, variables need to be enclosed with letters, such as `XPACK(O(a,b))`, `XPACK` can contain multiple letters, and each letter can contain multiple variables. Currently supported letters are: +- X Usage: X(F(flag1, flag2...), member1, member2,...) Multiple flags can be written in 'F', currently supported flags are: + - 0 no flag + - OE omitempty. When encoding, if the variable is 0 or an empty string or false, the variable is ignored + - EN empty as null. When json encoding, encode empty variable as null(OE is ignore the variable) + - M mandatory. When decoding, if the key corresponding to the variable does not exist, an exception is thrown + - ATTR attribute. When xml encode, put the value in attribute + - SL single line, When json encoding, put vector in single line +- C Usage: C(customcodec, F(flag1,flags...), member1, member2,...) For custom codec function, please refer to [Custom codec](#custom-codec) for details +- O Usage: O(member1, member2, ...) Same as X(F(0), member1, member2, ...) no flags +- M Usage: M(member1, member2, ...) Same as X(F(M), member1, member2, ...) mandatory +- A Usage: A(member1, alias1, member2, alias2...) Used when the key and the variable name are different, please refer to [Alias](#alias) for details +- AF Usage: AF(F(flag1, flag2,...), member1, alias1, member2, alias2...) Alias with flags +- B Usage: B(F(flag1, flag2, ...), member1, member2, ...) For bitfield, please refer to [Bitfield](#bitfield) for details. **bitfield not support alias** +- I Usage: I(baseclass1, baseclass2....), baseclassx is parent class, please refer to [Inherit](#inherit) for details +- E Usage: E(member1, member2) For enumeration variables that do not support C++11. **E not support alias** + +Alias +---- +- Used when the key and the variable name are different +- Usage: A(member1, alias1, member2, alias2...) or AF(F(flag1, flag2,...), member1, alias1, member2, alias2...), alias's format is "x t:n" + - x stands for global alias, not required + - t stands for type(currently support json/xml/bson), n stands for type alias, type alias is not required + - The order of precedence is: type alias, global alias, variable name + - For example, `id bson:_id`, in bson will use `_id` and json/xml will use `id` +- Type alias can take some type flags, format is "t:n,flag1,flag2,...", currently supported flags are: + - cdata. For xml, indicates that it is cdata + - sbs. For xml vector, In normal, `vector nums` will be encoded into `12`, in sbs is `12` + +``` C++ +#include +#include "xpack/json.h" + +using namespace std; + +struct Test { + long uid; + string name; + XPACK(A(uid, "id"), O(name)); // "uid" use alias "id" +}; + +int main(int argc, char *argv[]) { + Test t; + string json="{\"id\":123, \"name\":\"Pony\"}"; + + xpack::json::decode(json, t); + cout< +#include "xpack/json.h" + +using namespace std; + +struct Test { + short ver:8; + short len:8; + string name; + XPACK(B(F(0), ver, len), O(name)); +}; + +int main(int argc, char *argv[]) { + Test t; + string json="{\"ver\":4, \"len\":20, \"name\":\"IPv4\"}"; + + xpack::json::decode(json, t); + cout< +#include "xpack/json.h" + +using namespace std; + +struct P1 { + string mail; + XPACK(O(mail)); +}; + +struct P2 { + long version; + XPACK(O(version)); +}; + +struct Test:public P1, public P2 { + long uid; + string name; + XPACK(I(P1, P2), O(uid, name)); +}; + +int main(int argc, char *argv[]) { + Test t; + string json="{\"mail\":\"pony@xpack.com\", \"version\":2019, \"id\":123, \"name\":\"Pony\"}"; + + xpack::json::decode(json, t); + cout< +#include "xpack/json.h" + +using namespace std; + +enum Enum { + X = 0, + Y = 1, + Z = 2, +}; + +struct Test { + string name; + Enum e; + XPACK(O(name), E(F(0), e)); +}; + +int main(int argc, char *argv[]) { + Test t; + string json="{\"name\":\"IPv4\", \"e\":1}"; + + xpack::json::decode(json, t); + cout< +#include "xpack/json.h" + +using namespace std; + + +struct Test { + char name[64]; + char email[64]; + XPACK(O(name, email)); +}; + +int main(int argc, char *argv[]) { + Test t; + string json="{\"name\":\"Pony\", \"email\":\"pony@xpack.com\"}"; + + xpack::json::decode(json, t); + cout< +#include +#include "xpack/json.h" + +using namespace std; + +/* +struct timeval { + time_t tv_sec; + suseconds_t tv_usec; +}; +*/ + +// timeval is thirdparty struct +XPACK_OUT(timeval, O(tv_sec, tv_usec)); + +struct T { + int a; + string b; + timeval t; + XPACK(O(a, b, t)); +}; + + +int main(int argc, char *argv[]) { + T t; + T r; + t.a = 123; + t.b = "xpack"; + t.t.tv_sec = 888; + t.t.tv_usec = 999; + string s = xpack::json::encode(t); + cout< + 1 + 2 + 3 + +``` +- You can use alias to change the label name. For example: A(ids,"xml:ids,vl@id"), vl@id means to use id as label: +``` xml + + 1 + 2 + 3 + +``` +- If want to encode array element side by side, can use `sbs` flag. For example: `A(ids, "xml:ids,sbs")`. **Note that the sbs tag can only be used for arrays(vector), and it may crash if used elsewhere** +``` xml +1 +2 +3 +``` + +CDATA +---- +- For xml CDATA, need to use `cdata` flag. For example: A(data, "xml:data,cdata") +- variable type must be std::string +- Non-cdata types can also add cdata flag, which will be processed as ordinary strings + +Qt support +---- +- Modify [config.h](config.h) to enable XPACK_SUPPORT_QT(or enable it in compile flags) +- Currently supports: QString/QMap/QList/QVector + + +Important note +---- +- Try not to start the variable name with __x_pack, otherwise it may conflict with the library. +- vc6 is not supported +- msvc has only been tested on vs2019 +- The serialization and deserialization of json uses [rapidjson](https://github.com/Tencent/rapidjson) (November 2018 edition) +- The deserialization of xml uses [rapidxml](http://rapidxml.sourceforge.net) +- The serialization of xml is written by myself, without reference to RFC, there may be some differences from the standard. diff --git a/util/xpack/README.md b/util/xpack/README.md new file mode 100755 index 0000000..9466bb7 --- /dev/null +++ b/util/xpack/README.md @@ -0,0 +1,418 @@ +xpack +==== +[English](README-en.md) +* 用于在C++结构体和json/xml/yaml/bson/mysql/sqlite之间互相转换 +* 只有头文件, 无需编译库文件,所以也没有Makefile。 +* 支持bson,依赖于`libbson-1.0`,需自行安装。**未经充分测试**,具体请参考[README](README-bson.md) +* 支持MySQL,依赖于`libmysqlclient-dev`,需自行安装。**未经充分测试** +* 支持Sqlite,依赖于[libsqlite3](https://cppget.org/libsqlite3),需自行安装。**未经充分测试** +* 支持yaml,依赖于[yaml-cpp](https://github.com/jbeder/yaml-cpp),需自行安装。**未经充分测试** +* 具体可以参考example的例子 + +------ +* [基本用法](#基本用法) +* [容器支持](#容器支持) +* [FLAG](#flag) +* [别名](#别名) +* [位域](#位域) +* [继承](#继承) +* [枚举](#枚举) +* [自定义编解码](#自定义编解码) +* [union](#union) +* [不定类型](#不定类型) +* [数组](#数组) +* [第三方类和结构体](#第三方类和结构体) +* [格式化缩进](#格式化缩进) +* [XML数组](#xml数组) +* [CDATA](#cdata) +* [Qt支持](#qt支持) +* [MySQL](#mysql) +* [重要说明](#重要说明) + +基本用法 +---- +- 结构体后面用XPACK宏包含各个变量,XPACK内还需要一个字母,不同字母的意义请参考[FLAG](#flag) +- 用xpack::json::encode把结构体转json +- 用xpack::json::decode把json转结构体 +```C++ +#include +#include "xpack/json.h" // Json包含这个头文件,xml则包含xpack/xml.h + +using namespace std; + +struct User { + int id; + string name; + XPACK(O(id, name)); // 添加宏定义XPACK在结构体定义结尾 +}; + +int main(int argc, char *argv[]) { + User u; + string data = "{\"id\":12345, \"name\":\"xpack\"}"; + + xpack::json::decode(data, u); // json转结构体 + cout< +- map // 仅JSON,XML不支持 +- unordered_map (需要C++11支持) +- shared_ptr (需要C++11支持) + +FLAG +---- +宏XPACK里面,需要用字母将变量包含起来,比如XPACK(O(a,b)),XPACK可以包含多个字母,每个字母可以包含多个变量。目前支持的字母有: +- X。格式是X(F(flag1, flag2...), member1, member2,...) F里面包含各种FLAG,目前支持的有: + - 0 没有任何FLAG + - OE omitempty,encode的时候,如果变量是0或者空字符串或者false,则不生成对应的key信息 + - EN empty as null, 用于json的encode,OE是直接不生成empty的字段,EN则是生成一个null + - M mandatory,decode的时候,如果这个字段不存在,则抛出异常,用于一些id字段。 + - ATTR attribute,xml encode的时候,把值放到attribute里面。 + - SL single line, json encode的时候,对于数组,放在一行里面 +- C。格式是C(customcodec, F(flag1,flags...), member1, member2,...)用于自定义编解码函数,详情请参考[自定义编解码](#自定义编解码) +- O。等价于X(F(0), ...) 没有任何FLAG。 +- M。等价于X(F(M),...) 表示这些字段是必须存在的。 +- A。[别名](#别名),A(member1, alias1, member2, alias2...),用于变量和key名不一样的情况 +- AF。带FLAG的[别名](#别名),AF(F(flag1, flag2,...), member1, alias1, member2, alias2...) +- B。[位域](#位域),B(F(flag1, flag2, ...), member1, member2, ...) **位域不支持别名** +- I。[继承](#继承),I(baseclass1, baseclass2....),里面放父类 +- E。[枚举](#枚举): + - 如果编译器支持C++11,不需要用E,枚举可以放X/O/M/A里面。 + - 否则枚举只能放E里面,不支持别名 + +别名 +---- +- 用于变量名和key名不一致的场景 +- 格式是A(变量,别名....)或者AF(F(flags), 变量,别名....),别名的格式是"x t:n"的格式 + - x表示全局别名,t表示类型(目前支持json、xml、bson),n表示类型下的别名 + - 全局别名可以没有,比如`json:_id`是合法的 + - 类型别名可以没有,比如`_id`是合法的 + - 有类型别名优先用类型别名,否则用全局别名,都没有,则用变量名 + +``` C++ +#include +#include "xpack/json.h" + +using namespace std; + +struct Test { + long uid; + string name; + XPACK(A(uid, "id"), O(name)); // "uid"的别名是"id" +}; + +int main(int argc, char *argv[]) { + Test t; + string json="{\"id\":123, \"name\":\"Pony\"}"; + + xpack::json::decode(json, t); + cout< +#include "xpack/json.h" + +using namespace std; + +struct Test { + short ver:8; + short len:8; + string name; + XPACK(B(F(0), ver, len), O(name)); +}; + +int main(int argc, char *argv[]) { + Test t; + string json="{\"ver\":4, \"len\":20, \"name\":\"IPv4\"}"; + + xpack::json::decode(json, t); + cout< +#include "xpack/json.h" + +using namespace std; + +struct P1 { + string mail; + XPACK(O(mail)); +}; + +struct P2 { + long version; + XPACK(O(version)); +}; + +struct Test:public P1, public P2 { + long uid; + string name; + XPACK(I(P1, P2), O(uid, name)); +}; + +int main(int argc, char *argv[]) { + Test t; + string json="{\"mail\":\"pony@xpack.com\", \"version\":2019, \"id\":123, \"name\":\"Pony\"}"; + + xpack::json::decode(json, t); + cout< +#include "xpack/json.h" + +using namespace std; + +enum Enum { + X = 0, + Y = 1, + Z = 2, +}; + +struct Test { + string name; + Enum e; + XPACK(O(name), E(F(0), e)); +}; + +int main(int argc, char *argv[]) { + Test t; + string json="{\"name\":\"IPv4\", \"e\":1}"; + + xpack::json::decode(json, t); + cout< +#include "xpack/json.h" + +using namespace std; + + +struct Test { + char name[64]; + char email[64]; + XPACK(O(name, email)); +}; + +int main(int argc, char *argv[]) { + Test t; + string json="{\"name\":\"Pony\", \"email\":\"pony@xpack.com\"}"; + + xpack::json::decode(json, t); + cout< +#include +#include "xpack/json.h" + +using namespace std; + +/* +struct timeval { + time_t tv_sec; + suseconds_t tv_usec; +}; +*/ + +// timeval is thirdparty struct +XPACK_OUT(timeval, O(tv_sec, tv_usec)); + +struct T { + int a; + string b; + timeval t; + XPACK(O(a, b, t)); +}; + + +int main(int argc, char *argv[]) { + T t; + T r; + t.a = 123; + t.b = "xpack"; + t.t.tv_sec = 888; + t.t.tv_usec = 999; + string s = xpack::json::encode(t); + cout< + 1 + 2 + 3 + +``` +- 可以用别名的方式来控制数组的元素的标签,比如A(ids,"xml:ids,vl@id"),vl后面跟着一个@xx,xx就是数组的标签,生成的结果就是: +``` xml + + 1 + 2 + 3 + +``` +- 如果希望数组直接展开,而不是在外面包一层,可以用别名加"sbs"的flag来实现,比如A(ids, "xml:ids,sbs"),**注意,sbs标签仅能用于数组,其他地方使用可能会崩溃** +``` xml +1 +2 +3 +``` + +CDATA +---- +- 对于CDATA类型,需要用"cdata"的flag来实现,比如A(data, "xml:data,cdata") +- cdata只能用std::string来接收 +- 如果变量对应的xml不是CDATA结构,会按普通字符串来处理比如`hello`也可以解析成功 + +Qt支持 +---- +- 修改config.h,开启XPACK_SUPPORT_QT这个宏(或者在编译选项开启) +- 当前支持 QString/QMap/QList/QVector + +MySQL +---- +- 目前仅支持decode,还不支持encode +- 未经过完整测试,使用需要谨慎 +- 当前支持类型有: + - string。简单测试。 + - 整型。简单测试。 + - 浮点型。未测试。 + - 用整型(比如time_t)接收TIME/DATETIME/TIMESTAMP。未测试。 + - 自定义类型转换,is_xpack_mysql_xtype,类似于xtype。未测试。 +- api有两个(xpack::mysql::) : + - `static void decode(MYSQL_RES *result, T &val)` + - 用来将MYSQL_RES转成结构体或者vector<>,如果是非vector,则只转换第一个row + - `static void decode(MYSQL_RES *result, const std::string&field, T &val)` + - 用来解析某个字段,用于只想获得某个字段内容的场景,比如select id from mytable where name = lilei,只想获得id信息。val支持vector + + +重要说明 +---- +- 变量名尽量不要用__x_pack开头,不然可能会和库有冲突。 +- vc6不支持。 +- msvc没有做很多测试,只用2019做过简单测试。 +- json的序列化反序列化用的是[rapidjson](https://github.com/Tencent/rapidjson) +- xml的反序列化用的是[rapidxml](http://rapidxml.sourceforge.net) +- xml的序列化是我自己写的,没有参考RFC,可能有和标准不一样的地方. +- 有疑问可以加QQ群878041110 diff --git a/util/xpack/bson.h b/util/xpack/bson.h new file mode 100755 index 0000000..770afbf --- /dev/null +++ b/util/xpack/bson.h @@ -0,0 +1,54 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_BSON_H +#define __X_PACK_BSON_H + +#include "bson_decoder.h" +#include "bson_encoder.h" +#include "xpack.h" + +#ifdef X_PACK_SUPPORT_CXX0X // support c++11 or later +#include "bson_builder.h" +#endif + + +namespace xpack { + +class bson { +public: + template + static void decode(const std::string &data, T &val) { + BsonDecoder de; + de.decode(data, val); + } + + template + static void decode(const uint8_t* data, size_t len, T &val) { + BsonDecoder de; + de.decode(data, len, val); + } + + template + static std::string encode(const T &val) { + BsonEncoder en; + return en.encode(val); + } +}; + +} + +#endif diff --git a/util/xpack/bson_builder.h b/util/xpack/bson_builder.h new file mode 100755 index 0000000..1f28089 --- /dev/null +++ b/util/xpack/bson_builder.h @@ -0,0 +1,588 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_BSON_BUILDER_H +#define __X_PACK_BSON_BUILDER_H + +#include +#include "bson_encoder.h" + +#ifndef X_PACK_SUPPORT_CXX0X // support c++11 or later +#error "need c++11 or later" +#endif + +namespace xpack { + +class BsonBuilder { + enum { + Unknow = 0, + ObjectBegin = 1<<0, + ObjectEnd = 1<<1, + ArrayBegin = 1<<2, + ArrayEnd = 1<<3, + Comma = 1<<4, + Colon = 1<<5, + Integer = 1<<6, + Float = 1<<7, + String = 1<<8, + Bool = 1<<9, + Variable = 1<<10, + BEof = 1<<11 + }; + + std::string tokenName(int type) const { + std::string ret; + if (0 != (ObjectBegin&type)) { + ret += "/{"; + } + if (0 != (ObjectEnd&type)) { + ret += "/}"; + } + if (0 != (ArrayBegin&type)) { + ret += "/["; + } + if (0 != (ArrayEnd&type)) { + ret += "/]"; + } + if (0 != (Comma&type)) { + ret += "/,"; + } + if (0 != (Colon&type)) { + ret += "/:"; + } + if (0 != ((Float|Integer|Bool)&type)) { + ret += "/number/bool"; + } + if (0 != (String&type)) { + ret += "/string"; + } + if (0 != (Variable&type)) { + ret += "/variable"; + } + + if (!ret.empty()) { + ret = ret.substr(1); + } + return ret; + } + struct Token { + int type; // see enum + union { + const char *sval; + long lval; + double dval; + bool bval; + }; + Token():type(Unknow),sval(NULL){} + }; + struct Item { + Token tk; + const char *key; + bool kvar; // key is variable + Item():tk(),key(NULL), kvar(false) { + } + }; + struct Encoder { + const std::vector* items; + BsonWriter wr; + XEncoder en; + + size_t idx; // items index; + size_t vidx; // variable index; + + std::vector vkeys; // store variable key + Item tmpIt; // key:value when key is a variable, copy item to here + Item *pIt; // point to tmpIt + + std::vector arrayIndex; + std::vector objKeys; // store object key. use in object end + std::vector arrKeys; // store array key, use in array end + + Encoder(const std::vector&its):items(&its), en(wr), idx(0), vidx(0), pIt(NULL){} + + // array key. index: "0", "1", "2", "3" + const char *Key(const char *raw) { + if (NULL != raw) { + return raw; + } else if (arrayIndex.size()>0) { + size_t idx = arrayIndex.size() - 1; + return wr.IndexKey(arrayIndex[idx]++); + } else { + return NULL; + } + } + }; + +public: + BsonBuilder(const std::string& _fmt):raw(_fmt),en(NULL) { + dup = new char[raw.length()+1]; + strcpy(dup, raw.c_str()); + if (parse()) { + for (size_t i=0; ikvar || items[i]->tk.type==Variable) { + return; + } + } + + // no variable. pre build it + bstr = Encode(); + } + } + + ~BsonBuilder() { + if (NULL != dup) { + for (size_t i=0; i + std::string Encode(Args... args) { + if (!bstr.empty()) { + return bstr; + } else if (!fmtErr.empty()) { + return ""; + } + + BsonBuilder bd(*this); + bd.iencode(args...); + bd.end(NULL); + return bd.en->en.String(); + } + + template + std::string EncodeAsJson(Args... args) { + if (!fmtErr.empty()) { + return ""; + } + + BsonBuilder bd(*this); + bd.iencode(args...); + bd.end(NULL); + + return bd.en->wr.Json(); + } + template + static std::string En(const std::string&fmt, Args... args) { + BsonBuilder bb(fmt); + return bb.Encode(args...); + } + +private: // encoder + BsonBuilder(const BsonBuilder&bd):dup(NULL) { + en = new Encoder(bd.items); + } + + // implement iencode()/iencode(T)/iencode(T, Args...) to expand template arguments + void iencode() { + } + void iencode(const char*val) { // xencoder not allow pointer + std::string s(val); + encode(s); + ++en->vidx; + } + template + void iencode(const T &val) { + encode(val); + ++en->vidx; + } + template + void iencode(const T& first, Args... args) { + iencode(first); + iencode(args...); + } + + template + BsonBuilder& encode(const T &val) { + // encode data items without variable + for (; en->idxitems->size(); ++en->idx) { + const Item *it = en->items->at(en->idx); + if ((!it->kvar) && it->tk.type!=Variable) { + add(it, 1); + } else { + break; + } + } + + if (en->idx >= en->items->size()) { // finish + return *this; + } else if (NULL != en->pIt) { // key:val. key and val is variable, key replaced in last encode + add(en->pIt, val); + en->pIt = NULL; + ++en->idx; + return *this; + } + + const Item *it = en->items->at(en->idx); + if (it->kvar) { // key:val and key is variable, we fill string to key + en->tmpIt = *it; // copy item info + en->pIt = &en->tmpIt; + en->pIt->key = dumpStr(val); // dump key string + en->pIt->kvar = false; + if (it->tk.type != Variable) { // value not variable + add(en->pIt, 1); + en->pIt = NULL; + ++en->idx; + } + } else { // val is variable + add(it, val); + ++en->idx; + } + return *this; + } + + // add items to encoder + template + void add(const Item *it, const T&val) { + const char *key = en->Key(it->key); + + switch (it->tk.type) { + case ObjectBegin: + en->en.ob(key); + en->objKeys.push_back(key); + break; + case ObjectEnd: + en->en.oe(en->objKeys.back()); + en->objKeys.pop_back(); + break; + case ArrayBegin: + en->arrayIndex.push_back(0); + en->en.ab(key); + en->arrKeys.push_back(key); + break; + case ArrayEnd: + en->arrayIndex.pop_back(); + en->en.ae(en->arrKeys.back()); + en->arrKeys.pop_back(); + break; + case Integer: + en->en.add(key, it->tk.lval); + break; + case Float: + en->en.add(key, it->tk.dval); + break; + case String: + en->en.add(key, std::string(it->tk.sval)); + break; + case Bool: + en->en.add(key, it->tk.bval); + break; + case Variable: + en->en.add(key, val); + break; + } + } + + // encode the last none variable item + void end(std::string *err) { + for (; en->idxitems->size(); ++en->idx) { + const Item *it = en->items->at(en->idx); + if (it->kvar || it->tk.type==Variable) { + if (NULL != err) { + *err = "less variable"; + } + return; + } + add(it, 1); + } + } + + // type to const char* + const char *dumpStr(const std::string&str) { + en->vkeys.push_back(str); + return en->vkeys[en->vkeys.size()-1].c_str(); + } + const char *dumpStr(const char *key) { + return key; + } + + template + const char *dumpStr(const T&val) { + (void)val; + std::string err = "variable "+Util::itoa(en->vidx)+" is a key, must be std::string or const char*"; + throw std::runtime_error(err); + return ""; + } + + bool isEnd(char ch) { + return ch==' ' || ch==':' || ch==',' || ch=='}' || ch==']'|| ch=='\0'; + } + + // a simple bson string parser + bool parse() { + std::vector stack; + Item *item; + char *p = dup; + char *end = p; + int keyIndex = -1; + char ch; + bool waitkey = false; // {key:value, expect key + + int exp = ObjectBegin; // expect type + while (true) { + item = new Item; + p = end; + nextToken(p, &end, item->tk); + + if (item->tk.type == BEof) { + delete item; + if (!fmtErr.empty()) { + return false; + } + + if (stack.size() > 0) { + if ((ch=stack[stack.size()-1]) == '{') { // in object + fmtErr = "miss } at the end of string"; + } else { + fmtErr = "miss ] at the end of string"; + } + return false; + } + return true; + } else if (item->tk.type == Unknow) { + fmtErr = "unknow token:"; + fmtErr += p; + delete item; + break; + } + + if ((item->tk.type&exp) == 0) { + fmtErr = "unexpected token. expect["+tokenName(exp)+"]. but get["+tokenName(item->tk.type)+"]"; + if (NULL != item->tk.sval) { + fmtErr += ". in ["; + fmtErr += item->tk.sval; + fmtErr += "]"; + } + delete item; + break; + } + + // key:value parse value done + if (keyIndex >= 0) { + const Item *it = items[keyIndex]; + items.pop_back(); + keyIndex = -1; + if (it->tk.type == String) { + item->key = it->tk.sval; + } else { + item->kvar = true; + } + delete it; + } + + switch (item->tk.type) { + case ObjectBegin: + items.push_back(item); + exp = ObjectEnd | Variable | String; + stack.push_back('{'); + waitkey = true; + break; + case ObjectEnd: + waitkey = false; + case ArrayEnd: + items.push_back(item); + stack.pop_back(); + if (stack.size() == 0) { + exp = BEof; + } else if ((ch=stack[stack.size()-1]) == '{') { // in object + exp = ObjectEnd | Comma | String | Variable; + } else { + exp = ArrayEnd | Comma | Integer | Float | String | Bool | Variable; + } + break; + case ArrayBegin: + items.push_back(item); + exp = ObjectBegin | ArrayBegin | ArrayEnd | Integer | Float | String | Bool | Variable; + stack.push_back('['); + break; + case Comma: + if ((ch=stack[stack.size()-1]) == '{') { // in object + exp = String | Variable; + waitkey = true; + } else { + exp = ObjectBegin | ArrayBegin | Integer | Float | String | Bool | Variable; + } + delete item; + break; + case Colon: + exp = ObjectBegin | ArrayBegin | Integer | Float | String | Bool | Variable; + keyIndex = int(items.size()) - 1; + waitkey = false; + delete item; + break; + case Integer: + case Float: + case String: + case Bool: + case Variable: + items.push_back(item); + if ((ch=stack[stack.size()-1]) == '{') { // in object + if (waitkey) { + waitkey = false; + exp = Colon; + } else { + exp = ObjectEnd | Comma; + } + } else { + exp = ArrayEnd | Comma | Integer | Float | String | Bool | Variable; + } + break; + } + } + return false; + } + + // get next token from data, may modify data + void nextToken(char* data, char**end, Token &tk) { + char ch; + size_t i = 0; + + *end = NULL; + while (data[i] == ' ') { + i++; + } + + if (data[i] == '\0') { + tk.type = BEof; + return; + } + + tk.sval = data+i; + while ((ch=data[i++]) != '\0') { + if (tk.type == String) { + if (ch != '\'') { + continue; + } else if (isEnd(data[i])) { + data[i-1] = '\0'; + *end = data+i; + } else { + tk.type = Unknow; + } + return; + } else if (tk.type == Integer || tk.type == Float) { + if (ch>='0' && ch<='9') { + continue; + } else if (ch == '.') { + tk.type = Float; + continue; + } else if (isEnd(ch)) { + if (tk.type == Integer) { + data[i-1] = '\0'; + if (Util::atoi(tk.sval, tk.lval)) { + *end = data+i-1; + } else { + tk.type = Unknow; + } + data[i-1] = ch; + } else { + char *dend; + tk.dval = strtod(tk.sval, &dend); + if (dend != data+i-1) { + tk.type = Unknow; + } else { + *end = dend; + } + } + } else { + tk.type = Unknow; + } + return; + } else if (tk.type == Variable) { + if ((ch>='0' && ch<='9') || (ch>='a' && ch<='z') || (ch>='A' && ch<='Z') || ch=='_') { + continue; + } else if (isEnd(ch)) { + *end = data+i-1; + } else { + tk.type = Unknow; + } + return; + } else if (ch == '{') { + tk.type = ObjectBegin; + break; + } else if (ch == '}') { + tk.type = ObjectEnd; + break; + } else if (ch == '[') { + tk.type = ArrayBegin; + break; + } else if (ch == ']') { + tk.type = ArrayEnd; + break; + } else if (ch == ',') { + tk.type = Comma; + break; + } else if (ch == ':') { + tk.type = Colon; + break; + } else if (ch == '\'') { // string + tk.type = String; + tk.sval = data+i; + } else if (ch == '?') { + tk.type = Variable; + } else if (ch=='-' || ch=='+' || (ch>='0' && ch<='9')) { + tk.type = Integer; + tk.sval = data+i-1; + } else if (ch == 't') { // + if (strncmp(data+i-1, "true", 4) == 0 && isEnd(data[i+3])) { + tk.type = Bool; + tk.bval = true; + *end = data+i+3; + } + return; + } else if (ch == 'f') { // + if (strncmp(data+i-1, "false", 5) == 0 && isEnd(data[i+4])) { + tk.type = Bool; + tk.bval = false; + *end = data+i+4; + } + return; + } else { + return; + } + } + + if (tk.type != Unknow) { + if (*end==NULL && (tk.type==String||tk.type==Float||tk.type==Integer||tk.type==Variable)) { + fmtErr = "no end for string/integer/float/variable"; + tk.type = BEof; + } else { + *end = data+i; + } + } + } + + std::string raw; + std::string fmtErr; + std::string lastErr; + std::string bstr; // for that without any variable + char* dup; // for parse + std::vector items; + Encoder *en; +}; + +} + +#endif diff --git a/util/xpack/bson_decoder.h b/util/xpack/bson_decoder.h new file mode 100755 index 0000000..5da8bcb --- /dev/null +++ b/util/xpack/bson_decoder.h @@ -0,0 +1,279 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_BSON_DECODER_H +#define __X_PACK_BSON_DECODER_H + +#include +#include + +#include "util.h" +#include "xdecoder.h" +#include "bson_type.h" + +namespace xpack { + + +class BsonNode { + typedef XDecoder decoder; + friend class BsonDecoder; +public: + typedef size_t Iterator; + + BsonNode(const bson_iter_t* iter = NULL):it(iter),inited(false) { + if (NULL != it) { + type = bson_iter_type(it); + } + } + + inline static const char * Name() { + return "bson"; + } + operator bool () const { + return it != NULL; + } + bool IsNull() const { + return NULL!=it && BSON_TYPE_NULL==bson_iter_type(it); + } + BsonNode Find(decoder&de, const char*key, const Extend *ext) { + (void)de; + (void)ext; + + if (!inited) { + this->init(); + } + + if (BSON_TYPE_DOCUMENT != type) { + de.decode_exception("not document", NULL); + } + + node_index::iterator iter = _childs_index.find(key); + if (iter != _childs_index.end()) { + return BsonNode(&_childs[iter->second]); + } else { + return BsonNode(); + } + } + size_t Size(decoder&de) { + (void)de; + if (!inited) { + this->init(); + } + + if (BSON_TYPE_ARRAY == type) { + return _childs.size(); + } else { + de.decode_exception("not array", NULL); + return 0; + } + } + BsonNode At(size_t index) const { // no exception + return BsonNode(&(_childs[index])); + } + BsonNode Next(decoder&de, BsonNode&p, Iterator&iter, std::string&key) { + (void)de; + if (!p.inited) { + p.init(); + } + if (BSON_TYPE_DOCUMENT != p.type) { + de.decode_exception("not document", NULL); + } + + if (it != p.it) { + ++iter; + } else { + iter = 0; + } + + if (iter != p._childs.size()) { + bson_iter_t *t = &p._childs[iter]; + key = bson_iter_key(t); + return BsonNode(t); + } + + return BsonNode(); + } + + bool Get(decoder&de, std::string&val, const Extend*ext) { + (void)ext; + + if (BSON_TYPE_UTF8 == type) { + uint32_t length; + const char* data = bson_iter_utf8(it, &length); + if (NULL != data) { + val = std::string(data, length); + } + } else if (BSON_TYPE_NULL != type) { + de.decode_exception("not string", NULL); + } + return true; + } + bool Get(decoder&de, bool &val, const Extend*ext) { + (void)de; + (void)ext; + val = bson_iter_as_bool(it); + return true; + } + template + typename x_enable_if::is_integer, bool>::type Get(decoder&de, T &val, const Extend*ext){ + (void)de; + (void)ext; + val = (T)bson_iter_as_int64(it); + return true; + } + template + typename x_enable_if::is_float, bool>::type Get(decoder&de, T &val, const Extend*ext){ + (void)de; + (void)ext; + val = (T)bson_iter_double(it); + return true; + } + + /*std::string json() const { + if (NULL == _data) { + return ""; + } + size_t length=BSON_UINT32_TO_LE(*(int32_t*)_data); + bson_t b; // local is ok + bson_init_static(&b, _data, length); + + size_t s; + char *jstr = bson_as_json(&b, &s); + if (jstr) { + std::string j(jstr); + bson_free(jstr); + return j; + } else { + return ""; + } + }*/ + + // bson type + bool decode_type_spec(bson_oid_t &val, const Extend *ext) { + (void)ext; + const bson_oid_t *t = bson_iter_oid(it); + if (t != NULL) { + bson_oid_init_from_data(&val, t->bytes); + return true; + } else { + return false; + } + } + bool decode_type_spec(bson_date_time_t &val, const Extend *ext) { + (void)ext; + val.ts = bson_iter_date_time(it); + return true; + } + bool decode_type_spec(bson_timestamp_t &val, const Extend *ext) { + (void)ext; + bson_iter_timestamp(it, &val.timestamp, &val.increment); + return true; + } + bool decode_type_spec(bson_decimal128_t &val, const Extend *ext) { + (void)ext; + return bson_iter_decimal128(it, &val); + } + bool decode_type_spec(bson_regex_t &val, const Extend *ext) { + (void)ext; + const char *options = NULL; + const char *regex = bson_iter_regex(it, &options); + if (NULL != regex) { + val.pattern = regex; + } + if (NULL != options) { + val.options = options; + } + return true; + } + bool decode_type_spec(bson_binary_t &val, const Extend *ext) { + (void)ext; + uint32_t len = 0; + const uint8_t *data = NULL; + bson_iter_binary(it, &val.subType, &len, &data); + if (data != NULL && len > 0) { + val.data = std::string((const char*)data, size_t(len)); + } + return true; + } + + +private: + typedef std::map node_index; // index of _childs + + // get object or array info + void init(bool top = false) { + inited = true; + + bson_iter_t sub; + if (NULL != it) { + if (top) { + type = BSON_TYPE_DOCUMENT; + memcpy((void*)&sub, (void*)it, sizeof(sub)); + } else if (!bson_iter_recurse(it, &sub)) { + return; + } + + size_t i = 0; + while (bson_iter_next(&sub)) { + _childs.push_back(sub); + if (type == BSON_TYPE_DOCUMENT) { + _childs_index[bson_iter_key(&sub)] = i++; + } + } + } + } + + const bson_iter_t* it; + bson_type_t type; + bool inited; + std::vector _childs; // childs + node_index _childs_index; +}; + + +class BsonDecoder { +public: + // if length==0, length will get from data + template + bool decode(const uint8_t*data, size_t length, T&val) { + length = (length>0)?length:BSON_UINT32_TO_LE(*(int32_t*)data); + + bson_t b; + bson_iter_t it; + bson_init_static(&b, data, length); + bson_iter_init(&it, &b); + + BsonNode node(&it); + node.init(true); + return XDecoder(NULL, (const char*)NULL, node).decode(val, NULL); + } + + template + bool decode(const std::string&data, T&val) { + return decode((const uint8_t*)data.data(), data.length(), val); + } +}; + +template<>struct is_xpack_type_spec {static bool const value = true;}; +template<>struct is_xpack_type_spec {static bool const value = true;}; +template<>struct is_xpack_type_spec {static bool const value = true;}; +template<>struct is_xpack_type_spec {static bool const value = true;}; +template<>struct is_xpack_type_spec {static bool const value = true;}; +template<>struct is_xpack_type_spec {static bool const value = true;}; + +} + +#endif diff --git a/util/xpack/bson_encoder.h b/util/xpack/bson_encoder.h new file mode 100755 index 0000000..b3857cf --- /dev/null +++ b/util/xpack/bson_encoder.h @@ -0,0 +1,246 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +#ifndef __X_PACK_BSON_ENCODER_H +#define __X_PACK_BSON_ENCODER_H + +#include + +#include "xpack/util.h" +#include "xpack/xencoder.h" +#include "bson_type.h" + +namespace xpack { + +class BsonWriter: private noncopyable { + friend class BsonBuilder; + struct Node { + const char *key; + Node *parent; + bson_t data; + + Node(const char *_key = NULL, Node *_parent=NULL):key(_key), parent(_parent) { + if (NULL == parent) { + bson_init(&data); + } + } + ~Node() { + if (NULL == parent) { + bson_destroy(&data); + } + } + }; + friend class XEncoder; + friend class BsonEncoder; + const static bool support_null = true; +public: + BsonWriter() { + cur = new Node(NULL, NULL); + nodes.push_back(cur); + } + + ~BsonWriter() { + std::list::iterator it; + for (it = nodes.begin(); it!=nodes.end(); ++it) { + delete *it; + } + } +private: + inline static const char *Name() { + return "bson"; + } + inline const char *IndexKey(size_t index) { + if (index > indexStr.size()+1) { + std::string s = Util::itoa(index); + errCase.push_back(s); + return errCase.back().c_str(); + } + if (index >= indexStr.size()) { + std::string s = Util::itoa(index); + indexStr.push_back(s); + } + return indexStr[index].c_str(); + } + // return bson binary data + std::string String() const { + return std::string((const char*)bson_get_data(&cur->data), cur->data.len); + } + // return json format string + std::string Json() const { + size_t len; + char *jstr = bson_as_json(&cur->data, &len); + std::string ret(jstr); + bson_free(jstr); + + return ret; + } + + void ArrayBegin(const char *key, const Extend *ext) { + (void)ext; + if (key != NULL) { + Node *tmp = new Node(key, cur); + nodes.push_back(tmp); + + bson_append_array_begin(&cur->data, key, strlen(key), &tmp->data); + cur = tmp; + } + } + void ArrayEnd(const char *key, const Extend *ext) { + (void)ext; + if (NULL != key) { + bson_append_array_end(&cur->parent->data, &cur->data); + cur = cur->parent; // do not pop and delete + } + } + void ObjectBegin(const char *key, const Extend *ext) { + (void)ext; + if (key != NULL) { + Node *tmp = new Node(key, cur); + nodes.push_back(tmp); + + bson_append_document_begin(&cur->data, key, strlen(key), &tmp->data); + cur = tmp; + } + } + void ObjectEnd(const char *key, const Extend *ext) { + (void)ext; + if (NULL != key) { // in case of inherit, object key is NULL + bson_append_document_end(&cur->parent->data, &cur->data); + cur = cur->parent; // do not pop and delete + } + } + bool WriteNull(const char*key, const Extend *ext) { + (void)ext; + bson_append_null(&cur->data, key, strlen(key)); + return true; + } + bool encode_bool(const char*key, const bool &val, const Extend *ext) { + (void)ext; + bson_append_bool(&cur->data, key, strlen(key), val); + return true; + } + bool encode_string(const char*key, const char*val, size_t length, const Extend *ext) { + (void)ext; + bson_append_utf8(&cur->data, key, strlen(key), val, length); + return true; + } + bool encode_string(const char*key, const std::string& val, const Extend *ext) { + (void)ext; + bson_append_utf8(&cur->data, key, strlen(key), (const char*)val.data(), val.length()); + return true; + } + bool encode_string(const char*key, const char* val, const Extend *ext) { + (void)ext; + if (NULL != val) { + bson_append_utf8(&cur->data, key, strlen(key), val, strlen(val)); + } else { + bson_append_utf8(&cur->data, key, strlen(key), "", 0); + } + return true; + } + + #define X_PACK_BSON_ENCODE_NUMBER(vtype, ftype, ctype) \ + inline bool encode_number(const char*key, const vtype&val, const Extend*ext) {\ + (void)ext;\ + bson_append_##ftype(&cur->data, key, strlen(key), (vtype)val);\ + return true; \ + } + X_PACK_BSON_ENCODE_NUMBER(char, int32, int32_t) + X_PACK_BSON_ENCODE_NUMBER(signed char, int32, int32_t) + X_PACK_BSON_ENCODE_NUMBER(unsigned char, int32, int32_t) + X_PACK_BSON_ENCODE_NUMBER(short, int32, int32_t) + X_PACK_BSON_ENCODE_NUMBER(unsigned short, int32, int32_t) + X_PACK_BSON_ENCODE_NUMBER(int, int32, int32_t) + X_PACK_BSON_ENCODE_NUMBER(unsigned int, int32, int32_t) + + X_PACK_BSON_ENCODE_NUMBER(long, int64, int64_t) + X_PACK_BSON_ENCODE_NUMBER(unsigned long, int64, int64_t) + X_PACK_BSON_ENCODE_NUMBER(long long, int64, int64_t) + X_PACK_BSON_ENCODE_NUMBER(unsigned long long, int64, int64_t) + + X_PACK_BSON_ENCODE_NUMBER(float, double, double) + X_PACK_BSON_ENCODE_NUMBER(double, double, double) + X_PACK_BSON_ENCODE_NUMBER(long double, double, double) + + // bson types + bool encode_type_spec(const char*key, const bson_oid_t &val, const Extend *ext) { + (void)ext; + bson_append_oid(&cur->data, key, strlen(key), &val); + return true; + } + bool encode_type_spec(const char*key, const bson_date_time_t &val, const Extend *ext) { + (void)ext; + bson_append_date_time(&cur->data, key, strlen(key), val.ts); + return true; + } + bool encode_type_spec(const char *key, const bson_timestamp_t &val, const Extend *ext) { + (void)ext; + bson_append_timestamp(&cur->data, key, strlen(key), val.timestamp, val.increment); + return true; + } + bool encode_type_spec(const char *key, const bson_decimal128_t &val, const Extend *ext) { + (void)ext; + return bson_append_decimal128(&cur->data, key, strlen(key), &val); + } + bool encode_type_spec(const char *key, const bson_regex_t &val, const Extend *ext) { + (void)ext; + return bson_append_regex(&cur->data, key, strlen(key), val.pattern.c_str(), val.options.c_str()); + } + bool encode_type_spec(const char *key, const bson_binary_t &val, const Extend *ext) { + (void)ext; + return bson_append_binary(&cur->data, key, strlen(key), val.subType, (const uint8_t*)val.data.data(), val.data.length()); + } + +private: + std::vector indexStr; + std::list errCase; + std::list nodes; + Node *cur; +}; + +class BsonEncoder { +public: + BsonEncoder() { + } + + template + std::string encode(const T&val) { + BsonWriter wr; + XEncoder en(wr); + en.encode(NULL, val, NULL); + return wr.String(); + } + + template + std::string encode_as_json(const T&val) { + BsonWriter wr; + XEncoder en(wr); + en.encode(NULL, val, NULL); + return wr.Json(); + } +}; + +template<>struct is_xpack_type_spec {static bool const value = true;}; +template<>struct is_xpack_type_spec {static bool const value = true;}; +template<>struct is_xpack_type_spec {static bool const value = true;}; +template<>struct is_xpack_type_spec {static bool const value = true;}; +template<>struct is_xpack_type_spec {static bool const value = true;}; +template<>struct is_xpack_type_spec {static bool const value = true;}; + +} + +#endif diff --git a/util/xpack/bson_type.h b/util/xpack/bson_type.h new file mode 100755 index 0000000..46b1df0 --- /dev/null +++ b/util/xpack/bson_type.h @@ -0,0 +1,450 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_BSON_TYPE_H +#define __X_PACK_BSON_TYPE_H + +#include +#include +#include +#include +#include +#include + +#include + +#include "xencoder.h" +#include "xdecoder.h" +#include "xpack.h" + +/* +// define in this file +bson_date_time_t +bson_timestamp_t +bson_binary_t +bson_regex_t + +// define in bson.h +bson_oid_t +bson_decimal128_t +*/ + +// define in global namespace for consistency with bson +struct bson_timestamp_t { + uint32_t timestamp; + uint32_t increment; + bson_timestamp_t(const uint32_t&t=0, const uint32_t&i=0):timestamp(t),increment(i){} + XPACK(A(timestamp, "t", increment, "i")); +}; +struct bson_regex_t { + std::string pattern; // libbson not escape string, this should be a bug + std::string options; + // ver==1 {"$regularExpression": {pattern: , "options": "}} + // ver==2 {"$regex: , $options: "} + int ver; // ver==1 + bson_regex_t():ver(2){} +}; +struct bson_date_time_t { + int64_t ts; // milliseconds + bson_date_time_t(const int64_t&t=0):ts(t){} + operator int64_t() const {return ts;} + + // parse rfc3339 time format(milliseconds). C++ doesn't even have a full-featured standard time library T_T + // "2000-01-01T01:01:01[.001]Z[07:00]" string should ends with \0 + bool parse_rfc3339(const char buf[]) { + struct tm tm; + memset((void*)&tm, 0, sizeof(tm)); + + const char *end = strptime(buf, "%Y-%m-%dT%H:%M:%S", &tm); + if (end == NULL) { + return false; + } + + int msec = 0; + int offset = 0; + try { + size_t pos; + if (*end == '.') { + msec = std::stoi(end+1, &pos); + if (pos==0 || pos > 3) { + return false; + } + end += pos+1; + } + if (*end != 'Z') { + pos = 0; + int hour = std::stoi(end, &pos); + if (pos == 0 || pos > 3) { + return false; + } + end += pos; + if (*end != ':') { + return false; + } + int min = std::stoi(end+1, &pos); + if (hour > 0) { + offset = hour*3600 + min*60; + } else { + offset = -(-hour*3600 + min*60); + } + } else { + ++end; + if (*end != '\0') { + return false; + } + } + } catch(...) { + return false; + } + + time_t tmp = mktime(&tm); + ts = tmp*1000 + msec - offset - timezone*1000; + return true; + } +}; +struct bson_binary_t { + std::string data; // std::string is more general, so don't use std::basic_string + bson_subtype_t subType; + + inline static const char *b64() { + return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + } + + // b64_ntop and b64_pton maybe should move to a public place + static void b64_ntop(const uint8_t *bin, size_t size, std::string&out) { + const char *b = b64(); + + out.reserve((size+2)/3*4); + size_t n = size/3*3; + size_t i; + for (i=0; i>2]); + out.push_back(b[((bin[i]&0x3)<<4) | (bin[i+1]>>4)]); + out.push_back(b[((bin[i+1]&0xf)<<2) | (bin[i+2]>>6)]); + out.push_back(b[bin[i+2]&0x3f]); + } + size_t left = size%3; + if (left == 1) { + out.push_back(b[bin[i]>>2]); + out.push_back(b[(bin[i]&0x3)<<4]); + out.push_back('='); + out.push_back('='); + } else if (left == 2) { + out.push_back(b[bin[i]>>2]); + out.push_back(b[((bin[i]&0x3)<<4) | (bin[i+1]>>4)]); + out.push_back(b[(bin[i+1]&0xf)<<2]); + out.push_back('='); + } + } + static bool b64_pton(const std::string&in, std::string&out) { + static uint8_t bmap[256] = {0}; + if (bmap[255] == 0) { // init bmap, thread safe + for (size_t i=0; i<255; i++) bmap[i] = 0xff; + const char *p = b64(); + for (uint8_t i=0; p[i]!='\0'; ++i) bmap[(int)p[i]] = i; + bmap[255] = 0xff; + } + + size_t size = in.length(); + if (size==0 || 0 != (size&0x3)) { + return false; + } + out.reserve((size+3)/4*3); + + const char *data = in.data(); + for (size_t i=0; i>4))); + } else { + return false; + } + + if (i3 != 0xff) { + out.push_back((char)((i2<<4) | (i3>>2))); + } else if (data[i+2]=='=' && data[i+3]=='=' && i+4==size) { + return true; + } else { + return false; + } + if (i4 != 0xff) { + out.push_back((char)((i3<<6) | i4)); + } else if (data[i+3]=='=' && i+4==size) { + return true; + } else { + return false; + } + } + return true; + } +}; + +namespace xpack { + +// bson type to json +// ref https://github.com/mongodb/specifications/blob/master/source/extended-json.rst#conversion-table +// Relaxed Extended JSON Format Only + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ObjectId ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +// {"$oid":"xxxxx"} +template<> +struct is_xpack_xtype {static bool const value = true;}; +template +bool xpack_xtype_decode(Decoder &de, bson_oid_t &val, const Extend *ext) { + (void)ext; + char buf[25]; + bool ret = de.decode("$oid", buf, NULL); + if (ret) { + bson_oid_init_from_string(&val, buf); + } + return ret; +} +template +bool xpack_xtype_encode(Encoder &en, const char*key, const bson_oid_t &val, const Extend *ext) { + char buf[25]; + bson_oid_to_string(&val, buf); + en.ObjectBegin(key, ext); + en.encode("$oid", buf, NULL); + en.ObjectEnd(key, ext); + return true; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ date time ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// +// {"$date": 12345} +// {"$date":"2006-01-02T15:04:05Z"} +// {"$date":{"$numberLong":"123456"}} +template<> +struct is_xpack_xtype {static bool const value = true;}; +template +bool xpack_xtype_decode(Decoder &de, bson_date_time_t &val, const Extend *ext) { + (void)ext; + static const char *dkey = "$date"; + + bool ret; + try { + ret = de.decode(dkey, val.ts, NULL); + return ret; + } catch (...){} + + char buf[25]; + try { + ret = de.decode(dkey, buf, NULL); + if (ret) { + ret = val.parse_rfc3339(buf); + } + return ret; + } catch (...) {} + + try { + ret = de.decode("$numberLong", buf, NULL); + if (ret) { + ret = Util::atoi(buf, val.ts); + } + return ret; + } catch (...) {} + + return false; +} +template +bool xpack_xtype_encode(Encoder &en, const char*key, const bson_date_time_t &val, const Extend *ext) { + en.ObjectBegin(key, ext); + if (val.ts >= 0) { + char buf[32]; + int msecs = int(val.ts % 1000); + time_t secs = (time_t)(val.ts/1000); + + #ifndef _MSC_VER + struct tm posix_date; + size_t sz = strftime(buf, sizeof buf, "%FT%T", gmtime_r(&secs, &posix_date)); + #else + size_t sz = strftime(buf, sizeof buf, "%FT%T", gmtime(&secs)); + #endif + + if (msecs > 0) { + sprintf(&buf[sz], ".%03dZ", msecs); + } else { + strcpy(&buf[sz], "Z"); + } + en.encode("$date", buf, NULL); + } else { // key:{"$date":{"$numberLong":ts}} ts use string not int + en.ObjectBegin("$date", NULL); + en.encode("$numberLong", Util::itoa(val.ts), NULL); + en.ObjectEnd("$date", NULL); + } + en.ObjectEnd(key, ext); + return true; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ timestamp ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +// {"$timestamp":{"t":xxx, "i":xxx}} +template<> +struct is_xpack_xtype {static bool const value = true;}; +template +bool xpack_xtype_decode(Decoder &de, bson_timestamp_t &val, const Extend *ext) { + (void)ext; + bool ret = de.decode_struct("$timestamp", val, NULL); + return ret; +} +template +bool xpack_xtype_encode(Encoder &en, const char*key, const bson_timestamp_t &val, const Extend *ext) { + en.ObjectBegin(key, ext); + en.encode_struct("$timestamp", val, NULL); + en.ObjectEnd(key, ext); + return true; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ numberDecimal ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +// {"$numberDecimal":"123.4"} +template<> +struct is_xpack_xtype {static bool const value = true;}; +template +bool xpack_xtype_decode(Decoder &de, bson_decimal128_t &val, const Extend *ext) { + (void)ext; + char buf[BSON_DECIMAL128_STRING]; + bool ret = de.decode("$numberDecimal", buf, NULL); + if (ret) { + bson_decimal128_from_string(buf, &val); + } + return ret; +} +template +bool xpack_xtype_encode(Encoder &en, const char*key, const bson_decimal128_t &val, const Extend *ext) { + char buf[BSON_DECIMAL128_STRING]; + bson_decimal128_to_string(&val, buf); + en.ObjectBegin(key, ext); + en.encode("$numberDecimal", buf, NULL); + en.ObjectEnd(key, ext); + return true; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ binary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +// {"$binary":{"base64":"aGVsbG8=","subType":"80"}} +// {"$binary":"aGVsbG8=", "$type":"80"} +template<> +struct is_xpack_xtype {static bool const value = true;}; +template +bool xpack_xtype_decode(Decoder &de, bson_binary_t &val, const Extend *ext) { + (void)ext; + try { + std::string subType; + std::string data; + bool ret1; + bool ret2; + + Decoder st = de.Find("$type", NULL); + if (!st) { + Decoder bd = de.Find("$binary", NULL); + if (!bd) { + return false; + } + ret1 = bd.decode("subType", subType, NULL); + ret2 = bd.decode("base64", data, NULL); + } else { + ret1 = st.decode(subType, NULL); + ret2 = de.decode("$binary", data, NULL); + } + + if (ret1 && (subType.length()==1 || subType.length()==2)) { + int st = 0; + for (size_t i=0; i='0' && tmp<='9') { + tmp -= '0'; + } else if (tmp>='a' && tmp<='f') { + tmp = 10 + tmp-'a'; + } else if (tmp>='A' && tmp<='F') { + tmp = 10 + tmp-'A'; + } else { + return false; + } + st = (st<<4) + tmp; + } + val.subType = (bson_subtype_t)st; + } else { + return false; + } + + if (ret2) { + ret2 = bson_binary_t::b64_pton(data, val.data); + } + return ret2; + } catch (...){ + return false; + } + + return true; +} +template +bool xpack_xtype_encode(Encoder &en, const char*key, const bson_binary_t &val, const Extend *ext) { + en.ObjectBegin(key, ext); + en.ObjectBegin("$binary", NULL); + + std::string b64; + bson_binary_t::b64_ntop((const uint8_t*)val.data.data(), val.data.length(), b64); + en.encode("base64", b64, NULL); + + std::stringstream ss; + ss< +struct is_xpack_xtype {static bool const value = true;}; +template +bool xpack_xtype_decode(Decoder &de, bson_regex_t &val, const Extend *ext) { + (void)ext; + Decoder v1 = de.Find("$regularExpression", NULL); + try { + if (v1) { + v1.decode("pattern", val.pattern, NULL); + v1.decode("options", val.options, NULL); + val.ver = 1; + } else { + de.decode("$regex", val.pattern, NULL); + de.decode("$options", val.options, NULL); + val.ver = 2; + } + } catch(...){} + return true; +} +template +bool xpack_xtype_encode(Encoder &en, const char*key, const bson_regex_t &val, const Extend *ext) { + en.ObjectBegin(key, ext); + if (val.ver == 1) { + en.ObjectBegin("$regularExpression", NULL); + en.encode("pattern", val.pattern, NULL); + en.encode("options", val.options, NULL); + en.ObjectEnd("$regularExpression", NULL); + } else { + en.encode("$regex", val.pattern, NULL); + en.encode("$options", val.options, NULL); + } + en.ObjectEnd(key, ext); + return true; +} + + +} + +#endif diff --git a/util/xpack/config.h b/util/xpack/config.h new file mode 100755 index 0000000..eb5d329 --- /dev/null +++ b/util/xpack/config.h @@ -0,0 +1,25 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +#ifndef __X_PACK_CONFIG_H +#define __X_PACK_CONFIG_H + +// support qt +//#define XPACK_SUPPORT_QT + + +#endif diff --git a/util/xpack/example/alias.cpp b/util/xpack/example/alias.cpp new file mode 100755 index 0000000..748ae99 --- /dev/null +++ b/util/xpack/example/alias.cpp @@ -0,0 +1,27 @@ +#include +#include "xpack/json.h" +#include "xpack/xml.h" + +using namespace std; + +struct Test { + long uid; + string name; + XPACK(A(uid, "id xml:xid"), O(name)); // xml use 'xid' and others use 'id' +}; + +int main() { + Test t; + string json="{\"id\":123, \"name\":\"Pony\"}"; + + xpack::json::decode(json, t); + cout< +#include "xpack/json.h" +#include "xpack/xml.h" + +using namespace std; + +struct Test { + short ver:8; + short len:8; + string name; + XPACK(B(F(0), ver, len), O(name)); +}; + +int main() { + Test t; + string json="{\"ver\":4, \"len\":20, \"name\":\"IPv4\"}"; + + xpack::json::decode(json, t); + cout< +#include "xpack/json.h" + +using namespace std; + + +struct Test { + char name[64]; + char email[64]; + XPACK(O(name, email)); +}; + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + Test t; + string json="{\"name\":\"Pony\", \"email\":\"pony@xpack.com\"}"; + + xpack::json::decode(json, t); + cout< +#include +#include "xpack/json.h" + +using namespace std; + +struct Test { + unsigned int uid; + string name; + XPACK(C(hexint, F(0), uid), O(name)); +}; + +namespace xpack { + +template +bool hexint_decode(Decoder&de, Test&t, const char *name, unsigned int&data, const xpack::Extend *ext) { + (void)t; + string raw; + bool ret = de.decode(name, raw, ext); + if (ret) { + sscanf(raw.c_str(), "%x", &data); + } + return ret; +} + +template +bool hexint_encode(Encoder&en, const Test&t, const char *name, const unsigned &data, const xpack::Extend *ext) { + (void)t; + char buf[64]; + sprintf(buf, "%x", data); + bool ret = en.encode(name, buf, ext); + return ret; +} +} + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + Test t; + t.uid = 127; + t.name = "PonyJack"; + + string str = xpack::json::encode(t); + cout< +#include "xpack/json.h" +#include "xpack/xml.h" + +using namespace std; + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + vector vi(3); + vi[0] = 1; + vi[1] = 2; + vi[2] = 3; + + xpack::JsonWriter jw; + xpack::XEncoder je(jw); + + xpack::XmlWriter xw; + xpack::XEncoder xe(xw); + + je.ob(NULL).add("vv", vi).add("i",10).add("s","hello").oe(); + xe.ob("root").add("vv", vi).add("i",10).add("s","hello").oe(); + + cout< +#include "xpack/json.h" + +using namespace std; + +enum Enum { + X = 0, + Y = 1, + Z = 2, +}; + +enum class Enum1 { + X = 0, + Y = 1, + Z = 2, +}; + +enum class Enum2:int64_t { + X = 0, + Y = 1, + Z = 2, +}; + +struct Test { + string name; + Enum e; + Enum1 e1; + Enum2 e2; + XPACK(O(name), E(F(0), e), O(e1, e2)); +}; + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + Test t; + string json="{\"name\":\"IPv4\", \"e\":1, \"e1\":2, \"e2\":2}"; + + xpack::json::decode(json, t); + cout< +#include "xpack/json.h" + +using namespace std; + +struct P1 { + string mail; + XPACK(O(mail)); +}; + +struct P2 { + long version; + XPACK(O(version)); +}; + +struct Test:public P1, public P2 { + long uid; + string name; + XPACK(I(P1, P2), A(uid, "id"), O(name)); +}; + +struct Pa { + string mail; + XPACK(O(mail)); +}; + +struct Pb: public Pa { + long version; + XPACK(I(Pa), O(version)); +}; + +struct Pc:public Pb { + long uid; + string name; + XPACK(I(Pb), A(uid, "id"), O(name)); +}; + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + Test t; + string json="{\"mail\":\"pony@xpack.com\", \"version\":2019, \"id\":123, \"name\":\"Pony\"}"; + + xpack::json::decode(json, t); + cout< +#include "xpack/json.h" + +using namespace std; + +struct Example { + string type; + xpack::JsonData data; + XPACK(O(type, data)); +}; + +struct Range { + int min; + int max; + XPACK(O(min, max)); +}; + +struct User { + int id; + string name; + XPACK(O(id, name)); +}; + +static void test(const std::string &data) { + Example e; + xpack::json::decode(data, e); + if (e.type == "range") { + Range r; + e.data.Get(r); + cout<<"min in r:"<()<()<()<()< +#include +#include "xpack/json.h" + +using namespace std; + + +// assign value by name +class AssignDecoder { +public: + AssignDecoder(const std::vector&vs, const unsigned char *src, unsigned char *dst) { + for (size_t i=0; isrc = src; + this->dst = dst; + } + + // decoder need implemetion decode function + template + bool decode(const char *key, T&val, const xpack::Extend *ext) { + (void)ext; + // set value by offset + if (fields.end() != fields.find(key)) { + unsigned int offset = (unsigned int)((unsigned char*)&val - dst); + val = *((T*)(src+offset)); + } + return true; + } +private: + std::set fields; + const unsigned char *src; + unsigned char *dst; +}; + +// SetFields(src, dst, "a,b") means dst.a = src.a; dst.b = src.b; +template +void SetFields(const T&src, T&dst, const std::string&fields) { + std::vector vs; + xpack::Util::split(vs, fields, ','); + if (vs.size() == 0) { + return; + } + + AssignDecoder de(vs, (unsigned char*)(&src), (unsigned char*)(&dst)); + dst.__x_pack_decode(de, dst, NULL); +} + +struct Data { + int a; + int b; + std::string c; + XPACK(O(a,b,c)); +}; + + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + Data d1; + Data d2; + d1.a = 5; + d1.b = 10; + d1.c = "hello"; + d2.b = 9; + SetFields(d1, d2, "a,c"); + cout< +#include +#include +#include + +using namespace std; + +struct VID { + int64_t Vid; + int64_t vlen; + string sid; + int64_t level; + XPACK(A(Vid, "db:vid"), O(vlen, sid, level)); +}; + +typedef char cfgstr[64]; +struct config { + cfgstr host; + uint32_t port; + cfgstr user; + cfgstr password; + cfgstr dbname; + XPACK(M(host, port, user, password, dbname)) +}; + +int main(int argc, char **argv) { + MYSQL *mysql = NULL; + + if (mysql_library_init(argc, argv, NULL)) { + cout<<"could not initialize MySQL client library"< vids; + xpack::mysql::decode(result, vids);// decode all rows + + vector ids; + xpack::mysql::decode(result, "vid", ids); // decode vid column of all rows + + int64_t id; + xpack::mysql::decode(result, "vid", id); // decode vid column of first row + + cout< +#include "xpack/json.h" +#include "xpack/xml.h" + +using namespace std; + +struct User { + int64_t id; + string name; + string mail; + std::shared_ptr bio; + User(int64_t i=0, const string& n="", const string& m=""):id(i),name(n),mail(m){} + XPACK(O(id, name, mail, bio)); +}; + + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + User u1(1, "Jack", "jack@xpack.com"); + User u2; + User u3; + + string json = xpack::json::encode(u1); + string xml = xpack::xml::encode(u1, "root"); + cout<<"========================"< +#include +#include + +using namespace std; + +struct VID { + int64_t Vid; + int64_t vlen; + string sid; + int64_t level; + XPACK(A(Vid, "db:vid"), O(vlen, sid, level)); +}; + +int main(int argc, char **argv) { + (void)argc; + char *errMsg = NULL; + sqlite *pDB = sqlite_open("./test.db", 0, &errMsg); + if (NULL == pDB) { + cout<<"open sqlite db fail:"< +#include +#include "xpack/json.h" + +using namespace std; + +struct Base { + int a; + int b; + XPACK(O(a, b)); +}; + +struct Data { + int type; + union { + Base b; + int i; + char s[10]; + }; + string email; + string birthday; + // 'type' determines which member of the union to use, so we put type in 'C' + // union does not need to be put into XPACK, we will handled it in 'type' + XPACK(C(data_uinon, F(0), type), O(email, birthday)); +}; + +struct User { + string name; + Data data; + XPACK(O(name, data)); +}; + +namespace xpack { // must define in namespace xpack + +template +bool data_uinon_decode(OBJ &obj, Data &d, const char*key, int &type, const Extend *ext) { + (void)ext; + obj.decode(key, type, ext); + switch (type) { + case 1: + obj.decode("b", d.b, NULL); // key("b") should same as encode + break; + case 2: + obj.decode("i", d.i, NULL); + break; + case 3: + obj.decode("s", d.s, NULL); + break; + } + return true; +} + +template +bool data_uinon_encode(OBJ &obj, const Data &d, const char*key, const int &type, const Extend *ext) { + (void)ext; + obj.encode(key, type, NULL); + switch (type) { + case 1: + obj.encode("b", d.b, NULL); + break; + case 2: + obj.encode("i", d.i, NULL); + break; + case 3: + obj.encode("s", d.s, NULL); + break; + } + return true; +} + +} + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + User u; + u.name = "Pony"; + u.data.type = 1; + u.data.b.a = 10; + u.data.b.b = 12; + u.data.email = "Pony@xpack.com"; + u.data.birthday = "20200202"; + + string s = xpack::json::encode(u); + cout< +#include + +using namespace std; + +struct Base { + int a; + int b; + string c; + XPACK(X(F(ATTR), a, b), X(F(XML_CONTENT), c)); +}; + +struct Root { + Base root; + XPACK(O(root)); +}; + + +int main() { + Root r; + r.root.a = 1; + r.root.b = 2; + r.root.c = "hello"; + string s = xpack::xml::encode(r, "root", 0, 4, ' '); + cout< +#include + +using namespace std; + +struct Base { + int a; + int b; + XPACK(O(a, b));//, X(F(ATTR), b)); +}; + +struct Root { + Base root; + XPACK(O(root)); +}; + +struct Top { + int k; + string n; + vector > bs; + vector vi; + float f; + std::string name; // cdata + XPACK(O(k, n, f), A(bs, "xml:base,sbs", vi, "xml:vi,vl@x", name, "xml:name,cdata")); +}; + +int main() { + Top t; + t.k = 666; + t.n = "你好"; + t.f = 1.23333; + t.vi.resize(2); + t.vi[0] = 1; + t.vi[1] = 2; + t.bs.resize(2); + t.bs[0].resize(2); + t.bs[1].resize(2); + t.bs[0][0].a = 100; + t.bs[0][0].b = 200; + t.bs[0][1].a = 101; + t.bs[0][1].b = 201; + t.bs[1][0].a = 110; + t.bs[1][0].b = 210; + t.bs[1][1].a = 111; + t.bs[1][1].b = 211; + t.name = "好好学习,天天向上"; + string s = xpack::xml::encode(t, "root", 0, 4, ' '); + cout<12", b1); + cout<12", r1, true); + cout< linux only +#include +#include "xpack/json.h" +#ifndef _MSC_VER +#include +#endif + +using namespace std; + +#ifdef _MSC_VER +struct timeval { + long tv_sec; + long tv_usec; +}; +#endif + +// timeval is thirdparty struct +XPACK_OUT(timeval, O(tv_sec, tv_usec)); + +struct T { + int a; + string b; + timeval t; + XPACK(O(a, b, t)); +}; + + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + T t; + T r; + t.a = 123; + t.b = "xpack"; + t.t.tv_sec = 888; + t.t.tv_usec = 999; + string s = xpack::json::encode(t); + cout< +#include "xpack/json.h" +#include "xpack/xml.h" + +using namespace std; + +struct Date { + long unix_time; +}; + +namespace xpack { // must define in namespace xpack + +template<> +struct is_xpack_xtype {static bool const value = true;}; + +// implement decode +template +bool xpack_xtype_decode(OBJ &obj, Date &val, const Extend *ext) { + std::string str; + obj.decode(str, ext); + if (str.empty()) { + return false; + } + +#ifndef _MSC_VER + tm ttm; + + if (0 != strptime(str.c_str(), "%Y-%m-%d %H:%M:%S", &ttm)) { + val.unix_time = mktime(&ttm); + } else { + val.unix_time = 0; + } +#else + static int days[]={31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 32}; + struct tm ttm={0}; + sscanf_s(str.c_str(), "%d-%d-%d %d:%d:%d", &ttm.tm_year, &ttm.tm_mon, &ttm.tm_mday, &ttm.tm_hour, &ttm.tm_min, &ttm.tm_sec); + ttm.tm_mon-=1; // mon[0-11] + ttm.tm_year-=1900; // since 1900 + val.unix_time = mktime(&ttm); +#endif + return true; +} + +// implement encode +template +bool xpack_xtype_encode(OBJ &obj, const char*key, const Date &val, const Extend *ext) { + time_t tt = (time_t)val.unix_time; + tm ttm; + +#ifndef _MSC_VER + localtime_r(&tt, &ttm); +#else + localtime_s(&ttm, &tt); +#endif + + char buf[64]; + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &ttm); + return obj.encode(key, buf, ext); +} + +} + +struct User { + int64_t id; + string name; + string mail; + Date d; + User(int64_t i=0, const string& n="", const string& m="", long _d=0):id(i),name(n),mail(m){d.unix_time = _d;} + XPACK(O(id, name, mail, d)); +}; + +struct Group { + string name; + int64_t master; + vector members; + XPACK(O(name, master, members)); +}; + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + Group g; + g.name = "C++"; + g.master = 2019; + g.members.resize(2); + g.members[0] = User(1, "Jack", "jack@xpack.com", 1); + g.members[1] = User(2, "Pony", "pony@xpack.com", 1609249232); + + string json = xpack::json::encode(g, 0, 2, ' '); + cout< +#include +#include "xpack/json.h" + +using namespace std; + +// The requirement is to encode seq1 when type==1, and encode seq2 when type==2 +struct Sub { + int type; + string seq1; + string seq2; + XPACK(O(type, seq1, seq2)); +}; + + +struct Test { + unsigned int uid; + string name; + Sub sub; + XPACK(O(uid, name, sub)); +}; + +namespace xpack { + +template<> +struct is_xpack_xtype {static bool const value = true;}; + +template +bool xpack_xtype_decode(Decoder& de, Sub &data, const xpack::Extend *ext) { + de.decode_struct(data, ext); + return true; +} + +template +class MyEncoder { +public: + MyEncoder(BaseEncoder&b, const Sub&sub):_b(&b), _s(&sub){} + template + bool encode(const char*key, const T&val, const xpack::Extend *ext) { + if (strcmp(key, "seq1") == 0) { + if (_s->type == 1) { + return _b->encode(key, val, ext); + } else { + return false; + } + } else if (strcmp(key, "seq2") == 0) { + if (_s->type == 2) { + return _b->encode(key, val, ext); + } else { + return false; + } + } else { + return _b->encode(key, val, ext); + } + } +private: + BaseEncoder *_b; + const Sub *_s; +}; + +template +bool xpack_xtype_encode(Encoder &en, const char *name, const Sub &data, const xpack::Extend *ext) { + (void)name; + MyEncoder my(en, data); + data.__x_pack_encode(my, data, ext); + return true; +} + +} + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + Test t; + t.uid = 127; + t.name = "PonyJack"; + t.sub.type = 1; + t.sub.seq1 = "10086"; + t.sub.seq2 = "10001"; + + string str = xpack::json::encode(t); + cout< +#include "xpack/yaml.h" +#include "xpack/json.h" + +using namespace std; + +struct User { + int64_t id; + string name; + string mail; + User(int64_t i=0, const string& n="", const string& m=""):id(i),name(n),mail(m){} + XPACK(O(id, name, mail)); +}; + +struct Group { + string name; + int64_t master; + map flags; + vector members; + User arrays[2]; + XPACK(O(name, master, flags, members, arrays)); +}; + +int main() { + User u; + xpack::yaml::decode_file("./test.yml", u); + string json = xpack::json::encode(u); + cout<<"json:"< + +#include +#include +#include "config.h" +#include "util.h" + +namespace xpack { + +// user flag +#define X_PACK_FLAG_0 0 +#define X_PACK_FLAG_OE (1<<0) // omitempty, in encode +#define X_PACK_FLAG_M (1<<1) // mandatory, in decode +#define X_PACK_FLAG_EN (1<<2) // empty as null, in json encode +#define X_PACK_FLAG_SL (1<<3) // encode as one line, currently only supports json vector + +#define X_PACK_FLAG_ATTR (1<<15) // for xml encode, encode in attribute + +// for xml encode/decode: +// CONTENT flag for decode and encode content +// A structure can only have one CONTENT flag. If multiple ones are defined, the behavior is undefined. +#define X_PACK_FLAG_XML_CONTENT (1<<16) + +// control flag +#define X_PACK_CTRL_FLAG_INHERIT (1<<0) + +// Alias name. [def ][type:name[,flag,key@value,flag]] def not support flag +struct Alias { + const char *raw; // raw name + const char *alias; // alias define + + struct Type { + std::string name; + std::set flags; + std::map kv_flags; + }; + + Alias(const char *_raw, const char *_alias):raw(_raw), alias(_alias) { + std::vector tps; + Util::split(tps, alias, ' '); + + for (size_t i=0; i typeFlag; + Util::split(typeFlag, tps[i], ':', 1); + + if (typeFlag.size() == 1) { // no ':', default name + def = tps[i]; + } else { // type:name[,flags] + Type tp; + + std::vector nameFlags; + Util::split(nameFlags, typeFlag[1], ','); + tp.name = nameFlags[0]; + if (nameFlags.size() > 1) { + for (size_t j=1; j kvFlag; + Util::split(kvFlag, nameFlags[j], '@', 1); + if (kvFlag.size() == 1) { + tp.flags.insert(nameFlags[j]); + } else { + tp.kv_flags[kvFlag[0]] = kvFlag[1]; + } + } + } + + types[typeFlag[0]] = tp; + } + } + } + + const char *Name(const char *type) const { + std::map::const_iterator iter = types.find(type); + if (iter == types.end()) { + if (def.empty()) { + return raw; + } else { + return def.c_str(); + } + } else { + return iter->second.name.c_str(); + } + } + + bool Flag(const std::string&type, const std::string& flag, std::string *value=NULL) const { + std::map::const_iterator it1 = types.find(type); + if (it1 != types.end()) { + if (it1->second.flags.find(flag) != it1->second.flags.end()) { + return true; + } + std::map::const_iterator it2 = it1->second.kv_flags.find(flag); + if (it2 != it1->second.kv_flags.end()) { + if (NULL != value) { + *value = it2->second; + } + return true; + } + } + return false; + } +private: + std::string def; // default name + std::map types; +}; + +struct Extend { + int flag; + int ctrl_flag; + const Alias *alias; + + Extend(int _flag, const Alias *_alias):flag(_flag), ctrl_flag(0), alias(_alias) { + } + + Extend(const Extend *ext) { + if (NULL != ext) { + flag = ext->flag; + ctrl_flag = ext->ctrl_flag; + alias = ext->alias; + } else { + flag = 0; + ctrl_flag = 0; + alias = NULL; + } + } + + static int Flag(const Extend *ext) { + if (NULL == ext) { + return 0; + } else { + return ext->flag; + } + } + + static int CtrlFlag(const Extend *ext) { + if (NULL == ext) { + return 0; + } else { + return ext->ctrl_flag; + } + } + + static bool AliasFlag(const Extend *ext, const std::string&type, const std::string& flag, std::string *value=NULL) { + if (NULL==ext || NULL==ext->alias) { + return false; + } + return ext->alias->Flag(type, flag, value); + } + + static bool OmitEmpty(const Extend *ext) { + return NULL!=ext && (ext->flag&X_PACK_FLAG_OE); + } + static bool EmptyNull(const Extend *ext) { + return NULL!=ext && (ext->flag&X_PACK_FLAG_EN); + } + static bool Mandatory(const Extend *ext) { + return NULL!=ext && (ext->flag&X_PACK_FLAG_M); + } + static bool Attribute(const Extend *ext) { + return NULL!=ext && (ext->flag&X_PACK_FLAG_ATTR); + } + static bool XmlContent(const Extend *ext) { + return NULL!=ext && (ext->flag&X_PACK_FLAG_XML_CONTENT); + } +}; + +} + +#endif + diff --git a/util/xpack/gtest/bson_test.cpp b/util/xpack/gtest/bson_test.cpp new file mode 100755 index 0000000..abafe17 --- /dev/null +++ b/util/xpack/gtest/bson_test.cpp @@ -0,0 +1,557 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +// Linux only, need gtest + +#include +#ifdef XGTEST +#include +#else +#include "gtest_stub.h" +#endif + +#include "xpack/json.h" +#include "xpack/bson.h" +#include "string.h" + +using namespace std; + +struct Base { + int a; + string b; + Base(const int _a=0, const string&_b=""):a(_a), b(_b){} + XPACK(O(a, b)); +}; + +struct POD { + int a; + char b[10]; + XPACK(O(a, b)); +}; + +// ++++++++++++++++++++++BuiltInTypes++++++++++++++++++++++++++++ +struct BuiltInTypes { + signed char sch; + char ch; + unsigned char uch; + short sh; + unsigned short ush; + int i; + unsigned int ui; + long l; + unsigned long ul; + long long ll; + unsigned long long ull; + float f; + double d; + long double ld; + bool b; + XPACK(AF(F(ATTR), sch, "xml:s:ch"), X(F(ATTR), ch, uch, sh, ush), O(i, ui, l, ul, ll, ull, f, d, ld, b)); +}; + + +void checkBuiltInTypes(const BuiltInTypes&a, const BuiltInTypes&b) { + EXPECT_EQ(a.sch, b.sch); + EXPECT_EQ(a.ch, b.ch); + EXPECT_EQ(a.uch, b.uch); + EXPECT_EQ(a.sh, b.sh); + EXPECT_EQ(a.ush, b.ush); + EXPECT_EQ(a.i, b.i); + EXPECT_EQ(a.ui, b.ui); + EXPECT_EQ(a.l, b.l); + EXPECT_EQ(a.ul, b.ul); + EXPECT_EQ(a.ll, b.ll); + EXPECT_EQ(a.ull, b.ull); + EXPECT_FLOAT_EQ(a.f, b.f); + EXPECT_DOUBLE_EQ(a.d, b.d); + EXPECT_DOUBLE_EQ(a.ld, b.ld); + EXPECT_TRUE(a.b); +} +TEST(builtin, types) { + BuiltInTypes bt; + bt.sch = 0x7f; + bt.ch = 0x7f; + bt.uch = 0x80; + bt.sh = 0x7fff; + bt.ush = 0x8000; + bt.i = 0x7fffffff; + bt.ui = 0x80000000; + bt.l = 0x7fffffffffffffff; + bt.ul = 0x8000000000000000; + bt.ll = bt.l; + bt.ull = bt.ul; + bt.f = 1.234; + bt.d = 9.678; + bt.ld = 30.678; + bt.b = true; + + xpack::BsonEncoder en; + string js = en.encode_as_json(bt); + cout<<"bson:"< m; + set s; + vector v; + list l; + + vector > vv; + + XPACK(O(m, s, v, l, vv)); +}; + +void checkContainerBase(ContainerBase&cb) { + EXPECT_EQ(cb.m.size(), 2U); + EXPECT_EQ(cb.m["a"], 1); + EXPECT_EQ(cb.m["b"], 2); + EXPECT_EQ(cb.s.size(), 2U); + EXPECT_TRUE(cb.s.find(3)!=cb.s.end()); + EXPECT_TRUE(cb.s.find(4)!=cb.s.end()); + EXPECT_TRUE(cb.s.find(5)==cb.s.end()); + EXPECT_EQ(cb.v.size(), 2U); + EXPECT_EQ(cb.v[0], 5); + EXPECT_EQ(cb.v[1], 6); + EXPECT_EQ(cb.l.size(), 2U); + EXPECT_EQ(*(cb.l.begin()), 7); + EXPECT_EQ(*(++cb.l.begin()), 8); + + EXPECT_EQ(cb.vv.size(), 2U); + EXPECT_EQ(cb.vv[0].size(), 2U); + EXPECT_EQ(cb.vv[1].size(), 2U); + EXPECT_EQ(cb.vv[0][0], 11); + EXPECT_EQ(cb.vv[0][1], 12); + EXPECT_EQ(cb.vv[1][0], 22); + EXPECT_EQ(cb.vv[1][1], 23); +} +TEST(container, base) { + string s = "{\"m\":{\"a\":1, \"b\":2}, \"s\":[3, 4], \"v\":[5,6], \"l\":[7, 8], \"vv\":[[11,12],[22,23]]}"; + + ContainerBase cb; + xpack::json::decode(s, cb); + checkContainerBase(cb); + + xpack::BsonEncoder en; + string js = en.encode_as_json(cb); + + ContainerBase j1; + xpack::bson::decode(en.encode(cb), j1); + checkContainerBase(j1); +} +struct ContainerStruct { + map m; + vector v; + list l; + + vector > vv; + + XPACK(O(m, v, l, vv)); +}; + +void checkContainerStruct(ContainerStruct&a) { + EXPECT_EQ(a.m.size(), 2U); + EXPECT_EQ(a.m["a"].a, 1); + EXPECT_EQ(a.m["a"].b, "good"); + EXPECT_EQ(a.m["b"].a, 2); + EXPECT_EQ(a.m["b"].b, "nice"); + + EXPECT_EQ(a.v.size(), 2U); + EXPECT_EQ(a.v[0].a, 3); + EXPECT_EQ(a.v[0].b, "hello"); + EXPECT_EQ(a.v[1].a, 4); + EXPECT_EQ(a.v[1].b, "wow"); + + list::const_iterator lit = a.l.begin(); + EXPECT_EQ(a.l.size(), 2U); + EXPECT_EQ(lit->a, 5); + EXPECT_EQ(lit->b, "dida"); + ++lit; + EXPECT_EQ(lit->a, 6); + EXPECT_EQ(lit->b, "haha"); + + EXPECT_EQ(a.vv.size(), 2U); + EXPECT_EQ(a.vv[0].size(), 1U); + EXPECT_EQ(a.vv[1].size(), 2U); + EXPECT_EQ(a.vv[0][0].a, 7); + EXPECT_EQ(a.vv[0][0].b, "lala"); + EXPECT_EQ(a.vv[1][0].a, 8); + EXPECT_EQ(a.vv[1][0].b, "wawa"); + EXPECT_EQ(a.vv[1][1].a, 9); + EXPECT_EQ(a.vv[1][1].b, "kaka"); +} +TEST(container, struct) { + ContainerStruct cb; + cb.m["a"] = Base(1, "good"); + cb.m["b"] = Base(2, "nice"); + cb.v.push_back(Base(3, "hello")); + cb.v.push_back(Base(4, "wow")); + cb.l.push_back(Base(5, "dida")); + cb.l.push_back(Base(6, "haha")); + cb.vv.resize(2); + cb.vv[0].resize(1); + cb.vv[1].resize(2); + cb.vv[0][0].a = 7; + cb.vv[0][0].b = "lala"; + cb.vv[1][0].a = 8; + cb.vv[1][0].b = "wawa"; + cb.vv[1][1].a = 9; + cb.vv[1][1].b = "kaka"; + + xpack::BsonEncoder en; + string js = en.encode_as_json(cb); + + ContainerStruct j1; + xpack::bson::decode(en.encode(cb), j1); + checkContainerStruct(j1); +} + +// ++++++++++++++++++ BSON TYPES ++++++++++++++++++++++++++++ +struct BsonTypes { + bson_date_time_t dt; + bson_timestamp_t ts; + bson_binary_t binary; + bson_regex_t regex; + + bson_oid_t oid; + bson_decimal128_t decimal; + XPACK(A(oid, "_id"), O(dt, ts, binary, regex, decimal)); +}; + +void checkBsonTypes(const BsonTypes&a, const BsonTypes&b, bool checkDecimal) { + EXPECT_EQ(a.dt.ts, b.dt.ts); + + EXPECT_EQ(a.ts.timestamp, b.ts.timestamp); + EXPECT_EQ(a.ts.increment, b.ts.increment); + + EXPECT_EQ(a.binary.data, b.binary.data); + EXPECT_EQ(a.binary.subType, b.binary.subType); + + EXPECT_EQ(a.regex.pattern, b.regex.pattern); + EXPECT_EQ(a.regex.options, b.regex.options); + + EXPECT_EQ(0, memcmp(a.oid.bytes, b.oid.bytes, sizeof(a.oid.bytes))); + + if (checkDecimal) { + EXPECT_EQ(a.decimal.high, b.decimal.high); + EXPECT_EQ(a.decimal.low, b.decimal.low); + } +} +TEST(base, bson_types) { + BsonTypes bt; + + bt.dt.ts = 1638374400018; + + bt.ts.timestamp = 1638374400; + bt.ts.increment = 1; + + bt.binary.data = "hello"; + bt.binary.subType = BSON_SUBTYPE_USER; + + bt.regex.pattern = "^([0-9a-zA-Z]+)"; + bt.regex.options = "i"; + bt.regex.ver = 2; + + uint8_t bytes[] = {0x57, 0xe1, 0x93, 0xd7, 0xa9, 0xcc, 0x81, 0xb4, 0x02, 0x74, 0x98, 0xb5}; + memcpy((void*)bt.oid.bytes, bytes, sizeof(bt.oid.bytes)); + + bt.decimal.high = 1; + bt.decimal.low = 2; + + string js = xpack::json::encode(bt); + cout<<"json:"< v(3); + v[0] = 1; v[1] = 2; v[2] = 3; + cout<<"json1:"< m; + QVector v; + QList l; + + QVector > vv; + + XPACK(O(m, v, l, vv)); +}; + +void checkContainerQT(ContainerQT&a) { + EXPECT_EQ(a.m.size(), 2); + EXPECT_EQ(a.m["a"].a, 1); + EXPECT_EQ(a.m["a"].b, "good"); + EXPECT_EQ(a.m["b"].a, 2); + EXPECT_EQ(a.m["b"].b, "nice"); + + EXPECT_EQ(a.v.size(), 2); + EXPECT_EQ(a.v[0].a, 3); + EXPECT_EQ(a.v[0].b, "hello"); + EXPECT_EQ(a.v[1].a, 4); + EXPECT_EQ(a.v[1].b, "wow"); + + QList::const_iterator lit = a.l.begin(); + EXPECT_EQ(a.l.size(), 2); + EXPECT_EQ(lit->a, 5); + EXPECT_EQ(lit->b, "dida"); + ++lit; + EXPECT_EQ(lit->a, 6); + EXPECT_EQ(lit->b, "haha"); + + EXPECT_EQ(a.vv.size(), 2); + EXPECT_EQ(a.vv[0].size(), 1); + EXPECT_EQ(a.vv[1].size(), 2); + EXPECT_EQ(a.vv[0][0].a, 7); + EXPECT_EQ(a.vv[0][0].b, "lala"); + EXPECT_EQ(a.vv[1][0].a, 8); + EXPECT_EQ(a.vv[1][0].b, "wawa"); + EXPECT_EQ(a.vv[1][1].a, 9); + EXPECT_EQ(a.vv[1][1].b, "kaka"); +} +TEST(container, QT) { + ContainerQT cb; + cb.m["a"] = Base(1, "good"); + cb.m["b"] = Base(2, "nice"); + cb.v.push_back(Base(3, "hello")); + cb.v.push_back(Base(4, "wow")); + cb.l.push_back(Base(5, "dida")); + cb.l.push_back(Base(6, "haha")); + cb.vv.resize(2); + cb.vv[0].resize(1); + cb.vv[1].resize(2); + cb.vv[0][0].a = 7; + cb.vv[0][0].b = "lala"; + cb.vv[1][0].a = 8; + cb.vv[1][0].b = "wawa"; + cb.vv[1][1].a = 9; + cb.vv[1][1].b = "kaka"; + + xpack::BsonEncoder en; + en.encode(NULL, cb, NULL); + string js = en.Json(); + + ContainerQT j1; + xpack::bson::decode(en.String(), j1); + checkContainerQT(j1); +} +#endif + + +// +++++++++++++++++ flags ++++++++++++++++++ +struct FlagM { + int a; + string b; + XPACK(O(a), M(b)); +}; +TEST(flags, M) { + static xpack::BsonBuilder bd("{'a':1}"); + + bool except = false; + try { + FlagM j1; + xpack::bson::decode(bd.Encode(), j1); + } catch(...) { + except = true; + } + EXPECT_TRUE(except); +} +// ++++++++++++++++++bug history+++++++++++++++++++++++ +TEST(bughis, notexists) { + Base b(9, ""); + + static xpack::BsonBuilder bd("{'a':1}"); + xpack::bson::decode(bd.Encode(), b); + EXPECT_EQ(b.a, 1); +} + +int main(int argc, char *argv[]) { +#ifdef XGTEST + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +#else + (void)argc; + (void)argv; + TC_CONTAINER::RUN(); + return 0; +#endif +} diff --git a/util/xpack/gtest/gtest_stub.h b/util/xpack/gtest/gtest_stub.h new file mode 100755 index 0000000..a93be22 --- /dev/null +++ b/util/xpack/gtest/gtest_stub.h @@ -0,0 +1,167 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + + +#ifndef __X_GTEST_STUB_H +#define __X_GTEST_STUB_H + +#include +#include + +#ifdef XPACK_SUPPORT_QT +#include +#endif + +#include +#include + +// test callback function +typedef void(*test_case)(); + +// a test case +struct text_ctx { + const char *group; + const char *name; + test_case tc; +}; + +class Status { +public: + static int&c() { + static int _c; + return _c; + } +}; + +// to collect all test +class TC_CONTAINER { +public: + // add a test case to container + static void add(test_case tc, const char* g, const char*n) { + text_ctx tx; + tx.group = g; + tx.name = n; + tx.tc = tc; + I()._tcs.push_back(tx); + } + + // run all test case + static void RUN() { + const std::vector& tcs = I()._tcs; + for (size_t i=0; i"< _tcs; +}; + +// add test case in ctor +class AUTO_ADD_TC { +public: + AUTO_ADD_TC(test_case tc, const char* g, const char*n) { + TC_CONTAINER::add(tc, g, n); + } +}; + + +// type convert to support std::cout +class TV { +public: +#ifdef X_PACK_SUPPORT_CXX0X + template + static typename std::enable_if::value, int64_t>::type tv(const TYPE &d) { + return (int64_t)d; + } + template + static typename std::enable_if::value, TYPE>::type tv(const TYPE &d) { + return d; + } + static const char* tv(const char* d) { + return d; + } +#else + template + static const TYPE& tv(const TYPE&d) { + return d; + } +#endif + + #ifdef XPACK_SUPPORT_QT + static std::string tv(const QString &d) { + return d.toStdString(); + } + #endif +}; + +#define expect_eq_float(a,b,delta) \ +do {\ + if (!((a)>=(b)-delta && (a)<=(b)+delta)) {\ + std::cout< + +#ifdef XGTEST +#include +#else +#include "gtest_stub.h" +#endif + +#include "xpack/json.h" +#include "xpack/xml.h" +#include "string.h" + +using namespace std; + +struct Base { + int a; + string b; + Base(const int _a=0, const string&_b=""):a(_a), b(_b){} +#ifndef XPACK_OUT_TEST + XPACK(O(a, b)); +}; +#else +}; +XPACK_OUT(Base, O(a,b)); +#endif + + +struct POD { + int a; + char b[10]; +#ifndef XPACK_OUT_TEST + XPACK(O(a, b)); +}; +#else +}; +XPACK_OUT(POD, O(a,b)); +#endif + +// ++++++++++++++++++++++BuiltInTypes++++++++++++++++++++++++++++ +struct BuiltInTypes { + signed char sch; + char ch; + unsigned char uch; + short sh; + unsigned short ush; + int i; + unsigned int ui; + long l; + unsigned long ul; + long long ll; + unsigned long long ull; + float f; + double d; + long double ld; + bool b; +#ifndef XPACK_OUT_TEST + XPACK(AF(F(ATTR), sch, "xml:s:ch"), X(F(ATTR), ch, uch, sh, ush), O(i, ui, l, ul, ll, ull, f, d, ld, b)); +}; +#else +}; +XPACK_OUT(BuiltInTypes, AF(F(ATTR), sch, "xml:s:ch"), X(F(ATTR), ch, uch, sh, ush), O(i, ui, l, ul, ll, ull, f, d, ld, b)); +#endif + +void checkBuiltInTypes(const BuiltInTypes&a, const BuiltInTypes&b) { + EXPECT_EQ(a.sch, b.sch); + EXPECT_EQ(a.ch, b.ch); + EXPECT_EQ(a.uch, b.uch); + EXPECT_EQ(a.sh, b.sh); + EXPECT_EQ(a.ush, b.ush); + EXPECT_EQ(a.i, b.i); + EXPECT_EQ(a.ui, b.ui); + EXPECT_EQ(a.l, b.l); + EXPECT_EQ(a.ul, b.ul); + EXPECT_EQ(a.ll, b.ll); + EXPECT_EQ(a.ull, b.ull); + EXPECT_FLOAT_EQ(a.f, b.f); + EXPECT_DOUBLE_EQ(a.d, b.d); + EXPECT_DOUBLE_EQ(a.ld, b.ld); + EXPECT_TRUE(a.b); +} +TEST(builtin, types) { + BuiltInTypes bt; + bt.sch = 0x7f; + bt.ch = 0x7f; + bt.uch = 0x80; + bt.sh = 0x7fff; + bt.ush = 0x8000; + bt.i = 0x7fffffff; + bt.ui = 0x80000000; + bt.l = 0x7fffffffffffffff; + bt.ul = 0x8000000000000000; + bt.ll = bt.l; + bt.ull = bt.ul; + bt.f = 1.234; + bt.d = 9.678; + bt.ld = 30.678; + bt.b = true; + + string s1 = xpack::json::encode(bt); + BuiltInTypes j1; + xpack::json::decode(s1, j1); + checkBuiltInTypes(j1, bt); + + string s2 = xpack::xml::encode(bt, "root"); + BuiltInTypes j2; + xpack::xml::decode(s2, j2); + checkBuiltInTypes(j2, bt); +} + +// ++++++++++++++++++++bit field+++++++++++++++++++++++++++ +struct BitField { + short a:8; + short b:8; +#ifndef XPACK_OUT_TEST + XPACK(B(F(0), a, b)); +}; +#else +}; +XPACK_OUT(BitField, B(F(0), a, b)); +#endif +void checkBitField(const BitField&a, const BitField&b) { + EXPECT_EQ(a.a, b.a); + EXPECT_EQ(a.b, b.b); +} +TEST(bitfiled, base) { + BitField bf; + bf.a = 0x70; + bf.b = 0x7f; + + string s1 = xpack::json::encode(bf); + BitField j1; + xpack::json::decode(s1, j1); + checkBitField(j1, bf); + + string s2 = xpack::xml::encode(bf, "root"); + BitField j2; + xpack::xml::decode(s2, j2); + checkBitField(j2, bf); +} + +// +++++++++++++++++++ enum +++++++++++++++++++++ +enum Enum1 { + E0 = 0, + E1 = 1, +}; +#ifdef X_PACK_SUPPORT_CXX0X +enum class Enum2:int64_t { + E2 = 2, + E3 = 3, +}; +#else +enum Enum2 { + E2 = 2, + E3 = 3, +}; +#endif +struct EnumBase { + Enum1 e1; + Enum2 e2; +#ifndef XPACK_OUT_TEST + #ifdef X_PACK_SUPPORT_CXX0X + XPACK(O(e1), E(F(0), e2)); + #else + XPACK(E(F(0), e1, e2)); + #endif +}; +#else +}; +#ifdef X_PACK_SUPPORT_CXX0X +XPACK_OUT(EnumBase, O(e1), E(F(0), e2)); +#else +XPACK_OUT(EnumBase, E(F(0), e1, e2)); +#endif +#endif +void checkEnumBase(const EnumBase&a, const EnumBase&b) { + EXPECT_EQ(a.e1, b.e1); + EXPECT_EQ(a.e2, b.e2); +} +TEST(enum, base) { + EnumBase eb; + eb.e1 = E0; +#ifdef X_PACK_SUPPORT_CXX0X + eb.e2 = Enum2::E3; +#else + eb.e2 = E3; +#endif + + string s1 = xpack::json::encode(eb); + EnumBase j1; + xpack::json::decode(s1, j1); + checkEnumBase(j1, eb); + + string s2 = xpack::xml::encode(eb, "root"); + EnumBase j2; + xpack::xml::decode(s2, j2); + checkEnumBase(j2, eb); +} +#ifdef X_PACK_SUPPORT_CXX0X +TEST(enum, map) { + map m; + m[Enum2::E2] = 2; + m[Enum2::E3] = 3; + + string s1 = xpack::json::encode(m); + map j1; + xpack::json::decode(s1, j1); + EXPECT_EQ(j1[Enum2::E2], 2); + EXPECT_EQ(j1[Enum2::E3], 3); +} +#endif + +// ++++++++++++++++inherit+++++++++++++++++++++++++++++ +struct InheritBase { + int b1; + string b2; +#ifndef XPACK_OUT_TEST + XPACK(O(b1, b2)); +}; +#else +}; +XPACK_OUT(InheritBase, O(b1, b2)); +#endif +struct InheritChild:public InheritBase { + int c1; + string c2; +#ifndef XPACK_OUT_TEST + XPACK(I(InheritBase), O(c1, c2)); +}; +#else +}; +XPACK_OUT(InheritChild, I(InheritBase), O(c1, c2)); +#endif +void checkInherit(const InheritChild&a, const InheritChild&b) { + EXPECT_EQ(a.b1, b.b1); + EXPECT_EQ(a.b2, b.b2); + EXPECT_EQ(a.c1, b.c1); + EXPECT_EQ(a.c2, b.c2); +} +TEST(inherit, base) { + InheritChild c; + c.b1 = 1; + c.b2 = "base"; + c.c1 = 2; + c.c2 = "child"; + + string s1 = xpack::json::encode(c); + InheritChild j1; + xpack::json::decode(s1, j1); + checkInherit(j1, c); + + string s2 = xpack::xml::encode(c, "root"); + InheritChild j2; + xpack::xml::decode(s2, j2); + checkInherit(j2, c); +} + +// ++++++++++++++++++++++++array++++++++++++++++++++++++++ +struct Array { + int a[3]; + char b[3]; + Base c[3]; +#ifndef XPACK_OUT_TEST + XPACK(O(a, b, c)); +}; +#else +}; +XPACK_OUT(Array, O(a, b, c)); +#endif +void checkArray(const Array&a, const Array&b) { + EXPECT_EQ(a.a[0], b.a[0]); + EXPECT_EQ(a.a[1], b.a[1]); + EXPECT_EQ(a.a[2], b.a[2]); + EXPECT_EQ(a.b[0], b.b[0]); + EXPECT_EQ(a.b[1], b.b[1]); + EXPECT_EQ(a.b[2], b.b[2]); + EXPECT_EQ(a.c[0].a, b.c[0].a); + EXPECT_EQ(a.c[0].b, b.c[0].b); + EXPECT_EQ(a.c[1].a, b.c[1].a); + EXPECT_EQ(a.c[1].b, b.c[1].b); + EXPECT_EQ(a.c[2].a, b.c[2].a); + EXPECT_EQ(a.c[2].b, b.c[2].b); +} +TEST(array, base) { + Array a; + a.a[0] = 1; + a.a[1] = 2; + a.a[2] = 3; + a.b[0] = 'g'; + a.b[1] = 'o'; + a.b[2] = '\0'; + a.c[0].a = 11; + a.c[1].a = 12; + a.c[2].a = 13; + a.c[0].b = "hello"; + a.c[1].b = "good"; + a.c[1].b = "nice"; + + string s1 = xpack::json::encode(a); + Array j1; + xpack::json::decode(s1, j1); + checkArray(j1, a); + + string s2 = xpack::xml::encode(a, "root"); + Array j2; + xpack::xml::decode(s2, j2); + checkArray(j2, a); + + char ca[3]; + xpack::json::decode("\"good\"", ca); + EXPECT_TRUE(0==strcmp(ca, "go")); + + int ia[3]; + xpack::json::decode("[1, 2, 3, 4]", ia); + EXPECT_EQ(ia[2], 3); +} + + +// +++++++++++++++ container ++++++++++++++++++++++ +struct ContainerBase { + map m; + set s; + vector v; + list l; + + vector > vv; +#ifndef XPACK_OUT_TEST + XPACK(O(m, s, v, l, vv)); +}; +#else +}; +XPACK_OUT(ContainerBase, O(m, s, v, l, vv)); +#endif +void checkContainerBase(ContainerBase&cb) { + EXPECT_EQ(cb.m.size(), 2U); + EXPECT_EQ(cb.m["a"], 1); + EXPECT_EQ(cb.m["b"], 2); + EXPECT_EQ(cb.s.size(), 2U); + EXPECT_TRUE(cb.s.find(3)!=cb.s.end()); + EXPECT_TRUE(cb.s.find(4)!=cb.s.end()); + EXPECT_TRUE(cb.s.find(5)==cb.s.end()); + EXPECT_EQ(cb.v.size(), 2U); + EXPECT_EQ(cb.v[0], 5); + EXPECT_EQ(cb.v[1], 6); + EXPECT_EQ(cb.l.size(), 2U); + EXPECT_EQ(*(cb.l.begin()), 7); + EXPECT_EQ(*(++cb.l.begin()), 8); + + EXPECT_EQ(cb.vv.size(), 2U); + EXPECT_EQ(cb.vv[0].size(), 2U); + EXPECT_EQ(cb.vv[1].size(), 2U); + EXPECT_EQ(cb.vv[0][0], 11); + EXPECT_EQ(cb.vv[0][1], 12); + EXPECT_EQ(cb.vv[1][0], 22); + EXPECT_EQ(cb.vv[1][1], 23); +} +TEST(container, base) { + string s = "{\"m\":{\"a\":1, \"b\":2}, \"s\":[3, 4], \"v\":[5,6], \"l\":[7, 8], \"vv\":[[11,12],[22,23]]}"; + + ContainerBase cb; + xpack::json::decode(s, cb); + checkContainerBase(cb); + + string s1 = xpack::json::encode(cb); + ContainerBase cb2; + xpack::json::decode(s1, cb2); + string s2 = xpack::json::encode(cb2); + EXPECT_EQ(s1, s2); + + string s3 = xpack::xml::encode(cb, "root"); + ContainerBase cb3; + xpack::xml::decode(s3, cb3); + checkContainerBase(cb3); + string s4 = xpack::xml::encode(cb3, "root"); + EXPECT_EQ(s3, s4); +} +struct ContainerStruct { + map m; + vector v; + list l; + + vector > vv; +#ifndef XPACK_OUT_TEST + XPACK(O(m, v, l, vv)); +}; +#else +}; +XPACK_OUT(ContainerStruct, O(m, v, l, vv)); +#endif +void checkContainerStruct(ContainerStruct&a) { + EXPECT_EQ(a.m.size(), 2U); + EXPECT_EQ(a.m["a"].a, 1); + EXPECT_EQ(a.m["a"].b, "good"); + EXPECT_EQ(a.m["b"].a, 2); + EXPECT_EQ(a.m["b"].b, "nice"); + + EXPECT_EQ(a.v.size(), 2U); + EXPECT_EQ(a.v[0].a, 3); + EXPECT_EQ(a.v[0].b, "hello"); + EXPECT_EQ(a.v[1].a, 4); + EXPECT_EQ(a.v[1].b, "wow"); + + list::const_iterator lit = a.l.begin(); + EXPECT_EQ(a.l.size(), 2U); + EXPECT_EQ(lit->a, 5); + EXPECT_EQ(lit->b, "dida"); + ++lit; + EXPECT_EQ(lit->a, 6); + EXPECT_EQ(lit->b, "haha"); + + EXPECT_EQ(a.vv.size(), 2U); + EXPECT_EQ(a.vv[0].size(), 1U); + EXPECT_EQ(a.vv[1].size(), 2U); + EXPECT_EQ(a.vv[0][0].a, 7); + EXPECT_EQ(a.vv[0][0].b, "lala"); + EXPECT_EQ(a.vv[1][0].a, 8); + EXPECT_EQ(a.vv[1][0].b, "wawa"); + EXPECT_EQ(a.vv[1][1].a, 9); + EXPECT_EQ(a.vv[1][1].b, "kaka"); +} +TEST(container, struct) { + ContainerStruct cb; + cb.m["a"] = Base(1, "good"); + cb.m["b"] = Base(2, "nice"); + cb.v.push_back(Base(3, "hello")); + cb.v.push_back(Base(4, "wow")); + cb.l.push_back(Base(5, "dida")); + cb.l.push_back(Base(6, "haha")); + cb.vv.resize(2); + cb.vv[0].resize(1); + cb.vv[1].resize(2); + cb.vv[0][0].a = 7; + cb.vv[0][0].b = "lala"; + cb.vv[1][0].a = 8; + cb.vv[1][0].b = "wawa"; + cb.vv[1][1].a = 9; + cb.vv[1][1].b = "kaka"; + + string s1 = xpack::json::encode(cb); + ContainerStruct cb1; + xpack::json::decode(s1, cb1); + checkContainerStruct(cb1); + string s2 = xpack::json::encode(cb1); + EXPECT_EQ(s1, s2); + + string s3 = xpack::xml::encode(cb, "root"); + ContainerStruct cb3; + xpack::xml::decode(s3, cb3); + checkContainerStruct(cb3); + string s4 = xpack::xml::encode(cb3, "root"); + EXPECT_EQ(s3, s4); +} + +// +++++++++++++++++++ QT ++++++++++++++++++++ +#ifdef XPACK_SUPPORT_QT +struct ContainerQT { + QMap m; + QVector v; + QList l; + + QVector > vv; +#ifndef XPACK_OUT_TEST + XPACK(O(m, v, l, vv)); +}; +#else +}; +XPACK_OUT(ContainerQT, O(m, v, l, vv)); +#endif +void checkContainerQT(ContainerQT&a) { + EXPECT_EQ(a.m.size(), 2); + EXPECT_EQ(a.m["a"].a, 1); + EXPECT_EQ(a.m["a"].b, "good"); + EXPECT_EQ(a.m["b"].a, 2); + EXPECT_EQ(a.m["b"].b, "nice"); + + EXPECT_EQ(a.v.size(), 2); + EXPECT_EQ(a.v[0].a, 3); + EXPECT_EQ(a.v[0].b, "hello"); + EXPECT_EQ(a.v[1].a, 4); + EXPECT_EQ(a.v[1].b, "wow"); + + QList::const_iterator lit = a.l.begin(); + EXPECT_EQ(a.l.size(), 2); + EXPECT_EQ(lit->a, 5); + EXPECT_EQ(lit->b, "dida"); + ++lit; + EXPECT_EQ(lit->a, 6); + EXPECT_EQ(lit->b, "haha"); + + EXPECT_EQ(a.vv.size(), 2); + EXPECT_EQ(a.vv[0].size(), 1); + EXPECT_EQ(a.vv[1].size(), 2); + EXPECT_EQ(a.vv[0][0].a, 7); + EXPECT_EQ(a.vv[0][0].b, "lala"); + EXPECT_EQ(a.vv[1][0].a, 8); + EXPECT_EQ(a.vv[1][0].b, "wawa"); + EXPECT_EQ(a.vv[1][1].a, 9); + EXPECT_EQ(a.vv[1][1].b, "kaka"); +} +TEST(container, QT) { + ContainerQT cb; + cb.m["a"] = Base(1, "good"); + cb.m["b"] = Base(2, "nice"); + cb.v.push_back(Base(3, "hello")); + cb.v.push_back(Base(4, "wow")); + cb.l.push_back(Base(5, "dida")); + cb.l.push_back(Base(6, "haha")); + cb.vv.resize(2); + cb.vv[0].resize(1); + cb.vv[1].resize(2); + cb.vv[0][0].a = 7; + cb.vv[0][0].b = "lala"; + cb.vv[1][0].a = 8; + cb.vv[1][0].b = "wawa"; + cb.vv[1][1].a = 9; + cb.vv[1][1].b = "kaka"; + + string s1 = xpack::json::encode(cb); + ContainerQT cb1; + xpack::json::decode(s1, cb1); + checkContainerQT(cb1); + string s2 = xpack::json::encode(cb1); + EXPECT_EQ(s1, s2); + + string s3 = xpack::xml::encode(cb, "root"); + ContainerQT cb3; + xpack::xml::decode(s3, cb3); + checkContainerQT(cb3); + string s4 = xpack::xml::encode(cb3, "root"); + EXPECT_EQ(s3, s4); +} +#endif + +// ++++++++++++++ xtypes +++++++++++++++++++++ +struct XtypeUnion { + int type; + union { + POD p; + int i; + char s[10]; + }; +}; +struct XtypeUnionTop { + string name; + XtypeUnion un; +#ifndef XPACK_OUT_TEST + XPACK(O(name, un)); +}; +#else +}; +XPACK_OUT(XtypeUnionTop, O(name, un)); +#endif + +namespace xpack { // must define in namespace xpack + +template<> +struct is_xpack_xtype {static bool const value = true;}; + +template +bool xpack_xtype_decode(OBJ &obj, XtypeUnion &val, const Extend *ext) { + (void)ext; + OBJ *o = &obj; + o->decode("type", val.type, NULL); + switch (val.type) { + case 1: + o->decode("p", val.p, NULL); + break; + case 2: + o->decode("i", val.i, NULL); + break; + case 3: + o->decode("s", val.s, NULL); + break; + } + return true; +} + +template +bool xpack_xtype_encode(OBJ &obj, const char*key, const XtypeUnion &val, const Extend *ext) { + obj.ObjectBegin(key, ext); + obj.encode("type", val.type, NULL); + switch (val.type) { + case 1: + obj.encode("p", val.p, NULL); + break; + case 2: + obj.encode("i", val.i, NULL); + break; + case 3: + obj.encode("s", val.s, NULL); + break; + } + obj.ObjectEnd(key, ext); + return true; +} + +} + +void checkXtypesUnion(const XtypeUnionTop &xt, bool cn) { + if (cn) { + EXPECT_EQ(xt.name, "hello"); + } + + if (xt.un.type == 1) { + EXPECT_EQ(xt.un.p.a, 10); + EXPECT_TRUE(0 == strcmp(xt.un.p.b, "good")); + } else if (xt.un.type == 2) { + EXPECT_EQ(xt.un.i, 20); + } else if (xt.un.type == 3) { + EXPECT_TRUE(0 == strcmp(xt.un.s, "nice")); + } else { + EXPECT_EQ(xt.un.type, 1); + } +} +TEST(xtypes, union) { + XtypeUnionTop xt; + xt.name = "hello"; + xt.un.type = 1; + xt.un.p.a = 10; + strcpy(xt.un.p.b, "good"); + + string s = xpack::json::encode(xt); + XtypeUnionTop xt1; + xpack::json::decode(s, xt1); + checkXtypesUnion(xt1, true); + + s = xpack::xml::encode(xt, "root"); + XtypeUnionTop xt2; + xpack::xml::decode(s, xt2); + checkXtypesUnion(xt2, true); + + s = xpack::json::encode(xt.un); + XtypeUnionTop xt3; + xpack::json::decode(s, xt3.un); + checkXtypesUnion(xt3, false); + + s = xpack::xml::encode(xt.un, "root"); + XtypeUnionTop xt4; + xpack::xml::decode(s, xt4.un); + checkXtypesUnion(xt4, false); +} + +// +++++++++++++++++ custom +++++++++++++++++ +struct Custom { + int a; + int b; + int c; + XPACK(O(a, b), C(hex, F(0), c)); +}; + +namespace xpack { +template +bool hex_encode(OBJ &obj, const Custom&c, const char*key, const int &i, const Extend *ext) { + (void)c; + stringstream ss; + ss<<"0x"< +bool hex_decode(OBJ &obj, Custom&c, const char*key, int &i, const Extend *ext) { + (void)c; + string s; + if (!obj.decode(key, s, ext)) { + return false; + } + stringstream ss; + ss<>i; + return true; +} + +} +TEST(custom, hex) { + Custom c; + c.a = 1; + c.b = 2; + c.c = 0xe; + string s = xpack::json::encode(c); + EXPECT_EQ(s, "{\"a\":1,\"b\":2,\"c\":\"0xe\"}"); + + Custom c1; + xpack::json::decode(s, c1); + EXPECT_EQ(c1.a, 1); + EXPECT_EQ(c1.b, 2); + EXPECT_EQ(c1.c, 0xe); +} + +// +++++++++++++++++ flags ++++++++++++++++++ +struct FlagEN { + int a; + string b; +#ifdef X_PACK_SUPPORT_CXX0X + shared_ptr c; +#else + int c; +#endif + XPACK(X(F(EN), a, b, c)); + FlagEN(){c=0;} +}; +TEST(flags, EN) { + FlagEN fe; + fe.a = 1; + + string s = xpack::json::encode(fe); + EXPECT_EQ(s, "{\"a\":1,\"b\":null,\"c\":null}"); + + FlagEN f1; + xpack::json::decode(s, f1); + EXPECT_EQ(f1.a, 1); + EXPECT_EQ(f1.b, ""); +#ifdef X_PACK_SUPPORT_CXX0X + EXPECT_EQ(f1.c.get(), (int*)NULL); +#else + EXPECT_EQ(f1.c, 0); +#endif +} +struct FlagM { + int a; + string b; + XPACK(O(a), M(b)); +}; +TEST(flags, M) { + string s = "{\"a\":1}"; + bool except = false; + try { + FlagM f; + xpack::json::decode(s, f); + } catch(...) { + except = true; + } + EXPECT_TRUE(except); +} +struct FlagATTR { + int a; + string b; + XPACK(O(a), X(F(ATTR), b)); +}; +TEST(flags, ATTR) { + FlagATTR f; + f.a = 1; + f.b = "hello"; + string s = xpack::xml::encode(f, "root"); + EXPECT_EQ(s, "1"); + + FlagATTR f1; + xpack::xml::decode(s, f1); + EXPECT_EQ(f1.a, 1); + EXPECT_EQ(f1.b, "hello"); +} +struct FlagVectorLabel { + vector a; + vector b; + vector c; + XPACK(O(a), A(b, "xml:b,sbs", c, "xml:c,vl@x")); +}; +TEST(flags, vectorlabel) { + FlagVectorLabel f; + f.a.push_back(1); + f.a.push_back(2); + f.b.push_back(3); + f.b.push_back(4); + f.c.push_back(5); + f.c.push_back(6); + + string s = xpack::xml::encode(f, "root"); + EXPECT_EQ(s, "123456"); + + FlagVectorLabel f1; + xpack::xml::decode(s, f1); + EXPECT_EQ(f1.a.size(), 2U); + EXPECT_EQ(f1.a[0], 1); + EXPECT_EQ(f1.a[1], 2); + EXPECT_EQ(f1.b.size(), 2U); + EXPECT_EQ(f1.b[0], 3); + EXPECT_EQ(f1.b[1], 4); + EXPECT_EQ(f1.c.size(), 2U); + EXPECT_EQ(f1.c[0], 5); + EXPECT_EQ(f1.c[1], 6); +} + +TEST(jsondata, memory) { + xpack::JsonData *jd = new xpack::JsonData; + + xpack::json::decode("[0, 1, 2, 3]", *jd); + EXPECT_TRUE(jd->IsArray()); + EXPECT_EQ(jd->Size(), 4U); + EXPECT_TRUE((*jd)[(size_t)0].IsNumber()); + EXPECT_EQ((*jd)[1].Get(), 1); + + xpack::JsonData s = *jd; + delete jd; + + EXPECT_TRUE(s.IsArray()); + EXPECT_TRUE(s[(size_t)0].IsNumber()); + EXPECT_EQ(s[1].Get(), 1); +} + +// ++++++++++++++++++bug history+++++++++++++++++++++++ +TEST(bughis, notexists) { + Base b(9, ""); + xpack::json::decode("{\"a\":1}", b); + EXPECT_EQ(b.a, 1); + + Base b1(10, ""); + xpack::xml::decode("1", b1); + EXPECT_EQ(b1.a, 1); +} + +struct OmitEmpty{ + int a; + vector b; +#ifdef X_PACK_SUPPORT_CXX0X + shared_ptr c; +#else + Base c; +#endif + XPACK(X(F(OE), a, b, c)); +}; +TEST(bughis, omitempty) { + OmitEmpty oe; + oe.a = 0; + oe.b.push_back(0); + oe.b.push_back(0); +#ifdef X_PACK_SUPPORT_CXX0X + oe.c.reset(new Base(0, "")); +#endif + + string s = xpack::json::encode(oe); + EXPECT_EQ(s, "{\"b\":[0,0],\"c\":{\"a\":0,\"b\":\"\"}}"); + s = xpack::xml::encode(oe, "root"); + EXPECT_EQ(s, "000"); + + oe.b.clear(); + oe.a = 1; + s = xpack::json::encode(oe); + EXPECT_EQ(s, "{\"a\":1,\"c\":{\"a\":0,\"b\":\"\"}}"); + s = xpack::xml::encode(oe, "root"); + EXPECT_EQ(s, "10"); +} + +#ifdef X_PACK_SUPPORT_CXX0X +struct SharedPtrNull{ + shared_ptr jd; + XPACK(O(jd)); +}; +TEST(bughis, shared_ptr_null) { + SharedPtrNull sn; + + xpack::json::decode("{\"jd\":null}", sn); + EXPECT_FALSE(sn.jd); +} +#endif + +int main(int argc, char *argv[]) { +#ifdef XGTEST + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +#else + (void)argc; + (void)argv; + TC_CONTAINER::RUN(); + return 0; +#endif +} diff --git a/util/xpack/json.h b/util/xpack/json.h new file mode 100755 index 0000000..b660eec --- /dev/null +++ b/util/xpack/json.h @@ -0,0 +1,63 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_JSON_H +#define __X_PACK_JSON_H + +#include "json_decoder.h" +#include "json_encoder.h" +#if defined(X_PACK_SUPPORT_CXX0X) || defined (_GNU_SOURCE) +#include "json_data.h" +#endif +#include "xpack.h" + +namespace xpack { + +class json { +public: + template + static void decode(const std::string &data, T &val) { + JsonDecoder de; + de.decode(data, val); + } + template + static void decode(const rapidjson::Value &data, T &val) { + JsonNode node(&data); + XDecoder(NULL, (const char*)NULL, node).decode(val, NULL); + } + template + static void decode_file(const std::string &file_name, T &val) { + JsonDecoder de; + de.decode_file(file_name, val); + } + + template + static std::string encode(const T &val) { + JsonEncoder en; + return en.encode(val); + } + + template + static std::string encode(const T &val, int flag, int indentCount, char indentChar) { + (void)flag; + JsonEncoder en(indentCount, indentChar); + return en.encode(val); + } +}; + +} + +#endif diff --git a/util/xpack/json_data.h b/util/xpack/json_data.h new file mode 100755 index 0000000..6fb37cc --- /dev/null +++ b/util/xpack/json_data.h @@ -0,0 +1,115 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_JSON_DATA_H +#define __X_PACK_JSON_DATA_H + + +#include "rapidjson_custom.h" +#include "rapidjson/document.h" +#include "util.h" +#include "traits.h" + +namespace xpack { + +// wrapper for rapidjson::Value. +// If we use other json parser someday, users won't have to modify the code. +// Most of the code is copied from rapidjson. +enum JsonType { + kNullType = 0, //!< null + kFalseType = 1, //!< false + kTrueType = 2, //!< true + kObjectType = 3, //!< object + kArrayType = 4, //!< array + kStringType = 5, //!< string + kNumberType = 6 //!< number +}; + +class JsonData { + friend class JsonNode; + friend class JsonWriter; +public: + JsonData():current(NULL){} + + // check is valid JsonData + operator bool() const { + return NULL != current; + } + JsonType Type() const { + if (NULL != current) { + return static_cast(current->GetType()); + } + return kNullType; // ? + } + bool IsNull() const { return NULL==current||current->IsNull(); } + bool IsBool() const { return NULL!=current && current->IsBool(); } + bool IsObject() const { return NULL!=current && current->IsObject(); } + bool IsArray() const { return NULL!=current && current->IsArray(); } + bool IsNumber() const { return NULL!=current && current->IsNumber(); } + bool IsDouble() const { return NULL!=current && current->IsDouble(); } + bool IsString() const { return NULL!=current && current->IsString(); } + size_t Size() const {return (size_t)current->Size();} + + JsonData operator [](const char *key) const { + if (NULL != current && current->IsObject()) { + rapidjson::Value::ConstMemberIterator iter = current->FindMember(key); + if (iter != current->MemberEnd()) { + return JsonData(&iter->value); + } // no exception ? + } + return JsonData(); + } + JsonData operator [](size_t index) const { + if (NULL != current && current->IsArray() && index < (size_t)current->Size()) { + return JsonData(&(*current)[(rapidjson::SizeType)index]); + } + return JsonData(); + } + + std::string String() const; // Implementation in json_encoder.h + + template // For base byte(integer/float/double/bool/std::string), implementation in json_decoder.h + T Get() const; + + template // struct, implementation in json_decoder.h + bool Get(T &val) const; + +private: + JsonData(const rapidjson::Value*v):current(v){} + + void reset(const rapidjson::Value *v) { + node.reset(); + allocator.reset(); + current = NULL; + if (NULL != v) { + if (v->IsNull()) { + node.reset(new rapidjson::Value()); + } else { + allocator.reset(new rapidjson::MemoryPoolAllocator<>()); + node.reset(new rapidjson::Value(*v, *allocator, true)); + } + current = node.get(); + } + } + x_shared_ptr > allocator; + x_shared_ptr node; // release node first + + const rapidjson::Value* current; +}; + +} + +#endif diff --git a/util/xpack/json_decoder.h b/util/xpack/json_decoder.h new file mode 100755 index 0000000..de3f8e0 --- /dev/null +++ b/util/xpack/json_decoder.h @@ -0,0 +1,218 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_JSON_DECODER_H +#define __X_PACK_JSON_DECODER_H + +#include + +#include "rapidjson_custom.h" +#include "rapidjson/document.h" +#include "rapidjson/error/en.h" + +#include "xdecoder.h" +#include "json_data.h" + + +namespace xpack { + + +class JsonNode { + typedef XDecoder decoder; +public: + typedef rapidjson::Value::ConstMemberIterator Iterator; + + JsonNode(const rapidjson::Value* val=NULL):v(val){} + + // convert JsonData to JsonNode + // The life cycle of jd cannot be shorter than JsonNode + JsonNode(const JsonData&jd):v(jd.current){} + + inline static const char * Name() { + return "json"; + } + operator bool() const { + return v != NULL; + } + bool IsNull() const { + return v!=NULL && v->IsNull(); + } + JsonNode Find(decoder&de, const char*key, const Extend *ext) const { + (void)ext; + if (v->IsNull()) { + return JsonNode(); + } else if (!v->IsObject()) { + de.decode_exception("not object", NULL); + } + rapidjson::Value::ConstMemberIterator iter = v->FindMember(key); + if (iter != v->MemberEnd()) { + return JsonNode(&iter->value); + } else { + return JsonNode(); + } + } + size_t Size(decoder&de) const { + if (v->IsNull()) { + return 0; + } else if (!v->IsArray()) { + de.decode_exception("not array", NULL); + } + return (size_t)v->Size(); + } + JsonNode At(size_t index) const { // no exception + return JsonNode(&(*v)[(rapidjson::SizeType)index]); + } + JsonNode Next(decoder&de, const JsonNode&parent, Iterator&iter, std::string&key) const { + if (!parent.v->IsObject()) { + de.decode_exception("not object", NULL); + } + + if (v != parent.v) { + ++iter; + } else { + iter = parent.v->MemberBegin(); + } + + if (iter != parent.v->MemberEnd()) { + key = iter->name.GetString(); + return JsonNode(&iter->value); + } + + return JsonNode(); + } + + bool Get(decoder&de, std::string&val, const Extend*ext) { + (void)ext; + if (v->IsString()) { + val = std::string(v->GetString(), v->GetStringLength()); + } else if (!v->IsNull()) { + de.decode_exception("not string", NULL); + } + return true; + } + bool Get(decoder&de, bool &val, const Extend*ext) { + (void)ext; + if (v->IsBool()) { + val = v->GetBool(); + } else if (v->IsInt64()) { + val = (0 != (v->GetInt64())); + } else if (v->IsNull()) { + val = false; + } else { + de.decode_exception("not bool or integer", NULL); + } + return true; + } + template + typename x_enable_if::is_integer, bool>::type Get(decoder&de, T &val, const Extend*ext){ + (void)ext; + if (v->IsInt()) { + val = (T)v->GetInt(); + } else if (v->IsUint()) { + val = (T)v->GetUint(); + } else if (v->IsInt64()) { + val = (T)v->GetInt64(); + } else if (v->IsUint64()) { + val = (T)v->GetUint64(); + } else if (v->IsNull()) { + val = 0; + } else { + de.decode_exception("not integer", NULL); + } + return true; + } + template + typename x_enable_if::is_float, bool>::type Get(decoder&de, T &val, const Extend*ext){ + (void)ext; + if (v->IsNumber()) { + val = (T)v->GetDouble(); + } else if (v->IsNull()) { + val = 0; + } else { + de.decode_exception("not number", NULL); + } + return true; + } + + ////////////// for JsonData /////////////////////// + bool decode_type_spec(JsonData& val, const Extend *ext) { + (void)ext; + val.reset(v); + return true; + } + +private: + const rapidjson::Value* v; + rapidjson::Value::ConstMemberIterator iter; +}; + +class JsonDecoder { +public: + template + bool decode(const std::string&str, T&val) { + rapidjson::Document doc; + if (this->parse(str, doc)) { + JsonNode node(&doc); + return XDecoder(NULL, (const char*)NULL, node).decode(val, NULL); + } + return false; + } + template + bool decode_file(const std::string&fname, T&val) { + std::string data; + bool ret = Util::readfile(fname, data); + if (ret) { + ret = this->decode(data, val); + } + return ret; + } +private: + bool parse(const std::string&data, rapidjson::Document &doc) { + std::string err; + + const unsigned int parseFlags = rapidjson::kParseNanAndInfFlag; + doc.Parse(data.data(), data.length()); + + if (doc.HasParseError()) { + size_t offset = doc.GetErrorOffset(); + std::string parse_err(rapidjson::GetParseError_En(doc.GetParseError())); + std::string err_data = data.substr(offset, 32); + err = "Parse json fail. err="+parse_err+". offset="+err_data; + throw std::runtime_error(err); + } + return true; + } +}; + +// /////////////// JsonData /////////////////// +template<>struct is_xpack_type_spec {static bool const value = true;}; + +template +inline T JsonData::Get() const { + JsonNode node(*this); + T t; + XDecoder(NULL, (const char*)NULL, node).decode(t, NULL); + return t; +} +template +inline bool JsonData::Get(T&val) const { + JsonNode node(*this); + return XDecoder(NULL, (const char*)NULL, node).decode(val, NULL); +} + +} + +#endif diff --git a/util/xpack/json_encoder.h b/util/xpack/json_encoder.h new file mode 100755 index 0000000..74e0dd4 --- /dev/null +++ b/util/xpack/json_encoder.h @@ -0,0 +1,294 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_JSON_ENCODER_H +#define __X_PACK_JSON_ENCODER_H + +#include + +#include "rapidjson_custom.h" +#include "rapidjson/prettywriter.h" +#include "rapidjson/stringbuffer.h" + +#include "xencoder.h" +#include "json_data.h" + +namespace xpack { + +class JsonWriter:private noncopyable { + typedef rapidjson::StringBuffer JSON_WRITER_BUFFER; + typedef rapidjson::Writer JSON_WRITER_WRITER; + typedef rapidjson::PrettyWriter JSON_WRITER_PRETTY; + + friend class XEncoder; + friend class JsonEncoder; + + const static bool support_null = true; +public: + JsonWriter(int indentCount = -1, char indentChar = ' ', int maxDecimalPlaces = -1) { + _buf = new JSON_WRITER_BUFFER; + if (indentCount < 0) { + _writer = new JSON_WRITER_WRITER(*_buf); + if (maxDecimalPlaces > 0) { + _writer->SetMaxDecimalPlaces(maxDecimalPlaces); + } + _pretty = NULL; + } else { + _pretty = new JSON_WRITER_PRETTY(*_buf); + _pretty->SetIndent(indentChar, indentCount); + if (maxDecimalPlaces > 0) { + _pretty->SetMaxDecimalPlaces(maxDecimalPlaces); + } + _writer = NULL; + } + } + ~JsonWriter() { + if (NULL != _buf) { + delete _buf; + _buf = NULL; + } + if (NULL != _writer) { + delete _writer; + _writer = NULL; + } + if (NULL != _pretty) { + delete _pretty; + _pretty = NULL; + } + } + +private: + inline static const char *Name() { + return "json"; + } + inline const char *IndexKey(size_t index) { + (void)index; + return NULL; + } + std::string String() { + return _buf->GetString(); + } + + void ArrayBegin(const char *key, const Extend *ext) { + (void)ext; + xpack_set_key(key); + if (NULL != _writer) { + _writer->StartArray(); + } else { + if (Extend::Flag(ext) & X_PACK_FLAG_SL) { + _pretty->SetFormatOptions(rapidjson::kFormatSingleLineArray); + } + _pretty->StartArray(); + } + } + void ArrayEnd(const char *key, const Extend *ext) { + (void)key; + (void)ext; + if (NULL != _writer) { + _writer->EndArray(); + } else { + _pretty->EndArray(); + if (Extend::Flag(ext) & X_PACK_FLAG_SL) { + _pretty->SetFormatOptions(rapidjson::kFormatDefault); + } + } + } + void ObjectBegin(const char *key, const Extend *ext) { + (void)ext; + xpack_set_key(key); + if (NULL != _writer) { + _writer->StartObject(); + } else { + _pretty->StartObject(); + } + } + void ObjectEnd(const char *key, const Extend *ext) { + (void)key; + (void)ext; + if (NULL != _writer) { + _writer->EndObject(); + } else { + _pretty->EndObject(); + } + } + bool WriteNull(const char*key, const Extend *ext) { + (void)ext; + xpack_set_key(key); + if (NULL != _writer) { + _writer->Null(); + } else { + _pretty->Null(); + } + return true; + } + bool encode_bool(const char*key, const bool&val, const Extend *ext) { + (void)ext; + xpack_set_key(key); + if (NULL != _writer) { + _writer->Bool(val); + } else { + _pretty->Bool(val); + } + return true; + } + bool encode_string(const char*key, const char*val, size_t length, const Extend *ext) { + (void)ext; + xpack_set_key(key); + if (NULL != _writer) { + _writer->String(val, length); + } else { + _pretty->String(val, length); + } + return true; + } + bool encode_string(const char*key, const std::string&val, const Extend *ext) { + return this->encode_string(key, val.data(), val.length(), ext); + } + template + typename x_enable_if::is_integer && numeric::is_signed, bool>::type encode_number(const char*key, const T&val, const Extend *ext) { + (void)ext; + xpack_set_key(key); + if (NULL != _writer) { + _writer->Int64((int64_t)val); + } else { + _pretty->Int64((int64_t)val); + } + return true; + } + template + typename x_enable_if::is_integer && !numeric::is_signed, bool>::type encode_number(const char*key, const T&val, const Extend *ext) { + (void)ext; + xpack_set_key(key); + if (NULL != _writer) { + _writer->Uint64((uint64_t)val); + } else { + _pretty->Uint64((uint64_t)val); + } + return true; + } + template + typename x_enable_if::is_float, bool>::type encode_number(const char*key, const T&val, const Extend *ext) { + (void)ext; + xpack_set_key(key); + if (NULL != _writer) { + _writer->Double((double)val); + } else { + _pretty->Double((double)val); + } + return true; + } + + // JsonData + bool encode_type_spec(const char*key, const JsonData&val, const Extend *ext) { + if (val.current == NULL) { + return false; + } + return this->encode_json_value(key, *val.current, ext); + } + + bool encode_json_value(const char*key, const rapidjson::Value& val, const Extend *ext) { + switch (val.GetType()){ + case rapidjson::kNullType: + return this->WriteNull(key, ext); + case rapidjson::kFalseType: + case rapidjson::kTrueType: + return this->encode_bool(key, val.GetBool(), ext); + case rapidjson::kStringType: + return this->encode_string(key, val.GetString(), (size_t)val.GetStringLength(), ext); + case rapidjson::kNumberType: + if (val.IsDouble()) { + return this->encode_number(key, val.GetDouble(), ext); + } else { + return this->encode_number(key, val.GetInt64(), ext); + } + case rapidjson::kObjectType: + this->ObjectBegin(key, ext); + for (rapidjson::Value::ConstMemberIterator iter = val.MemberBegin(); iter!=val.MemberEnd(); ++iter) { + this->encode_json_value(iter->name.GetString(), iter->value, ext); + } + this->ObjectEnd(key, ext); + break; + case rapidjson::kArrayType:{ + this->ArrayBegin(key, ext); + size_t max = (size_t)val.Size(); + for (size_t i = 0; iencode_json_value(NULL, val[(rapidjson::SizeType)i], ext); + } + this->ArrayEnd(key, ext); + } + break; + } + return true; + } + + void xpack_set_key(const char*key) { // openssl defined set_key macro, so we named it xpack_set_key + if (NULL!=key && key[0]!='\0') { + if (NULL != _writer) { + _writer->Key(key); + } else { + _pretty->Key(key); + } + } + } + + JSON_WRITER_BUFFER* _buf; + JSON_WRITER_WRITER* _writer; + JSON_WRITER_PRETTY* _pretty; +}; + +class JsonEncoder { +public: + JsonEncoder() { + indentCount = -1; + indentChar = ' '; + maxDecimalPlaces = -1; + } + JsonEncoder(int _indentCount, char _indentChar, int _maxDecimalPlaces = -1) { // compat + indentCount = _indentCount; + indentChar = _indentChar; + maxDecimalPlaces = _maxDecimalPlaces; + } + + void SetMaxDecimalPlaces(int _maxDecimalPlaces) { + maxDecimalPlaces = _maxDecimalPlaces; + } + + template + std::string encode(const T&val) { + JsonWriter wr(indentCount, indentChar, maxDecimalPlaces); + XEncoder en(wr); + en.encode(NULL, val, NULL); + return wr.String(); + } + +private: + int indentCount; + char indentChar; + int maxDecimalPlaces; +}; + +// //////////////// JsonData /////////////////////// +template<>struct is_xpack_type_spec {static bool const value = true;}; + +inline std::string JsonData::String() const { + JsonEncoder en; + return en.encode(*this); +} + + +} + +#endif diff --git a/util/xpack/l1l2_expand.h b/util/xpack/l1l2_expand.h new file mode 100755 index 0000000..672a5b4 --- /dev/null +++ b/util/xpack/l1l2_expand.h @@ -0,0 +1,503 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +#ifndef __X_L1_L2_EXPAND_H +#define __X_L1_L2_EXPAND_H + + +// expand macro depend on parameters number +#define X_PACK_COUNT(LEVEL, ACTION, _99,_98,_97,_96,_95,_94,_93,_92,_91,_90,_89,_88,_87,_86,_85,_84,_83,_82,_81,_80,_79,_78,_77,_76,_75,_74,_73,_72,_71,_70,_69,_68,_67,_66,_65,_64,_63,_62,_61,_60,_59,_58,_57,_56,_55,_54,_53,_52,_51,_50,_49,_48,_47,_46,_45,_44,_43,_42,_41,_40,_39,_38,_37,_36,_35,_34,_33,_32,_31,_30,_29,_28,_27,_26,_25,_24,_23,_22,_21,_20,_19,_18,_17,_16,_15,_14,_13,_12,_11,_10,_9,_8,_7,_6,_5,_4,_3,_2,_1,N,...) LEVEL##N +#define X_PACK_EXPAND(...) __VA_ARGS__ + + +#if !defined(_MSC_VER) || defined(__clang__) + +#define X_PACK_N(LEVEL, ACTION, ...) X_PACK_COUNT(LEVEL, ACTION, __VA_ARGS__, _99,_98,_97,_96,_95,_94,_93,_92,_91,_90,_89,_88,_87,_86,_85,_84,_83,_82,_81,_80,_79,_78,_77,_76,_75,_74,_73,_72,_71,_70,_69,_68,_67,_66,_65,_64,_63,_62,_61,_60,_59,_58,_57,_56,_55,_54,_53,_52,_51,_50,_49,_48,_47,_46,_45,_44,_43,_42,_41,_40,_39,_38,_37,_36,_35,_34,_33,_32,_31,_30,_29,_28,_27,_26,_25,_24,_23,_22,_21,_20,_19,_18,_17,_16,_15,_14,_13,_12,_11,_10,_9,_8,_7,_6,_5,_4,_3,_2,_1) (ACTION, __VA_ARGS__) + +#define X_PACK_N2(LEVEL, ACTION, ARG, ...) X_PACK_COUNT(LEVEL, ACTION, __VA_ARGS__, _99,_98,_97,_96,_95,_94,_93,_92,_91,_90,_89,_88,_87,_86,_85,_84,_83,_82,_81,_80,_79,_78,_77,_76,_75,_74,_73,_72,_71,_70,_69,_68,_67,_66,_65,_64,_63,_62,_61,_60,_59,_58,_57,_56,_55,_54,_53,_52,_51,_50,_49,_48,_47,_46,_45,_44,_43,_42,_41,_40,_39,_38,_37,_36,_35,_34,_33,_32,_31,_30,_29,_28,_27,_26,_25,_24,_23,_22,_21,_20,_19,_18,_17,_16,_15,_14,_13,_12,_11,_10,_9,_8,_7,_6,_5,_4,_3,_2,_1) (ACTION, ARG, __VA_ARGS__) + +#define X_PACK_L1_DEF(ACT, M) ACT(M) // here will expand to ACT(O(xxx)), ACT(A(a,x)), ACT(M(xxx)) +#define X_PACK_L1_1(ACT, M) X_PACK_L1_DEF(ACT, M) +#define X_PACK_L1_2(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_1(ACT, __VA_ARGS__) +#define X_PACK_L1_3(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_2(ACT, __VA_ARGS__) +#define X_PACK_L1_4(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_3(ACT, __VA_ARGS__) +#define X_PACK_L1_5(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_4(ACT, __VA_ARGS__) +#define X_PACK_L1_6(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_5(ACT, __VA_ARGS__) +#define X_PACK_L1_7(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_6(ACT, __VA_ARGS__) +#define X_PACK_L1_8(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_7(ACT, __VA_ARGS__) +#define X_PACK_L1_9(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_8(ACT, __VA_ARGS__) +#define X_PACK_L1_10(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_9(ACT, __VA_ARGS__) +#define X_PACK_L1_11(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_10(ACT, __VA_ARGS__) +#define X_PACK_L1_12(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_11(ACT, __VA_ARGS__) +#define X_PACK_L1_13(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_12(ACT, __VA_ARGS__) +#define X_PACK_L1_14(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_13(ACT, __VA_ARGS__) +#define X_PACK_L1_15(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_14(ACT, __VA_ARGS__) +#define X_PACK_L1_16(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_15(ACT, __VA_ARGS__) +#define X_PACK_L1_17(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_16(ACT, __VA_ARGS__) +#define X_PACK_L1_18(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_17(ACT, __VA_ARGS__) +#define X_PACK_L1_19(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_18(ACT, __VA_ARGS__) +#define X_PACK_L1_20(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_19(ACT, __VA_ARGS__) +#define X_PACK_L1_21(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_20(ACT, __VA_ARGS__) +#define X_PACK_L1_22(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_21(ACT, __VA_ARGS__) +#define X_PACK_L1_23(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_22(ACT, __VA_ARGS__) +#define X_PACK_L1_24(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_23(ACT, __VA_ARGS__) +#define X_PACK_L1_25(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_24(ACT, __VA_ARGS__) +#define X_PACK_L1_26(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_25(ACT, __VA_ARGS__) +#define X_PACK_L1_27(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_26(ACT, __VA_ARGS__) +#define X_PACK_L1_28(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_27(ACT, __VA_ARGS__) +#define X_PACK_L1_29(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_28(ACT, __VA_ARGS__) +#define X_PACK_L1_30(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_29(ACT, __VA_ARGS__) +#define X_PACK_L1_31(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_30(ACT, __VA_ARGS__) +#define X_PACK_L1_32(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_31(ACT, __VA_ARGS__) +#define X_PACK_L1_33(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_32(ACT, __VA_ARGS__) +#define X_PACK_L1_34(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_33(ACT, __VA_ARGS__) +#define X_PACK_L1_35(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_34(ACT, __VA_ARGS__) +#define X_PACK_L1_36(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_35(ACT, __VA_ARGS__) +#define X_PACK_L1_37(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_36(ACT, __VA_ARGS__) +#define X_PACK_L1_38(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_37(ACT, __VA_ARGS__) +#define X_PACK_L1_39(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_38(ACT, __VA_ARGS__) +#define X_PACK_L1_40(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_39(ACT, __VA_ARGS__) +#define X_PACK_L1_41(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_40(ACT, __VA_ARGS__) +#define X_PACK_L1_42(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_41(ACT, __VA_ARGS__) +#define X_PACK_L1_43(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_42(ACT, __VA_ARGS__) +#define X_PACK_L1_44(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_43(ACT, __VA_ARGS__) +#define X_PACK_L1_45(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_44(ACT, __VA_ARGS__) +#define X_PACK_L1_46(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_45(ACT, __VA_ARGS__) +#define X_PACK_L1_47(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_46(ACT, __VA_ARGS__) +#define X_PACK_L1_48(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_47(ACT, __VA_ARGS__) +#define X_PACK_L1_49(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_48(ACT, __VA_ARGS__) +#define X_PACK_L1_50(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_49(ACT, __VA_ARGS__) +#define X_PACK_L1_51(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_50(ACT, __VA_ARGS__) +#define X_PACK_L1_52(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_51(ACT, __VA_ARGS__) +#define X_PACK_L1_53(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_52(ACT, __VA_ARGS__) +#define X_PACK_L1_54(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_53(ACT, __VA_ARGS__) +#define X_PACK_L1_55(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_54(ACT, __VA_ARGS__) +#define X_PACK_L1_56(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_55(ACT, __VA_ARGS__) +#define X_PACK_L1_57(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_56(ACT, __VA_ARGS__) +#define X_PACK_L1_58(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_57(ACT, __VA_ARGS__) +#define X_PACK_L1_59(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_58(ACT, __VA_ARGS__) +#define X_PACK_L1_60(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_59(ACT, __VA_ARGS__) +#define X_PACK_L1_61(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_60(ACT, __VA_ARGS__) +#define X_PACK_L1_62(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_61(ACT, __VA_ARGS__) +#define X_PACK_L1_63(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_62(ACT, __VA_ARGS__) +#define X_PACK_L1_64(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_63(ACT, __VA_ARGS__) +#define X_PACK_L1_65(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_64(ACT, __VA_ARGS__) +#define X_PACK_L1_66(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_65(ACT, __VA_ARGS__) +#define X_PACK_L1_67(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_66(ACT, __VA_ARGS__) +#define X_PACK_L1_68(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_67(ACT, __VA_ARGS__) +#define X_PACK_L1_69(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_68(ACT, __VA_ARGS__) +#define X_PACK_L1_70(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_69(ACT, __VA_ARGS__) +#define X_PACK_L1_71(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_70(ACT, __VA_ARGS__) +#define X_PACK_L1_72(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_71(ACT, __VA_ARGS__) +#define X_PACK_L1_73(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_72(ACT, __VA_ARGS__) +#define X_PACK_L1_74(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_73(ACT, __VA_ARGS__) +#define X_PACK_L1_75(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_74(ACT, __VA_ARGS__) +#define X_PACK_L1_76(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_75(ACT, __VA_ARGS__) +#define X_PACK_L1_77(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_76(ACT, __VA_ARGS__) +#define X_PACK_L1_78(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_77(ACT, __VA_ARGS__) +#define X_PACK_L1_79(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_78(ACT, __VA_ARGS__) +#define X_PACK_L1_80(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_79(ACT, __VA_ARGS__) +#define X_PACK_L1_81(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_80(ACT, __VA_ARGS__) +#define X_PACK_L1_82(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_81(ACT, __VA_ARGS__) +#define X_PACK_L1_83(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_82(ACT, __VA_ARGS__) +#define X_PACK_L1_84(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_83(ACT, __VA_ARGS__) +#define X_PACK_L1_85(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_84(ACT, __VA_ARGS__) +#define X_PACK_L1_86(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_85(ACT, __VA_ARGS__) +#define X_PACK_L1_87(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_86(ACT, __VA_ARGS__) +#define X_PACK_L1_88(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_87(ACT, __VA_ARGS__) +#define X_PACK_L1_89(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_88(ACT, __VA_ARGS__) +#define X_PACK_L1_90(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_89(ACT, __VA_ARGS__) +#define X_PACK_L1_91(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_90(ACT, __VA_ARGS__) +#define X_PACK_L1_92(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_91(ACT, __VA_ARGS__) +#define X_PACK_L1_93(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_92(ACT, __VA_ARGS__) +#define X_PACK_L1_94(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_93(ACT, __VA_ARGS__) +#define X_PACK_L1_95(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_94(ACT, __VA_ARGS__) +#define X_PACK_L1_96(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_95(ACT, __VA_ARGS__) +#define X_PACK_L1_97(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_96(ACT, __VA_ARGS__) +#define X_PACK_L1_98(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_97(ACT, __VA_ARGS__) +#define X_PACK_L1_99(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_98(ACT, __VA_ARGS__) + +#define X_PACK_L2_DEF(ACT, ARG, M) ACT(ARG, M) +#define X_PACK_L2_1(ACT, ARG, M) X_PACK_L2_DEF(ACT, ARG, M) +#define X_PACK_L2_2(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_1(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_3(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_2(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_4(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_3(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_5(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_4(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_6(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_5(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_7(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_6(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_8(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_7(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_9(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_8(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_10(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_9(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_11(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_10(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_12(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_11(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_13(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_12(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_14(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_13(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_15(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_14(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_16(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_15(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_17(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_16(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_18(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_17(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_19(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_18(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_20(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_19(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_21(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_20(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_22(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_21(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_23(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_22(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_24(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_23(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_25(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_24(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_26(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_25(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_27(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_26(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_28(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_27(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_29(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_28(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_30(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_29(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_31(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_30(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_32(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_31(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_33(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_32(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_34(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_33(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_35(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_34(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_36(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_35(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_37(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_36(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_38(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_37(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_39(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_38(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_40(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_39(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_41(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_40(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_42(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_41(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_43(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_42(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_44(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_43(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_45(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_44(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_46(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_45(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_47(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_46(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_48(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_47(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_49(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_48(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_50(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_49(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_51(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_50(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_52(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_51(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_53(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_52(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_54(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_53(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_55(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_54(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_56(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_55(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_57(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_56(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_58(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_57(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_59(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_58(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_60(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_59(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_61(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_60(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_62(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_61(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_63(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_62(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_64(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_63(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_65(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_64(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_66(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_65(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_67(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_66(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_68(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_67(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_69(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_68(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_70(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_69(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_71(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_70(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_72(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_71(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_73(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_72(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_74(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_73(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_75(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_74(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_76(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_75(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_77(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_76(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_78(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_77(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_79(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_78(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_80(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_79(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_81(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_80(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_82(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_81(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_83(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_82(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_84(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_83(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_85(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_84(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_86(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_85(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_87(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_86(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_88(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_87(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_89(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_88(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_90(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_89(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_91(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_90(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_92(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_91(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_93(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_92(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_94(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_93(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_95(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_94(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_96(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_95(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_97(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_96(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_98(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_97(ACT, ARG, __VA_ARGS__) +#define X_PACK_L2_99(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_98(ACT, ARG, __VA_ARGS__) + + +// two para every time. for alias +#define X_PACK_L2_2_DEF(ACT, ARG, M, NAME) ACT(ARG, M, NAME) +#define X_PACK_L2_2_2(ACT, ARG, M, NAME) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) +#define X_PACK_L2_2_4(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_2(ACT,ARG, __VA_ARGS__) +#define X_PACK_L2_2_6(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_4(ACT,ARG, __VA_ARGS__) +#define X_PACK_L2_2_8(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_6(ACT,ARG, __VA_ARGS__) +#define X_PACK_L2_2_10(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_8(ACT,ARG, __VA_ARGS__) +#define X_PACK_L2_2_12(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_10(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_14(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_12(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_16(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_14(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_18(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_16(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_20(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_18(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_22(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_20(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_24(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_22(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_26(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_24(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_28(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_26(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_30(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_28(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_32(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_30(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_34(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_32(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_36(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_34(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_38(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_36(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_40(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_38(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_42(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_40(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_44(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_42(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_46(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_44(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_48(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_46(ACT, ARG,__VA_ARGS__) +#define X_PACK_L2_2_50(ACT, ARG,M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_48(ACT, ARG,__VA_ARGS__) + +#else // msvc +// thx https://stackoverflow.com/questions/5134523/msvc-doesnt-expand-va-args-correctly +// in MSVC's preprocessor, __VA_ARGS__ is treat as a normal parameter, so it will expand at last, and in gcc, it's expand at first. so we need expand it first +#define X_PACK_N(LEVEL, ACTION, ...) X_PACK_EXPAND(X_PACK_COUNT(LEVEL, ACTION, __VA_ARGS__,_99,_98,_97,_96,_95,_94,_93,_92,_91,_90,_89,_88,_87,_86,_85,_84,_83,_82,_81,_80,_79,_78,_77,_76,_75,_74,_73,_72,_71,_70,_69,_68,_67,_66,_65,_64,_63,_62,_61,_60,_59,_58,_57,_56,_55,_54,_53,_52,_51,_50,_49,_48,_47,_46,_45,_44,_43,_42,_41,_40,_39,_38,_37,_36,_35,_34,_33,_32,_31,_30,_29,_28,_27,_26,_25,_24,_23,_22,_21,_20,_19,_18,_17,_16,_15,_14,_13,_12,_11,_10,_9,_8,_7,_6,_5,_4,_3,_2,_1)) X_PACK_EXPAND((ACTION, __VA_ARGS__)) + +#define X_PACK_N2(LEVEL, ACTION, ARG, ...) X_PACK_EXPAND(X_PACK_COUNT(LEVEL, ACTION, __VA_ARGS__,_99,_98,_97,_96,_95,_94,_93,_92,_91,_90,_89,_88,_87,_86,_85,_84,_83,_82,_81,_80,_79,_78,_77,_76,_75,_74,_73,_72,_71,_70,_69,_68,_67,_66,_65,_64,_63,_62,_61,_60,_59,_58,_57,_56,_55,_54,_53,_52,_51,_50,_49,_48,_47,_46,_45,_44,_43,_42,_41,_40,_39,_38,_37,_36,_35,_34,_33,_32,_31,_30,_29,_28,_27,_26,_25,_24,_23,_22,_21,_20,_19,_18,_17,_16,_15,_14,_13,_12,_11,_10,_9,_8,_7,_6,_5,_4,_3,_2,_1)) X_PACK_EXPAND((ACTION, ARG, __VA_ARGS__)) + +#define X_PACK_L1_DEF(ACT, M) ACT(M) // here will expand to ACT(O(xxx)), ACT(A(a,x)), ACT(M(xxx)) +#define X_PACK_L1_1(ACT, M) X_PACK_L1_DEF(ACT, M) +#define X_PACK_L1_2(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_1 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_3(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_2 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_4(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_3 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_5(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_4 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_6(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_5 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_7(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_6 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_8(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_7 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_9(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_8 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_10(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_9 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_11(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_10 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_12(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_11 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_13(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_12 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_14(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_13 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_15(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_14 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_16(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_15 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_17(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_16 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_18(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_17 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_19(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_18 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_20(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_19 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_21(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_20 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_22(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_21 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_23(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_22 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_24(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_23 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_25(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_24 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_26(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_25 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_27(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_26 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_28(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_27 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_29(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_28 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_30(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_29 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_31(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_30 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_32(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_31 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_33(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_32 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_34(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_33 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_35(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_34 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_36(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_35 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_37(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_36 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_38(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_37 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_39(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_38 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_40(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_39 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_41(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_40 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_42(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_41 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_43(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_42 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_44(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_43 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_45(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_44 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_46(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_45 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_47(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_46 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_48(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_47 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_49(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_48 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_50(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_49 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_51(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_50 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_52(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_51 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_53(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_52 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_54(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_53 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_55(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_54 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_56(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_55 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_57(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_56 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_58(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_57 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_59(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_58 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_60(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_59 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_61(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_60 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_62(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_61 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_63(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_62 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_64(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_63 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_65(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_64 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_66(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_65 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_67(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_66 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_68(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_67 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_69(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_68 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_70(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_69 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_71(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_70 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_72(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_71 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_73(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_72 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_74(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_73 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_75(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_74 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_76(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_75 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_77(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_76 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_78(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_77 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_79(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_78 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_80(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_79 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_81(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_80 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_82(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_81 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_83(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_82 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_84(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_83 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_85(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_84 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_86(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_85 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_87(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_86 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_88(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_87 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_89(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_88 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_90(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_89 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_91(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_90 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_92(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_91 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_93(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_92 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_94(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_93 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_95(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_94 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_96(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_95 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_97(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_96 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_98(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_97 X_PACK_EXPAND((ACT, __VA_ARGS__)) +#define X_PACK_L1_99(ACT, M,...) X_PACK_L1_DEF(ACT, M) X_PACK_L1_98 X_PACK_EXPAND((ACT, __VA_ARGS__)) + +#define X_PACK_L2_DEF(ACT, ARG, M) ACT(ARG, M) +#define X_PACK_L2_1(ACT, ARG, M) X_PACK_L2_DEF(ACT, ARG, M) +#define X_PACK_L2_2(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_1 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_3(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_2 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_4(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_3 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_5(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_4 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_6(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_5 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_7(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_6 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_8(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_7 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_9(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_8 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_10(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_9 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_11(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_10 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_12(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_11 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_13(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_12 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_14(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_13 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_15(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_14 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_16(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_15 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_17(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_16 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_18(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_17 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_19(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_18 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_20(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_19 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_21(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_20 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_22(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_21 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_23(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_22 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_24(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_23 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_25(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_24 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_26(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_25 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_27(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_26 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_28(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_27 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_29(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_28 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_30(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_29 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_31(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_30 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_32(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_31 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_33(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_32 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_34(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_33 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_35(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_34 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_36(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_35 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_37(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_36 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_38(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_37 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_39(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_38 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_40(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_39 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_41(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_40 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_42(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_41 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_43(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_42 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_44(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_43 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_45(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_44 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_46(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_45 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_47(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_46 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_48(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_47 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_49(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_48 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_50(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_49 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_51(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_50 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_52(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_51 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_53(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_52 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_54(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_53 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_55(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_54 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_56(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_55 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_57(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_56 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_58(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_57 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_59(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_58 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_60(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_59 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_61(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_60 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_62(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_61 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_63(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_62 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_64(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_63 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_65(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_64 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_66(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_65 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_67(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_66 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_68(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_67 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_69(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_68 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_70(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_69 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_71(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_70 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_72(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_71 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_73(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_72 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_74(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_73 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_75(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_74 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_76(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_75 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_77(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_76 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_78(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_77 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_79(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_78 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_80(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_79 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_81(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_80 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_82(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_81 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_83(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_82 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_84(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_83 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_85(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_84 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_86(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_85 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_87(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_86 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_88(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_87 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_89(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_88 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_90(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_89 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_91(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_90 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_92(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_91 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_93(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_92 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_94(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_93 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_95(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_94 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_96(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_95 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_97(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_96 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_98(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_97 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_99(ACT, ARG, M,...) X_PACK_L2_DEF(ACT, ARG, M) X_PACK_L2_98 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) + +// two para every time. for alias +#define X_PACK_L2_2_DEF(ACT, ARG, M, NAME) ACT(ARG, M, NAME) +#define X_PACK_L2_2_2(ACT, ARG, M, NAME) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) +#define X_PACK_L2_2_4(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_2 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_6(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_4 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_8(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_6 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_10(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_8 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_12(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_10 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_14(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_12 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_16(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_14 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_18(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_16 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_20(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_18 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_22(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_20 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_24(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_22 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_26(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_24 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_28(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_26 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_30(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_28 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_32(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_30 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_34(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_32 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_36(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_34 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_38(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_36 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_40(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_38 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_42(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_40 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_44(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_42 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_46(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_44 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_48(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_46 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) +#define X_PACK_L2_2_50(ACT, ARG, M, NAME,...) X_PACK_L2_2_DEF(ACT, ARG, M, NAME) X_PACK_L2_2_48 X_PACK_EXPAND((ACT, ARG, __VA_ARGS__)) + +#endif + +#endif diff --git a/util/xpack/mysql.h b/util/xpack/mysql.h new file mode 100755 index 0000000..40f4ecf --- /dev/null +++ b/util/xpack/mysql.h @@ -0,0 +1,44 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_MYSQL_H +#define __X_PACK_MYSQL_H + +#include "mysql_decoder.h" +#include "xpack.h" + +namespace xpack { + +class mysql { +public: + // convert MYSQL_RES to a struct or vector + template + static void decode(MYSQL_RES *result, T &val) { + MySQLDecoder de(result); + de.decode_top(val, NULL); + } + + // select name from test where id = 1; // just want to get name, did not want to use a struct + template + static void decode(MYSQL_RES *result, const std::string&field, T &val) { + MySQLDecoder de(result); + de.decode_column(field.c_str(), val, NULL); + } +}; + +} + +#endif \ No newline at end of file diff --git a/util/xpack/mysql_decoder.h b/util/xpack/mysql_decoder.h new file mode 100755 index 0000000..9fff8b2 --- /dev/null +++ b/util/xpack/mysql_decoder.h @@ -0,0 +1,222 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +#ifndef __X_PACK_MYSQL_DECODER_H +#define __X_PACK_MYSQL_DECODER_H + +#include +#include +#include +#include + +#include + +#include "extend.h" +#include "traits.h" +#include "json.h" + +#include "string.h" +#include "time.h" + +namespace xpack { + +// for mysql types not list here +// if true we will call xpack_mysql_decode(MYSQL_FIELD *field, char *column, T&val, const Extend *ext) +template +struct is_xpack_mysql_xtype {static bool const value = false;}; + +class MySQLDecoder { + friend class mysql; +public: + template + bool decode(const char*key, T&val, const Extend *ext) { + int idx = find(key); + if (idx >= 0) { + return this->decode_type(idx, val, ext); + } + return false; + } + const char *Name() const { + return "db"; + } + +private: + MYSQL_RES *_res; + MYSQL_ROW _row; + std::map _index; + + MySQLDecoder(MYSQL_RES *result):_res(result), _row(NULL) { + mysql_data_seek(_res, 0); // reset cursor to head + for (int i=0; ifield_count); ++i) { + _index[result->fields[i].name] = i; + } + } + + // decode to struct + template + inline XPACK_IS_XPACK(T) decode_top(T& val, const Extend *ext) { + if (NULL != (_row = mysql_fetch_row(_res))) { + val.__x_pack_decode(*this, val, ext); + return true; + } + return false; + } + template + inline XPACK_IS_XPACK(T) decode_top(std::vector& val, const Extend *ext) { + while (NULL != (_row = mysql_fetch_row(_res))) { + val.push_back(T()); + T& tmp = val.back(); + tmp.__x_pack_decode(*this, tmp, ext); + } + return true; + } + template + inline XPACK_IS_XOUT(T) decode_top(T& val, const Extend *ext) { + if (NULL != (_row = mysql_fetch_row(_res))) { + __x_pack_decode_out(*this, val, ext); + } + return true; + } + template + inline XPACK_IS_XOUT(T) decode_top(std::vector& val, const Extend *ext) { + while (NULL != (_row = mysql_fetch_row(_res))) { + val.push_back(T()); + __x_pack_decode_out(*this, val.back(), ext); + } + return true; + } + + // decode special column of first row + template + bool decode_column(const char*field, T&val, const Extend *ext) { + int idx; + if (NULL==_row && NULL == (_row = mysql_fetch_row(_res)) && (0 > (idx = find(field)))) { + return false; + } + return decode_type(idx, val, ext); + } + // decode special column of rows + template + bool decode_column(const char*field, std::vector&val, const Extend *ext) { + int idx; + if (0 > (idx = find(field))) { + return false; + } + while (NULL != (_row = mysql_fetch_row(_res))) { + T t; + decode_type(idx, t, ext); + val.push_back(t); + } + return true; + } + + + int find(const char*field) const { + std::map::const_iterator it = _index.find(field); + if (it != _index.end()) { + return it->second; + } + return -1; + } + + // std::string + bool decode_type(const int idx, std::string &val, const Extend *ext) { + val = _row[idx]; + return true; + } + // bool + bool decode_type(const int idx, bool &val, const Extend *ext) { + int tmp; + if (decode_type(idx, tmp, ext)) { + val = tmp != 0; + return true; + } + return false; + } + // unsigned integer. DATE and TIME use signed plz + template + typename x_enable_if::is_integer && !numeric::is_signed, bool>::type decode_type(const int idx, T &val, const Extend *ext) { + val = (T)std::strtoul(_row[idx], NULL, 10); + return true; + } + // signed integer + template + typename x_enable_if::is_integer && numeric::is_signed, bool>::type decode_type(const int idx, T &val, const Extend *ext) { + const char *str = _row[idx]; + if (str==NULL || str[0]=='\0') { + val = 0; + return true; + } + switch (_res->fields[idx].type) { + case MYSQL_TYPE_TIME: { + std::vector ss; + if (3 == Util::split(ss, str, ':')) { + long int s0 = std::strtol(ss[0].c_str(), NULL, 10); + long int s1 = std::strtol(ss[1].c_str(), NULL, 10); + long int s2 = std::strtol(ss[2].c_str(), NULL, 10); + if (s0 >= 0) { + val = 1; + } else { + val = -1; + s0 *= -1; + } + val *= (T)(s0*3600 + s1*60 +s2); + } + } + break; + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP:{ // parse to unix timestamp, seconds, discard fractional part + tm t; + strptime(str, "%F %T", &t); + val = (T)mktime(&t); + } + break; + default: + val = (T)std::strtol(str, NULL, 10); + } + return true; + } + // float + template + typename x_enable_if::is_float, bool>::type decode_type(const int idx, T &val, const Extend *ext) { + val = (T)std::strtod(_row[idx], NULL); + return true; + } + + // mysql covert type + template + inline typename x_enable_if::value, bool>::type decode_type(const int idx, T &val, const Extend *ext) { + return xpack_mysql_decode(&_res->fields[idx], _row[idx], val, ext); + } + + // class/struct defined XPACK/XPACK_OUT, default use json to parse, if use other, use xpack_mysql_decode + template + inline XPACK_IS_XOUT(T) decode_type(const int idx, T &val, const Extend *ext) { + (void)ext; + return xpack::json::decode(_row[idx], val); + } + template + inline XPACK_IS_XPACK(T) decode_type(const int idx, T &val, const Extend *ext) { + (void)ext; + return xpack::json::decode(_row[idx], val); + } +}; + + +} + +#endif diff --git a/util/xpack/numeric.h b/util/xpack/numeric.h new file mode 100755 index 0000000..bfc6ea0 --- /dev/null +++ b/util/xpack/numeric.h @@ -0,0 +1,69 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_NUMERIC_H +#define __X_PACK_NUMERIC_H + +namespace xpack { + +template +struct numeric{static bool const value = false; static bool const is_integer = false;static bool const is_float = false;static bool const is_signed = false;}; + +template<> +struct numeric{static bool const value = true; static bool const is_integer = true;static bool const is_signed = true;}; + +template<> +struct numeric{static bool const value = true; static bool const is_integer = true;static bool const is_signed = true;}; + +template<> +struct numeric{static bool const value = true; static bool const is_integer = true;;static bool const is_signed = false;}; + +template<> +struct numeric{static bool const value = true; static bool const is_integer = true;static bool const is_signed = true;}; + +template<> +struct numeric{static bool const value = true; static bool const is_integer = true;;static bool const is_signed = false;}; + +template<> +struct numeric{static bool const value = true; static bool const is_integer = true;static bool const is_signed = true;}; + +template<> +struct numeric{static bool const value = true; static bool const is_integer = true;;static bool const is_signed = false;}; + +template<> +struct numeric{static bool const value = true; static bool const is_integer = true;static bool const is_signed = true;}; + +template<> +struct numeric{static bool const value = true; static bool const is_integer = true;;static bool const is_signed = false;}; + +template<> +struct numeric{static bool const value = true; static bool const is_integer = true;static bool const is_signed = true;}; + +template<> +struct numeric{static bool const value = true; static bool const is_integer = true;;static bool const is_signed = false;}; + +template<> +struct numeric{static bool const value = true;static bool const is_float = true;}; + +template<> +struct numeric{static bool const value = true;static bool const is_float = true;}; + +template<> +struct numeric{static bool const value = true;static bool const is_float = true;}; + +} + +#endif diff --git a/util/xpack/rapidjson/allocators.h b/util/xpack/rapidjson/allocators.h new file mode 100755 index 0000000..3c80dae --- /dev/null +++ b/util/xpack/rapidjson/allocators.h @@ -0,0 +1,284 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ALLOCATORS_H_ +#define RAPIDJSON_ALLOCATORS_H_ + +#include "rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Allocator + +/*! \class rapidjson::Allocator + \brief Concept for allocating, resizing and freeing memory block. + + Note that Malloc() and Realloc() are non-static but Free() is static. + + So if an allocator need to support Free(), it needs to put its pointer in + the header of memory block. + +\code +concept Allocator { + static const bool kNeedFree; //!< Whether this allocator needs to call Free(). + + // Allocate a memory block. + // \param size of the memory block in bytes. + // \returns pointer to the memory block. + void* Malloc(size_t size); + + // Resize a memory block. + // \param originalPtr The pointer to current memory block. Null pointer is permitted. + // \param originalSize The current size in bytes. (Design issue: since some allocator may not book-keep this, explicitly pass to it can save memory.) + // \param newSize the new size in bytes. + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize); + + // Free a memory block. + // \param pointer to the memory block. Null pointer is permitted. + static void Free(void *ptr); +}; +\endcode +*/ + + +/*! \def RAPIDJSON_ALLOCATOR_DEFUALT_CHUNK_CAPACITY + \ingroup RAPIDJSON_CONFIG + \brief User-defined kDefaultChunkCapacity definition. + + User can define this as any \c size that is a power of 2. +*/ + +#ifndef RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY +#define RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY (64 * 1024) +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// CrtAllocator + +//! C-runtime library allocator. +/*! This class is just wrapper for standard C library memory routines. + \note implements Allocator concept +*/ +class CrtAllocator { +public: + static const bool kNeedFree = true; + void* Malloc(size_t size) { + if (size) // behavior of malloc(0) is implementation defined. + return std::malloc(size); + else + return NULL; // standardize to returning NULL. + } + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { + (void)originalSize; + if (newSize == 0) { + std::free(originalPtr); + return NULL; + } + return std::realloc(originalPtr, newSize); + } + static void Free(void *ptr) { std::free(ptr); } +}; + +/////////////////////////////////////////////////////////////////////////////// +// MemoryPoolAllocator + +//! Default memory allocator used by the parser and DOM. +/*! This allocator allocate memory blocks from pre-allocated memory chunks. + + It does not free memory blocks. And Realloc() only allocate new memory. + + The memory chunks are allocated by BaseAllocator, which is CrtAllocator by default. + + User may also supply a buffer as the first chunk. + + If the user-buffer is full then additional chunks are allocated by BaseAllocator. + + The user-buffer is not deallocated by this allocator. + + \tparam BaseAllocator the allocator type for allocating memory chunks. Default is CrtAllocator. + \note implements Allocator concept +*/ +template +class MemoryPoolAllocator { +public: + static const bool kNeedFree = false; //!< Tell users that no need to call Free() with this allocator. (concept Allocator) + + //! Constructor with chunkSize. + /*! \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. + \param baseAllocator The allocator for allocating memory chunks. + */ + MemoryPoolAllocator(size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : + chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(0), baseAllocator_(baseAllocator), ownBaseAllocator_(0) + { + } + + //! Constructor with user-supplied buffer. + /*! The user buffer will be used firstly. When it is full, memory pool allocates new chunk with chunk size. + + The user buffer will not be deallocated when this allocator is destructed. + + \param buffer User supplied buffer. + \param size Size of the buffer in bytes. It must at least larger than sizeof(ChunkHeader). + \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. + \param baseAllocator The allocator for allocating memory chunks. + */ + MemoryPoolAllocator(void *buffer, size_t size, size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : + chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(buffer), baseAllocator_(baseAllocator), ownBaseAllocator_(0) + { + RAPIDJSON_ASSERT(buffer != 0); + RAPIDJSON_ASSERT(size > sizeof(ChunkHeader)); + chunkHead_ = reinterpret_cast(buffer); + chunkHead_->capacity = size - sizeof(ChunkHeader); + chunkHead_->size = 0; + chunkHead_->next = 0; + } + + //! Destructor. + /*! This deallocates all memory chunks, excluding the user-supplied buffer. + */ + ~MemoryPoolAllocator() { + Clear(); + RAPIDJSON_DELETE(ownBaseAllocator_); + } + + //! Deallocates all memory chunks, excluding the user-supplied buffer. + void Clear() { + while (chunkHead_ && chunkHead_ != userBuffer_) { + ChunkHeader* next = chunkHead_->next; + baseAllocator_->Free(chunkHead_); + chunkHead_ = next; + } + if (chunkHead_ && chunkHead_ == userBuffer_) + chunkHead_->size = 0; // Clear user buffer + } + + //! Computes the total capacity of allocated memory chunks. + /*! \return total capacity in bytes. + */ + size_t Capacity() const { + size_t capacity = 0; + for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) + capacity += c->capacity; + return capacity; + } + + //! Computes the memory blocks allocated. + /*! \return total used bytes. + */ + size_t Size() const { + size_t size = 0; + for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) + size += c->size; + return size; + } + + //! Allocates a memory block. (concept Allocator) + void* Malloc(size_t size) { + if (!size) + return NULL; + + size = RAPIDJSON_ALIGN(size); + if (chunkHead_ == 0 || chunkHead_->size + size > chunkHead_->capacity) + if (!AddChunk(chunk_capacity_ > size ? chunk_capacity_ : size)) + return NULL; + + void *buffer = reinterpret_cast(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size; + chunkHead_->size += size; + return buffer; + } + + //! Resizes a memory block (concept Allocator) + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { + if (originalPtr == 0) + return Malloc(newSize); + + if (newSize == 0) + return NULL; + + originalSize = RAPIDJSON_ALIGN(originalSize); + newSize = RAPIDJSON_ALIGN(newSize); + + // Do not shrink if new size is smaller than original + if (originalSize >= newSize) + return originalPtr; + + // Simply expand it if it is the last allocation and there is sufficient space + if (originalPtr == reinterpret_cast(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size - originalSize) { + size_t increment = static_cast(newSize - originalSize); + if (chunkHead_->size + increment <= chunkHead_->capacity) { + chunkHead_->size += increment; + return originalPtr; + } + } + + // Realloc process: allocate and copy memory, do not free original buffer. + if (void* newBuffer = Malloc(newSize)) { + if (originalSize) + std::memcpy(newBuffer, originalPtr, originalSize); + return newBuffer; + } + else + return NULL; + } + + //! Frees a memory block (concept Allocator) + static void Free(void *ptr) { (void)ptr; } // Do nothing + +private: + //! Copy constructor is not permitted. + MemoryPoolAllocator(const MemoryPoolAllocator& rhs) /* = delete */; + //! Copy assignment operator is not permitted. + MemoryPoolAllocator& operator=(const MemoryPoolAllocator& rhs) /* = delete */; + + //! Creates a new chunk. + /*! \param capacity Capacity of the chunk in bytes. + \return true if success. + */ + bool AddChunk(size_t capacity) { + if (!baseAllocator_) + ownBaseAllocator_ = baseAllocator_ = RAPIDJSON_NEW(BaseAllocator)(); + if (ChunkHeader* chunk = reinterpret_cast(baseAllocator_->Malloc(RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + capacity))) { + chunk->capacity = capacity; + chunk->size = 0; + chunk->next = chunkHead_; + chunkHead_ = chunk; + return true; + } + else + return false; + } + + static const int kDefaultChunkCapacity = RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY; //!< Default chunk capacity. + + //! Chunk header for perpending to each chunk. + /*! Chunks are stored as a singly linked list. + */ + struct ChunkHeader { + size_t capacity; //!< Capacity of the chunk in bytes (excluding the header itself). + size_t size; //!< Current size of allocated memory in bytes. + ChunkHeader *next; //!< Next chunk in the linked list. + }; + + ChunkHeader *chunkHead_; //!< Head of the chunk linked-list. Only the head chunk serves allocation. + size_t chunk_capacity_; //!< The minimum capacity of chunk when they are allocated. + void *userBuffer_; //!< User supplied buffer. + BaseAllocator* baseAllocator_; //!< base allocator for allocating memory chunks. + BaseAllocator* ownBaseAllocator_; //!< base allocator created by this object. +}; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ENCODINGS_H_ diff --git a/util/xpack/rapidjson/cursorstreamwrapper.h b/util/xpack/rapidjson/cursorstreamwrapper.h new file mode 100755 index 0000000..f073aed --- /dev/null +++ b/util/xpack/rapidjson/cursorstreamwrapper.h @@ -0,0 +1,78 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_CURSORSTREAMWRAPPER_H_ +#define RAPIDJSON_CURSORSTREAMWRAPPER_H_ + +#include "stream.h" + +#if defined(__GNUC__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4702) // unreachable code +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +RAPIDJSON_NAMESPACE_BEGIN + + +//! Cursor stream wrapper for counting line and column number if error exists. +/*! + \tparam InputStream Any stream that implements Stream Concept +*/ +template > +class CursorStreamWrapper : public GenericStreamWrapper { +public: + typedef typename Encoding::Ch Ch; + + CursorStreamWrapper(InputStream& is): + GenericStreamWrapper(is), line_(1), col_(0) {} + + // counting line and column number + Ch Take() { + Ch ch = this->is_.Take(); + if(ch == '\n') { + line_ ++; + col_ = 0; + } else { + col_ ++; + } + return ch; + } + + //! Get the error line number, if error exists. + size_t GetLine() const { return line_; } + //! Get the error column number, if error exists. + size_t GetColumn() const { return col_; } + +private: + size_t line_; //!< Current Line + size_t col_; //!< Current Column +}; + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_POP +#endif + +#if defined(__GNUC__) +RAPIDJSON_DIAG_POP +#endif + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_CURSORSTREAMWRAPPER_H_ diff --git a/util/xpack/rapidjson/document.h b/util/xpack/rapidjson/document.h new file mode 100755 index 0000000..617b3ce --- /dev/null +++ b/util/xpack/rapidjson/document.h @@ -0,0 +1,2652 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_DOCUMENT_H_ +#define RAPIDJSON_DOCUMENT_H_ + +/*! \file document.h */ + +#include "reader.h" +#include "internal/meta.h" +#include "internal/strfunc.h" +#include "memorystream.h" +#include "encodedstream.h" +#include // placement new +#include + +RAPIDJSON_DIAG_PUSH +#ifdef __clang__ +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(c++98-compat) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +RAPIDJSON_DIAG_OFF(4244) // conversion from kXxxFlags to 'uint16_t', possible loss of data +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_OFF(effc++) +#endif // __GNUC__ + +#ifndef RAPIDJSON_NOMEMBERITERATORCLASS +#include // std::random_access_iterator_tag +#endif + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS +#include // std::move +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +// Forward declaration. +template +class GenericValue; + +template +class GenericDocument; + +//! Name-value pair in a JSON object value. +/*! + This class was internal to GenericValue. It used to be a inner struct. + But a compiler (IBM XL C/C++ for AIX) have reported to have problem with that so it moved as a namespace scope struct. + https://code.google.com/p/rapidjson/issues/detail?id=64 +*/ +template +struct GenericMember { + GenericValue name; //!< name of member (must be a string) + GenericValue value; //!< value of member. + + // swap() for std::sort() and other potential use in STL. + friend inline void swap(GenericMember& a, GenericMember& b) RAPIDJSON_NOEXCEPT { + a.name.Swap(b.name); + a.value.Swap(b.value); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericMemberIterator + +#ifndef RAPIDJSON_NOMEMBERITERATORCLASS + +//! (Constant) member iterator for a JSON object value +/*! + \tparam Const Is this a constant iterator? + \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) + \tparam Allocator Allocator type for allocating memory of object, array and string. + + This class implements a Random Access Iterator for GenericMember elements + of a GenericValue, see ISO/IEC 14882:2003(E) C++ standard, 24.1 [lib.iterator.requirements]. + + \note This iterator implementation is mainly intended to avoid implicit + conversions from iterator values to \c NULL, + e.g. from GenericValue::FindMember. + + \note Define \c RAPIDJSON_NOMEMBERITERATORCLASS to fall back to a + pointer-based implementation, if your platform doesn't provide + the C++ header. + + \see GenericMember, GenericValue::MemberIterator, GenericValue::ConstMemberIterator + */ +template +class GenericMemberIterator { + + friend class GenericValue; + template friend class GenericMemberIterator; + + typedef GenericMember PlainType; + typedef typename internal::MaybeAddConst::Type ValueType; + +public: + //! Iterator type itself + typedef GenericMemberIterator Iterator; + //! Constant iterator type + typedef GenericMemberIterator ConstIterator; + //! Non-constant iterator type + typedef GenericMemberIterator NonConstIterator; + + /** \name std::iterator_traits support */ + //@{ + typedef ValueType value_type; + typedef ValueType * pointer; + typedef ValueType & reference; + typedef std::ptrdiff_t difference_type; + typedef std::random_access_iterator_tag iterator_category; + //@} + + //! Pointer to (const) GenericMember + typedef pointer Pointer; + //! Reference to (const) GenericMember + typedef reference Reference; + //! Signed integer type (e.g. \c ptrdiff_t) + typedef difference_type DifferenceType; + + //! Default constructor (singular value) + /*! Creates an iterator pointing to no element. + \note All operations, except for comparisons, are undefined on such values. + */ + GenericMemberIterator() : ptr_() {} + + //! Iterator conversions to more const + /*! + \param it (Non-const) iterator to copy from + + Allows the creation of an iterator from another GenericMemberIterator + that is "less const". Especially, creating a non-constant iterator + from a constant iterator are disabled: + \li const -> non-const (not ok) + \li const -> const (ok) + \li non-const -> const (ok) + \li non-const -> non-const (ok) + + \note If the \c Const template parameter is already \c false, this + constructor effectively defines a regular copy-constructor. + Otherwise, the copy constructor is implicitly defined. + */ + GenericMemberIterator(const NonConstIterator & it) : ptr_(it.ptr_) {} + Iterator& operator=(const NonConstIterator & it) { ptr_ = it.ptr_; return *this; } + + //! @name stepping + //@{ + Iterator& operator++(){ ++ptr_; return *this; } + Iterator& operator--(){ --ptr_; return *this; } + Iterator operator++(int){ Iterator old(*this); ++ptr_; return old; } + Iterator operator--(int){ Iterator old(*this); --ptr_; return old; } + //@} + + //! @name increment/decrement + //@{ + Iterator operator+(DifferenceType n) const { return Iterator(ptr_+n); } + Iterator operator-(DifferenceType n) const { return Iterator(ptr_-n); } + + Iterator& operator+=(DifferenceType n) { ptr_+=n; return *this; } + Iterator& operator-=(DifferenceType n) { ptr_-=n; return *this; } + //@} + + //! @name relations + //@{ + bool operator==(ConstIterator that) const { return ptr_ == that.ptr_; } + bool operator!=(ConstIterator that) const { return ptr_ != that.ptr_; } + bool operator<=(ConstIterator that) const { return ptr_ <= that.ptr_; } + bool operator>=(ConstIterator that) const { return ptr_ >= that.ptr_; } + bool operator< (ConstIterator that) const { return ptr_ < that.ptr_; } + bool operator> (ConstIterator that) const { return ptr_ > that.ptr_; } + //@} + + //! @name dereference + //@{ + Reference operator*() const { return *ptr_; } + Pointer operator->() const { return ptr_; } + Reference operator[](DifferenceType n) const { return ptr_[n]; } + //@} + + //! Distance + DifferenceType operator-(ConstIterator that) const { return ptr_-that.ptr_; } + +private: + //! Internal constructor from plain pointer + explicit GenericMemberIterator(Pointer p) : ptr_(p) {} + + Pointer ptr_; //!< raw pointer +}; + +#else // RAPIDJSON_NOMEMBERITERATORCLASS + +// class-based member iterator implementation disabled, use plain pointers + +template +struct GenericMemberIterator; + +//! non-const GenericMemberIterator +template +struct GenericMemberIterator { + //! use plain pointer as iterator type + typedef GenericMember* Iterator; +}; +//! const GenericMemberIterator +template +struct GenericMemberIterator { + //! use plain const pointer as iterator type + typedef const GenericMember* Iterator; +}; + +#endif // RAPIDJSON_NOMEMBERITERATORCLASS + +/////////////////////////////////////////////////////////////////////////////// +// GenericStringRef + +//! Reference to a constant string (not taking a copy) +/*! + \tparam CharType character type of the string + + This helper class is used to automatically infer constant string + references for string literals, especially from \c const \b (!) + character arrays. + + The main use is for creating JSON string values without copying the + source string via an \ref Allocator. This requires that the referenced + string pointers have a sufficient lifetime, which exceeds the lifetime + of the associated GenericValue. + + \b Example + \code + Value v("foo"); // ok, no need to copy & calculate length + const char foo[] = "foo"; + v.SetString(foo); // ok + + const char* bar = foo; + // Value x(bar); // not ok, can't rely on bar's lifetime + Value x(StringRef(bar)); // lifetime explicitly guaranteed by user + Value y(StringRef(bar, 3)); // ok, explicitly pass length + \endcode + + \see StringRef, GenericValue::SetString +*/ +template +struct GenericStringRef { + typedef CharType Ch; //!< character type of the string + + //! Create string reference from \c const character array +#ifndef __clang__ // -Wdocumentation + /*! + This constructor implicitly creates a constant string reference from + a \c const character array. It has better performance than + \ref StringRef(const CharType*) by inferring the string \ref length + from the array length, and also supports strings containing null + characters. + + \tparam N length of the string, automatically inferred + + \param str Constant character array, lifetime assumed to be longer + than the use of the string in e.g. a GenericValue + + \post \ref s == str + + \note Constant complexity. + \note There is a hidden, private overload to disallow references to + non-const character arrays to be created via this constructor. + By this, e.g. function-scope arrays used to be filled via + \c snprintf are excluded from consideration. + In such cases, the referenced string should be \b copied to the + GenericValue instead. + */ +#endif + template + GenericStringRef(const CharType (&str)[N]) RAPIDJSON_NOEXCEPT + : s(str), length(N-1) {} + + //! Explicitly create string reference from \c const character pointer +#ifndef __clang__ // -Wdocumentation + /*! + This constructor can be used to \b explicitly create a reference to + a constant string pointer. + + \see StringRef(const CharType*) + + \param str Constant character pointer, lifetime assumed to be longer + than the use of the string in e.g. a GenericValue + + \post \ref s == str + + \note There is a hidden, private overload to disallow references to + non-const character arrays to be created via this constructor. + By this, e.g. function-scope arrays used to be filled via + \c snprintf are excluded from consideration. + In such cases, the referenced string should be \b copied to the + GenericValue instead. + */ +#endif + explicit GenericStringRef(const CharType* str) + : s(str), length(NotNullStrLen(str)) {} + + //! Create constant string reference from pointer and length +#ifndef __clang__ // -Wdocumentation + /*! \param str constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \param len length of the string, excluding the trailing NULL terminator + + \post \ref s == str && \ref length == len + \note Constant complexity. + */ +#endif + GenericStringRef(const CharType* str, SizeType len) + : s(RAPIDJSON_LIKELY(str) ? str : emptyString), length(len) { RAPIDJSON_ASSERT(str != 0 || len == 0u); } + + GenericStringRef(const GenericStringRef& rhs) : s(rhs.s), length(rhs.length) {} + + //! implicit conversion to plain CharType pointer + operator const Ch *() const { return s; } + + const Ch* const s; //!< plain CharType pointer + const SizeType length; //!< length of the string (excluding the trailing NULL terminator) + +private: + SizeType NotNullStrLen(const CharType* str) { + RAPIDJSON_ASSERT(str != 0); + return internal::StrLen(str); + } + + /// Empty string - used when passing in a NULL pointer + static const Ch emptyString[]; + + //! Disallow construction from non-const array + template + GenericStringRef(CharType (&str)[N]) /* = delete */; + //! Copy assignment operator not permitted - immutable type + GenericStringRef& operator=(const GenericStringRef& rhs) /* = delete */; +}; + +template +const CharType GenericStringRef::emptyString[] = { CharType() }; + +//! Mark a character pointer as constant string +/*! Mark a plain character pointer as a "string literal". This function + can be used to avoid copying a character string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + \tparam CharType Character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \return GenericStringRef string reference object + \relatesalso GenericStringRef + + \see GenericValue::GenericValue(StringRefType), GenericValue::operator=(StringRefType), GenericValue::SetString(StringRefType), GenericValue::PushBack(StringRefType, Allocator&), GenericValue::AddMember +*/ +template +inline GenericStringRef StringRef(const CharType* str) { + return GenericStringRef(str); +} + +//! Mark a character pointer as constant string +/*! Mark a plain character pointer as a "string literal". This function + can be used to avoid copying a character string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + + This version has better performance with supplied length, and also + supports string containing null characters. + + \tparam CharType character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \param length The length of source string. + \return GenericStringRef string reference object + \relatesalso GenericStringRef +*/ +template +inline GenericStringRef StringRef(const CharType* str, size_t length) { + return GenericStringRef(str, SizeType(length)); +} + +#if RAPIDJSON_HAS_STDSTRING +//! Mark a string object as constant string +/*! Mark a string object (e.g. \c std::string) as a "string literal". + This function can be used to avoid copying a string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + + \tparam CharType character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \return GenericStringRef string reference object + \relatesalso GenericStringRef + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. +*/ +template +inline GenericStringRef StringRef(const std::basic_string& str) { + return GenericStringRef(str.data(), SizeType(str.size())); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +// GenericValue type traits +namespace internal { + +template +struct IsGenericValueImpl : FalseType {}; + +// select candidates according to nested encoding and allocator types +template struct IsGenericValueImpl::Type, typename Void::Type> + : IsBaseOf, T>::Type {}; + +// helper to match arbitrary GenericValue instantiations, including derived classes +template struct IsGenericValue : IsGenericValueImpl::Type {}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// TypeHelper + +namespace internal { + +template +struct TypeHelper {}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsBool(); } + static bool Get(const ValueType& v) { return v.GetBool(); } + static ValueType& Set(ValueType& v, bool data) { return v.SetBool(data); } + static ValueType& Set(ValueType& v, bool data, typename ValueType::AllocatorType&) { return v.SetBool(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsInt(); } + static int Get(const ValueType& v) { return v.GetInt(); } + static ValueType& Set(ValueType& v, int data) { return v.SetInt(data); } + static ValueType& Set(ValueType& v, int data, typename ValueType::AllocatorType&) { return v.SetInt(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsUint(); } + static unsigned Get(const ValueType& v) { return v.GetUint(); } + static ValueType& Set(ValueType& v, unsigned data) { return v.SetUint(data); } + static ValueType& Set(ValueType& v, unsigned data, typename ValueType::AllocatorType&) { return v.SetUint(data); } +}; + +#ifdef _MSC_VER +RAPIDJSON_STATIC_ASSERT(sizeof(long) == sizeof(int)); +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsInt(); } + static long Get(const ValueType& v) { return v.GetInt(); } + static ValueType& Set(ValueType& v, long data) { return v.SetInt(data); } + static ValueType& Set(ValueType& v, long data, typename ValueType::AllocatorType&) { return v.SetInt(data); } +}; + +RAPIDJSON_STATIC_ASSERT(sizeof(unsigned long) == sizeof(unsigned)); +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsUint(); } + static unsigned long Get(const ValueType& v) { return v.GetUint(); } + static ValueType& Set(ValueType& v, unsigned long data) { return v.SetUint(data); } + static ValueType& Set(ValueType& v, unsigned long data, typename ValueType::AllocatorType&) { return v.SetUint(data); } +}; +#endif + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsInt64(); } + static int64_t Get(const ValueType& v) { return v.GetInt64(); } + static ValueType& Set(ValueType& v, int64_t data) { return v.SetInt64(data); } + static ValueType& Set(ValueType& v, int64_t data, typename ValueType::AllocatorType&) { return v.SetInt64(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsUint64(); } + static uint64_t Get(const ValueType& v) { return v.GetUint64(); } + static ValueType& Set(ValueType& v, uint64_t data) { return v.SetUint64(data); } + static ValueType& Set(ValueType& v, uint64_t data, typename ValueType::AllocatorType&) { return v.SetUint64(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsDouble(); } + static double Get(const ValueType& v) { return v.GetDouble(); } + static ValueType& Set(ValueType& v, double data) { return v.SetDouble(data); } + static ValueType& Set(ValueType& v, double data, typename ValueType::AllocatorType&) { return v.SetDouble(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsFloat(); } + static float Get(const ValueType& v) { return v.GetFloat(); } + static ValueType& Set(ValueType& v, float data) { return v.SetFloat(data); } + static ValueType& Set(ValueType& v, float data, typename ValueType::AllocatorType&) { return v.SetFloat(data); } +}; + +template +struct TypeHelper { + typedef const typename ValueType::Ch* StringType; + static bool Is(const ValueType& v) { return v.IsString(); } + static StringType Get(const ValueType& v) { return v.GetString(); } + static ValueType& Set(ValueType& v, const StringType data) { return v.SetString(typename ValueType::StringRefType(data)); } + static ValueType& Set(ValueType& v, const StringType data, typename ValueType::AllocatorType& a) { return v.SetString(data, a); } +}; + +#if RAPIDJSON_HAS_STDSTRING +template +struct TypeHelper > { + typedef std::basic_string StringType; + static bool Is(const ValueType& v) { return v.IsString(); } + static StringType Get(const ValueType& v) { return StringType(v.GetString(), v.GetStringLength()); } + static ValueType& Set(ValueType& v, const StringType& data, typename ValueType::AllocatorType& a) { return v.SetString(data, a); } +}; +#endif + +template +struct TypeHelper { + typedef typename ValueType::Array ArrayType; + static bool Is(const ValueType& v) { return v.IsArray(); } + static ArrayType Get(ValueType& v) { return v.GetArray(); } + static ValueType& Set(ValueType& v, ArrayType data) { return v = data; } + static ValueType& Set(ValueType& v, ArrayType data, typename ValueType::AllocatorType&) { return v = data; } +}; + +template +struct TypeHelper { + typedef typename ValueType::ConstArray ArrayType; + static bool Is(const ValueType& v) { return v.IsArray(); } + static ArrayType Get(const ValueType& v) { return v.GetArray(); } +}; + +template +struct TypeHelper { + typedef typename ValueType::Object ObjectType; + static bool Is(const ValueType& v) { return v.IsObject(); } + static ObjectType Get(ValueType& v) { return v.GetObject(); } + static ValueType& Set(ValueType& v, ObjectType data) { return v = data; } + static ValueType& Set(ValueType& v, ObjectType data, typename ValueType::AllocatorType&) { return v = data; } +}; + +template +struct TypeHelper { + typedef typename ValueType::ConstObject ObjectType; + static bool Is(const ValueType& v) { return v.IsObject(); } + static ObjectType Get(const ValueType& v) { return v.GetObject(); } +}; + +} // namespace internal + +// Forward declarations +template class GenericArray; +template class GenericObject; + +/////////////////////////////////////////////////////////////////////////////// +// GenericValue + +//! Represents a JSON value. Use Value for UTF8 encoding and default allocator. +/*! + A JSON value can be one of 7 types. This class is a variant type supporting + these types. + + Use the Value if UTF8 and default allocator + + \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) + \tparam Allocator Allocator type for allocating memory of object, array and string. +*/ +template > +class GenericValue { +public: + //! Name-value pair in an object. + typedef GenericMember Member; + typedef Encoding EncodingType; //!< Encoding type from template parameter. + typedef Allocator AllocatorType; //!< Allocator type from template parameter. + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef GenericStringRef StringRefType; //!< Reference to a constant string + typedef typename GenericMemberIterator::Iterator MemberIterator; //!< Member iterator for iterating in object. + typedef typename GenericMemberIterator::Iterator ConstMemberIterator; //!< Constant member iterator for iterating in object. + typedef GenericValue* ValueIterator; //!< Value iterator for iterating in array. + typedef const GenericValue* ConstValueIterator; //!< Constant value iterator for iterating in array. + typedef GenericValue ValueType; //!< Value type of itself. + typedef GenericArray Array; + typedef GenericArray ConstArray; + typedef GenericObject Object; + typedef GenericObject ConstObject; + + //!@name Constructors and destructor. + //@{ + + //! Default constructor creates a null value. + GenericValue() RAPIDJSON_NOEXCEPT : data_() { data_.f.flags = kNullFlag; } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericValue(GenericValue&& rhs) RAPIDJSON_NOEXCEPT : data_(rhs.data_) { + rhs.data_.f.flags = kNullFlag; // give up contents + } +#endif + +private: + //! Copy constructor is not permitted. + GenericValue(const GenericValue& rhs); + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Moving from a GenericDocument is not permitted. + template + GenericValue(GenericDocument&& rhs); + + //! Move assignment from a GenericDocument is not permitted. + template + GenericValue& operator=(GenericDocument&& rhs); +#endif + +public: + + //! Constructor with JSON value type. + /*! This creates a Value of specified type with default content. + \param type Type of the value. + \note Default content for number is zero. + */ + explicit GenericValue(Type type) RAPIDJSON_NOEXCEPT : data_() { + static const uint16_t defaultFlags[] = { + kNullFlag, kFalseFlag, kTrueFlag, kObjectFlag, kArrayFlag, kShortStringFlag, + kNumberAnyFlag + }; + RAPIDJSON_NOEXCEPT_ASSERT(type >= kNullType && type <= kNumberType); + data_.f.flags = defaultFlags[type]; + + // Use ShortString to store empty string. + if (type == kStringType) + data_.ss.SetLength(0); + } + + //! Explicit copy constructor (with allocator) + /*! Creates a copy of a Value by using the given Allocator + \tparam SourceAllocator allocator of \c rhs + \param rhs Value to copy from (read-only) + \param allocator Allocator for allocating copied elements and buffers. Commonly use GenericDocument::GetAllocator(). + \param copyConstStrings Force copying of constant strings (e.g. referencing an in-situ buffer) + \see CopyFrom() + */ + template + GenericValue(const GenericValue& rhs, Allocator& allocator, bool copyConstStrings = false) { + switch (rhs.GetType()) { + case kObjectType: { + SizeType count = rhs.data_.o.size; + Member* lm = reinterpret_cast(allocator.Malloc(count * sizeof(Member))); + const typename GenericValue::Member* rm = rhs.GetMembersPointer(); + for (SizeType i = 0; i < count; i++) { + new (&lm[i].name) GenericValue(rm[i].name, allocator, copyConstStrings); + new (&lm[i].value) GenericValue(rm[i].value, allocator, copyConstStrings); + } + data_.f.flags = kObjectFlag; + data_.o.size = data_.o.capacity = count; + SetMembersPointer(lm); + } + break; + case kArrayType: { + SizeType count = rhs.data_.a.size; + GenericValue* le = reinterpret_cast(allocator.Malloc(count * sizeof(GenericValue))); + const GenericValue* re = rhs.GetElementsPointer(); + for (SizeType i = 0; i < count; i++) + new (&le[i]) GenericValue(re[i], allocator, copyConstStrings); + data_.f.flags = kArrayFlag; + data_.a.size = data_.a.capacity = count; + SetElementsPointer(le); + } + break; + case kStringType: + if (rhs.data_.f.flags == kConstStringFlag && !copyConstStrings) { + data_.f.flags = rhs.data_.f.flags; + data_ = *reinterpret_cast(&rhs.data_); + } + else + SetStringRaw(StringRef(rhs.GetString(), rhs.GetStringLength()), allocator); + break; + default: + data_.f.flags = rhs.data_.f.flags; + data_ = *reinterpret_cast(&rhs.data_); + break; + } + } + + //! Constructor for boolean value. + /*! \param b Boolean value + \note This constructor is limited to \em real boolean values and rejects + implicitly converted types like arbitrary pointers. Use an explicit cast + to \c bool, if you want to construct a boolean JSON value in such cases. + */ +#ifndef RAPIDJSON_DOXYGEN_RUNNING // hide SFINAE from Doxygen + template + explicit GenericValue(T b, RAPIDJSON_ENABLEIF((internal::IsSame))) RAPIDJSON_NOEXCEPT // See #472 +#else + explicit GenericValue(bool b) RAPIDJSON_NOEXCEPT +#endif + : data_() { + // safe-guard against failing SFINAE + RAPIDJSON_STATIC_ASSERT((internal::IsSame::Value)); + data_.f.flags = b ? kTrueFlag : kFalseFlag; + } + + //! Constructor for int value. + explicit GenericValue(int i) RAPIDJSON_NOEXCEPT : data_() { + data_.n.i64 = i; + data_.f.flags = (i >= 0) ? (kNumberIntFlag | kUintFlag | kUint64Flag) : kNumberIntFlag; + } + + //! Constructor for unsigned value. + explicit GenericValue(unsigned u) RAPIDJSON_NOEXCEPT : data_() { + data_.n.u64 = u; + data_.f.flags = (u & 0x80000000) ? kNumberUintFlag : (kNumberUintFlag | kIntFlag | kInt64Flag); + } + + //! Constructor for int64_t value. + explicit GenericValue(int64_t i64) RAPIDJSON_NOEXCEPT : data_() { + data_.n.i64 = i64; + data_.f.flags = kNumberInt64Flag; + if (i64 >= 0) { + data_.f.flags |= kNumberUint64Flag; + if (!(static_cast(i64) & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x00000000))) + data_.f.flags |= kUintFlag; + if (!(static_cast(i64) & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + else if (i64 >= static_cast(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + + //! Constructor for uint64_t value. + explicit GenericValue(uint64_t u64) RAPIDJSON_NOEXCEPT : data_() { + data_.n.u64 = u64; + data_.f.flags = kNumberUint64Flag; + if (!(u64 & RAPIDJSON_UINT64_C2(0x80000000, 0x00000000))) + data_.f.flags |= kInt64Flag; + if (!(u64 & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x00000000))) + data_.f.flags |= kUintFlag; + if (!(u64 & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + + //! Constructor for double value. + explicit GenericValue(double d) RAPIDJSON_NOEXCEPT : data_() { data_.n.d = d; data_.f.flags = kNumberDoubleFlag; } + + //! Constructor for float value. + explicit GenericValue(float f) RAPIDJSON_NOEXCEPT : data_() { data_.n.d = static_cast(f); data_.f.flags = kNumberDoubleFlag; } + + //! Constructor for constant string (i.e. do not make a copy of string) + GenericValue(const Ch* s, SizeType length) RAPIDJSON_NOEXCEPT : data_() { SetStringRaw(StringRef(s, length)); } + + //! Constructor for constant string (i.e. do not make a copy of string) + explicit GenericValue(StringRefType s) RAPIDJSON_NOEXCEPT : data_() { SetStringRaw(s); } + + //! Constructor for copy-string (i.e. do make a copy of string) + GenericValue(const Ch* s, SizeType length, Allocator& allocator) : data_() { SetStringRaw(StringRef(s, length), allocator); } + + //! Constructor for copy-string (i.e. do make a copy of string) + GenericValue(const Ch*s, Allocator& allocator) : data_() { SetStringRaw(StringRef(s), allocator); } + +#if RAPIDJSON_HAS_STDSTRING + //! Constructor for copy-string from a string object (i.e. do make a copy of string) + /*! \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + GenericValue(const std::basic_string& s, Allocator& allocator) : data_() { SetStringRaw(StringRef(s), allocator); } +#endif + + //! Constructor for Array. + /*! + \param a An array obtained by \c GetArray(). + \note \c Array is always pass-by-value. + \note the source array is moved into this value and the sourec array becomes empty. + */ + GenericValue(Array a) RAPIDJSON_NOEXCEPT : data_(a.value_.data_) { + a.value_.data_ = Data(); + a.value_.data_.f.flags = kArrayFlag; + } + + //! Constructor for Object. + /*! + \param o An object obtained by \c GetObject(). + \note \c Object is always pass-by-value. + \note the source object is moved into this value and the sourec object becomes empty. + */ + GenericValue(Object o) RAPIDJSON_NOEXCEPT : data_(o.value_.data_) { + o.value_.data_ = Data(); + o.value_.data_.f.flags = kObjectFlag; + } + + //! Destructor. + /*! Need to destruct elements of array, members of object, or copy-string. + */ + ~GenericValue() { + if (Allocator::kNeedFree) { // Shortcut by Allocator's trait + switch(data_.f.flags) { + case kArrayFlag: + { + GenericValue* e = GetElementsPointer(); + for (GenericValue* v = e; v != e + data_.a.size; ++v) + v->~GenericValue(); + Allocator::Free(e); + } + break; + + case kObjectFlag: + for (MemberIterator m = MemberBegin(); m != MemberEnd(); ++m) + m->~Member(); + Allocator::Free(GetMembersPointer()); + break; + + case kCopyStringFlag: + Allocator::Free(const_cast(GetStringPointer())); + break; + + default: + break; // Do nothing for other types. + } + } + } + + //@} + + //!@name Assignment operators + //@{ + + //! Assignment with move semantics. + /*! \param rhs Source of the assignment. It will become a null value after assignment. + */ + GenericValue& operator=(GenericValue& rhs) RAPIDJSON_NOEXCEPT { + if (RAPIDJSON_LIKELY(this != &rhs)) { + this->~GenericValue(); + RawAssign(rhs); + } + return *this; + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move assignment in C++11 + GenericValue& operator=(GenericValue&& rhs) RAPIDJSON_NOEXCEPT { + return *this = rhs.Move(); + } +#endif + + //! Assignment of constant string reference (no copy) + /*! \param str Constant string reference to be assigned + \note This overload is needed to avoid clashes with the generic primitive type assignment overload below. + \see GenericStringRef, operator=(T) + */ + GenericValue& operator=(StringRefType str) RAPIDJSON_NOEXCEPT { + GenericValue s(str); + return *this = s; + } + + //! Assignment with primitive types. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param value The value to be assigned. + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref SetString(const Ch*, Allocator&) (for copying) or + \ref StringRef() (to explicitly mark the pointer as constant) instead. + All other pointer types would implicitly convert to \c bool, + use \ref SetBool() instead. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::IsPointer), (GenericValue&)) + operator=(T value) { + GenericValue v(value); + return *this = v; + } + + //! Deep-copy assignment from Value + /*! Assigns a \b copy of the Value to the current Value object + \tparam SourceAllocator Allocator type of \c rhs + \param rhs Value to copy from (read-only) + \param allocator Allocator to use for copying + \param copyConstStrings Force copying of constant strings (e.g. referencing an in-situ buffer) + */ + template + GenericValue& CopyFrom(const GenericValue& rhs, Allocator& allocator, bool copyConstStrings = false) { + RAPIDJSON_ASSERT(static_cast(this) != static_cast(&rhs)); + this->~GenericValue(); + new (this) GenericValue(rhs, allocator, copyConstStrings); + return *this; + } + + //! Exchange the contents of this value with those of other. + /*! + \param other Another value. + \note Constant complexity. + */ + GenericValue& Swap(GenericValue& other) RAPIDJSON_NOEXCEPT { + GenericValue temp; + temp.RawAssign(*this); + RawAssign(other); + other.RawAssign(temp); + return *this; + } + + //! free-standing swap function helper + /*! + Helper function to enable support for common swap implementation pattern based on \c std::swap: + \code + void swap(MyClass& a, MyClass& b) { + using std::swap; + swap(a.value, b.value); + // ... + } + \endcode + \see Swap() + */ + friend inline void swap(GenericValue& a, GenericValue& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } + + //! Prepare Value for move semantics + /*! \return *this */ + GenericValue& Move() RAPIDJSON_NOEXCEPT { return *this; } + //@} + + //!@name Equal-to and not-equal-to operators + //@{ + //! Equal-to operator + /*! + \note If an object contains duplicated named member, comparing equality with any object is always \c false. + \note Complexity is quadratic in Object's member number and linear for the rest (number of all values in the subtree and total lengths of all strings). + */ + template + bool operator==(const GenericValue& rhs) const { + typedef GenericValue RhsType; + if (GetType() != rhs.GetType()) + return false; + + switch (GetType()) { + case kObjectType: // Warning: O(n^2) inner-loop + if (data_.o.size != rhs.data_.o.size) + return false; + for (ConstMemberIterator lhsMemberItr = MemberBegin(); lhsMemberItr != MemberEnd(); ++lhsMemberItr) { + typename RhsType::ConstMemberIterator rhsMemberItr = rhs.FindMember(lhsMemberItr->name); + if (rhsMemberItr == rhs.MemberEnd() || lhsMemberItr->value != rhsMemberItr->value) + return false; + } + return true; + + case kArrayType: + if (data_.a.size != rhs.data_.a.size) + return false; + for (SizeType i = 0; i < data_.a.size; i++) + if ((*this)[i] != rhs[i]) + return false; + return true; + + case kStringType: + return StringEqual(rhs); + + case kNumberType: + if (IsDouble() || rhs.IsDouble()) { + double a = GetDouble(); // May convert from integer to double. + double b = rhs.GetDouble(); // Ditto + return a >= b && a <= b; // Prevent -Wfloat-equal + } + else + return data_.n.u64 == rhs.data_.n.u64; + + default: + return true; + } + } + + //! Equal-to operator with const C-string pointer + bool operator==(const Ch* rhs) const { return *this == GenericValue(StringRef(rhs)); } + +#if RAPIDJSON_HAS_STDSTRING + //! Equal-to operator with string object + /*! \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + bool operator==(const std::basic_string& rhs) const { return *this == GenericValue(StringRef(rhs)); } +#endif + + //! Equal-to operator with primitive types + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c double, \c true, \c false + */ + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr,internal::IsGenericValue >), (bool)) operator==(const T& rhs) const { return *this == GenericValue(rhs); } + + //! Not-equal-to operator + /*! \return !(*this == rhs) + */ + template + bool operator!=(const GenericValue& rhs) const { return !(*this == rhs); } + + //! Not-equal-to operator with const C-string pointer + bool operator!=(const Ch* rhs) const { return !(*this == rhs); } + + //! Not-equal-to operator with arbitrary types + /*! \return !(*this == rhs) + */ + template RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator!=(const T& rhs) const { return !(*this == rhs); } + + //! Equal-to operator with arbitrary types (symmetric version) + /*! \return (rhs == lhs) + */ + template friend RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator==(const T& lhs, const GenericValue& rhs) { return rhs == lhs; } + + //! Not-Equal-to operator with arbitrary types (symmetric version) + /*! \return !(rhs == lhs) + */ + template friend RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator!=(const T& lhs, const GenericValue& rhs) { return !(rhs == lhs); } + //@} + + //!@name Type + //@{ + + Type GetType() const { return static_cast(data_.f.flags & kTypeMask); } + bool IsNull() const { return data_.f.flags == kNullFlag; } + bool IsFalse() const { return data_.f.flags == kFalseFlag; } + bool IsTrue() const { return data_.f.flags == kTrueFlag; } + bool IsBool() const { return (data_.f.flags & kBoolFlag) != 0; } + bool IsObject() const { return data_.f.flags == kObjectFlag; } + bool IsArray() const { return data_.f.flags == kArrayFlag; } + bool IsNumber() const { return (data_.f.flags & kNumberFlag) != 0; } + bool IsInt() const { return (data_.f.flags & kIntFlag) != 0; } + bool IsUint() const { return (data_.f.flags & kUintFlag) != 0; } + bool IsInt64() const { return (data_.f.flags & kInt64Flag) != 0; } + bool IsUint64() const { return (data_.f.flags & kUint64Flag) != 0; } + bool IsDouble() const { return (data_.f.flags & kDoubleFlag) != 0; } + bool IsString() const { return (data_.f.flags & kStringFlag) != 0; } + + // Checks whether a number can be losslessly converted to a double. + bool IsLosslessDouble() const { + if (!IsNumber()) return false; + if (IsUint64()) { + uint64_t u = GetUint64(); + volatile double d = static_cast(u); + return (d >= 0.0) + && (d < static_cast((std::numeric_limits::max)())) + && (u == static_cast(d)); + } + if (IsInt64()) { + int64_t i = GetInt64(); + volatile double d = static_cast(i); + return (d >= static_cast((std::numeric_limits::min)())) + && (d < static_cast((std::numeric_limits::max)())) + && (i == static_cast(d)); + } + return true; // double, int, uint are always lossless + } + + // Checks whether a number is a float (possible lossy). + bool IsFloat() const { + if ((data_.f.flags & kDoubleFlag) == 0) + return false; + double d = GetDouble(); + return d >= -3.4028234e38 && d <= 3.4028234e38; + } + // Checks whether a number can be losslessly converted to a float. + bool IsLosslessFloat() const { + if (!IsNumber()) return false; + double a = GetDouble(); + if (a < static_cast(-(std::numeric_limits::max)()) + || a > static_cast((std::numeric_limits::max)())) + return false; + double b = static_cast(static_cast(a)); + return a >= b && a <= b; // Prevent -Wfloat-equal + } + + //@} + + //!@name Null + //@{ + + GenericValue& SetNull() { this->~GenericValue(); new (this) GenericValue(); return *this; } + + //@} + + //!@name Bool + //@{ + + bool GetBool() const { RAPIDJSON_ASSERT(IsBool()); return data_.f.flags == kTrueFlag; } + //!< Set boolean value + /*! \post IsBool() == true */ + GenericValue& SetBool(bool b) { this->~GenericValue(); new (this) GenericValue(b); return *this; } + + //@} + + //!@name Object + //@{ + + //! Set this value as an empty object. + /*! \post IsObject() == true */ + GenericValue& SetObject() { this->~GenericValue(); new (this) GenericValue(kObjectType); return *this; } + + //! Get the number of members in the object. + SizeType MemberCount() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.size; } + + //! Get the capacity of object. + SizeType MemberCapacity() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.capacity; } + + //! Check whether the object is empty. + bool ObjectEmpty() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.size == 0; } + + //! Get a value from an object associated with the name. + /*! \pre IsObject() == true + \tparam T Either \c Ch or \c const \c Ch (template used for disambiguation with \ref operator[](SizeType)) + \note In version 0.1x, if the member is not found, this function returns a null value. This makes issue 7. + Since 0.2, if the name is not correct, it will assert. + If user is unsure whether a member exists, user should use HasMember() first. + A better approach is to use FindMember(). + \note Linear time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >),(GenericValue&)) operator[](T* name) { + GenericValue n(StringRef(name)); + return (*this)[n]; + } + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >),(const GenericValue&)) operator[](T* name) const { return const_cast(*this)[name]; } + + //! Get a value from an object associated with the name. + /*! \pre IsObject() == true + \tparam SourceAllocator Allocator of the \c name value + + \note Compared to \ref operator[](T*), this version is faster because it does not need a StrLen(). + And it can also handle strings with embedded null characters. + + \note Linear time complexity. + */ + template + GenericValue& operator[](const GenericValue& name) { + MemberIterator member = FindMember(name); + if (member != MemberEnd()) + return member->value; + else { + RAPIDJSON_ASSERT(false); // see above note + + // This will generate -Wexit-time-destructors in clang + // static GenericValue NullValue; + // return NullValue; + + // Use static buffer and placement-new to prevent destruction + static char buffer[sizeof(GenericValue)]; + return *new (buffer) GenericValue(); + } + } + template + const GenericValue& operator[](const GenericValue& name) const { return const_cast(*this)[name]; } + +#if RAPIDJSON_HAS_STDSTRING + //! Get a value from an object associated with name (string object). + GenericValue& operator[](const std::basic_string& name) { return (*this)[GenericValue(StringRef(name))]; } + const GenericValue& operator[](const std::basic_string& name) const { return (*this)[GenericValue(StringRef(name))]; } +#endif + + //! Const member iterator + /*! \pre IsObject() == true */ + ConstMemberIterator MemberBegin() const { RAPIDJSON_ASSERT(IsObject()); return ConstMemberIterator(GetMembersPointer()); } + //! Const \em past-the-end member iterator + /*! \pre IsObject() == true */ + ConstMemberIterator MemberEnd() const { RAPIDJSON_ASSERT(IsObject()); return ConstMemberIterator(GetMembersPointer() + data_.o.size); } + //! Member iterator + /*! \pre IsObject() == true */ + MemberIterator MemberBegin() { RAPIDJSON_ASSERT(IsObject()); return MemberIterator(GetMembersPointer()); } + //! \em Past-the-end member iterator + /*! \pre IsObject() == true */ + MemberIterator MemberEnd() { RAPIDJSON_ASSERT(IsObject()); return MemberIterator(GetMembersPointer() + data_.o.size); } + + //! Request the object to have enough capacity to store members. + /*! \param newCapacity The capacity that the object at least need to have. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note Linear time complexity. + */ + GenericValue& MemberReserve(SizeType newCapacity, Allocator &allocator) { + RAPIDJSON_ASSERT(IsObject()); + if (newCapacity > data_.o.capacity) { + SetMembersPointer(reinterpret_cast(allocator.Realloc(GetMembersPointer(), data_.o.capacity * sizeof(Member), newCapacity * sizeof(Member)))); + data_.o.capacity = newCapacity; + } + return *this; + } + + //! Check whether a member exists in the object. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + bool HasMember(const Ch* name) const { return FindMember(name) != MemberEnd(); } + +#if RAPIDJSON_HAS_STDSTRING + //! Check whether a member exists in the object with string object. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + bool HasMember(const std::basic_string& name) const { return FindMember(name) != MemberEnd(); } +#endif + + //! Check whether a member exists in the object with GenericValue name. + /*! + This version is faster because it does not need a StrLen(). It can also handle string with null character. + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + template + bool HasMember(const GenericValue& name) const { return FindMember(name) != MemberEnd(); } + + //! Find member by name. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + + \note Earlier versions of Rapidjson returned a \c NULL pointer, in case + the requested member doesn't exist. For consistency with e.g. + \c std::map, this has been changed to MemberEnd() now. + \note Linear time complexity. + */ + MemberIterator FindMember(const Ch* name) { + GenericValue n(StringRef(name)); + return FindMember(n); + } + + ConstMemberIterator FindMember(const Ch* name) const { return const_cast(*this).FindMember(name); } + + //! Find member by name. + /*! + This version is faster because it does not need a StrLen(). It can also handle string with null character. + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + + \note Earlier versions of Rapidjson returned a \c NULL pointer, in case + the requested member doesn't exist. For consistency with e.g. + \c std::map, this has been changed to MemberEnd() now. + \note Linear time complexity. + */ + template + MemberIterator FindMember(const GenericValue& name) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(name.IsString()); + MemberIterator member = MemberBegin(); + for ( ; member != MemberEnd(); ++member) + if (name.StringEqual(member->name)) + break; + return member; + } + template ConstMemberIterator FindMember(const GenericValue& name) const { return const_cast(*this).FindMember(name); } + +#if RAPIDJSON_HAS_STDSTRING + //! Find member by string object name. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + */ + MemberIterator FindMember(const std::basic_string& name) { return FindMember(GenericValue(StringRef(name))); } + ConstMemberIterator FindMember(const std::basic_string& name) const { return FindMember(GenericValue(StringRef(name))); } +#endif + + //! Add a member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value Value of any type. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note The ownership of \c name and \c value will be transferred to this object on success. + \pre IsObject() && name.IsString() + \post name.IsNull() && value.IsNull() + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, GenericValue& value, Allocator& allocator) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(name.IsString()); + + ObjectData& o = data_.o; + if (o.size >= o.capacity) + MemberReserve(o.capacity == 0 ? kDefaultObjectCapacity : (o.capacity + (o.capacity + 1) / 2), allocator); + Member* members = GetMembersPointer(); + members[o.size].name.RawAssign(name); + members[o.size].value.RawAssign(value); + o.size++; + return *this; + } + + //! Add a constant string value as member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(GenericValue&,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, StringRefType value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Add a string object as member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(GenericValue&,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, std::basic_string& value, Allocator& allocator) { + GenericValue v(value, allocator); + return AddMember(name, v, allocator); + } +#endif + + //! Add any primitive value as member (name-value pair) to the object. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param name A string value as name of member. + \param value Value of primitive type \c T as value of member + \param allocator Allocator for reallocating memory. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref AddMember(StringRefType, GenericValue&, Allocator&) or \ref + AddMember(StringRefType, StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized Constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + AddMember(GenericValue& name, T value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericValue& AddMember(GenericValue&& name, GenericValue&& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(GenericValue&& name, GenericValue& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(GenericValue& name, GenericValue&& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(StringRefType name, GenericValue&& value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + + + //! Add a member (name-value pair) to the object. + /*! \param name A constant string reference as name of member. + \param value Value of any type. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note The ownership of \c value will be transferred to this object on success. + \pre IsObject() + \post value.IsNull() + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(StringRefType name, GenericValue& value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } + + //! Add a constant string value as member (name-value pair) to the object. + /*! \param name A constant string reference as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(StringRefType,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(StringRefType name, StringRefType value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + + //! Add any primitive value as member (name-value pair) to the object. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param name A constant string reference as name of member. + \param value Value of primitive type \c T as value of member + \param allocator Allocator for reallocating memory. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref AddMember(StringRefType, GenericValue&, Allocator&) or \ref + AddMember(StringRefType, StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized Constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + AddMember(StringRefType name, T value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } + + //! Remove all members in the object. + /*! This function do not deallocate memory in the object, i.e. the capacity is unchanged. + \note Linear time complexity. + */ + void RemoveAllMembers() { + RAPIDJSON_ASSERT(IsObject()); + for (MemberIterator m = MemberBegin(); m != MemberEnd(); ++m) + m->~Member(); + data_.o.size = 0; + } + + //! Remove a member in object by its name. + /*! \param name Name of member to be removed. + \return Whether the member existed. + \note This function may reorder the object members. Use \ref + EraseMember(ConstMemberIterator) if you need to preserve the + relative order of the remaining members. + \note Linear time complexity. + */ + bool RemoveMember(const Ch* name) { + GenericValue n(StringRef(name)); + return RemoveMember(n); + } + +#if RAPIDJSON_HAS_STDSTRING + bool RemoveMember(const std::basic_string& name) { return RemoveMember(GenericValue(StringRef(name))); } +#endif + + template + bool RemoveMember(const GenericValue& name) { + MemberIterator m = FindMember(name); + if (m != MemberEnd()) { + RemoveMember(m); + return true; + } + else + return false; + } + + //! Remove a member in object by iterator. + /*! \param m member iterator (obtained by FindMember() or MemberBegin()). + \return the new iterator after removal. + \note This function may reorder the object members. Use \ref + EraseMember(ConstMemberIterator) if you need to preserve the + relative order of the remaining members. + \note Constant time complexity. + */ + MemberIterator RemoveMember(MemberIterator m) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(data_.o.size > 0); + RAPIDJSON_ASSERT(GetMembersPointer() != 0); + RAPIDJSON_ASSERT(m >= MemberBegin() && m < MemberEnd()); + + MemberIterator last(GetMembersPointer() + (data_.o.size - 1)); + if (data_.o.size > 1 && m != last) + *m = *last; // Move the last one to this place + else + m->~Member(); // Only one left, just destroy + --data_.o.size; + return m; + } + + //! Remove a member from an object by iterator. + /*! \param pos iterator to the member to remove + \pre IsObject() == true && \ref MemberBegin() <= \c pos < \ref MemberEnd() + \return Iterator following the removed element. + If the iterator \c pos refers to the last element, the \ref MemberEnd() iterator is returned. + \note This function preserves the relative order of the remaining object + members. If you do not need this, use the more efficient \ref RemoveMember(MemberIterator). + \note Linear time complexity. + */ + MemberIterator EraseMember(ConstMemberIterator pos) { + return EraseMember(pos, pos +1); + } + + //! Remove members in the range [first, last) from an object. + /*! \param first iterator to the first member to remove + \param last iterator following the last member to remove + \pre IsObject() == true && \ref MemberBegin() <= \c first <= \c last <= \ref MemberEnd() + \return Iterator following the last removed element. + \note This function preserves the relative order of the remaining object + members. + \note Linear time complexity. + */ + MemberIterator EraseMember(ConstMemberIterator first, ConstMemberIterator last) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(data_.o.size > 0); + RAPIDJSON_ASSERT(GetMembersPointer() != 0); + RAPIDJSON_ASSERT(first >= MemberBegin()); + RAPIDJSON_ASSERT(first <= last); + RAPIDJSON_ASSERT(last <= MemberEnd()); + + MemberIterator pos = MemberBegin() + (first - MemberBegin()); + for (MemberIterator itr = pos; itr != last; ++itr) + itr->~Member(); + std::memmove(static_cast(&*pos), &*last, static_cast(MemberEnd() - last) * sizeof(Member)); + data_.o.size -= static_cast(last - first); + return pos; + } + + //! Erase a member in object by its name. + /*! \param name Name of member to be removed. + \return Whether the member existed. + \note Linear time complexity. + */ + bool EraseMember(const Ch* name) { + GenericValue n(StringRef(name)); + return EraseMember(n); + } + +#if RAPIDJSON_HAS_STDSTRING + bool EraseMember(const std::basic_string& name) { return EraseMember(GenericValue(StringRef(name))); } +#endif + + template + bool EraseMember(const GenericValue& name) { + MemberIterator m = FindMember(name); + if (m != MemberEnd()) { + EraseMember(m); + return true; + } + else + return false; + } + + Object GetObject() { RAPIDJSON_ASSERT(IsObject()); return Object(*this); } + ConstObject GetObject() const { RAPIDJSON_ASSERT(IsObject()); return ConstObject(*this); } + + //@} + + //!@name Array + //@{ + + //! Set this value as an empty array. + /*! \post IsArray == true */ + GenericValue& SetArray() { this->~GenericValue(); new (this) GenericValue(kArrayType); return *this; } + + //! Get the number of elements in array. + SizeType Size() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size; } + + //! Get the capacity of array. + SizeType Capacity() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.capacity; } + + //! Check whether the array is empty. + bool Empty() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size == 0; } + + //! Remove all elements in the array. + /*! This function do not deallocate memory in the array, i.e. the capacity is unchanged. + \note Linear time complexity. + */ + void Clear() { + RAPIDJSON_ASSERT(IsArray()); + GenericValue* e = GetElementsPointer(); + for (GenericValue* v = e; v != e + data_.a.size; ++v) + v->~GenericValue(); + data_.a.size = 0; + } + + //! Get an element from array by index. + /*! \pre IsArray() == true + \param index Zero-based index of element. + \see operator[](T*) + */ + GenericValue& operator[](SizeType index) { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(index < data_.a.size); + return GetElementsPointer()[index]; + } + const GenericValue& operator[](SizeType index) const { return const_cast(*this)[index]; } + + //! Element iterator + /*! \pre IsArray() == true */ + ValueIterator Begin() { RAPIDJSON_ASSERT(IsArray()); return GetElementsPointer(); } + //! \em Past-the-end element iterator + /*! \pre IsArray() == true */ + ValueIterator End() { RAPIDJSON_ASSERT(IsArray()); return GetElementsPointer() + data_.a.size; } + //! Constant element iterator + /*! \pre IsArray() == true */ + ConstValueIterator Begin() const { return const_cast(*this).Begin(); } + //! Constant \em past-the-end element iterator + /*! \pre IsArray() == true */ + ConstValueIterator End() const { return const_cast(*this).End(); } + + //! Request the array to have enough capacity to store elements. + /*! \param newCapacity The capacity that the array at least need to have. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note Linear time complexity. + */ + GenericValue& Reserve(SizeType newCapacity, Allocator &allocator) { + RAPIDJSON_ASSERT(IsArray()); + if (newCapacity > data_.a.capacity) { + SetElementsPointer(reinterpret_cast(allocator.Realloc(GetElementsPointer(), data_.a.capacity * sizeof(GenericValue), newCapacity * sizeof(GenericValue)))); + data_.a.capacity = newCapacity; + } + return *this; + } + + //! Append a GenericValue at the end of the array. + /*! \param value Value to be appended. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \post value.IsNull() == true + \return The value itself for fluent API. + \note The ownership of \c value will be transferred to this array on success. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + \note Amortized constant time complexity. + */ + GenericValue& PushBack(GenericValue& value, Allocator& allocator) { + RAPIDJSON_ASSERT(IsArray()); + if (data_.a.size >= data_.a.capacity) + Reserve(data_.a.capacity == 0 ? kDefaultArrayCapacity : (data_.a.capacity + (data_.a.capacity + 1) / 2), allocator); + GetElementsPointer()[data_.a.size++].RawAssign(value); + return *this; + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericValue& PushBack(GenericValue&& value, Allocator& allocator) { + return PushBack(value, allocator); + } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + + //! Append a constant string reference at the end of the array. + /*! \param value Constant string reference to be appended. + \param allocator Allocator for reallocating memory. It must be the same one used previously. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \return The value itself for fluent API. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + \note Amortized constant time complexity. + \see GenericStringRef + */ + GenericValue& PushBack(StringRefType value, Allocator& allocator) { + return (*this).template PushBack(value, allocator); + } + + //! Append a primitive value at the end of the array. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param value Value of primitive type T to be appended. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \return The value itself for fluent API. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref PushBack(GenericValue&, Allocator&) or \ref + PushBack(StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + PushBack(T value, Allocator& allocator) { + GenericValue v(value); + return PushBack(v, allocator); + } + + //! Remove the last element in the array. + /*! + \note Constant time complexity. + */ + GenericValue& PopBack() { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(!Empty()); + GetElementsPointer()[--data_.a.size].~GenericValue(); + return *this; + } + + //! Remove an element of array by iterator. + /*! + \param pos iterator to the element to remove + \pre IsArray() == true && \ref Begin() <= \c pos < \ref End() + \return Iterator following the removed element. If the iterator pos refers to the last element, the End() iterator is returned. + \note Linear time complexity. + */ + ValueIterator Erase(ConstValueIterator pos) { + return Erase(pos, pos + 1); + } + + //! Remove elements in the range [first, last) of the array. + /*! + \param first iterator to the first element to remove + \param last iterator following the last element to remove + \pre IsArray() == true && \ref Begin() <= \c first <= \c last <= \ref End() + \return Iterator following the last removed element. + \note Linear time complexity. + */ + ValueIterator Erase(ConstValueIterator first, ConstValueIterator last) { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(data_.a.size > 0); + RAPIDJSON_ASSERT(GetElementsPointer() != 0); + RAPIDJSON_ASSERT(first >= Begin()); + RAPIDJSON_ASSERT(first <= last); + RAPIDJSON_ASSERT(last <= End()); + ValueIterator pos = Begin() + (first - Begin()); + for (ValueIterator itr = pos; itr != last; ++itr) + itr->~GenericValue(); + std::memmove(static_cast(pos), last, static_cast(End() - last) * sizeof(GenericValue)); + data_.a.size -= static_cast(last - first); + return pos; + } + + Array GetArray() { RAPIDJSON_ASSERT(IsArray()); return Array(*this); } + ConstArray GetArray() const { RAPIDJSON_ASSERT(IsArray()); return ConstArray(*this); } + + //@} + + //!@name Number + //@{ + + int GetInt() const { RAPIDJSON_ASSERT(data_.f.flags & kIntFlag); return data_.n.i.i; } + unsigned GetUint() const { RAPIDJSON_ASSERT(data_.f.flags & kUintFlag); return data_.n.u.u; } + int64_t GetInt64() const { RAPIDJSON_ASSERT(data_.f.flags & kInt64Flag); return data_.n.i64; } + uint64_t GetUint64() const { RAPIDJSON_ASSERT(data_.f.flags & kUint64Flag); return data_.n.u64; } + + //! Get the value as double type. + /*! \note If the value is 64-bit integer type, it may lose precision. Use \c IsLosslessDouble() to check whether the converison is lossless. + */ + double GetDouble() const { + RAPIDJSON_ASSERT(IsNumber()); + if ((data_.f.flags & kDoubleFlag) != 0) return data_.n.d; // exact type, no conversion. + if ((data_.f.flags & kIntFlag) != 0) return data_.n.i.i; // int -> double + if ((data_.f.flags & kUintFlag) != 0) return data_.n.u.u; // unsigned -> double + if ((data_.f.flags & kInt64Flag) != 0) return static_cast(data_.n.i64); // int64_t -> double (may lose precision) + RAPIDJSON_ASSERT((data_.f.flags & kUint64Flag) != 0); return static_cast(data_.n.u64); // uint64_t -> double (may lose precision) + } + + //! Get the value as float type. + /*! \note If the value is 64-bit integer type, it may lose precision. Use \c IsLosslessFloat() to check whether the converison is lossless. + */ + float GetFloat() const { + return static_cast(GetDouble()); + } + + GenericValue& SetInt(int i) { this->~GenericValue(); new (this) GenericValue(i); return *this; } + GenericValue& SetUint(unsigned u) { this->~GenericValue(); new (this) GenericValue(u); return *this; } + GenericValue& SetInt64(int64_t i64) { this->~GenericValue(); new (this) GenericValue(i64); return *this; } + GenericValue& SetUint64(uint64_t u64) { this->~GenericValue(); new (this) GenericValue(u64); return *this; } + GenericValue& SetDouble(double d) { this->~GenericValue(); new (this) GenericValue(d); return *this; } + GenericValue& SetFloat(float f) { this->~GenericValue(); new (this) GenericValue(static_cast(f)); return *this; } + + //@} + + //!@name String + //@{ + + const Ch* GetString() const { RAPIDJSON_ASSERT(IsString()); return (data_.f.flags & kInlineStrFlag) ? data_.ss.str : GetStringPointer(); } + + //! Get the length of string. + /*! Since rapidjson permits "\\u0000" in the json string, strlen(v.GetString()) may not equal to v.GetStringLength(). + */ + SizeType GetStringLength() const { RAPIDJSON_ASSERT(IsString()); return ((data_.f.flags & kInlineStrFlag) ? (data_.ss.GetLength()) : data_.s.length); } + + //! Set this value as a string without copying source string. + /*! This version has better performance with supplied length, and also support string containing null character. + \param s source string pointer. + \param length The length of source string, excluding the trailing null terminator. + \return The value itself for fluent API. + \post IsString() == true && GetString() == s && GetStringLength() == length + \see SetString(StringRefType) + */ + GenericValue& SetString(const Ch* s, SizeType length) { return SetString(StringRef(s, length)); } + + //! Set this value as a string without copying source string. + /*! \param s source string reference + \return The value itself for fluent API. + \post IsString() == true && GetString() == s && GetStringLength() == s.length + */ + GenericValue& SetString(StringRefType s) { this->~GenericValue(); SetStringRaw(s); return *this; } + + //! Set this value as a string by copying from source string. + /*! This version has better performance with supplied length, and also support string containing null character. + \param s source string. + \param length The length of source string, excluding the trailing null terminator. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(const Ch* s, SizeType length, Allocator& allocator) { return SetString(StringRef(s, length), allocator); } + + //! Set this value as a string by copying from source string. + /*! \param s source string. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(const Ch* s, Allocator& allocator) { return SetString(StringRef(s), allocator); } + + //! Set this value as a string by copying from source string. + /*! \param s source string reference + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s.s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(StringRefType s, Allocator& allocator) { this->~GenericValue(); SetStringRaw(s, allocator); return *this; } + +#if RAPIDJSON_HAS_STDSTRING + //! Set this value as a string by copying from source string. + /*! \param s source string. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s.data() && strcmp(GetString(),s.data() == 0 && GetStringLength() == s.size() + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + GenericValue& SetString(const std::basic_string& s, Allocator& allocator) { return SetString(StringRef(s), allocator); } +#endif + + //@} + + //!@name Array + //@{ + + //! Templated version for checking whether this value is type T. + /*! + \tparam T Either \c bool, \c int, \c unsigned, \c int64_t, \c uint64_t, \c double, \c float, \c const \c char*, \c std::basic_string + */ + template + bool Is() const { return internal::TypeHelper::Is(*this); } + + template + T Get() const { return internal::TypeHelper::Get(*this); } + + template + T Get() { return internal::TypeHelper::Get(*this); } + + template + ValueType& Set(const T& data) { return internal::TypeHelper::Set(*this, data); } + + template + ValueType& Set(const T& data, AllocatorType& allocator) { return internal::TypeHelper::Set(*this, data, allocator); } + + //@} + + //! Generate events of this value to a Handler. + /*! This function adopts the GoF visitor pattern. + Typical usage is to output this JSON value as JSON text via Writer, which is a Handler. + It can also be used to deep clone this value via GenericDocument, which is also a Handler. + \tparam Handler type of handler. + \param handler An object implementing concept Handler. + */ + template + bool Accept(Handler& handler) const { + switch(GetType()) { + case kNullType: return handler.Null(); + case kFalseType: return handler.Bool(false); + case kTrueType: return handler.Bool(true); + + case kObjectType: + if (RAPIDJSON_UNLIKELY(!handler.StartObject())) + return false; + for (ConstMemberIterator m = MemberBegin(); m != MemberEnd(); ++m) { + RAPIDJSON_ASSERT(m->name.IsString()); // User may change the type of name by MemberIterator. + if (RAPIDJSON_UNLIKELY(!handler.Key(m->name.GetString(), m->name.GetStringLength(), (m->name.data_.f.flags & kCopyFlag) != 0))) + return false; + if (RAPIDJSON_UNLIKELY(!m->value.Accept(handler))) + return false; + } + return handler.EndObject(data_.o.size); + + case kArrayType: + if (RAPIDJSON_UNLIKELY(!handler.StartArray())) + return false; + for (const GenericValue* v = Begin(); v != End(); ++v) + if (RAPIDJSON_UNLIKELY(!v->Accept(handler))) + return false; + return handler.EndArray(data_.a.size); + + case kStringType: + return handler.String(GetString(), GetStringLength(), (data_.f.flags & kCopyFlag) != 0); + + default: + RAPIDJSON_ASSERT(GetType() == kNumberType); + if (IsDouble()) return handler.Double(data_.n.d); + else if (IsInt()) return handler.Int(data_.n.i.i); + else if (IsUint()) return handler.Uint(data_.n.u.u); + else if (IsInt64()) return handler.Int64(data_.n.i64); + else return handler.Uint64(data_.n.u64); + } + } + +private: + template friend class GenericValue; + template friend class GenericDocument; + + enum { + kBoolFlag = 0x0008, + kNumberFlag = 0x0010, + kIntFlag = 0x0020, + kUintFlag = 0x0040, + kInt64Flag = 0x0080, + kUint64Flag = 0x0100, + kDoubleFlag = 0x0200, + kStringFlag = 0x0400, + kCopyFlag = 0x0800, + kInlineStrFlag = 0x1000, + + // Initial flags of different types. + kNullFlag = kNullType, + kTrueFlag = kTrueType | kBoolFlag, + kFalseFlag = kFalseType | kBoolFlag, + kNumberIntFlag = kNumberType | kNumberFlag | kIntFlag | kInt64Flag, + kNumberUintFlag = kNumberType | kNumberFlag | kUintFlag | kUint64Flag | kInt64Flag, + kNumberInt64Flag = kNumberType | kNumberFlag | kInt64Flag, + kNumberUint64Flag = kNumberType | kNumberFlag | kUint64Flag, + kNumberDoubleFlag = kNumberType | kNumberFlag | kDoubleFlag, + kNumberAnyFlag = kNumberType | kNumberFlag | kIntFlag | kInt64Flag | kUintFlag | kUint64Flag | kDoubleFlag, + kConstStringFlag = kStringType | kStringFlag, + kCopyStringFlag = kStringType | kStringFlag | kCopyFlag, + kShortStringFlag = kStringType | kStringFlag | kCopyFlag | kInlineStrFlag, + kObjectFlag = kObjectType, + kArrayFlag = kArrayType, + + kTypeMask = 0x07 + }; + + static const SizeType kDefaultArrayCapacity = 16; + static const SizeType kDefaultObjectCapacity = 16; + + struct Flag { +#if RAPIDJSON_48BITPOINTER_OPTIMIZATION + char payload[sizeof(SizeType) * 2 + 6]; // 2 x SizeType + lower 48-bit pointer +#elif RAPIDJSON_64BIT + char payload[sizeof(SizeType) * 2 + sizeof(void*) + 6]; // 6 padding bytes +#else + char payload[sizeof(SizeType) * 2 + sizeof(void*) + 2]; // 2 padding bytes +#endif + uint16_t flags; + }; + + struct String { + SizeType length; + SizeType hashcode; //!< reserved + const Ch* str; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + // implementation detail: ShortString can represent zero-terminated strings up to MaxSize chars + // (excluding the terminating zero) and store a value to determine the length of the contained + // string in the last character str[LenPos] by storing "MaxSize - length" there. If the string + // to store has the maximal length of MaxSize then str[LenPos] will be 0 and therefore act as + // the string terminator as well. For getting the string length back from that value just use + // "MaxSize - str[LenPos]". + // This allows to store 13-chars strings in 32-bit mode, 21-chars strings in 64-bit mode, + // 13-chars strings for RAPIDJSON_48BITPOINTER_OPTIMIZATION=1 inline (for `UTF8`-encoded strings). + struct ShortString { + enum { MaxChars = sizeof(static_cast(0)->payload) / sizeof(Ch), MaxSize = MaxChars - 1, LenPos = MaxSize }; + Ch str[MaxChars]; + + inline static bool Usable(SizeType len) { return (MaxSize >= len); } + inline void SetLength(SizeType len) { str[LenPos] = static_cast(MaxSize - len); } + inline SizeType GetLength() const { return static_cast(MaxSize - str[LenPos]); } + }; // at most as many bytes as "String" above => 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + // By using proper binary layout, retrieval of different integer types do not need conversions. + union Number { +#if RAPIDJSON_ENDIAN == RAPIDJSON_LITTLEENDIAN + struct I { + int i; + char padding[4]; + }i; + struct U { + unsigned u; + char padding2[4]; + }u; +#else + struct I { + char padding[4]; + int i; + }i; + struct U { + char padding2[4]; + unsigned u; + }u; +#endif + int64_t i64; + uint64_t u64; + double d; + }; // 8 bytes + + struct ObjectData { + SizeType size; + SizeType capacity; + Member* members; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + struct ArrayData { + SizeType size; + SizeType capacity; + GenericValue* elements; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + union Data { + String s; + ShortString ss; + Number n; + ObjectData o; + ArrayData a; + Flag f; + }; // 16 bytes in 32-bit mode, 24 bytes in 64-bit mode, 16 bytes in 64-bit with RAPIDJSON_48BITPOINTER_OPTIMIZATION + + RAPIDJSON_FORCEINLINE const Ch* GetStringPointer() const { return RAPIDJSON_GETPOINTER(Ch, data_.s.str); } + RAPIDJSON_FORCEINLINE const Ch* SetStringPointer(const Ch* str) { return RAPIDJSON_SETPOINTER(Ch, data_.s.str, str); } + RAPIDJSON_FORCEINLINE GenericValue* GetElementsPointer() const { return RAPIDJSON_GETPOINTER(GenericValue, data_.a.elements); } + RAPIDJSON_FORCEINLINE GenericValue* SetElementsPointer(GenericValue* elements) { return RAPIDJSON_SETPOINTER(GenericValue, data_.a.elements, elements); } + RAPIDJSON_FORCEINLINE Member* GetMembersPointer() const { return RAPIDJSON_GETPOINTER(Member, data_.o.members); } + RAPIDJSON_FORCEINLINE Member* SetMembersPointer(Member* members) { return RAPIDJSON_SETPOINTER(Member, data_.o.members, members); } + + // Initialize this value as array with initial data, without calling destructor. + void SetArrayRaw(GenericValue* values, SizeType count, Allocator& allocator) { + data_.f.flags = kArrayFlag; + if (count) { + GenericValue* e = static_cast(allocator.Malloc(count * sizeof(GenericValue))); + SetElementsPointer(e); + std::memcpy(static_cast(e), values, count * sizeof(GenericValue)); + } + else + SetElementsPointer(0); + data_.a.size = data_.a.capacity = count; + } + + //! Initialize this value as object with initial data, without calling destructor. + void SetObjectRaw(Member* members, SizeType count, Allocator& allocator) { + data_.f.flags = kObjectFlag; + if (count) { + Member* m = static_cast(allocator.Malloc(count * sizeof(Member))); + SetMembersPointer(m); + std::memcpy(static_cast(m), members, count * sizeof(Member)); + } + else + SetMembersPointer(0); + data_.o.size = data_.o.capacity = count; + } + + //! Initialize this value as constant string, without calling destructor. + void SetStringRaw(StringRefType s) RAPIDJSON_NOEXCEPT { + data_.f.flags = kConstStringFlag; + SetStringPointer(s); + data_.s.length = s.length; + } + + //! Initialize this value as copy string with initial data, without calling destructor. + void SetStringRaw(StringRefType s, Allocator& allocator) { + Ch* str = 0; + if (ShortString::Usable(s.length)) { + data_.f.flags = kShortStringFlag; + data_.ss.SetLength(s.length); + str = data_.ss.str; + } else { + data_.f.flags = kCopyStringFlag; + data_.s.length = s.length; + str = static_cast(allocator.Malloc((s.length + 1) * sizeof(Ch))); + SetStringPointer(str); + } + std::memcpy(str, s, s.length * sizeof(Ch)); + str[s.length] = '\0'; + } + + //! Assignment without calling destructor + void RawAssign(GenericValue& rhs) RAPIDJSON_NOEXCEPT { + data_ = rhs.data_; + // data_.f.flags = rhs.data_.f.flags; + rhs.data_.f.flags = kNullFlag; + } + + template + bool StringEqual(const GenericValue& rhs) const { + RAPIDJSON_ASSERT(IsString()); + RAPIDJSON_ASSERT(rhs.IsString()); + + const SizeType len1 = GetStringLength(); + const SizeType len2 = rhs.GetStringLength(); + if(len1 != len2) { return false; } + + const Ch* const str1 = GetString(); + const Ch* const str2 = rhs.GetString(); + if(str1 == str2) { return true; } // fast path for constant string + + return (std::memcmp(str1, str2, sizeof(Ch) * len1) == 0); + } + + Data data_; +}; + +//! GenericValue with UTF8 encoding +typedef GenericValue > Value; + +/////////////////////////////////////////////////////////////////////////////// +// GenericDocument + +//! A document for parsing JSON text as DOM. +/*! + \note implements Handler concept + \tparam Encoding Encoding for both parsing and string storage. + \tparam Allocator Allocator for allocating memory for the DOM + \tparam StackAllocator Allocator for allocating memory for stack during parsing. + \warning Although GenericDocument inherits from GenericValue, the API does \b not provide any virtual functions, especially no virtual destructor. To avoid memory leaks, do not \c delete a GenericDocument object via a pointer to a GenericValue. +*/ +template , typename StackAllocator = CrtAllocator> +class GenericDocument : public GenericValue { +public: + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef GenericValue ValueType; //!< Value type of the document. + typedef Allocator AllocatorType; //!< Allocator type from template parameter. + + //! Constructor + /*! Creates an empty document of specified type. + \param type Mandatory type of object to create. + \param allocator Optional allocator for allocating memory. + \param stackCapacity Optional initial capacity of stack in bytes. + \param stackAllocator Optional allocator for allocating memory for stack. + */ + explicit GenericDocument(Type type, Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0) : + GenericValue(type), allocator_(allocator), ownAllocator_(0), stack_(stackAllocator, stackCapacity), parseResult_() + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + } + + //! Constructor + /*! Creates an empty document which type is Null. + \param allocator Optional allocator for allocating memory. + \param stackCapacity Optional initial capacity of stack in bytes. + \param stackAllocator Optional allocator for allocating memory for stack. + */ + GenericDocument(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0) : + allocator_(allocator), ownAllocator_(0), stack_(stackAllocator, stackCapacity), parseResult_() + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericDocument(GenericDocument&& rhs) RAPIDJSON_NOEXCEPT + : ValueType(std::forward(rhs)), // explicit cast to avoid prohibited move from Document + allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + stack_(std::move(rhs.stack_)), + parseResult_(rhs.parseResult_) + { + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.parseResult_ = ParseResult(); + } +#endif + + ~GenericDocument() { + Destroy(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move assignment in C++11 + GenericDocument& operator=(GenericDocument&& rhs) RAPIDJSON_NOEXCEPT + { + // The cast to ValueType is necessary here, because otherwise it would + // attempt to call GenericValue's templated assignment operator. + ValueType::operator=(std::forward(rhs)); + + // Calling the destructor here would prematurely call stack_'s destructor + Destroy(); + + allocator_ = rhs.allocator_; + ownAllocator_ = rhs.ownAllocator_; + stack_ = std::move(rhs.stack_); + parseResult_ = rhs.parseResult_; + + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.parseResult_ = ParseResult(); + + return *this; + } +#endif + + //! Exchange the contents of this document with those of another. + /*! + \param rhs Another document. + \note Constant complexity. + \see GenericValue::Swap + */ + GenericDocument& Swap(GenericDocument& rhs) RAPIDJSON_NOEXCEPT { + ValueType::Swap(rhs); + stack_.Swap(rhs.stack_); + internal::Swap(allocator_, rhs.allocator_); + internal::Swap(ownAllocator_, rhs.ownAllocator_); + internal::Swap(parseResult_, rhs.parseResult_); + return *this; + } + + // Allow Swap with ValueType. + // Refer to Effective C++ 3rd Edition/Item 33: Avoid hiding inherited names. + using ValueType::Swap; + + //! free-standing swap function helper + /*! + Helper function to enable support for common swap implementation pattern based on \c std::swap: + \code + void swap(MyClass& a, MyClass& b) { + using std::swap; + swap(a.doc, b.doc); + // ... + } + \endcode + \see Swap() + */ + friend inline void swap(GenericDocument& a, GenericDocument& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } + + //! Populate this document by a generator which produces SAX events. + /*! \tparam Generator A functor with bool f(Handler) prototype. + \param g Generator functor which sends SAX events to the parameter. + \return The document itself for fluent API. + */ + template + GenericDocument& Populate(Generator& g) { + ClearStackOnExit scope(*this); + if (g(*this)) { + RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object + ValueType::operator=(*stack_.template Pop(1));// Move value from stack to document + } + return *this; + } + + //!@name Parse from stream + //!@{ + + //! Parse JSON text from an input stream (with Encoding conversion) + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam SourceEncoding Encoding of input stream + \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + GenericReader reader( + stack_.HasAllocator() ? &stack_.GetAllocator() : 0); + ClearStackOnExit scope(*this); + parseResult_ = reader.template Parse(is, *this); + if (parseResult_) { + RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object + ValueType::operator=(*stack_.template Pop(1));// Move value from stack to document + } + return *this; + } + + //! Parse JSON text from an input stream + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + return ParseStream(is); + } + + //! Parse JSON text from an input stream (with \ref kParseDefaultFlags) + /*! \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + return ParseStream(is); + } + //!@} + + //!@name Parse in-place from mutable string + //!@{ + + //! Parse JSON text from a mutable string + /*! \tparam parseFlags Combination of \ref ParseFlag. + \param str Mutable zero-terminated string to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseInsitu(Ch* str) { + GenericInsituStringStream s(str); + return ParseStream(s); + } + + //! Parse JSON text from a mutable string (with \ref kParseDefaultFlags) + /*! \param str Mutable zero-terminated string to be parsed. + \return The document itself for fluent API. + */ + GenericDocument& ParseInsitu(Ch* str) { + return ParseInsitu(str); + } + //!@} + + //!@name Parse from read-only string + //!@{ + + //! Parse JSON text from a read-only string (with Encoding conversion) + /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). + \tparam SourceEncoding Transcoding from input Encoding + \param str Read-only zero-terminated string to be parsed. + */ + template + GenericDocument& Parse(const typename SourceEncoding::Ch* str) { + RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); + GenericStringStream s(str); + return ParseStream(s); + } + + //! Parse JSON text from a read-only string + /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). + \param str Read-only zero-terminated string to be parsed. + */ + template + GenericDocument& Parse(const Ch* str) { + return Parse(str); + } + + //! Parse JSON text from a read-only string (with \ref kParseDefaultFlags) + /*! \param str Read-only zero-terminated string to be parsed. + */ + GenericDocument& Parse(const Ch* str) { + return Parse(str); + } + + template + GenericDocument& Parse(const typename SourceEncoding::Ch* str, size_t length) { + RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); + MemoryStream ms(reinterpret_cast(str), length * sizeof(typename SourceEncoding::Ch)); + EncodedInputStream is(ms); + ParseStream(is); + return *this; + } + + template + GenericDocument& Parse(const Ch* str, size_t length) { + return Parse(str, length); + } + + GenericDocument& Parse(const Ch* str, size_t length) { + return Parse(str, length); + } + +#if RAPIDJSON_HAS_STDSTRING + template + GenericDocument& Parse(const std::basic_string& str) { + // c_str() is constant complexity according to standard. Should be faster than Parse(const char*, size_t) + return Parse(str.c_str()); + } + + template + GenericDocument& Parse(const std::basic_string& str) { + return Parse(str.c_str()); + } + + GenericDocument& Parse(const std::basic_string& str) { + return Parse(str); + } +#endif // RAPIDJSON_HAS_STDSTRING + + //!@} + + //!@name Handling parse errors + //!@{ + + //! Whether a parse error has occurred in the last parsing. + bool HasParseError() const { return parseResult_.IsError(); } + + //! Get the \ref ParseErrorCode of last parsing. + ParseErrorCode GetParseError() const { return parseResult_.Code(); } + + //! Get the position of last parsing error in input, 0 otherwise. + size_t GetErrorOffset() const { return parseResult_.Offset(); } + + //! Implicit conversion to get the last parse result +#ifndef __clang // -Wdocumentation + /*! \return \ref ParseResult of the last parse operation + + \code + Document doc; + ParseResult ok = doc.Parse(json); + if (!ok) + printf( "JSON parse error: %s (%u)\n", GetParseError_En(ok.Code()), ok.Offset()); + \endcode + */ +#endif + operator ParseResult() const { return parseResult_; } + //!@} + + //! Get the allocator of this document. + Allocator& GetAllocator() { + RAPIDJSON_ASSERT(allocator_); + return *allocator_; + } + + //! Get the capacity of stack in bytes. + size_t GetStackCapacity() const { return stack_.GetCapacity(); } + +private: + // clear stack on any exit from ParseStream, e.g. due to exception + struct ClearStackOnExit { + explicit ClearStackOnExit(GenericDocument& d) : d_(d) {} + ~ClearStackOnExit() { d_.ClearStack(); } + private: + ClearStackOnExit(const ClearStackOnExit&); + ClearStackOnExit& operator=(const ClearStackOnExit&); + GenericDocument& d_; + }; + + // callers of the following private Handler functions + // template friend class GenericReader; // for parsing + template friend class GenericValue; // for deep copying + +public: + // Implementation of Handler + bool Null() { new (stack_.template Push()) ValueType(); return true; } + bool Bool(bool b) { new (stack_.template Push()) ValueType(b); return true; } + bool Int(int i) { new (stack_.template Push()) ValueType(i); return true; } + bool Uint(unsigned i) { new (stack_.template Push()) ValueType(i); return true; } + bool Int64(int64_t i) { new (stack_.template Push()) ValueType(i); return true; } + bool Uint64(uint64_t i) { new (stack_.template Push()) ValueType(i); return true; } + bool Double(double d) { new (stack_.template Push()) ValueType(d); return true; } + + bool RawNumber(const Ch* str, SizeType length, bool copy) { + if (copy) + new (stack_.template Push()) ValueType(str, length, GetAllocator()); + else + new (stack_.template Push()) ValueType(str, length); + return true; + } + + bool String(const Ch* str, SizeType length, bool copy) { + if (copy) + new (stack_.template Push()) ValueType(str, length, GetAllocator()); + else + new (stack_.template Push()) ValueType(str, length); + return true; + } + + bool StartObject() { new (stack_.template Push()) ValueType(kObjectType); return true; } + + bool Key(const Ch* str, SizeType length, bool copy) { return String(str, length, copy); } + + bool EndObject(SizeType memberCount) { + typename ValueType::Member* members = stack_.template Pop(memberCount); + stack_.template Top()->SetObjectRaw(members, memberCount, GetAllocator()); + return true; + } + + bool StartArray() { new (stack_.template Push()) ValueType(kArrayType); return true; } + + bool EndArray(SizeType elementCount) { + ValueType* elements = stack_.template Pop(elementCount); + stack_.template Top()->SetArrayRaw(elements, elementCount, GetAllocator()); + return true; + } + +private: + //! Prohibit copying + GenericDocument(const GenericDocument&); + //! Prohibit assignment + GenericDocument& operator=(const GenericDocument&); + + void ClearStack() { + if (Allocator::kNeedFree) + while (stack_.GetSize() > 0) // Here assumes all elements in stack array are GenericValue (Member is actually 2 GenericValue objects) + (stack_.template Pop(1))->~ValueType(); + else + stack_.Clear(); + stack_.ShrinkToFit(); + } + + void Destroy() { + RAPIDJSON_DELETE(ownAllocator_); + } + + static const size_t kDefaultStackCapacity = 1024; + Allocator* allocator_; + Allocator* ownAllocator_; + internal::Stack stack_; + ParseResult parseResult_; +}; + +//! GenericDocument with UTF8 encoding +typedef GenericDocument > Document; + +//! Helper class for accessing Value of array type. +/*! + Instance of this helper class is obtained by \c GenericValue::GetArray(). + In addition to all APIs for array type, it provides range-based for loop if \c RAPIDJSON_HAS_CXX11_RANGE_FOR=1. +*/ +template +class GenericArray { +public: + typedef GenericArray ConstArray; + typedef GenericArray Array; + typedef ValueT PlainType; + typedef typename internal::MaybeAddConst::Type ValueType; + typedef ValueType* ValueIterator; // This may be const or non-const iterator + typedef const ValueT* ConstValueIterator; + typedef typename ValueType::AllocatorType AllocatorType; + typedef typename ValueType::StringRefType StringRefType; + + template + friend class GenericValue; + + GenericArray(const GenericArray& rhs) : value_(rhs.value_) {} + GenericArray& operator=(const GenericArray& rhs) { value_ = rhs.value_; return *this; } + ~GenericArray() {} + + SizeType Size() const { return value_.Size(); } + SizeType Capacity() const { return value_.Capacity(); } + bool Empty() const { return value_.Empty(); } + void Clear() const { value_.Clear(); } + ValueType& operator[](SizeType index) const { return value_[index]; } + ValueIterator Begin() const { return value_.Begin(); } + ValueIterator End() const { return value_.End(); } + GenericArray Reserve(SizeType newCapacity, AllocatorType &allocator) const { value_.Reserve(newCapacity, allocator); return *this; } + GenericArray PushBack(ValueType& value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericArray PushBack(ValueType&& value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericArray PushBack(StringRefType value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (const GenericArray&)) PushBack(T value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } + GenericArray PopBack() const { value_.PopBack(); return *this; } + ValueIterator Erase(ConstValueIterator pos) const { return value_.Erase(pos); } + ValueIterator Erase(ConstValueIterator first, ConstValueIterator last) const { return value_.Erase(first, last); } + +#if RAPIDJSON_HAS_CXX11_RANGE_FOR + ValueIterator begin() const { return value_.Begin(); } + ValueIterator end() const { return value_.End(); } +#endif + +private: + GenericArray(); + GenericArray(ValueType& value) : value_(value) {} + ValueType& value_; +}; + +//! Helper class for accessing Value of object type. +/*! + Instance of this helper class is obtained by \c GenericValue::GetObject(). + In addition to all APIs for array type, it provides range-based for loop if \c RAPIDJSON_HAS_CXX11_RANGE_FOR=1. +*/ +template +class GenericObject { +public: + typedef GenericObject ConstObject; + typedef GenericObject Object; + typedef ValueT PlainType; + typedef typename internal::MaybeAddConst::Type ValueType; + typedef GenericMemberIterator MemberIterator; // This may be const or non-const iterator + typedef GenericMemberIterator ConstMemberIterator; + typedef typename ValueType::AllocatorType AllocatorType; + typedef typename ValueType::StringRefType StringRefType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename ValueType::Ch Ch; + + template + friend class GenericValue; + + GenericObject(const GenericObject& rhs) : value_(rhs.value_) {} + GenericObject& operator=(const GenericObject& rhs) { value_ = rhs.value_; return *this; } + ~GenericObject() {} + + SizeType MemberCount() const { return value_.MemberCount(); } + SizeType MemberCapacity() const { return value_.MemberCapacity(); } + bool ObjectEmpty() const { return value_.ObjectEmpty(); } + template ValueType& operator[](T* name) const { return value_[name]; } + template ValueType& operator[](const GenericValue& name) const { return value_[name]; } +#if RAPIDJSON_HAS_STDSTRING + ValueType& operator[](const std::basic_string& name) const { return value_[name]; } +#endif + MemberIterator MemberBegin() const { return value_.MemberBegin(); } + MemberIterator MemberEnd() const { return value_.MemberEnd(); } + GenericObject MemberReserve(SizeType newCapacity, AllocatorType &allocator) const { value_.MemberReserve(newCapacity, allocator); return *this; } + bool HasMember(const Ch* name) const { return value_.HasMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool HasMember(const std::basic_string& name) const { return value_.HasMember(name); } +#endif + template bool HasMember(const GenericValue& name) const { return value_.HasMember(name); } + MemberIterator FindMember(const Ch* name) const { return value_.FindMember(name); } + template MemberIterator FindMember(const GenericValue& name) const { return value_.FindMember(name); } +#if RAPIDJSON_HAS_STDSTRING + MemberIterator FindMember(const std::basic_string& name) const { return value_.FindMember(name); } +#endif + GenericObject AddMember(ValueType& name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType& name, StringRefType value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#if RAPIDJSON_HAS_STDSTRING + GenericObject AddMember(ValueType& name, std::basic_string& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#endif + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) AddMember(ValueType& name, T value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericObject AddMember(ValueType&& name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType&& name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType& name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(StringRefType name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericObject AddMember(StringRefType name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(StringRefType name, StringRefType value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericObject)) AddMember(StringRefType name, T value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + void RemoveAllMembers() { value_.RemoveAllMembers(); } + bool RemoveMember(const Ch* name) const { return value_.RemoveMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool RemoveMember(const std::basic_string& name) const { return value_.RemoveMember(name); } +#endif + template bool RemoveMember(const GenericValue& name) const { return value_.RemoveMember(name); } + MemberIterator RemoveMember(MemberIterator m) const { return value_.RemoveMember(m); } + MemberIterator EraseMember(ConstMemberIterator pos) const { return value_.EraseMember(pos); } + MemberIterator EraseMember(ConstMemberIterator first, ConstMemberIterator last) const { return value_.EraseMember(first, last); } + bool EraseMember(const Ch* name) const { return value_.EraseMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool EraseMember(const std::basic_string& name) const { return EraseMember(ValueType(StringRef(name))); } +#endif + template bool EraseMember(const GenericValue& name) const { return value_.EraseMember(name); } + +#if RAPIDJSON_HAS_CXX11_RANGE_FOR + MemberIterator begin() const { return value_.MemberBegin(); } + MemberIterator end() const { return value_.MemberEnd(); } +#endif + +private: + GenericObject(); + GenericObject(ValueType& value) : value_(value) {} + ValueType& value_; +}; + +RAPIDJSON_NAMESPACE_END +RAPIDJSON_DIAG_POP + +#endif // RAPIDJSON_DOCUMENT_H_ diff --git a/util/xpack/rapidjson/encodedstream.h b/util/xpack/rapidjson/encodedstream.h new file mode 100755 index 0000000..34fea4e --- /dev/null +++ b/util/xpack/rapidjson/encodedstream.h @@ -0,0 +1,299 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ENCODEDSTREAM_H_ +#define RAPIDJSON_ENCODEDSTREAM_H_ + +#include "stream.h" +#include "memorystream.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Input byte stream wrapper with a statically bound encoding. +/*! + \tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE. + \tparam InputByteStream Type of input byte stream. For example, FileReadStream. +*/ +template +class EncodedInputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); +public: + typedef typename Encoding::Ch Ch; + + EncodedInputStream(InputByteStream& is) : is_(is) { + current_ = Encoding::TakeBOM(is_); + } + + Ch Peek() const { return current_; } + Ch Take() { Ch c = current_; current_ = Encoding::Take(is_); return c; } + size_t Tell() const { return is_.Tell(); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + EncodedInputStream(const EncodedInputStream&); + EncodedInputStream& operator=(const EncodedInputStream&); + + InputByteStream& is_; + Ch current_; +}; + +//! Specialized for UTF8 MemoryStream. +template <> +class EncodedInputStream, MemoryStream> { +public: + typedef UTF8<>::Ch Ch; + + EncodedInputStream(MemoryStream& is) : is_(is) { + if (static_cast(is_.Peek()) == 0xEFu) is_.Take(); + if (static_cast(is_.Peek()) == 0xBBu) is_.Take(); + if (static_cast(is_.Peek()) == 0xBFu) is_.Take(); + } + Ch Peek() const { return is_.Peek(); } + Ch Take() { return is_.Take(); } + size_t Tell() const { return is_.Tell(); } + + // Not implemented + void Put(Ch) {} + void Flush() {} + Ch* PutBegin() { return 0; } + size_t PutEnd(Ch*) { return 0; } + + MemoryStream& is_; + +private: + EncodedInputStream(const EncodedInputStream&); + EncodedInputStream& operator=(const EncodedInputStream&); +}; + +//! Output byte stream wrapper with statically bound encoding. +/*! + \tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE. + \tparam OutputByteStream Type of input byte stream. For example, FileWriteStream. +*/ +template +class EncodedOutputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); +public: + typedef typename Encoding::Ch Ch; + + EncodedOutputStream(OutputByteStream& os, bool putBOM = true) : os_(os) { + if (putBOM) + Encoding::PutBOM(os_); + } + + void Put(Ch c) { Encoding::Put(os_, c); } + void Flush() { os_.Flush(); } + + // Not implemented + Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;} + Ch Take() { RAPIDJSON_ASSERT(false); return 0;} + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + EncodedOutputStream(const EncodedOutputStream&); + EncodedOutputStream& operator=(const EncodedOutputStream&); + + OutputByteStream& os_; +}; + +#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8::x, UTF16LE::x, UTF16BE::x, UTF32LE::x, UTF32BE::x + +//! Input stream wrapper with dynamically bound encoding and automatic encoding detection. +/*! + \tparam CharType Type of character for reading. + \tparam InputByteStream type of input byte stream to be wrapped. +*/ +template +class AutoUTFInputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); +public: + typedef CharType Ch; + + //! Constructor. + /*! + \param is input stream to be wrapped. + \param type UTF encoding type if it is not detected from the stream. + */ + AutoUTFInputStream(InputByteStream& is, UTFType type = kUTF8) : is_(&is), type_(type), hasBOM_(false) { + RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE); + DetectType(); + static const TakeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Take) }; + takeFunc_ = f[type_]; + current_ = takeFunc_(*is_); + } + + UTFType GetType() const { return type_; } + bool HasBOM() const { return hasBOM_; } + + Ch Peek() const { return current_; } + Ch Take() { Ch c = current_; current_ = takeFunc_(*is_); return c; } + size_t Tell() const { return is_->Tell(); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + AutoUTFInputStream(const AutoUTFInputStream&); + AutoUTFInputStream& operator=(const AutoUTFInputStream&); + + // Detect encoding type with BOM or RFC 4627 + void DetectType() { + // BOM (Byte Order Mark): + // 00 00 FE FF UTF-32BE + // FF FE 00 00 UTF-32LE + // FE FF UTF-16BE + // FF FE UTF-16LE + // EF BB BF UTF-8 + + const unsigned char* c = reinterpret_cast(is_->Peek4()); + if (!c) + return; + + unsigned bom = static_cast(c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24)); + hasBOM_ = false; + if (bom == 0xFFFE0000) { type_ = kUTF32BE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); } + else if (bom == 0x0000FEFF) { type_ = kUTF32LE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); } + else if ((bom & 0xFFFF) == 0xFFFE) { type_ = kUTF16BE; hasBOM_ = true; is_->Take(); is_->Take(); } + else if ((bom & 0xFFFF) == 0xFEFF) { type_ = kUTF16LE; hasBOM_ = true; is_->Take(); is_->Take(); } + else if ((bom & 0xFFFFFF) == 0xBFBBEF) { type_ = kUTF8; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); } + + // RFC 4627: Section 3 + // "Since the first two characters of a JSON text will always be ASCII + // characters [RFC0020], it is possible to determine whether an octet + // stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking + // at the pattern of nulls in the first four octets." + // 00 00 00 xx UTF-32BE + // 00 xx 00 xx UTF-16BE + // xx 00 00 00 UTF-32LE + // xx 00 xx 00 UTF-16LE + // xx xx xx xx UTF-8 + + if (!hasBOM_) { + int pattern = (c[0] ? 1 : 0) | (c[1] ? 2 : 0) | (c[2] ? 4 : 0) | (c[3] ? 8 : 0); + switch (pattern) { + case 0x08: type_ = kUTF32BE; break; + case 0x0A: type_ = kUTF16BE; break; + case 0x01: type_ = kUTF32LE; break; + case 0x05: type_ = kUTF16LE; break; + case 0x0F: type_ = kUTF8; break; + default: break; // Use type defined by user. + } + } + + // Runtime check whether the size of character type is sufficient. It only perform checks with assertion. + if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2); + if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4); + } + + typedef Ch (*TakeFunc)(InputByteStream& is); + InputByteStream* is_; + UTFType type_; + Ch current_; + TakeFunc takeFunc_; + bool hasBOM_; +}; + +//! Output stream wrapper with dynamically bound encoding and automatic encoding detection. +/*! + \tparam CharType Type of character for writing. + \tparam OutputByteStream type of output byte stream to be wrapped. +*/ +template +class AutoUTFOutputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); +public: + typedef CharType Ch; + + //! Constructor. + /*! + \param os output stream to be wrapped. + \param type UTF encoding type. + \param putBOM Whether to write BOM at the beginning of the stream. + */ + AutoUTFOutputStream(OutputByteStream& os, UTFType type, bool putBOM) : os_(&os), type_(type) { + RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE); + + // Runtime check whether the size of character type is sufficient. It only perform checks with assertion. + if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2); + if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4); + + static const PutFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Put) }; + putFunc_ = f[type_]; + + if (putBOM) + PutBOM(); + } + + UTFType GetType() const { return type_; } + + void Put(Ch c) { putFunc_(*os_, c); } + void Flush() { os_->Flush(); } + + // Not implemented + Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;} + Ch Take() { RAPIDJSON_ASSERT(false); return 0;} + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + AutoUTFOutputStream(const AutoUTFOutputStream&); + AutoUTFOutputStream& operator=(const AutoUTFOutputStream&); + + void PutBOM() { + typedef void (*PutBOMFunc)(OutputByteStream&); + static const PutBOMFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(PutBOM) }; + f[type_](*os_); + } + + typedef void (*PutFunc)(OutputByteStream&, Ch); + + OutputByteStream* os_; + UTFType type_; + PutFunc putFunc_; +}; + +#undef RAPIDJSON_ENCODINGS_FUNC + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/util/xpack/rapidjson/encodings.h b/util/xpack/rapidjson/encodings.h new file mode 100755 index 0000000..d5b107f --- /dev/null +++ b/util/xpack/rapidjson/encodings.h @@ -0,0 +1,716 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ENCODINGS_H_ +#define RAPIDJSON_ENCODINGS_H_ + +#include "rapidjson.h" + +#if defined(_MSC_VER) && !defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4244) // conversion from 'type1' to 'type2', possible loss of data +RAPIDJSON_DIAG_OFF(4702) // unreachable code +#elif defined(__GNUC__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +RAPIDJSON_DIAG_OFF(overflow) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Encoding + +/*! \class rapidjson::Encoding + \brief Concept for encoding of Unicode characters. + +\code +concept Encoding { + typename Ch; //! Type of character. A "character" is actually a code unit in unicode's definition. + + enum { supportUnicode = 1 }; // or 0 if not supporting unicode + + //! \brief Encode a Unicode codepoint to an output stream. + //! \param os Output stream. + //! \param codepoint An unicode codepoint, ranging from 0x0 to 0x10FFFF inclusively. + template + static void Encode(OutputStream& os, unsigned codepoint); + + //! \brief Decode a Unicode codepoint from an input stream. + //! \param is Input stream. + //! \param codepoint Output of the unicode codepoint. + //! \return true if a valid codepoint can be decoded from the stream. + template + static bool Decode(InputStream& is, unsigned* codepoint); + + //! \brief Validate one Unicode codepoint from an encoded stream. + //! \param is Input stream to obtain codepoint. + //! \param os Output for copying one codepoint. + //! \return true if it is valid. + //! \note This function just validating and copying the codepoint without actually decode it. + template + static bool Validate(InputStream& is, OutputStream& os); + + // The following functions are deal with byte streams. + + //! Take a character from input byte stream, skip BOM if exist. + template + static CharType TakeBOM(InputByteStream& is); + + //! Take a character from input byte stream. + template + static Ch Take(InputByteStream& is); + + //! Put BOM to output byte stream. + template + static void PutBOM(OutputByteStream& os); + + //! Put a character to output byte stream. + template + static void Put(OutputByteStream& os, Ch c); +}; +\endcode +*/ + +/////////////////////////////////////////////////////////////////////////////// +// UTF8 + +//! UTF-8 encoding. +/*! http://en.wikipedia.org/wiki/UTF-8 + http://tools.ietf.org/html/rfc3629 + \tparam CharType Code unit for storing 8-bit UTF-8 data. Default is char. + \note implements Encoding concept +*/ +template +struct UTF8 { + typedef CharType Ch; + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + if (codepoint <= 0x7F) + os.Put(static_cast(codepoint & 0xFF)); + else if (codepoint <= 0x7FF) { + os.Put(static_cast(0xC0 | ((codepoint >> 6) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint & 0x3F)))); + } + else if (codepoint <= 0xFFFF) { + os.Put(static_cast(0xE0 | ((codepoint >> 12) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + os.Put(static_cast(0x80 | (codepoint & 0x3F))); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + os.Put(static_cast(0xF0 | ((codepoint >> 18) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint >> 12) & 0x3F))); + os.Put(static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + os.Put(static_cast(0x80 | (codepoint & 0x3F))); + } + } + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + if (codepoint <= 0x7F) + PutUnsafe(os, static_cast(codepoint & 0xFF)); + else if (codepoint <= 0x7FF) { + PutUnsafe(os, static_cast(0xC0 | ((codepoint >> 6) & 0xFF))); + PutUnsafe(os, static_cast(0x80 | ((codepoint & 0x3F)))); + } + else if (codepoint <= 0xFFFF) { + PutUnsafe(os, static_cast(0xE0 | ((codepoint >> 12) & 0xFF))); + PutUnsafe(os, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + PutUnsafe(os, static_cast(0x80 | (codepoint & 0x3F))); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + PutUnsafe(os, static_cast(0xF0 | ((codepoint >> 18) & 0xFF))); + PutUnsafe(os, static_cast(0x80 | ((codepoint >> 12) & 0x3F))); + PutUnsafe(os, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + PutUnsafe(os, static_cast(0x80 | (codepoint & 0x3F))); + } + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { +#define RAPIDJSON_COPY() c = is.Take(); *codepoint = (*codepoint << 6) | (static_cast(c) & 0x3Fu) +#define RAPIDJSON_TRANS(mask) result &= ((GetRange(static_cast(c)) & mask) != 0) +#define RAPIDJSON_TAIL() RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x70) + typename InputStream::Ch c = is.Take(); + if (!(c & 0x80)) { + *codepoint = static_cast(c); + return true; + } + + unsigned char type = GetRange(static_cast(c)); + if (type >= 32) { + *codepoint = 0; + } else { + *codepoint = (0xFFu >> type) & static_cast(c); + } + bool result = true; + switch (type) { + case 2: RAPIDJSON_TAIL(); return result; + case 3: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 4: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x50); RAPIDJSON_TAIL(); return result; + case 5: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x10); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 6: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 10: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x20); RAPIDJSON_TAIL(); return result; + case 11: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x60); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + default: return false; + } +#undef RAPIDJSON_COPY +#undef RAPIDJSON_TRANS +#undef RAPIDJSON_TAIL + } + + template + static bool Validate(InputStream& is, OutputStream& os) { +#define RAPIDJSON_COPY() os.Put(c = is.Take()) +#define RAPIDJSON_TRANS(mask) result &= ((GetRange(static_cast(c)) & mask) != 0) +#define RAPIDJSON_TAIL() RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x70) + Ch c; + RAPIDJSON_COPY(); + if (!(c & 0x80)) + return true; + + bool result = true; + switch (GetRange(static_cast(c))) { + case 2: RAPIDJSON_TAIL(); return result; + case 3: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 4: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x50); RAPIDJSON_TAIL(); return result; + case 5: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x10); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 6: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 10: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x20); RAPIDJSON_TAIL(); return result; + case 11: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x60); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + default: return false; + } +#undef RAPIDJSON_COPY +#undef RAPIDJSON_TRANS +#undef RAPIDJSON_TAIL + } + + static unsigned char GetRange(unsigned char c) { + // Referring to DFA of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + // With new mapping 1 -> 0x10, 7 -> 0x20, 9 -> 0x40, such that AND operation can test multiple types. + static const unsigned char type[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, + 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + }; + return type[c]; + } + + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + typename InputByteStream::Ch c = Take(is); + if (static_cast(c) != 0xEFu) return c; + c = is.Take(); + if (static_cast(c) != 0xBBu) return c; + c = is.Take(); + if (static_cast(c) != 0xBFu) return c; + c = is.Take(); + return c; + } + + template + static Ch Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + return static_cast(is.Take()); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xEFu)); + os.Put(static_cast(0xBBu)); + os.Put(static_cast(0xBFu)); + } + + template + static void Put(OutputByteStream& os, Ch c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(c)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// UTF16 + +//! UTF-16 encoding. +/*! http://en.wikipedia.org/wiki/UTF-16 + http://tools.ietf.org/html/rfc2781 + \tparam CharType Type for storing 16-bit UTF-16 data. Default is wchar_t. C++11 may use char16_t instead. + \note implements Encoding concept + + \note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness. + For streaming, use UTF16LE and UTF16BE, which handle endianness. +*/ +template +struct UTF16 { + typedef CharType Ch; + RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 2); + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + if (codepoint <= 0xFFFF) { + RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair + os.Put(static_cast(codepoint)); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + unsigned v = codepoint - 0x10000; + os.Put(static_cast((v >> 10) | 0xD800)); + os.Put(static_cast((v & 0x3FF) | 0xDC00)); + } + } + + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + if (codepoint <= 0xFFFF) { + RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair + PutUnsafe(os, static_cast(codepoint)); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + unsigned v = codepoint - 0x10000; + PutUnsafe(os, static_cast((v >> 10) | 0xD800)); + PutUnsafe(os, static_cast((v & 0x3FF) | 0xDC00)); + } + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2); + typename InputStream::Ch c = is.Take(); + if (c < 0xD800 || c > 0xDFFF) { + *codepoint = static_cast(c); + return true; + } + else if (c <= 0xDBFF) { + *codepoint = (static_cast(c) & 0x3FF) << 10; + c = is.Take(); + *codepoint |= (static_cast(c) & 0x3FF); + *codepoint += 0x10000; + return c >= 0xDC00 && c <= 0xDFFF; + } + return false; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2); + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + typename InputStream::Ch c; + os.Put(static_cast(c = is.Take())); + if (c < 0xD800 || c > 0xDFFF) + return true; + else if (c <= 0xDBFF) { + os.Put(c = is.Take()); + return c >= 0xDC00 && c <= 0xDFFF; + } + return false; + } +}; + +//! UTF-16 little endian encoding. +template +struct UTF16LE : UTF16 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0xFEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(is.Take()); + c |= static_cast(static_cast(is.Take())) << 8; + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xFFu)); + os.Put(static_cast(0xFEu)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(static_cast(c) & 0xFFu)); + os.Put(static_cast((static_cast(c) >> 8) & 0xFFu)); + } +}; + +//! UTF-16 big endian encoding. +template +struct UTF16BE : UTF16 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0xFEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(static_cast(is.Take())) << 8; + c |= static_cast(static_cast(is.Take())); + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xFEu)); + os.Put(static_cast(0xFFu)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast((static_cast(c) >> 8) & 0xFFu)); + os.Put(static_cast(static_cast(c) & 0xFFu)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// UTF32 + +//! UTF-32 encoding. +/*! http://en.wikipedia.org/wiki/UTF-32 + \tparam CharType Type for storing 32-bit UTF-32 data. Default is unsigned. C++11 may use char32_t instead. + \note implements Encoding concept + + \note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness. + For streaming, use UTF32LE and UTF32BE, which handle endianness. +*/ +template +struct UTF32 { + typedef CharType Ch; + RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 4); + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4); + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + os.Put(codepoint); + } + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4); + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + PutUnsafe(os, codepoint); + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4); + Ch c = is.Take(); + *codepoint = c; + return c <= 0x10FFFF; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4); + Ch c; + os.Put(c = is.Take()); + return c <= 0x10FFFF; + } +}; + +//! UTF-32 little endian enocoding. +template +struct UTF32LE : UTF32 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0x0000FEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(is.Take()); + c |= static_cast(static_cast(is.Take())) << 8; + c |= static_cast(static_cast(is.Take())) << 16; + c |= static_cast(static_cast(is.Take())) << 24; + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xFFu)); + os.Put(static_cast(0xFEu)); + os.Put(static_cast(0x00u)); + os.Put(static_cast(0x00u)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(c & 0xFFu)); + os.Put(static_cast((c >> 8) & 0xFFu)); + os.Put(static_cast((c >> 16) & 0xFFu)); + os.Put(static_cast((c >> 24) & 0xFFu)); + } +}; + +//! UTF-32 big endian encoding. +template +struct UTF32BE : UTF32 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0x0000FEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(static_cast(is.Take())) << 24; + c |= static_cast(static_cast(is.Take())) << 16; + c |= static_cast(static_cast(is.Take())) << 8; + c |= static_cast(static_cast(is.Take())); + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0x00u)); + os.Put(static_cast(0x00u)); + os.Put(static_cast(0xFEu)); + os.Put(static_cast(0xFFu)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast((c >> 24) & 0xFFu)); + os.Put(static_cast((c >> 16) & 0xFFu)); + os.Put(static_cast((c >> 8) & 0xFFu)); + os.Put(static_cast(c & 0xFFu)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// ASCII + +//! ASCII encoding. +/*! http://en.wikipedia.org/wiki/ASCII + \tparam CharType Code unit for storing 7-bit ASCII data. Default is char. + \note implements Encoding concept +*/ +template +struct ASCII { + typedef CharType Ch; + + enum { supportUnicode = 0 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_ASSERT(codepoint <= 0x7F); + os.Put(static_cast(codepoint & 0xFF)); + } + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_ASSERT(codepoint <= 0x7F); + PutUnsafe(os, static_cast(codepoint & 0xFF)); + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + uint8_t c = static_cast(is.Take()); + *codepoint = c; + return c <= 0X7F; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + uint8_t c = static_cast(is.Take()); + os.Put(static_cast(c)); + return c <= 0x7F; + } + + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + uint8_t c = static_cast(Take(is)); + return static_cast(c); + } + + template + static Ch Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + return static_cast(is.Take()); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + (void)os; + } + + template + static void Put(OutputByteStream& os, Ch c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(c)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// AutoUTF + +//! Runtime-specified UTF encoding type of a stream. +enum UTFType { + kUTF8 = 0, //!< UTF-8. + kUTF16LE = 1, //!< UTF-16 little endian. + kUTF16BE = 2, //!< UTF-16 big endian. + kUTF32LE = 3, //!< UTF-32 little endian. + kUTF32BE = 4 //!< UTF-32 big endian. +}; + +//! Dynamically select encoding according to stream's runtime-specified UTF encoding type. +/*! \note This class can be used with AutoUTFInputtStream and AutoUTFOutputStream, which provides GetType(). +*/ +template +struct AutoUTF { + typedef CharType Ch; + + enum { supportUnicode = 1 }; + +#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8::x, UTF16LE::x, UTF16BE::x, UTF32LE::x, UTF32BE::x + + template + static RAPIDJSON_FORCEINLINE void Encode(OutputStream& os, unsigned codepoint) { + typedef void (*EncodeFunc)(OutputStream&, unsigned); + static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Encode) }; + (*f[os.GetType()])(os, codepoint); + } + + template + static RAPIDJSON_FORCEINLINE void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + typedef void (*EncodeFunc)(OutputStream&, unsigned); + static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(EncodeUnsafe) }; + (*f[os.GetType()])(os, codepoint); + } + + template + static RAPIDJSON_FORCEINLINE bool Decode(InputStream& is, unsigned* codepoint) { + typedef bool (*DecodeFunc)(InputStream&, unsigned*); + static const DecodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Decode) }; + return (*f[is.GetType()])(is, codepoint); + } + + template + static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) { + typedef bool (*ValidateFunc)(InputStream&, OutputStream&); + static const ValidateFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Validate) }; + return (*f[is.GetType()])(is, os); + } + +#undef RAPIDJSON_ENCODINGS_FUNC +}; + +/////////////////////////////////////////////////////////////////////////////// +// Transcoder + +//! Encoding conversion. +template +struct Transcoder { + //! Take one Unicode codepoint from source encoding, convert it to target encoding and put it to the output stream. + template + static RAPIDJSON_FORCEINLINE bool Transcode(InputStream& is, OutputStream& os) { + unsigned codepoint; + if (!SourceEncoding::Decode(is, &codepoint)) + return false; + TargetEncoding::Encode(os, codepoint); + return true; + } + + template + static RAPIDJSON_FORCEINLINE bool TranscodeUnsafe(InputStream& is, OutputStream& os) { + unsigned codepoint; + if (!SourceEncoding::Decode(is, &codepoint)) + return false; + TargetEncoding::EncodeUnsafe(os, codepoint); + return true; + } + + //! Validate one Unicode codepoint from an encoded stream. + template + static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) { + return Transcode(is, os); // Since source/target encoding is different, must transcode. + } +}; + +// Forward declaration. +template +inline void PutUnsafe(Stream& stream, typename Stream::Ch c); + +//! Specialization of Transcoder with same source and target encoding. +template +struct Transcoder { + template + static RAPIDJSON_FORCEINLINE bool Transcode(InputStream& is, OutputStream& os) { + os.Put(is.Take()); // Just copy one code unit. This semantic is different from primary template class. + return true; + } + + template + static RAPIDJSON_FORCEINLINE bool TranscodeUnsafe(InputStream& is, OutputStream& os) { + PutUnsafe(os, is.Take()); // Just copy one code unit. This semantic is different from primary template class. + return true; + } + + template + static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) { + return Encoding::Validate(is, os); // source/target encoding are the same + } +}; + +RAPIDJSON_NAMESPACE_END + +#if defined(__GNUC__) || (defined(_MSC_VER) && !defined(__clang__)) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ENCODINGS_H_ diff --git a/util/xpack/rapidjson/error/en.h b/util/xpack/rapidjson/error/en.h new file mode 100755 index 0000000..5992cb4 --- /dev/null +++ b/util/xpack/rapidjson/error/en.h @@ -0,0 +1,74 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ERROR_EN_H_ +#define RAPIDJSON_ERROR_EN_H_ + +#include "error.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(covered-switch-default) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Maps error code of parsing into error message. +/*! + \ingroup RAPIDJSON_ERRORS + \param parseErrorCode Error code obtained in parsing. + \return the error message. + \note User can make a copy of this function for localization. + Using switch-case is safer for future modification of error codes. +*/ +inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErrorCode) { + switch (parseErrorCode) { + case kParseErrorNone: return RAPIDJSON_ERROR_STRING("No error."); + + case kParseErrorDocumentEmpty: return RAPIDJSON_ERROR_STRING("The document is empty."); + case kParseErrorDocumentRootNotSingular: return RAPIDJSON_ERROR_STRING("The document root must not be followed by other values."); + + case kParseErrorValueInvalid: return RAPIDJSON_ERROR_STRING("Invalid value."); + + case kParseErrorObjectMissName: return RAPIDJSON_ERROR_STRING("Missing a name for object member."); + case kParseErrorObjectMissColon: return RAPIDJSON_ERROR_STRING("Missing a colon after a name of object member."); + case kParseErrorObjectMissCommaOrCurlyBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or '}' after an object member."); + + case kParseErrorArrayMissCommaOrSquareBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or ']' after an array element."); + + case kParseErrorStringUnicodeEscapeInvalidHex: return RAPIDJSON_ERROR_STRING("Incorrect hex digit after \\u escape in string."); + case kParseErrorStringUnicodeSurrogateInvalid: return RAPIDJSON_ERROR_STRING("The surrogate pair in string is invalid."); + case kParseErrorStringEscapeInvalid: return RAPIDJSON_ERROR_STRING("Invalid escape character in string."); + case kParseErrorStringMissQuotationMark: return RAPIDJSON_ERROR_STRING("Missing a closing quotation mark in string."); + case kParseErrorStringInvalidEncoding: return RAPIDJSON_ERROR_STRING("Invalid encoding in string."); + + case kParseErrorNumberTooBig: return RAPIDJSON_ERROR_STRING("Number too big to be stored in double."); + case kParseErrorNumberMissFraction: return RAPIDJSON_ERROR_STRING("Miss fraction part in number."); + case kParseErrorNumberMissExponent: return RAPIDJSON_ERROR_STRING("Miss exponent in number."); + + case kParseErrorTermination: return RAPIDJSON_ERROR_STRING("Terminate parsing due to Handler error."); + case kParseErrorUnspecificSyntaxError: return RAPIDJSON_ERROR_STRING("Unspecific syntax error."); + + default: return RAPIDJSON_ERROR_STRING("Unknown error."); + } +} + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ERROR_EN_H_ diff --git a/util/xpack/rapidjson/error/error.h b/util/xpack/rapidjson/error/error.h new file mode 100755 index 0000000..070c74c --- /dev/null +++ b/util/xpack/rapidjson/error/error.h @@ -0,0 +1,161 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ERROR_ERROR_H_ +#define RAPIDJSON_ERROR_ERROR_H_ + +#include "../rapidjson.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +/*! \file error.h */ + +/*! \defgroup RAPIDJSON_ERRORS RapidJSON error handling */ + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_CHARTYPE + +//! Character type of error messages. +/*! \ingroup RAPIDJSON_ERRORS + The default character type is \c char. + On Windows, user can define this macro as \c TCHAR for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_CHARTYPE +#define RAPIDJSON_ERROR_CHARTYPE char +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_STRING + +//! Macro for converting string literial to \ref RAPIDJSON_ERROR_CHARTYPE[]. +/*! \ingroup RAPIDJSON_ERRORS + By default this conversion macro does nothing. + On Windows, user can define this macro as \c _T(x) for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_STRING +#define RAPIDJSON_ERROR_STRING(x) x +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// ParseErrorCode + +//! Error code of parsing. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericReader::Parse, GenericReader::GetParseErrorCode +*/ +enum ParseErrorCode { + kParseErrorNone = 0, //!< No error. + + kParseErrorDocumentEmpty, //!< The document is empty. + kParseErrorDocumentRootNotSingular, //!< The document root must not follow by other values. + + kParseErrorValueInvalid, //!< Invalid value. + + kParseErrorObjectMissName, //!< Missing a name for object member. + kParseErrorObjectMissColon, //!< Missing a colon after a name of object member. + kParseErrorObjectMissCommaOrCurlyBracket, //!< Missing a comma or '}' after an object member. + + kParseErrorArrayMissCommaOrSquareBracket, //!< Missing a comma or ']' after an array element. + + kParseErrorStringUnicodeEscapeInvalidHex, //!< Incorrect hex digit after \\u escape in string. + kParseErrorStringUnicodeSurrogateInvalid, //!< The surrogate pair in string is invalid. + kParseErrorStringEscapeInvalid, //!< Invalid escape character in string. + kParseErrorStringMissQuotationMark, //!< Missing a closing quotation mark in string. + kParseErrorStringInvalidEncoding, //!< Invalid encoding in string. + + kParseErrorNumberTooBig, //!< Number too big to be stored in double. + kParseErrorNumberMissFraction, //!< Miss fraction part in number. + kParseErrorNumberMissExponent, //!< Miss exponent in number. + + kParseErrorTermination, //!< Parsing was terminated. + kParseErrorUnspecificSyntaxError //!< Unspecific syntax error. +}; + +//! Result of parsing (wraps ParseErrorCode) +/*! + \ingroup RAPIDJSON_ERRORS + \code + Document doc; + ParseResult ok = doc.Parse("[42]"); + if (!ok) { + fprintf(stderr, "JSON parse error: %s (%u)", + GetParseError_En(ok.Code()), ok.Offset()); + exit(EXIT_FAILURE); + } + \endcode + \see GenericReader::Parse, GenericDocument::Parse +*/ +struct ParseResult { + //!! Unspecified boolean type + typedef bool (ParseResult::*BooleanType)() const; +public: + //! Default constructor, no error. + ParseResult() : code_(kParseErrorNone), offset_(0) {} + //! Constructor to set an error. + ParseResult(ParseErrorCode code, size_t offset) : code_(code), offset_(offset) {} + + //! Get the error code. + ParseErrorCode Code() const { return code_; } + //! Get the error offset, if \ref IsError(), 0 otherwise. + size_t Offset() const { return offset_; } + + //! Explicit conversion to \c bool, returns \c true, iff !\ref IsError(). + operator BooleanType() const { return !IsError() ? &ParseResult::IsError : NULL; } + //! Whether the result is an error. + bool IsError() const { return code_ != kParseErrorNone; } + + bool operator==(const ParseResult& that) const { return code_ == that.code_; } + bool operator==(ParseErrorCode code) const { return code_ == code; } + friend bool operator==(ParseErrorCode code, const ParseResult & err) { return code == err.code_; } + + bool operator!=(const ParseResult& that) const { return !(*this == that); } + bool operator!=(ParseErrorCode code) const { return !(*this == code); } + friend bool operator!=(ParseErrorCode code, const ParseResult & err) { return err != code; } + + //! Reset error code. + void Clear() { Set(kParseErrorNone); } + //! Update error code and offset. + void Set(ParseErrorCode code, size_t offset = 0) { code_ = code; offset_ = offset; } + +private: + ParseErrorCode code_; + size_t offset_; +}; + +//! Function pointer type of GetParseError(). +/*! \ingroup RAPIDJSON_ERRORS + + This is the prototype for \c GetParseError_X(), where \c X is a locale. + User can dynamically change locale in runtime, e.g.: +\code + GetParseErrorFunc GetParseError = GetParseError_En; // or whatever + const RAPIDJSON_ERROR_CHARTYPE* s = GetParseError(document.GetParseErrorCode()); +\endcode +*/ +typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetParseErrorFunc)(ParseErrorCode); + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ERROR_ERROR_H_ diff --git a/util/xpack/rapidjson/filereadstream.h b/util/xpack/rapidjson/filereadstream.h new file mode 100755 index 0000000..e166644 --- /dev/null +++ b/util/xpack/rapidjson/filereadstream.h @@ -0,0 +1,99 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FILEREADSTREAM_H_ +#define RAPIDJSON_FILEREADSTREAM_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(unreachable-code) +RAPIDJSON_DIAG_OFF(missing-noreturn) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! File byte stream for input using fread(). +/*! + \note implements Stream concept +*/ +class FileReadStream { +public: + typedef char Ch; //!< Character type (byte). + + //! Constructor. + /*! + \param fp File pointer opened for read. + \param buffer user-supplied buffer. + \param bufferSize size of buffer in bytes. Must >=4 bytes. + */ + FileReadStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferSize_(bufferSize), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) { + RAPIDJSON_ASSERT(fp_ != 0); + RAPIDJSON_ASSERT(bufferSize >= 4); + Read(); + } + + Ch Peek() const { return *current_; } + Ch Take() { Ch c = *current_; Read(); return c; } + size_t Tell() const { return count_ + static_cast(current_ - buffer_); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return (current_ + 4 - !eof_ <= bufferLast_) ? current_ : 0; + } + +private: + void Read() { + if (current_ < bufferLast_) + ++current_; + else if (!eof_) { + count_ += readCount_; + readCount_ = std::fread(buffer_, 1, bufferSize_, fp_); + bufferLast_ = buffer_ + readCount_ - 1; + current_ = buffer_; + + if (readCount_ < bufferSize_) { + buffer_[readCount_] = '\0'; + ++bufferLast_; + eof_ = true; + } + } + } + + std::FILE* fp_; + Ch *buffer_; + size_t bufferSize_; + Ch *bufferLast_; + Ch *current_; + size_t readCount_; + size_t count_; //!< Number of characters read + bool eof_; +}; + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/util/xpack/rapidjson/filewritestream.h b/util/xpack/rapidjson/filewritestream.h new file mode 100755 index 0000000..18fe506 --- /dev/null +++ b/util/xpack/rapidjson/filewritestream.h @@ -0,0 +1,104 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FILEWRITESTREAM_H_ +#define RAPIDJSON_FILEWRITESTREAM_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(unreachable-code) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of C file stream for output using fwrite(). +/*! + \note implements Stream concept +*/ +class FileWriteStream { +public: + typedef char Ch; //!< Character type. Only support char. + + FileWriteStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferEnd_(buffer + bufferSize), current_(buffer_) { + RAPIDJSON_ASSERT(fp_ != 0); + } + + void Put(char c) { + if (current_ >= bufferEnd_) + Flush(); + + *current_++ = c; + } + + void PutN(char c, size_t n) { + size_t avail = static_cast(bufferEnd_ - current_); + while (n > avail) { + std::memset(current_, c, avail); + current_ += avail; + Flush(); + n -= avail; + avail = static_cast(bufferEnd_ - current_); + } + + if (n > 0) { + std::memset(current_, c, n); + current_ += n; + } + } + + void Flush() { + if (current_ != buffer_) { + size_t result = std::fwrite(buffer_, 1, static_cast(current_ - buffer_), fp_); + if (result < static_cast(current_ - buffer_)) { + // failure deliberately ignored at this time + // added to avoid warn_unused_result build errors + } + current_ = buffer_; + } + } + + // Not implemented + char Peek() const { RAPIDJSON_ASSERT(false); return 0; } + char Take() { RAPIDJSON_ASSERT(false); return 0; } + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + // Prohibit copy constructor & assignment operator. + FileWriteStream(const FileWriteStream&); + FileWriteStream& operator=(const FileWriteStream&); + + std::FILE* fp_; + char *buffer_; + char *bufferEnd_; + char *current_; +}; + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(FileWriteStream& stream, char c, size_t n) { + stream.PutN(c, n); +} + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/util/xpack/rapidjson/fwd.h b/util/xpack/rapidjson/fwd.h new file mode 100755 index 0000000..96d020e --- /dev/null +++ b/util/xpack/rapidjson/fwd.h @@ -0,0 +1,151 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FWD_H_ +#define RAPIDJSON_FWD_H_ + +#include "rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN + +// encodings.h + +template struct UTF8; +template struct UTF16; +template struct UTF16BE; +template struct UTF16LE; +template struct UTF32; +template struct UTF32BE; +template struct UTF32LE; +template struct ASCII; +template struct AutoUTF; + +template +struct Transcoder; + +// allocators.h + +class CrtAllocator; + +template +class MemoryPoolAllocator; + +// stream.h + +template +struct GenericStringStream; + +typedef GenericStringStream > StringStream; + +template +struct GenericInsituStringStream; + +typedef GenericInsituStringStream > InsituStringStream; + +// stringbuffer.h + +template +class GenericStringBuffer; + +typedef GenericStringBuffer, CrtAllocator> StringBuffer; + +// filereadstream.h + +class FileReadStream; + +// filewritestream.h + +class FileWriteStream; + +// memorybuffer.h + +template +struct GenericMemoryBuffer; + +typedef GenericMemoryBuffer MemoryBuffer; + +// memorystream.h + +struct MemoryStream; + +// reader.h + +template +struct BaseReaderHandler; + +template +class GenericReader; + +typedef GenericReader, UTF8, CrtAllocator> Reader; + +// writer.h + +template +class Writer; + +// prettywriter.h + +template +class PrettyWriter; + +// document.h + +template +struct GenericMember; + +template +class GenericMemberIterator; + +template +struct GenericStringRef; + +template +class GenericValue; + +typedef GenericValue, MemoryPoolAllocator > Value; + +template +class GenericDocument; + +typedef GenericDocument, MemoryPoolAllocator, CrtAllocator> Document; + +// pointer.h + +template +class GenericPointer; + +typedef GenericPointer Pointer; + +// schema.h + +template +class IGenericRemoteSchemaDocumentProvider; + +template +class GenericSchemaDocument; + +typedef GenericSchemaDocument SchemaDocument; +typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProvider; + +template < + typename SchemaDocumentType, + typename OutputHandler, + typename StateAllocator> +class GenericSchemaValidator; + +typedef GenericSchemaValidator, void>, CrtAllocator> SchemaValidator; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_RAPIDJSONFWD_H_ diff --git a/util/xpack/rapidjson/internal/biginteger.h b/util/xpack/rapidjson/internal/biginteger.h new file mode 100755 index 0000000..dec199f --- /dev/null +++ b/util/xpack/rapidjson/internal/biginteger.h @@ -0,0 +1,290 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_BIGINTEGER_H_ +#define RAPIDJSON_BIGINTEGER_H_ + +#include "../rapidjson.h" + +#if defined(_MSC_VER) && !__INTEL_COMPILER && defined(_M_AMD64) +#include // for _umul128 +#pragma intrinsic(_umul128) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +class BigInteger { +public: + typedef uint64_t Type; + + BigInteger(const BigInteger& rhs) : count_(rhs.count_) { + std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type)); + } + + explicit BigInteger(uint64_t u) : count_(1) { + digits_[0] = u; + } + + BigInteger(const char* decimals, size_t length) : count_(1) { + RAPIDJSON_ASSERT(length > 0); + digits_[0] = 0; + size_t i = 0; + const size_t kMaxDigitPerIteration = 19; // 2^64 = 18446744073709551616 > 10^19 + while (length >= kMaxDigitPerIteration) { + AppendDecimal64(decimals + i, decimals + i + kMaxDigitPerIteration); + length -= kMaxDigitPerIteration; + i += kMaxDigitPerIteration; + } + + if (length > 0) + AppendDecimal64(decimals + i, decimals + i + length); + } + + BigInteger& operator=(const BigInteger &rhs) + { + if (this != &rhs) { + count_ = rhs.count_; + std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type)); + } + return *this; + } + + BigInteger& operator=(uint64_t u) { + digits_[0] = u; + count_ = 1; + return *this; + } + + BigInteger& operator+=(uint64_t u) { + Type backup = digits_[0]; + digits_[0] += u; + for (size_t i = 0; i < count_ - 1; i++) { + if (digits_[i] >= backup) + return *this; // no carry + backup = digits_[i + 1]; + digits_[i + 1] += 1; + } + + // Last carry + if (digits_[count_ - 1] < backup) + PushBack(1); + + return *this; + } + + BigInteger& operator*=(uint64_t u) { + if (u == 0) return *this = 0; + if (u == 1) return *this; + if (*this == 1) return *this = u; + + uint64_t k = 0; + for (size_t i = 0; i < count_; i++) { + uint64_t hi; + digits_[i] = MulAdd64(digits_[i], u, k, &hi); + k = hi; + } + + if (k > 0) + PushBack(k); + + return *this; + } + + BigInteger& operator*=(uint32_t u) { + if (u == 0) return *this = 0; + if (u == 1) return *this; + if (*this == 1) return *this = u; + + uint64_t k = 0; + for (size_t i = 0; i < count_; i++) { + const uint64_t c = digits_[i] >> 32; + const uint64_t d = digits_[i] & 0xFFFFFFFF; + const uint64_t uc = u * c; + const uint64_t ud = u * d; + const uint64_t p0 = ud + k; + const uint64_t p1 = uc + (p0 >> 32); + digits_[i] = (p0 & 0xFFFFFFFF) | (p1 << 32); + k = p1 >> 32; + } + + if (k > 0) + PushBack(k); + + return *this; + } + + BigInteger& operator<<=(size_t shift) { + if (IsZero() || shift == 0) return *this; + + size_t offset = shift / kTypeBit; + size_t interShift = shift % kTypeBit; + RAPIDJSON_ASSERT(count_ + offset <= kCapacity); + + if (interShift == 0) { + std::memmove(digits_ + offset, digits_, count_ * sizeof(Type)); + count_ += offset; + } + else { + digits_[count_] = 0; + for (size_t i = count_; i > 0; i--) + digits_[i + offset] = (digits_[i] << interShift) | (digits_[i - 1] >> (kTypeBit - interShift)); + digits_[offset] = digits_[0] << interShift; + count_ += offset; + if (digits_[count_]) + count_++; + } + + std::memset(digits_, 0, offset * sizeof(Type)); + + return *this; + } + + bool operator==(const BigInteger& rhs) const { + return count_ == rhs.count_ && std::memcmp(digits_, rhs.digits_, count_ * sizeof(Type)) == 0; + } + + bool operator==(const Type rhs) const { + return count_ == 1 && digits_[0] == rhs; + } + + BigInteger& MultiplyPow5(unsigned exp) { + static const uint32_t kPow5[12] = { + 5, + 5 * 5, + 5 * 5 * 5, + 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 + }; + if (exp == 0) return *this; + for (; exp >= 27; exp -= 27) *this *= RAPIDJSON_UINT64_C2(0X6765C793, 0XFA10079D); // 5^27 + for (; exp >= 13; exp -= 13) *this *= static_cast(1220703125u); // 5^13 + if (exp > 0) *this *= kPow5[exp - 1]; + return *this; + } + + // Compute absolute difference of this and rhs. + // Assume this != rhs + bool Difference(const BigInteger& rhs, BigInteger* out) const { + int cmp = Compare(rhs); + RAPIDJSON_ASSERT(cmp != 0); + const BigInteger *a, *b; // Makes a > b + bool ret; + if (cmp < 0) { a = &rhs; b = this; ret = true; } + else { a = this; b = &rhs; ret = false; } + + Type borrow = 0; + for (size_t i = 0; i < a->count_; i++) { + Type d = a->digits_[i] - borrow; + if (i < b->count_) + d -= b->digits_[i]; + borrow = (d > a->digits_[i]) ? 1 : 0; + out->digits_[i] = d; + if (d != 0) + out->count_ = i + 1; + } + + return ret; + } + + int Compare(const BigInteger& rhs) const { + if (count_ != rhs.count_) + return count_ < rhs.count_ ? -1 : 1; + + for (size_t i = count_; i-- > 0;) + if (digits_[i] != rhs.digits_[i]) + return digits_[i] < rhs.digits_[i] ? -1 : 1; + + return 0; + } + + size_t GetCount() const { return count_; } + Type GetDigit(size_t index) const { RAPIDJSON_ASSERT(index < count_); return digits_[index]; } + bool IsZero() const { return count_ == 1 && digits_[0] == 0; } + +private: + void AppendDecimal64(const char* begin, const char* end) { + uint64_t u = ParseUint64(begin, end); + if (IsZero()) + *this = u; + else { + unsigned exp = static_cast(end - begin); + (MultiplyPow5(exp) <<= exp) += u; // *this = *this * 10^exp + u + } + } + + void PushBack(Type digit) { + RAPIDJSON_ASSERT(count_ < kCapacity); + digits_[count_++] = digit; + } + + static uint64_t ParseUint64(const char* begin, const char* end) { + uint64_t r = 0; + for (const char* p = begin; p != end; ++p) { + RAPIDJSON_ASSERT(*p >= '0' && *p <= '9'); + r = r * 10u + static_cast(*p - '0'); + } + return r; + } + + // Assume a * b + k < 2^128 + static uint64_t MulAdd64(uint64_t a, uint64_t b, uint64_t k, uint64_t* outHigh) { +#if defined(_MSC_VER) && defined(_M_AMD64) + uint64_t low = _umul128(a, b, outHigh) + k; + if (low < k) + (*outHigh)++; + return low; +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) + __extension__ typedef unsigned __int128 uint128; + uint128 p = static_cast(a) * static_cast(b); + p += k; + *outHigh = static_cast(p >> 64); + return static_cast(p); +#else + const uint64_t a0 = a & 0xFFFFFFFF, a1 = a >> 32, b0 = b & 0xFFFFFFFF, b1 = b >> 32; + uint64_t x0 = a0 * b0, x1 = a0 * b1, x2 = a1 * b0, x3 = a1 * b1; + x1 += (x0 >> 32); // can't give carry + x1 += x2; + if (x1 < x2) + x3 += (static_cast(1) << 32); + uint64_t lo = (x1 << 32) + (x0 & 0xFFFFFFFF); + uint64_t hi = x3 + (x1 >> 32); + + lo += k; + if (lo < k) + hi++; + *outHigh = hi; + return lo; +#endif + } + + static const size_t kBitCount = 3328; // 64bit * 54 > 10^1000 + static const size_t kCapacity = kBitCount / sizeof(Type); + static const size_t kTypeBit = sizeof(Type) * 8; + + Type digits_[kCapacity]; + size_t count_; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_BIGINTEGER_H_ diff --git a/util/xpack/rapidjson/internal/diyfp.h b/util/xpack/rapidjson/internal/diyfp.h new file mode 100755 index 0000000..a26b2ac --- /dev/null +++ b/util/xpack/rapidjson/internal/diyfp.h @@ -0,0 +1,271 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// This is a C++ header-only implementation of Grisu2 algorithm from the publication: +// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with +// integers." ACM Sigplan Notices 45.6 (2010): 233-243. + +#ifndef RAPIDJSON_DIYFP_H_ +#define RAPIDJSON_DIYFP_H_ + +#include "../rapidjson.h" +#include + +#if defined(_MSC_VER) && defined(_M_AMD64) && !defined(__INTEL_COMPILER) +#include +#pragma intrinsic(_BitScanReverse64) +#pragma intrinsic(_umul128) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +struct DiyFp { + DiyFp() : f(), e() {} + + DiyFp(uint64_t fp, int exp) : f(fp), e(exp) {} + + explicit DiyFp(double d) { + union { + double d; + uint64_t u64; + } u = { d }; + + int biased_e = static_cast((u.u64 & kDpExponentMask) >> kDpSignificandSize); + uint64_t significand = (u.u64 & kDpSignificandMask); + if (biased_e != 0) { + f = significand + kDpHiddenBit; + e = biased_e - kDpExponentBias; + } + else { + f = significand; + e = kDpMinExponent + 1; + } + } + + DiyFp operator-(const DiyFp& rhs) const { + return DiyFp(f - rhs.f, e); + } + + DiyFp operator*(const DiyFp& rhs) const { +#if defined(_MSC_VER) && defined(_M_AMD64) + uint64_t h; + uint64_t l = _umul128(f, rhs.f, &h); + if (l & (uint64_t(1) << 63)) // rounding + h++; + return DiyFp(h, e + rhs.e + 64); +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) + __extension__ typedef unsigned __int128 uint128; + uint128 p = static_cast(f) * static_cast(rhs.f); + uint64_t h = static_cast(p >> 64); + uint64_t l = static_cast(p); + if (l & (uint64_t(1) << 63)) // rounding + h++; + return DiyFp(h, e + rhs.e + 64); +#else + const uint64_t M32 = 0xFFFFFFFF; + const uint64_t a = f >> 32; + const uint64_t b = f & M32; + const uint64_t c = rhs.f >> 32; + const uint64_t d = rhs.f & M32; + const uint64_t ac = a * c; + const uint64_t bc = b * c; + const uint64_t ad = a * d; + const uint64_t bd = b * d; + uint64_t tmp = (bd >> 32) + (ad & M32) + (bc & M32); + tmp += 1U << 31; /// mult_round + return DiyFp(ac + (ad >> 32) + (bc >> 32) + (tmp >> 32), e + rhs.e + 64); +#endif + } + + DiyFp Normalize() const { + RAPIDJSON_ASSERT(f != 0); // https://stackoverflow.com/a/26809183/291737 +#if defined(_MSC_VER) && defined(_M_AMD64) + unsigned long index; + _BitScanReverse64(&index, f); + return DiyFp(f << (63 - index), e - (63 - index)); +#elif defined(__GNUC__) && __GNUC__ >= 4 + int s = __builtin_clzll(f); + return DiyFp(f << s, e - s); +#else + DiyFp res = *this; + while (!(res.f & (static_cast(1) << 63))) { + res.f <<= 1; + res.e--; + } + return res; +#endif + } + + DiyFp NormalizeBoundary() const { + DiyFp res = *this; + while (!(res.f & (kDpHiddenBit << 1))) { + res.f <<= 1; + res.e--; + } + res.f <<= (kDiySignificandSize - kDpSignificandSize - 2); + res.e = res.e - (kDiySignificandSize - kDpSignificandSize - 2); + return res; + } + + void NormalizedBoundaries(DiyFp* minus, DiyFp* plus) const { + DiyFp pl = DiyFp((f << 1) + 1, e - 1).NormalizeBoundary(); + DiyFp mi = (f == kDpHiddenBit) ? DiyFp((f << 2) - 1, e - 2) : DiyFp((f << 1) - 1, e - 1); + mi.f <<= mi.e - pl.e; + mi.e = pl.e; + *plus = pl; + *minus = mi; + } + + double ToDouble() const { + union { + double d; + uint64_t u64; + }u; + RAPIDJSON_ASSERT(f <= kDpHiddenBit + kDpSignificandMask); + if (e < kDpDenormalExponent) { + // Underflow. + return 0.0; + } + if (e >= kDpMaxExponent) { + // Overflow. + return std::numeric_limits::infinity(); + } + const uint64_t be = (e == kDpDenormalExponent && (f & kDpHiddenBit) == 0) ? 0 : + static_cast(e + kDpExponentBias); + u.u64 = (f & kDpSignificandMask) | (be << kDpSignificandSize); + return u.d; + } + + static const int kDiySignificandSize = 64; + static const int kDpSignificandSize = 52; + static const int kDpExponentBias = 0x3FF + kDpSignificandSize; + static const int kDpMaxExponent = 0x7FF - kDpExponentBias; + static const int kDpMinExponent = -kDpExponentBias; + static const int kDpDenormalExponent = -kDpExponentBias + 1; + static const uint64_t kDpExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000); + static const uint64_t kDpSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); + static const uint64_t kDpHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000); + + uint64_t f; + int e; +}; + +inline DiyFp GetCachedPowerByIndex(size_t index) { + // 10^-348, 10^-340, ..., 10^340 + static const uint64_t kCachedPowers_F[] = { + RAPIDJSON_UINT64_C2(0xfa8fd5a0, 0x081c0288), RAPIDJSON_UINT64_C2(0xbaaee17f, 0xa23ebf76), + RAPIDJSON_UINT64_C2(0x8b16fb20, 0x3055ac76), RAPIDJSON_UINT64_C2(0xcf42894a, 0x5dce35ea), + RAPIDJSON_UINT64_C2(0x9a6bb0aa, 0x55653b2d), RAPIDJSON_UINT64_C2(0xe61acf03, 0x3d1a45df), + RAPIDJSON_UINT64_C2(0xab70fe17, 0xc79ac6ca), RAPIDJSON_UINT64_C2(0xff77b1fc, 0xbebcdc4f), + RAPIDJSON_UINT64_C2(0xbe5691ef, 0x416bd60c), RAPIDJSON_UINT64_C2(0x8dd01fad, 0x907ffc3c), + RAPIDJSON_UINT64_C2(0xd3515c28, 0x31559a83), RAPIDJSON_UINT64_C2(0x9d71ac8f, 0xada6c9b5), + RAPIDJSON_UINT64_C2(0xea9c2277, 0x23ee8bcb), RAPIDJSON_UINT64_C2(0xaecc4991, 0x4078536d), + RAPIDJSON_UINT64_C2(0x823c1279, 0x5db6ce57), RAPIDJSON_UINT64_C2(0xc2109436, 0x4dfb5637), + RAPIDJSON_UINT64_C2(0x9096ea6f, 0x3848984f), RAPIDJSON_UINT64_C2(0xd77485cb, 0x25823ac7), + RAPIDJSON_UINT64_C2(0xa086cfcd, 0x97bf97f4), RAPIDJSON_UINT64_C2(0xef340a98, 0x172aace5), + RAPIDJSON_UINT64_C2(0xb23867fb, 0x2a35b28e), RAPIDJSON_UINT64_C2(0x84c8d4df, 0xd2c63f3b), + RAPIDJSON_UINT64_C2(0xc5dd4427, 0x1ad3cdba), RAPIDJSON_UINT64_C2(0x936b9fce, 0xbb25c996), + RAPIDJSON_UINT64_C2(0xdbac6c24, 0x7d62a584), RAPIDJSON_UINT64_C2(0xa3ab6658, 0x0d5fdaf6), + RAPIDJSON_UINT64_C2(0xf3e2f893, 0xdec3f126), RAPIDJSON_UINT64_C2(0xb5b5ada8, 0xaaff80b8), + RAPIDJSON_UINT64_C2(0x87625f05, 0x6c7c4a8b), RAPIDJSON_UINT64_C2(0xc9bcff60, 0x34c13053), + RAPIDJSON_UINT64_C2(0x964e858c, 0x91ba2655), RAPIDJSON_UINT64_C2(0xdff97724, 0x70297ebd), + RAPIDJSON_UINT64_C2(0xa6dfbd9f, 0xb8e5b88f), RAPIDJSON_UINT64_C2(0xf8a95fcf, 0x88747d94), + RAPIDJSON_UINT64_C2(0xb9447093, 0x8fa89bcf), RAPIDJSON_UINT64_C2(0x8a08f0f8, 0xbf0f156b), + RAPIDJSON_UINT64_C2(0xcdb02555, 0x653131b6), RAPIDJSON_UINT64_C2(0x993fe2c6, 0xd07b7fac), + RAPIDJSON_UINT64_C2(0xe45c10c4, 0x2a2b3b06), RAPIDJSON_UINT64_C2(0xaa242499, 0x697392d3), + RAPIDJSON_UINT64_C2(0xfd87b5f2, 0x8300ca0e), RAPIDJSON_UINT64_C2(0xbce50864, 0x92111aeb), + RAPIDJSON_UINT64_C2(0x8cbccc09, 0x6f5088cc), RAPIDJSON_UINT64_C2(0xd1b71758, 0xe219652c), + RAPIDJSON_UINT64_C2(0x9c400000, 0x00000000), RAPIDJSON_UINT64_C2(0xe8d4a510, 0x00000000), + RAPIDJSON_UINT64_C2(0xad78ebc5, 0xac620000), RAPIDJSON_UINT64_C2(0x813f3978, 0xf8940984), + RAPIDJSON_UINT64_C2(0xc097ce7b, 0xc90715b3), RAPIDJSON_UINT64_C2(0x8f7e32ce, 0x7bea5c70), + RAPIDJSON_UINT64_C2(0xd5d238a4, 0xabe98068), RAPIDJSON_UINT64_C2(0x9f4f2726, 0x179a2245), + RAPIDJSON_UINT64_C2(0xed63a231, 0xd4c4fb27), RAPIDJSON_UINT64_C2(0xb0de6538, 0x8cc8ada8), + RAPIDJSON_UINT64_C2(0x83c7088e, 0x1aab65db), RAPIDJSON_UINT64_C2(0xc45d1df9, 0x42711d9a), + RAPIDJSON_UINT64_C2(0x924d692c, 0xa61be758), RAPIDJSON_UINT64_C2(0xda01ee64, 0x1a708dea), + RAPIDJSON_UINT64_C2(0xa26da399, 0x9aef774a), RAPIDJSON_UINT64_C2(0xf209787b, 0xb47d6b85), + RAPIDJSON_UINT64_C2(0xb454e4a1, 0x79dd1877), RAPIDJSON_UINT64_C2(0x865b8692, 0x5b9bc5c2), + RAPIDJSON_UINT64_C2(0xc83553c5, 0xc8965d3d), RAPIDJSON_UINT64_C2(0x952ab45c, 0xfa97a0b3), + RAPIDJSON_UINT64_C2(0xde469fbd, 0x99a05fe3), RAPIDJSON_UINT64_C2(0xa59bc234, 0xdb398c25), + RAPIDJSON_UINT64_C2(0xf6c69a72, 0xa3989f5c), RAPIDJSON_UINT64_C2(0xb7dcbf53, 0x54e9bece), + RAPIDJSON_UINT64_C2(0x88fcf317, 0xf22241e2), RAPIDJSON_UINT64_C2(0xcc20ce9b, 0xd35c78a5), + RAPIDJSON_UINT64_C2(0x98165af3, 0x7b2153df), RAPIDJSON_UINT64_C2(0xe2a0b5dc, 0x971f303a), + RAPIDJSON_UINT64_C2(0xa8d9d153, 0x5ce3b396), RAPIDJSON_UINT64_C2(0xfb9b7cd9, 0xa4a7443c), + RAPIDJSON_UINT64_C2(0xbb764c4c, 0xa7a44410), RAPIDJSON_UINT64_C2(0x8bab8eef, 0xb6409c1a), + RAPIDJSON_UINT64_C2(0xd01fef10, 0xa657842c), RAPIDJSON_UINT64_C2(0x9b10a4e5, 0xe9913129), + RAPIDJSON_UINT64_C2(0xe7109bfb, 0xa19c0c9d), RAPIDJSON_UINT64_C2(0xac2820d9, 0x623bf429), + RAPIDJSON_UINT64_C2(0x80444b5e, 0x7aa7cf85), RAPIDJSON_UINT64_C2(0xbf21e440, 0x03acdd2d), + RAPIDJSON_UINT64_C2(0x8e679c2f, 0x5e44ff8f), RAPIDJSON_UINT64_C2(0xd433179d, 0x9c8cb841), + RAPIDJSON_UINT64_C2(0x9e19db92, 0xb4e31ba9), RAPIDJSON_UINT64_C2(0xeb96bf6e, 0xbadf77d9), + RAPIDJSON_UINT64_C2(0xaf87023b, 0x9bf0ee6b) + }; + static const int16_t kCachedPowers_E[] = { + -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, + -954, -927, -901, -874, -847, -821, -794, -768, -741, -715, + -688, -661, -635, -608, -582, -555, -529, -502, -475, -449, + -422, -396, -369, -343, -316, -289, -263, -236, -210, -183, + -157, -130, -103, -77, -50, -24, 3, 30, 56, 83, + 109, 136, 162, 189, 216, 242, 269, 295, 322, 348, + 375, 402, 428, 455, 481, 508, 534, 561, 588, 614, + 641, 667, 694, 720, 747, 774, 800, 827, 853, 880, + 907, 933, 960, 986, 1013, 1039, 1066 + }; + RAPIDJSON_ASSERT(index < 87); + return DiyFp(kCachedPowers_F[index], kCachedPowers_E[index]); +} + +inline DiyFp GetCachedPower(int e, int* K) { + + //int k = static_cast(ceil((-61 - e) * 0.30102999566398114)) + 374; + double dk = (-61 - e) * 0.30102999566398114 + 347; // dk must be positive, so can do ceiling in positive + int k = static_cast(dk); + if (dk - k > 0.0) + k++; + + unsigned index = static_cast((k >> 3) + 1); + *K = -(-348 + static_cast(index << 3)); // decimal exponent no need lookup table + + return GetCachedPowerByIndex(index); +} + +inline DiyFp GetCachedPower10(int exp, int *outExp) { + RAPIDJSON_ASSERT(exp >= -348); + unsigned index = static_cast(exp + 348) / 8u; + *outExp = -348 + static_cast(index) * 8; + return GetCachedPowerByIndex(index); +} + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +RAPIDJSON_DIAG_OFF(padded) +#endif + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_DIYFP_H_ diff --git a/util/xpack/rapidjson/internal/dtoa.h b/util/xpack/rapidjson/internal/dtoa.h new file mode 100755 index 0000000..18b63f4 --- /dev/null +++ b/util/xpack/rapidjson/internal/dtoa.h @@ -0,0 +1,245 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// This is a C++ header-only implementation of Grisu2 algorithm from the publication: +// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with +// integers." ACM Sigplan Notices 45.6 (2010): 233-243. + +#ifndef RAPIDJSON_DTOA_ +#define RAPIDJSON_DTOA_ + +#include "itoa.h" // GetDigitsLut() +#include "diyfp.h" +#include "ieee754.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +RAPIDJSON_DIAG_OFF(array-bounds) // some gcc versions generate wrong warnings https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59124 +#endif + +inline void GrisuRound(char* buffer, int len, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t wp_w) { + while (rest < wp_w && delta - rest >= ten_kappa && + (rest + ten_kappa < wp_w || /// closer + wp_w - rest > rest + ten_kappa - wp_w)) { + buffer[len - 1]--; + rest += ten_kappa; + } +} + +inline int CountDecimalDigit32(uint32_t n) { + // Simple pure C++ implementation was faster than __builtin_clz version in this situation. + if (n < 10) return 1; + if (n < 100) return 2; + if (n < 1000) return 3; + if (n < 10000) return 4; + if (n < 100000) return 5; + if (n < 1000000) return 6; + if (n < 10000000) return 7; + if (n < 100000000) return 8; + // Will not reach 10 digits in DigitGen() + //if (n < 1000000000) return 9; + //return 10; + return 9; +} + +inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buffer, int* len, int* K) { + static const uint32_t kPow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + const DiyFp one(uint64_t(1) << -Mp.e, Mp.e); + const DiyFp wp_w = Mp - W; + uint32_t p1 = static_cast(Mp.f >> -one.e); + uint64_t p2 = Mp.f & (one.f - 1); + int kappa = CountDecimalDigit32(p1); // kappa in [0, 9] + *len = 0; + + while (kappa > 0) { + uint32_t d = 0; + switch (kappa) { + case 9: d = p1 / 100000000; p1 %= 100000000; break; + case 8: d = p1 / 10000000; p1 %= 10000000; break; + case 7: d = p1 / 1000000; p1 %= 1000000; break; + case 6: d = p1 / 100000; p1 %= 100000; break; + case 5: d = p1 / 10000; p1 %= 10000; break; + case 4: d = p1 / 1000; p1 %= 1000; break; + case 3: d = p1 / 100; p1 %= 100; break; + case 2: d = p1 / 10; p1 %= 10; break; + case 1: d = p1; p1 = 0; break; + default:; + } + if (d || *len) + buffer[(*len)++] = static_cast('0' + static_cast(d)); + kappa--; + uint64_t tmp = (static_cast(p1) << -one.e) + p2; + if (tmp <= delta) { + *K += kappa; + GrisuRound(buffer, *len, delta, tmp, static_cast(kPow10[kappa]) << -one.e, wp_w.f); + return; + } + } + + // kappa = 0 + for (;;) { + p2 *= 10; + delta *= 10; + char d = static_cast(p2 >> -one.e); + if (d || *len) + buffer[(*len)++] = static_cast('0' + d); + p2 &= one.f - 1; + kappa--; + if (p2 < delta) { + *K += kappa; + int index = -kappa; + GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * (index < 9 ? kPow10[index] : 0)); + return; + } + } +} + +inline void Grisu2(double value, char* buffer, int* length, int* K) { + const DiyFp v(value); + DiyFp w_m, w_p; + v.NormalizedBoundaries(&w_m, &w_p); + + const DiyFp c_mk = GetCachedPower(w_p.e, K); + const DiyFp W = v.Normalize() * c_mk; + DiyFp Wp = w_p * c_mk; + DiyFp Wm = w_m * c_mk; + Wm.f++; + Wp.f--; + DigitGen(W, Wp, Wp.f - Wm.f, buffer, length, K); +} + +inline char* WriteExponent(int K, char* buffer) { + if (K < 0) { + *buffer++ = '-'; + K = -K; + } + + if (K >= 100) { + *buffer++ = static_cast('0' + static_cast(K / 100)); + K %= 100; + const char* d = GetDigitsLut() + K * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + else if (K >= 10) { + const char* d = GetDigitsLut() + K * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + else + *buffer++ = static_cast('0' + static_cast(K)); + + return buffer; +} + +inline char* Prettify(char* buffer, int length, int k, int maxDecimalPlaces) { + const int kk = length + k; // 10^(kk-1) <= v < 10^kk + + if (0 <= k && kk <= 21) { + // 1234e7 -> 12340000000 + for (int i = length; i < kk; i++) + buffer[i] = '0'; + buffer[kk] = '.'; + buffer[kk + 1] = '0'; + return &buffer[kk + 2]; + } + else if (0 < kk && kk <= 21) { + // 1234e-2 -> 12.34 + std::memmove(&buffer[kk + 1], &buffer[kk], static_cast(length - kk)); + buffer[kk] = '.'; + if (0 > k + maxDecimalPlaces) { + // When maxDecimalPlaces = 2, 1.2345 -> 1.23, 1.102 -> 1.1 + // Remove extra trailing zeros (at least one) after truncation. + for (int i = kk + maxDecimalPlaces; i > kk + 1; i--) + if (buffer[i] != '0') + return &buffer[i + 1]; + return &buffer[kk + 2]; // Reserve one zero + } + else + return &buffer[length + 1]; + } + else if (-6 < kk && kk <= 0) { + // 1234e-6 -> 0.001234 + const int offset = 2 - kk; + std::memmove(&buffer[offset], &buffer[0], static_cast(length)); + buffer[0] = '0'; + buffer[1] = '.'; + for (int i = 2; i < offset; i++) + buffer[i] = '0'; + if (length - kk > maxDecimalPlaces) { + // When maxDecimalPlaces = 2, 0.123 -> 0.12, 0.102 -> 0.1 + // Remove extra trailing zeros (at least one) after truncation. + for (int i = maxDecimalPlaces + 1; i > 2; i--) + if (buffer[i] != '0') + return &buffer[i + 1]; + return &buffer[3]; // Reserve one zero + } + else + return &buffer[length + offset]; + } + else if (kk < -maxDecimalPlaces) { + // Truncate to zero + buffer[0] = '0'; + buffer[1] = '.'; + buffer[2] = '0'; + return &buffer[3]; + } + else if (length == 1) { + // 1e30 + buffer[1] = 'e'; + return WriteExponent(kk - 1, &buffer[2]); + } + else { + // 1234e30 -> 1.234e33 + std::memmove(&buffer[2], &buffer[1], static_cast(length - 1)); + buffer[1] = '.'; + buffer[length + 1] = 'e'; + return WriteExponent(kk - 1, &buffer[0 + length + 2]); + } +} + +inline char* dtoa(double value, char* buffer, int maxDecimalPlaces = 324) { + RAPIDJSON_ASSERT(maxDecimalPlaces >= 1); + Double d(value); + if (d.IsZero()) { + if (d.Sign()) + *buffer++ = '-'; // -0.0, Issue #289 + buffer[0] = '0'; + buffer[1] = '.'; + buffer[2] = '0'; + return &buffer[3]; + } + else { + if (value < 0) { + *buffer++ = '-'; + value = -value; + } + int length, K; + Grisu2(value, buffer, &length, &K); + return Prettify(buffer, length, K, maxDecimalPlaces); + } +} + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_DTOA_ diff --git a/util/xpack/rapidjson/internal/ieee754.h b/util/xpack/rapidjson/internal/ieee754.h new file mode 100755 index 0000000..1789654 --- /dev/null +++ b/util/xpack/rapidjson/internal/ieee754.h @@ -0,0 +1,78 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_IEEE754_ +#define RAPIDJSON_IEEE754_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +class Double { +public: + Double() {} + Double(double d) : d_(d) {} + Double(uint64_t u) : u_(u) {} + + double Value() const { return d_; } + uint64_t Uint64Value() const { return u_; } + + double NextPositiveDouble() const { + RAPIDJSON_ASSERT(!Sign()); + return Double(u_ + 1).Value(); + } + + bool Sign() const { return (u_ & kSignMask) != 0; } + uint64_t Significand() const { return u_ & kSignificandMask; } + int Exponent() const { return static_cast(((u_ & kExponentMask) >> kSignificandSize) - kExponentBias); } + + bool IsNan() const { return (u_ & kExponentMask) == kExponentMask && Significand() != 0; } + bool IsInf() const { return (u_ & kExponentMask) == kExponentMask && Significand() == 0; } + bool IsNanOrInf() const { return (u_ & kExponentMask) == kExponentMask; } + bool IsNormal() const { return (u_ & kExponentMask) != 0 || Significand() == 0; } + bool IsZero() const { return (u_ & (kExponentMask | kSignificandMask)) == 0; } + + uint64_t IntegerSignificand() const { return IsNormal() ? Significand() | kHiddenBit : Significand(); } + int IntegerExponent() const { return (IsNormal() ? Exponent() : kDenormalExponent) - kSignificandSize; } + uint64_t ToBias() const { return (u_ & kSignMask) ? ~u_ + 1 : u_ | kSignMask; } + + static int EffectiveSignificandSize(int order) { + if (order >= -1021) + return 53; + else if (order <= -1074) + return 0; + else + return order + 1074; + } + +private: + static const int kSignificandSize = 52; + static const int kExponentBias = 0x3FF; + static const int kDenormalExponent = 1 - kExponentBias; + static const uint64_t kSignMask = RAPIDJSON_UINT64_C2(0x80000000, 0x00000000); + static const uint64_t kExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000); + static const uint64_t kSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); + static const uint64_t kHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000); + + union { + double d_; + uint64_t u_; + }; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_IEEE754_ diff --git a/util/xpack/rapidjson/internal/itoa.h b/util/xpack/rapidjson/internal/itoa.h new file mode 100755 index 0000000..c8fb53b --- /dev/null +++ b/util/xpack/rapidjson/internal/itoa.h @@ -0,0 +1,308 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ITOA_ +#define RAPIDJSON_ITOA_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +inline const char* GetDigitsLut() { + static const char cDigitsLut[200] = { + '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9', + '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9', + '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9', + '3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9', + '4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9', + '5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9', + '6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9', + '7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9', + '8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9', + '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9' + }; + return cDigitsLut; +} + +inline char* u32toa(uint32_t value, char* buffer) { + RAPIDJSON_ASSERT(buffer != 0); + + const char* cDigitsLut = GetDigitsLut(); + + if (value < 10000) { + const uint32_t d1 = (value / 100) << 1; + const uint32_t d2 = (value % 100) << 1; + + if (value >= 1000) + *buffer++ = cDigitsLut[d1]; + if (value >= 100) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 10) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + } + else if (value < 100000000) { + // value = bbbbcccc + const uint32_t b = value / 10000; + const uint32_t c = value % 10000; + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + if (value >= 10000000) + *buffer++ = cDigitsLut[d1]; + if (value >= 1000000) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 100000) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + else { + // value = aabbbbcccc in decimal + + const uint32_t a = value / 100000000; // 1 to 42 + value %= 100000000; + + if (a >= 10) { + const unsigned i = a << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else + *buffer++ = static_cast('0' + static_cast(a)); + + const uint32_t b = value / 10000; // 0 to 9999 + const uint32_t c = value % 10000; // 0 to 9999 + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + *buffer++ = cDigitsLut[d1]; + *buffer++ = cDigitsLut[d1 + 1]; + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + return buffer; +} + +inline char* i32toa(int32_t value, char* buffer) { + RAPIDJSON_ASSERT(buffer != 0); + uint32_t u = static_cast(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + + return u32toa(u, buffer); +} + +inline char* u64toa(uint64_t value, char* buffer) { + RAPIDJSON_ASSERT(buffer != 0); + const char* cDigitsLut = GetDigitsLut(); + const uint64_t kTen8 = 100000000; + const uint64_t kTen9 = kTen8 * 10; + const uint64_t kTen10 = kTen8 * 100; + const uint64_t kTen11 = kTen8 * 1000; + const uint64_t kTen12 = kTen8 * 10000; + const uint64_t kTen13 = kTen8 * 100000; + const uint64_t kTen14 = kTen8 * 1000000; + const uint64_t kTen15 = kTen8 * 10000000; + const uint64_t kTen16 = kTen8 * kTen8; + + if (value < kTen8) { + uint32_t v = static_cast(value); + if (v < 10000) { + const uint32_t d1 = (v / 100) << 1; + const uint32_t d2 = (v % 100) << 1; + + if (v >= 1000) + *buffer++ = cDigitsLut[d1]; + if (v >= 100) + *buffer++ = cDigitsLut[d1 + 1]; + if (v >= 10) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + } + else { + // value = bbbbcccc + const uint32_t b = v / 10000; + const uint32_t c = v % 10000; + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + if (value >= 10000000) + *buffer++ = cDigitsLut[d1]; + if (value >= 1000000) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 100000) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + } + else if (value < kTen16) { + const uint32_t v0 = static_cast(value / kTen8); + const uint32_t v1 = static_cast(value % kTen8); + + const uint32_t b0 = v0 / 10000; + const uint32_t c0 = v0 % 10000; + + const uint32_t d1 = (b0 / 100) << 1; + const uint32_t d2 = (b0 % 100) << 1; + + const uint32_t d3 = (c0 / 100) << 1; + const uint32_t d4 = (c0 % 100) << 1; + + const uint32_t b1 = v1 / 10000; + const uint32_t c1 = v1 % 10000; + + const uint32_t d5 = (b1 / 100) << 1; + const uint32_t d6 = (b1 % 100) << 1; + + const uint32_t d7 = (c1 / 100) << 1; + const uint32_t d8 = (c1 % 100) << 1; + + if (value >= kTen15) + *buffer++ = cDigitsLut[d1]; + if (value >= kTen14) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= kTen13) + *buffer++ = cDigitsLut[d2]; + if (value >= kTen12) + *buffer++ = cDigitsLut[d2 + 1]; + if (value >= kTen11) + *buffer++ = cDigitsLut[d3]; + if (value >= kTen10) + *buffer++ = cDigitsLut[d3 + 1]; + if (value >= kTen9) + *buffer++ = cDigitsLut[d4]; + + *buffer++ = cDigitsLut[d4 + 1]; + *buffer++ = cDigitsLut[d5]; + *buffer++ = cDigitsLut[d5 + 1]; + *buffer++ = cDigitsLut[d6]; + *buffer++ = cDigitsLut[d6 + 1]; + *buffer++ = cDigitsLut[d7]; + *buffer++ = cDigitsLut[d7 + 1]; + *buffer++ = cDigitsLut[d8]; + *buffer++ = cDigitsLut[d8 + 1]; + } + else { + const uint32_t a = static_cast(value / kTen16); // 1 to 1844 + value %= kTen16; + + if (a < 10) + *buffer++ = static_cast('0' + static_cast(a)); + else if (a < 100) { + const uint32_t i = a << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else if (a < 1000) { + *buffer++ = static_cast('0' + static_cast(a / 100)); + + const uint32_t i = (a % 100) << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else { + const uint32_t i = (a / 100) << 1; + const uint32_t j = (a % 100) << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + *buffer++ = cDigitsLut[j]; + *buffer++ = cDigitsLut[j + 1]; + } + + const uint32_t v0 = static_cast(value / kTen8); + const uint32_t v1 = static_cast(value % kTen8); + + const uint32_t b0 = v0 / 10000; + const uint32_t c0 = v0 % 10000; + + const uint32_t d1 = (b0 / 100) << 1; + const uint32_t d2 = (b0 % 100) << 1; + + const uint32_t d3 = (c0 / 100) << 1; + const uint32_t d4 = (c0 % 100) << 1; + + const uint32_t b1 = v1 / 10000; + const uint32_t c1 = v1 % 10000; + + const uint32_t d5 = (b1 / 100) << 1; + const uint32_t d6 = (b1 % 100) << 1; + + const uint32_t d7 = (c1 / 100) << 1; + const uint32_t d8 = (c1 % 100) << 1; + + *buffer++ = cDigitsLut[d1]; + *buffer++ = cDigitsLut[d1 + 1]; + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + *buffer++ = cDigitsLut[d5]; + *buffer++ = cDigitsLut[d5 + 1]; + *buffer++ = cDigitsLut[d6]; + *buffer++ = cDigitsLut[d6 + 1]; + *buffer++ = cDigitsLut[d7]; + *buffer++ = cDigitsLut[d7 + 1]; + *buffer++ = cDigitsLut[d8]; + *buffer++ = cDigitsLut[d8 + 1]; + } + + return buffer; +} + +inline char* i64toa(int64_t value, char* buffer) { + RAPIDJSON_ASSERT(buffer != 0); + uint64_t u = static_cast(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + + return u64toa(u, buffer); +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ITOA_ diff --git a/util/xpack/rapidjson/internal/meta.h b/util/xpack/rapidjson/internal/meta.h new file mode 100755 index 0000000..520d62b --- /dev/null +++ b/util/xpack/rapidjson/internal/meta.h @@ -0,0 +1,186 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_META_H_ +#define RAPIDJSON_INTERNAL_META_H_ + +#include "../rapidjson.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(6334) +#endif + +#if RAPIDJSON_HAS_CXX11_TYPETRAITS +#include +#endif + +//@cond RAPIDJSON_INTERNAL +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +// Helper to wrap/convert arbitrary types to void, useful for arbitrary type matching +template struct Void { typedef void Type; }; + +/////////////////////////////////////////////////////////////////////////////// +// BoolType, TrueType, FalseType +// +template struct BoolType { + static const bool Value = Cond; + typedef BoolType Type; +}; +typedef BoolType TrueType; +typedef BoolType FalseType; + + +/////////////////////////////////////////////////////////////////////////////// +// SelectIf, BoolExpr, NotExpr, AndExpr, OrExpr +// + +template struct SelectIfImpl { template struct Apply { typedef T1 Type; }; }; +template <> struct SelectIfImpl { template struct Apply { typedef T2 Type; }; }; +template struct SelectIfCond : SelectIfImpl::template Apply {}; +template struct SelectIf : SelectIfCond {}; + +template struct AndExprCond : FalseType {}; +template <> struct AndExprCond : TrueType {}; +template struct OrExprCond : TrueType {}; +template <> struct OrExprCond : FalseType {}; + +template struct BoolExpr : SelectIf::Type {}; +template struct NotExpr : SelectIf::Type {}; +template struct AndExpr : AndExprCond::Type {}; +template struct OrExpr : OrExprCond::Type {}; + + +/////////////////////////////////////////////////////////////////////////////// +// AddConst, MaybeAddConst, RemoveConst +template struct AddConst { typedef const T Type; }; +template struct MaybeAddConst : SelectIfCond {}; +template struct RemoveConst { typedef T Type; }; +template struct RemoveConst { typedef T Type; }; + + +/////////////////////////////////////////////////////////////////////////////// +// IsSame, IsConst, IsMoreConst, IsPointer +// +template struct IsSame : FalseType {}; +template struct IsSame : TrueType {}; + +template struct IsConst : FalseType {}; +template struct IsConst : TrueType {}; + +template +struct IsMoreConst + : AndExpr::Type, typename RemoveConst::Type>, + BoolType::Value >= IsConst::Value> >::Type {}; + +template struct IsPointer : FalseType {}; +template struct IsPointer : TrueType {}; + +/////////////////////////////////////////////////////////////////////////////// +// IsBaseOf +// +#if RAPIDJSON_HAS_CXX11_TYPETRAITS + +template struct IsBaseOf + : BoolType< ::std::is_base_of::value> {}; + +#else // simplified version adopted from Boost + +template struct IsBaseOfImpl { + RAPIDJSON_STATIC_ASSERT(sizeof(B) != 0); + RAPIDJSON_STATIC_ASSERT(sizeof(D) != 0); + + typedef char (&Yes)[1]; + typedef char (&No) [2]; + + template + static Yes Check(const D*, T); + static No Check(const B*, int); + + struct Host { + operator const B*() const; + operator const D*(); + }; + + enum { Value = (sizeof(Check(Host(), 0)) == sizeof(Yes)) }; +}; + +template struct IsBaseOf + : OrExpr, BoolExpr > >::Type {}; + +#endif // RAPIDJSON_HAS_CXX11_TYPETRAITS + + +////////////////////////////////////////////////////////////////////////// +// EnableIf / DisableIf +// +template struct EnableIfCond { typedef T Type; }; +template struct EnableIfCond { /* empty */ }; + +template struct DisableIfCond { typedef T Type; }; +template struct DisableIfCond { /* empty */ }; + +template +struct EnableIf : EnableIfCond {}; + +template +struct DisableIf : DisableIfCond {}; + +// SFINAE helpers +struct SfinaeTag {}; +template struct RemoveSfinaeTag; +template struct RemoveSfinaeTag { typedef T Type; }; + +#define RAPIDJSON_REMOVEFPTR_(type) \ + typename ::RAPIDJSON_NAMESPACE::internal::RemoveSfinaeTag \ + < ::RAPIDJSON_NAMESPACE::internal::SfinaeTag&(*) type>::Type + +#define RAPIDJSON_ENABLEIF(cond) \ + typename ::RAPIDJSON_NAMESPACE::internal::EnableIf \ + ::Type * = NULL + +#define RAPIDJSON_DISABLEIF(cond) \ + typename ::RAPIDJSON_NAMESPACE::internal::DisableIf \ + ::Type * = NULL + +#define RAPIDJSON_ENABLEIF_RETURN(cond,returntype) \ + typename ::RAPIDJSON_NAMESPACE::internal::EnableIf \ + ::Type + +#define RAPIDJSON_DISABLEIF_RETURN(cond,returntype) \ + typename ::RAPIDJSON_NAMESPACE::internal::DisableIf \ + ::Type + +} // namespace internal +RAPIDJSON_NAMESPACE_END +//@endcond + +#if defined(_MSC_VER) && !defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_META_H_ diff --git a/util/xpack/rapidjson/internal/pow10.h b/util/xpack/rapidjson/internal/pow10.h new file mode 100755 index 0000000..1d2dff0 --- /dev/null +++ b/util/xpack/rapidjson/internal/pow10.h @@ -0,0 +1,55 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_POW10_ +#define RAPIDJSON_POW10_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Computes integer powers of 10 in double (10.0^n). +/*! This function uses lookup table for fast and accurate results. + \param n non-negative exponent. Must <= 308. + \return 10.0^n +*/ +inline double Pow10(int n) { + static const double e[] = { // 1e-0...1e308: 309 * 8 bytes = 2472 bytes + 1e+0, + 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10, 1e+11, 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20, + 1e+21, 1e+22, 1e+23, 1e+24, 1e+25, 1e+26, 1e+27, 1e+28, 1e+29, 1e+30, 1e+31, 1e+32, 1e+33, 1e+34, 1e+35, 1e+36, 1e+37, 1e+38, 1e+39, 1e+40, + 1e+41, 1e+42, 1e+43, 1e+44, 1e+45, 1e+46, 1e+47, 1e+48, 1e+49, 1e+50, 1e+51, 1e+52, 1e+53, 1e+54, 1e+55, 1e+56, 1e+57, 1e+58, 1e+59, 1e+60, + 1e+61, 1e+62, 1e+63, 1e+64, 1e+65, 1e+66, 1e+67, 1e+68, 1e+69, 1e+70, 1e+71, 1e+72, 1e+73, 1e+74, 1e+75, 1e+76, 1e+77, 1e+78, 1e+79, 1e+80, + 1e+81, 1e+82, 1e+83, 1e+84, 1e+85, 1e+86, 1e+87, 1e+88, 1e+89, 1e+90, 1e+91, 1e+92, 1e+93, 1e+94, 1e+95, 1e+96, 1e+97, 1e+98, 1e+99, 1e+100, + 1e+101,1e+102,1e+103,1e+104,1e+105,1e+106,1e+107,1e+108,1e+109,1e+110,1e+111,1e+112,1e+113,1e+114,1e+115,1e+116,1e+117,1e+118,1e+119,1e+120, + 1e+121,1e+122,1e+123,1e+124,1e+125,1e+126,1e+127,1e+128,1e+129,1e+130,1e+131,1e+132,1e+133,1e+134,1e+135,1e+136,1e+137,1e+138,1e+139,1e+140, + 1e+141,1e+142,1e+143,1e+144,1e+145,1e+146,1e+147,1e+148,1e+149,1e+150,1e+151,1e+152,1e+153,1e+154,1e+155,1e+156,1e+157,1e+158,1e+159,1e+160, + 1e+161,1e+162,1e+163,1e+164,1e+165,1e+166,1e+167,1e+168,1e+169,1e+170,1e+171,1e+172,1e+173,1e+174,1e+175,1e+176,1e+177,1e+178,1e+179,1e+180, + 1e+181,1e+182,1e+183,1e+184,1e+185,1e+186,1e+187,1e+188,1e+189,1e+190,1e+191,1e+192,1e+193,1e+194,1e+195,1e+196,1e+197,1e+198,1e+199,1e+200, + 1e+201,1e+202,1e+203,1e+204,1e+205,1e+206,1e+207,1e+208,1e+209,1e+210,1e+211,1e+212,1e+213,1e+214,1e+215,1e+216,1e+217,1e+218,1e+219,1e+220, + 1e+221,1e+222,1e+223,1e+224,1e+225,1e+226,1e+227,1e+228,1e+229,1e+230,1e+231,1e+232,1e+233,1e+234,1e+235,1e+236,1e+237,1e+238,1e+239,1e+240, + 1e+241,1e+242,1e+243,1e+244,1e+245,1e+246,1e+247,1e+248,1e+249,1e+250,1e+251,1e+252,1e+253,1e+254,1e+255,1e+256,1e+257,1e+258,1e+259,1e+260, + 1e+261,1e+262,1e+263,1e+264,1e+265,1e+266,1e+267,1e+268,1e+269,1e+270,1e+271,1e+272,1e+273,1e+274,1e+275,1e+276,1e+277,1e+278,1e+279,1e+280, + 1e+281,1e+282,1e+283,1e+284,1e+285,1e+286,1e+287,1e+288,1e+289,1e+290,1e+291,1e+292,1e+293,1e+294,1e+295,1e+296,1e+297,1e+298,1e+299,1e+300, + 1e+301,1e+302,1e+303,1e+304,1e+305,1e+306,1e+307,1e+308 + }; + RAPIDJSON_ASSERT(n >= 0 && n <= 308); + return e[n]; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_POW10_ diff --git a/util/xpack/rapidjson/internal/regex.h b/util/xpack/rapidjson/internal/regex.h new file mode 100755 index 0000000..6a22d5a --- /dev/null +++ b/util/xpack/rapidjson/internal/regex.h @@ -0,0 +1,740 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_REGEX_H_ +#define RAPIDJSON_INTERNAL_REGEX_H_ + +#include "../allocators.h" +#include "../stream.h" +#include "stack.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(implicit-fallthrough) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#if __GNUC__ >= 7 +RAPIDJSON_DIAG_OFF(implicit-fallthrough) +#endif +#endif + +#ifndef RAPIDJSON_REGEX_VERBOSE +#define RAPIDJSON_REGEX_VERBOSE 0 +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +/////////////////////////////////////////////////////////////////////////////// +// DecodedStream + +template +class DecodedStream { +public: + DecodedStream(SourceStream& ss) : ss_(ss), codepoint_() { Decode(); } + unsigned Peek() { return codepoint_; } + unsigned Take() { + unsigned c = codepoint_; + if (c) // No further decoding when '\0' + Decode(); + return c; + } + +private: + void Decode() { + if (!Encoding::Decode(ss_, &codepoint_)) + codepoint_ = 0; + } + + SourceStream& ss_; + unsigned codepoint_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericRegex + +static const SizeType kRegexInvalidState = ~SizeType(0); //!< Represents an invalid index in GenericRegex::State::out, out1 +static const SizeType kRegexInvalidRange = ~SizeType(0); + +template +class GenericRegexSearch; + +//! Regular expression engine with subset of ECMAscript grammar. +/*! + Supported regular expression syntax: + - \c ab Concatenation + - \c a|b Alternation + - \c a? Zero or one + - \c a* Zero or more + - \c a+ One or more + - \c a{3} Exactly 3 times + - \c a{3,} At least 3 times + - \c a{3,5} 3 to 5 times + - \c (ab) Grouping + - \c ^a At the beginning + - \c a$ At the end + - \c . Any character + - \c [abc] Character classes + - \c [a-c] Character class range + - \c [a-z0-9_] Character class combination + - \c [^abc] Negated character classes + - \c [^a-c] Negated character class range + - \c [\b] Backspace (U+0008) + - \c \\| \\\\ ... Escape characters + - \c \\f Form feed (U+000C) + - \c \\n Line feed (U+000A) + - \c \\r Carriage return (U+000D) + - \c \\t Tab (U+0009) + - \c \\v Vertical tab (U+000B) + + \note This is a Thompson NFA engine, implemented with reference to + Cox, Russ. "Regular Expression Matching Can Be Simple And Fast (but is slow in Java, Perl, PHP, Python, Ruby,...).", + https://swtch.com/~rsc/regexp/regexp1.html +*/ +template +class GenericRegex { +public: + typedef Encoding EncodingType; + typedef typename Encoding::Ch Ch; + template friend class GenericRegexSearch; + + GenericRegex(const Ch* source, Allocator* allocator = 0) : + ownAllocator_(allocator ? 0 : RAPIDJSON_NEW(Allocator)()), allocator_(allocator ? allocator : ownAllocator_), + states_(allocator_, 256), ranges_(allocator_, 256), root_(kRegexInvalidState), stateCount_(), rangeCount_(), + anchorBegin_(), anchorEnd_() + { + GenericStringStream ss(source); + DecodedStream, Encoding> ds(ss); + Parse(ds); + } + + ~GenericRegex() + { + RAPIDJSON_DELETE(ownAllocator_); + } + + bool IsValid() const { + return root_ != kRegexInvalidState; + } + +private: + enum Operator { + kZeroOrOne, + kZeroOrMore, + kOneOrMore, + kConcatenation, + kAlternation, + kLeftParenthesis + }; + + static const unsigned kAnyCharacterClass = 0xFFFFFFFF; //!< For '.' + static const unsigned kRangeCharacterClass = 0xFFFFFFFE; + static const unsigned kRangeNegationFlag = 0x80000000; + + struct Range { + unsigned start; // + unsigned end; + SizeType next; + }; + + struct State { + SizeType out; //!< Equals to kInvalid for matching state + SizeType out1; //!< Equals to non-kInvalid for split + SizeType rangeStart; + unsigned codepoint; + }; + + struct Frag { + Frag(SizeType s, SizeType o, SizeType m) : start(s), out(o), minIndex(m) {} + SizeType start; + SizeType out; //!< link-list of all output states + SizeType minIndex; + }; + + State& GetState(SizeType index) { + RAPIDJSON_ASSERT(index < stateCount_); + return states_.template Bottom()[index]; + } + + const State& GetState(SizeType index) const { + RAPIDJSON_ASSERT(index < stateCount_); + return states_.template Bottom()[index]; + } + + Range& GetRange(SizeType index) { + RAPIDJSON_ASSERT(index < rangeCount_); + return ranges_.template Bottom()[index]; + } + + const Range& GetRange(SizeType index) const { + RAPIDJSON_ASSERT(index < rangeCount_); + return ranges_.template Bottom()[index]; + } + + template + void Parse(DecodedStream& ds) { + Stack operandStack(allocator_, 256); // Frag + Stack operatorStack(allocator_, 256); // Operator + Stack atomCountStack(allocator_, 256); // unsigned (Atom per parenthesis) + + *atomCountStack.template Push() = 0; + + unsigned codepoint; + while (ds.Peek() != 0) { + switch (codepoint = ds.Take()) { + case '^': + anchorBegin_ = true; + break; + + case '$': + anchorEnd_ = true; + break; + + case '|': + while (!operatorStack.Empty() && *operatorStack.template Top() < kAlternation) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + *operatorStack.template Push() = kAlternation; + *atomCountStack.template Top() = 0; + break; + + case '(': + *operatorStack.template Push() = kLeftParenthesis; + *atomCountStack.template Push() = 0; + break; + + case ')': + while (!operatorStack.Empty() && *operatorStack.template Top() != kLeftParenthesis) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + if (operatorStack.Empty()) + return; + operatorStack.template Pop(1); + atomCountStack.template Pop(1); + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '?': + if (!Eval(operandStack, kZeroOrOne)) + return; + break; + + case '*': + if (!Eval(operandStack, kZeroOrMore)) + return; + break; + + case '+': + if (!Eval(operandStack, kOneOrMore)) + return; + break; + + case '{': + { + unsigned n, m; + if (!ParseUnsigned(ds, &n)) + return; + + if (ds.Peek() == ',') { + ds.Take(); + if (ds.Peek() == '}') + m = kInfinityQuantifier; + else if (!ParseUnsigned(ds, &m) || m < n) + return; + } + else + m = n; + + if (!EvalQuantifier(operandStack, n, m) || ds.Peek() != '}') + return; + ds.Take(); + } + break; + + case '.': + PushOperand(operandStack, kAnyCharacterClass); + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '[': + { + SizeType range; + if (!ParseRange(ds, &range)) + return; + SizeType s = NewState(kRegexInvalidState, kRegexInvalidState, kRangeCharacterClass); + GetState(s).rangeStart = range; + *operandStack.template Push() = Frag(s, s, s); + } + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '\\': // Escape character + if (!CharacterEscape(ds, &codepoint)) + return; // Unsupported escape character + // fall through to default + + default: // Pattern character + PushOperand(operandStack, codepoint); + ImplicitConcatenation(atomCountStack, operatorStack); + } + } + + while (!operatorStack.Empty()) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + + // Link the operand to matching state. + if (operandStack.GetSize() == sizeof(Frag)) { + Frag* e = operandStack.template Pop(1); + Patch(e->out, NewState(kRegexInvalidState, kRegexInvalidState, 0)); + root_ = e->start; + +#if RAPIDJSON_REGEX_VERBOSE + printf("root: %d\n", root_); + for (SizeType i = 0; i < stateCount_ ; i++) { + State& s = GetState(i); + printf("[%2d] out: %2d out1: %2d c: '%c'\n", i, s.out, s.out1, (char)s.codepoint); + } + printf("\n"); +#endif + } + } + + SizeType NewState(SizeType out, SizeType out1, unsigned codepoint) { + State* s = states_.template Push(); + s->out = out; + s->out1 = out1; + s->codepoint = codepoint; + s->rangeStart = kRegexInvalidRange; + return stateCount_++; + } + + void PushOperand(Stack& operandStack, unsigned codepoint) { + SizeType s = NewState(kRegexInvalidState, kRegexInvalidState, codepoint); + *operandStack.template Push() = Frag(s, s, s); + } + + void ImplicitConcatenation(Stack& atomCountStack, Stack& operatorStack) { + if (*atomCountStack.template Top()) + *operatorStack.template Push() = kConcatenation; + (*atomCountStack.template Top())++; + } + + SizeType Append(SizeType l1, SizeType l2) { + SizeType old = l1; + while (GetState(l1).out != kRegexInvalidState) + l1 = GetState(l1).out; + GetState(l1).out = l2; + return old; + } + + void Patch(SizeType l, SizeType s) { + for (SizeType next; l != kRegexInvalidState; l = next) { + next = GetState(l).out; + GetState(l).out = s; + } + } + + bool Eval(Stack& operandStack, Operator op) { + switch (op) { + case kConcatenation: + RAPIDJSON_ASSERT(operandStack.GetSize() >= sizeof(Frag) * 2); + { + Frag e2 = *operandStack.template Pop(1); + Frag e1 = *operandStack.template Pop(1); + Patch(e1.out, e2.start); + *operandStack.template Push() = Frag(e1.start, e2.out, Min(e1.minIndex, e2.minIndex)); + } + return true; + + case kAlternation: + if (operandStack.GetSize() >= sizeof(Frag) * 2) { + Frag e2 = *operandStack.template Pop(1); + Frag e1 = *operandStack.template Pop(1); + SizeType s = NewState(e1.start, e2.start, 0); + *operandStack.template Push() = Frag(s, Append(e1.out, e2.out), Min(e1.minIndex, e2.minIndex)); + return true; + } + return false; + + case kZeroOrOne: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + *operandStack.template Push() = Frag(s, Append(e.out, s), e.minIndex); + return true; + } + return false; + + case kZeroOrMore: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + Patch(e.out, s); + *operandStack.template Push() = Frag(s, s, e.minIndex); + return true; + } + return false; + + case kOneOrMore: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + Patch(e.out, s); + *operandStack.template Push() = Frag(e.start, s, e.minIndex); + return true; + } + return false; + + default: + // syntax error (e.g. unclosed kLeftParenthesis) + return false; + } + } + + bool EvalQuantifier(Stack& operandStack, unsigned n, unsigned m) { + RAPIDJSON_ASSERT(n <= m); + RAPIDJSON_ASSERT(operandStack.GetSize() >= sizeof(Frag)); + + if (n == 0) { + if (m == 0) // a{0} not support + return false; + else if (m == kInfinityQuantifier) + Eval(operandStack, kZeroOrMore); // a{0,} -> a* + else { + Eval(operandStack, kZeroOrOne); // a{0,5} -> a? + for (unsigned i = 0; i < m - 1; i++) + CloneTopOperand(operandStack); // a{0,5} -> a? a? a? a? a? + for (unsigned i = 0; i < m - 1; i++) + Eval(operandStack, kConcatenation); // a{0,5} -> a?a?a?a?a? + } + return true; + } + + for (unsigned i = 0; i < n - 1; i++) // a{3} -> a a a + CloneTopOperand(operandStack); + + if (m == kInfinityQuantifier) + Eval(operandStack, kOneOrMore); // a{3,} -> a a a+ + else if (m > n) { + CloneTopOperand(operandStack); // a{3,5} -> a a a a + Eval(operandStack, kZeroOrOne); // a{3,5} -> a a a a? + for (unsigned i = n; i < m - 1; i++) + CloneTopOperand(operandStack); // a{3,5} -> a a a a? a? + for (unsigned i = n; i < m; i++) + Eval(operandStack, kConcatenation); // a{3,5} -> a a aa?a? + } + + for (unsigned i = 0; i < n - 1; i++) + Eval(operandStack, kConcatenation); // a{3} -> aaa, a{3,} -> aaa+, a{3.5} -> aaaa?a? + + return true; + } + + static SizeType Min(SizeType a, SizeType b) { return a < b ? a : b; } + + void CloneTopOperand(Stack& operandStack) { + const Frag src = *operandStack.template Top(); // Copy constructor to prevent invalidation + SizeType count = stateCount_ - src.minIndex; // Assumes top operand contains states in [src->minIndex, stateCount_) + State* s = states_.template Push(count); + memcpy(s, &GetState(src.minIndex), count * sizeof(State)); + for (SizeType j = 0; j < count; j++) { + if (s[j].out != kRegexInvalidState) + s[j].out += count; + if (s[j].out1 != kRegexInvalidState) + s[j].out1 += count; + } + *operandStack.template Push() = Frag(src.start + count, src.out + count, src.minIndex + count); + stateCount_ += count; + } + + template + bool ParseUnsigned(DecodedStream& ds, unsigned* u) { + unsigned r = 0; + if (ds.Peek() < '0' || ds.Peek() > '9') + return false; + while (ds.Peek() >= '0' && ds.Peek() <= '9') { + if (r >= 429496729 && ds.Peek() > '5') // 2^32 - 1 = 4294967295 + return false; // overflow + r = r * 10 + (ds.Take() - '0'); + } + *u = r; + return true; + } + + template + bool ParseRange(DecodedStream& ds, SizeType* range) { + bool isBegin = true; + bool negate = false; + int step = 0; + SizeType start = kRegexInvalidRange; + SizeType current = kRegexInvalidRange; + unsigned codepoint; + while ((codepoint = ds.Take()) != 0) { + if (isBegin) { + isBegin = false; + if (codepoint == '^') { + negate = true; + continue; + } + } + + switch (codepoint) { + case ']': + if (start == kRegexInvalidRange) + return false; // Error: nothing inside [] + if (step == 2) { // Add trailing '-' + SizeType r = NewRange('-'); + RAPIDJSON_ASSERT(current != kRegexInvalidRange); + GetRange(current).next = r; + } + if (negate) + GetRange(start).start |= kRangeNegationFlag; + *range = start; + return true; + + case '\\': + if (ds.Peek() == 'b') { + ds.Take(); + codepoint = 0x0008; // Escape backspace character + } + else if (!CharacterEscape(ds, &codepoint)) + return false; + // fall through to default + + default: + switch (step) { + case 1: + if (codepoint == '-') { + step++; + break; + } + // fall through to step 0 for other characters + + case 0: + { + SizeType r = NewRange(codepoint); + if (current != kRegexInvalidRange) + GetRange(current).next = r; + if (start == kRegexInvalidRange) + start = r; + current = r; + } + step = 1; + break; + + default: + RAPIDJSON_ASSERT(step == 2); + GetRange(current).end = codepoint; + step = 0; + } + } + } + return false; + } + + SizeType NewRange(unsigned codepoint) { + Range* r = ranges_.template Push(); + r->start = r->end = codepoint; + r->next = kRegexInvalidRange; + return rangeCount_++; + } + + template + bool CharacterEscape(DecodedStream& ds, unsigned* escapedCodepoint) { + unsigned codepoint; + switch (codepoint = ds.Take()) { + case '^': + case '$': + case '|': + case '(': + case ')': + case '?': + case '*': + case '+': + case '.': + case '[': + case ']': + case '{': + case '}': + case '\\': + *escapedCodepoint = codepoint; return true; + case 'f': *escapedCodepoint = 0x000C; return true; + case 'n': *escapedCodepoint = 0x000A; return true; + case 'r': *escapedCodepoint = 0x000D; return true; + case 't': *escapedCodepoint = 0x0009; return true; + case 'v': *escapedCodepoint = 0x000B; return true; + default: + return false; // Unsupported escape character + } + } + + Allocator* ownAllocator_; + Allocator* allocator_; + Stack states_; + Stack ranges_; + SizeType root_; + SizeType stateCount_; + SizeType rangeCount_; + + static const unsigned kInfinityQuantifier = ~0u; + + // For SearchWithAnchoring() + bool anchorBegin_; + bool anchorEnd_; +}; + +template +class GenericRegexSearch { +public: + typedef typename RegexType::EncodingType Encoding; + typedef typename Encoding::Ch Ch; + + GenericRegexSearch(const RegexType& regex, Allocator* allocator = 0) : + regex_(regex), allocator_(allocator), ownAllocator_(0), + state0_(allocator, 0), state1_(allocator, 0), stateSet_() + { + RAPIDJSON_ASSERT(regex_.IsValid()); + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + stateSet_ = static_cast(allocator_->Malloc(GetStateSetSize())); + state0_.template Reserve(regex_.stateCount_); + state1_.template Reserve(regex_.stateCount_); + } + + ~GenericRegexSearch() { + Allocator::Free(stateSet_); + RAPIDJSON_DELETE(ownAllocator_); + } + + template + bool Match(InputStream& is) { + return SearchWithAnchoring(is, true, true); + } + + bool Match(const Ch* s) { + GenericStringStream is(s); + return Match(is); + } + + template + bool Search(InputStream& is) { + return SearchWithAnchoring(is, regex_.anchorBegin_, regex_.anchorEnd_); + } + + bool Search(const Ch* s) { + GenericStringStream is(s); + return Search(is); + } + +private: + typedef typename RegexType::State State; + typedef typename RegexType::Range Range; + + template + bool SearchWithAnchoring(InputStream& is, bool anchorBegin, bool anchorEnd) { + DecodedStream ds(is); + + state0_.Clear(); + Stack *current = &state0_, *next = &state1_; + const size_t stateSetSize = GetStateSetSize(); + std::memset(stateSet_, 0, stateSetSize); + + bool matched = AddState(*current, regex_.root_); + unsigned codepoint; + while (!current->Empty() && (codepoint = ds.Take()) != 0) { + std::memset(stateSet_, 0, stateSetSize); + next->Clear(); + matched = false; + for (const SizeType* s = current->template Bottom(); s != current->template End(); ++s) { + const State& sr = regex_.GetState(*s); + if (sr.codepoint == codepoint || + sr.codepoint == RegexType::kAnyCharacterClass || + (sr.codepoint == RegexType::kRangeCharacterClass && MatchRange(sr.rangeStart, codepoint))) + { + matched = AddState(*next, sr.out) || matched; + if (!anchorEnd && matched) + return true; + } + if (!anchorBegin) + AddState(*next, regex_.root_); + } + internal::Swap(current, next); + } + + return matched; + } + + size_t GetStateSetSize() const { + return (regex_.stateCount_ + 31) / 32 * 4; + } + + // Return whether the added states is a match state + bool AddState(Stack& l, SizeType index) { + RAPIDJSON_ASSERT(index != kRegexInvalidState); + + const State& s = regex_.GetState(index); + if (s.out1 != kRegexInvalidState) { // Split + bool matched = AddState(l, s.out); + return AddState(l, s.out1) || matched; + } + else if (!(stateSet_[index >> 5] & (1u << (index & 31)))) { + stateSet_[index >> 5] |= (1u << (index & 31)); + *l.template PushUnsafe() = index; + } + return s.out == kRegexInvalidState; // by using PushUnsafe() above, we can ensure s is not validated due to reallocation. + } + + bool MatchRange(SizeType rangeIndex, unsigned codepoint) const { + bool yes = (regex_.GetRange(rangeIndex).start & RegexType::kRangeNegationFlag) == 0; + while (rangeIndex != kRegexInvalidRange) { + const Range& r = regex_.GetRange(rangeIndex); + if (codepoint >= (r.start & ~RegexType::kRangeNegationFlag) && codepoint <= r.end) + return yes; + rangeIndex = r.next; + } + return !yes; + } + + const RegexType& regex_; + Allocator* allocator_; + Allocator* ownAllocator_; + Stack state0_; + Stack state1_; + uint32_t* stateSet_; +}; + +typedef GenericRegex > Regex; +typedef GenericRegexSearch RegexSearch; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_REGEX_H_ diff --git a/util/xpack/rapidjson/internal/stack.h b/util/xpack/rapidjson/internal/stack.h new file mode 100755 index 0000000..cc6d0e7 --- /dev/null +++ b/util/xpack/rapidjson/internal/stack.h @@ -0,0 +1,232 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_STACK_H_ +#define RAPIDJSON_INTERNAL_STACK_H_ + +#include "../allocators.h" +#include "swap.h" +#include + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +/////////////////////////////////////////////////////////////////////////////// +// Stack + +//! A type-unsafe stack for storing different types of data. +/*! \tparam Allocator Allocator for allocating stack memory. +*/ +template +class Stack { +public: + // Optimization note: Do not allocate memory for stack_ in constructor. + // Do it lazily when first Push() -> Expand() -> Resize(). + Stack(Allocator* allocator, size_t stackCapacity) : allocator_(allocator), ownAllocator_(0), stack_(0), stackTop_(0), stackEnd_(0), initialCapacity_(stackCapacity) { + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Stack(Stack&& rhs) + : allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + stack_(rhs.stack_), + stackTop_(rhs.stackTop_), + stackEnd_(rhs.stackEnd_), + initialCapacity_(rhs.initialCapacity_) + { + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.stack_ = 0; + rhs.stackTop_ = 0; + rhs.stackEnd_ = 0; + rhs.initialCapacity_ = 0; + } +#endif + + ~Stack() { + Destroy(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Stack& operator=(Stack&& rhs) { + if (&rhs != this) + { + Destroy(); + + allocator_ = rhs.allocator_; + ownAllocator_ = rhs.ownAllocator_; + stack_ = rhs.stack_; + stackTop_ = rhs.stackTop_; + stackEnd_ = rhs.stackEnd_; + initialCapacity_ = rhs.initialCapacity_; + + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.stack_ = 0; + rhs.stackTop_ = 0; + rhs.stackEnd_ = 0; + rhs.initialCapacity_ = 0; + } + return *this; + } +#endif + + void Swap(Stack& rhs) RAPIDJSON_NOEXCEPT { + internal::Swap(allocator_, rhs.allocator_); + internal::Swap(ownAllocator_, rhs.ownAllocator_); + internal::Swap(stack_, rhs.stack_); + internal::Swap(stackTop_, rhs.stackTop_); + internal::Swap(stackEnd_, rhs.stackEnd_); + internal::Swap(initialCapacity_, rhs.initialCapacity_); + } + + void Clear() { stackTop_ = stack_; } + + void ShrinkToFit() { + if (Empty()) { + // If the stack is empty, completely deallocate the memory. + Allocator::Free(stack_); // NOLINT (+clang-analyzer-unix.Malloc) + stack_ = 0; + stackTop_ = 0; + stackEnd_ = 0; + } + else + Resize(GetSize()); + } + + // Optimization note: try to minimize the size of this function for force inline. + // Expansion is run very infrequently, so it is moved to another (probably non-inline) function. + template + RAPIDJSON_FORCEINLINE void Reserve(size_t count = 1) { + // Expand the stack if needed + if (RAPIDJSON_UNLIKELY(static_cast(sizeof(T) * count) > (stackEnd_ - stackTop_))) + Expand(count); + } + + template + RAPIDJSON_FORCEINLINE T* Push(size_t count = 1) { + Reserve(count); + return PushUnsafe(count); + } + + template + RAPIDJSON_FORCEINLINE T* PushUnsafe(size_t count = 1) { + RAPIDJSON_ASSERT(stackTop_); + RAPIDJSON_ASSERT(static_cast(sizeof(T) * count) <= (stackEnd_ - stackTop_)); + T* ret = reinterpret_cast(stackTop_); + stackTop_ += sizeof(T) * count; + return ret; + } + + template + T* Pop(size_t count) { + RAPIDJSON_ASSERT(GetSize() >= count * sizeof(T)); + stackTop_ -= count * sizeof(T); + return reinterpret_cast(stackTop_); + } + + template + T* Top() { + RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); + return reinterpret_cast(stackTop_ - sizeof(T)); + } + + template + const T* Top() const { + RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); + return reinterpret_cast(stackTop_ - sizeof(T)); + } + + template + T* End() { return reinterpret_cast(stackTop_); } + + template + const T* End() const { return reinterpret_cast(stackTop_); } + + template + T* Bottom() { return reinterpret_cast(stack_); } + + template + const T* Bottom() const { return reinterpret_cast(stack_); } + + bool HasAllocator() const { + return allocator_ != 0; + } + + Allocator& GetAllocator() { + RAPIDJSON_ASSERT(allocator_); + return *allocator_; + } + + bool Empty() const { return stackTop_ == stack_; } + size_t GetSize() const { return static_cast(stackTop_ - stack_); } + size_t GetCapacity() const { return static_cast(stackEnd_ - stack_); } + +private: + template + void Expand(size_t count) { + // Only expand the capacity if the current stack exists. Otherwise just create a stack with initial capacity. + size_t newCapacity; + if (stack_ == 0) { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + newCapacity = initialCapacity_; + } else { + newCapacity = GetCapacity(); + newCapacity += (newCapacity + 1) / 2; + } + size_t newSize = GetSize() + sizeof(T) * count; + if (newCapacity < newSize) + newCapacity = newSize; + + Resize(newCapacity); + } + + void Resize(size_t newCapacity) { + const size_t size = GetSize(); // Backup the current size + stack_ = static_cast(allocator_->Realloc(stack_, GetCapacity(), newCapacity)); + stackTop_ = stack_ + size; + stackEnd_ = stack_ + newCapacity; + } + + void Destroy() { + Allocator::Free(stack_); + RAPIDJSON_DELETE(ownAllocator_); // Only delete if it is owned by the stack + } + + // Prohibit copy constructor & assignment operator. + Stack(const Stack&); + Stack& operator=(const Stack&); + + Allocator* allocator_; + Allocator* ownAllocator_; + char *stack_; + char *stackTop_; + char *stackEnd_; + size_t initialCapacity_; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_STACK_H_ diff --git a/util/xpack/rapidjson/internal/strfunc.h b/util/xpack/rapidjson/internal/strfunc.h new file mode 100755 index 0000000..b7e41a3 --- /dev/null +++ b/util/xpack/rapidjson/internal/strfunc.h @@ -0,0 +1,69 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_STRFUNC_H_ +#define RAPIDJSON_INTERNAL_STRFUNC_H_ + +#include "../stream.h" +#include + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Custom strlen() which works on different character types. +/*! \tparam Ch Character type (e.g. char, wchar_t, short) + \param s Null-terminated input string. + \return Number of characters in the string. + \note This has the same semantics as strlen(), the return value is not number of Unicode codepoints. +*/ +template +inline SizeType StrLen(const Ch* s) { + RAPIDJSON_ASSERT(s != 0); + const Ch* p = s; + while (*p) ++p; + return SizeType(p - s); +} + +template <> +inline SizeType StrLen(const char* s) { + return SizeType(std::strlen(s)); +} + +template <> +inline SizeType StrLen(const wchar_t* s) { + return SizeType(std::wcslen(s)); +} + +//! Returns number of code points in a encoded string. +template +bool CountStringCodePoint(const typename Encoding::Ch* s, SizeType length, SizeType* outCount) { + RAPIDJSON_ASSERT(s != 0); + RAPIDJSON_ASSERT(outCount != 0); + GenericStringStream is(s); + const typename Encoding::Ch* end = s + length; + SizeType count = 0; + while (is.src_ < end) { + unsigned codepoint; + if (!Encoding::Decode(is, &codepoint)) + return false; + count++; + } + *outCount = count; + return true; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_INTERNAL_STRFUNC_H_ diff --git a/util/xpack/rapidjson/internal/strtod.h b/util/xpack/rapidjson/internal/strtod.h new file mode 100755 index 0000000..c1a302a --- /dev/null +++ b/util/xpack/rapidjson/internal/strtod.h @@ -0,0 +1,290 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_STRTOD_ +#define RAPIDJSON_STRTOD_ + +#include "ieee754.h" +#include "biginteger.h" +#include "diyfp.h" +#include "pow10.h" +#include +#include + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +inline double FastPath(double significand, int exp) { + if (exp < -308) + return 0.0; + else if (exp >= 0) + return significand * internal::Pow10(exp); + else + return significand / internal::Pow10(-exp); +} + +inline double StrtodNormalPrecision(double d, int p) { + if (p < -308) { + // Prevent expSum < -308, making Pow10(p) = 0 + d = FastPath(d, -308); + d = FastPath(d, p + 308); + } + else + d = FastPath(d, p); + return d; +} + +template +inline T Min3(T a, T b, T c) { + T m = a; + if (m > b) m = b; + if (m > c) m = c; + return m; +} + +inline int CheckWithinHalfULP(double b, const BigInteger& d, int dExp) { + const Double db(b); + const uint64_t bInt = db.IntegerSignificand(); + const int bExp = db.IntegerExponent(); + const int hExp = bExp - 1; + + int dS_Exp2 = 0, dS_Exp5 = 0, bS_Exp2 = 0, bS_Exp5 = 0, hS_Exp2 = 0, hS_Exp5 = 0; + + // Adjust for decimal exponent + if (dExp >= 0) { + dS_Exp2 += dExp; + dS_Exp5 += dExp; + } + else { + bS_Exp2 -= dExp; + bS_Exp5 -= dExp; + hS_Exp2 -= dExp; + hS_Exp5 -= dExp; + } + + // Adjust for binary exponent + if (bExp >= 0) + bS_Exp2 += bExp; + else { + dS_Exp2 -= bExp; + hS_Exp2 -= bExp; + } + + // Adjust for half ulp exponent + if (hExp >= 0) + hS_Exp2 += hExp; + else { + dS_Exp2 -= hExp; + bS_Exp2 -= hExp; + } + + // Remove common power of two factor from all three scaled values + int common_Exp2 = Min3(dS_Exp2, bS_Exp2, hS_Exp2); + dS_Exp2 -= common_Exp2; + bS_Exp2 -= common_Exp2; + hS_Exp2 -= common_Exp2; + + BigInteger dS = d; + dS.MultiplyPow5(static_cast(dS_Exp5)) <<= static_cast(dS_Exp2); + + BigInteger bS(bInt); + bS.MultiplyPow5(static_cast(bS_Exp5)) <<= static_cast(bS_Exp2); + + BigInteger hS(1); + hS.MultiplyPow5(static_cast(hS_Exp5)) <<= static_cast(hS_Exp2); + + BigInteger delta(0); + dS.Difference(bS, &delta); + + return delta.Compare(hS); +} + +inline bool StrtodFast(double d, int p, double* result) { + // Use fast path for string-to-double conversion if possible + // see http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ + if (p > 22 && p < 22 + 16) { + // Fast Path Cases In Disguise + d *= internal::Pow10(p - 22); + p = 22; + } + + if (p >= -22 && p <= 22 && d <= 9007199254740991.0) { // 2^53 - 1 + *result = FastPath(d, p); + return true; + } + else + return false; +} + +// Compute an approximation and see if it is within 1/2 ULP +inline bool StrtodDiyFp(const char* decimals, int dLen, int dExp, double* result) { + uint64_t significand = 0; + int i = 0; // 2^64 - 1 = 18446744073709551615, 1844674407370955161 = 0x1999999999999999 + for (; i < dLen; i++) { + if (significand > RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || + (significand == RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) && decimals[i] > '5')) + break; + significand = significand * 10u + static_cast(decimals[i] - '0'); + } + + if (i < dLen && decimals[i] >= '5') // Rounding + significand++; + + int remaining = dLen - i; + const int kUlpShift = 3; + const int kUlp = 1 << kUlpShift; + int64_t error = (remaining == 0) ? 0 : kUlp / 2; + + DiyFp v(significand, 0); + v = v.Normalize(); + error <<= -v.e; + + dExp += remaining; + + int actualExp; + DiyFp cachedPower = GetCachedPower10(dExp, &actualExp); + if (actualExp != dExp) { + static const DiyFp kPow10[] = { + DiyFp(RAPIDJSON_UINT64_C2(0xa0000000, 0x00000000), -60), // 10^1 + DiyFp(RAPIDJSON_UINT64_C2(0xc8000000, 0x00000000), -57), // 10^2 + DiyFp(RAPIDJSON_UINT64_C2(0xfa000000, 0x00000000), -54), // 10^3 + DiyFp(RAPIDJSON_UINT64_C2(0x9c400000, 0x00000000), -50), // 10^4 + DiyFp(RAPIDJSON_UINT64_C2(0xc3500000, 0x00000000), -47), // 10^5 + DiyFp(RAPIDJSON_UINT64_C2(0xf4240000, 0x00000000), -44), // 10^6 + DiyFp(RAPIDJSON_UINT64_C2(0x98968000, 0x00000000), -40) // 10^7 + }; + int adjustment = dExp - actualExp; + RAPIDJSON_ASSERT(adjustment >= 1 && adjustment < 8); + v = v * kPow10[adjustment - 1]; + if (dLen + adjustment > 19) // has more digits than decimal digits in 64-bit + error += kUlp / 2; + } + + v = v * cachedPower; + + error += kUlp + (error == 0 ? 0 : 1); + + const int oldExp = v.e; + v = v.Normalize(); + error <<= oldExp - v.e; + + const int effectiveSignificandSize = Double::EffectiveSignificandSize(64 + v.e); + int precisionSize = 64 - effectiveSignificandSize; + if (precisionSize + kUlpShift >= 64) { + int scaleExp = (precisionSize + kUlpShift) - 63; + v.f >>= scaleExp; + v.e += scaleExp; + error = (error >> scaleExp) + 1 + kUlp; + precisionSize -= scaleExp; + } + + DiyFp rounded(v.f >> precisionSize, v.e + precisionSize); + const uint64_t precisionBits = (v.f & ((uint64_t(1) << precisionSize) - 1)) * kUlp; + const uint64_t halfWay = (uint64_t(1) << (precisionSize - 1)) * kUlp; + if (precisionBits >= halfWay + static_cast(error)) { + rounded.f++; + if (rounded.f & (DiyFp::kDpHiddenBit << 1)) { // rounding overflows mantissa (issue #340) + rounded.f >>= 1; + rounded.e++; + } + } + + *result = rounded.ToDouble(); + + return halfWay - static_cast(error) >= precisionBits || precisionBits >= halfWay + static_cast(error); +} + +inline double StrtodBigInteger(double approx, const char* decimals, int dLen, int dExp) { + RAPIDJSON_ASSERT(dLen >= 0); + const BigInteger dInt(decimals, static_cast(dLen)); + Double a(approx); + int cmp = CheckWithinHalfULP(a.Value(), dInt, dExp); + if (cmp < 0) + return a.Value(); // within half ULP + else if (cmp == 0) { + // Round towards even + if (a.Significand() & 1) + return a.NextPositiveDouble(); + else + return a.Value(); + } + else // adjustment + return a.NextPositiveDouble(); +} + +inline double StrtodFullPrecision(double d, int p, const char* decimals, size_t length, size_t decimalPosition, int exp) { + RAPIDJSON_ASSERT(d >= 0.0); + RAPIDJSON_ASSERT(length >= 1); + + double result = 0.0; + if (StrtodFast(d, p, &result)) + return result; + + RAPIDJSON_ASSERT(length <= INT_MAX); + int dLen = static_cast(length); + + RAPIDJSON_ASSERT(length >= decimalPosition); + RAPIDJSON_ASSERT(length - decimalPosition <= INT_MAX); + int dExpAdjust = static_cast(length - decimalPosition); + + RAPIDJSON_ASSERT(exp >= INT_MIN + dExpAdjust); + int dExp = exp - dExpAdjust; + + // Make sure length+dExp does not overflow + RAPIDJSON_ASSERT(dExp <= INT_MAX - dLen); + + // Trim leading zeros + while (dLen > 0 && *decimals == '0') { + dLen--; + decimals++; + } + + // Trim trailing zeros + while (dLen > 0 && decimals[dLen - 1] == '0') { + dLen--; + dExp++; + } + + if (dLen == 0) { // Buffer only contains zeros. + return 0.0; + } + + // Trim right-most digits + const int kMaxDecimalDigit = 767 + 1; + if (dLen > kMaxDecimalDigit) { + dExp += dLen - kMaxDecimalDigit; + dLen = kMaxDecimalDigit; + } + + // If too small, underflow to zero. + // Any x <= 10^-324 is interpreted as zero. + if (dLen + dExp <= -324) + return 0.0; + + // If too large, overflow to infinity. + // Any x >= 10^309 is interpreted as +infinity. + if (dLen + dExp > 309) + return std::numeric_limits::infinity(); + + if (StrtodDiyFp(decimals, dLen, dExp, &result)) + return result; + + // Use approximation from StrtodDiyFp and make adjustment with BigInteger comparison + return StrtodBigInteger(result, decimals, dLen, dExp); +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_STRTOD_ diff --git a/util/xpack/rapidjson/internal/swap.h b/util/xpack/rapidjson/internal/swap.h new file mode 100755 index 0000000..cbb2abd --- /dev/null +++ b/util/xpack/rapidjson/internal/swap.h @@ -0,0 +1,46 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_SWAP_H_ +#define RAPIDJSON_INTERNAL_SWAP_H_ + +#include "../rapidjson.h" + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Custom swap() to avoid dependency on C++ header +/*! \tparam T Type of the arguments to swap, should be instantiated with primitive C++ types only. + \note This has the same semantics as std::swap(). +*/ +template +inline void Swap(T& a, T& b) RAPIDJSON_NOEXCEPT { + T tmp = a; + a = b; + b = tmp; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_SWAP_H_ diff --git a/util/xpack/rapidjson/istreamwrapper.h b/util/xpack/rapidjson/istreamwrapper.h new file mode 100755 index 0000000..c9f1a77 --- /dev/null +++ b/util/xpack/rapidjson/istreamwrapper.h @@ -0,0 +1,127 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ISTREAMWRAPPER_H_ +#define RAPIDJSON_ISTREAMWRAPPER_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4351) // new behavior: elements of array 'array' will be default initialized +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of \c std::basic_istream into RapidJSON's Stream concept. +/*! + The classes can be wrapped including but not limited to: + + - \c std::istringstream + - \c std::stringstream + - \c std::wistringstream + - \c std::wstringstream + - \c std::ifstream + - \c std::fstream + - \c std::wifstream + - \c std::wfstream + + \tparam StreamType Class derived from \c std::basic_istream. +*/ + +template +class BasicIStreamWrapper { +public: + typedef typename StreamType::char_type Ch; + + //! Constructor. + /*! + \param stream stream opened for read. + */ + BasicIStreamWrapper(StreamType &stream) : stream_(stream), buffer_(peekBuffer_), bufferSize_(4), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) { + Read(); + } + + //! Constructor. + /*! + \param stream stream opened for read. + \param buffer user-supplied buffer. + \param bufferSize size of buffer in bytes. Must >=4 bytes. + */ + BasicIStreamWrapper(StreamType &stream, char* buffer, size_t bufferSize) : stream_(stream), buffer_(buffer), bufferSize_(bufferSize), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) { + RAPIDJSON_ASSERT(bufferSize >= 4); + Read(); + } + + Ch Peek() const { return *current_; } + Ch Take() { Ch c = *current_; Read(); return c; } + size_t Tell() const { return count_ + static_cast(current_ - buffer_); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return (current_ + 4 - !eof_ <= bufferLast_) ? current_ : 0; + } + +private: + BasicIStreamWrapper(); + BasicIStreamWrapper(const BasicIStreamWrapper&); + BasicIStreamWrapper& operator=(const BasicIStreamWrapper&); + + void Read() { + if (current_ < bufferLast_) + ++current_; + else if (!eof_) { + count_ += readCount_; + readCount_ = bufferSize_; + bufferLast_ = buffer_ + readCount_ - 1; + current_ = buffer_; + + if (!stream_.read(buffer_, static_cast(bufferSize_))) { + readCount_ = static_cast(stream_.gcount()); + *(bufferLast_ = buffer_ + readCount_) = '\0'; + eof_ = true; + } + } + } + + StreamType &stream_; + Ch peekBuffer_[4], *buffer_; + size_t bufferSize_; + Ch *bufferLast_; + Ch *current_; + size_t readCount_; + size_t count_; //!< Number of characters read + bool eof_; +}; + +typedef BasicIStreamWrapper IStreamWrapper; +typedef BasicIStreamWrapper WIStreamWrapper; + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ISTREAMWRAPPER_H_ diff --git a/util/xpack/rapidjson/memorybuffer.h b/util/xpack/rapidjson/memorybuffer.h new file mode 100755 index 0000000..54157f1 --- /dev/null +++ b/util/xpack/rapidjson/memorybuffer.h @@ -0,0 +1,70 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_MEMORYBUFFER_H_ +#define RAPIDJSON_MEMORYBUFFER_H_ + +#include "stream.h" +#include "internal/stack.h" + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory output byte stream. +/*! + This class is mainly for being wrapped by EncodedOutputStream or AutoUTFOutputStream. + + It is similar to FileWriteBuffer but the destination is an in-memory buffer instead of a file. + + Differences between MemoryBuffer and StringBuffer: + 1. StringBuffer has Encoding but MemoryBuffer is only a byte buffer. + 2. StringBuffer::GetString() returns a null-terminated string. MemoryBuffer::GetBuffer() returns a buffer without terminator. + + \tparam Allocator type for allocating memory buffer. + \note implements Stream concept +*/ +template +struct GenericMemoryBuffer { + typedef char Ch; // byte + + GenericMemoryBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} + + void Put(Ch c) { *stack_.template Push() = c; } + void Flush() {} + + void Clear() { stack_.Clear(); } + void ShrinkToFit() { stack_.ShrinkToFit(); } + Ch* Push(size_t count) { return stack_.template Push(count); } + void Pop(size_t count) { stack_.template Pop(count); } + + const Ch* GetBuffer() const { + return stack_.template Bottom(); + } + + size_t GetSize() const { return stack_.GetSize(); } + + static const size_t kDefaultCapacity = 256; + mutable internal::Stack stack_; +}; + +typedef GenericMemoryBuffer<> MemoryBuffer; + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(MemoryBuffer& memoryBuffer, char c, size_t n) { + std::memset(memoryBuffer.stack_.Push(n), c, n * sizeof(c)); +} + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_MEMORYBUFFER_H_ diff --git a/util/xpack/rapidjson/memorystream.h b/util/xpack/rapidjson/memorystream.h new file mode 100755 index 0000000..cbfe0f2 --- /dev/null +++ b/util/xpack/rapidjson/memorystream.h @@ -0,0 +1,71 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_MEMORYSTREAM_H_ +#define RAPIDJSON_MEMORYSTREAM_H_ + +#include "stream.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(unreachable-code) +RAPIDJSON_DIAG_OFF(missing-noreturn) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory input byte stream. +/*! + This class is mainly for being wrapped by EncodedInputStream or AutoUTFInputStream. + + It is similar to FileReadBuffer but the source is an in-memory buffer instead of a file. + + Differences between MemoryStream and StringStream: + 1. StringStream has encoding but MemoryStream is a byte stream. + 2. MemoryStream needs size of the source buffer and the buffer don't need to be null terminated. StringStream assume null-terminated string as source. + 3. MemoryStream supports Peek4() for encoding detection. StringStream is specified with an encoding so it should not have Peek4(). + \note implements Stream concept +*/ +struct MemoryStream { + typedef char Ch; // byte + + MemoryStream(const Ch *src, size_t size) : src_(src), begin_(src), end_(src + size), size_(size) {} + + Ch Peek() const { return RAPIDJSON_UNLIKELY(src_ == end_) ? '\0' : *src_; } + Ch Take() { return RAPIDJSON_UNLIKELY(src_ == end_) ? '\0' : *src_++; } + size_t Tell() const { return static_cast(src_ - begin_); } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return Tell() + 4 <= size_ ? src_ : 0; + } + + const Ch* src_; //!< Current read position. + const Ch* begin_; //!< Original head of the string. + const Ch* end_; //!< End of stream. + size_t size_; //!< Size of the stream. +}; + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_MEMORYBUFFER_H_ diff --git a/util/xpack/rapidjson/msinttypes/inttypes.h b/util/xpack/rapidjson/msinttypes/inttypes.h new file mode 100755 index 0000000..3f4bd08 --- /dev/null +++ b/util/xpack/rapidjson/msinttypes/inttypes.h @@ -0,0 +1,316 @@ +// ISO C9x compliant inttypes.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2013 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the product nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +// The above software in this distribution may have been modified by +// THL A29 Limited ("Tencent Modifications"). +// All Tencent Modifications are Copyright (C) 2015 THL A29 Limited. + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_INTTYPES_H_ // [ +#define _MSC_INTTYPES_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include "stdint.h" + +// miloyip: VC supports inttypes.h since VC2013 +#if _MSC_VER >= 1800 +#include +#else + +// 7.8 Format conversion of integer types + +typedef struct { + intmax_t quot; + intmax_t rem; +} imaxdiv_t; + +// 7.8.1 Macros for format specifiers + +#if !defined(__cplusplus) || defined(__STDC_FORMAT_MACROS) // [ See footnote 185 at page 198 + +// The fprintf macros for signed integers are: +#define PRId8 "d" +#define PRIi8 "i" +#define PRIdLEAST8 "d" +#define PRIiLEAST8 "i" +#define PRIdFAST8 "d" +#define PRIiFAST8 "i" + +#define PRId16 "hd" +#define PRIi16 "hi" +#define PRIdLEAST16 "hd" +#define PRIiLEAST16 "hi" +#define PRIdFAST16 "hd" +#define PRIiFAST16 "hi" + +#define PRId32 "I32d" +#define PRIi32 "I32i" +#define PRIdLEAST32 "I32d" +#define PRIiLEAST32 "I32i" +#define PRIdFAST32 "I32d" +#define PRIiFAST32 "I32i" + +#define PRId64 "I64d" +#define PRIi64 "I64i" +#define PRIdLEAST64 "I64d" +#define PRIiLEAST64 "I64i" +#define PRIdFAST64 "I64d" +#define PRIiFAST64 "I64i" + +#define PRIdMAX "I64d" +#define PRIiMAX "I64i" + +#define PRIdPTR "Id" +#define PRIiPTR "Ii" + +// The fprintf macros for unsigned integers are: +#define PRIo8 "o" +#define PRIu8 "u" +#define PRIx8 "x" +#define PRIX8 "X" +#define PRIoLEAST8 "o" +#define PRIuLEAST8 "u" +#define PRIxLEAST8 "x" +#define PRIXLEAST8 "X" +#define PRIoFAST8 "o" +#define PRIuFAST8 "u" +#define PRIxFAST8 "x" +#define PRIXFAST8 "X" + +#define PRIo16 "ho" +#define PRIu16 "hu" +#define PRIx16 "hx" +#define PRIX16 "hX" +#define PRIoLEAST16 "ho" +#define PRIuLEAST16 "hu" +#define PRIxLEAST16 "hx" +#define PRIXLEAST16 "hX" +#define PRIoFAST16 "ho" +#define PRIuFAST16 "hu" +#define PRIxFAST16 "hx" +#define PRIXFAST16 "hX" + +#define PRIo32 "I32o" +#define PRIu32 "I32u" +#define PRIx32 "I32x" +#define PRIX32 "I32X" +#define PRIoLEAST32 "I32o" +#define PRIuLEAST32 "I32u" +#define PRIxLEAST32 "I32x" +#define PRIXLEAST32 "I32X" +#define PRIoFAST32 "I32o" +#define PRIuFAST32 "I32u" +#define PRIxFAST32 "I32x" +#define PRIXFAST32 "I32X" + +#define PRIo64 "I64o" +#define PRIu64 "I64u" +#define PRIx64 "I64x" +#define PRIX64 "I64X" +#define PRIoLEAST64 "I64o" +#define PRIuLEAST64 "I64u" +#define PRIxLEAST64 "I64x" +#define PRIXLEAST64 "I64X" +#define PRIoFAST64 "I64o" +#define PRIuFAST64 "I64u" +#define PRIxFAST64 "I64x" +#define PRIXFAST64 "I64X" + +#define PRIoMAX "I64o" +#define PRIuMAX "I64u" +#define PRIxMAX "I64x" +#define PRIXMAX "I64X" + +#define PRIoPTR "Io" +#define PRIuPTR "Iu" +#define PRIxPTR "Ix" +#define PRIXPTR "IX" + +// The fscanf macros for signed integers are: +#define SCNd8 "d" +#define SCNi8 "i" +#define SCNdLEAST8 "d" +#define SCNiLEAST8 "i" +#define SCNdFAST8 "d" +#define SCNiFAST8 "i" + +#define SCNd16 "hd" +#define SCNi16 "hi" +#define SCNdLEAST16 "hd" +#define SCNiLEAST16 "hi" +#define SCNdFAST16 "hd" +#define SCNiFAST16 "hi" + +#define SCNd32 "ld" +#define SCNi32 "li" +#define SCNdLEAST32 "ld" +#define SCNiLEAST32 "li" +#define SCNdFAST32 "ld" +#define SCNiFAST32 "li" + +#define SCNd64 "I64d" +#define SCNi64 "I64i" +#define SCNdLEAST64 "I64d" +#define SCNiLEAST64 "I64i" +#define SCNdFAST64 "I64d" +#define SCNiFAST64 "I64i" + +#define SCNdMAX "I64d" +#define SCNiMAX "I64i" + +#ifdef _WIN64 // [ +# define SCNdPTR "I64d" +# define SCNiPTR "I64i" +#else // _WIN64 ][ +# define SCNdPTR "ld" +# define SCNiPTR "li" +#endif // _WIN64 ] + +// The fscanf macros for unsigned integers are: +#define SCNo8 "o" +#define SCNu8 "u" +#define SCNx8 "x" +#define SCNX8 "X" +#define SCNoLEAST8 "o" +#define SCNuLEAST8 "u" +#define SCNxLEAST8 "x" +#define SCNXLEAST8 "X" +#define SCNoFAST8 "o" +#define SCNuFAST8 "u" +#define SCNxFAST8 "x" +#define SCNXFAST8 "X" + +#define SCNo16 "ho" +#define SCNu16 "hu" +#define SCNx16 "hx" +#define SCNX16 "hX" +#define SCNoLEAST16 "ho" +#define SCNuLEAST16 "hu" +#define SCNxLEAST16 "hx" +#define SCNXLEAST16 "hX" +#define SCNoFAST16 "ho" +#define SCNuFAST16 "hu" +#define SCNxFAST16 "hx" +#define SCNXFAST16 "hX" + +#define SCNo32 "lo" +#define SCNu32 "lu" +#define SCNx32 "lx" +#define SCNX32 "lX" +#define SCNoLEAST32 "lo" +#define SCNuLEAST32 "lu" +#define SCNxLEAST32 "lx" +#define SCNXLEAST32 "lX" +#define SCNoFAST32 "lo" +#define SCNuFAST32 "lu" +#define SCNxFAST32 "lx" +#define SCNXFAST32 "lX" + +#define SCNo64 "I64o" +#define SCNu64 "I64u" +#define SCNx64 "I64x" +#define SCNX64 "I64X" +#define SCNoLEAST64 "I64o" +#define SCNuLEAST64 "I64u" +#define SCNxLEAST64 "I64x" +#define SCNXLEAST64 "I64X" +#define SCNoFAST64 "I64o" +#define SCNuFAST64 "I64u" +#define SCNxFAST64 "I64x" +#define SCNXFAST64 "I64X" + +#define SCNoMAX "I64o" +#define SCNuMAX "I64u" +#define SCNxMAX "I64x" +#define SCNXMAX "I64X" + +#ifdef _WIN64 // [ +# define SCNoPTR "I64o" +# define SCNuPTR "I64u" +# define SCNxPTR "I64x" +# define SCNXPTR "I64X" +#else // _WIN64 ][ +# define SCNoPTR "lo" +# define SCNuPTR "lu" +# define SCNxPTR "lx" +# define SCNXPTR "lX" +#endif // _WIN64 ] + +#endif // __STDC_FORMAT_MACROS ] + +// 7.8.2 Functions for greatest-width integer types + +// 7.8.2.1 The imaxabs function +#define imaxabs _abs64 + +// 7.8.2.2 The imaxdiv function + +// This is modified version of div() function from Microsoft's div.c found +// in %MSVC.NET%\crt\src\div.c +#ifdef STATIC_IMAXDIV // [ +static +#else // STATIC_IMAXDIV ][ +_inline +#endif // STATIC_IMAXDIV ] +imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom) +{ + imaxdiv_t result; + + result.quot = numer / denom; + result.rem = numer % denom; + + if (numer < 0 && result.rem > 0) { + // did division wrong; must fix up + ++result.quot; + result.rem -= denom; + } + + return result; +} + +// 7.8.2.3 The strtoimax and strtoumax functions +#define strtoimax _strtoi64 +#define strtoumax _strtoui64 + +// 7.8.2.4 The wcstoimax and wcstoumax functions +#define wcstoimax _wcstoi64 +#define wcstoumax _wcstoui64 + +#endif // _MSC_VER >= 1800 + +#endif // _MSC_INTTYPES_H_ ] diff --git a/util/xpack/rapidjson/msinttypes/stdint.h b/util/xpack/rapidjson/msinttypes/stdint.h new file mode 100755 index 0000000..03d2a24 --- /dev/null +++ b/util/xpack/rapidjson/msinttypes/stdint.h @@ -0,0 +1,300 @@ +// ISO C9x compliant stdint.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2013 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the product nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +// The above software in this distribution may have been modified by +// THL A29 Limited ("Tencent Modifications"). +// All Tencent Modifications are Copyright (C) 2015 THL A29 Limited. + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_STDINT_H_ // [ +#define _MSC_STDINT_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +// miloyip: Originally Visual Studio 2010 uses its own stdint.h. However it generates warning with INT64_C(), so change to use this file for vs2010. +#if _MSC_VER >= 1600 // [ +#include + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +#undef INT8_C +#undef INT16_C +#undef INT32_C +#undef INT64_C +#undef UINT8_C +#undef UINT16_C +#undef UINT32_C +#undef UINT64_C + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +// These #ifndef's are needed to prevent collisions with . +// Check out Issue 9 for the details. +#ifndef INTMAX_C // [ +# define INTMAX_C INT64_C +#endif // INTMAX_C ] +#ifndef UINTMAX_C // [ +# define UINTMAX_C UINT64_C +#endif // UINTMAX_C ] + +#endif // __STDC_CONSTANT_MACROS ] + +#else // ] _MSC_VER >= 1700 [ + +#include + +// For Visual Studio 6 in C++ mode and for many Visual Studio versions when +// compiling for ARM we have to wrap include with 'extern "C++" {}' +// or compiler would give many errors like this: +// error C2733: second C linkage of overloaded function 'wmemchr' not allowed +#if defined(__cplusplus) && !defined(_M_ARM) +extern "C" { +#endif +# include +#if defined(__cplusplus) && !defined(_M_ARM) +} +#endif + +// Define _W64 macros to mark types changing their size, like intptr_t. +#ifndef _W64 +# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 +# define _W64 __w64 +# else +# define _W64 +# endif +#endif + + +// 7.18.1 Integer types + +// 7.18.1.1 Exact-width integer types + +// Visual Studio 6 and Embedded Visual C++ 4 doesn't +// realize that, e.g. char has the same size as __int8 +// so we give up on __intX for them. +#if (_MSC_VER < 1300) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; +#else + typedef signed __int8 int8_t; + typedef signed __int16 int16_t; + typedef signed __int32 int32_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +#endif +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + + +// 7.18.1.2 Minimum-width integer types +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +// 7.18.1.3 Fastest minimum-width integer types +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + +// 7.18.1.4 Integer types capable of holding object pointers +#ifdef _WIN64 // [ + typedef signed __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +#else // _WIN64 ][ + typedef _W64 signed int intptr_t; + typedef _W64 unsigned int uintptr_t; +#endif // _WIN64 ] + +// 7.18.1.5 Greatest-width integer types +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + + +// 7.18.2 Limits of specified-width integer types + +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 + +// 7.18.2.1 Limits of exact-width integer types +#define INT8_MIN ((int8_t)_I8_MIN) +#define INT8_MAX _I8_MAX +#define INT16_MIN ((int16_t)_I16_MIN) +#define INT16_MAX _I16_MAX +#define INT32_MIN ((int32_t)_I32_MIN) +#define INT32_MAX _I32_MAX +#define INT64_MIN ((int64_t)_I64_MIN) +#define INT64_MAX _I64_MAX +#define UINT8_MAX _UI8_MAX +#define UINT16_MAX _UI16_MAX +#define UINT32_MAX _UI32_MAX +#define UINT64_MAX _UI64_MAX + +// 7.18.2.2 Limits of minimum-width integer types +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MIN INT64_MIN +#define INT_LEAST64_MAX INT64_MAX +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +// 7.18.2.3 Limits of fastest minimum-width integer types +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST16_MAX INT16_MAX +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST32_MAX INT32_MAX +#define INT_FAST64_MIN INT64_MIN +#define INT_FAST64_MAX INT64_MAX +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX UINT16_MAX +#define UINT_FAST32_MAX UINT32_MAX +#define UINT_FAST64_MAX UINT64_MAX + +// 7.18.2.4 Limits of integer types capable of holding object pointers +#ifdef _WIN64 // [ +# define INTPTR_MIN INT64_MIN +# define INTPTR_MAX INT64_MAX +# define UINTPTR_MAX UINT64_MAX +#else // _WIN64 ][ +# define INTPTR_MIN INT32_MIN +# define INTPTR_MAX INT32_MAX +# define UINTPTR_MAX UINT32_MAX +#endif // _WIN64 ] + +// 7.18.2.5 Limits of greatest-width integer types +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +// 7.18.3 Limits of other integer types + +#ifdef _WIN64 // [ +# define PTRDIFF_MIN _I64_MIN +# define PTRDIFF_MAX _I64_MAX +#else // _WIN64 ][ +# define PTRDIFF_MIN _I32_MIN +# define PTRDIFF_MAX _I32_MAX +#endif // _WIN64 ] + +#define SIG_ATOMIC_MIN INT_MIN +#define SIG_ATOMIC_MAX INT_MAX + +#ifndef SIZE_MAX // [ +# ifdef _WIN64 // [ +# define SIZE_MAX _UI64_MAX +# else // _WIN64 ][ +# define SIZE_MAX _UI32_MAX +# endif // _WIN64 ] +#endif // SIZE_MAX ] + +// WCHAR_MIN and WCHAR_MAX are also defined in +#ifndef WCHAR_MIN // [ +# define WCHAR_MIN 0 +#endif // WCHAR_MIN ] +#ifndef WCHAR_MAX // [ +# define WCHAR_MAX _UI16_MAX +#endif // WCHAR_MAX ] + +#define WINT_MIN 0 +#define WINT_MAX _UI16_MAX + +#endif // __STDC_LIMIT_MACROS ] + + +// 7.18.4 Limits of other integer types + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +// These #ifndef's are needed to prevent collisions with . +// Check out Issue 9 for the details. +#ifndef INTMAX_C // [ +# define INTMAX_C INT64_C +#endif // INTMAX_C ] +#ifndef UINTMAX_C // [ +# define UINTMAX_C UINT64_C +#endif // UINTMAX_C ] + +#endif // __STDC_CONSTANT_MACROS ] + +#endif // _MSC_VER >= 1600 ] + +#endif // _MSC_STDINT_H_ ] diff --git a/util/xpack/rapidjson/ostreamwrapper.h b/util/xpack/rapidjson/ostreamwrapper.h new file mode 100755 index 0000000..9ff7d18 --- /dev/null +++ b/util/xpack/rapidjson/ostreamwrapper.h @@ -0,0 +1,81 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_OSTREAMWRAPPER_H_ +#define RAPIDJSON_OSTREAMWRAPPER_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of \c std::basic_ostream into RapidJSON's Stream concept. +/*! + The classes can be wrapped including but not limited to: + + - \c std::ostringstream + - \c std::stringstream + - \c std::wpstringstream + - \c std::wstringstream + - \c std::ifstream + - \c std::fstream + - \c std::wofstream + - \c std::wfstream + + \tparam StreamType Class derived from \c std::basic_ostream. +*/ + +template +class BasicOStreamWrapper { +public: + typedef typename StreamType::char_type Ch; + BasicOStreamWrapper(StreamType& stream) : stream_(stream) {} + + void Put(Ch c) { + stream_.put(c); + } + + void Flush() { + stream_.flush(); + } + + // Not implemented + char Peek() const { RAPIDJSON_ASSERT(false); return 0; } + char Take() { RAPIDJSON_ASSERT(false); return 0; } + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + BasicOStreamWrapper(const BasicOStreamWrapper&); + BasicOStreamWrapper& operator=(const BasicOStreamWrapper&); + + StreamType& stream_; +}; + +typedef BasicOStreamWrapper OStreamWrapper; +typedef BasicOStreamWrapper WOStreamWrapper; + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_OSTREAMWRAPPER_H_ diff --git a/util/xpack/rapidjson/pointer.h b/util/xpack/rapidjson/pointer.h new file mode 100755 index 0000000..c217b9e --- /dev/null +++ b/util/xpack/rapidjson/pointer.h @@ -0,0 +1,1414 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_POINTER_H_ +#define RAPIDJSON_POINTER_H_ + +#include "document.h" +#include "internal/itoa.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(switch-enum) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +static const SizeType kPointerInvalidIndex = ~SizeType(0); //!< Represents an invalid index in GenericPointer::Token + +//! Error code of parsing. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericPointer::GenericPointer, GenericPointer::GetParseErrorCode +*/ +enum PointerParseErrorCode { + kPointerParseErrorNone = 0, //!< The parse is successful + + kPointerParseErrorTokenMustBeginWithSolidus, //!< A token must begin with a '/' + kPointerParseErrorInvalidEscape, //!< Invalid escape + kPointerParseErrorInvalidPercentEncoding, //!< Invalid percent encoding in URI fragment + kPointerParseErrorCharacterMustPercentEncode //!< A character must percent encoded in URI fragment +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericPointer + +//! Represents a JSON Pointer. Use Pointer for UTF8 encoding and default allocator. +/*! + This class implements RFC 6901 "JavaScript Object Notation (JSON) Pointer" + (https://tools.ietf.org/html/rfc6901). + + A JSON pointer is for identifying a specific value in a JSON document + (GenericDocument). It can simplify coding of DOM tree manipulation, because it + can access multiple-level depth of DOM tree with single API call. + + After it parses a string representation (e.g. "/foo/0" or URI fragment + representation (e.g. "#/foo/0") into its internal representation (tokens), + it can be used to resolve a specific value in multiple documents, or sub-tree + of documents. + + Contrary to GenericValue, Pointer can be copy constructed and copy assigned. + Apart from assignment, a Pointer cannot be modified after construction. + + Although Pointer is very convenient, please aware that constructing Pointer + involves parsing and dynamic memory allocation. A special constructor with user- + supplied tokens eliminates these. + + GenericPointer depends on GenericDocument and GenericValue. + + \tparam ValueType The value type of the DOM tree. E.g. GenericValue > + \tparam Allocator The allocator type for allocating memory for internal representation. + + \note GenericPointer uses same encoding of ValueType. + However, Allocator of GenericPointer is independent of Allocator of Value. +*/ +template +class GenericPointer { +public: + typedef typename ValueType::EncodingType EncodingType; //!< Encoding type from Value + typedef typename ValueType::Ch Ch; //!< Character type from Value + + //! A token is the basic units of internal representation. + /*! + A JSON pointer string representation "/foo/123" is parsed to two tokens: + "foo" and 123. 123 will be represented in both numeric form and string form. + They are resolved according to the actual value type (object or array). + + For token that are not numbers, or the numeric value is out of bound + (greater than limits of SizeType), they are only treated as string form + (i.e. the token's index will be equal to kPointerInvalidIndex). + + This struct is public so that user can create a Pointer without parsing and + allocation, using a special constructor. + */ + struct Token { + const Ch* name; //!< Name of the token. It has null character at the end but it can contain null character. + SizeType length; //!< Length of the name. + SizeType index; //!< A valid array index, if it is not equal to kPointerInvalidIndex. + }; + + //!@name Constructors and destructor. + //@{ + + //! Default constructor. + GenericPointer(Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} + + //! Constructor that parses a string or URI fragment representation. + /*! + \param source A null-terminated, string or URI fragment representation of JSON pointer. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + */ + explicit GenericPointer(const Ch* source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source, internal::StrLen(source)); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Constructor that parses a string or URI fragment representation. + /*! + \param source A string or URI fragment representation of JSON pointer. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + explicit GenericPointer(const std::basic_string& source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source.c_str(), source.size()); + } +#endif + + //! Constructor that parses a string or URI fragment representation, with length of the source string. + /*! + \param source A string or URI fragment representation of JSON pointer. + \param length Length of source. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + \note Slightly faster than the overload without length. + */ + GenericPointer(const Ch* source, size_t length, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source, length); + } + + //! Constructor with user-supplied tokens. + /*! + This constructor let user supplies const array of tokens. + This prevents the parsing process and eliminates allocation. + This is preferred for memory constrained environments. + + \param tokens An constant array of tokens representing the JSON pointer. + \param tokenCount Number of tokens. + + \b Example + \code + #define NAME(s) { s, sizeof(s) / sizeof(s[0]) - 1, kPointerInvalidIndex } + #define INDEX(i) { #i, sizeof(#i) - 1, i } + + static const Pointer::Token kTokens[] = { NAME("foo"), INDEX(123) }; + static const Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); + // Equivalent to static const Pointer p("/foo/123"); + + #undef NAME + #undef INDEX + \endcode + */ + GenericPointer(const Token* tokens, size_t tokenCount) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(const_cast(tokens)), tokenCount_(tokenCount), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} + + //! Copy constructor. + GenericPointer(const GenericPointer& rhs) : allocator_(rhs.allocator_), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + *this = rhs; + } + + //! Copy constructor. + GenericPointer(const GenericPointer& rhs, Allocator* allocator) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + *this = rhs; + } + + //! Destructor. + ~GenericPointer() { + if (nameBuffer_) // If user-supplied tokens constructor is used, nameBuffer_ is nullptr and tokens_ are not deallocated. + Allocator::Free(tokens_); + RAPIDJSON_DELETE(ownAllocator_); + } + + //! Assignment operator. + GenericPointer& operator=(const GenericPointer& rhs) { + if (this != &rhs) { + // Do not delete ownAllcator + if (nameBuffer_) + Allocator::Free(tokens_); + + tokenCount_ = rhs.tokenCount_; + parseErrorOffset_ = rhs.parseErrorOffset_; + parseErrorCode_ = rhs.parseErrorCode_; + + if (rhs.nameBuffer_) + CopyFromRaw(rhs); // Normally parsed tokens. + else { + tokens_ = rhs.tokens_; // User supplied const tokens. + nameBuffer_ = 0; + } + } + return *this; + } + + //! Swap the content of this pointer with an other. + /*! + \param other The pointer to swap with. + \note Constant complexity. + */ + GenericPointer& Swap(GenericPointer& other) RAPIDJSON_NOEXCEPT { + internal::Swap(allocator_, other.allocator_); + internal::Swap(ownAllocator_, other.ownAllocator_); + internal::Swap(nameBuffer_, other.nameBuffer_); + internal::Swap(tokens_, other.tokens_); + internal::Swap(tokenCount_, other.tokenCount_); + internal::Swap(parseErrorOffset_, other.parseErrorOffset_); + internal::Swap(parseErrorCode_, other.parseErrorCode_); + return *this; + } + + //! free-standing swap function helper + /*! + Helper function to enable support for common swap implementation pattern based on \c std::swap: + \code + void swap(MyClass& a, MyClass& b) { + using std::swap; + swap(a.pointer, b.pointer); + // ... + } + \endcode + \see Swap() + */ + friend inline void swap(GenericPointer& a, GenericPointer& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } + + //@} + + //!@name Append token + //@{ + + //! Append a token and return a new Pointer + /*! + \param token Token to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const Token& token, Allocator* allocator = 0) const { + GenericPointer r; + r.allocator_ = allocator; + Ch *p = r.CopyFromRaw(*this, 1, token.length + 1); + std::memcpy(p, token.name, (token.length + 1) * sizeof(Ch)); + r.tokens_[tokenCount_].name = p; + r.tokens_[tokenCount_].length = token.length; + r.tokens_[tokenCount_].index = token.index; + return r; + } + + //! Append a name token with length, and return a new Pointer + /*! + \param name Name to be appended. + \param length Length of name. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const Ch* name, SizeType length, Allocator* allocator = 0) const { + Token token = { name, length, kPointerInvalidIndex }; + return Append(token, allocator); + } + + //! Append a name token without length, and return a new Pointer + /*! + \param name Name (const Ch*) to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >), (GenericPointer)) + Append(T* name, Allocator* allocator = 0) const { + return Append(name, internal::StrLen(name), allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Append a name token, and return a new Pointer + /*! + \param name Name to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const std::basic_string& name, Allocator* allocator = 0) const { + return Append(name.c_str(), static_cast(name.size()), allocator); + } +#endif + + //! Append a index token, and return a new Pointer + /*! + \param index Index to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(SizeType index, Allocator* allocator = 0) const { + char buffer[21]; + char* end = sizeof(SizeType) == 4 ? internal::u32toa(index, buffer) : internal::u64toa(index, buffer); + SizeType length = static_cast(end - buffer); + buffer[length] = '\0'; + + if (sizeof(Ch) == 1) { + Token token = { reinterpret_cast(buffer), length, index }; + return Append(token, allocator); + } + else { + Ch name[21]; + for (size_t i = 0; i <= length; i++) + name[i] = static_cast(buffer[i]); + Token token = { name, length, index }; + return Append(token, allocator); + } + } + + //! Append a token by value, and return a new Pointer + /*! + \param token token to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const ValueType& token, Allocator* allocator = 0) const { + if (token.IsString()) + return Append(token.GetString(), token.GetStringLength(), allocator); + else { + RAPIDJSON_ASSERT(token.IsUint64()); + RAPIDJSON_ASSERT(token.GetUint64() <= SizeType(~0)); + return Append(static_cast(token.GetUint64()), allocator); + } + } + + //!@name Handling Parse Error + //@{ + + //! Check whether this is a valid pointer. + bool IsValid() const { return parseErrorCode_ == kPointerParseErrorNone; } + + //! Get the parsing error offset in code unit. + size_t GetParseErrorOffset() const { return parseErrorOffset_; } + + //! Get the parsing error code. + PointerParseErrorCode GetParseErrorCode() const { return parseErrorCode_; } + + //@} + + //! Get the allocator of this pointer. + Allocator& GetAllocator() { return *allocator_; } + + //!@name Tokens + //@{ + + //! Get the token array (const version only). + const Token* GetTokens() const { return tokens_; } + + //! Get the number of tokens. + size_t GetTokenCount() const { return tokenCount_; } + + //@} + + //!@name Equality/inequality operators + //@{ + + //! Equality operator. + /*! + \note When any pointers are invalid, always returns false. + */ + bool operator==(const GenericPointer& rhs) const { + if (!IsValid() || !rhs.IsValid() || tokenCount_ != rhs.tokenCount_) + return false; + + for (size_t i = 0; i < tokenCount_; i++) { + if (tokens_[i].index != rhs.tokens_[i].index || + tokens_[i].length != rhs.tokens_[i].length || + (tokens_[i].length != 0 && std::memcmp(tokens_[i].name, rhs.tokens_[i].name, sizeof(Ch)* tokens_[i].length) != 0)) + { + return false; + } + } + + return true; + } + + //! Inequality operator. + /*! + \note When any pointers are invalid, always returns true. + */ + bool operator!=(const GenericPointer& rhs) const { return !(*this == rhs); } + + //! Less than operator. + /*! + \note Invalid pointers are always greater than valid ones. + */ + bool operator<(const GenericPointer& rhs) const { + if (!IsValid()) + return false; + if (!rhs.IsValid()) + return true; + + if (tokenCount_ != rhs.tokenCount_) + return tokenCount_ < rhs.tokenCount_; + + for (size_t i = 0; i < tokenCount_; i++) { + if (tokens_[i].index != rhs.tokens_[i].index) + return tokens_[i].index < rhs.tokens_[i].index; + + if (tokens_[i].length != rhs.tokens_[i].length) + return tokens_[i].length < rhs.tokens_[i].length; + + if (int cmp = std::memcmp(tokens_[i].name, rhs.tokens_[i].name, sizeof(Ch) * tokens_[i].length)) + return cmp < 0; + } + + return false; + } + + //@} + + //!@name Stringify + //@{ + + //! Stringify the pointer into string representation. + /*! + \tparam OutputStream Type of output stream. + \param os The output stream. + */ + template + bool Stringify(OutputStream& os) const { + return Stringify(os); + } + + //! Stringify the pointer into URI fragment representation. + /*! + \tparam OutputStream Type of output stream. + \param os The output stream. + */ + template + bool StringifyUriFragment(OutputStream& os) const { + return Stringify(os); + } + + //@} + + //!@name Create value + //@{ + + //! Create a value in a subtree. + /*! + If the value is not exist, it creates all parent values and a JSON Null value. + So it always succeed and return the newly created or existing value. + + Remind that it may change types of parents according to tokens, so it + potentially removes previously stored values. For example, if a document + was an array, and "/foo" is used to create a value, then the document + will be changed to an object, and all existing array elements are lost. + + \param root Root value of a DOM subtree to be resolved. It can be any value other than document root. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \param alreadyExist If non-null, it stores whether the resolved value is already exist. + \return The resolved newly created (a JSON Null value), or already exists value. + */ + ValueType& Create(ValueType& root, typename ValueType::AllocatorType& allocator, bool* alreadyExist = 0) const { + RAPIDJSON_ASSERT(IsValid()); + ValueType* v = &root; + bool exist = true; + for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + if (v->IsArray() && t->name[0] == '-' && t->length == 1) { + v->PushBack(ValueType().Move(), allocator); + v = &((*v)[v->Size() - 1]); + exist = false; + } + else { + if (t->index == kPointerInvalidIndex) { // must be object name + if (!v->IsObject()) + v->SetObject(); // Change to Object + } + else { // object name or array index + if (!v->IsArray() && !v->IsObject()) + v->SetArray(); // Change to Array + } + + if (v->IsArray()) { + if (t->index >= v->Size()) { + v->Reserve(t->index + 1, allocator); + while (t->index >= v->Size()) + v->PushBack(ValueType().Move(), allocator); + exist = false; + } + v = &((*v)[t->index]); + } + else { + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) { + v->AddMember(ValueType(t->name, t->length, allocator).Move(), ValueType().Move(), allocator); + v = &(--v->MemberEnd())->value; // Assumes AddMember() appends at the end + exist = false; + } + else + v = &m->value; + } + } + } + + if (alreadyExist) + *alreadyExist = exist; + + return *v; + } + + //! Creates a value in a document. + /*! + \param document A document to be resolved. + \param alreadyExist If non-null, it stores whether the resolved value is already exist. + \return The resolved newly created, or already exists value. + */ + template + ValueType& Create(GenericDocument& document, bool* alreadyExist = 0) const { + return Create(document, document.GetAllocator(), alreadyExist); + } + + //@} + + //!@name Query value + //@{ + + //! Query a value in a subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param unresolvedTokenIndex If the pointer cannot resolve a token in the pointer, this parameter can obtain the index of unresolved token. + \return Pointer to the value if it can be resolved. Otherwise null. + + \note + There are only 3 situations when a value cannot be resolved: + 1. A value in the path is not an array nor object. + 2. An object value does not contain the token. + 3. A token is out of range of an array value. + + Use unresolvedTokenIndex to retrieve the token index. + */ + ValueType* Get(ValueType& root, size_t* unresolvedTokenIndex = 0) const { + RAPIDJSON_ASSERT(IsValid()); + ValueType* v = &root; + for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + switch (v->GetType()) { + case kObjectType: + { + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) + break; + v = &m->value; + } + continue; + case kArrayType: + if (t->index == kPointerInvalidIndex || t->index >= v->Size()) + break; + v = &((*v)[t->index]); + continue; + default: + break; + } + + // Error: unresolved token + if (unresolvedTokenIndex) + *unresolvedTokenIndex = static_cast(t - tokens_); + return 0; + } + return v; + } + + //! Query a const value in a const subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \return Pointer to the value if it can be resolved. Otherwise null. + */ + const ValueType* Get(const ValueType& root, size_t* unresolvedTokenIndex = 0) const { + return Get(const_cast(root), unresolvedTokenIndex); + } + + //@} + + //!@name Query a value with default + //@{ + + //! Query a value in a subtree with default value. + /*! + Similar to Get(), but if the specified value do not exists, it creates all parents and clone the default value. + So that this function always succeed. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param defaultValue Default value to be cloned if the value was not exists. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& GetWithDefault(ValueType& root, const ValueType& defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + ValueType& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.CopyFrom(defaultValue, allocator); + } + + //! Query a value in a subtree with default null-terminated string. + ValueType& GetWithDefault(ValueType& root, const Ch* defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + ValueType& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.SetString(defaultValue, allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Query a value in a subtree with default std::basic_string. + ValueType& GetWithDefault(ValueType& root, const std::basic_string& defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + ValueType& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.SetString(defaultValue, allocator); + } +#endif + + //! Query a value in a subtree with default primitive value. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + GetWithDefault(ValueType& root, T defaultValue, typename ValueType::AllocatorType& allocator) const { + return GetWithDefault(root, ValueType(defaultValue).Move(), allocator); + } + + //! Query a value in a document with default value. + template + ValueType& GetWithDefault(GenericDocument& document, const ValueType& defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + + //! Query a value in a document with default null-terminated string. + template + ValueType& GetWithDefault(GenericDocument& document, const Ch* defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Query a value in a document with default std::basic_string. + template + ValueType& GetWithDefault(GenericDocument& document, const std::basic_string& defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } +#endif + + //! Query a value in a document with default primitive value. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + GetWithDefault(GenericDocument& document, T defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + + //@} + + //!@name Set a value + //@{ + + //! Set a value in a subtree, with move semantics. + /*! + It creates all parents if they are not exist or types are different to the tokens. + So this function always succeeds but potentially remove existing values. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param value Value to be set. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& Set(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = value; + } + + //! Set a value in a subtree, with copy semantics. + ValueType& Set(ValueType& root, const ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator).CopyFrom(value, allocator); + } + + //! Set a null-terminated string in a subtree. + ValueType& Set(ValueType& root, const Ch* value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value, allocator).Move(); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Set a std::basic_string in a subtree. + ValueType& Set(ValueType& root, const std::basic_string& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value, allocator).Move(); + } +#endif + + //! Set a primitive value in a subtree. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + Set(ValueType& root, T value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value).Move(); + } + + //! Set a value in a document, with move semantics. + template + ValueType& Set(GenericDocument& document, ValueType& value) const { + return Create(document) = value; + } + + //! Set a value in a document, with copy semantics. + template + ValueType& Set(GenericDocument& document, const ValueType& value) const { + return Create(document).CopyFrom(value, document.GetAllocator()); + } + + //! Set a null-terminated string in a document. + template + ValueType& Set(GenericDocument& document, const Ch* value) const { + return Create(document) = ValueType(value, document.GetAllocator()).Move(); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Sets a std::basic_string in a document. + template + ValueType& Set(GenericDocument& document, const std::basic_string& value) const { + return Create(document) = ValueType(value, document.GetAllocator()).Move(); + } +#endif + + //! Set a primitive value in a document. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + Set(GenericDocument& document, T value) const { + return Create(document) = value; + } + + //@} + + //!@name Swap a value + //@{ + + //! Swap a value with a value in a subtree. + /*! + It creates all parents if they are not exist or types are different to the tokens. + So this function always succeeds but potentially remove existing values. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param value Value to be swapped. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& Swap(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator).Swap(value); + } + + //! Swap a value with a value in a document. + template + ValueType& Swap(GenericDocument& document, ValueType& value) const { + return Create(document).Swap(value); + } + + //@} + + //! Erase a value in a subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \return Whether the resolved value is found and erased. + + \note Erasing with an empty pointer \c Pointer(""), i.e. the root, always fail and return false. + */ + bool Erase(ValueType& root) const { + RAPIDJSON_ASSERT(IsValid()); + if (tokenCount_ == 0) // Cannot erase the root + return false; + + ValueType* v = &root; + const Token* last = tokens_ + (tokenCount_ - 1); + for (const Token *t = tokens_; t != last; ++t) { + switch (v->GetType()) { + case kObjectType: + { + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) + return false; + v = &m->value; + } + break; + case kArrayType: + if (t->index == kPointerInvalidIndex || t->index >= v->Size()) + return false; + v = &((*v)[t->index]); + break; + default: + return false; + } + } + + switch (v->GetType()) { + case kObjectType: + return v->EraseMember(GenericStringRef(last->name, last->length)); + case kArrayType: + if (last->index == kPointerInvalidIndex || last->index >= v->Size()) + return false; + v->Erase(v->Begin() + last->index); + return true; + default: + return false; + } + } + +private: + //! Clone the content from rhs to this. + /*! + \param rhs Source pointer. + \param extraToken Extra tokens to be allocated. + \param extraNameBufferSize Extra name buffer size (in number of Ch) to be allocated. + \return Start of non-occupied name buffer, for storing extra names. + */ + Ch* CopyFromRaw(const GenericPointer& rhs, size_t extraToken = 0, size_t extraNameBufferSize = 0) { + if (!allocator_) // allocator is independently owned. + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + + size_t nameBufferSize = rhs.tokenCount_; // null terminators for tokens + for (Token *t = rhs.tokens_; t != rhs.tokens_ + rhs.tokenCount_; ++t) + nameBufferSize += t->length; + + tokenCount_ = rhs.tokenCount_ + extraToken; + tokens_ = static_cast(allocator_->Malloc(tokenCount_ * sizeof(Token) + (nameBufferSize + extraNameBufferSize) * sizeof(Ch))); + nameBuffer_ = reinterpret_cast(tokens_ + tokenCount_); + if (rhs.tokenCount_ > 0) { + std::memcpy(tokens_, rhs.tokens_, rhs.tokenCount_ * sizeof(Token)); + } + if (nameBufferSize > 0) { + std::memcpy(nameBuffer_, rhs.nameBuffer_, nameBufferSize * sizeof(Ch)); + } + + // Adjust pointers to name buffer + std::ptrdiff_t diff = nameBuffer_ - rhs.nameBuffer_; + for (Token *t = tokens_; t != tokens_ + rhs.tokenCount_; ++t) + t->name += diff; + + return nameBuffer_ + nameBufferSize; + } + + //! Check whether a character should be percent-encoded. + /*! + According to RFC 3986 2.3 Unreserved Characters. + \param c The character (code unit) to be tested. + */ + bool NeedPercentEncode(Ch c) const { + return !((c >= '0' && c <= '9') || (c >= 'A' && c <='Z') || (c >= 'a' && c <= 'z') || c == '-' || c == '.' || c == '_' || c =='~'); + } + + //! Parse a JSON String or its URI fragment representation into tokens. +#ifndef __clang__ // -Wdocumentation + /*! + \param source Either a JSON Pointer string, or its URI fragment representation. Not need to be null terminated. + \param length Length of the source string. + \note Source cannot be JSON String Representation of JSON Pointer, e.g. In "/\u0000", \u0000 will not be unescaped. + */ +#endif + void Parse(const Ch* source, size_t length) { + RAPIDJSON_ASSERT(source != NULL); + RAPIDJSON_ASSERT(nameBuffer_ == 0); + RAPIDJSON_ASSERT(tokens_ == 0); + + // Create own allocator if user did not supply. + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + + // Count number of '/' as tokenCount + tokenCount_ = 0; + for (const Ch* s = source; s != source + length; s++) + if (*s == '/') + tokenCount_++; + + Token* token = tokens_ = static_cast(allocator_->Malloc(tokenCount_ * sizeof(Token) + length * sizeof(Ch))); + Ch* name = nameBuffer_ = reinterpret_cast(tokens_ + tokenCount_); + size_t i = 0; + + // Detect if it is a URI fragment + bool uriFragment = false; + if (source[i] == '#') { + uriFragment = true; + i++; + } + + if (i != length && source[i] != '/') { + parseErrorCode_ = kPointerParseErrorTokenMustBeginWithSolidus; + goto error; + } + + while (i < length) { + RAPIDJSON_ASSERT(source[i] == '/'); + i++; // consumes '/' + + token->name = name; + bool isNumber = true; + + while (i < length && source[i] != '/') { + Ch c = source[i]; + if (uriFragment) { + // Decoding percent-encoding for URI fragment + if (c == '%') { + PercentDecodeStream is(&source[i], source + length); + GenericInsituStringStream os(name); + Ch* begin = os.PutBegin(); + if (!Transcoder, EncodingType>().Validate(is, os) || !is.IsValid()) { + parseErrorCode_ = kPointerParseErrorInvalidPercentEncoding; + goto error; + } + size_t len = os.PutEnd(begin); + i += is.Tell() - 1; + if (len == 1) + c = *name; + else { + name += len; + isNumber = false; + i++; + continue; + } + } + else if (NeedPercentEncode(c)) { + parseErrorCode_ = kPointerParseErrorCharacterMustPercentEncode; + goto error; + } + } + + i++; + + // Escaping "~0" -> '~', "~1" -> '/' + if (c == '~') { + if (i < length) { + c = source[i]; + if (c == '0') c = '~'; + else if (c == '1') c = '/'; + else { + parseErrorCode_ = kPointerParseErrorInvalidEscape; + goto error; + } + i++; + } + else { + parseErrorCode_ = kPointerParseErrorInvalidEscape; + goto error; + } + } + + // First check for index: all of characters are digit + if (c < '0' || c > '9') + isNumber = false; + + *name++ = c; + } + token->length = static_cast(name - token->name); + if (token->length == 0) + isNumber = false; + *name++ = '\0'; // Null terminator + + // Second check for index: more than one digit cannot have leading zero + if (isNumber && token->length > 1 && token->name[0] == '0') + isNumber = false; + + // String to SizeType conversion + SizeType n = 0; + if (isNumber) { + for (size_t j = 0; j < token->length; j++) { + SizeType m = n * 10 + static_cast(token->name[j] - '0'); + if (m < n) { // overflow detection + isNumber = false; + break; + } + n = m; + } + } + + token->index = isNumber ? n : kPointerInvalidIndex; + token++; + } + + RAPIDJSON_ASSERT(name <= nameBuffer_ + length); // Should not overflow buffer + parseErrorCode_ = kPointerParseErrorNone; + return; + + error: + Allocator::Free(tokens_); + nameBuffer_ = 0; + tokens_ = 0; + tokenCount_ = 0; + parseErrorOffset_ = i; + return; + } + + //! Stringify to string or URI fragment representation. + /*! + \tparam uriFragment True for stringifying to URI fragment representation. False for string representation. + \tparam OutputStream type of output stream. + \param os The output stream. + */ + template + bool Stringify(OutputStream& os) const { + RAPIDJSON_ASSERT(IsValid()); + + if (uriFragment) + os.Put('#'); + + for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + os.Put('/'); + for (size_t j = 0; j < t->length; j++) { + Ch c = t->name[j]; + if (c == '~') { + os.Put('~'); + os.Put('0'); + } + else if (c == '/') { + os.Put('~'); + os.Put('1'); + } + else if (uriFragment && NeedPercentEncode(c)) { + // Transcode to UTF8 sequence + GenericStringStream source(&t->name[j]); + PercentEncodeStream target(os); + if (!Transcoder >().Validate(source, target)) + return false; + j += source.Tell() - 1; + } + else + os.Put(c); + } + } + return true; + } + + //! A helper stream for decoding a percent-encoded sequence into code unit. + /*! + This stream decodes %XY triplet into code unit (0-255). + If it encounters invalid characters, it sets output code unit as 0 and + mark invalid, and to be checked by IsValid(). + */ + class PercentDecodeStream { + public: + typedef typename ValueType::Ch Ch; + + //! Constructor + /*! + \param source Start of the stream + \param end Past-the-end of the stream. + */ + PercentDecodeStream(const Ch* source, const Ch* end) : src_(source), head_(source), end_(end), valid_(true) {} + + Ch Take() { + if (*src_ != '%' || src_ + 3 > end_) { // %XY triplet + valid_ = false; + return 0; + } + src_++; + Ch c = 0; + for (int j = 0; j < 2; j++) { + c = static_cast(c << 4); + Ch h = *src_; + if (h >= '0' && h <= '9') c = static_cast(c + h - '0'); + else if (h >= 'A' && h <= 'F') c = static_cast(c + h - 'A' + 10); + else if (h >= 'a' && h <= 'f') c = static_cast(c + h - 'a' + 10); + else { + valid_ = false; + return 0; + } + src_++; + } + return c; + } + + size_t Tell() const { return static_cast(src_ - head_); } + bool IsValid() const { return valid_; } + + private: + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. + const Ch* end_; //!< Past-the-end position. + bool valid_; //!< Whether the parsing is valid. + }; + + //! A helper stream to encode character (UTF-8 code unit) into percent-encoded sequence. + template + class PercentEncodeStream { + public: + PercentEncodeStream(OutputStream& os) : os_(os) {} + void Put(char c) { // UTF-8 must be byte + unsigned char u = static_cast(c); + static const char hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + os_.Put('%'); + os_.Put(static_cast(hexDigits[u >> 4])); + os_.Put(static_cast(hexDigits[u & 15])); + } + private: + OutputStream& os_; + }; + + Allocator* allocator_; //!< The current allocator. It is either user-supplied or equal to ownAllocator_. + Allocator* ownAllocator_; //!< Allocator owned by this Pointer. + Ch* nameBuffer_; //!< A buffer containing all names in tokens. + Token* tokens_; //!< A list of tokens. + size_t tokenCount_; //!< Number of tokens in tokens_. + size_t parseErrorOffset_; //!< Offset in code unit when parsing fail. + PointerParseErrorCode parseErrorCode_; //!< Parsing error code. +}; + +//! GenericPointer for Value (UTF-8, default allocator). +typedef GenericPointer Pointer; + +//!@name Helper functions for GenericPointer +//@{ + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& CreateValueByPointer(T& root, const GenericPointer& pointer, typename T::AllocatorType& a) { + return pointer.Create(root, a); +} + +template +typename T::ValueType& CreateValueByPointer(T& root, const CharType(&source)[N], typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Create(root, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const GenericPointer& pointer) { + return pointer.Create(document); +} + +template +typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const CharType(&source)[N]) { + return GenericPointer(source, N - 1).Create(document); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType* GetValueByPointer(T& root, const GenericPointer& pointer, size_t* unresolvedTokenIndex = 0) { + return pointer.Get(root, unresolvedTokenIndex); +} + +template +const typename T::ValueType* GetValueByPointer(const T& root, const GenericPointer& pointer, size_t* unresolvedTokenIndex = 0) { + return pointer.Get(root, unresolvedTokenIndex); +} + +template +typename T::ValueType* GetValueByPointer(T& root, const CharType (&source)[N], size_t* unresolvedTokenIndex = 0) { + return GenericPointer(source, N - 1).Get(root, unresolvedTokenIndex); +} + +template +const typename T::ValueType* GetValueByPointer(const T& root, const CharType(&source)[N], size_t* unresolvedTokenIndex = 0) { + return GenericPointer(source, N - 1).Get(root, unresolvedTokenIndex); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::Ch* defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const std::basic_string& defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, T2 defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::Ch* defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const std::basic_string& defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const CharType(&source)[N], T2 defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::ValueType& defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::Ch* defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const std::basic_string& defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, T2 defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const std::basic_string& defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], T2 defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::Ch* value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const std::basic_string& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +SetValueByPointer(T& root, const GenericPointer& pointer, T2 value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::Ch* value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const std::basic_string& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +SetValueByPointer(T& root, const CharType(&source)[N], T2 value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, typename DocumentType::ValueType& value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::ValueType& value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::Ch* value) { + return pointer.Set(document, value); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const std::basic_string& value) { + return pointer.Set(document, value); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +SetValueByPointer(DocumentType& document, const GenericPointer& pointer, T2 value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const std::basic_string& value) { + return GenericPointer(source, N - 1).Set(document, value); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +SetValueByPointer(DocumentType& document, const CharType(&source)[N], T2 value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& SwapValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Swap(root, value, a); +} + +template +typename T::ValueType& SwapValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Swap(root, value, a); +} + +template +typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const GenericPointer& pointer, typename DocumentType::ValueType& value) { + return pointer.Swap(document, value); +} + +template +typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Swap(document, value); +} + +////////////////////////////////////////////////////////////////////////////// + +template +bool EraseValueByPointer(T& root, const GenericPointer& pointer) { + return pointer.Erase(root); +} + +template +bool EraseValueByPointer(T& root, const CharType(&source)[N]) { + return GenericPointer(source, N - 1).Erase(root); +} + +//@} + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_POINTER_H_ diff --git a/util/xpack/rapidjson/prettywriter.h b/util/xpack/rapidjson/prettywriter.h new file mode 100755 index 0000000..5bbc887 --- /dev/null +++ b/util/xpack/rapidjson/prettywriter.h @@ -0,0 +1,277 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_PRETTYWRITER_H_ +#define RAPIDJSON_PRETTYWRITER_H_ + +#include "writer.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Combination of PrettyWriter format flags. +/*! \see PrettyWriter::SetFormatOptions + */ +enum PrettyFormatOptions { + kFormatDefault = 0, //!< Default pretty formatting. + kFormatSingleLineArray = 1 //!< Format arrays on a single line. +}; + +//! Writer with indentation and spacing. +/*! + \tparam OutputStream Type of output os. + \tparam SourceEncoding Encoding of source string. + \tparam TargetEncoding Encoding of output stream. + \tparam StackAllocator Type of allocator for allocating memory of stack. +*/ +template, typename TargetEncoding = UTF8<>, typename StackAllocator = CrtAllocator, unsigned writeFlags = kWriteDefaultFlags> +class PrettyWriter : public Writer { +public: + typedef Writer Base; + typedef typename Base::Ch Ch; + + //! Constructor + /*! \param os Output stream. + \param allocator User supplied allocator. If it is null, it will create a private one. + \param levelDepth Initial capacity of stack. + */ + explicit PrettyWriter(OutputStream& os, StackAllocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : + Base(os, allocator, levelDepth), indentChar_(' '), indentCharCount_(4), formatOptions_(kFormatDefault) {} + + + explicit PrettyWriter(StackAllocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : + Base(allocator, levelDepth), indentChar_(' '), indentCharCount_(4) {} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + PrettyWriter(PrettyWriter&& rhs) : + Base(std::forward(rhs)), indentChar_(rhs.indentChar_), indentCharCount_(rhs.indentCharCount_), formatOptions_(rhs.formatOptions_) {} +#endif + + //! Set custom indentation. + /*! \param indentChar Character for indentation. Must be whitespace character (' ', '\\t', '\\n', '\\r'). + \param indentCharCount Number of indent characters for each indentation level. + \note The default indentation is 4 spaces. + */ + PrettyWriter& SetIndent(Ch indentChar, unsigned indentCharCount) { + RAPIDJSON_ASSERT(indentChar == ' ' || indentChar == '\t' || indentChar == '\n' || indentChar == '\r'); + indentChar_ = indentChar; + indentCharCount_ = indentCharCount; + return *this; + } + + //! Set pretty writer formatting options. + /*! \param options Formatting options. + */ + PrettyWriter& SetFormatOptions(PrettyFormatOptions options) { + formatOptions_ = options; + return *this; + } + + /*! @name Implementation of Handler + \see Handler + */ + //@{ + + bool Null() { PrettyPrefix(kNullType); return Base::EndValue(Base::WriteNull()); } + bool Bool(bool b) { PrettyPrefix(b ? kTrueType : kFalseType); return Base::EndValue(Base::WriteBool(b)); } + bool Int(int i) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteInt(i)); } + bool Uint(unsigned u) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteUint(u)); } + bool Int64(int64_t i64) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteInt64(i64)); } + bool Uint64(uint64_t u64) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteUint64(u64)); } + bool Double(double d) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteDouble(d)); } + + bool RawNumber(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + PrettyPrefix(kNumberType); + return Base::EndValue(Base::WriteString(str, length)); + } + + bool String(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + PrettyPrefix(kStringType); + return Base::EndValue(Base::WriteString(str, length)); + } + +#if RAPIDJSON_HAS_STDSTRING + bool String(const std::basic_string& str) { + return String(str.data(), SizeType(str.size())); + } +#endif + + bool StartObject() { + PrettyPrefix(kObjectType); + new (Base::level_stack_.template Push()) typename Base::Level(false); + return Base::WriteStartObject(); + } + + bool Key(const Ch* str, SizeType length, bool copy = false) { return String(str, length, copy); } + +#if RAPIDJSON_HAS_STDSTRING + bool Key(const std::basic_string& str) { + return Key(str.data(), SizeType(str.size())); + } +#endif + + bool EndObject(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); // not inside an Object + RAPIDJSON_ASSERT(!Base::level_stack_.template Top()->inArray); // currently inside an Array, not Object + RAPIDJSON_ASSERT(0 == Base::level_stack_.template Top()->valueCount % 2); // Object has a Key without a Value + + bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; + + if (!empty) { + Base::os_->Put('\n'); + WriteIndent(); + } + bool ret = Base::EndValue(Base::WriteEndObject()); + (void)ret; + RAPIDJSON_ASSERT(ret == true); + if (Base::level_stack_.Empty()) // end of json text + Base::Flush(); + return true; + } + + bool StartArray() { + PrettyPrefix(kArrayType); + new (Base::level_stack_.template Push()) typename Base::Level(true); + return Base::WriteStartArray(); + } + + bool EndArray(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); + RAPIDJSON_ASSERT(Base::level_stack_.template Top()->inArray); + bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; + + if (!empty && !(formatOptions_ & kFormatSingleLineArray)) { + Base::os_->Put('\n'); + WriteIndent(); + } + bool ret = Base::EndValue(Base::WriteEndArray()); + (void)ret; + RAPIDJSON_ASSERT(ret == true); + if (Base::level_stack_.Empty()) // end of json text + Base::Flush(); + return true; + } + + //@} + + /*! @name Convenience extensions */ + //@{ + + //! Simpler but slower overload. + bool String(const Ch* str) { return String(str, internal::StrLen(str)); } + bool Key(const Ch* str) { return Key(str, internal::StrLen(str)); } + + //@} + + //! Write a raw JSON value. + /*! + For user to write a stringified JSON as a value. + + \param json A well-formed JSON value. It should not contain null character within [0, length - 1] range. + \param length Length of the json. + \param type Type of the root of json. + \note When using PrettyWriter::RawValue(), the result json may not be indented correctly. + */ + bool RawValue(const Ch* json, size_t length, Type type) { + RAPIDJSON_ASSERT(json != 0); + PrettyPrefix(type); + return Base::EndValue(Base::WriteRawValue(json, length)); + } + +protected: + void PrettyPrefix(Type type) { + (void)type; + if (Base::level_stack_.GetSize() != 0) { // this value is not at root + typename Base::Level* level = Base::level_stack_.template Top(); + + if (level->inArray) { + if (level->valueCount > 0) { + Base::os_->Put(','); // add comma if it is not the first element in array + if (formatOptions_ & kFormatSingleLineArray) + Base::os_->Put(' '); + } + + if (!(formatOptions_ & kFormatSingleLineArray)) { + Base::os_->Put('\n'); + WriteIndent(); + } + } + else { // in object + if (level->valueCount > 0) { + if (level->valueCount % 2 == 0) { + Base::os_->Put(','); + Base::os_->Put('\n'); + } + else { + Base::os_->Put(':'); + Base::os_->Put(' '); + } + } + else + Base::os_->Put('\n'); + + if (level->valueCount % 2 == 0) + WriteIndent(); + } + if (!level->inArray && level->valueCount % 2 == 0) + RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name + level->valueCount++; + } + else { + RAPIDJSON_ASSERT(!Base::hasRoot_); // Should only has one and only one root. + Base::hasRoot_ = true; + } + } + + void WriteIndent() { + size_t count = (Base::level_stack_.GetSize() / sizeof(typename Base::Level)) * indentCharCount_; + PutN(*Base::os_, static_cast(indentChar_), count); + } + + Ch indentChar_; + unsigned indentCharCount_; + PrettyFormatOptions formatOptions_; + +private: + // Prohibit copy constructor & assignment operator. + PrettyWriter(const PrettyWriter&); + PrettyWriter& operator=(const PrettyWriter&); +}; + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/util/xpack/rapidjson/rapidjson.h b/util/xpack/rapidjson/rapidjson.h new file mode 100755 index 0000000..97cd77e --- /dev/null +++ b/util/xpack/rapidjson/rapidjson.h @@ -0,0 +1,656 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_RAPIDJSON_H_ +#define RAPIDJSON_RAPIDJSON_H_ + +/*!\file rapidjson.h + \brief common definitions and configuration + + \see RAPIDJSON_CONFIG + */ + +/*! \defgroup RAPIDJSON_CONFIG RapidJSON configuration + \brief Configuration macros for library features + + Some RapidJSON features are configurable to adapt the library to a wide + variety of platforms, environments and usage scenarios. Most of the + features can be configured in terms of overridden or predefined + preprocessor macros at compile-time. + + Some additional customization is available in the \ref RAPIDJSON_ERRORS APIs. + + \note These macros should be given on the compiler command-line + (where applicable) to avoid inconsistent values when compiling + different translation units of a single application. + */ + +#include // malloc(), realloc(), free(), size_t +#include // memset(), memcpy(), memmove(), memcmp() + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_VERSION_STRING +// +// ALWAYS synchronize the following 3 macros with corresponding variables in /CMakeLists.txt. +// + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +// token stringification +#define RAPIDJSON_STRINGIFY(x) RAPIDJSON_DO_STRINGIFY(x) +#define RAPIDJSON_DO_STRINGIFY(x) #x + +// token concatenation +#define RAPIDJSON_JOIN(X, Y) RAPIDJSON_DO_JOIN(X, Y) +#define RAPIDJSON_DO_JOIN(X, Y) RAPIDJSON_DO_JOIN2(X, Y) +#define RAPIDJSON_DO_JOIN2(X, Y) X##Y +//!@endcond + +/*! \def RAPIDJSON_MAJOR_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Major version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_MINOR_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Minor version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_PATCH_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Patch version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_VERSION_STRING + \ingroup RAPIDJSON_CONFIG + \brief Version of RapidJSON in ".." string format. +*/ +#define RAPIDJSON_MAJOR_VERSION 1 +#define RAPIDJSON_MINOR_VERSION 1 +#define RAPIDJSON_PATCH_VERSION 0 +#define RAPIDJSON_VERSION_STRING \ + RAPIDJSON_STRINGIFY(RAPIDJSON_MAJOR_VERSION.RAPIDJSON_MINOR_VERSION.RAPIDJSON_PATCH_VERSION) + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NAMESPACE_(BEGIN|END) +/*! \def RAPIDJSON_NAMESPACE + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace + + In order to avoid symbol clashes and/or "One Definition Rule" errors + between multiple inclusions of (different versions of) RapidJSON in + a single binary, users can customize the name of the main RapidJSON + namespace. + + In case of a single nesting level, defining \c RAPIDJSON_NAMESPACE + to a custom name (e.g. \c MyRapidJSON) is sufficient. If multiple + levels are needed, both \ref RAPIDJSON_NAMESPACE_BEGIN and \ref + RAPIDJSON_NAMESPACE_END need to be defined as well: + + \code + // in some .cpp file + #define RAPIDJSON_NAMESPACE my::rapidjson + #define RAPIDJSON_NAMESPACE_BEGIN namespace my { namespace rapidjson { + #define RAPIDJSON_NAMESPACE_END } } + #include "rapidjson/..." + \endcode + + \see rapidjson + */ +/*! \def RAPIDJSON_NAMESPACE_BEGIN + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace (opening expression) + \see RAPIDJSON_NAMESPACE +*/ +/*! \def RAPIDJSON_NAMESPACE_END + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace (closing expression) + \see RAPIDJSON_NAMESPACE +*/ +#ifndef RAPIDJSON_NAMESPACE +#define RAPIDJSON_NAMESPACE rapidjson +#endif +#ifndef RAPIDJSON_NAMESPACE_BEGIN +#define RAPIDJSON_NAMESPACE_BEGIN namespace RAPIDJSON_NAMESPACE { +#endif +#ifndef RAPIDJSON_NAMESPACE_END +#define RAPIDJSON_NAMESPACE_END } +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_HAS_STDSTRING + +#ifndef RAPIDJSON_HAS_STDSTRING +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_HAS_STDSTRING 1 // force generation of documentation +#else +#define RAPIDJSON_HAS_STDSTRING 0 // no std::string support by default +#endif +/*! \def RAPIDJSON_HAS_STDSTRING + \ingroup RAPIDJSON_CONFIG + \brief Enable RapidJSON support for \c std::string + + By defining this preprocessor symbol to \c 1, several convenience functions for using + \ref rapidjson::GenericValue with \c std::string are enabled, especially + for construction and comparison. + + \hideinitializer +*/ +#endif // !defined(RAPIDJSON_HAS_STDSTRING) + +#if RAPIDJSON_HAS_STDSTRING +#include +#endif // RAPIDJSON_HAS_STDSTRING + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NO_INT64DEFINE + +/*! \def RAPIDJSON_NO_INT64DEFINE + \ingroup RAPIDJSON_CONFIG + \brief Use external 64-bit integer types. + + RapidJSON requires the 64-bit integer types \c int64_t and \c uint64_t types + to be available at global scope. + + If users have their own definition, define RAPIDJSON_NO_INT64DEFINE to + prevent RapidJSON from defining its own types. +*/ +#ifndef RAPIDJSON_NO_INT64DEFINE +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#if defined(_MSC_VER) && (_MSC_VER < 1800) // Visual Studio 2013 +#include "msinttypes/stdint.h" +#include "msinttypes/inttypes.h" +#else +// Other compilers should have this. +#include +#include +#endif +//!@endcond +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_NO_INT64DEFINE +#endif +#endif // RAPIDJSON_NO_INT64TYPEDEF + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_FORCEINLINE + +#ifndef RAPIDJSON_FORCEINLINE +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#if defined(_MSC_VER) && defined(NDEBUG) +#define RAPIDJSON_FORCEINLINE __forceinline +#elif defined(__GNUC__) && __GNUC__ >= 4 && defined(NDEBUG) +#define RAPIDJSON_FORCEINLINE __attribute__((always_inline)) +#else +#define RAPIDJSON_FORCEINLINE +#endif +//!@endcond +#endif // RAPIDJSON_FORCEINLINE + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ENDIAN +#define RAPIDJSON_LITTLEENDIAN 0 //!< Little endian machine +#define RAPIDJSON_BIGENDIAN 1 //!< Big endian machine + +//! Endianness of the machine. +/*! + \def RAPIDJSON_ENDIAN + \ingroup RAPIDJSON_CONFIG + + GCC 4.6 provided macro for detecting endianness of the target machine. But other + compilers may not have this. User can define RAPIDJSON_ENDIAN to either + \ref RAPIDJSON_LITTLEENDIAN or \ref RAPIDJSON_BIGENDIAN. + + Default detection implemented with reference to + \li https://gcc.gnu.org/onlinedocs/gcc-4.6.0/cpp/Common-Predefined-Macros.html + \li http://www.boost.org/doc/libs/1_42_0/boost/detail/endian.hpp +*/ +#ifndef RAPIDJSON_ENDIAN +// Detect with GCC 4.6's macro +# ifdef __BYTE_ORDER__ +# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# else +# error Unknown machine endianness detected. User needs to define RAPIDJSON_ENDIAN. +# endif // __BYTE_ORDER__ +// Detect with GLIBC's endian.h +# elif defined(__GLIBC__) +# include +# if (__BYTE_ORDER == __LITTLE_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif (__BYTE_ORDER == __BIG_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# else +# error Unknown machine endianness detected. User needs to define RAPIDJSON_ENDIAN. +# endif // __GLIBC__ +// Detect with _LITTLE_ENDIAN and _BIG_ENDIAN macro +# elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +// Detect with architecture macros +# elif defined(__sparc) || defined(__sparc__) || defined(_POWER) || defined(__powerpc__) || defined(__ppc__) || defined(__hpux) || defined(__hppa) || defined(_MIPSEB) || defined(_POWER) || defined(__s390__) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# elif defined(__i386__) || defined(__alpha__) || defined(__ia64) || defined(__ia64__) || defined(_M_IX86) || defined(_M_IA64) || defined(_M_ALPHA) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) || defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || defined(__bfin__) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64)) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(RAPIDJSON_DOXYGEN_RUNNING) +# define RAPIDJSON_ENDIAN +# else +# error Unknown machine endianness detected. User needs to define RAPIDJSON_ENDIAN. +# endif +#endif // RAPIDJSON_ENDIAN + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_64BIT + +//! Whether using 64-bit architecture +#ifndef RAPIDJSON_64BIT +#if defined(__LP64__) || (defined(__x86_64__) && defined(__ILP32__)) || defined(_WIN64) || defined(__EMSCRIPTEN__) +#define RAPIDJSON_64BIT 1 +#else +#define RAPIDJSON_64BIT 0 +#endif +#endif // RAPIDJSON_64BIT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ALIGN + +//! Data alignment of the machine. +/*! \ingroup RAPIDJSON_CONFIG + \param x pointer to align + + Some machines require strict data alignment. The default is 8 bytes. + User can customize by defining the RAPIDJSON_ALIGN function macro. +*/ +#ifndef RAPIDJSON_ALIGN +#define RAPIDJSON_ALIGN(x) (((x) + static_cast(7u)) & ~static_cast(7u)) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_UINT64_C2 + +//! Construct a 64-bit literal by a pair of 32-bit integer. +/*! + 64-bit literal with or without ULL suffix is prone to compiler warnings. + UINT64_C() is C macro which cause compilation problems. + Use this macro to define 64-bit constants by a pair of 32-bit integer. +*/ +#ifndef RAPIDJSON_UINT64_C2 +#define RAPIDJSON_UINT64_C2(high32, low32) ((static_cast(high32) << 32) | static_cast(low32)) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_48BITPOINTER_OPTIMIZATION + +//! Use only lower 48-bit address for some pointers. +/*! + \ingroup RAPIDJSON_CONFIG + + This optimization uses the fact that current X86-64 architecture only implement lower 48-bit virtual address. + The higher 16-bit can be used for storing other data. + \c GenericValue uses this optimization to reduce its size form 24 bytes to 16 bytes in 64-bit architecture. +*/ +#ifndef RAPIDJSON_48BITPOINTER_OPTIMIZATION +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64) +#define RAPIDJSON_48BITPOINTER_OPTIMIZATION 1 +#else +#define RAPIDJSON_48BITPOINTER_OPTIMIZATION 0 +#endif +#endif // RAPIDJSON_48BITPOINTER_OPTIMIZATION + +#if RAPIDJSON_48BITPOINTER_OPTIMIZATION == 1 +#if RAPIDJSON_64BIT != 1 +#error RAPIDJSON_48BITPOINTER_OPTIMIZATION can only be set to 1 when RAPIDJSON_64BIT=1 +#endif +#define RAPIDJSON_SETPOINTER(type, p, x) (p = reinterpret_cast((reinterpret_cast(p) & static_cast(RAPIDJSON_UINT64_C2(0xFFFF0000, 0x00000000))) | reinterpret_cast(reinterpret_cast(x)))) +#define RAPIDJSON_GETPOINTER(type, p) (reinterpret_cast(reinterpret_cast(p) & static_cast(RAPIDJSON_UINT64_C2(0x0000FFFF, 0xFFFFFFFF)))) +#else +#define RAPIDJSON_SETPOINTER(type, p, x) (p = (x)) +#define RAPIDJSON_GETPOINTER(type, p) (p) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_SSE2/RAPIDJSON_SSE42/RAPIDJSON_NEON/RAPIDJSON_SIMD + +/*! \def RAPIDJSON_SIMD + \ingroup RAPIDJSON_CONFIG + \brief Enable SSE2/SSE4.2/Neon optimization. + + RapidJSON supports optimized implementations for some parsing operations + based on the SSE2, SSE4.2 or NEon SIMD extensions on modern Intel + or ARM compatible processors. + + To enable these optimizations, three different symbols can be defined; + \code + // Enable SSE2 optimization. + #define RAPIDJSON_SSE2 + + // Enable SSE4.2 optimization. + #define RAPIDJSON_SSE42 + \endcode + + // Enable ARM Neon optimization. + #define RAPIDJSON_NEON + \endcode + + \c RAPIDJSON_SSE42 takes precedence over SSE2, if both are defined. + + If any of these symbols is defined, RapidJSON defines the macro + \c RAPIDJSON_SIMD to indicate the availability of the optimized code. +*/ +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) \ + || defined(RAPIDJSON_NEON) || defined(RAPIDJSON_DOXYGEN_RUNNING) +#define RAPIDJSON_SIMD +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NO_SIZETYPEDEFINE + +#ifndef RAPIDJSON_NO_SIZETYPEDEFINE +/*! \def RAPIDJSON_NO_SIZETYPEDEFINE + \ingroup RAPIDJSON_CONFIG + \brief User-provided \c SizeType definition. + + In order to avoid using 32-bit size types for indexing strings and arrays, + define this preprocessor symbol and provide the type rapidjson::SizeType + before including RapidJSON: + \code + #define RAPIDJSON_NO_SIZETYPEDEFINE + namespace rapidjson { typedef ::std::size_t SizeType; } + #include "rapidjson/..." + \endcode + + \see rapidjson::SizeType +*/ +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_NO_SIZETYPEDEFINE +#endif +RAPIDJSON_NAMESPACE_BEGIN +//! Size type (for string lengths, array sizes, etc.) +/*! RapidJSON uses 32-bit array/string indices even on 64-bit platforms, + instead of using \c size_t. Users may override the SizeType by defining + \ref RAPIDJSON_NO_SIZETYPEDEFINE. +*/ +typedef unsigned SizeType; +RAPIDJSON_NAMESPACE_END +#endif + +// always import std::size_t to rapidjson namespace +RAPIDJSON_NAMESPACE_BEGIN +using std::size_t; +RAPIDJSON_NAMESPACE_END + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ASSERT + +//! Assertion. +/*! \ingroup RAPIDJSON_CONFIG + By default, rapidjson uses C \c assert() for internal assertions. + User can override it by defining RAPIDJSON_ASSERT(x) macro. + + \note Parsing errors are handled and can be customized by the + \ref RAPIDJSON_ERRORS APIs. +*/ +#ifndef RAPIDJSON_ASSERT +#include +#define RAPIDJSON_ASSERT(x) assert(x) +#endif // RAPIDJSON_ASSERT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_STATIC_ASSERT + +// Prefer C++11 static_assert, if available +#ifndef RAPIDJSON_STATIC_ASSERT +#if __cplusplus >= 201103L || ( defined(_MSC_VER) && _MSC_VER >= 1800 ) +#define RAPIDJSON_STATIC_ASSERT(x) \ + static_assert(x, RAPIDJSON_STRINGIFY(x)) +#endif // C++11 +#endif // RAPIDJSON_STATIC_ASSERT + +// Adopt C++03 implementation from boost +#ifndef RAPIDJSON_STATIC_ASSERT +#ifndef __clang__ +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#endif +RAPIDJSON_NAMESPACE_BEGIN +template struct STATIC_ASSERTION_FAILURE; +template <> struct STATIC_ASSERTION_FAILURE { enum { value = 1 }; }; +template struct StaticAssertTest {}; +RAPIDJSON_NAMESPACE_END + +#if defined(__GNUC__) || defined(__clang__) +#define RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE __attribute__((unused)) +#else +#define RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE +#endif +#ifndef __clang__ +//!@endcond +#endif + +/*! \def RAPIDJSON_STATIC_ASSERT + \brief (Internal) macro to check for conditions at compile-time + \param x compile-time condition + \hideinitializer + */ +#define RAPIDJSON_STATIC_ASSERT(x) \ + typedef ::RAPIDJSON_NAMESPACE::StaticAssertTest< \ + sizeof(::RAPIDJSON_NAMESPACE::STATIC_ASSERTION_FAILURE)> \ + RAPIDJSON_JOIN(StaticAssertTypedef, __LINE__) RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE +#endif // RAPIDJSON_STATIC_ASSERT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_LIKELY, RAPIDJSON_UNLIKELY + +//! Compiler branching hint for expression with high probability to be true. +/*! + \ingroup RAPIDJSON_CONFIG + \param x Boolean expression likely to be true. +*/ +#ifndef RAPIDJSON_LIKELY +#if defined(__GNUC__) || defined(__clang__) +#define RAPIDJSON_LIKELY(x) __builtin_expect(!!(x), 1) +#else +#define RAPIDJSON_LIKELY(x) (x) +#endif +#endif + +//! Compiler branching hint for expression with low probability to be true. +/*! + \ingroup RAPIDJSON_CONFIG + \param x Boolean expression unlikely to be true. +*/ +#ifndef RAPIDJSON_UNLIKELY +#if defined(__GNUC__) || defined(__clang__) +#define RAPIDJSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +#define RAPIDJSON_UNLIKELY(x) (x) +#endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Helpers + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN + +#define RAPIDJSON_MULTILINEMACRO_BEGIN do { +#define RAPIDJSON_MULTILINEMACRO_END \ +} while((void)0, 0) + +// adopted from Boost +#define RAPIDJSON_VERSION_CODE(x,y,z) \ + (((x)*100000) + ((y)*100) + (z)) + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_DIAG_PUSH/POP, RAPIDJSON_DIAG_OFF + +#if defined(__GNUC__) +#define RAPIDJSON_GNUC \ + RAPIDJSON_VERSION_CODE(__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__) +#endif + +#if defined(__clang__) || (defined(RAPIDJSON_GNUC) && RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,2,0)) + +#define RAPIDJSON_PRAGMA(x) _Pragma(RAPIDJSON_STRINGIFY(x)) +#define RAPIDJSON_DIAG_PRAGMA(x) RAPIDJSON_PRAGMA(GCC diagnostic x) +#define RAPIDJSON_DIAG_OFF(x) \ + RAPIDJSON_DIAG_PRAGMA(ignored RAPIDJSON_STRINGIFY(RAPIDJSON_JOIN(-W,x))) + +// push/pop support in Clang and GCC>=4.6 +#if defined(__clang__) || (defined(RAPIDJSON_GNUC) && RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) +#define RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_PRAGMA(push) +#define RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_PRAGMA(pop) +#else // GCC >= 4.2, < 4.6 +#define RAPIDJSON_DIAG_PUSH /* ignored */ +#define RAPIDJSON_DIAG_POP /* ignored */ +#endif + +#elif defined(_MSC_VER) + +// pragma (MSVC specific) +#define RAPIDJSON_PRAGMA(x) __pragma(x) +#define RAPIDJSON_DIAG_PRAGMA(x) RAPIDJSON_PRAGMA(warning(x)) + +#define RAPIDJSON_DIAG_OFF(x) RAPIDJSON_DIAG_PRAGMA(disable: x) +#define RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_PRAGMA(push) +#define RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_PRAGMA(pop) + +#else + +#define RAPIDJSON_DIAG_OFF(x) /* ignored */ +#define RAPIDJSON_DIAG_PUSH /* ignored */ +#define RAPIDJSON_DIAG_POP /* ignored */ + +#endif // RAPIDJSON_DIAG_* + +/////////////////////////////////////////////////////////////////////////////// +// C++11 features + +#ifndef RAPIDJSON_HAS_CXX11_RVALUE_REFS +#if defined(__clang__) +#if __has_feature(cxx_rvalue_references) && \ + (defined(_MSC_VER) || defined(_LIBCPP_VERSION) || defined(__GLIBCXX__) && __GLIBCXX__ >= 20080306) +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 +#else +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 0 +#endif +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,3,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ + (defined(_MSC_VER) && _MSC_VER >= 1600) || \ + (defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x5140 && defined(__GXX_EXPERIMENTAL_CXX0X__)) + +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 +#else +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 0 +#endif +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + +#ifndef RAPIDJSON_HAS_CXX11_NOEXCEPT +#if defined(__clang__) +#define RAPIDJSON_HAS_CXX11_NOEXCEPT __has_feature(cxx_noexcept) +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ + (defined(_MSC_VER) && _MSC_VER >= 1900) || \ + (defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x5140 && defined(__GXX_EXPERIMENTAL_CXX0X__)) +#define RAPIDJSON_HAS_CXX11_NOEXCEPT 1 +#else +#define RAPIDJSON_HAS_CXX11_NOEXCEPT 0 +#endif +#endif +#if RAPIDJSON_HAS_CXX11_NOEXCEPT +#define RAPIDJSON_NOEXCEPT noexcept +#else +#define RAPIDJSON_NOEXCEPT /* noexcept */ +#endif // RAPIDJSON_HAS_CXX11_NOEXCEPT + +// no automatic detection, yet +#ifndef RAPIDJSON_HAS_CXX11_TYPETRAITS +#if (defined(_MSC_VER) && _MSC_VER >= 1700) +#define RAPIDJSON_HAS_CXX11_TYPETRAITS 1 +#else +#define RAPIDJSON_HAS_CXX11_TYPETRAITS 0 +#endif +#endif + +#ifndef RAPIDJSON_HAS_CXX11_RANGE_FOR +#if defined(__clang__) +#define RAPIDJSON_HAS_CXX11_RANGE_FOR __has_feature(cxx_range_for) +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ + (defined(_MSC_VER) && _MSC_VER >= 1700) || \ + (defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x5140 && defined(__GXX_EXPERIMENTAL_CXX0X__)) +#define RAPIDJSON_HAS_CXX11_RANGE_FOR 1 +#else +#define RAPIDJSON_HAS_CXX11_RANGE_FOR 0 +#endif +#endif // RAPIDJSON_HAS_CXX11_RANGE_FOR + +//!@endcond + +//! Assertion (in non-throwing contexts). + /*! \ingroup RAPIDJSON_CONFIG + Some functions provide a \c noexcept guarantee, if the compiler supports it. + In these cases, the \ref RAPIDJSON_ASSERT macro cannot be overridden to + throw an exception. This macro adds a separate customization point for + such cases. + + Defaults to C \c assert() (as \ref RAPIDJSON_ASSERT), if \c noexcept is + supported, and to \ref RAPIDJSON_ASSERT otherwise. + */ + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NOEXCEPT_ASSERT + +#ifndef RAPIDJSON_NOEXCEPT_ASSERT +#ifdef RAPIDJSON_ASSERT_THROWS +#if RAPIDJSON_HAS_CXX11_NOEXCEPT +#define RAPIDJSON_NOEXCEPT_ASSERT(x) +#else +#define RAPIDJSON_NOEXCEPT_ASSERT(x) RAPIDJSON_ASSERT(x) +#endif // RAPIDJSON_HAS_CXX11_NOEXCEPT +#else +#define RAPIDJSON_NOEXCEPT_ASSERT(x) RAPIDJSON_ASSERT(x) +#endif // RAPIDJSON_ASSERT_THROWS +#endif // RAPIDJSON_NOEXCEPT_ASSERT + +/////////////////////////////////////////////////////////////////////////////// +// new/delete + +#ifndef RAPIDJSON_NEW +///! customization point for global \c new +#define RAPIDJSON_NEW(TypeName) new TypeName +#endif +#ifndef RAPIDJSON_DELETE +///! customization point for global \c delete +#define RAPIDJSON_DELETE(x) delete x +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Type + +/*! \namespace rapidjson + \brief main RapidJSON namespace + \see RAPIDJSON_NAMESPACE +*/ +RAPIDJSON_NAMESPACE_BEGIN + +//! Type of JSON value +enum Type { + kNullType = 0, //!< null + kFalseType = 1, //!< false + kTrueType = 2, //!< true + kObjectType = 3, //!< object + kArrayType = 4, //!< array + kStringType = 5, //!< string + kNumberType = 6 //!< number +}; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/util/xpack/rapidjson/reader.h b/util/xpack/rapidjson/reader.h new file mode 100755 index 0000000..b424235 --- /dev/null +++ b/util/xpack/rapidjson/reader.h @@ -0,0 +1,2230 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_READER_H_ +#define RAPIDJSON_READER_H_ + +/*! \file reader.h */ + +#include "allocators.h" +#include "stream.h" +#include "encodedstream.h" +#include "internal/meta.h" +#include "internal/stack.h" +#include "internal/strtod.h" +#include + +#if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) +#include +#pragma intrinsic(_BitScanForward) +#endif +#ifdef RAPIDJSON_SSE42 +#include +#elif defined(RAPIDJSON_SSE2) +#include +#elif defined(RAPIDJSON_NEON) +#include +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(old-style-cast) +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +RAPIDJSON_DIAG_OFF(4702) // unreachable code +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define RAPIDJSON_NOTHING /* deliberately empty */ +#ifndef RAPIDJSON_PARSE_ERROR_EARLY_RETURN +#define RAPIDJSON_PARSE_ERROR_EARLY_RETURN(value) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + if (RAPIDJSON_UNLIKELY(HasParseError())) { return value; } \ + RAPIDJSON_MULTILINEMACRO_END +#endif +#define RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID \ + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(RAPIDJSON_NOTHING) +//!@endcond + +/*! \def RAPIDJSON_PARSE_ERROR_NORETURN + \ingroup RAPIDJSON_ERRORS + \brief Macro to indicate a parse error. + \param parseErrorCode \ref rapidjson::ParseErrorCode of the error + \param offset position of the error in JSON input (\c size_t) + + This macros can be used as a customization point for the internal + error handling mechanism of RapidJSON. + + A common usage model is to throw an exception instead of requiring the + caller to explicitly check the \ref rapidjson::GenericReader::Parse's + return value: + + \code + #define RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode,offset) \ + throw ParseException(parseErrorCode, #parseErrorCode, offset) + + #include // std::runtime_error + #include "rapidjson/error/error.h" // rapidjson::ParseResult + + struct ParseException : std::runtime_error, rapidjson::ParseResult { + ParseException(rapidjson::ParseErrorCode code, const char* msg, size_t offset) + : std::runtime_error(msg), ParseResult(code, offset) {} + }; + + #include "rapidjson/reader.h" + \endcode + + \see RAPIDJSON_PARSE_ERROR, rapidjson::GenericReader::Parse + */ +#ifndef RAPIDJSON_PARSE_ERROR_NORETURN +#define RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + RAPIDJSON_ASSERT(!HasParseError()); /* Error can only be assigned once */ \ + SetParseError(parseErrorCode, offset); \ + RAPIDJSON_MULTILINEMACRO_END +#endif + +/*! \def RAPIDJSON_PARSE_ERROR + \ingroup RAPIDJSON_ERRORS + \brief (Internal) macro to indicate and handle a parse error. + \param parseErrorCode \ref rapidjson::ParseErrorCode of the error + \param offset position of the error in JSON input (\c size_t) + + Invokes RAPIDJSON_PARSE_ERROR_NORETURN and stops the parsing. + + \see RAPIDJSON_PARSE_ERROR_NORETURN + \hideinitializer + */ +#ifndef RAPIDJSON_PARSE_ERROR +#define RAPIDJSON_PARSE_ERROR(parseErrorCode, offset) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset); \ + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; \ + RAPIDJSON_MULTILINEMACRO_END +#endif + +#include "error/error.h" // ParseErrorCode, ParseResult + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// ParseFlag + +/*! \def RAPIDJSON_PARSE_DEFAULT_FLAGS + \ingroup RAPIDJSON_CONFIG + \brief User-defined kParseDefaultFlags definition. + + User can define this as any \c ParseFlag combinations. +*/ +#ifndef RAPIDJSON_PARSE_DEFAULT_FLAGS +#define RAPIDJSON_PARSE_DEFAULT_FLAGS kParseNoFlags +#endif + +//! Combination of parseFlags +/*! \see Reader::Parse, Document::Parse, Document::ParseInsitu, Document::ParseStream + */ +enum ParseFlag { + kParseNoFlags = 0, //!< No flags are set. + kParseInsituFlag = 1, //!< In-situ(destructive) parsing. + kParseValidateEncodingFlag = 2, //!< Validate encoding of JSON strings. + kParseIterativeFlag = 4, //!< Iterative(constant complexity in terms of function call stack size) parsing. + kParseStopWhenDoneFlag = 8, //!< After parsing a complete JSON root from stream, stop further processing the rest of stream. When this flag is used, parser will not generate kParseErrorDocumentRootNotSingular error. + kParseFullPrecisionFlag = 16, //!< Parse number in full precision (but slower). + kParseCommentsFlag = 32, //!< Allow one-line (//) and multi-line (/**/) comments. + kParseNumbersAsStringsFlag = 64, //!< Parse all numbers (ints/doubles) as strings. + kParseTrailingCommasFlag = 128, //!< Allow trailing commas at the end of objects and arrays. + kParseNanAndInfFlag = 256, //!< Allow parsing NaN, Inf, Infinity, -Inf and -Infinity as doubles. + kParseDefaultFlags = RAPIDJSON_PARSE_DEFAULT_FLAGS //!< Default parse flags. Can be customized by defining RAPIDJSON_PARSE_DEFAULT_FLAGS +}; + +/////////////////////////////////////////////////////////////////////////////// +// Handler + +/*! \class rapidjson::Handler + \brief Concept for receiving events from GenericReader upon parsing. + The functions return true if no error occurs. If they return false, + the event publisher should terminate the process. +\code +concept Handler { + typename Ch; + + bool Null(); + bool Bool(bool b); + bool Int(int i); + bool Uint(unsigned i); + bool Int64(int64_t i); + bool Uint64(uint64_t i); + bool Double(double d); + /// enabled via kParseNumbersAsStringsFlag, string is not null-terminated (use length) + bool RawNumber(const Ch* str, SizeType length, bool copy); + bool String(const Ch* str, SizeType length, bool copy); + bool StartObject(); + bool Key(const Ch* str, SizeType length, bool copy); + bool EndObject(SizeType memberCount); + bool StartArray(); + bool EndArray(SizeType elementCount); +}; +\endcode +*/ +/////////////////////////////////////////////////////////////////////////////// +// BaseReaderHandler + +//! Default implementation of Handler. +/*! This can be used as base class of any reader handler. + \note implements Handler concept +*/ +template, typename Derived = void> +struct BaseReaderHandler { + typedef typename Encoding::Ch Ch; + + typedef typename internal::SelectIf, BaseReaderHandler, Derived>::Type Override; + + bool Default() { return true; } + bool Null() { return static_cast(*this).Default(); } + bool Bool(bool) { return static_cast(*this).Default(); } + bool Int(int) { return static_cast(*this).Default(); } + bool Uint(unsigned) { return static_cast(*this).Default(); } + bool Int64(int64_t) { return static_cast(*this).Default(); } + bool Uint64(uint64_t) { return static_cast(*this).Default(); } + bool Double(double) { return static_cast(*this).Default(); } + /// enabled via kParseNumbersAsStringsFlag, string is not null-terminated (use length) + bool RawNumber(const Ch* str, SizeType len, bool copy) { return static_cast(*this).String(str, len, copy); } + bool String(const Ch*, SizeType, bool) { return static_cast(*this).Default(); } + bool StartObject() { return static_cast(*this).Default(); } + bool Key(const Ch* str, SizeType len, bool copy) { return static_cast(*this).String(str, len, copy); } + bool EndObject(SizeType) { return static_cast(*this).Default(); } + bool StartArray() { return static_cast(*this).Default(); } + bool EndArray(SizeType) { return static_cast(*this).Default(); } +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamLocalCopy + +namespace internal { + +template::copyOptimization> +class StreamLocalCopy; + +//! Do copy optimization. +template +class StreamLocalCopy { +public: + StreamLocalCopy(Stream& original) : s(original), original_(original) {} + ~StreamLocalCopy() { original_ = s; } + + Stream s; + +private: + StreamLocalCopy& operator=(const StreamLocalCopy&) /* = delete */; + + Stream& original_; +}; + +//! Keep reference. +template +class StreamLocalCopy { +public: + StreamLocalCopy(Stream& original) : s(original) {} + + Stream& s; + +private: + StreamLocalCopy& operator=(const StreamLocalCopy&) /* = delete */; +}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// SkipWhitespace + +//! Skip the JSON white spaces in a stream. +/*! \param is A input stream for skipping white spaces. + \note This function has SSE2/SSE4.2 specialization. +*/ +template +void SkipWhitespace(InputStream& is) { + internal::StreamLocalCopy copy(is); + InputStream& s(copy.s); + + typename InputStream::Ch c; + while ((c = s.Peek()) == ' ' || c == '\n' || c == '\r' || c == '\t') + s.Take(); +} + +inline const char* SkipWhitespace(const char* p, const char* end) { + while (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + return p; +} + +#ifdef RAPIDJSON_SSE42 +//! Skip whitespace with SSE 4.2 pcmpistrm instruction, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // The rest of string using SIMD + static const char whitespace[16] = " \n\r\t"; + const __m128i w = _mm_loadu_si128(reinterpret_cast(&whitespace[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const int r = _mm_cmpistri(w, s, _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_LEAST_SIGNIFICANT | _SIDD_NEGATIVE_POLARITY); + if (r != 16) // some of characters is non-whitespace + return p + r; + } +} + +inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { + // Fast return for single non-whitespace + if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + else + return p; + + // The middle of string using SIMD + static const char whitespace[16] = " \n\r\t"; + const __m128i w = _mm_loadu_si128(reinterpret_cast(&whitespace[0])); + + for (; p <= end - 16; p += 16) { + const __m128i s = _mm_loadu_si128(reinterpret_cast(p)); + const int r = _mm_cmpistri(w, s, _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_LEAST_SIGNIFICANT | _SIDD_NEGATIVE_POLARITY); + if (r != 16) // some of characters is non-whitespace + return p + r; + } + + return SkipWhitespace(p, end); +} + +#elif defined(RAPIDJSON_SSE2) + +//! Skip whitespace with SSE2 instructions, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // The rest of string + #define C16(c) { c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c } + static const char whitespaces[4][16] = { C16(' '), C16('\n'), C16('\r'), C16('\t') }; + #undef C16 + + const __m128i w0 = _mm_loadu_si128(reinterpret_cast(&whitespaces[0][0])); + const __m128i w1 = _mm_loadu_si128(reinterpret_cast(&whitespaces[1][0])); + const __m128i w2 = _mm_loadu_si128(reinterpret_cast(&whitespaces[2][0])); + const __m128i w3 = _mm_loadu_si128(reinterpret_cast(&whitespaces[3][0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + __m128i x = _mm_cmpeq_epi8(s, w0); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); + unsigned short r = static_cast(~_mm_movemask_epi8(x)); + if (r != 0) { // some of characters may be non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } +} + +inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { + // Fast return for single non-whitespace + if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + else + return p; + + // The rest of string + #define C16(c) { c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c } + static const char whitespaces[4][16] = { C16(' '), C16('\n'), C16('\r'), C16('\t') }; + #undef C16 + + const __m128i w0 = _mm_loadu_si128(reinterpret_cast(&whitespaces[0][0])); + const __m128i w1 = _mm_loadu_si128(reinterpret_cast(&whitespaces[1][0])); + const __m128i w2 = _mm_loadu_si128(reinterpret_cast(&whitespaces[2][0])); + const __m128i w3 = _mm_loadu_si128(reinterpret_cast(&whitespaces[3][0])); + + for (; p <= end - 16; p += 16) { + const __m128i s = _mm_loadu_si128(reinterpret_cast(p)); + __m128i x = _mm_cmpeq_epi8(s, w0); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); + unsigned short r = static_cast(~_mm_movemask_epi8(x)); + if (r != 0) { // some of characters may be non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } + + return SkipWhitespace(p, end); +} + +#elif defined(RAPIDJSON_NEON) + +//! Skip whitespace with ARM Neon instructions, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + const uint8x16_t w0 = vmovq_n_u8(' '); + const uint8x16_t w1 = vmovq_n_u8('\n'); + const uint8x16_t w2 = vmovq_n_u8('\r'); + const uint8x16_t w3 = vmovq_n_u8('\t'); + + for (;; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, w0); + x = vorrq_u8(x, vceqq_u8(s, w1)); + x = vorrq_u8(x, vceqq_u8(s, w2)); + x = vorrq_u8(x, vceqq_u8(s, w3)); + + x = vmvnq_u8(x); // Negate + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract + uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract + + if (low == 0) { + if (high != 0) { + int lz =__builtin_clzll(high);; + return p + 8 + (lz >> 3); + } + } else { + int lz = __builtin_clzll(low);; + return p + (lz >> 3); + } + } +} + +inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { + // Fast return for single non-whitespace + if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + else + return p; + + const uint8x16_t w0 = vmovq_n_u8(' '); + const uint8x16_t w1 = vmovq_n_u8('\n'); + const uint8x16_t w2 = vmovq_n_u8('\r'); + const uint8x16_t w3 = vmovq_n_u8('\t'); + + for (; p <= end - 16; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, w0); + x = vorrq_u8(x, vceqq_u8(s, w1)); + x = vorrq_u8(x, vceqq_u8(s, w2)); + x = vorrq_u8(x, vceqq_u8(s, w3)); + + x = vmvnq_u8(x); // Negate + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract + uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract + + if (low == 0) { + if (high != 0) { + int lz = __builtin_clzll(high); + return p + 8 + (lz >> 3); + } + } else { + int lz = __builtin_clzll(low); + return p + (lz >> 3); + } + } + + return SkipWhitespace(p, end); +} + +#endif // RAPIDJSON_NEON + +#ifdef RAPIDJSON_SIMD +//! Template function specialization for InsituStringStream +template<> inline void SkipWhitespace(InsituStringStream& is) { + is.src_ = const_cast(SkipWhitespace_SIMD(is.src_)); +} + +//! Template function specialization for StringStream +template<> inline void SkipWhitespace(StringStream& is) { + is.src_ = SkipWhitespace_SIMD(is.src_); +} + +template<> inline void SkipWhitespace(EncodedInputStream, MemoryStream>& is) { + is.is_.src_ = SkipWhitespace_SIMD(is.is_.src_, is.is_.end_); +} +#endif // RAPIDJSON_SIMD + +/////////////////////////////////////////////////////////////////////////////// +// GenericReader + +//! SAX-style JSON parser. Use \ref Reader for UTF8 encoding and default allocator. +/*! GenericReader parses JSON text from a stream, and send events synchronously to an + object implementing Handler concept. + + It needs to allocate a stack for storing a single decoded string during + non-destructive parsing. + + For in-situ parsing, the decoded string is directly written to the source + text string, no temporary buffer is required. + + A GenericReader object can be reused for parsing multiple JSON text. + + \tparam SourceEncoding Encoding of the input stream. + \tparam TargetEncoding Encoding of the parse output. + \tparam StackAllocator Allocator type for stack. +*/ +template +class GenericReader { +public: + typedef typename SourceEncoding::Ch Ch; //!< SourceEncoding character type + + //! Constructor. + /*! \param stackAllocator Optional allocator for allocating stack memory. (Only use for non-destructive parsing) + \param stackCapacity stack capacity in bytes for storing a single decoded string. (Only use for non-destructive parsing) + */ + GenericReader(StackAllocator* stackAllocator = 0, size_t stackCapacity = kDefaultStackCapacity) : + stack_(stackAllocator, stackCapacity), parseResult_(), state_(IterativeParsingStartState) {} + + //! Parse JSON text. + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept. + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template + ParseResult Parse(InputStream& is, Handler& handler) { + if (parseFlags & kParseIterativeFlag) + return IterativeParse(is, handler); + + parseResult_.Clear(); + + ClearStackOnExit scope(*this); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (RAPIDJSON_UNLIKELY(is.Peek() == '\0')) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentEmpty, is.Tell()); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + else { + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (!(parseFlags & kParseStopWhenDoneFlag)) { + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (RAPIDJSON_UNLIKELY(is.Peek() != '\0')) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentRootNotSingular, is.Tell()); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + } + } + + return parseResult_; + } + + //! Parse JSON text (with \ref kParseDefaultFlags) + /*! \tparam InputStream Type of input stream, implementing Stream concept + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template + ParseResult Parse(InputStream& is, Handler& handler) { + return Parse(is, handler); + } + + //! Initialize JSON text token-by-token parsing + /*! + */ + void IterativeParseInit() { + parseResult_.Clear(); + state_ = IterativeParsingStartState; + } + + //! Parse one token from JSON text + /*! \tparam InputStream Type of input stream, implementing Stream concept + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template + bool IterativeParseNext(InputStream& is, Handler& handler) { + while (RAPIDJSON_LIKELY(is.Peek() != '\0')) { + SkipWhitespaceAndComments(is); + + Token t = Tokenize(is.Peek()); + IterativeParsingState n = Predict(state_, t); + IterativeParsingState d = Transit(state_, t, n, is, handler); + + // If we've finished or hit an error... + if (RAPIDJSON_UNLIKELY(IsIterativeParsingCompleteState(d))) { + // Report errors. + if (d == IterativeParsingErrorState) { + HandleError(state_, is); + return false; + } + + // Transition to the finish state. + RAPIDJSON_ASSERT(d == IterativeParsingFinishState); + state_ = d; + + // If StopWhenDone is not set... + if (!(parseFlags & kParseStopWhenDoneFlag)) { + // ... and extra non-whitespace data is found... + SkipWhitespaceAndComments(is); + if (is.Peek() != '\0') { + // ... this is considered an error. + HandleError(state_, is); + return false; + } + } + + // Success! We are done! + return true; + } + + // Transition to the new state. + state_ = d; + + // If we parsed anything other than a delimiter, we invoked the handler, so we can return true now. + if (!IsIterativeParsingDelimiterState(n)) + return true; + } + + // We reached the end of file. + stack_.Clear(); + + if (state_ != IterativeParsingFinishState) { + HandleError(state_, is); + return false; + } + + return true; + } + + //! Check if token-by-token parsing JSON text is complete + /*! \return Whether the JSON has been fully decoded. + */ + RAPIDJSON_FORCEINLINE bool IterativeParseComplete() const { + return IsIterativeParsingCompleteState(state_); + } + + //! Whether a parse error has occurred in the last parsing. + bool HasParseError() const { return parseResult_.IsError(); } + + //! Get the \ref ParseErrorCode of last parsing. + ParseErrorCode GetParseErrorCode() const { return parseResult_.Code(); } + + //! Get the position of last parsing error in input, 0 otherwise. + size_t GetErrorOffset() const { return parseResult_.Offset(); } + +protected: + void SetParseError(ParseErrorCode code, size_t offset) { parseResult_.Set(code, offset); } + +private: + // Prohibit copy constructor & assignment operator. + GenericReader(const GenericReader&); + GenericReader& operator=(const GenericReader&); + + void ClearStack() { stack_.Clear(); } + + // clear stack on any exit from ParseStream, e.g. due to exception + struct ClearStackOnExit { + explicit ClearStackOnExit(GenericReader& r) : r_(r) {} + ~ClearStackOnExit() { r_.ClearStack(); } + private: + GenericReader& r_; + ClearStackOnExit(const ClearStackOnExit&); + ClearStackOnExit& operator=(const ClearStackOnExit&); + }; + + template + void SkipWhitespaceAndComments(InputStream& is) { + SkipWhitespace(is); + + if (parseFlags & kParseCommentsFlag) { + while (RAPIDJSON_UNLIKELY(Consume(is, '/'))) { + if (Consume(is, '*')) { + while (true) { + if (RAPIDJSON_UNLIKELY(is.Peek() == '\0')) + RAPIDJSON_PARSE_ERROR(kParseErrorUnspecificSyntaxError, is.Tell()); + else if (Consume(is, '*')) { + if (Consume(is, '/')) + break; + } + else + is.Take(); + } + } + else if (RAPIDJSON_LIKELY(Consume(is, '/'))) + while (is.Peek() != '\0' && is.Take() != '\n') {} + else + RAPIDJSON_PARSE_ERROR(kParseErrorUnspecificSyntaxError, is.Tell()); + + SkipWhitespace(is); + } + } + } + + // Parse object: { string : value, ... } + template + void ParseObject(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == '{'); + is.Take(); // Skip '{' + + if (RAPIDJSON_UNLIKELY(!handler.StartObject())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, '}')) { + if (RAPIDJSON_UNLIKELY(!handler.EndObject(0))) // empty object + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + + for (SizeType memberCount = 0;;) { + if (RAPIDJSON_UNLIKELY(is.Peek() != '"')) + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); + + ParseString(is, handler, true); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (RAPIDJSON_UNLIKELY(!Consume(is, ':'))) + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ++memberCount; + + switch (is.Peek()) { + case ',': + is.Take(); + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + break; + case '}': + is.Take(); + if (RAPIDJSON_UNLIKELY(!handler.EndObject(memberCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + default: + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); break; // This useless break is only for making warning and coverage happy + } + + if (parseFlags & kParseTrailingCommasFlag) { + if (is.Peek() == '}') { + if (RAPIDJSON_UNLIKELY(!handler.EndObject(memberCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + is.Take(); + return; + } + } + } + } + + // Parse array: [ value, ... ] + template + void ParseArray(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == '['); + is.Take(); // Skip '[' + + if (RAPIDJSON_UNLIKELY(!handler.StartArray())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, ']')) { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(0))) // empty array + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + + for (SizeType elementCount = 0;;) { + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ++elementCount; + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, ',')) { + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + } + else if (Consume(is, ']')) { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(elementCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); + + if (parseFlags & kParseTrailingCommasFlag) { + if (is.Peek() == ']') { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(elementCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + is.Take(); + return; + } + } + } + } + + template + void ParseNull(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 'n'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'u') && Consume(is, 'l') && Consume(is, 'l'))) { + if (RAPIDJSON_UNLIKELY(!handler.Null())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template + void ParseTrue(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 't'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'r') && Consume(is, 'u') && Consume(is, 'e'))) { + if (RAPIDJSON_UNLIKELY(!handler.Bool(true))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template + void ParseFalse(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 'f'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'a') && Consume(is, 'l') && Consume(is, 's') && Consume(is, 'e'))) { + if (RAPIDJSON_UNLIKELY(!handler.Bool(false))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template + RAPIDJSON_FORCEINLINE static bool Consume(InputStream& is, typename InputStream::Ch expect) { + if (RAPIDJSON_LIKELY(is.Peek() == expect)) { + is.Take(); + return true; + } + else + return false; + } + + // Helper function to parse four hexadecimal digits in \uXXXX in ParseString(). + template + unsigned ParseHex4(InputStream& is, size_t escapeOffset) { + unsigned codepoint = 0; + for (int i = 0; i < 4; i++) { + Ch c = is.Peek(); + codepoint <<= 4; + codepoint += static_cast(c); + if (c >= '0' && c <= '9') + codepoint -= '0'; + else if (c >= 'A' && c <= 'F') + codepoint -= 'A' - 10; + else if (c >= 'a' && c <= 'f') + codepoint -= 'a' - 10; + else { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorStringUnicodeEscapeInvalidHex, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(0); + } + is.Take(); + } + return codepoint; + } + + template + class StackStream { + public: + typedef CharType Ch; + + StackStream(internal::Stack& stack) : stack_(stack), length_(0) {} + RAPIDJSON_FORCEINLINE void Put(Ch c) { + *stack_.template Push() = c; + ++length_; + } + + RAPIDJSON_FORCEINLINE void* Push(SizeType count) { + length_ += count; + return stack_.template Push(count); + } + + size_t Length() const { return length_; } + + Ch* Pop() { + return stack_.template Pop(length_); + } + + private: + StackStream(const StackStream&); + StackStream& operator=(const StackStream&); + + internal::Stack& stack_; + SizeType length_; + }; + + // Parse string and generate String event. Different code paths for kParseInsituFlag. + template + void ParseString(InputStream& is, Handler& handler, bool isKey = false) { + internal::StreamLocalCopy copy(is); + InputStream& s(copy.s); + + RAPIDJSON_ASSERT(s.Peek() == '\"'); + s.Take(); // Skip '\"' + + bool success = false; + if (parseFlags & kParseInsituFlag) { + typename InputStream::Ch *head = s.PutBegin(); + ParseStringToStream(s, s); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + size_t length = s.PutEnd(head) - 1; + RAPIDJSON_ASSERT(length <= 0xFFFFFFFF); + const typename TargetEncoding::Ch* const str = reinterpret_cast(head); + success = (isKey ? handler.Key(str, SizeType(length), false) : handler.String(str, SizeType(length), false)); + } + else { + StackStream stackStream(stack_); + ParseStringToStream(s, stackStream); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + SizeType length = static_cast(stackStream.Length()) - 1; + const typename TargetEncoding::Ch* const str = stackStream.Pop(); + success = (isKey ? handler.Key(str, length, true) : handler.String(str, length, true)); + } + if (RAPIDJSON_UNLIKELY(!success)) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, s.Tell()); + } + + // Parse string to an output is + // This function handles the prefix/suffix double quotes, escaping, and optional encoding validation. + template + RAPIDJSON_FORCEINLINE void ParseStringToStream(InputStream& is, OutputStream& os) { +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + static const char escape[256] = { + Z16, Z16, 0, 0,'\"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'/', + Z16, Z16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, + 0, 0,'\b', 0, 0, 0,'\f', 0, 0, 0, 0, 0, 0, 0,'\n', 0, + 0, 0,'\r', 0,'\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 + }; +#undef Z16 +//!@endcond + + for (;;) { + // Scan and copy string before "\\\"" or < 0x20. This is an optional optimzation. + if (!(parseFlags & kParseValidateEncodingFlag)) + ScanCopyUnescapedString(is, os); + + Ch c = is.Peek(); + if (RAPIDJSON_UNLIKELY(c == '\\')) { // Escape + size_t escapeOffset = is.Tell(); // For invalid escaping, report the initial '\\' as error offset + is.Take(); + Ch e = is.Peek(); + if ((sizeof(Ch) == 1 || unsigned(e) < 256) && RAPIDJSON_LIKELY(escape[static_cast(e)])) { + is.Take(); + os.Put(static_cast(escape[static_cast(e)])); + } + else if (RAPIDJSON_LIKELY(e == 'u')) { // Unicode + is.Take(); + unsigned codepoint = ParseHex4(is, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + if (RAPIDJSON_UNLIKELY(codepoint >= 0xD800 && codepoint <= 0xDBFF)) { + // Handle UTF-16 surrogate pair + if (RAPIDJSON_UNLIKELY(!Consume(is, '\\') || !Consume(is, 'u'))) + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, escapeOffset); + unsigned codepoint2 = ParseHex4(is, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + if (RAPIDJSON_UNLIKELY(codepoint2 < 0xDC00 || codepoint2 > 0xDFFF)) + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, escapeOffset); + codepoint = (((codepoint - 0xD800) << 10) | (codepoint2 - 0xDC00)) + 0x10000; + } + TEncoding::Encode(os, codepoint); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorStringEscapeInvalid, escapeOffset); + } + else if (RAPIDJSON_UNLIKELY(c == '"')) { // Closing double quote + is.Take(); + os.Put('\0'); // null-terminate the string + return; + } + else if (RAPIDJSON_UNLIKELY(static_cast(c) < 0x20)) { // RFC 4627: unescaped = %x20-21 / %x23-5B / %x5D-10FFFF + if (c == '\0') + RAPIDJSON_PARSE_ERROR(kParseErrorStringMissQuotationMark, is.Tell()); + else + RAPIDJSON_PARSE_ERROR(kParseErrorStringInvalidEncoding, is.Tell()); + } + else { + size_t offset = is.Tell(); + if (RAPIDJSON_UNLIKELY((parseFlags & kParseValidateEncodingFlag ? + !Transcoder::Validate(is, os) : + !Transcoder::Transcode(is, os)))) + RAPIDJSON_PARSE_ERROR(kParseErrorStringInvalidEncoding, offset); + } + } + } + + template + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InputStream&, OutputStream&) { + // Do nothing for generic version + } + +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) + // StringStream -> StackStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(StringStream& is, StackStream& os) { + const char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = p; + return; + } + else + os.Put(*p++); + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + SizeType length; + #ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; + #else + length = static_cast(__builtin_ffs(r) - 1); + #endif + if (length != 0) { + char* q = reinterpret_cast(os.Push(length)); + for (size_t i = 0; i < length; i++) + q[i] = p[i]; + + p += length; + } + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(os.Push(16)), s); + } + + is.src_ = p; + } + + // InsituStringStream -> InsituStringStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InsituStringStream& is, InsituStringStream& os) { + RAPIDJSON_ASSERT(&is == &os); + (void)os; + + if (is.src_ == is.dst_) { + SkipUnescapedString(is); + return; + } + + char* p = is.src_; + char *q = is.dst_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = p; + is.dst_ = q; + return; + } + else + *q++ = *p++; + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (;; p += 16, q += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + size_t length; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; +#else + length = static_cast(__builtin_ffs(r) - 1); +#endif + for (const char* pend = p + length; p != pend; ) + *q++ = *p++; + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(q), s); + } + + is.src_ = p; + is.dst_ = q; + } + + // When read/write pointers are the same for insitu stream, just skip unescaped characters + static RAPIDJSON_FORCEINLINE void SkipUnescapedString(InsituStringStream& is) { + RAPIDJSON_ASSERT(is.src_ == is.dst_); + char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + for (; p != nextAligned; p++) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = is.dst_ = p; + return; + } + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + size_t length; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; +#else + length = static_cast(__builtin_ffs(r) - 1); +#endif + p += length; + break; + } + } + + is.src_ = is.dst_ = p; + } +#elif defined(RAPIDJSON_NEON) + // StringStream -> StackStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(StringStream& is, StackStream& os) { + const char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = p; + return; + } + else + os.Put(*p++); + + // The rest of string using SIMD + const uint8x16_t s0 = vmovq_n_u8('"'); + const uint8x16_t s1 = vmovq_n_u8('\\'); + const uint8x16_t s2 = vmovq_n_u8('\b'); + const uint8x16_t s3 = vmovq_n_u8(32); + + for (;; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, s0); + x = vorrq_u8(x, vceqq_u8(s, s1)); + x = vorrq_u8(x, vceqq_u8(s, s2)); + x = vorrq_u8(x, vcltq_u8(s, s3)); + + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract + uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract + + SizeType length = 0; + bool escaped = false; + if (low == 0) { + if (high != 0) { + unsigned lz = (unsigned)__builtin_clzll(high);; + length = 8 + (lz >> 3); + escaped = true; + } + } else { + unsigned lz = (unsigned)__builtin_clzll(low);; + length = lz >> 3; + escaped = true; + } + if (RAPIDJSON_UNLIKELY(escaped)) { // some of characters is escaped + if (length != 0) { + char* q = reinterpret_cast(os.Push(length)); + for (size_t i = 0; i < length; i++) + q[i] = p[i]; + + p += length; + } + break; + } + vst1q_u8(reinterpret_cast(os.Push(16)), s); + } + + is.src_ = p; + } + + // InsituStringStream -> InsituStringStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InsituStringStream& is, InsituStringStream& os) { + RAPIDJSON_ASSERT(&is == &os); + (void)os; + + if (is.src_ == is.dst_) { + SkipUnescapedString(is); + return; + } + + char* p = is.src_; + char *q = is.dst_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = p; + is.dst_ = q; + return; + } + else + *q++ = *p++; + + // The rest of string using SIMD + const uint8x16_t s0 = vmovq_n_u8('"'); + const uint8x16_t s1 = vmovq_n_u8('\\'); + const uint8x16_t s2 = vmovq_n_u8('\b'); + const uint8x16_t s3 = vmovq_n_u8(32); + + for (;; p += 16, q += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, s0); + x = vorrq_u8(x, vceqq_u8(s, s1)); + x = vorrq_u8(x, vceqq_u8(s, s2)); + x = vorrq_u8(x, vcltq_u8(s, s3)); + + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract + uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract + + SizeType length = 0; + bool escaped = false; + if (low == 0) { + if (high != 0) { + unsigned lz = (unsigned)__builtin_clzll(high); + length = 8 + (lz >> 3); + escaped = true; + } + } else { + unsigned lz = (unsigned)__builtin_clzll(low); + length = lz >> 3; + escaped = true; + } + if (RAPIDJSON_UNLIKELY(escaped)) { // some of characters is escaped + for (const char* pend = p + length; p != pend; ) { + *q++ = *p++; + } + break; + } + vst1q_u8(reinterpret_cast(q), s); + } + + is.src_ = p; + is.dst_ = q; + } + + // When read/write pointers are the same for insitu stream, just skip unescaped characters + static RAPIDJSON_FORCEINLINE void SkipUnescapedString(InsituStringStream& is) { + RAPIDJSON_ASSERT(is.src_ == is.dst_); + char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + for (; p != nextAligned; p++) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = is.dst_ = p; + return; + } + + // The rest of string using SIMD + const uint8x16_t s0 = vmovq_n_u8('"'); + const uint8x16_t s1 = vmovq_n_u8('\\'); + const uint8x16_t s2 = vmovq_n_u8('\b'); + const uint8x16_t s3 = vmovq_n_u8(32); + + for (;; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, s0); + x = vorrq_u8(x, vceqq_u8(s, s1)); + x = vorrq_u8(x, vceqq_u8(s, s2)); + x = vorrq_u8(x, vcltq_u8(s, s3)); + + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract + uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract + + if (low == 0) { + if (high != 0) { + int lz = __builtin_clzll(high); + p += 8 + (lz >> 3); + break; + } + } else { + int lz = __builtin_clzll(low); + p += lz >> 3; + break; + } + } + + is.src_ = is.dst_ = p; + } +#endif // RAPIDJSON_NEON + + template + class NumberStream; + + template + class NumberStream { + public: + typedef typename InputStream::Ch Ch; + + NumberStream(GenericReader& reader, InputStream& s) : is(s) { (void)reader; } + + RAPIDJSON_FORCEINLINE Ch Peek() const { return is.Peek(); } + RAPIDJSON_FORCEINLINE Ch TakePush() { return is.Take(); } + RAPIDJSON_FORCEINLINE Ch Take() { return is.Take(); } + RAPIDJSON_FORCEINLINE void Push(char) {} + + size_t Tell() { return is.Tell(); } + size_t Length() { return 0; } + const char* Pop() { return 0; } + + protected: + NumberStream& operator=(const NumberStream&); + + InputStream& is; + }; + + template + class NumberStream : public NumberStream { + typedef NumberStream Base; + public: + NumberStream(GenericReader& reader, InputStream& is) : Base(reader, is), stackStream(reader.stack_) {} + + RAPIDJSON_FORCEINLINE Ch TakePush() { + stackStream.Put(static_cast(Base::is.Peek())); + return Base::is.Take(); + } + + RAPIDJSON_FORCEINLINE void Push(char c) { + stackStream.Put(c); + } + + size_t Length() { return stackStream.Length(); } + + const char* Pop() { + stackStream.Put('\0'); + return stackStream.Pop(); + } + + private: + StackStream stackStream; + }; + + template + class NumberStream : public NumberStream { + typedef NumberStream Base; + public: + NumberStream(GenericReader& reader, InputStream& is) : Base(reader, is) {} + + RAPIDJSON_FORCEINLINE Ch Take() { return Base::TakePush(); } + }; + + template + void ParseNumber(InputStream& is, Handler& handler) { + internal::StreamLocalCopy copy(is); + NumberStream s(*this, copy.s); + + size_t startOffset = s.Tell(); + double d = 0.0; + bool useNanOrInf = false; + + // Parse minus + bool minus = Consume(s, '-'); + + // Parse int: zero / ( digit1-9 *DIGIT ) + unsigned i = 0; + uint64_t i64 = 0; + bool use64bit = false; + int significandDigit = 0; + if (RAPIDJSON_UNLIKELY(s.Peek() == '0')) { + i = 0; + s.TakePush(); + } + else if (RAPIDJSON_LIKELY(s.Peek() >= '1' && s.Peek() <= '9')) { + i = static_cast(s.TakePush() - '0'); + + if (minus) + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i >= 214748364)) { // 2^31 = 2147483648 + if (RAPIDJSON_LIKELY(i != 214748364 || s.Peek() > '8')) { + i64 = i; + use64bit = true; + break; + } + } + i = i * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + else + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i >= 429496729)) { // 2^32 - 1 = 4294967295 + if (RAPIDJSON_LIKELY(i != 429496729 || s.Peek() > '5')) { + i64 = i; + use64bit = true; + break; + } + } + i = i * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + } + // Parse NaN or Infinity here + else if ((parseFlags & kParseNanAndInfFlag) && RAPIDJSON_LIKELY((s.Peek() == 'I' || s.Peek() == 'N'))) { + if (Consume(s, 'N')) { + if (Consume(s, 'a') && Consume(s, 'N')) { + d = std::numeric_limits::quiet_NaN(); + useNanOrInf = true; + } + } + else if (RAPIDJSON_LIKELY(Consume(s, 'I'))) { + if (Consume(s, 'n') && Consume(s, 'f')) { + d = (minus ? -std::numeric_limits::infinity() : std::numeric_limits::infinity()); + useNanOrInf = true; + + if (RAPIDJSON_UNLIKELY(s.Peek() == 'i' && !(Consume(s, 'i') && Consume(s, 'n') + && Consume(s, 'i') && Consume(s, 't') && Consume(s, 'y')))) { + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + } + } + } + + if (RAPIDJSON_UNLIKELY(!useNanOrInf)) { + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + } + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + + // Parse 64bit int + bool useDouble = false; + if (use64bit) { + if (minus) + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i64 >= RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC))) // 2^63 = 9223372036854775808 + if (RAPIDJSON_LIKELY(i64 != RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC) || s.Peek() > '8')) { + d = static_cast(i64); + useDouble = true; + break; + } + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + else + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i64 >= RAPIDJSON_UINT64_C2(0x19999999, 0x99999999))) // 2^64 - 1 = 18446744073709551615 + if (RAPIDJSON_LIKELY(i64 != RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || s.Peek() > '5')) { + d = static_cast(i64); + useDouble = true; + break; + } + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + } + + // Force double for big integer + if (useDouble) { + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + d = d * 10 + (s.TakePush() - '0'); + } + } + + // Parse frac = decimal-point 1*DIGIT + int expFrac = 0; + size_t decimalPosition; + if (Consume(s, '.')) { + decimalPosition = s.Length(); + + if (RAPIDJSON_UNLIKELY(!(s.Peek() >= '0' && s.Peek() <= '9'))) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissFraction, s.Tell()); + + if (!useDouble) { +#if RAPIDJSON_64BIT + // Use i64 to store significand in 64-bit architecture + if (!use64bit) + i64 = i; + + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (i64 > RAPIDJSON_UINT64_C2(0x1FFFFF, 0xFFFFFFFF)) // 2^53 - 1 for fast path + break; + else { + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + --expFrac; + if (i64 != 0) + significandDigit++; + } + } + + d = static_cast(i64); +#else + // Use double to store significand in 32-bit architecture + d = static_cast(use64bit ? i64 : i); +#endif + useDouble = true; + } + + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (significandDigit < 17) { + d = d * 10.0 + (s.TakePush() - '0'); + --expFrac; + if (RAPIDJSON_LIKELY(d > 0.0)) + significandDigit++; + } + else + s.TakePush(); + } + } + else + decimalPosition = s.Length(); // decimal position at the end of integer. + + // Parse exp = e [ minus / plus ] 1*DIGIT + int exp = 0; + if (Consume(s, 'e') || Consume(s, 'E')) { + if (!useDouble) { + d = static_cast(use64bit ? i64 : i); + useDouble = true; + } + + bool expMinus = false; + if (Consume(s, '+')) + ; + else if (Consume(s, '-')) + expMinus = true; + + if (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = static_cast(s.Take() - '0'); + if (expMinus) { + // (exp + expFrac) must not underflow int => we're detecting when -exp gets + // dangerously close to INT_MIN (a pessimistic next digit 9 would push it into + // underflow territory): + // + // -(exp * 10 + 9) + expFrac >= INT_MIN + // <=> exp <= (expFrac - INT_MIN - 9) / 10 + RAPIDJSON_ASSERT(expFrac <= 0); + int maxExp = (expFrac + 2147483639) / 10; + + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = exp * 10 + static_cast(s.Take() - '0'); + if (RAPIDJSON_UNLIKELY(exp > maxExp)) { + while (RAPIDJSON_UNLIKELY(s.Peek() >= '0' && s.Peek() <= '9')) // Consume the rest of exponent + s.Take(); + } + } + } + else { // positive exp + int maxExp = 308 - expFrac; + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = exp * 10 + static_cast(s.Take() - '0'); + if (RAPIDJSON_UNLIKELY(exp > maxExp)) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, startOffset); + } + } + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissExponent, s.Tell()); + + if (expMinus) + exp = -exp; + } + + // Finish parsing, call event according to the type of number. + bool cont = true; + + if (parseFlags & kParseNumbersAsStringsFlag) { + if (parseFlags & kParseInsituFlag) { + s.Pop(); // Pop stack no matter if it will be used or not. + typename InputStream::Ch* head = is.PutBegin(); + const size_t length = s.Tell() - startOffset; + RAPIDJSON_ASSERT(length <= 0xFFFFFFFF); + // unable to insert the \0 character here, it will erase the comma after this number + const typename TargetEncoding::Ch* const str = reinterpret_cast(head); + cont = handler.RawNumber(str, SizeType(length), false); + } + else { + SizeType numCharsToCopy = static_cast(s.Length()); + StringStream srcStream(s.Pop()); + StackStream dstStream(stack_); + while (numCharsToCopy--) { + Transcoder, TargetEncoding>::Transcode(srcStream, dstStream); + } + dstStream.Put('\0'); + const typename TargetEncoding::Ch* str = dstStream.Pop(); + const SizeType length = static_cast(dstStream.Length()) - 1; + cont = handler.RawNumber(str, SizeType(length), true); + } + } + else { + size_t length = s.Length(); + const char* decimal = s.Pop(); // Pop stack no matter if it will be used or not. + + if (useDouble) { + int p = exp + expFrac; + if (parseFlags & kParseFullPrecisionFlag) + d = internal::StrtodFullPrecision(d, p, decimal, length, decimalPosition, exp); + else + d = internal::StrtodNormalPrecision(d, p); + + // Use > max, instead of == inf, to fix bogus warning -Wfloat-equal + if (d > (std::numeric_limits::max)()) { + // Overflow + // TODO: internal::StrtodX should report overflow (or underflow) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, startOffset); + } + + cont = handler.Double(minus ? -d : d); + } + else if (useNanOrInf) { + cont = handler.Double(d); + } + else { + if (use64bit) { + if (minus) + cont = handler.Int64(static_cast(~i64 + 1)); + else + cont = handler.Uint64(i64); + } + else { + if (minus) + cont = handler.Int(static_cast(~i + 1)); + else + cont = handler.Uint(i); + } + } + } + if (RAPIDJSON_UNLIKELY(!cont)) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, startOffset); + } + + // Parse any JSON value + template + void ParseValue(InputStream& is, Handler& handler) { + switch (is.Peek()) { + case 'n': ParseNull (is, handler); break; + case 't': ParseTrue (is, handler); break; + case 'f': ParseFalse (is, handler); break; + case '"': ParseString(is, handler); break; + case '{': ParseObject(is, handler); break; + case '[': ParseArray (is, handler); break; + default : + ParseNumber(is, handler); + break; + + } + } + + // Iterative Parsing + + // States + enum IterativeParsingState { + IterativeParsingFinishState = 0, // sink states at top + IterativeParsingErrorState, // sink states at top + IterativeParsingStartState, + + // Object states + IterativeParsingObjectInitialState, + IterativeParsingMemberKeyState, + IterativeParsingMemberValueState, + IterativeParsingObjectFinishState, + + // Array states + IterativeParsingArrayInitialState, + IterativeParsingElementState, + IterativeParsingArrayFinishState, + + // Single value state + IterativeParsingValueState, + + // Delimiter states (at bottom) + IterativeParsingElementDelimiterState, + IterativeParsingMemberDelimiterState, + IterativeParsingKeyValueDelimiterState, + + cIterativeParsingStateCount + }; + + // Tokens + enum Token { + LeftBracketToken = 0, + RightBracketToken, + + LeftCurlyBracketToken, + RightCurlyBracketToken, + + CommaToken, + ColonToken, + + StringToken, + FalseToken, + TrueToken, + NullToken, + NumberToken, + + kTokenCount + }; + + RAPIDJSON_FORCEINLINE Token Tokenize(Ch c) const { + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define N NumberToken +#define N16 N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N + // Maps from ASCII to Token + static const unsigned char tokenMap[256] = { + N16, // 00~0F + N16, // 10~1F + N, N, StringToken, N, N, N, N, N, N, N, N, N, CommaToken, N, N, N, // 20~2F + N, N, N, N, N, N, N, N, N, N, ColonToken, N, N, N, N, N, // 30~3F + N16, // 40~4F + N, N, N, N, N, N, N, N, N, N, N, LeftBracketToken, N, RightBracketToken, N, N, // 50~5F + N, N, N, N, N, N, FalseToken, N, N, N, N, N, N, N, NullToken, N, // 60~6F + N, N, N, N, TrueToken, N, N, N, N, N, N, LeftCurlyBracketToken, N, RightCurlyBracketToken, N, N, // 70~7F + N16, N16, N16, N16, N16, N16, N16, N16 // 80~FF + }; +#undef N +#undef N16 +//!@endcond + + if (sizeof(Ch) == 1 || static_cast(c) < 256) + return static_cast(tokenMap[static_cast(c)]); + else + return NumberToken; + } + + RAPIDJSON_FORCEINLINE IterativeParsingState Predict(IterativeParsingState state, Token token) const { + // current state x one lookahead token -> new state + static const char G[cIterativeParsingStateCount][kTokenCount] = { + // Finish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Error(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Start + { + IterativeParsingArrayInitialState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingValueState, // String + IterativeParsingValueState, // False + IterativeParsingValueState, // True + IterativeParsingValueState, // Null + IterativeParsingValueState // Number + }, + // ObjectInitial + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberKeyState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // MemberKey + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingKeyValueDelimiterState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // MemberValue + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingMemberDelimiterState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // ObjectFinish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // ArrayInitial + { + IterativeParsingArrayInitialState, // Left bracket(push Element state) + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push Element state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingElementState, // String + IterativeParsingElementState, // False + IterativeParsingElementState, // True + IterativeParsingElementState, // Null + IterativeParsingElementState // Number + }, + // Element + { + IterativeParsingErrorState, // Left bracket + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingElementDelimiterState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // ArrayFinish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Single Value (sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // ElementDelimiter + { + IterativeParsingArrayInitialState, // Left bracket(push Element state) + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push Element state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingElementState, // String + IterativeParsingElementState, // False + IterativeParsingElementState, // True + IterativeParsingElementState, // Null + IterativeParsingElementState // Number + }, + // MemberDelimiter + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberKeyState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // KeyValueDelimiter + { + IterativeParsingArrayInitialState, // Left bracket(push MemberValue state) + IterativeParsingErrorState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push MemberValue state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberValueState, // String + IterativeParsingMemberValueState, // False + IterativeParsingMemberValueState, // True + IterativeParsingMemberValueState, // Null + IterativeParsingMemberValueState // Number + }, + }; // End of G + + return static_cast(G[state][token]); + } + + // Make an advance in the token stream and state based on the candidate destination state which was returned by Transit(). + // May return a new state on state pop. + template + RAPIDJSON_FORCEINLINE IterativeParsingState Transit(IterativeParsingState src, Token token, IterativeParsingState dst, InputStream& is, Handler& handler) { + (void)token; + + switch (dst) { + case IterativeParsingErrorState: + return dst; + + case IterativeParsingObjectInitialState: + case IterativeParsingArrayInitialState: + { + // Push the state(Element or MemeberValue) if we are nested in another array or value of member. + // In this way we can get the correct state on ObjectFinish or ArrayFinish by frame pop. + IterativeParsingState n = src; + if (src == IterativeParsingArrayInitialState || src == IterativeParsingElementDelimiterState) + n = IterativeParsingElementState; + else if (src == IterativeParsingKeyValueDelimiterState) + n = IterativeParsingMemberValueState; + // Push current state. + *stack_.template Push(1) = n; + // Initialize and push the member/element count. + *stack_.template Push(1) = 0; + // Call handler + bool hr = (dst == IterativeParsingObjectInitialState) ? handler.StartObject() : handler.StartArray(); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return dst; + } + } + + case IterativeParsingMemberKeyState: + ParseString(is, handler, true); + if (HasParseError()) + return IterativeParsingErrorState; + else + return dst; + + case IterativeParsingKeyValueDelimiterState: + RAPIDJSON_ASSERT(token == ColonToken); + is.Take(); + return dst; + + case IterativeParsingMemberValueState: + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return dst; + + case IterativeParsingElementState: + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return dst; + + case IterativeParsingMemberDelimiterState: + case IterativeParsingElementDelimiterState: + is.Take(); + // Update member/element count. + *stack_.template Top() = *stack_.template Top() + 1; + return dst; + + case IterativeParsingObjectFinishState: + { + // Transit from delimiter is only allowed when trailing commas are enabled + if (!(parseFlags & kParseTrailingCommasFlag) && src == IterativeParsingMemberDelimiterState) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorObjectMissName, is.Tell()); + return IterativeParsingErrorState; + } + // Get member count. + SizeType c = *stack_.template Pop(1); + // If the object is not empty, count the last member. + if (src == IterativeParsingMemberValueState) + ++c; + // Restore the state. + IterativeParsingState n = static_cast(*stack_.template Pop(1)); + // Transit to Finish state if this is the topmost scope. + if (n == IterativeParsingStartState) + n = IterativeParsingFinishState; + // Call handler + bool hr = handler.EndObject(c); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return n; + } + } + + case IterativeParsingArrayFinishState: + { + // Transit from delimiter is only allowed when trailing commas are enabled + if (!(parseFlags & kParseTrailingCommasFlag) && src == IterativeParsingElementDelimiterState) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorValueInvalid, is.Tell()); + return IterativeParsingErrorState; + } + // Get element count. + SizeType c = *stack_.template Pop(1); + // If the array is not empty, count the last element. + if (src == IterativeParsingElementState) + ++c; + // Restore the state. + IterativeParsingState n = static_cast(*stack_.template Pop(1)); + // Transit to Finish state if this is the topmost scope. + if (n == IterativeParsingStartState) + n = IterativeParsingFinishState; + // Call handler + bool hr = handler.EndArray(c); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return n; + } + } + + default: + // This branch is for IterativeParsingValueState actually. + // Use `default:` rather than + // `case IterativeParsingValueState:` is for code coverage. + + // The IterativeParsingStartState is not enumerated in this switch-case. + // It is impossible for that case. And it can be caught by following assertion. + + // The IterativeParsingFinishState is not enumerated in this switch-case either. + // It is a "derivative" state which cannot triggered from Predict() directly. + // Therefore it cannot happen here. And it can be caught by following assertion. + RAPIDJSON_ASSERT(dst == IterativeParsingValueState); + + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return IterativeParsingFinishState; + } + } + + template + void HandleError(IterativeParsingState src, InputStream& is) { + if (HasParseError()) { + // Error flag has been set. + return; + } + + switch (src) { + case IterativeParsingStartState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentEmpty, is.Tell()); return; + case IterativeParsingFinishState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentRootNotSingular, is.Tell()); return; + case IterativeParsingObjectInitialState: + case IterativeParsingMemberDelimiterState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); return; + case IterativeParsingMemberKeyState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); return; + case IterativeParsingMemberValueState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); return; + case IterativeParsingKeyValueDelimiterState: + case IterativeParsingArrayInitialState: + case IterativeParsingElementDelimiterState: RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); return; + default: RAPIDJSON_ASSERT(src == IterativeParsingElementState); RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); return; + } + } + + RAPIDJSON_FORCEINLINE bool IsIterativeParsingDelimiterState(IterativeParsingState s) const { + return s >= IterativeParsingElementDelimiterState; + } + + RAPIDJSON_FORCEINLINE bool IsIterativeParsingCompleteState(IterativeParsingState s) const { + return s <= IterativeParsingErrorState; + } + + template + ParseResult IterativeParse(InputStream& is, Handler& handler) { + parseResult_.Clear(); + ClearStackOnExit scope(*this); + IterativeParsingState state = IterativeParsingStartState; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + while (is.Peek() != '\0') { + Token t = Tokenize(is.Peek()); + IterativeParsingState n = Predict(state, t); + IterativeParsingState d = Transit(state, t, n, is, handler); + + if (d == IterativeParsingErrorState) { + HandleError(state, is); + break; + } + + state = d; + + // Do not further consume streams if a root JSON has been parsed. + if ((parseFlags & kParseStopWhenDoneFlag) && state == IterativeParsingFinishState) + break; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + + // Handle the end of file. + if (state != IterativeParsingFinishState) + HandleError(state, is); + + return parseResult_; + } + + static const size_t kDefaultStackCapacity = 256; //!< Default stack capacity in bytes for storing a single decoded string. + internal::Stack stack_; //!< A stack for storing decoded string temporarily during non-destructive parsing. + ParseResult parseResult_; + IterativeParsingState state_; +}; // class GenericReader + +//! Reader with UTF8 encoding and default allocator. +typedef GenericReader, UTF8<> > Reader; + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_READER_H_ diff --git a/util/xpack/rapidjson/schema.h b/util/xpack/rapidjson/schema.h new file mode 100755 index 0000000..d8dfe59 --- /dev/null +++ b/util/xpack/rapidjson/schema.h @@ -0,0 +1,2496 @@ +// Tencent is pleased to support the open source community by making RapidJSON available-> +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip-> All rights reserved-> +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License-> You may obtain a copy of the License at +// +// http://opensource->org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied-> See the License for the +// specific language governing permissions and limitations under the License-> + +#ifndef RAPIDJSON_SCHEMA_H_ +#define RAPIDJSON_SCHEMA_H_ + +#include "document.h" +#include "pointer.h" +#include "stringbuffer.h" +#include // abs, floor + +#if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX) +#define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 1 +#else +#define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 0 +#endif + +#if !RAPIDJSON_SCHEMA_USE_INTERNALREGEX && defined(RAPIDJSON_SCHEMA_USE_STDREGEX) && (__cplusplus >=201103L || (defined(_MSC_VER) && _MSC_VER >= 1800)) +#define RAPIDJSON_SCHEMA_USE_STDREGEX 1 +#else +#define RAPIDJSON_SCHEMA_USE_STDREGEX 0 +#endif + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX +#include "internal/regex.h" +#elif RAPIDJSON_SCHEMA_USE_STDREGEX +#include +#endif + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX || RAPIDJSON_SCHEMA_USE_STDREGEX +#define RAPIDJSON_SCHEMA_HAS_REGEX 1 +#else +#define RAPIDJSON_SCHEMA_HAS_REGEX 0 +#endif + +#ifndef RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_SCHEMA_VERBOSE 0 +#endif + +#if RAPIDJSON_SCHEMA_VERBOSE +#include "stringbuffer.h" +#endif + +RAPIDJSON_DIAG_PUSH + +#if defined(__GNUC__) +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_OFF(weak-vtables) +RAPIDJSON_DIAG_OFF(exit-time-destructors) +RAPIDJSON_DIAG_OFF(c++98-compat-pedantic) +RAPIDJSON_DIAG_OFF(variadic-macros) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Verbose Utilities + +#if RAPIDJSON_SCHEMA_VERBOSE + +namespace internal { + +inline void PrintInvalidKeyword(const char* keyword) { + printf("Fail keyword: %s\n", keyword); +} + +inline void PrintInvalidKeyword(const wchar_t* keyword) { + wprintf(L"Fail keyword: %ls\n", keyword); +} + +inline void PrintInvalidDocument(const char* document) { + printf("Fail document: %s\n\n", document); +} + +inline void PrintInvalidDocument(const wchar_t* document) { + wprintf(L"Fail document: %ls\n\n", document); +} + +inline void PrintValidatorPointers(unsigned depth, const char* s, const char* d) { + printf("S: %*s%s\nD: %*s%s\n\n", depth * 4, " ", s, depth * 4, " ", d); +} + +inline void PrintValidatorPointers(unsigned depth, const wchar_t* s, const wchar_t* d) { + wprintf(L"S: %*ls%ls\nD: %*ls%ls\n\n", depth * 4, L" ", s, depth * 4, L" ", d); +} + +} // namespace internal + +#endif // RAPIDJSON_SCHEMA_VERBOSE + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_INVALID_KEYWORD_RETURN + +#if RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) internal::PrintInvalidKeyword(keyword) +#else +#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) +#endif + +#define RAPIDJSON_INVALID_KEYWORD_RETURN(keyword)\ +RAPIDJSON_MULTILINEMACRO_BEGIN\ + context.invalidKeyword = keyword.GetString();\ + RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword.GetString());\ + return false;\ +RAPIDJSON_MULTILINEMACRO_END + +/////////////////////////////////////////////////////////////////////////////// +// Forward declarations + +template +class GenericSchemaDocument; + +namespace internal { + +template +class Schema; + +/////////////////////////////////////////////////////////////////////////////// +// ISchemaValidator + +class ISchemaValidator { +public: + virtual ~ISchemaValidator() {} + virtual bool IsValid() const = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// ISchemaStateFactory + +template +class ISchemaStateFactory { +public: + virtual ~ISchemaStateFactory() {} + virtual ISchemaValidator* CreateSchemaValidator(const SchemaType&) = 0; + virtual void DestroySchemaValidator(ISchemaValidator* validator) = 0; + virtual void* CreateHasher() = 0; + virtual uint64_t GetHashCode(void* hasher) = 0; + virtual void DestroryHasher(void* hasher) = 0; + virtual void* MallocState(size_t size) = 0; + virtual void FreeState(void* p) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// IValidationErrorHandler + +template +class IValidationErrorHandler { +public: + typedef typename SchemaType::Ch Ch; + typedef typename SchemaType::SValue SValue; + + virtual ~IValidationErrorHandler() {} + + virtual void NotMultipleOf(int64_t actual, const SValue& expected) = 0; + virtual void NotMultipleOf(uint64_t actual, const SValue& expected) = 0; + virtual void NotMultipleOf(double actual, const SValue& expected) = 0; + virtual void AboveMaximum(int64_t actual, const SValue& expected, bool exclusive) = 0; + virtual void AboveMaximum(uint64_t actual, const SValue& expected, bool exclusive) = 0; + virtual void AboveMaximum(double actual, const SValue& expected, bool exclusive) = 0; + virtual void BelowMinimum(int64_t actual, const SValue& expected, bool exclusive) = 0; + virtual void BelowMinimum(uint64_t actual, const SValue& expected, bool exclusive) = 0; + virtual void BelowMinimum(double actual, const SValue& expected, bool exclusive) = 0; + + virtual void TooLong(const Ch* str, SizeType length, SizeType expected) = 0; + virtual void TooShort(const Ch* str, SizeType length, SizeType expected) = 0; + virtual void DoesNotMatch(const Ch* str, SizeType length) = 0; + + virtual void DisallowedItem(SizeType index) = 0; + virtual void TooFewItems(SizeType actualCount, SizeType expectedCount) = 0; + virtual void TooManyItems(SizeType actualCount, SizeType expectedCount) = 0; + virtual void DuplicateItems(SizeType index1, SizeType index2) = 0; + + virtual void TooManyProperties(SizeType actualCount, SizeType expectedCount) = 0; + virtual void TooFewProperties(SizeType actualCount, SizeType expectedCount) = 0; + virtual void StartMissingProperties() = 0; + virtual void AddMissingProperty(const SValue& name) = 0; + virtual bool EndMissingProperties() = 0; + virtual void PropertyViolations(ISchemaValidator** subvalidators, SizeType count) = 0; + virtual void DisallowedProperty(const Ch* name, SizeType length) = 0; + + virtual void StartDependencyErrors() = 0; + virtual void StartMissingDependentProperties() = 0; + virtual void AddMissingDependentProperty(const SValue& targetName) = 0; + virtual void EndMissingDependentProperties(const SValue& sourceName) = 0; + virtual void AddDependencySchemaError(const SValue& souceName, ISchemaValidator* subvalidator) = 0; + virtual bool EndDependencyErrors() = 0; + + virtual void DisallowedValue() = 0; + virtual void StartDisallowedType() = 0; + virtual void AddExpectedType(const typename SchemaType::ValueType& expectedType) = 0; + virtual void EndDisallowedType(const typename SchemaType::ValueType& actualType) = 0; + virtual void NotAllOf(ISchemaValidator** subvalidators, SizeType count) = 0; + virtual void NoneOf(ISchemaValidator** subvalidators, SizeType count) = 0; + virtual void NotOneOf(ISchemaValidator** subvalidators, SizeType count) = 0; + virtual void Disallowed() = 0; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// Hasher + +// For comparison of compound value +template +class Hasher { +public: + typedef typename Encoding::Ch Ch; + + Hasher(Allocator* allocator = 0, size_t stackCapacity = kDefaultSize) : stack_(allocator, stackCapacity) {} + + bool Null() { return WriteType(kNullType); } + bool Bool(bool b) { return WriteType(b ? kTrueType : kFalseType); } + bool Int(int i) { Number n; n.u.i = i; n.d = static_cast(i); return WriteNumber(n); } + bool Uint(unsigned u) { Number n; n.u.u = u; n.d = static_cast(u); return WriteNumber(n); } + bool Int64(int64_t i) { Number n; n.u.i = i; n.d = static_cast(i); return WriteNumber(n); } + bool Uint64(uint64_t u) { Number n; n.u.u = u; n.d = static_cast(u); return WriteNumber(n); } + bool Double(double d) { + Number n; + if (d < 0) n.u.i = static_cast(d); + else n.u.u = static_cast(d); + n.d = d; + return WriteNumber(n); + } + + bool RawNumber(const Ch* str, SizeType len, bool) { + WriteBuffer(kNumberType, str, len * sizeof(Ch)); + return true; + } + + bool String(const Ch* str, SizeType len, bool) { + WriteBuffer(kStringType, str, len * sizeof(Ch)); + return true; + } + + bool StartObject() { return true; } + bool Key(const Ch* str, SizeType len, bool copy) { return String(str, len, copy); } + bool EndObject(SizeType memberCount) { + uint64_t h = Hash(0, kObjectType); + uint64_t* kv = stack_.template Pop(memberCount * 2); + for (SizeType i = 0; i < memberCount; i++) + h ^= Hash(kv[i * 2], kv[i * 2 + 1]); // Use xor to achieve member order insensitive + *stack_.template Push() = h; + return true; + } + + bool StartArray() { return true; } + bool EndArray(SizeType elementCount) { + uint64_t h = Hash(0, kArrayType); + uint64_t* e = stack_.template Pop(elementCount); + for (SizeType i = 0; i < elementCount; i++) + h = Hash(h, e[i]); // Use hash to achieve element order sensitive + *stack_.template Push() = h; + return true; + } + + bool IsValid() const { return stack_.GetSize() == sizeof(uint64_t); } + + uint64_t GetHashCode() const { + RAPIDJSON_ASSERT(IsValid()); + return *stack_.template Top(); + } + +private: + static const size_t kDefaultSize = 256; + struct Number { + union U { + uint64_t u; + int64_t i; + }u; + double d; + }; + + bool WriteType(Type type) { return WriteBuffer(type, 0, 0); } + + bool WriteNumber(const Number& n) { return WriteBuffer(kNumberType, &n, sizeof(n)); } + + bool WriteBuffer(Type type, const void* data, size_t len) { + // FNV-1a from http://isthe.com/chongo/tech/comp/fnv/ + uint64_t h = Hash(RAPIDJSON_UINT64_C2(0x84222325, 0xcbf29ce4), type); + const unsigned char* d = static_cast(data); + for (size_t i = 0; i < len; i++) + h = Hash(h, d[i]); + *stack_.template Push() = h; + return true; + } + + static uint64_t Hash(uint64_t h, uint64_t d) { + static const uint64_t kPrime = RAPIDJSON_UINT64_C2(0x00000100, 0x000001b3); + h ^= d; + h *= kPrime; + return h; + } + + Stack stack_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// SchemaValidationContext + +template +struct SchemaValidationContext { + typedef Schema SchemaType; + typedef ISchemaStateFactory SchemaValidatorFactoryType; + typedef IValidationErrorHandler ErrorHandlerType; + typedef typename SchemaType::ValueType ValueType; + typedef typename ValueType::Ch Ch; + + enum PatternValidatorType { + kPatternValidatorOnly, + kPatternValidatorWithProperty, + kPatternValidatorWithAdditionalProperty + }; + + SchemaValidationContext(SchemaValidatorFactoryType& f, ErrorHandlerType& eh, const SchemaType* s) : + factory(f), + error_handler(eh), + schema(s), + valueSchema(), + invalidKeyword(), + hasher(), + arrayElementHashCodes(), + validators(), + validatorCount(), + patternPropertiesValidators(), + patternPropertiesValidatorCount(), + patternPropertiesSchemas(), + patternPropertiesSchemaCount(), + valuePatternValidatorType(kPatternValidatorOnly), + propertyExist(), + inArray(false), + valueUniqueness(false), + arrayUniqueness(false) + { + } + + ~SchemaValidationContext() { + if (hasher) + factory.DestroryHasher(hasher); + if (validators) { + for (SizeType i = 0; i < validatorCount; i++) + factory.DestroySchemaValidator(validators[i]); + factory.FreeState(validators); + } + if (patternPropertiesValidators) { + for (SizeType i = 0; i < patternPropertiesValidatorCount; i++) + factory.DestroySchemaValidator(patternPropertiesValidators[i]); + factory.FreeState(patternPropertiesValidators); + } + if (patternPropertiesSchemas) + factory.FreeState(patternPropertiesSchemas); + if (propertyExist) + factory.FreeState(propertyExist); + } + + SchemaValidatorFactoryType& factory; + ErrorHandlerType& error_handler; + const SchemaType* schema; + const SchemaType* valueSchema; + const Ch* invalidKeyword; + void* hasher; // Only validator access + void* arrayElementHashCodes; // Only validator access this + ISchemaValidator** validators; + SizeType validatorCount; + ISchemaValidator** patternPropertiesValidators; + SizeType patternPropertiesValidatorCount; + const SchemaType** patternPropertiesSchemas; + SizeType patternPropertiesSchemaCount; + PatternValidatorType valuePatternValidatorType; + PatternValidatorType objectPatternValidatorType; + SizeType arrayElementIndex; + bool* propertyExist; + bool inArray; + bool valueUniqueness; + bool arrayUniqueness; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Schema + +template +class Schema { +public: + typedef typename SchemaDocumentType::ValueType ValueType; + typedef typename SchemaDocumentType::AllocatorType AllocatorType; + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + typedef SchemaValidationContext Context; + typedef Schema SchemaType; + typedef GenericValue SValue; + typedef IValidationErrorHandler ErrorHandler; + friend class GenericSchemaDocument; + + Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator) : + allocator_(allocator), + uri_(schemaDocument->GetURI(), *allocator), + pointer_(p, allocator), + typeless_(schemaDocument->GetTypeless()), + enum_(), + enumCount_(), + not_(), + type_((1 << kTotalSchemaType) - 1), // typeless + validatorCount_(), + notValidatorIndex_(), + properties_(), + additionalPropertiesSchema_(), + patternProperties_(), + patternPropertyCount_(), + propertyCount_(), + minProperties_(), + maxProperties_(SizeType(~0)), + additionalProperties_(true), + hasDependencies_(), + hasRequired_(), + hasSchemaDependencies_(), + additionalItemsSchema_(), + itemsList_(), + itemsTuple_(), + itemsTupleCount_(), + minItems_(), + maxItems_(SizeType(~0)), + additionalItems_(true), + uniqueItems_(false), + pattern_(), + minLength_(0), + maxLength_(~SizeType(0)), + exclusiveMinimum_(false), + exclusiveMaximum_(false), + defaultValueLength_(0) + { + typedef typename SchemaDocumentType::ValueType ValueType; + typedef typename ValueType::ConstValueIterator ConstValueIterator; + typedef typename ValueType::ConstMemberIterator ConstMemberIterator; + + if (!value.IsObject()) + return; + + if (const ValueType* v = GetMember(value, GetTypeString())) { + type_ = 0; + if (v->IsString()) + AddType(*v); + else if (v->IsArray()) + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) + AddType(*itr); + } + + if (const ValueType* v = GetMember(value, GetEnumString())) + if (v->IsArray() && v->Size() > 0) { + enum_ = static_cast(allocator_->Malloc(sizeof(uint64_t) * v->Size())); + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) { + typedef Hasher > EnumHasherType; + char buffer[256u + 24]; + MemoryPoolAllocator<> hasherAllocator(buffer, sizeof(buffer)); + EnumHasherType h(&hasherAllocator, 256); + itr->Accept(h); + enum_[enumCount_++] = h.GetHashCode(); + } + } + + if (schemaDocument) { + AssignIfExist(allOf_, *schemaDocument, p, value, GetAllOfString(), document); + AssignIfExist(anyOf_, *schemaDocument, p, value, GetAnyOfString(), document); + AssignIfExist(oneOf_, *schemaDocument, p, value, GetOneOfString(), document); + } + + if (const ValueType* v = GetMember(value, GetNotString())) { + schemaDocument->CreateSchema(¬_, p.Append(GetNotString(), allocator_), *v, document); + notValidatorIndex_ = validatorCount_; + validatorCount_++; + } + + // Object + + const ValueType* properties = GetMember(value, GetPropertiesString()); + const ValueType* required = GetMember(value, GetRequiredString()); + const ValueType* dependencies = GetMember(value, GetDependenciesString()); + { + // Gather properties from properties/required/dependencies + SValue allProperties(kArrayType); + + if (properties && properties->IsObject()) + for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) + AddUniqueElement(allProperties, itr->name); + + if (required && required->IsArray()) + for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) + if (itr->IsString()) + AddUniqueElement(allProperties, *itr); + + if (dependencies && dependencies->IsObject()) + for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { + AddUniqueElement(allProperties, itr->name); + if (itr->value.IsArray()) + for (ConstValueIterator i = itr->value.Begin(); i != itr->value.End(); ++i) + if (i->IsString()) + AddUniqueElement(allProperties, *i); + } + + if (allProperties.Size() > 0) { + propertyCount_ = allProperties.Size(); + properties_ = static_cast(allocator_->Malloc(sizeof(Property) * propertyCount_)); + for (SizeType i = 0; i < propertyCount_; i++) { + new (&properties_[i]) Property(); + properties_[i].name = allProperties[i]; + properties_[i].schema = typeless_; + } + } + } + + if (properties && properties->IsObject()) { + PointerType q = p.Append(GetPropertiesString(), allocator_); + for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) { + SizeType index; + if (FindPropertyIndex(itr->name, &index)) + schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document); + } + } + + if (const ValueType* v = GetMember(value, GetPatternPropertiesString())) { + PointerType q = p.Append(GetPatternPropertiesString(), allocator_); + patternProperties_ = static_cast(allocator_->Malloc(sizeof(PatternProperty) * v->MemberCount())); + patternPropertyCount_ = 0; + + for (ConstMemberIterator itr = v->MemberBegin(); itr != v->MemberEnd(); ++itr) { + new (&patternProperties_[patternPropertyCount_]) PatternProperty(); + patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name); + schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document); + patternPropertyCount_++; + } + } + + if (required && required->IsArray()) + for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) + if (itr->IsString()) { + SizeType index; + if (FindPropertyIndex(*itr, &index)) { + properties_[index].required = true; + hasRequired_ = true; + } + } + + if (dependencies && dependencies->IsObject()) { + PointerType q = p.Append(GetDependenciesString(), allocator_); + hasDependencies_ = true; + for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { + SizeType sourceIndex; + if (FindPropertyIndex(itr->name, &sourceIndex)) { + if (itr->value.IsArray()) { + properties_[sourceIndex].dependencies = static_cast(allocator_->Malloc(sizeof(bool) * propertyCount_)); + std::memset(properties_[sourceIndex].dependencies, 0, sizeof(bool)* propertyCount_); + for (ConstValueIterator targetItr = itr->value.Begin(); targetItr != itr->value.End(); ++targetItr) { + SizeType targetIndex; + if (FindPropertyIndex(*targetItr, &targetIndex)) + properties_[sourceIndex].dependencies[targetIndex] = true; + } + } + else if (itr->value.IsObject()) { + hasSchemaDependencies_ = true; + schemaDocument->CreateSchema(&properties_[sourceIndex].dependenciesSchema, q.Append(itr->name, allocator_), itr->value, document); + properties_[sourceIndex].dependenciesValidatorIndex = validatorCount_; + validatorCount_++; + } + } + } + } + + if (const ValueType* v = GetMember(value, GetAdditionalPropertiesString())) { + if (v->IsBool()) + additionalProperties_ = v->GetBool(); + else if (v->IsObject()) + schemaDocument->CreateSchema(&additionalPropertiesSchema_, p.Append(GetAdditionalPropertiesString(), allocator_), *v, document); + } + + AssignIfExist(minProperties_, value, GetMinPropertiesString()); + AssignIfExist(maxProperties_, value, GetMaxPropertiesString()); + + // Array + if (const ValueType* v = GetMember(value, GetItemsString())) { + PointerType q = p.Append(GetItemsString(), allocator_); + if (v->IsObject()) // List validation + schemaDocument->CreateSchema(&itemsList_, q, *v, document); + else if (v->IsArray()) { // Tuple validation + itemsTuple_ = static_cast(allocator_->Malloc(sizeof(const Schema*) * v->Size())); + SizeType index = 0; + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr, index++) + schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document); + } + } + + AssignIfExist(minItems_, value, GetMinItemsString()); + AssignIfExist(maxItems_, value, GetMaxItemsString()); + + if (const ValueType* v = GetMember(value, GetAdditionalItemsString())) { + if (v->IsBool()) + additionalItems_ = v->GetBool(); + else if (v->IsObject()) + schemaDocument->CreateSchema(&additionalItemsSchema_, p.Append(GetAdditionalItemsString(), allocator_), *v, document); + } + + AssignIfExist(uniqueItems_, value, GetUniqueItemsString()); + + // String + AssignIfExist(minLength_, value, GetMinLengthString()); + AssignIfExist(maxLength_, value, GetMaxLengthString()); + + if (const ValueType* v = GetMember(value, GetPatternString())) + pattern_ = CreatePattern(*v); + + // Number + if (const ValueType* v = GetMember(value, GetMinimumString())) + if (v->IsNumber()) + minimum_.CopyFrom(*v, *allocator_); + + if (const ValueType* v = GetMember(value, GetMaximumString())) + if (v->IsNumber()) + maximum_.CopyFrom(*v, *allocator_); + + AssignIfExist(exclusiveMinimum_, value, GetExclusiveMinimumString()); + AssignIfExist(exclusiveMaximum_, value, GetExclusiveMaximumString()); + + if (const ValueType* v = GetMember(value, GetMultipleOfString())) + if (v->IsNumber() && v->GetDouble() > 0.0) + multipleOf_.CopyFrom(*v, *allocator_); + + // Default + if (const ValueType* v = GetMember(value, GetDefaultValueString())) + if (v->IsString()) + defaultValueLength_ = v->GetStringLength(); + + } + + ~Schema() { + AllocatorType::Free(enum_); + if (properties_) { + for (SizeType i = 0; i < propertyCount_; i++) + properties_[i].~Property(); + AllocatorType::Free(properties_); + } + if (patternProperties_) { + for (SizeType i = 0; i < patternPropertyCount_; i++) + patternProperties_[i].~PatternProperty(); + AllocatorType::Free(patternProperties_); + } + AllocatorType::Free(itemsTuple_); +#if RAPIDJSON_SCHEMA_HAS_REGEX + if (pattern_) { + pattern_->~RegexType(); + AllocatorType::Free(pattern_); + } +#endif + } + + const SValue& GetURI() const { + return uri_; + } + + const PointerType& GetPointer() const { + return pointer_; + } + + bool BeginValue(Context& context) const { + if (context.inArray) { + if (uniqueItems_) + context.valueUniqueness = true; + + if (itemsList_) + context.valueSchema = itemsList_; + else if (itemsTuple_) { + if (context.arrayElementIndex < itemsTupleCount_) + context.valueSchema = itemsTuple_[context.arrayElementIndex]; + else if (additionalItemsSchema_) + context.valueSchema = additionalItemsSchema_; + else if (additionalItems_) + context.valueSchema = typeless_; + else { + context.error_handler.DisallowedItem(context.arrayElementIndex); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetItemsString()); + } + } + else + context.valueSchema = typeless_; + + context.arrayElementIndex++; + } + return true; + } + + RAPIDJSON_FORCEINLINE bool EndValue(Context& context) const { + if (context.patternPropertiesValidatorCount > 0) { + bool otherValid = false; + SizeType count = context.patternPropertiesValidatorCount; + if (context.objectPatternValidatorType != Context::kPatternValidatorOnly) + otherValid = context.patternPropertiesValidators[--count]->IsValid(); + + bool patternValid = true; + for (SizeType i = 0; i < count; i++) + if (!context.patternPropertiesValidators[i]->IsValid()) { + patternValid = false; + break; + } + + if (context.objectPatternValidatorType == Context::kPatternValidatorOnly) { + if (!patternValid) { + context.error_handler.PropertyViolations(context.patternPropertiesValidators, count); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + } + else if (context.objectPatternValidatorType == Context::kPatternValidatorWithProperty) { + if (!patternValid || !otherValid) { + context.error_handler.PropertyViolations(context.patternPropertiesValidators, count + 1); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + } + else if (!patternValid && !otherValid) { // kPatternValidatorWithAdditionalProperty) + context.error_handler.PropertyViolations(context.patternPropertiesValidators, count + 1); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + } + + if (enum_) { + const uint64_t h = context.factory.GetHashCode(context.hasher); + for (SizeType i = 0; i < enumCount_; i++) + if (enum_[i] == h) + goto foundEnum; + context.error_handler.DisallowedValue(); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetEnumString()); + foundEnum:; + } + + if (allOf_.schemas) + for (SizeType i = allOf_.begin; i < allOf_.begin + allOf_.count; i++) + if (!context.validators[i]->IsValid()) { + context.error_handler.NotAllOf(&context.validators[allOf_.begin], allOf_.count); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAllOfString()); + } + + if (anyOf_.schemas) { + for (SizeType i = anyOf_.begin; i < anyOf_.begin + anyOf_.count; i++) + if (context.validators[i]->IsValid()) + goto foundAny; + context.error_handler.NoneOf(&context.validators[anyOf_.begin], anyOf_.count); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAnyOfString()); + foundAny:; + } + + if (oneOf_.schemas) { + bool oneValid = false; + for (SizeType i = oneOf_.begin; i < oneOf_.begin + oneOf_.count; i++) + if (context.validators[i]->IsValid()) { + if (oneValid) { + context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); + } else + oneValid = true; + } + if (!oneValid) { + context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); + } + } + + if (not_ && context.validators[notValidatorIndex_]->IsValid()) { + context.error_handler.Disallowed(); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetNotString()); + } + + return true; + } + + bool Null(Context& context) const { + if (!(type_ & (1 << kNullSchemaType))) { + DisallowedType(context, GetNullString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + return CreateParallelValidator(context); + } + + bool Bool(Context& context, bool) const { + if (!(type_ & (1 << kBooleanSchemaType))) { + DisallowedType(context, GetBooleanString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + return CreateParallelValidator(context); + } + + bool Int(Context& context, int i) const { + if (!CheckInt(context, i)) + return false; + return CreateParallelValidator(context); + } + + bool Uint(Context& context, unsigned u) const { + if (!CheckUint(context, u)) + return false; + return CreateParallelValidator(context); + } + + bool Int64(Context& context, int64_t i) const { + if (!CheckInt(context, i)) + return false; + return CreateParallelValidator(context); + } + + bool Uint64(Context& context, uint64_t u) const { + if (!CheckUint(context, u)) + return false; + return CreateParallelValidator(context); + } + + bool Double(Context& context, double d) const { + if (!(type_ & (1 << kNumberSchemaType))) { + DisallowedType(context, GetNumberString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + if (!minimum_.IsNull() && !CheckDoubleMinimum(context, d)) + return false; + + if (!maximum_.IsNull() && !CheckDoubleMaximum(context, d)) + return false; + + if (!multipleOf_.IsNull() && !CheckDoubleMultipleOf(context, d)) + return false; + + return CreateParallelValidator(context); + } + + bool String(Context& context, const Ch* str, SizeType length, bool) const { + if (!(type_ & (1 << kStringSchemaType))) { + DisallowedType(context, GetStringString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + if (minLength_ != 0 || maxLength_ != SizeType(~0)) { + SizeType count; + if (internal::CountStringCodePoint(str, length, &count)) { + if (count < minLength_) { + context.error_handler.TooShort(str, length, minLength_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinLengthString()); + } + if (count > maxLength_) { + context.error_handler.TooLong(str, length, maxLength_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxLengthString()); + } + } + } + + if (pattern_ && !IsPatternMatch(pattern_, str, length)) { + context.error_handler.DoesNotMatch(str, length); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternString()); + } + + return CreateParallelValidator(context); + } + + bool StartObject(Context& context) const { + if (!(type_ & (1 << kObjectSchemaType))) { + DisallowedType(context, GetObjectString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + if (hasDependencies_ || hasRequired_) { + context.propertyExist = static_cast(context.factory.MallocState(sizeof(bool) * propertyCount_)); + std::memset(context.propertyExist, 0, sizeof(bool) * propertyCount_); + } + + if (patternProperties_) { // pre-allocate schema array + SizeType count = patternPropertyCount_ + 1; // extra for valuePatternValidatorType + context.patternPropertiesSchemas = static_cast(context.factory.MallocState(sizeof(const SchemaType*) * count)); + context.patternPropertiesSchemaCount = 0; + std::memset(context.patternPropertiesSchemas, 0, sizeof(SchemaType*) * count); + } + + return CreateParallelValidator(context); + } + + bool Key(Context& context, const Ch* str, SizeType len, bool) const { + if (patternProperties_) { + context.patternPropertiesSchemaCount = 0; + for (SizeType i = 0; i < patternPropertyCount_; i++) + if (patternProperties_[i].pattern && IsPatternMatch(patternProperties_[i].pattern, str, len)) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = patternProperties_[i].schema; + context.valueSchema = typeless_; + } + } + + SizeType index; + if (FindPropertyIndex(ValueType(str, len).Move(), &index)) { + if (context.patternPropertiesSchemaCount > 0) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = properties_[index].schema; + context.valueSchema = typeless_; + context.valuePatternValidatorType = Context::kPatternValidatorWithProperty; + } + else + context.valueSchema = properties_[index].schema; + + if (context.propertyExist) + context.propertyExist[index] = true; + + return true; + } + + if (additionalPropertiesSchema_) { + if (additionalPropertiesSchema_ && context.patternPropertiesSchemaCount > 0) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = additionalPropertiesSchema_; + context.valueSchema = typeless_; + context.valuePatternValidatorType = Context::kPatternValidatorWithAdditionalProperty; + } + else + context.valueSchema = additionalPropertiesSchema_; + return true; + } + else if (additionalProperties_) { + context.valueSchema = typeless_; + return true; + } + + if (context.patternPropertiesSchemaCount == 0) { // patternProperties are not additional properties + context.error_handler.DisallowedProperty(str, len); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAdditionalPropertiesString()); + } + + return true; + } + + bool EndObject(Context& context, SizeType memberCount) const { + if (hasRequired_) { + context.error_handler.StartMissingProperties(); + for (SizeType index = 0; index < propertyCount_; index++) + if (properties_[index].required && !context.propertyExist[index]) + if (properties_[index].schema->defaultValueLength_ == 0 ) + context.error_handler.AddMissingProperty(properties_[index].name); + if (context.error_handler.EndMissingProperties()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetRequiredString()); + } + + if (memberCount < minProperties_) { + context.error_handler.TooFewProperties(memberCount, minProperties_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinPropertiesString()); + } + + if (memberCount > maxProperties_) { + context.error_handler.TooManyProperties(memberCount, maxProperties_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxPropertiesString()); + } + + if (hasDependencies_) { + context.error_handler.StartDependencyErrors(); + for (SizeType sourceIndex = 0; sourceIndex < propertyCount_; sourceIndex++) { + const Property& source = properties_[sourceIndex]; + if (context.propertyExist[sourceIndex]) { + if (source.dependencies) { + context.error_handler.StartMissingDependentProperties(); + for (SizeType targetIndex = 0; targetIndex < propertyCount_; targetIndex++) + if (source.dependencies[targetIndex] && !context.propertyExist[targetIndex]) + context.error_handler.AddMissingDependentProperty(properties_[targetIndex].name); + context.error_handler.EndMissingDependentProperties(source.name); + } + else if (source.dependenciesSchema) { + ISchemaValidator* dependenciesValidator = context.validators[source.dependenciesValidatorIndex]; + if (!dependenciesValidator->IsValid()) + context.error_handler.AddDependencySchemaError(source.name, dependenciesValidator); + } + } + } + if (context.error_handler.EndDependencyErrors()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetDependenciesString()); + } + + return true; + } + + bool StartArray(Context& context) const { + if (!(type_ & (1 << kArraySchemaType))) { + DisallowedType(context, GetArrayString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + context.arrayElementIndex = 0; + context.inArray = true; + + return CreateParallelValidator(context); + } + + bool EndArray(Context& context, SizeType elementCount) const { + context.inArray = false; + + if (elementCount < minItems_) { + context.error_handler.TooFewItems(elementCount, minItems_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinItemsString()); + } + + if (elementCount > maxItems_) { + context.error_handler.TooManyItems(elementCount, maxItems_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxItemsString()); + } + + return true; + } + + // Generate functions for string literal according to Ch +#define RAPIDJSON_STRING_(name, ...) \ + static const ValueType& Get##name##String() {\ + static const Ch s[] = { __VA_ARGS__, '\0' };\ + static const ValueType v(s, static_cast(sizeof(s) / sizeof(Ch) - 1));\ + return v;\ + } + + RAPIDJSON_STRING_(Null, 'n', 'u', 'l', 'l') + RAPIDJSON_STRING_(Boolean, 'b', 'o', 'o', 'l', 'e', 'a', 'n') + RAPIDJSON_STRING_(Object, 'o', 'b', 'j', 'e', 'c', 't') + RAPIDJSON_STRING_(Array, 'a', 'r', 'r', 'a', 'y') + RAPIDJSON_STRING_(String, 's', 't', 'r', 'i', 'n', 'g') + RAPIDJSON_STRING_(Number, 'n', 'u', 'm', 'b', 'e', 'r') + RAPIDJSON_STRING_(Integer, 'i', 'n', 't', 'e', 'g', 'e', 'r') + RAPIDJSON_STRING_(Type, 't', 'y', 'p', 'e') + RAPIDJSON_STRING_(Enum, 'e', 'n', 'u', 'm') + RAPIDJSON_STRING_(AllOf, 'a', 'l', 'l', 'O', 'f') + RAPIDJSON_STRING_(AnyOf, 'a', 'n', 'y', 'O', 'f') + RAPIDJSON_STRING_(OneOf, 'o', 'n', 'e', 'O', 'f') + RAPIDJSON_STRING_(Not, 'n', 'o', 't') + RAPIDJSON_STRING_(Properties, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(Required, 'r', 'e', 'q', 'u', 'i', 'r', 'e', 'd') + RAPIDJSON_STRING_(Dependencies, 'd', 'e', 'p', 'e', 'n', 'd', 'e', 'n', 'c', 'i', 'e', 's') + RAPIDJSON_STRING_(PatternProperties, 'p', 'a', 't', 't', 'e', 'r', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(AdditionalProperties, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(MinProperties, 'm', 'i', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(MaxProperties, 'm', 'a', 'x', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(Items, 'i', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MinItems, 'm', 'i', 'n', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MaxItems, 'm', 'a', 'x', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(AdditionalItems, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(UniqueItems, 'u', 'n', 'i', 'q', 'u', 'e', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MinLength, 'm', 'i', 'n', 'L', 'e', 'n', 'g', 't', 'h') + RAPIDJSON_STRING_(MaxLength, 'm', 'a', 'x', 'L', 'e', 'n', 'g', 't', 'h') + RAPIDJSON_STRING_(Pattern, 'p', 'a', 't', 't', 'e', 'r', 'n') + RAPIDJSON_STRING_(Minimum, 'm', 'i', 'n', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(Maximum, 'm', 'a', 'x', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(ExclusiveMinimum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'i', 'n', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(ExclusiveMaximum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'a', 'x', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(MultipleOf, 'm', 'u', 'l', 't', 'i', 'p', 'l', 'e', 'O', 'f') + RAPIDJSON_STRING_(DefaultValue, 'd', 'e', 'f', 'a', 'u', 'l', 't') + +#undef RAPIDJSON_STRING_ + +private: + enum SchemaValueType { + kNullSchemaType, + kBooleanSchemaType, + kObjectSchemaType, + kArraySchemaType, + kStringSchemaType, + kNumberSchemaType, + kIntegerSchemaType, + kTotalSchemaType + }; + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX + typedef internal::GenericRegex RegexType; +#elif RAPIDJSON_SCHEMA_USE_STDREGEX + typedef std::basic_regex RegexType; +#else + typedef char RegexType; +#endif + + struct SchemaArray { + SchemaArray() : schemas(), count() {} + ~SchemaArray() { AllocatorType::Free(schemas); } + const SchemaType** schemas; + SizeType begin; // begin index of context.validators + SizeType count; + }; + + template + void AddUniqueElement(V1& a, const V2& v) { + for (typename V1::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr) + if (*itr == v) + return; + V1 c(v, *allocator_); + a.PushBack(c, *allocator_); + } + + static const ValueType* GetMember(const ValueType& value, const ValueType& name) { + typename ValueType::ConstMemberIterator itr = value.FindMember(name); + return itr != value.MemberEnd() ? &(itr->value) : 0; + } + + static void AssignIfExist(bool& out, const ValueType& value, const ValueType& name) { + if (const ValueType* v = GetMember(value, name)) + if (v->IsBool()) + out = v->GetBool(); + } + + static void AssignIfExist(SizeType& out, const ValueType& value, const ValueType& name) { + if (const ValueType* v = GetMember(value, name)) + if (v->IsUint64() && v->GetUint64() <= SizeType(~0)) + out = static_cast(v->GetUint64()); + } + + void AssignIfExist(SchemaArray& out, SchemaDocumentType& schemaDocument, const PointerType& p, const ValueType& value, const ValueType& name, const ValueType& document) { + if (const ValueType* v = GetMember(value, name)) { + if (v->IsArray() && v->Size() > 0) { + PointerType q = p.Append(name, allocator_); + out.count = v->Size(); + out.schemas = static_cast(allocator_->Malloc(out.count * sizeof(const Schema*))); + memset(out.schemas, 0, sizeof(Schema*)* out.count); + for (SizeType i = 0; i < out.count; i++) + schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document); + out.begin = validatorCount_; + validatorCount_ += out.count; + } + } + } + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX + template + RegexType* CreatePattern(const ValueType& value) { + if (value.IsString()) { + RegexType* r = new (allocator_->Malloc(sizeof(RegexType))) RegexType(value.GetString(), allocator_); + if (!r->IsValid()) { + r->~RegexType(); + AllocatorType::Free(r); + r = 0; + } + return r; + } + return 0; + } + + static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType) { + GenericRegexSearch rs(*pattern); + return rs.Search(str); + } +#elif RAPIDJSON_SCHEMA_USE_STDREGEX + template + RegexType* CreatePattern(const ValueType& value) { + if (value.IsString()) + RegexType *r = static_cast(allocator_->Malloc(sizeof(RegexType))); + try { + return new (r) RegexType(value.GetString(), std::size_t(value.GetStringLength()), std::regex_constants::ECMAScript); + } + catch (const std::regex_error&) { + AllocatorType::Free(r); + } + return 0; + } + + static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType length) { + std::match_results r; + return std::regex_search(str, str + length, r, *pattern); + } +#else + template + RegexType* CreatePattern(const ValueType&) { return 0; } + + static bool IsPatternMatch(const RegexType*, const Ch *, SizeType) { return true; } +#endif // RAPIDJSON_SCHEMA_USE_STDREGEX + + void AddType(const ValueType& type) { + if (type == GetNullString() ) type_ |= 1 << kNullSchemaType; + else if (type == GetBooleanString()) type_ |= 1 << kBooleanSchemaType; + else if (type == GetObjectString() ) type_ |= 1 << kObjectSchemaType; + else if (type == GetArrayString() ) type_ |= 1 << kArraySchemaType; + else if (type == GetStringString() ) type_ |= 1 << kStringSchemaType; + else if (type == GetIntegerString()) type_ |= 1 << kIntegerSchemaType; + else if (type == GetNumberString() ) type_ |= (1 << kNumberSchemaType) | (1 << kIntegerSchemaType); + } + + bool CreateParallelValidator(Context& context) const { + if (enum_ || context.arrayUniqueness) + context.hasher = context.factory.CreateHasher(); + + if (validatorCount_) { + RAPIDJSON_ASSERT(context.validators == 0); + context.validators = static_cast(context.factory.MallocState(sizeof(ISchemaValidator*) * validatorCount_)); + context.validatorCount = validatorCount_; + + if (allOf_.schemas) + CreateSchemaValidators(context, allOf_); + + if (anyOf_.schemas) + CreateSchemaValidators(context, anyOf_); + + if (oneOf_.schemas) + CreateSchemaValidators(context, oneOf_); + + if (not_) + context.validators[notValidatorIndex_] = context.factory.CreateSchemaValidator(*not_); + + if (hasSchemaDependencies_) { + for (SizeType i = 0; i < propertyCount_; i++) + if (properties_[i].dependenciesSchema) + context.validators[properties_[i].dependenciesValidatorIndex] = context.factory.CreateSchemaValidator(*properties_[i].dependenciesSchema); + } + } + + return true; + } + + void CreateSchemaValidators(Context& context, const SchemaArray& schemas) const { + for (SizeType i = 0; i < schemas.count; i++) + context.validators[schemas.begin + i] = context.factory.CreateSchemaValidator(*schemas.schemas[i]); + } + + // O(n) + bool FindPropertyIndex(const ValueType& name, SizeType* outIndex) const { + SizeType len = name.GetStringLength(); + const Ch* str = name.GetString(); + for (SizeType index = 0; index < propertyCount_; index++) + if (properties_[index].name.GetStringLength() == len && + (std::memcmp(properties_[index].name.GetString(), str, sizeof(Ch) * len) == 0)) + { + *outIndex = index; + return true; + } + return false; + } + + bool CheckInt(Context& context, int64_t i) const { + if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) { + DisallowedType(context, GetIntegerString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + if (!minimum_.IsNull()) { + if (minimum_.IsInt64()) { + if (exclusiveMinimum_ ? i <= minimum_.GetInt64() : i < minimum_.GetInt64()) { + context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + } + } + else if (minimum_.IsUint64()) { + context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); // i <= max(int64_t) < minimum.GetUint64() + } + else if (!CheckDoubleMinimum(context, static_cast(i))) + return false; + } + + if (!maximum_.IsNull()) { + if (maximum_.IsInt64()) { + if (exclusiveMaximum_ ? i >= maximum_.GetInt64() : i > maximum_.GetInt64()) { + context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + } + } + else if (maximum_.IsUint64()) { } + /* do nothing */ // i <= max(int64_t) < maximum_.GetUint64() + else if (!CheckDoubleMaximum(context, static_cast(i))) + return false; + } + + if (!multipleOf_.IsNull()) { + if (multipleOf_.IsUint64()) { + if (static_cast(i >= 0 ? i : -i) % multipleOf_.GetUint64() != 0) { + context.error_handler.NotMultipleOf(i, multipleOf_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + } + } + else if (!CheckDoubleMultipleOf(context, static_cast(i))) + return false; + } + + return true; + } + + bool CheckUint(Context& context, uint64_t i) const { + if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) { + DisallowedType(context, GetIntegerString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + if (!minimum_.IsNull()) { + if (minimum_.IsUint64()) { + if (exclusiveMinimum_ ? i <= minimum_.GetUint64() : i < minimum_.GetUint64()) { + context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + } + } + else if (minimum_.IsInt64()) + /* do nothing */; // i >= 0 > minimum.Getint64() + else if (!CheckDoubleMinimum(context, static_cast(i))) + return false; + } + + if (!maximum_.IsNull()) { + if (maximum_.IsUint64()) { + if (exclusiveMaximum_ ? i >= maximum_.GetUint64() : i > maximum_.GetUint64()) { + context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + } + } + else if (maximum_.IsInt64()) { + context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); // i >= 0 > maximum_ + } + else if (!CheckDoubleMaximum(context, static_cast(i))) + return false; + } + + if (!multipleOf_.IsNull()) { + if (multipleOf_.IsUint64()) { + if (i % multipleOf_.GetUint64() != 0) { + context.error_handler.NotMultipleOf(i, multipleOf_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + } + } + else if (!CheckDoubleMultipleOf(context, static_cast(i))) + return false; + } + + return true; + } + + bool CheckDoubleMinimum(Context& context, double d) const { + if (exclusiveMinimum_ ? d <= minimum_.GetDouble() : d < minimum_.GetDouble()) { + context.error_handler.BelowMinimum(d, minimum_, exclusiveMinimum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + } + return true; + } + + bool CheckDoubleMaximum(Context& context, double d) const { + if (exclusiveMaximum_ ? d >= maximum_.GetDouble() : d > maximum_.GetDouble()) { + context.error_handler.AboveMaximum(d, maximum_, exclusiveMaximum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + } + return true; + } + + bool CheckDoubleMultipleOf(Context& context, double d) const { + double a = std::abs(d), b = std::abs(multipleOf_.GetDouble()); + double q = std::floor(a / b); + double r = a - q * b; + if (r > 0.0) { + context.error_handler.NotMultipleOf(d, multipleOf_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + } + return true; + } + + void DisallowedType(Context& context, const ValueType& actualType) const { + ErrorHandler& eh = context.error_handler; + eh.StartDisallowedType(); + + if (type_ & (1 << kNullSchemaType)) eh.AddExpectedType(GetNullString()); + if (type_ & (1 << kBooleanSchemaType)) eh.AddExpectedType(GetBooleanString()); + if (type_ & (1 << kObjectSchemaType)) eh.AddExpectedType(GetObjectString()); + if (type_ & (1 << kArraySchemaType)) eh.AddExpectedType(GetArrayString()); + if (type_ & (1 << kStringSchemaType)) eh.AddExpectedType(GetStringString()); + + if (type_ & (1 << kNumberSchemaType)) eh.AddExpectedType(GetNumberString()); + else if (type_ & (1 << kIntegerSchemaType)) eh.AddExpectedType(GetIntegerString()); + + eh.EndDisallowedType(actualType); + } + + struct Property { + Property() : schema(), dependenciesSchema(), dependenciesValidatorIndex(), dependencies(), required(false) {} + ~Property() { AllocatorType::Free(dependencies); } + SValue name; + const SchemaType* schema; + const SchemaType* dependenciesSchema; + SizeType dependenciesValidatorIndex; + bool* dependencies; + bool required; + }; + + struct PatternProperty { + PatternProperty() : schema(), pattern() {} + ~PatternProperty() { + if (pattern) { + pattern->~RegexType(); + AllocatorType::Free(pattern); + } + } + const SchemaType* schema; + RegexType* pattern; + }; + + AllocatorType* allocator_; + SValue uri_; + PointerType pointer_; + const SchemaType* typeless_; + uint64_t* enum_; + SizeType enumCount_; + SchemaArray allOf_; + SchemaArray anyOf_; + SchemaArray oneOf_; + const SchemaType* not_; + unsigned type_; // bitmask of kSchemaType + SizeType validatorCount_; + SizeType notValidatorIndex_; + + Property* properties_; + const SchemaType* additionalPropertiesSchema_; + PatternProperty* patternProperties_; + SizeType patternPropertyCount_; + SizeType propertyCount_; + SizeType minProperties_; + SizeType maxProperties_; + bool additionalProperties_; + bool hasDependencies_; + bool hasRequired_; + bool hasSchemaDependencies_; + + const SchemaType* additionalItemsSchema_; + const SchemaType* itemsList_; + const SchemaType** itemsTuple_; + SizeType itemsTupleCount_; + SizeType minItems_; + SizeType maxItems_; + bool additionalItems_; + bool uniqueItems_; + + RegexType* pattern_; + SizeType minLength_; + SizeType maxLength_; + + SValue minimum_; + SValue maximum_; + SValue multipleOf_; + bool exclusiveMinimum_; + bool exclusiveMaximum_; + + SizeType defaultValueLength_; +}; + +template +struct TokenHelper { + RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { + *documentStack.template Push() = '/'; + char buffer[21]; + size_t length = static_cast((sizeof(SizeType) == 4 ? u32toa(index, buffer) : u64toa(index, buffer)) - buffer); + for (size_t i = 0; i < length; i++) + *documentStack.template Push() = static_cast(buffer[i]); + } +}; + +// Partial specialized version for char to prevent buffer copying. +template +struct TokenHelper { + RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { + if (sizeof(SizeType) == 4) { + char *buffer = documentStack.template Push(1 + 10); // '/' + uint + *buffer++ = '/'; + const char* end = internal::u32toa(index, buffer); + documentStack.template Pop(static_cast(10 - (end - buffer))); + } + else { + char *buffer = documentStack.template Push(1 + 20); // '/' + uint64 + *buffer++ = '/'; + const char* end = internal::u64toa(index, buffer); + documentStack.template Pop(static_cast(20 - (end - buffer))); + } + } +}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// IGenericRemoteSchemaDocumentProvider + +template +class IGenericRemoteSchemaDocumentProvider { +public: + typedef typename SchemaDocumentType::Ch Ch; + + virtual ~IGenericRemoteSchemaDocumentProvider() {} + virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericSchemaDocument + +//! JSON schema document. +/*! + A JSON schema document is a compiled version of a JSON schema. + It is basically a tree of internal::Schema. + + \note This is an immutable class (i.e. its instance cannot be modified after construction). + \tparam ValueT Type of JSON value (e.g. \c Value ), which also determine the encoding. + \tparam Allocator Allocator type for allocating memory of this document. +*/ +template +class GenericSchemaDocument { +public: + typedef ValueT ValueType; + typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProviderType; + typedef Allocator AllocatorType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + typedef internal::Schema SchemaType; + typedef GenericPointer PointerType; + typedef GenericValue URIType; + friend class internal::Schema; + template + friend class GenericSchemaValidator; + + //! Constructor. + /*! + Compile a JSON document into schema document. + + \param document A JSON document as source. + \param uri The base URI of this schema document for purposes of violation reporting. + \param uriLength Length of \c name, in code points. + \param remoteProvider An optional remote schema document provider for resolving remote reference. Can be null. + \param allocator An optional allocator instance for allocating memory. Can be null. + */ + explicit GenericSchemaDocument(const ValueType& document, const Ch* uri = 0, SizeType uriLength = 0, + IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0) : + remoteProvider_(remoteProvider), + allocator_(allocator), + ownAllocator_(), + root_(), + typeless_(), + schemaMap_(allocator, kInitialSchemaMapSize), + schemaRef_(allocator, kInitialSchemaRefSize) + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + + Ch noUri[1] = {0}; + uri_.SetString(uri ? uri : noUri, uriLength, *allocator_); + + typeless_ = static_cast(allocator_->Malloc(sizeof(SchemaType))); + new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_); + + // Generate root schema, it will call CreateSchema() to create sub-schemas, + // And call AddRefSchema() if there are $ref. + CreateSchemaRecursive(&root_, PointerType(), document, document); + + // Resolve $ref + while (!schemaRef_.Empty()) { + SchemaRefEntry* refEntry = schemaRef_.template Pop(1); + if (const SchemaType* s = GetSchema(refEntry->target)) { + if (refEntry->schema) + *refEntry->schema = s; + + // Create entry in map if not exist + if (!GetSchema(refEntry->source)) { + new (schemaMap_.template Push()) SchemaEntry(refEntry->source, const_cast(s), false, allocator_); + } + } + else if (refEntry->schema) + *refEntry->schema = typeless_; + + refEntry->~SchemaRefEntry(); + } + + RAPIDJSON_ASSERT(root_ != 0); + + schemaRef_.ShrinkToFit(); // Deallocate all memory for ref + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericSchemaDocument(GenericSchemaDocument&& rhs) RAPIDJSON_NOEXCEPT : + remoteProvider_(rhs.remoteProvider_), + allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + root_(rhs.root_), + typeless_(rhs.typeless_), + schemaMap_(std::move(rhs.schemaMap_)), + schemaRef_(std::move(rhs.schemaRef_)), + uri_(std::move(rhs.uri_)) + { + rhs.remoteProvider_ = 0; + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.typeless_ = 0; + } +#endif + + //! Destructor + ~GenericSchemaDocument() { + while (!schemaMap_.Empty()) + schemaMap_.template Pop(1)->~SchemaEntry(); + + if (typeless_) { + typeless_->~SchemaType(); + Allocator::Free(typeless_); + } + + RAPIDJSON_DELETE(ownAllocator_); + } + + const URIType& GetURI() const { return uri_; } + + //! Get the root schema. + const SchemaType& GetRoot() const { return *root_; } + +private: + //! Prohibit copying + GenericSchemaDocument(const GenericSchemaDocument&); + //! Prohibit assignment + GenericSchemaDocument& operator=(const GenericSchemaDocument&); + + struct SchemaRefEntry { + SchemaRefEntry(const PointerType& s, const PointerType& t, const SchemaType** outSchema, Allocator *allocator) : source(s, allocator), target(t, allocator), schema(outSchema) {} + PointerType source; + PointerType target; + const SchemaType** schema; + }; + + struct SchemaEntry { + SchemaEntry(const PointerType& p, SchemaType* s, bool o, Allocator* allocator) : pointer(p, allocator), schema(s), owned(o) {} + ~SchemaEntry() { + if (owned) { + schema->~SchemaType(); + Allocator::Free(schema); + } + } + PointerType pointer; + SchemaType* schema; + bool owned; + }; + + void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { + if (schema) + *schema = typeless_; + + if (v.GetType() == kObjectType) { + const SchemaType* s = GetSchema(pointer); + if (!s) + CreateSchema(schema, pointer, v, document); + + for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr) + CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document); + } + else if (v.GetType() == kArrayType) + for (SizeType i = 0; i < v.Size(); i++) + CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document); + } + + void CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { + RAPIDJSON_ASSERT(pointer.IsValid()); + if (v.IsObject()) { + if (!HandleRefSchema(pointer, schema, v, document)) { + SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_); + new (schemaMap_.template Push()) SchemaEntry(pointer, s, true, allocator_); + if (schema) + *schema = s; + } + } + } + + bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document) { + static const Ch kRefString[] = { '$', 'r', 'e', 'f', '\0' }; + static const ValueType kRefValue(kRefString, 4); + + typename ValueType::ConstMemberIterator itr = v.FindMember(kRefValue); + if (itr == v.MemberEnd()) + return false; + + if (itr->value.IsString()) { + SizeType len = itr->value.GetStringLength(); + if (len > 0) { + const Ch* s = itr->value.GetString(); + SizeType i = 0; + while (i < len && s[i] != '#') // Find the first # + i++; + + if (i > 0) { // Remote reference, resolve immediately + if (remoteProvider_) { + if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(s, i)) { + PointerType pointer(&s[i], len - i, allocator_); + if (pointer.IsValid()) { + if (const SchemaType* sc = remoteDocument->GetSchema(pointer)) { + if (schema) + *schema = sc; + new (schemaMap_.template Push()) SchemaEntry(source, const_cast(sc), false, allocator_); + return true; + } + } + } + } + } + else if (s[i] == '#') { // Local reference, defer resolution + PointerType pointer(&s[i], len - i, allocator_); + if (pointer.IsValid()) { + if (const ValueType* nv = pointer.Get(document)) + if (HandleRefSchema(source, schema, *nv, document)) + return true; + + new (schemaRef_.template Push()) SchemaRefEntry(source, pointer, schema, allocator_); + return true; + } + } + } + } + return false; + } + + const SchemaType* GetSchema(const PointerType& pointer) const { + for (const SchemaEntry* target = schemaMap_.template Bottom(); target != schemaMap_.template End(); ++target) + if (pointer == target->pointer) + return target->schema; + return 0; + } + + PointerType GetPointer(const SchemaType* schema) const { + for (const SchemaEntry* target = schemaMap_.template Bottom(); target != schemaMap_.template End(); ++target) + if (schema == target->schema) + return target->pointer; + return PointerType(); + } + + const SchemaType* GetTypeless() const { return typeless_; } + + static const size_t kInitialSchemaMapSize = 64; + static const size_t kInitialSchemaRefSize = 64; + + IRemoteSchemaDocumentProviderType* remoteProvider_; + Allocator *allocator_; + Allocator *ownAllocator_; + const SchemaType* root_; //!< Root schema. + SchemaType* typeless_; + internal::Stack schemaMap_; // Stores created Pointer -> Schemas + internal::Stack schemaRef_; // Stores Pointer from $ref and schema which holds the $ref + URIType uri_; +}; + +//! GenericSchemaDocument using Value type. +typedef GenericSchemaDocument SchemaDocument; +//! IGenericRemoteSchemaDocumentProvider using SchemaDocument. +typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProvider; + +/////////////////////////////////////////////////////////////////////////////// +// GenericSchemaValidator + +//! JSON Schema Validator. +/*! + A SAX style JSON schema validator. + It uses a \c GenericSchemaDocument to validate SAX events. + It delegates the incoming SAX events to an output handler. + The default output handler does nothing. + It can be reused multiple times by calling \c Reset(). + + \tparam SchemaDocumentType Type of schema document. + \tparam OutputHandler Type of output handler. Default handler does nothing. + \tparam StateAllocator Allocator for storing the internal validation states. +*/ +template < + typename SchemaDocumentType, + typename OutputHandler = BaseReaderHandler, + typename StateAllocator = CrtAllocator> +class GenericSchemaValidator : + public internal::ISchemaStateFactory, + public internal::ISchemaValidator, + public internal::IValidationErrorHandler +{ +public: + typedef typename SchemaDocumentType::SchemaType SchemaType; + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename SchemaType::EncodingType EncodingType; + typedef typename SchemaType::SValue SValue; + typedef typename EncodingType::Ch Ch; + typedef GenericStringRef StringRefType; + typedef GenericValue ValueType; + + //! Constructor without output handler. + /*! + \param schemaDocument The schema document to conform to. + \param allocator Optional allocator for storing internal validation states. + \param schemaStackCapacity Optional initial capacity of schema path stack. + \param documentStackCapacity Optional initial capacity of document path stack. + */ + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(schemaDocument.GetRoot()), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + outputHandler_(0), + error_(kObjectType), + currentError_(), + missingDependents_(), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(0) +#endif + { + } + + //! Constructor with output handler. + /*! + \param schemaDocument The schema document to conform to. + \param allocator Optional allocator for storing internal validation states. + \param schemaStackCapacity Optional initial capacity of schema path stack. + \param documentStackCapacity Optional initial capacity of document path stack. + */ + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + OutputHandler& outputHandler, + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(schemaDocument.GetRoot()), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + outputHandler_(&outputHandler), + error_(kObjectType), + currentError_(), + missingDependents_(), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(0) +#endif + { + } + + //! Destructor. + ~GenericSchemaValidator() { + Reset(); + RAPIDJSON_DELETE(ownStateAllocator_); + } + + //! Reset the internal states. + void Reset() { + while (!schemaStack_.Empty()) + PopSchema(); + documentStack_.Clear(); + error_.SetObject(); + currentError_.SetNull(); + missingDependents_.SetNull(); + valid_ = true; + } + + //! Checks whether the current state is valid. + // Implementation of ISchemaValidator + virtual bool IsValid() const { return valid_; } + + //! Gets the error object. + ValueType& GetError() { return error_; } + const ValueType& GetError() const { return error_; } + + //! Gets the JSON pointer pointed to the invalid schema. + PointerType GetInvalidSchemaPointer() const { + return schemaStack_.Empty() ? PointerType() : CurrentSchema().GetPointer(); + } + + //! Gets the keyword of invalid schema. + const Ch* GetInvalidSchemaKeyword() const { + return schemaStack_.Empty() ? 0 : CurrentContext().invalidKeyword; + } + + //! Gets the JSON pointer pointed to the invalid value. + PointerType GetInvalidDocumentPointer() const { + if (documentStack_.Empty()) { + return PointerType(); + } + else { + return PointerType(documentStack_.template Bottom(), documentStack_.GetSize() / sizeof(Ch)); + } + } + + void NotMultipleOf(int64_t actual, const SValue& expected) { + AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected); + } + void NotMultipleOf(uint64_t actual, const SValue& expected) { + AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected); + } + void NotMultipleOf(double actual, const SValue& expected) { + AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected); + } + void AboveMaximum(int64_t actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMaximumString : 0); + } + void AboveMaximum(uint64_t actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMaximumString : 0); + } + void AboveMaximum(double actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMaximumString : 0); + } + void BelowMinimum(int64_t actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMinimumString : 0); + } + void BelowMinimum(uint64_t actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMinimumString : 0); + } + void BelowMinimum(double actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMinimumString : 0); + } + + void TooLong(const Ch* str, SizeType length, SizeType expected) { + AddNumberError(SchemaType::GetMaxLengthString(), + ValueType(str, length, GetStateAllocator()).Move(), SValue(expected).Move()); + } + void TooShort(const Ch* str, SizeType length, SizeType expected) { + AddNumberError(SchemaType::GetMinLengthString(), + ValueType(str, length, GetStateAllocator()).Move(), SValue(expected).Move()); + } + void DoesNotMatch(const Ch* str, SizeType length) { + currentError_.SetObject(); + currentError_.AddMember(GetActualString(), ValueType(str, length, GetStateAllocator()).Move(), GetStateAllocator()); + AddCurrentError(SchemaType::GetPatternString()); + } + + void DisallowedItem(SizeType index) { + currentError_.SetObject(); + currentError_.AddMember(GetDisallowedString(), ValueType(index).Move(), GetStateAllocator()); + AddCurrentError(SchemaType::GetAdditionalItemsString(), true); + } + void TooFewItems(SizeType actualCount, SizeType expectedCount) { + AddNumberError(SchemaType::GetMinItemsString(), + ValueType(actualCount).Move(), SValue(expectedCount).Move()); + } + void TooManyItems(SizeType actualCount, SizeType expectedCount) { + AddNumberError(SchemaType::GetMaxItemsString(), + ValueType(actualCount).Move(), SValue(expectedCount).Move()); + } + void DuplicateItems(SizeType index1, SizeType index2) { + ValueType duplicates(kArrayType); + duplicates.PushBack(index1, GetStateAllocator()); + duplicates.PushBack(index2, GetStateAllocator()); + currentError_.SetObject(); + currentError_.AddMember(GetDuplicatesString(), duplicates, GetStateAllocator()); + AddCurrentError(SchemaType::GetUniqueItemsString(), true); + } + + void TooManyProperties(SizeType actualCount, SizeType expectedCount) { + AddNumberError(SchemaType::GetMaxPropertiesString(), + ValueType(actualCount).Move(), SValue(expectedCount).Move()); + } + void TooFewProperties(SizeType actualCount, SizeType expectedCount) { + AddNumberError(SchemaType::GetMinPropertiesString(), + ValueType(actualCount).Move(), SValue(expectedCount).Move()); + } + void StartMissingProperties() { + currentError_.SetArray(); + } + void AddMissingProperty(const SValue& name) { + currentError_.PushBack(ValueType(name, GetStateAllocator()).Move(), GetStateAllocator()); + } + bool EndMissingProperties() { + if (currentError_.Empty()) + return false; + ValueType error(kObjectType); + error.AddMember(GetMissingString(), currentError_, GetStateAllocator()); + currentError_ = error; + AddCurrentError(SchemaType::GetRequiredString()); + return true; + } + void PropertyViolations(ISchemaValidator** subvalidators, SizeType count) { + for (SizeType i = 0; i < count; ++i) + MergeError(static_cast(subvalidators[i])->GetError()); + } + void DisallowedProperty(const Ch* name, SizeType length) { + currentError_.SetObject(); + currentError_.AddMember(GetDisallowedString(), ValueType(name, length, GetStateAllocator()).Move(), GetStateAllocator()); + AddCurrentError(SchemaType::GetAdditionalPropertiesString(), true); + } + + void StartDependencyErrors() { + currentError_.SetObject(); + } + void StartMissingDependentProperties() { + missingDependents_.SetArray(); + } + void AddMissingDependentProperty(const SValue& targetName) { + missingDependents_.PushBack(ValueType(targetName, GetStateAllocator()).Move(), GetStateAllocator()); + } + void EndMissingDependentProperties(const SValue& sourceName) { + if (!missingDependents_.Empty()) + currentError_.AddMember(ValueType(sourceName, GetStateAllocator()).Move(), + missingDependents_, GetStateAllocator()); + } + void AddDependencySchemaError(const SValue& sourceName, ISchemaValidator* subvalidator) { + currentError_.AddMember(ValueType(sourceName, GetStateAllocator()).Move(), + static_cast(subvalidator)->GetError(), GetStateAllocator()); + } + bool EndDependencyErrors() { + if (currentError_.ObjectEmpty()) + return false; + ValueType error(kObjectType); + error.AddMember(GetErrorsString(), currentError_, GetStateAllocator()); + currentError_ = error; + AddCurrentError(SchemaType::GetDependenciesString()); + return true; + } + + void DisallowedValue() { + currentError_.SetObject(); + AddCurrentError(SchemaType::GetEnumString()); + } + void StartDisallowedType() { + currentError_.SetArray(); + } + void AddExpectedType(const typename SchemaType::ValueType& expectedType) { + currentError_.PushBack(ValueType(expectedType, GetStateAllocator()).Move(), GetStateAllocator()); + } + void EndDisallowedType(const typename SchemaType::ValueType& actualType) { + ValueType error(kObjectType); + error.AddMember(GetExpectedString(), currentError_, GetStateAllocator()); + error.AddMember(GetActualString(), ValueType(actualType, GetStateAllocator()).Move(), GetStateAllocator()); + currentError_ = error; + AddCurrentError(SchemaType::GetTypeString()); + } + void NotAllOf(ISchemaValidator** subvalidators, SizeType count) { + for (SizeType i = 0; i < count; ++i) { + MergeError(static_cast(subvalidators[i])->GetError()); + } + } + void NoneOf(ISchemaValidator** subvalidators, SizeType count) { + AddErrorArray(SchemaType::GetAnyOfString(), subvalidators, count); + } + void NotOneOf(ISchemaValidator** subvalidators, SizeType count) { + AddErrorArray(SchemaType::GetOneOfString(), subvalidators, count); + } + void Disallowed() { + currentError_.SetObject(); + AddCurrentError(SchemaType::GetNotString()); + } + +#define RAPIDJSON_STRING_(name, ...) \ + static const StringRefType& Get##name##String() {\ + static const Ch s[] = { __VA_ARGS__, '\0' };\ + static const StringRefType v(s, static_cast(sizeof(s) / sizeof(Ch) - 1)); \ + return v;\ + } + + RAPIDJSON_STRING_(InstanceRef, 'i', 'n', 's', 't', 'a', 'n', 'c', 'e', 'R', 'e', 'f') + RAPIDJSON_STRING_(SchemaRef, 's', 'c', 'h', 'e', 'm', 'a', 'R', 'e', 'f') + RAPIDJSON_STRING_(Expected, 'e', 'x', 'p', 'e', 'c', 't', 'e', 'd') + RAPIDJSON_STRING_(Actual, 'a', 'c', 't', 'u', 'a', 'l') + RAPIDJSON_STRING_(Disallowed, 'd', 'i', 's', 'a', 'l', 'l', 'o', 'w', 'e', 'd') + RAPIDJSON_STRING_(Missing, 'm', 'i', 's', 's', 'i', 'n', 'g') + RAPIDJSON_STRING_(Errors, 'e', 'r', 'r', 'o', 'r', 's') + RAPIDJSON_STRING_(Duplicates, 'd', 'u', 'p', 'l', 'i', 'c', 'a', 't', 'e', 's') + +#undef RAPIDJSON_STRING_ + +#if RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() \ +RAPIDJSON_MULTILINEMACRO_BEGIN\ + *documentStack_.template Push() = '\0';\ + documentStack_.template Pop(1);\ + internal::PrintInvalidDocument(documentStack_.template Bottom());\ +RAPIDJSON_MULTILINEMACRO_END +#else +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() +#endif + +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_(method, arg1)\ + if (!valid_) return false; \ + if (!BeginValue() || !CurrentSchema().method arg1) {\ + RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_();\ + return valid_ = false;\ + } + +#define RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2)\ + for (Context* context = schemaStack_.template Bottom(); context != schemaStack_.template End(); context++) {\ + if (context->hasher)\ + static_cast(context->hasher)->method arg2;\ + if (context->validators)\ + for (SizeType i_ = 0; i_ < context->validatorCount; i_++)\ + static_cast(context->validators[i_])->method arg2;\ + if (context->patternPropertiesValidators)\ + for (SizeType i_ = 0; i_ < context->patternPropertiesValidatorCount; i_++)\ + static_cast(context->patternPropertiesValidators[i_])->method arg2;\ + } + +#define RAPIDJSON_SCHEMA_HANDLE_END_(method, arg2)\ + return valid_ = EndValue() && (!outputHandler_ || outputHandler_->method arg2) + +#define RAPIDJSON_SCHEMA_HANDLE_VALUE_(method, arg1, arg2) \ + RAPIDJSON_SCHEMA_HANDLE_BEGIN_ (method, arg1);\ + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2);\ + RAPIDJSON_SCHEMA_HANDLE_END_ (method, arg2) + + bool Null() { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Null, (CurrentContext()), ( )); } + bool Bool(bool b) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Bool, (CurrentContext(), b), (b)); } + bool Int(int i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int, (CurrentContext(), i), (i)); } + bool Uint(unsigned u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint, (CurrentContext(), u), (u)); } + bool Int64(int64_t i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int64, (CurrentContext(), i), (i)); } + bool Uint64(uint64_t u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint64, (CurrentContext(), u), (u)); } + bool Double(double d) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Double, (CurrentContext(), d), (d)); } + bool RawNumber(const Ch* str, SizeType length, bool copy) + { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } + bool String(const Ch* str, SizeType length, bool copy) + { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } + + bool StartObject() { + RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartObject, (CurrentContext())); + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartObject, ()); + return valid_ = !outputHandler_ || outputHandler_->StartObject(); + } + + bool Key(const Ch* str, SizeType len, bool copy) { + if (!valid_) return false; + AppendToken(str, len); + if (!CurrentSchema().Key(CurrentContext(), str, len, copy)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(Key, (str, len, copy)); + return valid_ = !outputHandler_ || outputHandler_->Key(str, len, copy); + } + + bool EndObject(SizeType memberCount) { + if (!valid_) return false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndObject, (memberCount)); + if (!CurrentSchema().EndObject(CurrentContext(), memberCount)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_END_(EndObject, (memberCount)); + } + + bool StartArray() { + RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartArray, (CurrentContext())); + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartArray, ()); + return valid_ = !outputHandler_ || outputHandler_->StartArray(); + } + + bool EndArray(SizeType elementCount) { + if (!valid_) return false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndArray, (elementCount)); + if (!CurrentSchema().EndArray(CurrentContext(), elementCount)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_END_(EndArray, (elementCount)); + } + +#undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_ +#undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_ +#undef RAPIDJSON_SCHEMA_HANDLE_PARALLEL_ +#undef RAPIDJSON_SCHEMA_HANDLE_VALUE_ + + // Implementation of ISchemaStateFactory + virtual ISchemaValidator* CreateSchemaValidator(const SchemaType& root) { + return new (GetStateAllocator().Malloc(sizeof(GenericSchemaValidator))) GenericSchemaValidator(*schemaDocument_, root, documentStack_.template Bottom(), documentStack_.GetSize(), +#if RAPIDJSON_SCHEMA_VERBOSE + depth_ + 1, +#endif + &GetStateAllocator()); + } + + virtual void DestroySchemaValidator(ISchemaValidator* validator) { + GenericSchemaValidator* v = static_cast(validator); + v->~GenericSchemaValidator(); + StateAllocator::Free(v); + } + + virtual void* CreateHasher() { + return new (GetStateAllocator().Malloc(sizeof(HasherType))) HasherType(&GetStateAllocator()); + } + + virtual uint64_t GetHashCode(void* hasher) { + return static_cast(hasher)->GetHashCode(); + } + + virtual void DestroryHasher(void* hasher) { + HasherType* h = static_cast(hasher); + h->~HasherType(); + StateAllocator::Free(h); + } + + virtual void* MallocState(size_t size) { + return GetStateAllocator().Malloc(size); + } + + virtual void FreeState(void* p) { + StateAllocator::Free(p); + } + +private: + typedef typename SchemaType::Context Context; + typedef GenericValue, StateAllocator> HashCodeArray; + typedef internal::Hasher HasherType; + + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + const SchemaType& root, + const char* basePath, size_t basePathSize, +#if RAPIDJSON_SCHEMA_VERBOSE + unsigned depth, +#endif + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(root), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + outputHandler_(0), + error_(kObjectType), + currentError_(), + missingDependents_(), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(depth) +#endif + { + if (basePath && basePathSize) + memcpy(documentStack_.template Push(basePathSize), basePath, basePathSize); + } + + StateAllocator& GetStateAllocator() { + if (!stateAllocator_) + stateAllocator_ = ownStateAllocator_ = RAPIDJSON_NEW(StateAllocator)(); + return *stateAllocator_; + } + + bool BeginValue() { + if (schemaStack_.Empty()) + PushSchema(root_); + else { + if (CurrentContext().inArray) + internal::TokenHelper, Ch>::AppendIndexToken(documentStack_, CurrentContext().arrayElementIndex); + + if (!CurrentSchema().BeginValue(CurrentContext())) + return false; + + SizeType count = CurrentContext().patternPropertiesSchemaCount; + const SchemaType** sa = CurrentContext().patternPropertiesSchemas; + typename Context::PatternValidatorType patternValidatorType = CurrentContext().valuePatternValidatorType; + bool valueUniqueness = CurrentContext().valueUniqueness; + RAPIDJSON_ASSERT(CurrentContext().valueSchema); + PushSchema(*CurrentContext().valueSchema); + + if (count > 0) { + CurrentContext().objectPatternValidatorType = patternValidatorType; + ISchemaValidator**& va = CurrentContext().patternPropertiesValidators; + SizeType& validatorCount = CurrentContext().patternPropertiesValidatorCount; + va = static_cast(MallocState(sizeof(ISchemaValidator*) * count)); + for (SizeType i = 0; i < count; i++) + va[validatorCount++] = CreateSchemaValidator(*sa[i]); + } + + CurrentContext().arrayUniqueness = valueUniqueness; + } + return true; + } + + bool EndValue() { + if (!CurrentSchema().EndValue(CurrentContext())) + return false; + +#if RAPIDJSON_SCHEMA_VERBOSE + GenericStringBuffer sb; + schemaDocument_->GetPointer(&CurrentSchema()).Stringify(sb); + + *documentStack_.template Push() = '\0'; + documentStack_.template Pop(1); + internal::PrintValidatorPointers(depth_, sb.GetString(), documentStack_.template Bottom()); +#endif + + uint64_t h = CurrentContext().arrayUniqueness ? static_cast(CurrentContext().hasher)->GetHashCode() : 0; + + PopSchema(); + + if (!schemaStack_.Empty()) { + Context& context = CurrentContext(); + if (context.valueUniqueness) { + HashCodeArray* a = static_cast(context.arrayElementHashCodes); + if (!a) + CurrentContext().arrayElementHashCodes = a = new (GetStateAllocator().Malloc(sizeof(HashCodeArray))) HashCodeArray(kArrayType); + for (typename HashCodeArray::ConstValueIterator itr = a->Begin(); itr != a->End(); ++itr) + if (itr->GetUint64() == h) { + DuplicateItems(static_cast(itr - a->Begin()), a->Size()); + RAPIDJSON_INVALID_KEYWORD_RETURN(SchemaType::GetUniqueItemsString()); + } + a->PushBack(h, GetStateAllocator()); + } + } + + // Remove the last token of document pointer + while (!documentStack_.Empty() && *documentStack_.template Pop(1) != '/') + ; + + return true; + } + + void AppendToken(const Ch* str, SizeType len) { + documentStack_.template Reserve(1 + len * 2); // worst case all characters are escaped as two characters + *documentStack_.template PushUnsafe() = '/'; + for (SizeType i = 0; i < len; i++) { + if (str[i] == '~') { + *documentStack_.template PushUnsafe() = '~'; + *documentStack_.template PushUnsafe() = '0'; + } + else if (str[i] == '/') { + *documentStack_.template PushUnsafe() = '~'; + *documentStack_.template PushUnsafe() = '1'; + } + else + *documentStack_.template PushUnsafe() = str[i]; + } + } + + RAPIDJSON_FORCEINLINE void PushSchema(const SchemaType& schema) { new (schemaStack_.template Push()) Context(*this, *this, &schema); } + + RAPIDJSON_FORCEINLINE void PopSchema() { + Context* c = schemaStack_.template Pop(1); + if (HashCodeArray* a = static_cast(c->arrayElementHashCodes)) { + a->~HashCodeArray(); + StateAllocator::Free(a); + } + c->~Context(); + } + + void AddErrorLocation(ValueType& result, bool parent) { + GenericStringBuffer sb; + PointerType instancePointer = GetInvalidDocumentPointer(); + ((parent && instancePointer.GetTokenCount() > 0) + ? PointerType(instancePointer.GetTokens(), instancePointer.GetTokenCount() - 1) + : instancePointer).StringifyUriFragment(sb); + ValueType instanceRef(sb.GetString(), static_cast(sb.GetSize() / sizeof(Ch)), + GetStateAllocator()); + result.AddMember(GetInstanceRefString(), instanceRef, GetStateAllocator()); + sb.Clear(); + memcpy(sb.Push(CurrentSchema().GetURI().GetStringLength()), + CurrentSchema().GetURI().GetString(), + CurrentSchema().GetURI().GetStringLength() * sizeof(Ch)); + GetInvalidSchemaPointer().StringifyUriFragment(sb); + ValueType schemaRef(sb.GetString(), static_cast(sb.GetSize() / sizeof(Ch)), + GetStateAllocator()); + result.AddMember(GetSchemaRefString(), schemaRef, GetStateAllocator()); + } + + void AddError(ValueType& keyword, ValueType& error) { + typename ValueType::MemberIterator member = error_.FindMember(keyword); + if (member == error_.MemberEnd()) + error_.AddMember(keyword, error, GetStateAllocator()); + else { + if (member->value.IsObject()) { + ValueType errors(kArrayType); + errors.PushBack(member->value, GetStateAllocator()); + member->value = errors; + } + member->value.PushBack(error, GetStateAllocator()); + } + } + + void AddCurrentError(const typename SchemaType::ValueType& keyword, bool parent = false) { + AddErrorLocation(currentError_, parent); + AddError(ValueType(keyword, GetStateAllocator(), false).Move(), currentError_); + } + + void MergeError(ValueType& other) { + for (typename ValueType::MemberIterator it = other.MemberBegin(), end = other.MemberEnd(); it != end; ++it) { + AddError(it->name, it->value); + } + } + + void AddNumberError(const typename SchemaType::ValueType& keyword, ValueType& actual, const SValue& expected, + const typename SchemaType::ValueType& (*exclusive)() = 0) { + currentError_.SetObject(); + currentError_.AddMember(GetActualString(), actual, GetStateAllocator()); + currentError_.AddMember(GetExpectedString(), ValueType(expected, GetStateAllocator()).Move(), GetStateAllocator()); + if (exclusive) + currentError_.AddMember(ValueType(exclusive(), GetStateAllocator()).Move(), true, GetStateAllocator()); + AddCurrentError(keyword); + } + + void AddErrorArray(const typename SchemaType::ValueType& keyword, + ISchemaValidator** subvalidators, SizeType count) { + ValueType errors(kArrayType); + for (SizeType i = 0; i < count; ++i) + errors.PushBack(static_cast(subvalidators[i])->GetError(), GetStateAllocator()); + currentError_.SetObject(); + currentError_.AddMember(GetErrorsString(), errors, GetStateAllocator()); + AddCurrentError(keyword); + } + + const SchemaType& CurrentSchema() const { return *schemaStack_.template Top()->schema; } + Context& CurrentContext() { return *schemaStack_.template Top(); } + const Context& CurrentContext() const { return *schemaStack_.template Top(); } + + static const size_t kDefaultSchemaStackCapacity = 1024; + static const size_t kDefaultDocumentStackCapacity = 256; + const SchemaDocumentType* schemaDocument_; + const SchemaType& root_; + StateAllocator* stateAllocator_; + StateAllocator* ownStateAllocator_; + internal::Stack schemaStack_; //!< stack to store the current path of schema (BaseSchemaType *) + internal::Stack documentStack_; //!< stack to store the current path of validating document (Ch) + OutputHandler* outputHandler_; + ValueType error_; + ValueType currentError_; + ValueType missingDependents_; + bool valid_; +#if RAPIDJSON_SCHEMA_VERBOSE + unsigned depth_; +#endif +}; + +typedef GenericSchemaValidator SchemaValidator; + +/////////////////////////////////////////////////////////////////////////////// +// SchemaValidatingReader + +//! A helper class for parsing with validation. +/*! + This helper class is a functor, designed as a parameter of \ref GenericDocument::Populate(). + + \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept. + \tparam SourceEncoding Encoding of the input stream. + \tparam SchemaDocumentType Type of schema document. + \tparam StackAllocator Allocator type for stack. +*/ +template < + unsigned parseFlags, + typename InputStream, + typename SourceEncoding, + typename SchemaDocumentType = SchemaDocument, + typename StackAllocator = CrtAllocator> +class SchemaValidatingReader { +public: + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename InputStream::Ch Ch; + typedef GenericValue ValueType; + + //! Constructor + /*! + \param is Input stream. + \param sd Schema document. + */ + SchemaValidatingReader(InputStream& is, const SchemaDocumentType& sd) : is_(is), sd_(sd), invalidSchemaKeyword_(), error_(kObjectType), isValid_(true) {} + + template + bool operator()(Handler& handler) { + GenericReader reader; + GenericSchemaValidator validator(sd_, handler); + parseResult_ = reader.template Parse(is_, validator); + + isValid_ = validator.IsValid(); + if (isValid_) { + invalidSchemaPointer_ = PointerType(); + invalidSchemaKeyword_ = 0; + invalidDocumentPointer_ = PointerType(); + error_.SetObject(); + } + else { + invalidSchemaPointer_ = validator.GetInvalidSchemaPointer(); + invalidSchemaKeyword_ = validator.GetInvalidSchemaKeyword(); + invalidDocumentPointer_ = validator.GetInvalidDocumentPointer(); + error_.CopyFrom(validator.GetError(), allocator_); + } + + return parseResult_; + } + + const ParseResult& GetParseResult() const { return parseResult_; } + bool IsValid() const { return isValid_; } + const PointerType& GetInvalidSchemaPointer() const { return invalidSchemaPointer_; } + const Ch* GetInvalidSchemaKeyword() const { return invalidSchemaKeyword_; } + const PointerType& GetInvalidDocumentPointer() const { return invalidDocumentPointer_; } + const ValueType& GetError() const { return error_; } + +private: + InputStream& is_; + const SchemaDocumentType& sd_; + + ParseResult parseResult_; + PointerType invalidSchemaPointer_; + const Ch* invalidSchemaKeyword_; + PointerType invalidDocumentPointer_; + StackAllocator allocator_; + ValueType error_; + bool isValid_; +}; + +RAPIDJSON_NAMESPACE_END +RAPIDJSON_DIAG_POP + +#endif // RAPIDJSON_SCHEMA_H_ diff --git a/util/xpack/rapidjson/stream.h b/util/xpack/rapidjson/stream.h new file mode 100755 index 0000000..99587fd --- /dev/null +++ b/util/xpack/rapidjson/stream.h @@ -0,0 +1,223 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "rapidjson.h" + +#ifndef RAPIDJSON_STREAM_H_ +#define RAPIDJSON_STREAM_H_ + +#include "encodings.h" + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Stream + +/*! \class rapidjson::Stream + \brief Concept for reading and writing characters. + + For read-only stream, no need to implement PutBegin(), Put(), Flush() and PutEnd(). + + For write-only stream, only need to implement Put() and Flush(). + +\code +concept Stream { + typename Ch; //!< Character type of the stream. + + //! Read the current character from stream without moving the read cursor. + Ch Peek() const; + + //! Read the current character from stream and moving the read cursor to next character. + Ch Take(); + + //! Get the current read cursor. + //! \return Number of characters read from start. + size_t Tell(); + + //! Begin writing operation at the current read pointer. + //! \return The begin writer pointer. + Ch* PutBegin(); + + //! Write a character. + void Put(Ch c); + + //! Flush the buffer. + void Flush(); + + //! End the writing operation. + //! \param begin The begin write pointer returned by PutBegin(). + //! \return Number of characters written. + size_t PutEnd(Ch* begin); +} +\endcode +*/ + +//! Provides additional information for stream. +/*! + By using traits pattern, this type provides a default configuration for stream. + For custom stream, this type can be specialized for other configuration. + See TEST(Reader, CustomStringStream) in readertest.cpp for example. +*/ +template +struct StreamTraits { + //! Whether to make local copy of stream for optimization during parsing. + /*! + By default, for safety, streams do not use local copy optimization. + Stream that can be copied fast should specialize this, like StreamTraits. + */ + enum { copyOptimization = 0 }; +}; + +//! Reserve n characters for writing to a stream. +template +inline void PutReserve(Stream& stream, size_t count) { + (void)stream; + (void)count; +} + +//! Write character to a stream, presuming buffer is reserved. +template +inline void PutUnsafe(Stream& stream, typename Stream::Ch c) { + stream.Put(c); +} + +//! Put N copies of a character to a stream. +template +inline void PutN(Stream& stream, Ch c, size_t n) { + PutReserve(stream, n); + for (size_t i = 0; i < n; i++) + PutUnsafe(stream, c); +} + +/////////////////////////////////////////////////////////////////////////////// +// GenericStreamWrapper + +//! A Stream Wrapper +/*! \tThis string stream is a wrapper for any stream by just forwarding any + \treceived message to the origin stream. + \note implements Stream concept +*/ + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4702) // unreachable code +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +template > +class GenericStreamWrapper { +public: + typedef typename Encoding::Ch Ch; + GenericStreamWrapper(InputStream& is): is_(is) {} + + Ch Peek() const { return is_.Peek(); } + Ch Take() { return is_.Take(); } + size_t Tell() { return is_.Tell(); } + Ch* PutBegin() { return is_.PutBegin(); } + void Put(Ch ch) { is_.Put(ch); } + void Flush() { is_.Flush(); } + size_t PutEnd(Ch* ch) { return is_.PutEnd(ch); } + + // wrapper for MemoryStream + const Ch* Peek4() const { return is_.Peek4(); } + + // wrapper for AutoUTFInputStream + UTFType GetType() const { return is_.GetType(); } + bool HasBOM() const { return is_.HasBOM(); } + +protected: + InputStream& is_; +}; + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_POP +#endif + +/////////////////////////////////////////////////////////////////////////////// +// StringStream + +//! Read-only string stream. +/*! \note implements Stream concept +*/ +template +struct GenericStringStream { + typedef typename Encoding::Ch Ch; + + GenericStringStream(const Ch *src) : src_(src), head_(src) {} + + Ch Peek() const { return *src_; } + Ch Take() { return *src_++; } + size_t Tell() const { return static_cast(src_ - head_); } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. +}; + +template +struct StreamTraits > { + enum { copyOptimization = 1 }; +}; + +//! String stream with UTF8 encoding. +typedef GenericStringStream > StringStream; + +/////////////////////////////////////////////////////////////////////////////// +// InsituStringStream + +//! A read-write string stream. +/*! This string stream is particularly designed for in-situ parsing. + \note implements Stream concept +*/ +template +struct GenericInsituStringStream { + typedef typename Encoding::Ch Ch; + + GenericInsituStringStream(Ch *src) : src_(src), dst_(0), head_(src) {} + + // Read + Ch Peek() { return *src_; } + Ch Take() { return *src_++; } + size_t Tell() { return static_cast(src_ - head_); } + + // Write + void Put(Ch c) { RAPIDJSON_ASSERT(dst_ != 0); *dst_++ = c; } + + Ch* PutBegin() { return dst_ = src_; } + size_t PutEnd(Ch* begin) { return static_cast(dst_ - begin); } + void Flush() {} + + Ch* Push(size_t count) { Ch* begin = dst_; dst_ += count; return begin; } + void Pop(size_t count) { dst_ -= count; } + + Ch* src_; + Ch* dst_; + Ch* head_; +}; + +template +struct StreamTraits > { + enum { copyOptimization = 1 }; +}; + +//! Insitu string stream with UTF8 encoding. +typedef GenericInsituStringStream > InsituStringStream; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_STREAM_H_ diff --git a/util/xpack/rapidjson/stringbuffer.h b/util/xpack/rapidjson/stringbuffer.h new file mode 100755 index 0000000..b8ed820 --- /dev/null +++ b/util/xpack/rapidjson/stringbuffer.h @@ -0,0 +1,121 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_STRINGBUFFER_H_ +#define RAPIDJSON_STRINGBUFFER_H_ + +#include "stream.h" +#include "internal/stack.h" + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS +#include // std::move +#endif + +#include "internal/stack.h" + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory output stream. +/*! + \tparam Encoding Encoding of the stream. + \tparam Allocator type for allocating memory buffer. + \note implements Stream concept +*/ +template +class GenericStringBuffer { +public: + typedef typename Encoding::Ch Ch; + + GenericStringBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericStringBuffer(GenericStringBuffer&& rhs) : stack_(std::move(rhs.stack_)) {} + GenericStringBuffer& operator=(GenericStringBuffer&& rhs) { + if (&rhs != this) + stack_ = std::move(rhs.stack_); + return *this; + } +#endif + + void Put(Ch c) { *stack_.template Push() = c; } + void PutUnsafe(Ch c) { *stack_.template PushUnsafe() = c; } + void Flush() {} + + void Clear() { stack_.Clear(); } + void ShrinkToFit() { + // Push and pop a null terminator. This is safe. + *stack_.template Push() = '\0'; + stack_.ShrinkToFit(); + stack_.template Pop(1); + } + + void Reserve(size_t count) { stack_.template Reserve(count); } + Ch* Push(size_t count) { return stack_.template Push(count); } + Ch* PushUnsafe(size_t count) { return stack_.template PushUnsafe(count); } + void Pop(size_t count) { stack_.template Pop(count); } + + const Ch* GetString() const { + // Push and pop a null terminator. This is safe. + *stack_.template Push() = '\0'; + stack_.template Pop(1); + + return stack_.template Bottom(); + } + + //! Get the size of string in bytes in the string buffer. + size_t GetSize() const { return stack_.GetSize(); } + + //! Get the length of string in Ch in the string buffer. + size_t GetLength() const { return stack_.GetSize() / sizeof(Ch); } + + static const size_t kDefaultCapacity = 256; + mutable internal::Stack stack_; + +private: + // Prohibit copy constructor & assignment operator. + GenericStringBuffer(const GenericStringBuffer&); + GenericStringBuffer& operator=(const GenericStringBuffer&); +}; + +//! String buffer with UTF8 encoding +typedef GenericStringBuffer > StringBuffer; + +template +inline void PutReserve(GenericStringBuffer& stream, size_t count) { + stream.Reserve(count); +} + +template +inline void PutUnsafe(GenericStringBuffer& stream, typename Encoding::Ch c) { + stream.PutUnsafe(c); +} + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(GenericStringBuffer >& stream, char c, size_t n) { + std::memset(stream.stack_.Push(n), c, n * sizeof(c)); +} + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_STRINGBUFFER_H_ diff --git a/util/xpack/rapidjson/writer.h b/util/xpack/rapidjson/writer.h new file mode 100755 index 0000000..fc33723 --- /dev/null +++ b/util/xpack/rapidjson/writer.h @@ -0,0 +1,709 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_WRITER_H_ +#define RAPIDJSON_WRITER_H_ + +#include "stream.h" +#include "internal/meta.h" +#include "internal/stack.h" +#include "internal/strfunc.h" +#include "internal/dtoa.h" +#include "internal/itoa.h" +#include "stringbuffer.h" +#include // placement new + +#if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) +#include +#pragma intrinsic(_BitScanForward) +#endif +#ifdef RAPIDJSON_SSE42 +#include +#elif defined(RAPIDJSON_SSE2) +#include +#elif defined(RAPIDJSON_NEON) +#include +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(unreachable-code) +RAPIDJSON_DIAG_OFF(c++98-compat) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// WriteFlag + +/*! \def RAPIDJSON_WRITE_DEFAULT_FLAGS + \ingroup RAPIDJSON_CONFIG + \brief User-defined kWriteDefaultFlags definition. + + User can define this as any \c WriteFlag combinations. +*/ +#ifndef RAPIDJSON_WRITE_DEFAULT_FLAGS +#define RAPIDJSON_WRITE_DEFAULT_FLAGS kWriteNoFlags +#endif + +//! Combination of writeFlags +enum WriteFlag { + kWriteNoFlags = 0, //!< No flags are set. + kWriteValidateEncodingFlag = 1, //!< Validate encoding of JSON strings. + kWriteNanAndInfFlag = 2, //!< Allow writing of Infinity, -Infinity and NaN. + kWriteDefaultFlags = RAPIDJSON_WRITE_DEFAULT_FLAGS //!< Default write flags. Can be customized by defining RAPIDJSON_WRITE_DEFAULT_FLAGS +}; + +//! JSON writer +/*! Writer implements the concept Handler. + It generates JSON text by events to an output os. + + User may programmatically calls the functions of a writer to generate JSON text. + + On the other side, a writer can also be passed to objects that generates events, + + for example Reader::Parse() and Document::Accept(). + + \tparam OutputStream Type of output stream. + \tparam SourceEncoding Encoding of source string. + \tparam TargetEncoding Encoding of output stream. + \tparam StackAllocator Type of allocator for allocating memory of stack. + \note implements Handler concept +*/ +template, typename TargetEncoding = UTF8<>, typename StackAllocator = CrtAllocator, unsigned writeFlags = kWriteDefaultFlags> +class Writer { +public: + typedef typename SourceEncoding::Ch Ch; + + static const int kDefaultMaxDecimalPlaces = 324; + + //! Constructor + /*! \param os Output stream. + \param stackAllocator User supplied allocator. If it is null, it will create a private one. + \param levelDepth Initial capacity of stack. + */ + explicit + Writer(OutputStream& os, StackAllocator* stackAllocator = 0, size_t levelDepth = kDefaultLevelDepth) : + os_(&os), level_stack_(stackAllocator, levelDepth * sizeof(Level)), maxDecimalPlaces_(kDefaultMaxDecimalPlaces), hasRoot_(false) {} + + explicit + Writer(StackAllocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) : + os_(0), level_stack_(allocator, levelDepth * sizeof(Level)), maxDecimalPlaces_(kDefaultMaxDecimalPlaces), hasRoot_(false) {} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Writer(Writer&& rhs) : + os_(rhs.os_), level_stack_(std::move(rhs.level_stack_)), maxDecimalPlaces_(rhs.maxDecimalPlaces_), hasRoot_(rhs.hasRoot_) { + rhs.os_ = 0; + } +#endif + + //! Reset the writer with a new stream. + /*! + This function reset the writer with a new stream and default settings, + in order to make a Writer object reusable for output multiple JSONs. + + \param os New output stream. + \code + Writer writer(os1); + writer.StartObject(); + // ... + writer.EndObject(); + + writer.Reset(os2); + writer.StartObject(); + // ... + writer.EndObject(); + \endcode + */ + void Reset(OutputStream& os) { + os_ = &os; + hasRoot_ = false; + level_stack_.Clear(); + } + + //! Checks whether the output is a complete JSON. + /*! + A complete JSON has a complete root object or array. + */ + bool IsComplete() const { + return hasRoot_ && level_stack_.Empty(); + } + + int GetMaxDecimalPlaces() const { + return maxDecimalPlaces_; + } + + //! Sets the maximum number of decimal places for double output. + /*! + This setting truncates the output with specified number of decimal places. + + For example, + + \code + writer.SetMaxDecimalPlaces(3); + writer.StartArray(); + writer.Double(0.12345); // "0.123" + writer.Double(0.0001); // "0.0" + writer.Double(1.234567890123456e30); // "1.234567890123456e30" (do not truncate significand for positive exponent) + writer.Double(1.23e-4); // "0.0" (do truncate significand for negative exponent) + writer.EndArray(); + \endcode + + The default setting does not truncate any decimal places. You can restore to this setting by calling + \code + writer.SetMaxDecimalPlaces(Writer::kDefaultMaxDecimalPlaces); + \endcode + */ + void SetMaxDecimalPlaces(int maxDecimalPlaces) { + maxDecimalPlaces_ = maxDecimalPlaces; + } + + /*!@name Implementation of Handler + \see Handler + */ + //@{ + + bool Null() { Prefix(kNullType); return EndValue(WriteNull()); } + bool Bool(bool b) { Prefix(b ? kTrueType : kFalseType); return EndValue(WriteBool(b)); } + bool Int(int i) { Prefix(kNumberType); return EndValue(WriteInt(i)); } + bool Uint(unsigned u) { Prefix(kNumberType); return EndValue(WriteUint(u)); } + bool Int64(int64_t i64) { Prefix(kNumberType); return EndValue(WriteInt64(i64)); } + bool Uint64(uint64_t u64) { Prefix(kNumberType); return EndValue(WriteUint64(u64)); } + + //! Writes the given \c double value to the stream + /*! + \param d The value to be written. + \return Whether it is succeed. + */ + bool Double(double d) { Prefix(kNumberType); return EndValue(WriteDouble(d)); } + + bool RawNumber(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + Prefix(kNumberType); + return EndValue(WriteString(str, length)); + } + + bool String(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + Prefix(kStringType); + return EndValue(WriteString(str, length)); + } + +#if RAPIDJSON_HAS_STDSTRING + bool String(const std::basic_string& str) { + return String(str.data(), SizeType(str.size())); + } +#endif + + bool StartObject() { + Prefix(kObjectType); + new (level_stack_.template Push()) Level(false); + return WriteStartObject(); + } + + bool Key(const Ch* str, SizeType length, bool copy = false) { return String(str, length, copy); } + +#if RAPIDJSON_HAS_STDSTRING + bool Key(const std::basic_string& str) + { + return Key(str.data(), SizeType(str.size())); + } +#endif + + bool EndObject(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); // not inside an Object + RAPIDJSON_ASSERT(!level_stack_.template Top()->inArray); // currently inside an Array, not Object + RAPIDJSON_ASSERT(0 == level_stack_.template Top()->valueCount % 2); // Object has a Key without a Value + level_stack_.template Pop(1); + return EndValue(WriteEndObject()); + } + + bool StartArray() { + Prefix(kArrayType); + new (level_stack_.template Push()) Level(true); + return WriteStartArray(); + } + + bool EndArray(SizeType elementCount = 0) { + (void)elementCount; + RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); + RAPIDJSON_ASSERT(level_stack_.template Top()->inArray); + level_stack_.template Pop(1); + return EndValue(WriteEndArray()); + } + //@} + + /*! @name Convenience extensions */ + //@{ + + //! Simpler but slower overload. + bool String(const Ch* const& str) { return String(str, internal::StrLen(str)); } + bool Key(const Ch* const& str) { return Key(str, internal::StrLen(str)); } + + //@} + + //! Write a raw JSON value. + /*! + For user to write a stringified JSON as a value. + + \param json A well-formed JSON value. It should not contain null character within [0, length - 1] range. + \param length Length of the json. + \param type Type of the root of json. + */ + bool RawValue(const Ch* json, size_t length, Type type) { + RAPIDJSON_ASSERT(json != 0); + Prefix(type); + return EndValue(WriteRawValue(json, length)); + } + + //! Flush the output stream. + /*! + Allows the user to flush the output stream immediately. + */ + void Flush() { + os_->Flush(); + } + +protected: + //! Information for each nested level + struct Level { + Level(bool inArray_) : valueCount(0), inArray(inArray_) {} + size_t valueCount; //!< number of values in this level + bool inArray; //!< true if in array, otherwise in object + }; + + static const size_t kDefaultLevelDepth = 32; + + bool WriteNull() { + PutReserve(*os_, 4); + PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'u'); PutUnsafe(*os_, 'l'); PutUnsafe(*os_, 'l'); return true; + } + + bool WriteBool(bool b) { + if (b) { + PutReserve(*os_, 4); + PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'r'); PutUnsafe(*os_, 'u'); PutUnsafe(*os_, 'e'); + } + else { + PutReserve(*os_, 5); + PutUnsafe(*os_, 'f'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'l'); PutUnsafe(*os_, 's'); PutUnsafe(*os_, 'e'); + } + return true; + } + + bool WriteInt(int i) { + char buffer[11]; + const char* end = internal::i32toa(i, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteUint(unsigned u) { + char buffer[10]; + const char* end = internal::u32toa(u, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteInt64(int64_t i64) { + char buffer[21]; + const char* end = internal::i64toa(i64, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteUint64(uint64_t u64) { + char buffer[20]; + char* end = internal::u64toa(u64, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteDouble(double d) { + if (internal::Double(d).IsNanOrInf()) { + if (!(writeFlags & kWriteNanAndInfFlag)) + return false; + if (internal::Double(d).IsNan()) { + PutReserve(*os_, 3); + PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N'); + return true; + } + if (internal::Double(d).Sign()) { + PutReserve(*os_, 9); + PutUnsafe(*os_, '-'); + } + else + PutReserve(*os_, 8); + PutUnsafe(*os_, 'I'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'f'); + PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'y'); + return true; + } + + char buffer[25]; + char* end = internal::dtoa(d, buffer, maxDecimalPlaces_); + PutReserve(*os_, static_cast(end - buffer)); + for (char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteString(const Ch* str, SizeType length) { + static const typename OutputStream::Ch hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + static const char escape[256] = { +#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't', 'n', 'u', 'f', 'r', 'u', 'u', // 00 + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', // 10 + 0, 0, '"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20 + Z16, Z16, // 30~4F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, // 50 + Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 // 60~FF +#undef Z16 + }; + + if (TargetEncoding::supportUnicode) + PutReserve(*os_, 2 + length * 6); // "\uxxxx..." + else + PutReserve(*os_, 2 + length * 12); // "\uxxxx\uyyyy..." + + PutUnsafe(*os_, '\"'); + GenericStringStream is(str); + while (ScanWriteUnescapedString(is, length)) { + const Ch c = is.Peek(); + if (!TargetEncoding::supportUnicode && static_cast(c) >= 0x80) { + // Unicode escaping + unsigned codepoint; + if (RAPIDJSON_UNLIKELY(!SourceEncoding::Decode(is, &codepoint))) + return false; + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, 'u'); + if (codepoint <= 0xD7FF || (codepoint >= 0xE000 && codepoint <= 0xFFFF)) { + PutUnsafe(*os_, hexDigits[(codepoint >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint ) & 15]); + } + else { + RAPIDJSON_ASSERT(codepoint >= 0x010000 && codepoint <= 0x10FFFF); + // Surrogate pair + unsigned s = codepoint - 0x010000; + unsigned lead = (s >> 10) + 0xD800; + unsigned trail = (s & 0x3FF) + 0xDC00; + PutUnsafe(*os_, hexDigits[(lead >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(lead >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(lead >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(lead ) & 15]); + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, 'u'); + PutUnsafe(*os_, hexDigits[(trail >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(trail >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(trail >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(trail ) & 15]); + } + } + else if ((sizeof(Ch) == 1 || static_cast(c) < 256) && RAPIDJSON_UNLIKELY(escape[static_cast(c)])) { + is.Take(); + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, static_cast(escape[static_cast(c)])); + if (escape[static_cast(c)] == 'u') { + PutUnsafe(*os_, '0'); + PutUnsafe(*os_, '0'); + PutUnsafe(*os_, hexDigits[static_cast(c) >> 4]); + PutUnsafe(*os_, hexDigits[static_cast(c) & 0xF]); + } + } + else if (RAPIDJSON_UNLIKELY(!(writeFlags & kWriteValidateEncodingFlag ? + Transcoder::Validate(is, *os_) : + Transcoder::TranscodeUnsafe(is, *os_)))) + return false; + } + PutUnsafe(*os_, '\"'); + return true; + } + + bool ScanWriteUnescapedString(GenericStringStream& is, size_t length) { + return RAPIDJSON_LIKELY(is.Tell() < length); + } + + bool WriteStartObject() { os_->Put('{'); return true; } + bool WriteEndObject() { os_->Put('}'); return true; } + bool WriteStartArray() { os_->Put('['); return true; } + bool WriteEndArray() { os_->Put(']'); return true; } + + bool WriteRawValue(const Ch* json, size_t length) { + PutReserve(*os_, length); + GenericStringStream is(json); + while (RAPIDJSON_LIKELY(is.Tell() < length)) { + RAPIDJSON_ASSERT(is.Peek() != '\0'); + if (RAPIDJSON_UNLIKELY(!(writeFlags & kWriteValidateEncodingFlag ? + Transcoder::Validate(is, *os_) : + Transcoder::TranscodeUnsafe(is, *os_)))) + return false; + } + return true; + } + + void Prefix(Type type) { + (void)type; + if (RAPIDJSON_LIKELY(level_stack_.GetSize() != 0)) { // this value is not at root + Level* level = level_stack_.template Top(); + if (level->valueCount > 0) { + if (level->inArray) + os_->Put(','); // add comma if it is not the first element in array + else // in object + os_->Put((level->valueCount % 2 == 0) ? ',' : ':'); + } + if (!level->inArray && level->valueCount % 2 == 0) + RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name + level->valueCount++; + } + else { + RAPIDJSON_ASSERT(!hasRoot_); // Should only has one and only one root. + hasRoot_ = true; + } + } + + // Flush the value if it is the top level one. + bool EndValue(bool ret) { + if (RAPIDJSON_UNLIKELY(level_stack_.Empty())) // end of json text + Flush(); + return ret; + } + + OutputStream* os_; + internal::Stack level_stack_; + int maxDecimalPlaces_; + bool hasRoot_; + +private: + // Prohibit copy constructor & assignment operator. + Writer(const Writer&); + Writer& operator=(const Writer&); +}; + +// Full specialization for StringStream to prevent memory copying + +template<> +inline bool Writer::WriteInt(int i) { + char *buffer = os_->Push(11); + const char* end = internal::i32toa(i, buffer); + os_->Pop(static_cast(11 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteUint(unsigned u) { + char *buffer = os_->Push(10); + const char* end = internal::u32toa(u, buffer); + os_->Pop(static_cast(10 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteInt64(int64_t i64) { + char *buffer = os_->Push(21); + const char* end = internal::i64toa(i64, buffer); + os_->Pop(static_cast(21 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteUint64(uint64_t u) { + char *buffer = os_->Push(20); + const char* end = internal::u64toa(u, buffer); + os_->Pop(static_cast(20 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteDouble(double d) { + if (internal::Double(d).IsNanOrInf()) { + // Note: This code path can only be reached if (RAPIDJSON_WRITE_DEFAULT_FLAGS & kWriteNanAndInfFlag). + if (!(kWriteDefaultFlags & kWriteNanAndInfFlag)) + return false; + if (internal::Double(d).IsNan()) { + PutReserve(*os_, 3); + PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N'); + return true; + } + if (internal::Double(d).Sign()) { + PutReserve(*os_, 9); + PutUnsafe(*os_, '-'); + } + else + PutReserve(*os_, 8); + PutUnsafe(*os_, 'I'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'f'); + PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'y'); + return true; + } + + char *buffer = os_->Push(25); + char* end = internal::dtoa(d, buffer, maxDecimalPlaces_); + os_->Pop(static_cast(25 - (end - buffer))); + return true; +} + +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) +template<> +inline bool Writer::ScanWriteUnescapedString(StringStream& is, size_t length) { + if (length < 16) + return RAPIDJSON_LIKELY(is.Tell() < length); + + if (!RAPIDJSON_LIKELY(is.Tell() < length)) + return false; + + const char* p = is.src_; + const char* end = is.head_ + length; + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + const char* endAligned = reinterpret_cast(reinterpret_cast(end) & static_cast(~15)); + if (nextAligned > end) + return true; + + while (p != nextAligned) + if (*p < 0x20 || *p == '\"' || *p == '\\') { + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); + } + else + os_->PutUnsafe(*p++); + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (; p != endAligned; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + SizeType len; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + len = offset; +#else + len = static_cast(__builtin_ffs(r) - 1); +#endif + char* q = reinterpret_cast(os_->PushUnsafe(len)); + for (size_t i = 0; i < len; i++) + q[i] = p[i]; + + p += len; + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(os_->PushUnsafe(16)), s); + } + + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); +} +#elif defined(RAPIDJSON_NEON) +template<> +inline bool Writer::ScanWriteUnescapedString(StringStream& is, size_t length) { + if (length < 16) + return RAPIDJSON_LIKELY(is.Tell() < length); + + if (!RAPIDJSON_LIKELY(is.Tell() < length)) + return false; + + const char* p = is.src_; + const char* end = is.head_ + length; + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + const char* endAligned = reinterpret_cast(reinterpret_cast(end) & static_cast(~15)); + if (nextAligned > end) + return true; + + while (p != nextAligned) + if (*p < 0x20 || *p == '\"' || *p == '\\') { + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); + } + else + os_->PutUnsafe(*p++); + + // The rest of string using SIMD + const uint8x16_t s0 = vmovq_n_u8('"'); + const uint8x16_t s1 = vmovq_n_u8('\\'); + const uint8x16_t s2 = vmovq_n_u8('\b'); + const uint8x16_t s3 = vmovq_n_u8(32); + + for (; p != endAligned; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, s0); + x = vorrq_u8(x, vceqq_u8(s, s1)); + x = vorrq_u8(x, vceqq_u8(s, s2)); + x = vorrq_u8(x, vcltq_u8(s, s3)); + + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(reinterpret_cast(x), 0); // extract + uint64_t high = vgetq_lane_u64(reinterpret_cast(x), 1); // extract + + SizeType len = 0; + bool escaped = false; + if (low == 0) { + if (high != 0) { + unsigned lz = (unsigned)__builtin_clzll(high); + len = 8 + (lz >> 3); + escaped = true; + } + } else { + unsigned lz = (unsigned)__builtin_clzll(low); + len = lz >> 3; + escaped = true; + } + if (RAPIDJSON_UNLIKELY(escaped)) { // some of characters is escaped + char* q = reinterpret_cast(os_->PushUnsafe(len)); + for (size_t i = 0; i < len; i++) + q[i] = p[i]; + + p += len; + break; + } + vst1q_u8(reinterpret_cast(os_->PushUnsafe(16)), s); + } + + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); +} +#endif // RAPIDJSON_NEON + +RAPIDJSON_NAMESPACE_END + +#if defined(_MSC_VER) || defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/util/xpack/rapidjson_custom.h b/util/xpack/rapidjson_custom.h new file mode 100755 index 0000000..fa73567 --- /dev/null +++ b/util/xpack/rapidjson_custom.h @@ -0,0 +1,19 @@ +#ifndef __X_PACK_RAPIDJSON_CUSTOME_H +#define __X_PACK_RAPIDJSON_CUSTOME_H + +#ifndef RAPIDJSON_NOEXCEPT_ASSERT + #include + #define RAPIDJSON_NOEXCEPT_ASSERT(x) assert(x) +#endif + +#ifndef RAPIDJSON_ASSERT + #include + #define RAPIDJSON_ASSERT(x) if(!(x)) throw std::runtime_error(#x) +#endif + +#ifndef RAPIDJSON_WRITE_DEFAULT_FLAGS +#define RAPIDJSON_WRITE_DEFAULT_FLAGS kWriteNanAndInfFlag +#endif + +#endif + diff --git a/util/xpack/rapidxml/license.txt b/util/xpack/rapidxml/license.txt new file mode 100755 index 0000000..0095bc7 --- /dev/null +++ b/util/xpack/rapidxml/license.txt @@ -0,0 +1,52 @@ +Use of this software is granted under one of the following two licenses, +to be chosen freely by the user. + +1. Boost Software License - Version 1.0 - August 17th, 2003 +=============================================================================== + +Copyright (c) 2006, 2007 Marcin Kalicinski + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +2. The MIT License +=============================================================================== + +Copyright (c) 2006, 2007 Marcin Kalicinski + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/util/xpack/rapidxml/manual.html b/util/xpack/rapidxml/manual.html new file mode 100755 index 0000000..4e6a5f1 --- /dev/null +++ b/util/xpack/rapidxml/manual.html @@ -0,0 +1,406 @@ +

RAPIDXML Manual

Version 1.13

Copyright (C) 2006, 2009 Marcin Kalicinski
See accompanying file license.txt for license information.

Table of Contents

1. What is RapidXml?
1.1 Dependencies And Compatibility
1.2 Character Types And Encodings
1.3 Error Handling
1.4 Memory Allocation
1.5 W3C Compliance
1.6 API Design
1.7 Reliability
1.8 Acknowledgements
2. Two Minute Tutorial
2.1 Parsing
2.2 Accessing The DOM Tree
2.3 Modifying The DOM Tree
2.4 Printing XML
3. Differences From Regular XML Parsers
3.1 Lifetime Of Source Text
3.2 Ownership Of Strings
3.3 Destructive Vs Non-Destructive Mode
4. Performance
4.1 Comparison With Other Parsers
5. Reference

1. What is RapidXml?

RapidXml is an attempt to create the fastest XML DOM parser possible, while retaining useability, portability and reasonable W3C compatibility. It is an in-situ parser written in C++, with parsing speed approaching that of strlen() function executed on the same data.

+ Entire parser is contained in a single header file, so no building or linking is neccesary. To use it you just need to copy rapidxml.hpp file to a convenient place (such as your project directory), and include it where needed. You may also want to use printing functions contained in header rapidxml_print.hpp.

1.1 Dependencies And Compatibility

RapidXml has no dependencies other than a very small subset of standard C++ library (<cassert>, <cstdlib>, <new> and <exception>, unless exceptions are disabled). It should compile on any reasonably conformant compiler, and was tested on Visual C++ 2003, Visual C++ 2005, Visual C++ 2008, gcc 3, gcc 4, and Comeau 4.3.3. Care was taken that no warnings are produced on these compilers, even with highest warning levels enabled.

1.2 Character Types And Encodings

RapidXml is character type agnostic, and can work both with narrow and wide characters. Current version does not fully support UTF-16 or UTF-32, so use of wide characters is somewhat incapacitated. However, it should succesfully parse wchar_t strings containing UTF-16 or UTF-32 if endianness of the data matches that of the machine. UTF-8 is fully supported, including all numeric character references, which are expanded into appropriate UTF-8 byte sequences (unless you enable parse_no_utf8 flag).

+ Note that RapidXml performs no decoding - strings returned by name() and value() functions will contain text encoded using the same encoding as source file. Rapidxml understands and expands the following character references: &apos; &amp; &quot; &lt; &gt; &#...; Other character references are not expanded.

1.3 Error Handling

By default, RapidXml uses C++ exceptions to report errors. If this behaviour is undesirable, RAPIDXML_NO_EXCEPTIONS can be defined to suppress exception code. See parse_error class and parse_error_handler() function for more information.

1.4 Memory Allocation

RapidXml uses a special memory pool object to allocate nodes and attributes, because direct allocation using new operator would be far too slow. Underlying memory allocations performed by the pool can be customized by use of memory_pool::set_allocator() function. See class memory_pool for more information.

1.5 W3C Compliance

RapidXml is not a W3C compliant parser, primarily because it ignores DOCTYPE declarations. There is a number of other, minor incompatibilities as well. Still, it can successfully parse and produce complete trees of all valid XML files in W3C conformance suite (over 1000 files specially designed to find flaws in XML processors). In destructive mode it performs whitespace normalization and character entity substitution for a small set of built-in entities.

1.6 API Design

RapidXml API is minimalistic, to reduce code size as much as possible, and facilitate use in embedded environments. Additional convenience functions are provided in separate headers: rapidxml_utils.hpp and rapidxml_print.hpp. Contents of these headers is not an essential part of the library, and is currently not documented (otherwise than with comments in code).

1.7 Reliability

RapidXml is very robust and comes with a large harness of unit tests. Special care has been taken to ensure stability of the parser no matter what source text is thrown at it. One of the unit tests produces 100,000 randomly corrupted variants of XML document, which (when uncorrupted) contains all constructs recognized by RapidXml. RapidXml passes this test when it correctly recognizes that errors have been introduced, and does not crash or loop indefinitely.

+ Another unit test puts RapidXml head-to-head with another, well estabilished XML parser, and verifies that their outputs match across a wide variety of small and large documents.

+ Yet another test feeds RapidXml with over 1000 test files from W3C compliance suite, and verifies that correct results are obtained. There are also additional tests that verify each API function separately, and test that various parsing modes work as expected.

1.8 Acknowledgements

I would like to thank Arseny Kapoulkine for his work on pugixml, which was an inspiration for this project. Additional thanks go to Kristen Wegner for creating pugxml, from which pugixml was derived. Janusz Wohlfeil kindly ran RapidXml speed tests on hardware that I did not have access to, allowing me to expand performance comparison table.

2. Two Minute Tutorial

2.1 Parsing

The following code causes RapidXml to parse a zero-terminated string named text:
using namespace rapidxml;
+xml_document<> doc;    // character type defaults to char
+doc.parse<0>(text);    // 0 means default parse flags
+
doc object is now a root of DOM tree containing representation of the parsed XML. Because all RapidXml interface is contained inside namespace rapidxml, users must either bring contents of this namespace into scope, or fully qualify all the names. Class xml_document represents a root of the DOM hierarchy. By means of public inheritance, it is also an xml_node and a memory_pool. Template parameter of xml_document::parse() function is used to specify parsing flags, with which you can fine-tune behaviour of the parser. Note that flags must be a compile-time constant.

2.2 Accessing The DOM Tree

To access the DOM tree, use methods of xml_node and xml_attribute classes:
cout << "Name of my first node is: " << doc.first_node()->name() << "\n";
+xml_node<> *node = doc.first_node("foobar");
+cout << "Node foobar has value " << node->value() << "\n";
+for (xml_attribute<> *attr = node->first_attribute();
+     attr; attr = attr->next_attribute())
+{
+    cout << "Node foobar has attribute " << attr->name() << " ";
+    cout << "with value " << attr->value() << "\n";
+}
+

2.3 Modifying The DOM Tree

DOM tree produced by the parser is fully modifiable. Nodes and attributes can be added/removed, and their contents changed. The below example creates a HTML document, whose sole contents is a link to google.com website:
xml_document<> doc;
+xml_node<> *node = doc.allocate_node(node_element, "a", "Google");
+doc.append_node(node);
+xml_attribute<> *attr = doc.allocate_attribute("href", "google.com");
+node->append_attribute(attr);
+
One quirk is that nodes and attributes do not own the text of their names and values. This is because normally they only store pointers to the source text. So, when assigning a new name or value to the node, care must be taken to ensure proper lifetime of the string. The easiest way to achieve it is to allocate the string from the xml_document memory pool. In the above example this is not necessary, because we are only assigning character constants. But the code below uses memory_pool::allocate_string() function to allocate node name (which will have the same lifetime as the document), and assigns it to a new node:
xml_document<> doc;
+char *node_name = doc.allocate_string(name);        // Allocate string and copy name into it
+xml_node<> *node = doc.allocate_node(node_element, node_name);  // Set node name to node_name
+
Check Reference section for description of the entire interface.

2.4 Printing XML

You can print xml_document and xml_node objects into an XML string. Use print() function or operator <<, which are defined in rapidxml_print.hpp header.
using namespace rapidxml;
+xml_document<> doc;    // character type defaults to char
+// ... some code to fill the document
+
+// Print to stream using operator <<
+std::cout << doc;   
+
+// Print to stream using print function, specifying printing flags
+print(std::cout, doc, 0);   // 0 means default printing flags
+
+// Print to string using output iterator
+std::string s;
+print(std::back_inserter(s), doc, 0);
+
+// Print to memory buffer using output iterator
+char buffer[4096];                      // You are responsible for making the buffer large enough!
+char *end = print(buffer, doc, 0);      // end contains pointer to character after last printed character
+*end = 0;                               // Add string terminator after XML
+

3. Differences From Regular XML Parsers

RapidXml is an in-situ parser, which allows it to achieve very high parsing speed. In-situ means that parser does not make copies of strings. Instead, it places pointers to the source text in the DOM hierarchy.

3.1 Lifetime Of Source Text

In-situ parsing requires that source text lives at least as long as the document object. If source text is destroyed, names and values of nodes in DOM tree will become destroyed as well. Additionally, whitespace processing, character entity translation, and zero-termination of strings require that source text be modified during parsing (but see non-destructive mode). This makes the text useless for further processing once it was parsed by RapidXml.

+ In many cases however, these are not serious issues.

3.2 Ownership Of Strings

Nodes and attributes produced by RapidXml do not own their name and value strings. They merely hold the pointers to them. This means you have to be careful when setting these values manually, by using xml_base::name(const Ch *) or xml_base::value(const Ch *) functions. Care must be taken to ensure that lifetime of the string passed is at least as long as lifetime of the node/attribute. The easiest way to achieve it is to allocate the string from memory_pool owned by the document. Use memory_pool::allocate_string() function for this purpose.

3.3 Destructive Vs Non-Destructive Mode

By default, the parser modifies source text during the parsing process. This is required to achieve character entity translation, whitespace normalization, and zero-termination of strings.

+ In some cases this behaviour may be undesirable, for example if source text resides in read only memory, or is mapped to memory directly from file. By using appropriate parser flags (parse_non_destructive), source text modifications can be disabled. However, because RapidXml does in-situ parsing, it obviously has the following side-effects:

4. Performance

RapidXml achieves its speed through use of several techniques:
  • In-situ parsing. When building DOM tree, RapidXml does not make copies of string data, such as node names and values. Instead, it stores pointers to interior of the source text.
  • Use of template metaprogramming techniques. This allows it to move much of the work to compile time. Through magic of the templates, C++ compiler generates a separate copy of parsing code for any combination of parser flags you use. In each copy, all possible decisions are made at compile time and all unused code is omitted.
  • Extensive use of lookup tables for parsing.
  • Hand-tuned C++ with profiling done on several most popular CPUs.
This results in a very small and fast code: a parser which is custom tailored to exact needs with each invocation.

4.1 Comparison With Other Parsers

The table below compares speed of RapidXml to some other parsers, and to strlen() function executed on the same data. On a modern CPU (as of 2007), you can expect parsing throughput to be close to 1 GB/s. As a rule of thumb, parsing speed is about 50-100x faster than Xerces DOM, 30-60x faster than TinyXml, 3-12x faster than pugxml, and about 5% - 30% faster than pugixml, the fastest XML parser I know of.
  • The test file is a real-world, 50kB large, moderately dense XML file.
  • All timing is done by using RDTSC instruction present in Pentium-compatible CPUs.
  • No profile-guided optimizations are used.
  • All parsers are running in their fastest modes.
  • The results are given in CPU cycles per character, so frequency of CPUs is irrelevant.
  • The results are minimum values from a large number of runs, to minimize effects of operating system activity, task switching, interrupt handling etc.
  • A single parse of the test file takes about 1/10th of a millisecond, so with large number of runs there is a good chance of hitting at least one no-interrupt streak, and obtaining undisturbed results.
Platform
Compiler
strlen() RapidXml pugixml 0.3 pugxml TinyXml
Pentium 4
MSVC 8.0
2.5
5.4
7.0
61.7
298.8
Pentium 4
gcc 4.1.1
0.8
6.1
9.5
67.0
413.2
Core 2
MSVC 8.0
1.0
4.5
5.0
24.6
154.8
Core 2
gcc 4.1.1
0.6
4.6
5.4
28.3
229.3
Athlon XP
MSVC 8.0
3.1
7.7
8.0
25.5
182.6
Athlon XP
gcc 4.1.1
0.9
8.2
9.2
33.7
265.2
Pentium 3
MSVC 8.0
2.0
6.3
7.0
30.9
211.9
Pentium 3
gcc 4.1.1
1.0
6.7
8.9
35.3
316.0
(*) All results are in CPU cycles per character of source text

5. Reference

This section lists all classes, functions, constants etc. and describes them in detail.
class + template + rapidxml::memory_pool
+ constructor + memory_pool()
+ destructor + ~memory_pool()
function allocate_node(node_type type, const Ch *name=0, const Ch *value=0, std::size_t name_size=0, std::size_t value_size=0)
function allocate_attribute(const Ch *name=0, const Ch *value=0, std::size_t name_size=0, std::size_t value_size=0)
function allocate_string(const Ch *source=0, std::size_t size=0)
function clone_node(const xml_node< Ch > *source, xml_node< Ch > *result=0)
function clear()
function set_allocator(alloc_func *af, free_func *ff)

class rapidxml::parse_error
+ constructor + parse_error(const char *what, void *where)
function what() const
function where() const

class + template + rapidxml::xml_attribute
+ constructor + xml_attribute()
function document() const
function previous_attribute(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const
function next_attribute(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const

class + template + rapidxml::xml_base
+ constructor + xml_base()
function name() const
function name_size() const
function value() const
function value_size() const
function name(const Ch *name, std::size_t size)
function name(const Ch *name)
function value(const Ch *value, std::size_t size)
function value(const Ch *value)
function parent() const

class + template + rapidxml::xml_document
+ constructor + xml_document()
function parse(Ch *text)
function clear()

class + template + rapidxml::xml_node
+ constructor + xml_node(node_type type)
function type() const
function document() const
function first_node(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const
function last_node(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const
function previous_sibling(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const
function next_sibling(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const
function first_attribute(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const
function last_attribute(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const
function type(node_type type)
function prepend_node(xml_node< Ch > *child)
function append_node(xml_node< Ch > *child)
function insert_node(xml_node< Ch > *where, xml_node< Ch > *child)
function remove_first_node()
function remove_last_node()
function remove_node(xml_node< Ch > *where)
function remove_all_nodes()
function prepend_attribute(xml_attribute< Ch > *attribute)
function append_attribute(xml_attribute< Ch > *attribute)
function insert_attribute(xml_attribute< Ch > *where, xml_attribute< Ch > *attribute)
function remove_first_attribute()
function remove_last_attribute()
function remove_attribute(xml_attribute< Ch > *where)
function remove_all_attributes()

namespace rapidxml
enum node_type
function parse_error_handler(const char *what, void *where)
function print(OutIt out, const xml_node< Ch > &node, int flags=0)
function print(std::basic_ostream< Ch > &out, const xml_node< Ch > &node, int flags=0)
function operator<<(std::basic_ostream< Ch > &out, const xml_node< Ch > &node)
+ constant + parse_no_data_nodes
+ constant + parse_no_element_values
+ constant + parse_no_string_terminators
+ constant + parse_no_entity_translation
+ constant + parse_no_utf8
+ constant + parse_declaration_node
+ constant + parse_comment_nodes
+ constant + parse_doctype_node
+ constant + parse_pi_nodes
+ constant + parse_validate_closing_tags
+ constant + parse_trim_whitespace
+ constant + parse_normalize_whitespace
+ constant + parse_default
+ constant + parse_non_destructive
+ constant + parse_fastest
+ constant + parse_full
+ constant + print_no_indenting


class + template + rapidxml::memory_pool

+ + Defined in rapidxml.hpp
+ Base class for + xml_document

Description

This class is used by the parser to create new nodes and attributes, without overheads of dynamic memory allocation. In most cases, you will not need to use this class directly. However, if you need to create nodes manually or modify names/values of nodes, you are encouraged to use memory_pool of relevant xml_document to allocate the memory. Not only is this faster than allocating them by using new operator, but also their lifetime will be tied to the lifetime of document, possibly simplyfing memory management.

+ Call allocate_node() or allocate_attribute() functions to obtain new nodes or attributes from the pool. You can also call allocate_string() function to allocate strings. Such strings can then be used as names or values of nodes without worrying about their lifetime. Note that there is no free() function -- all allocations are freed at once when clear() function is called, or when the pool is destroyed.

+ It is also possible to create a standalone memory_pool, and use it to allocate nodes, whose lifetime will not be tied to any document.

+ Pool maintains RAPIDXML_STATIC_POOL_SIZE bytes of statically allocated memory. Until static memory is exhausted, no dynamic memory allocations are done. When static memory is exhausted, pool allocates additional blocks of memory of size RAPIDXML_DYNAMIC_POOL_SIZE each, by using global new[] and delete[] operators. This behaviour can be changed by setting custom allocation routines. Use set_allocator() function to set them.

+ Allocations for nodes, attributes and strings are aligned at RAPIDXML_ALIGNMENT bytes. This value defaults to the size of pointer on target architecture.

+ To obtain absolutely top performance from the parser, it is important that all nodes are allocated from a single, contiguous block of memory. Otherwise, cache misses when jumping between two (or more) disjoint blocks of memory can slow down parsing quite considerably. If required, you can tweak RAPIDXML_STATIC_POOL_SIZE, RAPIDXML_DYNAMIC_POOL_SIZE and RAPIDXML_ALIGNMENT to obtain best wasted memory to performance compromise. To do it, define their values before rapidxml.hpp file is included.

Parameters

Ch
Character type of created nodes.

+ constructor + memory_pool::memory_pool

Synopsis

memory_pool(); +

Description

Constructs empty pool with default allocator functions.

+ destructor + memory_pool::~memory_pool

Synopsis

~memory_pool(); +

Description

Destroys pool and frees all the memory. This causes memory occupied by nodes allocated by the pool to be freed. Nodes allocated from the pool are no longer valid.

function memory_pool::allocate_node

Synopsis

xml_node<Ch>* allocate_node(node_type type, const Ch *name=0, const Ch *value=0, std::size_t name_size=0, std::size_t value_size=0); +

Description

Allocates a new node from the pool, and optionally assigns name and value to it. If the allocation request cannot be accomodated, this function will throw std::bad_alloc. If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function will call rapidxml::parse_error_handler() function.

Parameters

type
Type of node to create.
name
Name to assign to the node, or 0 to assign no name.
value
Value to assign to the node, or 0 to assign no value.
name_size
Size of name to assign, or 0 to automatically calculate size from name string.
value_size
Size of value to assign, or 0 to automatically calculate size from value string.

Returns

Pointer to allocated node. This pointer will never be NULL.

function memory_pool::allocate_attribute

Synopsis

xml_attribute<Ch>* allocate_attribute(const Ch *name=0, const Ch *value=0, std::size_t name_size=0, std::size_t value_size=0); +

Description

Allocates a new attribute from the pool, and optionally assigns name and value to it. If the allocation request cannot be accomodated, this function will throw std::bad_alloc. If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function will call rapidxml::parse_error_handler() function.

Parameters

name
Name to assign to the attribute, or 0 to assign no name.
value
Value to assign to the attribute, or 0 to assign no value.
name_size
Size of name to assign, or 0 to automatically calculate size from name string.
value_size
Size of value to assign, or 0 to automatically calculate size from value string.

Returns

Pointer to allocated attribute. This pointer will never be NULL.

function memory_pool::allocate_string

Synopsis

Ch* allocate_string(const Ch *source=0, std::size_t size=0); +

Description

Allocates a char array of given size from the pool, and optionally copies a given string to it. If the allocation request cannot be accomodated, this function will throw std::bad_alloc. If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function will call rapidxml::parse_error_handler() function.

Parameters

source
String to initialize the allocated memory with, or 0 to not initialize it.
size
Number of characters to allocate, or zero to calculate it automatically from source string length; if size is 0, source string must be specified and null terminated.

Returns

Pointer to allocated char array. This pointer will never be NULL.

function memory_pool::clone_node

Synopsis

xml_node<Ch>* clone_node(const xml_node< Ch > *source, xml_node< Ch > *result=0); +

Description

Clones an xml_node and its hierarchy of child nodes and attributes. Nodes and attributes are allocated from this memory pool. Names and values are not cloned, they are shared between the clone and the source. Result node can be optionally specified as a second parameter, in which case its contents will be replaced with cloned source node. This is useful when you want to clone entire document.

Parameters

source
Node to clone.
result
Node to put results in, or 0 to automatically allocate result node

Returns

Pointer to cloned node. This pointer will never be NULL.

function memory_pool::clear

Synopsis

void clear(); +

Description

Clears the pool. This causes memory occupied by nodes allocated by the pool to be freed. Any nodes or strings allocated from the pool will no longer be valid.

function memory_pool::set_allocator

Synopsis

void set_allocator(alloc_func *af, free_func *ff); +

Description

Sets or resets the user-defined memory allocation functions for the pool. This can only be called when no memory is allocated from the pool yet, otherwise results are undefined. Allocation function must not return invalid pointer on failure. It should either throw, stop the program, or use longjmp() function to pass control to other place of program. If it returns invalid pointer, results are undefined.

+ User defined allocation functions must have the following forms:

+void *allocate(std::size_t size);
+void free(void *pointer);

Parameters

af
Allocation function, or 0 to restore default function
ff
Free function, or 0 to restore default function

class rapidxml::parse_error

+ + Defined in rapidxml.hpp

Description

Parse error exception. This exception is thrown by the parser when an error occurs. Use what() function to get human-readable error message. Use where() function to get a pointer to position within source text where error was detected.

+ If throwing exceptions by the parser is undesirable, it can be disabled by defining RAPIDXML_NO_EXCEPTIONS macro before rapidxml.hpp is included. This will cause the parser to call rapidxml::parse_error_handler() function instead of throwing an exception. This function must be defined by the user.

+ This class derives from std::exception class.

+ constructor + parse_error::parse_error

Synopsis

parse_error(const char *what, void *where); +

Description

Constructs parse error.

function parse_error::what

Synopsis

virtual const char* what() const; +

Description

Gets human readable description of error.

Returns

Pointer to null terminated description of the error.

function parse_error::where

Synopsis

Ch* where() const; +

Description

Gets pointer to character data where error happened. Ch should be the same as char type of xml_document that produced the error.

Returns

Pointer to location within the parsed string where error occured.

class + template + rapidxml::xml_attribute

+ + Defined in rapidxml.hpp
+ Inherits from + xml_base

Description

Class representing attribute node of XML document. Each attribute has name and value strings, which are available through name() and value() functions (inherited from xml_base). Note that after parse, both name and value of attribute will point to interior of source text used for parsing. Thus, this text must persist in memory for the lifetime of attribute.

Parameters

Ch
Character type to use.

+ constructor + xml_attribute::xml_attribute

Synopsis

xml_attribute(); +

Description

Constructs an empty attribute with the specified type. Consider using memory_pool of appropriate xml_document if allocating attributes manually.

function xml_attribute::document

Synopsis

xml_document<Ch>* document() const; +

Description

Gets document of which attribute is a child.

Returns

Pointer to document that contains this attribute, or 0 if there is no parent document.

function xml_attribute::previous_attribute

Synopsis

xml_attribute<Ch>* previous_attribute(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const; +

Description

Gets previous attribute, optionally matching attribute name.

Parameters

name
Name of attribute to find, or 0 to return previous attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
name_size
Size of name, in characters, or 0 to have size calculated automatically from string
case_sensitive
Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters

Returns

Pointer to found attribute, or 0 if not found.

function xml_attribute::next_attribute

Synopsis

xml_attribute<Ch>* next_attribute(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const; +

Description

Gets next attribute, optionally matching attribute name.

Parameters

name
Name of attribute to find, or 0 to return next attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
name_size
Size of name, in characters, or 0 to have size calculated automatically from string
case_sensitive
Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters

Returns

Pointer to found attribute, or 0 if not found.

class + template + rapidxml::xml_base

+ + Defined in rapidxml.hpp
+ Base class for + xml_attribute xml_node

Description

Base class for xml_node and xml_attribute implementing common functions: name(), name_size(), value(), value_size() and parent().

Parameters

Ch
Character type to use

+ constructor + xml_base::xml_base

Synopsis

xml_base(); +

function xml_base::name

Synopsis

Ch* name() const; +

Description

Gets name of the node. Interpretation of name depends on type of node. Note that name will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse.

+ Use name_size() function to determine length of the name.

Returns

Name of node, or empty string if node has no name.

function xml_base::name_size

Synopsis

std::size_t name_size() const; +

Description

Gets size of node name, not including terminator character. This function works correctly irrespective of whether name is or is not zero terminated.

Returns

Size of node name, in characters.

function xml_base::value

Synopsis

Ch* value() const; +

Description

Gets value of node. Interpretation of value depends on type of node. Note that value will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse.

+ Use value_size() function to determine length of the value.

Returns

Value of node, or empty string if node has no value.

function xml_base::value_size

Synopsis

std::size_t value_size() const; +

Description

Gets size of node value, not including terminator character. This function works correctly irrespective of whether value is or is not zero terminated.

Returns

Size of node value, in characters.

function xml_base::name

Synopsis

void name(const Ch *name, std::size_t size); +

Description

Sets name of node to a non zero-terminated string. See Ownership Of Strings .

+ Note that node does not own its name or value, it only stores a pointer to it. It will not delete or otherwise free the pointer on destruction. It is reponsibility of the user to properly manage lifetime of the string. The easiest way to achieve it is to use memory_pool of the document to allocate the string - on destruction of the document the string will be automatically freed.

+ Size of name must be specified separately, because name does not have to be zero terminated. Use name(const Ch *) function to have the length automatically calculated (string must be zero terminated).

Parameters

name
Name of node to set. Does not have to be zero terminated.
size
Size of name, in characters. This does not include zero terminator, if one is present.

function xml_base::name

Synopsis

void name(const Ch *name); +

Description

Sets name of node to a zero-terminated string. See also Ownership Of Strings and xml_node::name(const Ch *, std::size_t).

Parameters

name
Name of node to set. Must be zero terminated.

function xml_base::value

Synopsis

void value(const Ch *value, std::size_t size); +

Description

Sets value of node to a non zero-terminated string. See Ownership Of Strings .

+ Note that node does not own its name or value, it only stores a pointer to it. It will not delete or otherwise free the pointer on destruction. It is reponsibility of the user to properly manage lifetime of the string. The easiest way to achieve it is to use memory_pool of the document to allocate the string - on destruction of the document the string will be automatically freed.

+ Size of value must be specified separately, because it does not have to be zero terminated. Use value(const Ch *) function to have the length automatically calculated (string must be zero terminated).

+ If an element has a child node of type node_data, it will take precedence over element value when printing. If you want to manipulate data of elements using values, use parser flag rapidxml::parse_no_data_nodes to prevent creation of data nodes by the parser.

Parameters

value
value of node to set. Does not have to be zero terminated.
size
Size of value, in characters. This does not include zero terminator, if one is present.

function xml_base::value

Synopsis

void value(const Ch *value); +

Description

Sets value of node to a zero-terminated string. See also Ownership Of Strings and xml_node::value(const Ch *, std::size_t).

Parameters

value
Vame of node to set. Must be zero terminated.

function xml_base::parent

Synopsis

xml_node<Ch>* parent() const; +

Description

Gets node parent.

Returns

Pointer to parent node, or 0 if there is no parent.

class + template + rapidxml::xml_document

+ + Defined in rapidxml.hpp
+ Inherits from + xml_node memory_pool

Description

This class represents root of the DOM hierarchy. It is also an xml_node and a memory_pool through public inheritance. Use parse() function to build a DOM tree from a zero-terminated XML text string. parse() function allocates memory for nodes and attributes by using functions of xml_document, which are inherited from memory_pool. To access root node of the document, use the document itself, as if it was an xml_node.

Parameters

Ch
Character type to use.

+ constructor + xml_document::xml_document

Synopsis

xml_document(); +

Description

Constructs empty XML document.

function xml_document::parse

Synopsis

void parse(Ch *text); +

Description

Parses zero-terminated XML string according to given flags. Passed string will be modified by the parser, unless rapidxml::parse_non_destructive flag is used. The string must persist for the lifetime of the document. In case of error, rapidxml::parse_error exception will be thrown.

+ If you want to parse contents of a file, you must first load the file into the memory, and pass pointer to its beginning. Make sure that data is zero-terminated.

+ Document can be parsed into multiple times. Each new call to parse removes previous nodes and attributes (if any), but does not clear memory pool.

Parameters

text
XML data to parse; pointer is non-const to denote fact that this data may be modified by the parser.

function xml_document::clear

Synopsis

void clear(); +

Description

Clears the document by deleting all nodes and clearing the memory pool. All nodes owned by document pool are destroyed.

class + template + rapidxml::xml_node

+ + Defined in rapidxml.hpp
+ Inherits from + xml_base
+ Base class for + xml_document

Description

Class representing a node of XML document. Each node may have associated name and value strings, which are available through name() and value() functions. Interpretation of name and value depends on type of the node. Type of node can be determined by using type() function.

+ Note that after parse, both name and value of node, if any, will point interior of source text used for parsing. Thus, this text must persist in the memory for the lifetime of node.

Parameters

Ch
Character type to use.

+ constructor + xml_node::xml_node

Synopsis

xml_node(node_type type); +

Description

Constructs an empty node with the specified type. Consider using memory_pool of appropriate document to allocate nodes manually.

Parameters

type
Type of node to construct.

function xml_node::type

Synopsis

node_type type() const; +

Description

Gets type of node.

Returns

Type of node.

function xml_node::document

Synopsis

xml_document<Ch>* document() const; +

Description

Gets document of which node is a child.

Returns

Pointer to document that contains this node, or 0 if there is no parent document.

function xml_node::first_node

Synopsis

xml_node<Ch>* first_node(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const; +

Description

Gets first child node, optionally matching node name.

Parameters

name
Name of child to find, or 0 to return first child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
name_size
Size of name, in characters, or 0 to have size calculated automatically from string
case_sensitive
Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters

Returns

Pointer to found child, or 0 if not found.

function xml_node::last_node

Synopsis

xml_node<Ch>* last_node(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const; +

Description

Gets last child node, optionally matching node name. Behaviour is undefined if node has no children. Use first_node() to test if node has children.

Parameters

name
Name of child to find, or 0 to return last child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
name_size
Size of name, in characters, or 0 to have size calculated automatically from string
case_sensitive
Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters

Returns

Pointer to found child, or 0 if not found.

function xml_node::previous_sibling

Synopsis

xml_node<Ch>* previous_sibling(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const; +

Description

Gets previous sibling node, optionally matching node name. Behaviour is undefined if node has no parent. Use parent() to test if node has a parent.

Parameters

name
Name of sibling to find, or 0 to return previous sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
name_size
Size of name, in characters, or 0 to have size calculated automatically from string
case_sensitive
Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters

Returns

Pointer to found sibling, or 0 if not found.

function xml_node::next_sibling

Synopsis

xml_node<Ch>* next_sibling(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const; +

Description

Gets next sibling node, optionally matching node name. Behaviour is undefined if node has no parent. Use parent() to test if node has a parent.

Parameters

name
Name of sibling to find, or 0 to return next sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
name_size
Size of name, in characters, or 0 to have size calculated automatically from string
case_sensitive
Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters

Returns

Pointer to found sibling, or 0 if not found.

function xml_node::first_attribute

Synopsis

xml_attribute<Ch>* first_attribute(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const; +

Description

Gets first attribute of node, optionally matching attribute name.

Parameters

name
Name of attribute to find, or 0 to return first attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
name_size
Size of name, in characters, or 0 to have size calculated automatically from string
case_sensitive
Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters

Returns

Pointer to found attribute, or 0 if not found.

function xml_node::last_attribute

Synopsis

xml_attribute<Ch>* last_attribute(const Ch *name=0, std::size_t name_size=0, bool case_sensitive=true) const; +

Description

Gets last attribute of node, optionally matching attribute name.

Parameters

name
Name of attribute to find, or 0 to return last attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
name_size
Size of name, in characters, or 0 to have size calculated automatically from string
case_sensitive
Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters

Returns

Pointer to found attribute, or 0 if not found.

function xml_node::type

Synopsis

void type(node_type type); +

Description

Sets type of node.

Parameters

type
Type of node to set.

function xml_node::prepend_node

Synopsis

void prepend_node(xml_node< Ch > *child); +

Description

Prepends a new child node. The prepended child becomes the first child, and all existing children are moved one position back.

Parameters

child
Node to prepend.

function xml_node::append_node

Synopsis

void append_node(xml_node< Ch > *child); +

Description

Appends a new child node. The appended child becomes the last child.

Parameters

child
Node to append.

function xml_node::insert_node

Synopsis

void insert_node(xml_node< Ch > *where, xml_node< Ch > *child); +

Description

Inserts a new child node at specified place inside the node. All children after and including the specified node are moved one position back.

Parameters

where
Place where to insert the child, or 0 to insert at the back.
child
Node to insert.

function xml_node::remove_first_node

Synopsis

void remove_first_node(); +

Description

Removes first child node. If node has no children, behaviour is undefined. Use first_node() to test if node has children.

function xml_node::remove_last_node

Synopsis

void remove_last_node(); +

Description

Removes last child of the node. If node has no children, behaviour is undefined. Use first_node() to test if node has children.

function xml_node::remove_node

Synopsis

void remove_node(xml_node< Ch > *where); +

Description

Removes specified child from the node.

function xml_node::remove_all_nodes

Synopsis

void remove_all_nodes(); +

Description

Removes all child nodes (but not attributes).

function xml_node::prepend_attribute

Synopsis

void prepend_attribute(xml_attribute< Ch > *attribute); +

Description

Prepends a new attribute to the node.

Parameters

attribute
Attribute to prepend.

function xml_node::append_attribute

Synopsis

void append_attribute(xml_attribute< Ch > *attribute); +

Description

Appends a new attribute to the node.

Parameters

attribute
Attribute to append.

function xml_node::insert_attribute

Synopsis

void insert_attribute(xml_attribute< Ch > *where, xml_attribute< Ch > *attribute); +

Description

Inserts a new attribute at specified place inside the node. All attributes after and including the specified attribute are moved one position back.

Parameters

where
Place where to insert the attribute, or 0 to insert at the back.
attribute
Attribute to insert.

function xml_node::remove_first_attribute

Synopsis

void remove_first_attribute(); +

Description

Removes first attribute of the node. If node has no attributes, behaviour is undefined. Use first_attribute() to test if node has attributes.

function xml_node::remove_last_attribute

Synopsis

void remove_last_attribute(); +

Description

Removes last attribute of the node. If node has no attributes, behaviour is undefined. Use first_attribute() to test if node has attributes.

function xml_node::remove_attribute

Synopsis

void remove_attribute(xml_attribute< Ch > *where); +

Description

Removes specified attribute from node.

Parameters

where
Pointer to attribute to be removed.

function xml_node::remove_all_attributes

Synopsis

void remove_all_attributes(); +

Description

Removes all attributes of node.

enum node_type

Description

Enumeration listing all node types produced by the parser. Use xml_node::type() function to query node type.

Values

node_document
A document node. Name and value are empty.
node_element
An element node. Name contains element name. Value contains text of first data node.
node_data
A data node. Name is empty. Value contains data text.
node_cdata
A CDATA node. Name is empty. Value contains data text.
node_comment
A comment node. Name is empty. Value contains comment text.
node_declaration
A declaration node. Name and value are empty. Declaration parameters (version, encoding and standalone) are in node attributes.
node_doctype
A DOCTYPE node. Name is empty. Value contains DOCTYPE text.
node_pi
A PI node. Name contains target. Value contains instructions.

function parse_error_handler

Synopsis

void rapidxml::parse_error_handler(const char *what, void *where); +

Description

When exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function is called to notify user about the error. It must be defined by the user.

+ This function cannot return. If it does, the results are undefined.

+ A very simple definition might look like that: + void rapidxml::parse_error_handler(const char *what, void *where) + { + std::cout << "Parse error: " << what << "\n"; + std::abort(); + } +

Parameters

what
Human readable description of the error.
where
Pointer to character data where error was detected.

function print

Synopsis

OutIt rapidxml::print(OutIt out, const xml_node< Ch > &node, int flags=0); +

Description

Prints XML to given output iterator.

Parameters

out
Output iterator to print to.
node
Node to be printed. Pass xml_document to print entire document.
flags
Flags controlling how XML is printed.

Returns

Output iterator pointing to position immediately after last character of printed text.

function print

Synopsis

std::basic_ostream<Ch>& rapidxml::print(std::basic_ostream< Ch > &out, const xml_node< Ch > &node, int flags=0); +

Description

Prints XML to given output stream.

Parameters

out
Output stream to print to.
node
Node to be printed. Pass xml_document to print entire document.
flags
Flags controlling how XML is printed.

Returns

Output stream.

function operator<<

Synopsis

std::basic_ostream<Ch>& rapidxml::operator<<(std::basic_ostream< Ch > &out, const xml_node< Ch > &node); +

Description

Prints formatted XML to given output stream. Uses default printing flags. Use print() function to customize printing process.

Parameters

out
Output stream to print to.
node
Node to be printed.

Returns

Output stream.

+ constant + parse_no_data_nodes

Synopsis

const int parse_no_data_nodes + = 0x1; +

Description

Parse flag instructing the parser to not create data nodes. Text of first data node will still be placed in value of parent element, unless rapidxml::parse_no_element_values flag is also specified. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_no_element_values

Synopsis

const int parse_no_element_values + = 0x2; +

Description

Parse flag instructing the parser to not use text of first data node as a value of parent element. Can be combined with other flags by use of | operator. Note that child data nodes of element node take precendence over its value when printing. That is, if element has one or more child data nodes and a value, the value will be ignored. Use rapidxml::parse_no_data_nodes flag to prevent creation of data nodes if you want to manipulate data using values of elements.

+ See xml_document::parse() function.

+ constant + parse_no_string_terminators

Synopsis

const int parse_no_string_terminators + = 0x4; +

Description

Parse flag instructing the parser to not place zero terminators after strings in the source text. By default zero terminators are placed, modifying source text. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_no_entity_translation

Synopsis

const int parse_no_entity_translation + = 0x8; +

Description

Parse flag instructing the parser to not translate entities in the source text. By default entities are translated, modifying source text. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_no_utf8

Synopsis

const int parse_no_utf8 + = 0x10; +

Description

Parse flag instructing the parser to disable UTF-8 handling and assume plain 8 bit characters. By default, UTF-8 handling is enabled. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_declaration_node

Synopsis

const int parse_declaration_node + = 0x20; +

Description

Parse flag instructing the parser to create XML declaration node. By default, declaration node is not created. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_comment_nodes

Synopsis

const int parse_comment_nodes + = 0x40; +

Description

Parse flag instructing the parser to create comments nodes. By default, comment nodes are not created. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_doctype_node

Synopsis

const int parse_doctype_node + = 0x80; +

Description

Parse flag instructing the parser to create DOCTYPE node. By default, doctype node is not created. Although W3C specification allows at most one DOCTYPE node, RapidXml will silently accept documents with more than one. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_pi_nodes

Synopsis

const int parse_pi_nodes + = 0x100; +

Description

Parse flag instructing the parser to create PI nodes. By default, PI nodes are not created. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_validate_closing_tags

Synopsis

const int parse_validate_closing_tags + = 0x200; +

Description

Parse flag instructing the parser to validate closing tag names. If not set, name inside closing tag is irrelevant to the parser. By default, closing tags are not validated. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_trim_whitespace

Synopsis

const int parse_trim_whitespace + = 0x400; +

Description

Parse flag instructing the parser to trim all leading and trailing whitespace of data nodes. By default, whitespace is not trimmed. This flag does not cause the parser to modify source text. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_normalize_whitespace

Synopsis

const int parse_normalize_whitespace + = 0x800; +

Description

Parse flag instructing the parser to condense all whitespace runs of data nodes to a single space character. Trimming of leading and trailing whitespace of data is controlled by rapidxml::parse_trim_whitespace flag. By default, whitespace is not normalized. If this flag is specified, source text will be modified. Can be combined with other flags by use of | operator.

+ See xml_document::parse() function.

+ constant + parse_default

Synopsis

const int parse_default + = 0; +

Description

Parse flags which represent default behaviour of the parser. This is always equal to 0, so that all other flags can be simply ored together. Normally there is no need to inconveniently disable flags by anding with their negated (~) values. This also means that meaning of each flag is a negation of the default setting. For example, if flag name is rapidxml::parse_no_utf8, it means that utf-8 is enabled by default, and using the flag will disable it.

+ See xml_document::parse() function.

+ constant + parse_non_destructive

Synopsis

const int parse_non_destructive + = parse_no_string_terminators | parse_no_entity_translation; +

Description

A combination of parse flags that forbids any modifications of the source text. This also results in faster parsing. However, note that the following will occur:
  • names and values of nodes will not be zero terminated, you have to use xml_base::name_size() and xml_base::value_size() functions to determine where name and value ends
  • entities will not be translated
  • whitespace will not be normalized
+See xml_document::parse() function.

+ constant + parse_fastest

Synopsis

const int parse_fastest + = parse_non_destructive | parse_no_data_nodes; +

Description

A combination of parse flags resulting in fastest possible parsing, without sacrificing important data.

+ See xml_document::parse() function.

+ constant + parse_full

Synopsis

const int parse_full + = parse_declaration_node | parse_comment_nodes | parse_doctype_node | parse_pi_nodes | parse_validate_closing_tags; +

Description

A combination of parse flags resulting in largest amount of data being extracted. This usually results in slowest parsing.

+ See xml_document::parse() function.

+ constant + print_no_indenting

Synopsis

const int print_no_indenting + = 0x1; +

Description

Printer flag instructing the printer to suppress indenting of XML. See print() function.

\ No newline at end of file diff --git a/util/xpack/rapidxml/rapidxml.hpp b/util/xpack/rapidxml/rapidxml.hpp new file mode 100755 index 0000000..6b82f20 --- /dev/null +++ b/util/xpack/rapidxml/rapidxml.hpp @@ -0,0 +1,2596 @@ +#ifndef RAPIDXML_HPP_INCLUDED +#define RAPIDXML_HPP_INCLUDED + +// Copyright (C) 2006, 2009 Marcin Kalicinski +// Version 1.13 +// Revision $DateTime: 2009/05/13 01:46:17 $ +//! \file rapidxml.hpp This file contains rapidxml parser and DOM implementation + +// If standard library is disabled, user must provide implementations of required functions and typedefs +#if !defined(RAPIDXML_NO_STDLIB) + #include // For std::size_t + #include // For assert + #include // For placement new +#endif + +// On MSVC, disable "conditional expression is constant" warning (level 4). +// This warning is almost impossible to avoid with certain types of templated code +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable:4127) // Conditional expression is constant +#endif + +/////////////////////////////////////////////////////////////////////////// +// RAPIDXML_PARSE_ERROR + +#if defined(RAPIDXML_NO_EXCEPTIONS) + +#define RAPIDXML_PARSE_ERROR(what, where) { parse_error_handler(what, where); assert(0); } + +namespace rapidxml +{ + //! When exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, + //! this function is called to notify user about the error. + //! It must be defined by the user. + //!

+ //! This function cannot return. If it does, the results are undefined. + //!

+ //! A very simple definition might look like that: + //!

+    //! void %rapidxml::%parse_error_handler(const char *what, void *where)
+    //! {
+    //!     std::cout << "Parse error: " << what << "\n";
+    //!     std::abort();
+    //! }
+    //! 
+ //! \param what Human readable description of the error. + //! \param where Pointer to character data where error was detected. + void parse_error_handler(const char *what, void *where); +} + +#else + +#include // For std::exception + +#define RAPIDXML_PARSE_ERROR(what, where) throw parse_error(what, where) + +namespace rapidxml +{ + + //! Parse error exception. + //! This exception is thrown by the parser when an error occurs. + //! Use what() function to get human-readable error message. + //! Use where() function to get a pointer to position within source text where error was detected. + //!

+ //! If throwing exceptions by the parser is undesirable, + //! it can be disabled by defining RAPIDXML_NO_EXCEPTIONS macro before rapidxml.hpp is included. + //! This will cause the parser to call rapidxml::parse_error_handler() function instead of throwing an exception. + //! This function must be defined by the user. + //!

+ //! This class derives from std::exception class. + class parse_error: public std::exception + { + + public: + + //! Constructs parse error + parse_error(const char *what, void *where) + : m_what(what) + , m_where(where) + { + } + + //! Gets human readable description of error. + //! \return Pointer to null terminated description of the error. + virtual const char *what() const throw() + { + return m_what; + } + + //! Gets pointer to character data where error happened. + //! Ch should be the same as char type of xml_document that produced the error. + //! \return Pointer to location within the parsed string where error occured. + template + Ch *where() const + { + return reinterpret_cast(m_where); + } + + private: + + const char *m_what; + void *m_where; + + }; +} + +#endif + +/////////////////////////////////////////////////////////////////////////// +// Pool sizes + +#ifndef RAPIDXML_STATIC_POOL_SIZE + // Size of static memory block of memory_pool. + // Define RAPIDXML_STATIC_POOL_SIZE before including rapidxml.hpp if you want to override the default value. + // No dynamic memory allocations are performed by memory_pool until static memory is exhausted. + #define RAPIDXML_STATIC_POOL_SIZE (64 * 1024) +#endif + +#ifndef RAPIDXML_DYNAMIC_POOL_SIZE + // Size of dynamic memory block of memory_pool. + // Define RAPIDXML_DYNAMIC_POOL_SIZE before including rapidxml.hpp if you want to override the default value. + // After the static block is exhausted, dynamic blocks with approximately this size are allocated by memory_pool. + #define RAPIDXML_DYNAMIC_POOL_SIZE (64 * 1024) +#endif + +#ifndef RAPIDXML_ALIGNMENT + // Memory allocation alignment. + // Define RAPIDXML_ALIGNMENT before including rapidxml.hpp if you want to override the default value, which is the size of pointer. + // All memory allocations for nodes, attributes and strings will be aligned to this value. + // This must be a power of 2 and at least 1, otherwise memory_pool will not work. + #define RAPIDXML_ALIGNMENT sizeof(void *) +#endif + +namespace rapidxml +{ + // Forward declarations + template class xml_node; + template class xml_attribute; + template class xml_document; + + //! Enumeration listing all node types produced by the parser. + //! Use xml_node::type() function to query node type. + enum node_type + { + node_document, //!< A document node. Name and value are empty. + node_element, //!< An element node. Name contains element name. Value contains text of first data node. + node_data, //!< A data node. Name is empty. Value contains data text. + node_cdata, //!< A CDATA node. Name is empty. Value contains data text. + node_comment, //!< A comment node. Name is empty. Value contains comment text. + node_declaration, //!< A declaration node. Name and value are empty. Declaration parameters (version, encoding and standalone) are in node attributes. + node_doctype, //!< A DOCTYPE node. Name is empty. Value contains DOCTYPE text. + node_pi //!< A PI node. Name contains target. Value contains instructions. + }; + + /////////////////////////////////////////////////////////////////////// + // Parsing flags + + //! Parse flag instructing the parser to not create data nodes. + //! Text of first data node will still be placed in value of parent element, unless rapidxml::parse_no_element_values flag is also specified. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_no_data_nodes = 0x1; + + //! Parse flag instructing the parser to not use text of first data node as a value of parent element. + //! Can be combined with other flags by use of | operator. + //! Note that child data nodes of element node take precendence over its value when printing. + //! That is, if element has one or more child data nodes and a value, the value will be ignored. + //! Use rapidxml::parse_no_data_nodes flag to prevent creation of data nodes if you want to manipulate data using values of elements. + //!

+ //! See xml_document::parse() function. + const int parse_no_element_values = 0x2; + + //! Parse flag instructing the parser to not place zero terminators after strings in the source text. + //! By default zero terminators are placed, modifying source text. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_no_string_terminators = 0x4; + + //! Parse flag instructing the parser to not translate entities in the source text. + //! By default entities are translated, modifying source text. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_no_entity_translation = 0x8; + + //! Parse flag instructing the parser to disable UTF-8 handling and assume plain 8 bit characters. + //! By default, UTF-8 handling is enabled. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_no_utf8 = 0x10; + + //! Parse flag instructing the parser to create XML declaration node. + //! By default, declaration node is not created. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_declaration_node = 0x20; + + //! Parse flag instructing the parser to create comments nodes. + //! By default, comment nodes are not created. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_comment_nodes = 0x40; + + //! Parse flag instructing the parser to create DOCTYPE node. + //! By default, doctype node is not created. + //! Although W3C specification allows at most one DOCTYPE node, RapidXml will silently accept documents with more than one. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_doctype_node = 0x80; + + //! Parse flag instructing the parser to create PI nodes. + //! By default, PI nodes are not created. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_pi_nodes = 0x100; + + //! Parse flag instructing the parser to validate closing tag names. + //! If not set, name inside closing tag is irrelevant to the parser. + //! By default, closing tags are not validated. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_validate_closing_tags = 0x200; + + //! Parse flag instructing the parser to trim all leading and trailing whitespace of data nodes. + //! By default, whitespace is not trimmed. + //! This flag does not cause the parser to modify source text. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_trim_whitespace = 0x400; + + //! Parse flag instructing the parser to condense all whitespace runs of data nodes to a single space character. + //! Trimming of leading and trailing whitespace of data is controlled by rapidxml::parse_trim_whitespace flag. + //! By default, whitespace is not normalized. + //! If this flag is specified, source text will be modified. + //! Can be combined with other flags by use of | operator. + //!

+ //! See xml_document::parse() function. + const int parse_normalize_whitespace = 0x800; + + // Compound flags + + //! Parse flags which represent default behaviour of the parser. + //! This is always equal to 0, so that all other flags can be simply ored together. + //! Normally there is no need to inconveniently disable flags by anding with their negated (~) values. + //! This also means that meaning of each flag is a negation of the default setting. + //! For example, if flag name is rapidxml::parse_no_utf8, it means that utf-8 is enabled by default, + //! and using the flag will disable it. + //!

+ //! See xml_document::parse() function. + const int parse_default = 0; + + //! A combination of parse flags that forbids any modifications of the source text. + //! This also results in faster parsing. However, note that the following will occur: + //!
    + //!
  • names and values of nodes will not be zero terminated, you have to use xml_base::name_size() and xml_base::value_size() functions to determine where name and value ends
  • + //!
  • entities will not be translated
  • + //!
  • whitespace will not be normalized
  • + //!
+ //! See xml_document::parse() function. + const int parse_non_destructive = parse_no_string_terminators | parse_no_entity_translation; + + //! A combination of parse flags resulting in fastest possible parsing, without sacrificing important data. + //!

+ //! See xml_document::parse() function. + const int parse_fastest = parse_non_destructive | parse_no_data_nodes; + + //! A combination of parse flags resulting in largest amount of data being extracted. + //! This usually results in slowest parsing. + //!

+ //! See xml_document::parse() function. + const int parse_full = parse_declaration_node | parse_comment_nodes | parse_doctype_node | parse_pi_nodes | parse_validate_closing_tags; + + /////////////////////////////////////////////////////////////////////// + // Internals + + //! \cond internal + namespace internal + { + + // Struct that contains lookup tables for the parser + // It must be a template to allow correct linking (because it has static data members, which are defined in a header file). + template + struct lookup_tables + { + static const unsigned char lookup_whitespace[256]; // Whitespace table + static const unsigned char lookup_node_name[256]; // Node name table + static const unsigned char lookup_text[256]; // Text table + static const unsigned char lookup_text_pure_no_ws[256]; // Text table + static const unsigned char lookup_text_pure_with_ws[256]; // Text table + static const unsigned char lookup_attribute_name[256]; // Attribute name table + static const unsigned char lookup_attribute_data_1[256]; // Attribute data table with single quote + static const unsigned char lookup_attribute_data_1_pure[256]; // Attribute data table with single quote + static const unsigned char lookup_attribute_data_2[256]; // Attribute data table with double quotes + static const unsigned char lookup_attribute_data_2_pure[256]; // Attribute data table with double quotes + static const unsigned char lookup_digits[256]; // Digits + static const unsigned char lookup_upcase[256]; // To uppercase conversion table for ASCII characters + }; + + // Find length of the string + template + inline std::size_t measure(const Ch *p) + { + const Ch *tmp = p; + while (*tmp) + ++tmp; + return tmp - p; + } + + // Compare strings for equality + template + inline bool compare(const Ch *p1, std::size_t size1, const Ch *p2, std::size_t size2, bool case_sensitive) + { + if (size1 != size2) + return false; + if (case_sensitive) + { + for (const Ch *end = p1 + size1; p1 < end; ++p1, ++p2) + if (*p1 != *p2) + return false; + } + else + { + for (const Ch *end = p1 + size1; p1 < end; ++p1, ++p2) + if (lookup_tables<0>::lookup_upcase[static_cast(*p1)] != lookup_tables<0>::lookup_upcase[static_cast(*p2)]) + return false; + } + return true; + } + } + //! \endcond + + /////////////////////////////////////////////////////////////////////// + // Memory pool + + //! This class is used by the parser to create new nodes and attributes, without overheads of dynamic memory allocation. + //! In most cases, you will not need to use this class directly. + //! However, if you need to create nodes manually or modify names/values of nodes, + //! you are encouraged to use memory_pool of relevant xml_document to allocate the memory. + //! Not only is this faster than allocating them by using new operator, + //! but also their lifetime will be tied to the lifetime of document, + //! possibly simplyfing memory management. + //!

+ //! Call allocate_node() or allocate_attribute() functions to obtain new nodes or attributes from the pool. + //! You can also call allocate_string() function to allocate strings. + //! Such strings can then be used as names or values of nodes without worrying about their lifetime. + //! Note that there is no free() function -- all allocations are freed at once when clear() function is called, + //! or when the pool is destroyed. + //!

+ //! It is also possible to create a standalone memory_pool, and use it + //! to allocate nodes, whose lifetime will not be tied to any document. + //!

+ //! Pool maintains RAPIDXML_STATIC_POOL_SIZE bytes of statically allocated memory. + //! Until static memory is exhausted, no dynamic memory allocations are done. + //! When static memory is exhausted, pool allocates additional blocks of memory of size RAPIDXML_DYNAMIC_POOL_SIZE each, + //! by using global new[] and delete[] operators. + //! This behaviour can be changed by setting custom allocation routines. + //! Use set_allocator() function to set them. + //!

+ //! Allocations for nodes, attributes and strings are aligned at RAPIDXML_ALIGNMENT bytes. + //! This value defaults to the size of pointer on target architecture. + //!

+ //! To obtain absolutely top performance from the parser, + //! it is important that all nodes are allocated from a single, contiguous block of memory. + //! Otherwise, cache misses when jumping between two (or more) disjoint blocks of memory can slow down parsing quite considerably. + //! If required, you can tweak RAPIDXML_STATIC_POOL_SIZE, RAPIDXML_DYNAMIC_POOL_SIZE and RAPIDXML_ALIGNMENT + //! to obtain best wasted memory to performance compromise. + //! To do it, define their values before rapidxml.hpp file is included. + //! \param Ch Character type of created nodes. + template + class memory_pool + { + + public: + + //! \cond internal + typedef void *(alloc_func)(std::size_t); // Type of user-defined function used to allocate memory + typedef void (free_func)(void *); // Type of user-defined function used to free memory + //! \endcond + + //! Constructs empty pool with default allocator functions. + memory_pool() + : m_alloc_func(0) + , m_free_func(0) + { + init(); + } + + //! Destroys pool and frees all the memory. + //! This causes memory occupied by nodes allocated by the pool to be freed. + //! Nodes allocated from the pool are no longer valid. + ~memory_pool() + { + clear(); + } + + //! Allocates a new node from the pool, and optionally assigns name and value to it. + //! If the allocation request cannot be accomodated, this function will throw std::bad_alloc. + //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function + //! will call rapidxml::parse_error_handler() function. + //! \param type Type of node to create. + //! \param name Name to assign to the node, or 0 to assign no name. + //! \param value Value to assign to the node, or 0 to assign no value. + //! \param name_size Size of name to assign, or 0 to automatically calculate size from name string. + //! \param value_size Size of value to assign, or 0 to automatically calculate size from value string. + //! \return Pointer to allocated node. This pointer will never be NULL. + xml_node *allocate_node(node_type type, + const Ch *name = 0, const Ch *value = 0, + std::size_t name_size = 0, std::size_t value_size = 0) + { + void *memory = allocate_aligned(sizeof(xml_node)); + xml_node *node = new(memory) xml_node(type); + if (name) + { + if (name_size > 0) + node->name(name, name_size); + else + node->name(name); + } + if (value) + { + if (value_size > 0) + node->value(value, value_size); + else + node->value(value); + } + return node; + } + + //! Allocates a new attribute from the pool, and optionally assigns name and value to it. + //! If the allocation request cannot be accomodated, this function will throw std::bad_alloc. + //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function + //! will call rapidxml::parse_error_handler() function. + //! \param name Name to assign to the attribute, or 0 to assign no name. + //! \param value Value to assign to the attribute, or 0 to assign no value. + //! \param name_size Size of name to assign, or 0 to automatically calculate size from name string. + //! \param value_size Size of value to assign, or 0 to automatically calculate size from value string. + //! \return Pointer to allocated attribute. This pointer will never be NULL. + xml_attribute *allocate_attribute(const Ch *name = 0, const Ch *value = 0, + std::size_t name_size = 0, std::size_t value_size = 0) + { + void *memory = allocate_aligned(sizeof(xml_attribute)); + xml_attribute *attribute = new(memory) xml_attribute; + if (name) + { + if (name_size > 0) + attribute->name(name, name_size); + else + attribute->name(name); + } + if (value) + { + if (value_size > 0) + attribute->value(value, value_size); + else + attribute->value(value); + } + return attribute; + } + + //! Allocates a char array of given size from the pool, and optionally copies a given string to it. + //! If the allocation request cannot be accomodated, this function will throw std::bad_alloc. + //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function + //! will call rapidxml::parse_error_handler() function. + //! \param source String to initialize the allocated memory with, or 0 to not initialize it. + //! \param size Number of characters to allocate, or zero to calculate it automatically from source string length; if size is 0, source string must be specified and null terminated. + //! \return Pointer to allocated char array. This pointer will never be NULL. + Ch *allocate_string(const Ch *source = 0, std::size_t size = 0) + { + assert(source || size); // Either source or size (or both) must be specified + if (size == 0) + size = internal::measure(source) + 1; + Ch *result = static_cast(allocate_aligned(size * sizeof(Ch))); + if (source) + for (std::size_t i = 0; i < size; ++i) + result[i] = source[i]; + return result; + } + + //! Clones an xml_node and its hierarchy of child nodes and attributes. + //! Nodes and attributes are allocated from this memory pool. + //! Names and values are not cloned, they are shared between the clone and the source. + //! Result node can be optionally specified as a second parameter, + //! in which case its contents will be replaced with cloned source node. + //! This is useful when you want to clone entire document. + //! \param source Node to clone. + //! \param result Node to put results in, or 0 to automatically allocate result node + //! \return Pointer to cloned node. This pointer will never be NULL. + xml_node *clone_node(const xml_node *source, xml_node *result = 0) + { + // Prepare result node + if (result) + { + result->remove_all_attributes(); + result->remove_all_nodes(); + result->type(source->type()); + } + else + result = allocate_node(source->type()); + + // Clone name and value + result->name(source->name(), source->name_size()); + result->value(source->value(), source->value_size()); + + // Clone child nodes and attributes + for (xml_node *child = source->first_node(); child; child = child->next_sibling()) + result->append_node(clone_node(child)); + for (xml_attribute *attr = source->first_attribute(); attr; attr = attr->next_attribute()) + result->append_attribute(allocate_attribute(attr->name(), attr->value(), attr->name_size(), attr->value_size())); + + return result; + } + + //! Clears the pool. + //! This causes memory occupied by nodes allocated by the pool to be freed. + //! Any nodes or strings allocated from the pool will no longer be valid. + void clear() + { + while (m_begin != m_static_memory) + { + char *previous_begin = reinterpret_cast
(align(m_begin))->previous_begin; + if (m_free_func) + m_free_func(m_begin); + else + delete[] m_begin; + m_begin = previous_begin; + } + init(); + } + + //! Sets or resets the user-defined memory allocation functions for the pool. + //! This can only be called when no memory is allocated from the pool yet, otherwise results are undefined. + //! Allocation function must not return invalid pointer on failure. It should either throw, + //! stop the program, or use longjmp() function to pass control to other place of program. + //! If it returns invalid pointer, results are undefined. + //!

+ //! User defined allocation functions must have the following forms: + //!
+ //!
void *allocate(std::size_t size); + //!
void free(void *pointer); + //!

+ //! \param af Allocation function, or 0 to restore default function + //! \param ff Free function, or 0 to restore default function + void set_allocator(alloc_func *af, free_func *ff) + { + assert(m_begin == m_static_memory && m_ptr == align(m_begin)); // Verify that no memory is allocated yet + m_alloc_func = af; + m_free_func = ff; + } + + private: + + struct header + { + char *previous_begin; + }; + + void init() + { + m_begin = m_static_memory; + m_ptr = align(m_begin); + m_end = m_static_memory + sizeof(m_static_memory); + } + + char *align(char *ptr) + { + std::size_t alignment = ((RAPIDXML_ALIGNMENT - (std::size_t(ptr) & (RAPIDXML_ALIGNMENT - 1))) & (RAPIDXML_ALIGNMENT - 1)); + return ptr + alignment; + } + + char *allocate_raw(std::size_t size) + { + // Allocate + void *memory; + if (m_alloc_func) // Allocate memory using either user-specified allocation function or global operator new[] + { + memory = m_alloc_func(size); + assert(memory); // Allocator is not allowed to return 0, on failure it must either throw, stop the program or use longjmp + } + else + { + memory = new char[size]; +#ifdef RAPIDXML_NO_EXCEPTIONS + if (!memory) // If exceptions are disabled, verify memory allocation, because new will not be able to throw bad_alloc + RAPIDXML_PARSE_ERROR("out of memory", 0); +#endif + } + return static_cast(memory); + } + + void *allocate_aligned(std::size_t size) + { + // Calculate aligned pointer + char *result = align(m_ptr); + + // If not enough memory left in current pool, allocate a new pool + if (result + size > m_end) + { + // Calculate required pool size (may be bigger than RAPIDXML_DYNAMIC_POOL_SIZE) + std::size_t pool_size = RAPIDXML_DYNAMIC_POOL_SIZE; + if (pool_size < size) + pool_size = size; + + // Allocate + std::size_t alloc_size = sizeof(header) + (2 * RAPIDXML_ALIGNMENT - 2) + pool_size; // 2 alignments required in worst case: one for header, one for actual allocation + char *raw_memory = allocate_raw(alloc_size); + + // Setup new pool in allocated memory + char *pool = align(raw_memory); + header *new_header = reinterpret_cast
(pool); + new_header->previous_begin = m_begin; + m_begin = raw_memory; + m_ptr = pool + sizeof(header); + m_end = raw_memory + alloc_size; + + // Calculate aligned pointer again using new pool + result = align(m_ptr); + } + + // Update pool and return aligned pointer + m_ptr = result + size; + return result; + } + + char *m_begin; // Start of raw memory making up current pool + char *m_ptr; // First free byte in current pool + char *m_end; // One past last available byte in current pool + char m_static_memory[RAPIDXML_STATIC_POOL_SIZE]; // Static raw memory + alloc_func *m_alloc_func; // Allocator function, or 0 if default is to be used + free_func *m_free_func; // Free function, or 0 if default is to be used + }; + + /////////////////////////////////////////////////////////////////////////// + // XML base + + //! Base class for xml_node and xml_attribute implementing common functions: + //! name(), name_size(), value(), value_size() and parent(). + //! \param Ch Character type to use + template + class xml_base + { + + public: + + /////////////////////////////////////////////////////////////////////////// + // Construction & destruction + + // Construct a base with empty name, value and parent + xml_base() + : m_name(0) + , m_value(0) + , m_parent(0) + { + } + + /////////////////////////////////////////////////////////////////////////// + // Node data access + + //! Gets name of the node. + //! Interpretation of name depends on type of node. + //! Note that name will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse. + //!

+ //! Use name_size() function to determine length of the name. + //! \return Name of node, or empty string if node has no name. + Ch *name() const + { + return m_name ? m_name : nullstr(); + } + + //! Gets size of node name, not including terminator character. + //! This function works correctly irrespective of whether name is or is not zero terminated. + //! \return Size of node name, in characters. + std::size_t name_size() const + { + return m_name ? m_name_size : 0; + } + + //! Gets value of node. + //! Interpretation of value depends on type of node. + //! Note that value will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse. + //!

+ //! Use value_size() function to determine length of the value. + //! \return Value of node, or empty string if node has no value. + Ch *value() const + { + return m_value ? m_value : nullstr(); + } + + //! Gets size of node value, not including terminator character. + //! This function works correctly irrespective of whether value is or is not zero terminated. + //! \return Size of node value, in characters. + std::size_t value_size() const + { + return m_value ? m_value_size : 0; + } + + /////////////////////////////////////////////////////////////////////////// + // Node modification + + //! Sets name of node to a non zero-terminated string. + //! See \ref ownership_of_strings. + //!

+ //! Note that node does not own its name or value, it only stores a pointer to it. + //! It will not delete or otherwise free the pointer on destruction. + //! It is reponsibility of the user to properly manage lifetime of the string. + //! The easiest way to achieve it is to use memory_pool of the document to allocate the string - + //! on destruction of the document the string will be automatically freed. + //!

+ //! Size of name must be specified separately, because name does not have to be zero terminated. + //! Use name(const Ch *) function to have the length automatically calculated (string must be zero terminated). + //! \param name Name of node to set. Does not have to be zero terminated. + //! \param size Size of name, in characters. This does not include zero terminator, if one is present. + void name(const Ch *name, std::size_t size) + { + m_name = const_cast(name); + m_name_size = size; + } + + //! Sets name of node to a zero-terminated string. + //! See also \ref ownership_of_strings and xml_node::name(const Ch *, std::size_t). + //! \param name Name of node to set. Must be zero terminated. + void name(const Ch *name) + { + this->name(name, internal::measure(name)); + } + + //! Sets value of node to a non zero-terminated string. + //! See \ref ownership_of_strings. + //!

+ //! Note that node does not own its name or value, it only stores a pointer to it. + //! It will not delete or otherwise free the pointer on destruction. + //! It is reponsibility of the user to properly manage lifetime of the string. + //! The easiest way to achieve it is to use memory_pool of the document to allocate the string - + //! on destruction of the document the string will be automatically freed. + //!

+ //! Size of value must be specified separately, because it does not have to be zero terminated. + //! Use value(const Ch *) function to have the length automatically calculated (string must be zero terminated). + //!

+ //! If an element has a child node of type node_data, it will take precedence over element value when printing. + //! If you want to manipulate data of elements using values, use parser flag rapidxml::parse_no_data_nodes to prevent creation of data nodes by the parser. + //! \param value value of node to set. Does not have to be zero terminated. + //! \param size Size of value, in characters. This does not include zero terminator, if one is present. + void value(const Ch *value, std::size_t size) + { + m_value = const_cast(value); + m_value_size = size; + } + + //! Sets value of node to a zero-terminated string. + //! See also \ref ownership_of_strings and xml_node::value(const Ch *, std::size_t). + //! \param value Vame of node to set. Must be zero terminated. + void value(const Ch *value) + { + this->value(value, internal::measure(value)); + } + + /////////////////////////////////////////////////////////////////////////// + // Related nodes access + + //! Gets node parent. + //! \return Pointer to parent node, or 0 if there is no parent. + xml_node *parent() const + { + return m_parent; + } + + protected: + + // Return empty string + static Ch *nullstr() + { + static Ch zero = Ch('\0'); + return &zero; + } + + Ch *m_name; // Name of node, or 0 if no name + Ch *m_value; // Value of node, or 0 if no value + std::size_t m_name_size; // Length of node name, or undefined of no name + std::size_t m_value_size; // Length of node value, or undefined if no value + xml_node *m_parent; // Pointer to parent node, or 0 if none + + }; + + //! Class representing attribute node of XML document. + //! Each attribute has name and value strings, which are available through name() and value() functions (inherited from xml_base). + //! Note that after parse, both name and value of attribute will point to interior of source text used for parsing. + //! Thus, this text must persist in memory for the lifetime of attribute. + //! \param Ch Character type to use. + template + class xml_attribute: public xml_base + { + + friend class xml_node; + + public: + + /////////////////////////////////////////////////////////////////////////// + // Construction & destruction + + //! Constructs an empty attribute with the specified type. + //! Consider using memory_pool of appropriate xml_document if allocating attributes manually. + xml_attribute() + { + } + + /////////////////////////////////////////////////////////////////////////// + // Related nodes access + + //! Gets document of which attribute is a child. + //! \return Pointer to document that contains this attribute, or 0 if there is no parent document. + xml_document *document() const + { + if (xml_node *node = this->parent()) + { + while (node->parent()) + node = node->parent(); + return node->type() == node_document ? static_cast *>(node) : 0; + } + else + return 0; + } + + //! Gets previous attribute, optionally matching attribute name. + //! \param name Name of attribute to find, or 0 to return previous attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero + //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string + //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters + //! \return Pointer to found attribute, or 0 if not found. + xml_attribute *previous_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const + { + if (name) + { + if (name_size == 0) + name_size = internal::measure(name); + for (xml_attribute *attribute = m_prev_attribute; attribute; attribute = attribute->m_prev_attribute) + if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive)) + return attribute; + return 0; + } + else + return this->m_parent ? m_prev_attribute : 0; + } + + //! Gets next attribute, optionally matching attribute name. + //! \param name Name of attribute to find, or 0 to return next attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero + //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string + //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters + //! \return Pointer to found attribute, or 0 if not found. + xml_attribute *next_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const + { + if (name) + { + if (name_size == 0) + name_size = internal::measure(name); + for (xml_attribute *attribute = m_next_attribute; attribute; attribute = attribute->m_next_attribute) + if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive)) + return attribute; + return 0; + } + else + return this->m_parent ? m_next_attribute : 0; + } + + private: + + xml_attribute *m_prev_attribute; // Pointer to previous sibling of attribute, or 0 if none; only valid if parent is non-zero + xml_attribute *m_next_attribute; // Pointer to next sibling of attribute, or 0 if none; only valid if parent is non-zero + + }; + + /////////////////////////////////////////////////////////////////////////// + // XML node + + //! Class representing a node of XML document. + //! Each node may have associated name and value strings, which are available through name() and value() functions. + //! Interpretation of name and value depends on type of the node. + //! Type of node can be determined by using type() function. + //!

+ //! Note that after parse, both name and value of node, if any, will point interior of source text used for parsing. + //! Thus, this text must persist in the memory for the lifetime of node. + //! \param Ch Character type to use. + template + class xml_node: public xml_base + { + + public: + + /////////////////////////////////////////////////////////////////////////// + // Construction & destruction + + //! Constructs an empty node with the specified type. + //! Consider using memory_pool of appropriate document to allocate nodes manually. + //! \param type Type of node to construct. + xml_node(node_type type) + : m_type(type) + , m_first_node(0) + , m_first_attribute(0) + { + } + + /////////////////////////////////////////////////////////////////////////// + // Node data access + + //! Gets type of node. + //! \return Type of node. + node_type type() const + { + return m_type; + } + + /////////////////////////////////////////////////////////////////////////// + // Related nodes access + + //! Gets document of which node is a child. + //! \return Pointer to document that contains this node, or 0 if there is no parent document. + xml_document *document() const + { + xml_node *node = const_cast *>(this); + while (node->parent()) + node = node->parent(); + return node->type() == node_document ? static_cast *>(node) : 0; + } + + //! Gets first child node, optionally matching node name. + //! \param name Name of child to find, or 0 to return first child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero + //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string + //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters + //! \return Pointer to found child, or 0 if not found. + xml_node *first_node(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const + { + if (name) + { + if (name_size == 0) + name_size = internal::measure(name); + for (xml_node *child = m_first_node; child; child = child->next_sibling()) + if (internal::compare(child->name(), child->name_size(), name, name_size, case_sensitive)) + return child; + return 0; + } + else + return m_first_node; + } + + //! Gets last child node, optionally matching node name. + //! Behaviour is undefined if node has no children. + //! Use first_node() to test if node has children. + //! \param name Name of child to find, or 0 to return last child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero + //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string + //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters + //! \return Pointer to found child, or 0 if not found. + xml_node *last_node(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const + { + assert(m_first_node); // Cannot query for last child if node has no children + if (name) + { + if (name_size == 0) + name_size = internal::measure(name); + for (xml_node *child = m_last_node; child; child = child->previous_sibling()) + if (internal::compare(child->name(), child->name_size(), name, name_size, case_sensitive)) + return child; + return 0; + } + else + return m_last_node; + } + + //! Gets previous sibling node, optionally matching node name. + //! Behaviour is undefined if node has no parent. + //! Use parent() to test if node has a parent. + //! \param name Name of sibling to find, or 0 to return previous sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero + //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string + //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters + //! \return Pointer to found sibling, or 0 if not found. + xml_node *previous_sibling(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const + { + assert(this->m_parent); // Cannot query for siblings if node has no parent + if (name) + { + if (name_size == 0) + name_size = internal::measure(name); + for (xml_node *sibling = m_prev_sibling; sibling; sibling = sibling->m_prev_sibling) + if (internal::compare(sibling->name(), sibling->name_size(), name, name_size, case_sensitive)) + return sibling; + return 0; + } + else + return m_prev_sibling; + } + + //! Gets next sibling node, optionally matching node name. + //! Behaviour is undefined if node has no parent. + //! Use parent() to test if node has a parent. + //! \param name Name of sibling to find, or 0 to return next sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero + //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string + //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters + //! \return Pointer to found sibling, or 0 if not found. + xml_node *next_sibling(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const + { + assert(this->m_parent); // Cannot query for siblings if node has no parent + if (name) + { + if (name_size == 0) + name_size = internal::measure(name); + for (xml_node *sibling = m_next_sibling; sibling; sibling = sibling->m_next_sibling) + if (internal::compare(sibling->name(), sibling->name_size(), name, name_size, case_sensitive)) + return sibling; + return 0; + } + else + return m_next_sibling; + } + + //! Gets first attribute of node, optionally matching attribute name. + //! \param name Name of attribute to find, or 0 to return first attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero + //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string + //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters + //! \return Pointer to found attribute, or 0 if not found. + xml_attribute *first_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const + { + if (name) + { + if (name_size == 0) + name_size = internal::measure(name); + for (xml_attribute *attribute = m_first_attribute; attribute; attribute = attribute->m_next_attribute) + if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive)) + return attribute; + return 0; + } + else + return m_first_attribute; + } + + //! Gets last attribute of node, optionally matching attribute name. + //! \param name Name of attribute to find, or 0 to return last attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero + //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string + //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters + //! \return Pointer to found attribute, or 0 if not found. + xml_attribute *last_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const + { + if (name) + { + if (name_size == 0) + name_size = internal::measure(name); + for (xml_attribute *attribute = m_last_attribute; attribute; attribute = attribute->m_prev_attribute) + if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive)) + return attribute; + return 0; + } + else + return m_first_attribute ? m_last_attribute : 0; + } + + /////////////////////////////////////////////////////////////////////////// + // Node modification + + //! Sets type of node. + //! \param type Type of node to set. + void type(node_type type) + { + m_type = type; + } + + /////////////////////////////////////////////////////////////////////////// + // Node manipulation + + //! Prepends a new child node. + //! The prepended child becomes the first child, and all existing children are moved one position back. + //! \param child Node to prepend. + void prepend_node(xml_node *child) + { + assert(child && !child->parent() && child->type() != node_document); + if (first_node()) + { + child->m_next_sibling = m_first_node; + m_first_node->m_prev_sibling = child; + } + else + { + child->m_next_sibling = 0; + m_last_node = child; + } + m_first_node = child; + child->m_parent = this; + child->m_prev_sibling = 0; + } + + //! Appends a new child node. + //! The appended child becomes the last child. + //! \param child Node to append. + void append_node(xml_node *child) + { + assert(child && !child->parent() && child->type() != node_document); + if (first_node()) + { + child->m_prev_sibling = m_last_node; + m_last_node->m_next_sibling = child; + } + else + { + child->m_prev_sibling = 0; + m_first_node = child; + } + m_last_node = child; + child->m_parent = this; + child->m_next_sibling = 0; + } + + //! Inserts a new child node at specified place inside the node. + //! All children after and including the specified node are moved one position back. + //! \param where Place where to insert the child, or 0 to insert at the back. + //! \param child Node to insert. + void insert_node(xml_node *where, xml_node *child) + { + assert(!where || where->parent() == this); + assert(child && !child->parent() && child->type() != node_document); + if (where == m_first_node) + prepend_node(child); + else if (where == 0) + append_node(child); + else + { + child->m_prev_sibling = where->m_prev_sibling; + child->m_next_sibling = where; + where->m_prev_sibling->m_next_sibling = child; + where->m_prev_sibling = child; + child->m_parent = this; + } + } + + //! Removes first child node. + //! If node has no children, behaviour is undefined. + //! Use first_node() to test if node has children. + void remove_first_node() + { + assert(first_node()); + xml_node *child = m_first_node; + m_first_node = child->m_next_sibling; + if (child->m_next_sibling) + child->m_next_sibling->m_prev_sibling = 0; + else + m_last_node = 0; + child->m_parent = 0; + } + + //! Removes last child of the node. + //! If node has no children, behaviour is undefined. + //! Use first_node() to test if node has children. + void remove_last_node() + { + assert(first_node()); + xml_node *child = m_last_node; + if (child->m_prev_sibling) + { + m_last_node = child->m_prev_sibling; + child->m_prev_sibling->m_next_sibling = 0; + } + else + m_first_node = 0; + child->m_parent = 0; + } + + //! Removes specified child from the node + // \param where Pointer to child to be removed. + void remove_node(xml_node *where) + { + assert(where && where->parent() == this); + assert(first_node()); + if (where == m_first_node) + remove_first_node(); + else if (where == m_last_node) + remove_last_node(); + else + { + where->m_prev_sibling->m_next_sibling = where->m_next_sibling; + where->m_next_sibling->m_prev_sibling = where->m_prev_sibling; + where->m_parent = 0; + } + } + + //! Removes all child nodes (but not attributes). + void remove_all_nodes() + { + for (xml_node *node = first_node(); node; node = node->m_next_sibling) + node->m_parent = 0; + m_first_node = 0; + } + + //! Prepends a new attribute to the node. + //! \param attribute Attribute to prepend. + void prepend_attribute(xml_attribute *attribute) + { + assert(attribute && !attribute->parent()); + if (first_attribute()) + { + attribute->m_next_attribute = m_first_attribute; + m_first_attribute->m_prev_attribute = attribute; + } + else + { + attribute->m_next_attribute = 0; + m_last_attribute = attribute; + } + m_first_attribute = attribute; + attribute->m_parent = this; + attribute->m_prev_attribute = 0; + } + + //! Appends a new attribute to the node. + //! \param attribute Attribute to append. + void append_attribute(xml_attribute *attribute) + { + assert(attribute && !attribute->parent()); + if (first_attribute()) + { + attribute->m_prev_attribute = m_last_attribute; + m_last_attribute->m_next_attribute = attribute; + } + else + { + attribute->m_prev_attribute = 0; + m_first_attribute = attribute; + } + m_last_attribute = attribute; + attribute->m_parent = this; + attribute->m_next_attribute = 0; + } + + //! Inserts a new attribute at specified place inside the node. + //! All attributes after and including the specified attribute are moved one position back. + //! \param where Place where to insert the attribute, or 0 to insert at the back. + //! \param attribute Attribute to insert. + void insert_attribute(xml_attribute *where, xml_attribute *attribute) + { + assert(!where || where->parent() == this); + assert(attribute && !attribute->parent()); + if (where == m_first_attribute) + prepend_attribute(attribute); + else if (where == 0) + append_attribute(attribute); + else + { + attribute->m_prev_attribute = where->m_prev_attribute; + attribute->m_next_attribute = where; + where->m_prev_attribute->m_next_attribute = attribute; + where->m_prev_attribute = attribute; + attribute->m_parent = this; + } + } + + //! Removes first attribute of the node. + //! If node has no attributes, behaviour is undefined. + //! Use first_attribute() to test if node has attributes. + void remove_first_attribute() + { + assert(first_attribute()); + xml_attribute *attribute = m_first_attribute; + if (attribute->m_next_attribute) + { + attribute->m_next_attribute->m_prev_attribute = 0; + } + else + m_last_attribute = 0; + attribute->m_parent = 0; + m_first_attribute = attribute->m_next_attribute; + } + + //! Removes last attribute of the node. + //! If node has no attributes, behaviour is undefined. + //! Use first_attribute() to test if node has attributes. + void remove_last_attribute() + { + assert(first_attribute()); + xml_attribute *attribute = m_last_attribute; + if (attribute->m_prev_attribute) + { + attribute->m_prev_attribute->m_next_attribute = 0; + m_last_attribute = attribute->m_prev_attribute; + } + else + m_first_attribute = 0; + attribute->m_parent = 0; + } + + //! Removes specified attribute from node. + //! \param where Pointer to attribute to be removed. + void remove_attribute(xml_attribute *where) + { + assert(first_attribute() && where->parent() == this); + if (where == m_first_attribute) + remove_first_attribute(); + else if (where == m_last_attribute) + remove_last_attribute(); + else + { + where->m_prev_attribute->m_next_attribute = where->m_next_attribute; + where->m_next_attribute->m_prev_attribute = where->m_prev_attribute; + where->m_parent = 0; + } + } + + //! Removes all attributes of node. + void remove_all_attributes() + { + for (xml_attribute *attribute = first_attribute(); attribute; attribute = attribute->m_next_attribute) + attribute->m_parent = 0; + m_first_attribute = 0; + } + + private: + + /////////////////////////////////////////////////////////////////////////// + // Restrictions + + // No copying + xml_node(const xml_node &); + void operator =(const xml_node &); + + /////////////////////////////////////////////////////////////////////////// + // Data members + + // Note that some of the pointers below have UNDEFINED values if certain other pointers are 0. + // This is required for maximum performance, as it allows the parser to omit initialization of + // unneded/redundant values. + // + // The rules are as follows: + // 1. first_node and first_attribute contain valid pointers, or 0 if node has no children/attributes respectively + // 2. last_node and last_attribute are valid only if node has at least one child/attribute respectively, otherwise they contain garbage + // 3. prev_sibling and next_sibling are valid only if node has a parent, otherwise they contain garbage + + node_type m_type; // Type of node; always valid + xml_node *m_first_node; // Pointer to first child node, or 0 if none; always valid + xml_node *m_last_node; // Pointer to last child node, or 0 if none; this value is only valid if m_first_node is non-zero + xml_attribute *m_first_attribute; // Pointer to first attribute of node, or 0 if none; always valid + xml_attribute *m_last_attribute; // Pointer to last attribute of node, or 0 if none; this value is only valid if m_first_attribute is non-zero + xml_node *m_prev_sibling; // Pointer to previous sibling of node, or 0 if none; this value is only valid if m_parent is non-zero + xml_node *m_next_sibling; // Pointer to next sibling of node, or 0 if none; this value is only valid if m_parent is non-zero + + }; + + /////////////////////////////////////////////////////////////////////////// + // XML document + + //! This class represents root of the DOM hierarchy. + //! It is also an xml_node and a memory_pool through public inheritance. + //! Use parse() function to build a DOM tree from a zero-terminated XML text string. + //! parse() function allocates memory for nodes and attributes by using functions of xml_document, + //! which are inherited from memory_pool. + //! To access root node of the document, use the document itself, as if it was an xml_node. + //! \param Ch Character type to use. + template + class xml_document: public xml_node, public memory_pool + { + + public: + + //! Constructs empty XML document + xml_document() + : xml_node(node_document) + { + } + + //! Parses zero-terminated XML string according to given flags. + //! Passed string will be modified by the parser, unless rapidxml::parse_non_destructive flag is used. + //! The string must persist for the lifetime of the document. + //! In case of error, rapidxml::parse_error exception will be thrown. + //!

+ //! If you want to parse contents of a file, you must first load the file into the memory, and pass pointer to its beginning. + //! Make sure that data is zero-terminated. + //!

+ //! Document can be parsed into multiple times. + //! Each new call to parse removes previous nodes and attributes (if any), but does not clear memory pool. + //! \param text XML data to parse; pointer is non-const to denote fact that this data may be modified by the parser. + template + void parse(Ch *text) + { + assert(text); + + // Remove current contents + this->remove_all_nodes(); + this->remove_all_attributes(); + + // Parse BOM, if any + parse_bom(text); + + // Parse children + while (1) + { + // Skip whitespace before node + skip(text); + if (*text == 0) + break; + + // Parse and append new child + if (*text == Ch('<')) + { + ++text; // Skip '<' + if (xml_node *node = parse_node(text)) + this->append_node(node); + } + else + RAPIDXML_PARSE_ERROR("expected <", text); + } + + } + + //! Clears the document by deleting all nodes and clearing the memory pool. + //! All nodes owned by document pool are destroyed. + void clear() + { + this->remove_all_nodes(); + this->remove_all_attributes(); + memory_pool::clear(); + } + + private: + + /////////////////////////////////////////////////////////////////////// + // Internal character utility functions + + // Detect whitespace character + struct whitespace_pred + { + static unsigned char test(Ch ch) + { + return internal::lookup_tables<0>::lookup_whitespace[static_cast(ch)]; + } + }; + + // Detect node name character + struct node_name_pred + { + static unsigned char test(Ch ch) + { + return internal::lookup_tables<0>::lookup_node_name[static_cast(ch)]; + } + }; + + // Detect attribute name character + struct attribute_name_pred + { + static unsigned char test(Ch ch) + { + return internal::lookup_tables<0>::lookup_attribute_name[static_cast(ch)]; + } + }; + + // Detect text character (PCDATA) + struct text_pred + { + static unsigned char test(Ch ch) + { + return internal::lookup_tables<0>::lookup_text[static_cast(ch)]; + } + }; + + // Detect text character (PCDATA) that does not require processing + struct text_pure_no_ws_pred + { + static unsigned char test(Ch ch) + { + return internal::lookup_tables<0>::lookup_text_pure_no_ws[static_cast(ch)]; + } + }; + + // Detect text character (PCDATA) that does not require processing + struct text_pure_with_ws_pred + { + static unsigned char test(Ch ch) + { + return internal::lookup_tables<0>::lookup_text_pure_with_ws[static_cast(ch)]; + } + }; + + // Detect attribute value character + template + struct attribute_value_pred + { + static unsigned char test(Ch ch) + { + if (Quote == Ch('\'')) + return internal::lookup_tables<0>::lookup_attribute_data_1[static_cast(ch)]; + if (Quote == Ch('\"')) + return internal::lookup_tables<0>::lookup_attribute_data_2[static_cast(ch)]; + return 0; // Should never be executed, to avoid warnings on Comeau + } + }; + + // Detect attribute value character + template + struct attribute_value_pure_pred + { + static unsigned char test(Ch ch) + { + if (Quote == Ch('\'')) + return internal::lookup_tables<0>::lookup_attribute_data_1_pure[static_cast(ch)]; + if (Quote == Ch('\"')) + return internal::lookup_tables<0>::lookup_attribute_data_2_pure[static_cast(ch)]; + return 0; // Should never be executed, to avoid warnings on Comeau + } + }; + + // Insert coded character, using UTF8 or 8-bit ASCII + template + static void insert_coded_character(Ch *&text, unsigned long code) + { + if (Flags & parse_no_utf8) + { + // Insert 8-bit ASCII character + // Todo: possibly verify that code is less than 256 and use replacement char otherwise? + text[0] = static_cast(code); + text += 1; + } + else + { + // Insert UTF8 sequence + if (code < 0x80) // 1 byte sequence + { + text[0] = static_cast(code); + text += 1; + } + else if (code < 0x800) // 2 byte sequence + { + text[1] = static_cast((code | 0x80) & 0xBF); code >>= 6; + text[0] = static_cast(code | 0xC0); + text += 2; + } + else if (code < 0x10000) // 3 byte sequence + { + text[2] = static_cast((code | 0x80) & 0xBF); code >>= 6; + text[1] = static_cast((code | 0x80) & 0xBF); code >>= 6; + text[0] = static_cast(code | 0xE0); + text += 3; + } + else if (code < 0x110000) // 4 byte sequence + { + text[3] = static_cast((code | 0x80) & 0xBF); code >>= 6; + text[2] = static_cast((code | 0x80) & 0xBF); code >>= 6; + text[1] = static_cast((code | 0x80) & 0xBF); code >>= 6; + text[0] = static_cast(code | 0xF0); + text += 4; + } + else // Invalid, only codes up to 0x10FFFF are allowed in Unicode + { + RAPIDXML_PARSE_ERROR("invalid numeric character entity", text); + } + } + } + + // Skip characters until predicate evaluates to true + template + static void skip(Ch *&text) + { + Ch *tmp = text; + while (StopPred::test(*tmp)) + ++tmp; + text = tmp; + } + + // Skip characters until predicate evaluates to true while doing the following: + // - replacing XML character entity references with proper characters (' & " < > &#...;) + // - condensing whitespace sequences to single space character + template + static Ch *skip_and_expand_character_refs(Ch *&text) + { + // If entity translation, whitespace condense and whitespace trimming is disabled, use plain skip + if (Flags & parse_no_entity_translation && + !(Flags & parse_normalize_whitespace) && + !(Flags & parse_trim_whitespace)) + { + skip(text); + return text; + } + + // Use simple skip until first modification is detected + skip(text); + + // Use translation skip + Ch *src = text; + Ch *dest = src; + while (StopPred::test(*src)) + { + // If entity translation is enabled + if (!(Flags & parse_no_entity_translation)) + { + // Test if replacement is needed + if (src[0] == Ch('&')) + { + switch (src[1]) + { + + // & ' + case Ch('a'): + if (src[2] == Ch('m') && src[3] == Ch('p') && src[4] == Ch(';')) + { + *dest = Ch('&'); + ++dest; + src += 5; + continue; + } + if (src[2] == Ch('p') && src[3] == Ch('o') && src[4] == Ch('s') && src[5] == Ch(';')) + { + *dest = Ch('\''); + ++dest; + src += 6; + continue; + } + break; + + // " + case Ch('q'): + if (src[2] == Ch('u') && src[3] == Ch('o') && src[4] == Ch('t') && src[5] == Ch(';')) + { + *dest = Ch('"'); + ++dest; + src += 6; + continue; + } + break; + + // > + case Ch('g'): + if (src[2] == Ch('t') && src[3] == Ch(';')) + { + *dest = Ch('>'); + ++dest; + src += 4; + continue; + } + break; + + // < + case Ch('l'): + if (src[2] == Ch('t') && src[3] == Ch(';')) + { + *dest = Ch('<'); + ++dest; + src += 4; + continue; + } + break; + + // &#...; - assumes ASCII + case Ch('#'): + if (src[2] == Ch('x')) + { + unsigned long code = 0; + src += 3; // Skip &#x + while (1) + { + unsigned char digit = internal::lookup_tables<0>::lookup_digits[static_cast(*src)]; + if (digit == 0xFF) + break; + code = code * 16 + digit; + ++src; + } + insert_coded_character(dest, code); // Put character in output + } + else + { + unsigned long code = 0; + src += 2; // Skip &# + while (1) + { + unsigned char digit = internal::lookup_tables<0>::lookup_digits[static_cast(*src)]; + if (digit == 0xFF) + break; + code = code * 10 + digit; + ++src; + } + insert_coded_character(dest, code); // Put character in output + } + if (*src == Ch(';')) + ++src; + else + RAPIDXML_PARSE_ERROR("expected ;", src); + continue; + + // Something else + default: + // Ignore, just copy '&' verbatim + break; + + } + } + } + + // If whitespace condensing is enabled + if (Flags & parse_normalize_whitespace) + { + // Test if condensing is needed + if (whitespace_pred::test(*src)) + { + *dest = Ch(' '); ++dest; // Put single space in dest + ++src; // Skip first whitespace char + // Skip remaining whitespace chars + while (whitespace_pred::test(*src)) + ++src; + continue; + } + } + + // No replacement, only copy character + *dest++ = *src++; + + } + + // Return new end + text = src; + return dest; + + } + + /////////////////////////////////////////////////////////////////////// + // Internal parsing functions + + // Parse BOM, if any + template + void parse_bom(Ch *&text) + { + // UTF-8? + if (static_cast(text[0]) == 0xEF && + static_cast(text[1]) == 0xBB && + static_cast(text[2]) == 0xBF) + { + text += 3; // Skup utf-8 bom + } + } + + // Parse XML declaration ( + xml_node *parse_xml_declaration(Ch *&text) + { + // If parsing of declaration is disabled + if (!(Flags & parse_declaration_node)) + { + // Skip until end of declaration + while (text[0] != Ch('?') || text[1] != Ch('>')) + { + if (!text[0]) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + text += 2; // Skip '?>' + return 0; + } + + // Create declaration + xml_node *declaration = this->allocate_node(node_declaration); + + // Skip whitespace before attributes or ?> + skip(text); + + // Parse declaration attributes + parse_node_attributes(text, declaration); + + // Skip ?> + if (text[0] != Ch('?') || text[1] != Ch('>')) + RAPIDXML_PARSE_ERROR("expected ?>", text); + text += 2; + + return declaration; + } + + // Parse XML comment (' + return 0; // Do not produce comment node + } + + // Remember value start + Ch *value = text; + + // Skip until end of comment + while (text[0] != Ch('-') || text[1] != Ch('-') || text[2] != Ch('>')) + { + if (!text[0]) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + + // Create comment node + xml_node *comment = this->allocate_node(node_comment); + comment->value(value, text - value); + + // Place zero terminator after comment value + if (!(Flags & parse_no_string_terminators)) + *text = Ch('\0'); + + text += 3; // Skip '-->' + return comment; + } + + // Parse DOCTYPE + template + xml_node *parse_doctype(Ch *&text) + { + // Remember value start + Ch *value = text; + + // Skip to > + while (*text != Ch('>')) + { + // Determine character type + switch (*text) + { + + // If '[' encountered, scan for matching ending ']' using naive algorithm with depth + // This works for all W3C test files except for 2 most wicked + case Ch('['): + { + ++text; // Skip '[' + int depth = 1; + while (depth > 0) + { + switch (*text) + { + case Ch('['): ++depth; break; + case Ch(']'): --depth; break; + case 0: RAPIDXML_PARSE_ERROR("unexpected end of data", text); + } + ++text; + } + break; + } + + // Error on end of text + case Ch('\0'): + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + + // Other character, skip it + default: + ++text; + + } + } + + // If DOCTYPE nodes enabled + if (Flags & parse_doctype_node) + { + // Create a new doctype node + xml_node *doctype = this->allocate_node(node_doctype); + doctype->value(value, text - value); + + // Place zero terminator after value + if (!(Flags & parse_no_string_terminators)) + *text = Ch('\0'); + + text += 1; // skip '>' + return doctype; + } + else + { + text += 1; // skip '>' + return 0; + } + + } + + // Parse PI + template + xml_node *parse_pi(Ch *&text) + { + // If creation of PI nodes is enabled + if (Flags & parse_pi_nodes) + { + // Create pi node + xml_node *pi = this->allocate_node(node_pi); + + // Extract PI target name + Ch *name = text; + skip(text); + if (text == name) + RAPIDXML_PARSE_ERROR("expected PI target", text); + pi->name(name, text - name); + + // Skip whitespace between pi target and pi + skip(text); + + // Remember start of pi + Ch *value = text; + + // Skip to '?>' + while (text[0] != Ch('?') || text[1] != Ch('>')) + { + if (*text == Ch('\0')) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + + // Set pi value (verbatim, no entity expansion or whitespace normalization) + pi->value(value, text - value); + + // Place zero terminator after name and value + if (!(Flags & parse_no_string_terminators)) + { + pi->name()[pi->name_size()] = Ch('\0'); + pi->value()[pi->value_size()] = Ch('\0'); + } + + text += 2; // Skip '?>' + return pi; + } + else + { + // Skip to '?>' + while (text[0] != Ch('?') || text[1] != Ch('>')) + { + if (*text == Ch('\0')) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + text += 2; // Skip '?>' + return 0; + } + } + + // Parse and append data + // Return character that ends data. + // This is necessary because this character might have been overwritten by a terminating 0 + template + Ch parse_and_append_data(xml_node *node, Ch *&text, Ch *contents_start) + { + // Backup to contents start if whitespace trimming is disabled + if (!(Flags & parse_trim_whitespace)) + text = contents_start; + + // Skip until end of data + Ch *value = text, *end; + if (Flags & parse_normalize_whitespace) + end = skip_and_expand_character_refs(text); + else + end = skip_and_expand_character_refs(text); + + // Trim trailing whitespace if flag is set; leading was already trimmed by whitespace skip after > + if (Flags & parse_trim_whitespace) + { + if (Flags & parse_normalize_whitespace) + { + // Whitespace is already condensed to single space characters by skipping function, so just trim 1 char off the end + if (*(end - 1) == Ch(' ')) + --end; + } + else + { + // Backup until non-whitespace character is found + while (whitespace_pred::test(*(end - 1))) + --end; + } + } + + // If characters are still left between end and value (this test is only necessary if normalization is enabled) + // Create new data node + if (!(Flags & parse_no_data_nodes)) + { + xml_node *data = this->allocate_node(node_data); + data->value(value, end - value); + node->append_node(data); + } + + // Add data to parent node if no data exists yet + if (!(Flags & parse_no_element_values)) + if (*node->value() == Ch('\0')) + node->value(value, end - value); + + // Place zero terminator after value + if (!(Flags & parse_no_string_terminators)) + { + Ch ch = *text; + *end = Ch('\0'); + return ch; // Return character that ends data; this is required because zero terminator overwritten it + } + + // Return character that ends data + return *text; + } + + // Parse CDATA + template + xml_node *parse_cdata(Ch *&text) + { + // If CDATA is disabled + if (Flags & parse_no_data_nodes) + { + // Skip until end of cdata + while (text[0] != Ch(']') || text[1] != Ch(']') || text[2] != Ch('>')) + { + if (!text[0]) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + text += 3; // Skip ]]> + return 0; // Do not produce CDATA node + } + + // Skip until end of cdata + Ch *value = text; + while (text[0] != Ch(']') || text[1] != Ch(']') || text[2] != Ch('>')) + { + if (!text[0]) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + + // Create new cdata node + xml_node *cdata = this->allocate_node(node_cdata); + cdata->value(value, text - value); + + // Place zero terminator after value + if (!(Flags & parse_no_string_terminators)) + *text = Ch('\0'); + + text += 3; // Skip ]]> + return cdata; + } + + // Parse element node + template + xml_node *parse_element(Ch *&text) + { + // Create element node + xml_node *element = this->allocate_node(node_element); + + // Extract element name + Ch *name = text; + skip(text); + if (text == name) + RAPIDXML_PARSE_ERROR("expected element name", text); + element->name(name, text - name); + + // Skip whitespace between element name and attributes or > + skip(text); + + // Parse attributes, if any + parse_node_attributes(text, element); + + // Determine ending type + if (*text == Ch('>')) + { + ++text; + parse_node_contents(text, element); + } + else if (*text == Ch('/')) + { + ++text; + if (*text != Ch('>')) + RAPIDXML_PARSE_ERROR("expected >", text); + ++text; + } + else + RAPIDXML_PARSE_ERROR("expected >", text); + + // Place zero terminator after name + if (!(Flags & parse_no_string_terminators)) + element->name()[element->name_size()] = Ch('\0'); + + // Return parsed element + return element; + } + + // Determine node type, and parse it + template + xml_node *parse_node(Ch *&text) + { + // Parse proper node type + switch (text[0]) + { + + // <... + default: + // Parse and append element node + return parse_element(text); + + // (text); + } + else + { + // Parse PI + return parse_pi(text); + } + + // (text); + } + break; + + // (text); + } + break; + + // (text); + } + + } // switch + + // Attempt to skip other, unrecognized node types starting with ')) + { + if (*text == 0) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + ++text; // Skip '>' + return 0; // No node recognized + + } + } + + // Parse contents of the node - children, data etc. + template + void parse_node_contents(Ch *&text, xml_node *node) + { + // For all children and text + while (1) + { + // Skip whitespace between > and node contents + Ch *contents_start = text; // Store start of node contents before whitespace is skipped + skip(text); + Ch next_char = *text; + + // After data nodes, instead of continuing the loop, control jumps here. + // This is because zero termination inside parse_and_append_data() function + // would wreak havoc with the above code. + // Also, skipping whitespace after data nodes is unnecessary. + after_data_node: + + // Determine what comes next: node closing, child node, data node, or 0? + switch (next_char) + { + + // Node closing or child node + case Ch('<'): + if (text[1] == Ch('/')) + { + // Node closing + text += 2; // Skip '(text); + if (!internal::compare(node->name(), node->name_size(), closing_name, text - closing_name, true)) + RAPIDXML_PARSE_ERROR("invalid closing tag name", text); + } + else + { + // No validation, just skip name + skip(text); + } + // Skip remaining whitespace after node name + skip(text); + if (*text != Ch('>')) + RAPIDXML_PARSE_ERROR("expected >", text); + ++text; // Skip '>' + return; // Node closed, finished parsing contents + } + else + { + // Child node + ++text; // Skip '<' + if (xml_node *child = parse_node(text)) + node->append_node(child); + } + break; + + // End of data - error + case Ch('\0'): + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + + // Data node + default: + next_char = parse_and_append_data(node, text, contents_start); + goto after_data_node; // Bypass regular processing after data nodes + + } + } + } + + // Parse XML attributes of the node + template + void parse_node_attributes(Ch *&text, xml_node *node) + { + // For all attributes + while (attribute_name_pred::test(*text)) + { + // Extract attribute name + Ch *name = text; + ++text; // Skip first character of attribute name + skip(text); + if (text == name) + RAPIDXML_PARSE_ERROR("expected attribute name", name); + + // Create new attribute + xml_attribute *attribute = this->allocate_attribute(); + attribute->name(name, text - name); + node->append_attribute(attribute); + + // Skip whitespace after attribute name + skip(text); + + // Skip = + if (*text != Ch('=')) + RAPIDXML_PARSE_ERROR("expected =", text); + ++text; + + // Add terminating zero after name + if (!(Flags & parse_no_string_terminators)) + attribute->name()[attribute->name_size()] = 0; + + // Skip whitespace after = + skip(text); + + // Skip quote and remember if it was ' or " + Ch quote = *text; + if (quote != Ch('\'') && quote != Ch('"')) + RAPIDXML_PARSE_ERROR("expected ' or \"", text); + ++text; + + // Extract attribute value and expand char refs in it + Ch *value = text, *end; + const int AttFlags = Flags & ~parse_normalize_whitespace; // No whitespace normalization in attributes + if (quote == Ch('\'')) + end = skip_and_expand_character_refs, attribute_value_pure_pred, AttFlags>(text); + else + end = skip_and_expand_character_refs, attribute_value_pure_pred, AttFlags>(text); + + // Set attribute value + attribute->value(value, end - value); + + // Make sure that end quote is present + if (*text != quote) + RAPIDXML_PARSE_ERROR("expected ' or \"", text); + ++text; // Skip quote + + // Add terminating zero after value + if (!(Flags & parse_no_string_terminators)) + attribute->value()[attribute->value_size()] = 0; + + // Skip whitespace after attribute value + skip(text); + } + } + + }; + + //! \cond internal + namespace internal + { + + // Whitespace (space \n \r \t) + template + const unsigned char lookup_tables::lookup_whitespace[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 5 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F + }; + + // Node name (anything but space \n \r \t / > ? \0) + template + const unsigned char lookup_tables::lookup_node_name[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Text (i.e. PCDATA) (anything but < \0) + template + const unsigned char lookup_tables::lookup_text[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Text (i.e. PCDATA) that does not require processing when ws normalization is disabled + // (anything but < \0 &) + template + const unsigned char lookup_tables::lookup_text_pure_no_ws[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Text (i.e. PCDATA) that does not require processing when ws normalizationis is enabled + // (anything but < \0 & space \n \r \t) + template + const unsigned char lookup_tables::lookup_text_pure_with_ws[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Attribute name (anything but space \n \r \t / < > = ? ! \0) + template + const unsigned char lookup_tables::lookup_attribute_name[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Attribute data with single quote (anything but ' \0) + template + const unsigned char lookup_tables::lookup_attribute_data_1[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Attribute data with single quote that does not require processing (anything but ' \0 &) + template + const unsigned char lookup_tables::lookup_attribute_data_1_pure[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Attribute data with double quote (anything but " \0) + template + const unsigned char lookup_tables::lookup_attribute_data_2[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Attribute data with double quote that does not require processing (anything but " \0 &) + template + const unsigned char lookup_tables::lookup_attribute_data_2_pure[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F + }; + + // Digits (dec and hex, 255 denotes end of numeric character reference) + template + const unsigned char lookup_tables::lookup_digits[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 0 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 1 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 2 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,255,255,255,255,255,255, // 3 + 255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255, // 4 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 5 + 255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255, // 6 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 7 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 8 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 9 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // A + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // B + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // C + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // D + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // E + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255 // F + }; + + // Upper case conversion + template + const unsigned char lookup_tables::lookup_upcase[256] = + { + // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A B C D E F + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // 0 + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, // 1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // 2 + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // 3 + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, // 4 + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, // 5 + 96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, // 6 + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 123,124,125,126,127, // 7 + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, // 8 + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, // 9 + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, // A + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, // B + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, // C + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, // D + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, // E + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 // F + }; + } + //! \endcond + +} + +// Undefine internal macros +#undef RAPIDXML_PARSE_ERROR + +// On MSVC, restore warnings state +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +#endif diff --git a/util/xpack/rapidxml/rapidxml_iterators.hpp b/util/xpack/rapidxml/rapidxml_iterators.hpp new file mode 100755 index 0000000..85c5894 --- /dev/null +++ b/util/xpack/rapidxml/rapidxml_iterators.hpp @@ -0,0 +1,174 @@ +#ifndef RAPIDXML_ITERATORS_HPP_INCLUDED +#define RAPIDXML_ITERATORS_HPP_INCLUDED + +// Copyright (C) 2006, 2009 Marcin Kalicinski +// Version 1.13 +// Revision $DateTime: 2009/05/13 01:46:17 $ +//! \file rapidxml_iterators.hpp This file contains rapidxml iterators + +#include "rapidxml.hpp" + +namespace rapidxml +{ + + //! Iterator of child nodes of xml_node + template + class node_iterator + { + + public: + + typedef typename xml_node value_type; + typedef typename xml_node &reference; + typedef typename xml_node *pointer; + typedef std::ptrdiff_t difference_type; + typedef std::bidirectional_iterator_tag iterator_category; + + node_iterator() + : m_node(0) + { + } + + node_iterator(xml_node *node) + : m_node(node->first_node()) + { + } + + reference operator *() const + { + assert(m_node); + return *m_node; + } + + pointer operator->() const + { + assert(m_node); + return m_node; + } + + node_iterator& operator++() + { + assert(m_node); + m_node = m_node->next_sibling(); + return *this; + } + + node_iterator operator++(int) + { + node_iterator tmp = *this; + ++this; + return tmp; + } + + node_iterator& operator--() + { + assert(m_node && m_node->previous_sibling()); + m_node = m_node->previous_sibling(); + return *this; + } + + node_iterator operator--(int) + { + node_iterator tmp = *this; + ++this; + return tmp; + } + + bool operator ==(const node_iterator &rhs) + { + return m_node == rhs.m_node; + } + + bool operator !=(const node_iterator &rhs) + { + return m_node != rhs.m_node; + } + + private: + + xml_node *m_node; + + }; + + //! Iterator of child attributes of xml_node + template + class attribute_iterator + { + + public: + + typedef typename xml_attribute value_type; + typedef typename xml_attribute &reference; + typedef typename xml_attribute *pointer; + typedef std::ptrdiff_t difference_type; + typedef std::bidirectional_iterator_tag iterator_category; + + attribute_iterator() + : m_attribute(0) + { + } + + attribute_iterator(xml_node *node) + : m_attribute(node->first_attribute()) + { + } + + reference operator *() const + { + assert(m_attribute); + return *m_attribute; + } + + pointer operator->() const + { + assert(m_attribute); + return m_attribute; + } + + attribute_iterator& operator++() + { + assert(m_attribute); + m_attribute = m_attribute->next_attribute(); + return *this; + } + + attribute_iterator operator++(int) + { + attribute_iterator tmp = *this; + ++this; + return tmp; + } + + attribute_iterator& operator--() + { + assert(m_attribute && m_attribute->previous_attribute()); + m_attribute = m_attribute->previous_attribute(); + return *this; + } + + attribute_iterator operator--(int) + { + attribute_iterator tmp = *this; + ++this; + return tmp; + } + + bool operator ==(const attribute_iterator &rhs) + { + return m_attribute == rhs.m_attribute; + } + + bool operator !=(const attribute_iterator &rhs) + { + return m_attribute != rhs.m_attribute; + } + + private: + + xml_attribute *m_attribute; + + }; + +} + +#endif diff --git a/util/xpack/rapidxml/rapidxml_print.hpp b/util/xpack/rapidxml/rapidxml_print.hpp new file mode 100755 index 0000000..d03d5f5 --- /dev/null +++ b/util/xpack/rapidxml/rapidxml_print.hpp @@ -0,0 +1,421 @@ +#ifndef RAPIDXML_PRINT_HPP_INCLUDED +#define RAPIDXML_PRINT_HPP_INCLUDED + +// Copyright (C) 2006, 2009 Marcin Kalicinski +// Version 1.13 +// Revision $DateTime: 2009/05/13 01:46:17 $ +//! \file rapidxml_print.hpp This file contains rapidxml printer implementation + +#include "rapidxml.hpp" + +// Only include streams if not disabled +#ifndef RAPIDXML_NO_STREAMS + #include + #include +#endif + +namespace rapidxml +{ + + /////////////////////////////////////////////////////////////////////// + // Printing flags + + const int print_no_indenting = 0x1; //!< Printer flag instructing the printer to suppress indenting of XML. See print() function. + + /////////////////////////////////////////////////////////////////////// + // Internal + + //! \cond internal + namespace internal + { + + /////////////////////////////////////////////////////////////////////////// + // Internal character operations + + // Copy characters from given range to given output iterator + template + inline OutIt copy_chars(const Ch *begin, const Ch *end, OutIt out) + { + while (begin != end) + *out++ = *begin++; + return out; + } + + // Copy characters from given range to given output iterator and expand + // characters into references (< > ' " &) + template + inline OutIt copy_and_expand_chars(const Ch *begin, const Ch *end, Ch noexpand, OutIt out) + { + while (begin != end) + { + if (*begin == noexpand) + { + *out++ = *begin; // No expansion, copy character + } + else + { + switch (*begin) + { + case Ch('<'): + *out++ = Ch('&'); *out++ = Ch('l'); *out++ = Ch('t'); *out++ = Ch(';'); + break; + case Ch('>'): + *out++ = Ch('&'); *out++ = Ch('g'); *out++ = Ch('t'); *out++ = Ch(';'); + break; + case Ch('\''): + *out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('p'); *out++ = Ch('o'); *out++ = Ch('s'); *out++ = Ch(';'); + break; + case Ch('"'): + *out++ = Ch('&'); *out++ = Ch('q'); *out++ = Ch('u'); *out++ = Ch('o'); *out++ = Ch('t'); *out++ = Ch(';'); + break; + case Ch('&'): + *out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('m'); *out++ = Ch('p'); *out++ = Ch(';'); + break; + default: + *out++ = *begin; // No expansion, copy character + } + } + ++begin; // Step to next character + } + return out; + } + + // Fill given output iterator with repetitions of the same character + template + inline OutIt fill_chars(OutIt out, int n, Ch ch) + { + for (int i = 0; i < n; ++i) + *out++ = ch; + return out; + } + + // Find character + template + inline bool find_char(const Ch *begin, const Ch *end) + { + while (begin != end) + if (*begin++ == ch) + return true; + return false; + } + + /////////////////////////////////////////////////////////////////////////// + // Internal printing operations + + // Print node + template + inline OutIt print_node(OutIt out, const xml_node *node, int flags, int indent) + { + // Print proper node type + switch (node->type()) + { + + // Document + case node_document: + out = print_children(out, node, flags, indent); + break; + + // Element + case node_element: + out = print_element_node(out, node, flags, indent); + break; + + // Data + case node_data: + out = print_data_node(out, node, flags, indent); + break; + + // CDATA + case node_cdata: + out = print_cdata_node(out, node, flags, indent); + break; + + // Declaration + case node_declaration: + out = print_declaration_node(out, node, flags, indent); + break; + + // Comment + case node_comment: + out = print_comment_node(out, node, flags, indent); + break; + + // Doctype + case node_doctype: + out = print_doctype_node(out, node, flags, indent); + break; + + // Pi + case node_pi: + out = print_pi_node(out, node, flags, indent); + break; + + // Unknown + default: + assert(0); + break; + } + + // If indenting not disabled, add line break after node + if (!(flags & print_no_indenting)) + *out = Ch('\n'), ++out; + + // Return modified iterator + return out; + } + + // Print children of the node + template + inline OutIt print_children(OutIt out, const xml_node *node, int flags, int indent) + { + for (xml_node *child = node->first_node(); child; child = child->next_sibling()) + out = print_node(out, child, flags, indent); + return out; + } + + // Print attributes of the node + template + inline OutIt print_attributes(OutIt out, const xml_node *node, int flags) + { + for (xml_attribute *attribute = node->first_attribute(); attribute; attribute = attribute->next_attribute()) + { + if (attribute->name() && attribute->value()) + { + // Print attribute name + *out = Ch(' '), ++out; + out = copy_chars(attribute->name(), attribute->name() + attribute->name_size(), out); + *out = Ch('='), ++out; + // Print attribute value using appropriate quote type + if (find_char(attribute->value(), attribute->value() + attribute->value_size())) + { + *out = Ch('\''), ++out; + out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('"'), out); + *out = Ch('\''), ++out; + } + else + { + *out = Ch('"'), ++out; + out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('\''), out); + *out = Ch('"'), ++out; + } + } + } + return out; + } + + // Print data node + template + inline OutIt print_data_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_data); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out); + return out; + } + + // Print data node + template + inline OutIt print_cdata_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_cdata); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'); ++out; + *out = Ch('!'); ++out; + *out = Ch('['); ++out; + *out = Ch('C'); ++out; + *out = Ch('D'); ++out; + *out = Ch('A'); ++out; + *out = Ch('T'); ++out; + *out = Ch('A'); ++out; + *out = Ch('['); ++out; + out = copy_chars(node->value(), node->value() + node->value_size(), out); + *out = Ch(']'); ++out; + *out = Ch(']'); ++out; + *out = Ch('>'); ++out; + return out; + } + + // Print element node + template + inline OutIt print_element_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_element); + + // Print element name and attributes, if any + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'), ++out; + out = copy_chars(node->name(), node->name() + node->name_size(), out); + out = print_attributes(out, node, flags); + + // If node is childless + if (node->value_size() == 0 && !node->first_node()) + { + // Print childless node tag ending + *out = Ch('/'), ++out; + *out = Ch('>'), ++out; + } + else + { + // Print normal node tag ending + *out = Ch('>'), ++out; + + // Test if node contains a single data node only (and no other nodes) + xml_node *child = node->first_node(); + if (!child) + { + // If node has no children, only print its value without indenting + out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out); + } + else if (child->next_sibling() == 0 && child->type() == node_data) + { + // If node has a sole data child, only print its value without indenting + out = copy_and_expand_chars(child->value(), child->value() + child->value_size(), Ch(0), out); + } + else + { + // Print all children with full indenting + if (!(flags & print_no_indenting)) + *out = Ch('\n'), ++out; + out = print_children(out, node, flags, indent + 1); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + } + + // Print node end + *out = Ch('<'), ++out; + *out = Ch('/'), ++out; + out = copy_chars(node->name(), node->name() + node->name_size(), out); + *out = Ch('>'), ++out; + } + return out; + } + + // Print declaration node + template + inline OutIt print_declaration_node(OutIt out, const xml_node *node, int flags, int indent) + { + // Print declaration start + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'), ++out; + *out = Ch('?'), ++out; + *out = Ch('x'), ++out; + *out = Ch('m'), ++out; + *out = Ch('l'), ++out; + + // Print attributes + out = print_attributes(out, node, flags); + + // Print declaration end + *out = Ch('?'), ++out; + *out = Ch('>'), ++out; + + return out; + } + + // Print comment node + template + inline OutIt print_comment_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_comment); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'), ++out; + *out = Ch('!'), ++out; + *out = Ch('-'), ++out; + *out = Ch('-'), ++out; + out = copy_chars(node->value(), node->value() + node->value_size(), out); + *out = Ch('-'), ++out; + *out = Ch('-'), ++out; + *out = Ch('>'), ++out; + return out; + } + + // Print doctype node + template + inline OutIt print_doctype_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_doctype); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'), ++out; + *out = Ch('!'), ++out; + *out = Ch('D'), ++out; + *out = Ch('O'), ++out; + *out = Ch('C'), ++out; + *out = Ch('T'), ++out; + *out = Ch('Y'), ++out; + *out = Ch('P'), ++out; + *out = Ch('E'), ++out; + *out = Ch(' '), ++out; + out = copy_chars(node->value(), node->value() + node->value_size(), out); + *out = Ch('>'), ++out; + return out; + } + + // Print pi node + template + inline OutIt print_pi_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_pi); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'), ++out; + *out = Ch('?'), ++out; + out = copy_chars(node->name(), node->name() + node->name_size(), out); + *out = Ch(' '), ++out; + out = copy_chars(node->value(), node->value() + node->value_size(), out); + *out = Ch('?'), ++out; + *out = Ch('>'), ++out; + return out; + } + + } + //! \endcond + + /////////////////////////////////////////////////////////////////////////// + // Printing + + //! Prints XML to given output iterator. + //! \param out Output iterator to print to. + //! \param node Node to be printed. Pass xml_document to print entire document. + //! \param flags Flags controlling how XML is printed. + //! \return Output iterator pointing to position immediately after last character of printed text. + template + inline OutIt print(OutIt out, const xml_node &node, int flags = 0) + { + return internal::print_node(out, &node, flags, 0); + } + +#ifndef RAPIDXML_NO_STREAMS + + //! Prints XML to given output stream. + //! \param out Output stream to print to. + //! \param node Node to be printed. Pass xml_document to print entire document. + //! \param flags Flags controlling how XML is printed. + //! \return Output stream. + template + inline std::basic_ostream &print(std::basic_ostream &out, const xml_node &node, int flags = 0) + { + print(std::ostream_iterator(out), node, flags); + return out; + } + + //! Prints formatted XML to given output stream. Uses default printing flags. Use print() function to customize printing process. + //! \param out Output stream to print to. + //! \param node Node to be printed. + //! \return Output stream. + template + inline std::basic_ostream &operator <<(std::basic_ostream &out, const xml_node &node) + { + return print(out, node); + } + +#endif + +} + +#endif diff --git a/util/xpack/rapidxml/rapidxml_utils.hpp b/util/xpack/rapidxml/rapidxml_utils.hpp new file mode 100755 index 0000000..5eafa35 --- /dev/null +++ b/util/xpack/rapidxml/rapidxml_utils.hpp @@ -0,0 +1,122 @@ +#ifndef RAPIDXML_UTILS_HPP_INCLUDED +#define RAPIDXML_UTILS_HPP_INCLUDED + +// Copyright (C) 2006, 2009 Marcin Kalicinski +// Version 1.13 +// Revision $DateTime: 2009/05/13 01:46:17 $ +//! \file rapidxml_utils.hpp This file contains high-level rapidxml utilities that can be useful +//! in certain simple scenarios. They should probably not be used if maximizing performance is the main objective. + +#include "rapidxml.hpp" +#include +#include +#include +#include + +namespace rapidxml +{ + + //! Represents data loaded from a file + template + class file + { + + public: + + //! Loads file into the memory. Data will be automatically destroyed by the destructor. + //! \param filename Filename to load. + file(const char *filename) + { + using namespace std; + + // Open stream + basic_ifstream stream(filename, ios::binary); + if (!stream) + throw runtime_error(string("cannot open file ") + filename); + stream.unsetf(ios::skipws); + + // Determine stream size + stream.seekg(0, ios::end); + size_t size = stream.tellg(); + stream.seekg(0); + + // Load data and add terminating 0 + m_data.resize(size + 1); + stream.read(&m_data.front(), static_cast(size)); + m_data[size] = 0; + } + + //! Loads file into the memory. Data will be automatically destroyed by the destructor + //! \param stream Stream to load from + file(std::basic_istream &stream) + { + using namespace std; + + // Load data and add terminating 0 + stream.unsetf(ios::skipws); + m_data.assign(istreambuf_iterator(stream), istreambuf_iterator()); + if (stream.fail() || stream.bad()) + throw runtime_error("error reading stream"); + m_data.push_back(0); + } + + //! Gets file data. + //! \return Pointer to data of file. + Ch *data() + { + return &m_data.front(); + } + + //! Gets file data. + //! \return Pointer to data of file. + const Ch *data() const + { + return &m_data.front(); + } + + //! Gets file data size. + //! \return Size of file data, in characters. + std::size_t size() const + { + return m_data.size(); + } + + private: + + std::vector m_data; // File data + + }; + + //! Counts children of node. Time complexity is O(n). + //! \return Number of children of node + template + inline std::size_t count_children(xml_node *node) + { + xml_node *child = node->first_node(); + std::size_t count = 0; + while (child) + { + ++count; + child = child->next_sibling(); + } + return count; + } + + //! Counts attributes of node. Time complexity is O(n). + //! \return Number of attributes of node + template + inline std::size_t count_attributes(xml_node *node) + { + xml_attribute *attr = node->first_attribute(); + std::size_t count = 0; + while (attr) + { + ++count; + attr = attr->next_attribute(); + } + return count; + } + +} + +#endif diff --git a/util/xpack/sqlite.h b/util/xpack/sqlite.h new file mode 100755 index 0000000..9d92b37 --- /dev/null +++ b/util/xpack/sqlite.h @@ -0,0 +1,44 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_SQLITE_H +#define __X_PACK_SQLITE_H + +#include "sqlite_decoder.h" +#include "xpack.h" + +namespace xpack { + +class sqlite { +public: + // convert result of sqlite_get_table to a struct or vector + template + static void decode(const char **result, int rows, int cols, T &val) { + SQLiteDecoder de(result, rows, cols); + de.decode_top(val, NULL); + } + + // select name from test where id = 1; // just want to get name, did not want to use a struct + template + static void decode(const char **result, int rows, int cols, const std::string&field, T &val) { + SQLiteDecoder de(result, rows, cols); + de.decode_column(field.c_str(), val, NULL); + } +}; + +} + +#endif \ No newline at end of file diff --git a/util/xpack/sqlite_decoder.h b/util/xpack/sqlite_decoder.h new file mode 100755 index 0000000..28d3ea8 --- /dev/null +++ b/util/xpack/sqlite_decoder.h @@ -0,0 +1,195 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +#ifndef __X_PACK_SQLITE_DECODER_H +#define __X_PACK_SQLITE_DECODER_H + +#include +#include +#include +#include + +#include + +#include "extend.h" +#include "traits.h" +#include "json.h" + +#include "string.h" +#include "time.h" + +namespace xpack { + +class SQLiteDecoder { + friend class sqlite; +public: + template + bool decode(const char*key, T&val, const Extend *ext) { + int idx = find(key); + if (idx >= 0) { + return this->decode_type(idx, val, ext); + } + return false; + } + const char *Name() const { // mysql and sqlite use db + return "db"; + } + +private: + const char **_res; + const int _rows; + const int _cols; + int _offset; + std::map _index; + + SQLiteDecoder(const char**result, int rows, int cols):_res(result), _rows(rows), _cols(cols) { + for (int i=0; i + inline XPACK_IS_XPACK(T) decode_top(T& val, const Extend *ext) { + if (_rows > 0) { + val.__x_pack_decode(*this, val, ext); + return true; + } + return false; + } + template + inline XPACK_IS_XPACK(T) decode_top(std::vector& val, const Extend *ext) { + for (int i=0; i<_rows; ++i) { + val.push_back(T()); + T& tmp = val.back(); + tmp.__x_pack_decode(*this, tmp, ext); + _offset += _cols; + } + return true; + } + template + inline XPACK_IS_XOUT(T) decode_top(T& val, const Extend *ext) { + if (_rows > 0) { + __x_pack_decode_out(*this, val, ext); + } + return true; + } + template + inline XPACK_IS_XOUT(T) decode_top(std::vector& val, const Extend *ext) { + for (int i=0; i<_rows; ++i) { + val.push_back(T()); + __x_pack_decode_out(*this, val.back(), ext); + _offset += _cols; + } + return true; + } + + // decode special column of first row + template + bool decode_column(const char*field, T&val, const Extend *ext) { + int idx; + if (_rows>0 && (0 <= (idx = find(field)))) { + return decode_type(idx, val, ext); + } + return false; + } + // decode special column of rows + template + bool decode_column(const char*field, std::vector&val, const Extend *ext) { + int idx; + if (0 > (idx = find(field))) { + return false; + } + for (int i=0; i<_rows; ++i) { + T t; + decode_type(idx, t, ext); + val.push_back(t); + _offset += _cols; + } + return true; + } + + + int find(const char*field) const { + std::map::const_iterator it = _index.find(field); + if (it != _index.end()) { + return it->second; + } + return -1; + } + + // std::string + bool decode_type(const int idx, std::string &val, const Extend *ext) { + const char* s; + if (NULL != (s = _res[idx + _offset])) { + val = s; + } + return true; + } + // bool + bool decode_type(const int idx, bool &val, const Extend *ext) { + int tmp; + if (decode_type(idx, tmp, ext)) { + val = tmp != 0; + return true; + } + return false; + } + // integer + template + typename x_enable_if::is_integer, bool>::type decode_type(const int idx, T &val, const Extend *ext) { + const char* s; + if (NULL != (s = _res[idx + _offset])) { + val = (T)std::strtoul(s, NULL, 10); + } // else val = 0 ??? + return true; + } + // float + template + typename x_enable_if::is_float, bool>::type decode_type(const int idx, T &val, const Extend *ext) { + const char* s; + if (NULL != (s = _res[idx + _offset])) { + val = (T)std::strtod(s, NULL); + } + return true; + } + // class/struct defined XPACK/XPACK_OUT, default use json to parse + template + inline XPACK_IS_XOUT(T) decode_type(const int idx, T &val, const Extend *ext) { + (void)ext; + const char* s; + if (NULL != (s = _res[idx + _offset])) { + return xpack::json::decode(s, val); + } + return true; + } + template + inline XPACK_IS_XPACK(T) decode_type(const int idx, T &val, const Extend *ext) { + (void)ext; + const char* s; + if (NULL != (s = _res[idx + _offset])) { + return xpack::json::decode(s, val); + } + return true; + } +}; + + +} + +#endif diff --git a/util/xpack/traits.h b/util/xpack/traits.h new file mode 100755 index 0000000..e0db381 --- /dev/null +++ b/util/xpack/traits.h @@ -0,0 +1,111 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +#ifndef __X_PACK_TRAITS_H +#define __X_PACK_TRAITS_H + +#if __GXX_EXPERIMENTAL_CXX0X__ || _MSC_VER>=1700 +#define X_PACK_SUPPORT_CXX0X 1 +#endif + +namespace xpack { + +// implement std::enable_if +template +struct x_enable_if {}; +template +struct x_enable_if { typedef T type; }; + +// mark XPACK_OUT +template +struct is_xpack_out{static bool const value = false;}; + +template +struct is_xpack_xtype {static bool const value = false;}; + +template +struct is_xpack_type_spec {static bool const value = false;}; + + +// for bitfield, declare raw type. thx https://stackoverflow.com/a/12199635/5845104 +template struct x_size { char value[N]; }; +x_size<1> x_decltype_encode(char); +x_size<2> x_decltype_encode(signed char); +x_size<3> x_decltype_encode(unsigned char); +x_size<4> x_decltype_encode(short); +x_size<5> x_decltype_encode(unsigned short); +x_size<6> x_decltype_encode(int); +x_size<7> x_decltype_encode(unsigned int); +x_size<8> x_decltype_encode(long); +x_size<9> x_decltype_encode(unsigned long); +x_size<10> x_decltype_encode(long long); +x_size<11> x_decltype_encode(unsigned long long); + +template struct x_decltype_decode {}; +template <> struct x_decltype_decode<1> {typedef char type;}; +template <> struct x_decltype_decode<2> {typedef signed char type;}; +template <> struct x_decltype_decode<3> {typedef unsigned char type;}; +template <> struct x_decltype_decode<4> {typedef short type;}; +template <> struct x_decltype_decode<5> {typedef unsigned short type;}; +template <> struct x_decltype_decode<6> {typedef int type;}; +template <> struct x_decltype_decode<7> {typedef unsigned int type;}; +template <> struct x_decltype_decode<8> {typedef long type;}; +template <> struct x_decltype_decode<9> {typedef unsigned long type;}; +template <> struct x_decltype_decode<10> {typedef long long type;}; +template <> struct x_decltype_decode<11> {typedef unsigned long long type;}; + +class noncopyable { +public: + noncopyable(){} + ~noncopyable(){} +private: + noncopyable(const noncopyable&v); + noncopyable& operator = (const noncopyable&v); +}; + +} + +#define x_pack_decltype(T) typename xpack::x_decltype_decode::type + + +// fix SFINAE bug of vs2005, so vs2005(or pre version) not support XPACK_OUT define xtype. +// can use custom +// The priority of xtype is the highest +#if defined _MSC_VER && _MSC_VER<=1400 +#define XPACK_IS_XTYPE(Coder, T) typename x_enable_if::value && !is_xpack_type_spec::value && !is_xpack_out::value, bool>::type +#else +#define XPACK_IS_XTYPE(Coder, T) typename x_enable_if::value && !is_xpack_type_spec::value, bool>::type +#endif + +/* +struct A { + XPACK(...); +}; +struct B : public A{ +}; +XPACK_OUT(B, ...); + +in this case, B::__x_pack_value is true and should hit xpack_out, so +XPACK_IS_XPACK need to add !is_xpack_out::value +*/ + +#define XPACK_IS_XOUT(T) typename x_enable_if::value && !is_xpack_xtype::value, bool>::type +#define XPACK_IS_XPACK(T) typename x_enable_if::value && !is_xpack_xtype::value, bool>::type + + +#endif + diff --git a/util/xpack/util.h b/util/xpack/util.h new file mode 100755 index 0000000..79760f5 --- /dev/null +++ b/util/xpack/util.h @@ -0,0 +1,257 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_UTIL_H +#define __X_PACK_UTIL_H + +#include +#include +#include +#include +#include + +#include +#include + +#include "traits.h" +#include "numeric.h" + +namespace xpack { + +struct cmp_str { + bool operator()(char const *a, char const *b) const { + return strcmp(a, b) < 0; + } +}; + +class Util { +public: + static bool readfile(const std::string&fname, std::string&data) { + std::ifstream fs(fname.c_str(), std::ifstream::binary); + if (!fs) { + std::string err = "Open file["+fname+"] fail."; + throw std::runtime_error(err); + } + std::string _tmp((std::istreambuf_iterator(fs)), std::istreambuf_iterator()); + data.swap(_tmp); + return true; + } + // if n<0 will split all + static size_t split(std::vector&slice, const std::string&str, char c, int n = -1) { + size_t last = 0; + size_t pos = 0; + int cnt = 0; + while (std::string::npos != (pos=str.find(c, last))) { + slice.push_back(str.substr(last, pos-last)); + last = pos+1; + ++cnt; + if (n>0 && n>=cnt) { + break; + } + } + + slice.push_back(str.substr(last)); + return slice.size(); + } + + static size_t split(std::vector&slice, const std::string&str, const std::string& sp, int n =-1) { + size_t last = 0; + size_t pos = 0; + size_t len = sp.length(); + if (len == 0) { + slice.push_back(str); + return 1; + } + int cnt = 0; + while (std::string::npos != (pos=str.find(sp, last))) { + slice.push_back(str.substr(last, pos-last)); + last = pos+len; + ++cnt; + if (n>0 && n>=cnt) { + break; + } + } + + slice.push_back(str.substr(last)); + return slice.size(); + } + + // not support float. + template + static typename x_enable_if::is_integer, std::string>::type itoa(const T&val) { + #ifdef X_PACK_SUPPORT_CXX0X + return std::to_string(val); + #else + char buf[128]; + size_t i = sizeof(buf)-1; + + if (val == 0) { + return std::string("0"); + } + + T _tmp = val>=0?val:val*-1; + while (_tmp > 0) { + buf[i--] = char(int('0')+int(_tmp%10)); + _tmp /= 10; + } + if (val < 0) { + buf[i--] = '-'; + } + return std::string(&buf[i+1], sizeof(buf)-i-1); + #endif + } + + #ifdef X_PACK_SUPPORT_CXX0X + template + inline static typename x_enable_if::value, std::string>::type itoa(const E& val) { + return itoa((typename std::underlying_type::type)val); + } + template + static typename x_enable_if::value, bool>::type atoi(const std::string& key, E& val) { + typename std::underlying_type::type tmp; + bool ret = atoi(key, tmp); + if (ret) { + val = (E)tmp; + } + return ret; + } + #endif + + // not support float. and only decimal + template + static typename x_enable_if::is_integer, bool>::type atoi(const std::string&s, T&val) { + if (s.empty()) { + return false; + } + + T _tmp = 0; + size_t i = 0; + if (s[0] == '-') { + if (s.length() == 1) { + return false; + } else if (s.length()>2 && s[1]=='0') { + return false; + } + + for (i=1; i='0' && s[i]<='9') { + T _c = _tmp*10 - (s[i]-'0'); + if (_c < _tmp) { + _tmp = _c; + } else if (i > 0) { + return false; // overflow + } + } else { + return false; + } + } + } else { + if (s[0] == '+') { + ++i; + } + if (s[i]=='0' && i+1='0' && s[i]<='9') { + T _c = _tmp*10 + (s[i]-'0'); + if (_c > _tmp) { + _tmp = _c; + } else if (i > 0) { + return false; // overflow + } + } else { + return false; + } + } + } + + val = _tmp; + return true; + } + template + static typename x_enable_if::is_integer, bool>::type atoi(const char*s, T&val) { + if (NULL == s) { + return false; + } + std::string _t(s); + return atoi(_t, val); + } +}; + + +#if defined(X_PACK_SUPPORT_CXX0X) + template + using x_shared_ptr = std::shared_ptr; +#elif defined(_GNU_SOURCE) +// A very crude implementation of shared_ptr +template +class x_shared_ptr { +public: + x_shared_ptr(T *p = NULL):ptr(p) { + if (NULL != p) { + counter = new int; + *counter = 1; + } else { + counter = NULL; + } + } + x_shared_ptr(const x_shared_ptr &src) { + ptr = src.ptr; + counter = src.counter; + add_ref(1); + } + ~x_shared_ptr() { + add_ref(-1); + } + x_shared_ptr& operator = (const x_shared_ptr &src) { + add_ref(-1); + ptr = src.ptr; + counter = src.counter; + add_ref(1); + return *this; + } + void reset(T *p = NULL) { + *this = x_shared_ptr(p); + } + T* operator ->() { + return ptr; + } + T* get() { + return ptr; + } + T& operator *() { + return *ptr; + } +private: + void add_ref(int cnt) { + if (counter == NULL) { + return; + } + int ncnt = __sync_add_and_fetch(counter, cnt); + if (0 == ncnt) { + delete counter; + delete ptr; + } + } + T *ptr; + mutable int *counter; +}; +#endif + +} + +#endif diff --git a/util/xpack/xdecoder.h b/util/xpack/xdecoder.h new file mode 100755 index 0000000..0e01885 --- /dev/null +++ b/util/xpack/xdecoder.h @@ -0,0 +1,416 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +#ifndef __X_PACK_DECODER_H +#define __X_PACK_DECODER_H + +#include +#include +#include +#include +#include + +#include "extend.h" +#include "traits.h" + +#include "string.h" + +#ifdef XPACK_SUPPORT_QT +#include +#include +#include +#include +#endif + +#ifdef X_PACK_SUPPORT_CXX0X +#include +#include +#include +#endif + +namespace xpack { + + +/* + Node need implement: + static const char *Type() const; "json/bson/...." + + operator bool() const; // check is a valid node + bool IsNull() const; // only for json, others should always return false + Node Find(decoder&, const char*key); // Find a child node(object) + Node At(size_t index); // Find a child node(array) + size_t Size(decoder&, ); // Get array size + + // iterator + Node Next(decoder&, const Node&p, Iterator&iter, std::string&key); // iter object + + template + bool Get(decoder&, T&val, const Extend*ext); // T is integer/double/std::string/bool +*/ +template +class XDecoder { +public: + typedef XDecoder decoder; + + XDecoder(const decoder* parent, const char* key, Node node):_p(parent),_k(key),_i(-1),_n(node) {} + XDecoder(const decoder *parent, int index, Node node):_p(parent),_k(NULL),_i(index),_n(node) {} + XDecoder():_i(-2){} + + const char *Name() const { + return Node::Name(); + } + void decode_exception(const char* what, const char *key) const { + std::string err; + err.reserve(128); + if (NULL != what) { + err.append(what); + } + err.append(". (path:"); + + std::string p = path(); + err.append(p); + if (NULL != key) { + if (!p.empty()) { + err.append("."); + } + err.append(key); + } + + err.append(")"); + throw std::runtime_error(err); + } + // find by key + decoder Find(const char *key, const Extend *ext) { + Node child = _n.Find(*this, key, ext); + if (child){ + return XDecoder(this, key, child); + } else if (Extend::Mandatory(ext)) { + decode_exception("mandatory key not found", key); + } + + return XDecoder(); + } + operator bool() const { + return _i != -2; + } + +public: + template + bool decode(const char*key, T&val, const Extend*ext) { + decoder child = Find(key, ext); + if (child) { + return child.decode_type(val, ext); + } + return false; + } + + template + bool decode(T &val, const Extend*ext) { + return this->decode_type(val, ext); + } + + // class/struct that defined macro XPACK, !is_xpack_out to avoid inherit __x_pack_value + template + inline typename x_enable_if::value, bool>::type decode_struct(T& val, const Extend *ext) { + return val.__x_pack_decode(*this, val, ext); + } + // class/struct that defined macro XPACK_OUT + template + inline typename x_enable_if::value, bool>::type decode_struct(T& val, const Extend *ext) { + return __x_pack_decode_out(*this, val, ext); + } + + template + inline bool decode_struct(const char*key, T&val, const Extend *ext) { + decoder child = Find(key, ext); + if (child) { + return child.decode_struct(val, ext); + } + return false; + } + +private: + // numeric + template + inline typename x_enable_if::value, bool>::type decode_type(T&val, const Extend *ext) { + return _n.Get(*this, val, ext); + } + // std::string + inline bool decode_type(std::string&val, const Extend *ext) { + return _n.Get(*this, val, ext); + } + // std::string + inline bool decode_type(bool&val, const Extend *ext) { + return _n.Get(*this, val, ext); + } + // array + template + inline bool decode_type(T (&val)[N], const Extend *ext) { + return this->decode_array(val, N, ext); + } + // vector + template + inline bool decode_type(std::vector &val, const Extend *ext) { + return this->decode_vector(val, ext); + } + // list + template + inline bool decode_type(std::list &val, const Extend *ext) { + return this->decode_list, T>(val, ext); + } + // set + template + inline bool decode_type(std::set &val, const Extend *ext) { + return this->decode_list, T>(val, ext); + } + // map + template + inline bool decode_type(std::map &val, const Extend *ext) { + return decode_map, K, V>(val, ext); + } + // XPACK or XPACK_OUT and not XTYPE + template + inline XPACK_IS_XOUT(T) decode_type(T&val, const Extend *ext) { + return decode_struct(val, ext); + } + template + inline XPACK_IS_XPACK(T) decode_type(T&val, const Extend *ext) { + return decode_struct(val, ext); + } + // xtype. add && !is_xpack_out::value to fix SFINAE bug of vs2005 + template + inline XPACK_IS_XTYPE(Node, T) decode_type(T& val, const Extend *ext) { + return xpack_xtype_decode(*this, val, ext); + } + + #ifdef X_PACK_SUPPORT_CXX0X + // unordered_map + template + inline bool decode_type(std::unordered_map &val, const Extend *ext) { + return decode_map, K, V>(val, ext); + } + // shared_ptr + template + bool decode_type(std::shared_ptr& val, const Extend *ext) { + bool ret = false; + if (!_n.IsNull()) { + val.reset(new T); + ret = this->decode_type(*val, ext); + if (!ret) { + val.reset(); + } + } + return ret; + } + // enum is_enum implementation is too complicated, so in c++03, we use macro E + template + inline typename x_enable_if::value && !is_xpack_xtype::value, bool>::type decode_type(T& val, const Extend *ext) { + typename std::underlying_type::type tmp; + bool ret = this->decode_type(tmp, ext); + if (ret) { + val = (T)tmp; + } + return ret; + } + // assert pointer + template + typename x_enable_if::value, bool>::type decode_type(T &val, const Extend *ext) { + static_assert(!std::is_pointer::value, "not support pointer, use shared_ptr please"); + (void)val;(void)ext; + return false; + } + #endif + + //////////////////// QT /////////////////////////////// + #ifdef XPACK_SUPPORT_QT + bool decode_type(QString &val, const Extend *ext) { + std::string str; + bool ret = this->decode_type(str, ext); + if (ret) { + val = QString::fromStdString(str); + } + return ret; + } + template + inline bool decode_type(QList &val, const Extend *ext) { + return this->decode_list, T>(val, ext); + } + #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) //https://www.qt.io/blog/qlist-changes-in-qt-6 + template + inline bool decode_type(QVector &val, const Extend *ext) { + return this->decode_vector(val, ext); + } + #endif + + template + inline bool decode_type(QMap &val, const Extend *ext) { + return decode_map, K, V>(val, ext); + } + #endif + + template + inline typename x_enable_if::value, bool>::type decode_type(T&val, const Extend *ext) { + return _n.decode_type_spec(val, ext); + } + + + /////////////////////////////////////////////////// + template + bool decode_array(T *val, size_t N, const Extend *ext) { + size_t mx = _n.Size(*this); + mx = mx>N?N:mx; + + for (size_t i=0; iat(i, ext).decode_type(val[i], ext); + } + return true; + } + // char[] is special + bool decode_array(char* val, size_t N, const Extend *ext) { + std::string str; + bool ret = this->decode_type(str, ext); + if (ret) { + size_t mx = str.length(); + mx = mx>N-1?N-1:mx; + strncpy(val, str.data(), mx); + val[mx] = '\0'; + } + return ret; + } + template + bool decode_vector(Vector &val, const Extend *ext) { + size_t s = _n.Size(*this); + val.resize(s); + for (size_t i=0; iat(i, ext).decode_type(val[i], ext); + } + return true; + } + // list + template + bool decode_list(List &val, const Extend *ext) { + size_t s = _n.Size(*this); + for (size_t i=0; iat(i, ext).decode_type(_t, ext); + this->add_ele(val, _t); + } + return true; + } + // map + template + bool decode_map(Map &val, const Extend *ext) { + typename Node::Iterator iter; + std::string key; + Node tmp = _n.Next(*this, _n, iter, key); + while (tmp) { + K k; + V v; + if (keyConvert(key, k) && XDecoder(this, key.c_str(), tmp).decode_type(v, ext)) { + val[k] = v; + } + tmp = tmp.Next(*this, _n, iter, key); + } + + return true; + } + + ////// container process ////// + template + inline void add_ele(std::list&val, T &t) { + val.push_back(t); + } + template + inline void add_ele(std::set&val, T &t) { + val.insert(t); + } + #ifdef XPACK_SUPPORT_QT + template + inline void add_ele(QList&val, T &t) { + val.push_back(t); + } + #endif + + // convert map key + inline bool keyConvert(std::string&s, std::string&key) { + key.swap(s); + return true; + } + #ifdef XPACK_SUPPORT_QT + inline bool keyConvert(std::string&s, QString &key) { + key = QString::fromStdString(s); + return true; + } + #endif + #ifdef X_PACK_SUPPORT_CXX0X + // enum is_enum implementation is too complicated, so not support in c++03 + template + typename x_enable_if::value, bool>::type keyConvert(std::string&s, T&key) { + typename std::underlying_type::type tmp; + bool ret = Util::atoi(s, tmp); + if (ret) { + key = (T)tmp; + } + return ret; + } + #endif + template + inline typename x_enable_if::is_integer, bool>::type keyConvert(std::string&s, T&key) { + return Util::atoi(s, key); + } + + // index + decoder at(size_t i, const Extend *ext) { + (void)ext; + return XDecoder(this, (int)i, _n.At(i)); + } + + // exception process + std::string path() const { + std::vector nodes; + const decoder* tmp = this; + while (tmp) { + std::string k; + k.reserve(32); + if (NULL != tmp->_k) { + if (NULL!=tmp->_p && NULL!=tmp->_p->_p) { + k.append("."); + } + k.append(tmp->_k); + } else if (_i >= 0) { + k.append("[").append(Util::itoa(tmp->_i)).append("]"); + } + nodes.push_back(k); + tmp = tmp->_p; + } + std::string p; + p.reserve(64); + for (int i=(int)nodes.size()-1; i>=0; --i) { + p.append(nodes[i]); + } + return p; + } + + const decoder* _p; + const char* _k; + int _i; + Node _n; +}; + +} + +#endif diff --git a/util/xpack/xencoder.h b/util/xpack/xencoder.h new file mode 100755 index 0000000..c9793fa --- /dev/null +++ b/util/xpack/xencoder.h @@ -0,0 +1,369 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +#ifndef __X_PACK_ENCODER_H +#define __X_PACK_ENCODER_H + +#include +#include +#include +#include + +#include "extend.h" +#include "traits.h" +#include "numeric.h" + +#ifdef XPACK_SUPPORT_QT +#include +#include +#include +#include +#endif + +#ifdef X_PACK_SUPPORT_CXX0X +#include +#include +#include +#endif + +namespace xpack { + +/* + DOC need implement: + const char *Name() const; "json/bson/...." + const static bool support_null = true/false; + + ArrayBegin(const char *key, const Extend *ext) begin encode array + ArrayEnd(const char *key, const Extend *ext) end encode array + + ObjectBegin(const char *key, const Extend *ext) begin encode object + ObjectEnd(const char *key, const Extend *ext) end encode object + + WriteNull(const char *key, const Extend *ext) + + std::string String() return the encode result + + // basic type + bool encode_bool(const char*key, const bool&val, const Extend *ext); + bool encode_string(const char*key, const std::string&val, const Extend *ext); + bool encode_number(const char*key, const T&val, const Extend *ext); +*/ +template +class XEncoder :private noncopyable{ + typedef XEncoder encoder; + Writer &_w; + +public: + XEncoder(Writer &w):_w(w){} + + const char *Name() const { + return Writer::Name(); + } + + // After calling String, this Encoder has finished its work and should not be used anymore + inline std::string String() { + return _w.String(); + } + + #define XPACK_WRITE_EMPTY(cond) \ + if (cond) { \ + if (Extend::OmitEmpty(ext)) { \ + return false;\ + } else if (Writer::support_null && Extend::EmptyNull(ext)) {\ + return _w.WriteNull(key, ext);\ + }\ + } + + // basic type + bool encode(const char*key, const bool &val, const Extend *ext) { + XPACK_WRITE_EMPTY(!val); + return _w.encode_bool(key, val, ext); + } + bool encode(const char*key, const std::string &val, const Extend *ext) { + XPACK_WRITE_EMPTY(val.empty()); + return _w.encode_string(key, val, ext); + } + template + typename x_enable_if::value, bool>::type encode(const char*key, const T&val, const Extend *ext) { + XPACK_WRITE_EMPTY(val == 0); + return _w.encode_number(key, val, ext); + } + + // array + template + inline bool encode(const char*key, const T (&val)[N], const Extend *ext) { + return this->encode(key, val, N, ext); + } + template + bool encode(const char *key, const T *val, size_t N, const Extend *ext) { + XPACK_WRITE_EMPTY((N==0)) + + _w.ArrayBegin(key, ext); + for (size_t i=0; iencode(_w.IndexKey(i), val[i], NULL); + } + _w.ArrayEnd(key, ext); + return true; + } + inline bool encode(const char*key, const char *val, size_t N, const Extend *ext) { + (void)N; + std::string str(val); + return this->encode(key, str, ext); + } + // vector + template + inline bool encode(const char*key, const std::vector &val, const Extend *ext) { + return encode_list >(key, val, ext); + } + // list + template + inline bool encode(const char*key, const std::list &val, const Extend *ext) { + return encode_list >(key, val, ext); + } + // set + template + inline bool encode(const char*key, const std::set &val, const Extend *ext) { + return encode_list >(key, val, ext); + } + // map + template + inline bool encode(const char*key, const std::map &val, const Extend *ext) { + return encode_map >(key, val, ext); + } + // class/struct that defined XPACK + template + inline XPACK_IS_XPACK(T) encode(const char*key, const T& val, const Extend *ext) { + return encode_struct(key, val, ext); + } + // class/struct that defined XPACK_OUT + template + inline XPACK_IS_XOUT(T) encode(const char*key, const T& val, const Extend *ext) { + return encode_struct(key, val, ext); + } + // xtype + template + inline XPACK_IS_XTYPE(Writer, T) encode(const char*key, const T& val, const Extend *ext) { + return encode_xtype(key, val, ext); + } + + #ifdef X_PACK_SUPPORT_CXX0X + // unordered_map + template + inline bool encode(const char*key, const std::unordered_map &val, const Extend *ext) { + return encode_map>(key, val, ext); + } + // shared_ptr + template + bool encode(const char*key, const std::shared_ptr& val, const Extend *ext) { + if (val.get() == NULL) { // if shared ptr is null + return _w.WriteNull(key, ext); + } + + return this->encode(key, *val, ext); + } + // enum + template + typename x_enable_if::value && !is_xpack_xtype::value, bool>::type encode(const char*key, const T& val, const Extend *ext) { + typename std::underlying_type::type tmp = (typename std::underlying_type::type)val; + return this->encode(key, tmp, ext); + } + // assert pointer + template + typename x_enable_if::value, bool>::type encode(const char*key, const T &val, const Extend *ext) { + static_assert(!std::is_pointer::value, "no support pointer, use shared_ptr please"); + (void)key;(void)val;(void)ext; + return false; + } + #endif // cxx + template + inline bool encode_xtype(const char*key, const T &val, const Extend *ext) { + return xpack_xtype_encode(*this, key, val, ext); + } + + #ifdef XPACK_SUPPORT_QT + bool encode(const char*key, const QString &val, const Extend *ext) { + std::string str = val.toStdString(); + return this->encode(key, str, ext); + } + + template + bool encode(const char*key, const QList&val, const Extend *ext) { + return encode_list >(key, val, ext); + } + template + bool encode(const char*key, const QMap&val, const Extend *ext) { + return encode_qmap >(key, val, ext); + } + + #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) //https://www.qt.io/blog/qlist-changes-in-qt-6 + template + bool encode(const char*key, const QVector&val, const Extend *ext) { + return encode_list >(key, val, ext); + } + #endif // Qt5 + + #endif // Qt + + template + inline typename x_enable_if::value, bool>::type encode(const char*key, const T&val, const Extend *ext) { + return _w.encode_type_spec(key, val, ext); + } + + // only for class/struct that defined XPACK + template + typename x_enable_if::value, bool>::type encode_struct(const char*key, const T& val, const Extend *ext) { + bool inherit = 0!=(X_PACK_CTRL_FLAG_INHERIT&Extend::CtrlFlag(ext)); + if (!inherit) { + _w.ObjectBegin(key, ext); + } + bool ret = val.__x_pack_encode(*this, val, ext); + if (!inherit) { + _w.ObjectEnd(key, ext); + } + return ret; + } + // only for class/struct that defined XPACK_OUT + template + typename x_enable_if::value, bool>::type encode_struct(const char*key, const T& val, const Extend *ext) { + bool inherit = 0!=(X_PACK_CTRL_FLAG_INHERIT&Extend::CtrlFlag(ext)); + + if (!inherit) { + _w.ObjectBegin(key, ext); + } + bool ret = __x_pack_encode_out(*this, val, ext); + if (!inherit) { + _w.ObjectEnd(key, ext); + } + return ret; + } + + // wrapper for encode manual + inline void ArrayBegin(const char *key, const Extend *ext) { + _w.ArrayBegin(key, ext); + } + inline void ArrayEnd(const char *key, const Extend *ext) { + _w.ArrayEnd(key, ext); + } + inline void ObjectBegin(const char *key, const Extend *ext) { + _w.ObjectBegin(key, ext); + } + inline void ObjectEnd(const char *key, const Extend *ext) { + _w.ObjectEnd(key, ext); + } + inline void WriteNull(const char *key, const Extend *ext) { + _w.WriteNull(key, ext); + } + template + encoder& add(const char *key, const T&val, const Extend *ext=NULL) { + this->encode(key, val, ext); + return *this; + } + // object begin + encoder& ob(const char *key) { + this->ObjectBegin(key, NULL); + return *this; + } + // object end + encoder& oe(const char *key=NULL) { + this->ObjectEnd(key, NULL); + return *this; + } + + // array begin + encoder& ab(const char *key) { + this->ArrayBegin(key, NULL); + return *this; + } + // array end + encoder& ae(const char *key=NULL) { + this->ArrayEnd(key, NULL); + return *this; + } + +private: + + // list + template + bool encode_list(const char*key, const LIST &val, const Extend *ext) { + size_t s = val.size(); + XPACK_WRITE_EMPTY((s==0)) + + _w.ArrayBegin(key, ext); + size_t i = 0; + for (typename LIST::const_iterator it=val.begin(); it!=val.end(); ++it, ++i) { + this->encode(_w.IndexKey(i), *it, NULL); + } + _w.ArrayEnd(key, ext); + return true; + } + + // map + template + bool encode_map(const char*key, Map &val, const Extend *ext) { + size_t s = val.size(); + XPACK_WRITE_EMPTY((s==0)) + + _w.ObjectBegin(key, ext); + for (typename Map::const_iterator it=val.begin(); it!=val.end(); ++it) { + this->encode(keyConvert(it->first).c_str(), it->second, NULL); + } + _w.ObjectEnd(key, ext); + return true; + } + + // qmap + template + bool encode_qmap(const char*key, Map &val, const Extend *ext) { + size_t s = val.size(); + XPACK_WRITE_EMPTY((s==0)) + + _w.ObjectBegin(key, ext); + for (typename Map::const_iterator it=val.begin(); it!=val.end(); ++it) { + this->encode(keyConvert(it.key()).c_str(), it.value(), NULL); + } + _w.ObjectEnd(key, ext); + return true; + } + + // convert map key + inline std::string keyConvert(const std::string&key) { + return key; + } + #ifdef XPACK_SUPPORT_QT + inline std::string keyConvert(const QString &key) { + return key.toStdString(); + } + #endif + #ifdef X_PACK_SUPPORT_CXX0X + // enum is_enum implementation is too complicated, so not support in c++03 + template + typename x_enable_if::value, std::string>::type keyConvert(const T&key) { + typename std::underlying_type::type tmp = (typename std::underlying_type::type)key; + return Util::itoa(tmp); + } + #endif + template + inline typename x_enable_if::is_integer, std::string>::type keyConvert(const T&key) { + return Util::itoa(key); + } + + #undef XPACK_WRITE_EMPTY +}; + +} + +#endif diff --git a/util/xpack/xml.h b/util/xpack/xml.h new file mode 100755 index 0000000..bc30129 --- /dev/null +++ b/util/xpack/xml.h @@ -0,0 +1,79 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_XML_H +#define __X_PACK_XML_H + +#include "xml_decoder.h" +#include "xml_encoder.h" +#include "xpack.h" + +namespace xpack { +/* +for xml like + + Pony + 20 + + +Just define such a structure +struct User { + string name; + int age; + XPACK(O(name, age)); +}; + +!!!!NO NEED!!! to define: +struct Data { + User user; + XPACK(O(user)); +}; + +xml::decode will skip the root node, so for the xml defined above, +it only needs to be decode like this: +User u; +xpack::xml::decode(str, u); +*/ + +class xml { +public: + template + static void decode(const std::string &data, T &val) { + XmlDecoder de; + de.decode(data, val); + } + template + static void decode_file(const std::string &file_name, T &val) { + XmlDecoder de; + de.decode_file(file_name, val); + } + template + static std::string encode(const T &val, const std::string&root) { + XmlEncoder en; + return en.encode(val, root); + } + + template + static std::string encode(const T &val, const std::string&root, int flag, int indentCount, char indentChar) { + (void)flag; + XmlEncoder en(indentCount, indentChar); + return en.encode(val, root); + } +}; + +} + +#endif \ No newline at end of file diff --git a/util/xpack/xml_decoder.h b/util/xpack/xml_decoder.h new file mode 100755 index 0000000..8230031 --- /dev/null +++ b/util/xpack/xml_decoder.h @@ -0,0 +1,254 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_XML_DECODER_H +#define __X_PACK_XML_DECODER_H + +#include +#include +#include + +#include +#include + +#include "rapidxml/rapidxml.hpp" + +#include "xdecoder.h" + +namespace xpack { + +class XmlNode { + typedef rapidxml::xml_node<> Node; + typedef XDecoder decoder; +public: + typedef size_t Iterator; + +public: + XmlNode(Node *n=NULL):node(n), attr(NULL), inited(false) {} + + inline static const char * Name() { + return "xml"; + } + operator bool() const { + return node != NULL; + } + bool IsNull() const { + return false; + } + XmlNode Find(decoder&de, const char*key, const Extend *ext) { + (void)de; + if (!inited) { + this->init(); + } + if (Extend::XmlContent(ext)) { + return *this; + } + + node_index::iterator iter; + if (_childs_index.end() != (iter=_childs_index.find(key))) { + bool sbs = Extend::AliasFlag(ext, "xml", "sbs"); + if (!sbs) { + return XmlNode(_childs[iter->second]); + } else { + XmlNode node(_childs[iter->second]); + node.initsbs(*this, key); + return node; + } + } else { // sbs not support attribute + XmlNode tmp; + tmp.attr = node->first_attribute(key); + if (NULL != tmp.attr) { + tmp.node = this->node; + } + return tmp; + } + } + size_t Size(decoder&de) { + (void)de; + if (!inited) { + this->init(); + } + return _childs.size(); + } + XmlNode At(size_t index) const { // no exception + return XmlNode(_childs[index]); + } + // if child node defined sbs, results can be confusing + XmlNode Next(decoder&de, XmlNode&p, Iterator&iter, std::string&key) { + (void)de; + if (!p.inited) { + p.init(); + } + if (node != NULL) { + if (node != p.node) { + ++iter; + } else { + iter = 0; + } + + if (iter < p._childs.size()) { + key = p._childs[iter]->name(); + return XmlNode(p._childs[iter]); + } + } + return XmlNode(); + } + bool Get(decoder&de, std::string&val, const Extend*ext) { + if (!Extend::AliasFlag(ext, "xml", "cdata")) { + val = get_val(Extend::XmlContent(ext)); + } else { + const Node *tmp = node->first_node(); + if (NULL != tmp) { + if (tmp->type() == rapidxml::node_cdata || tmp->type() == rapidxml::node_data) { + val = tmp->value(); + } else { + de.decode_exception("not cdata type", NULL); + } + } else { // if node contain text not CDATA, get it + val = node->value(); + } + } + return true; + } + bool Get(decoder&de, bool &val, const Extend*ext) { + (void)ext; + std::string v = get_val(Extend::XmlContent(ext)); + if (v=="1" || v=="true" || v=="TRUE" || v=="True") { + val = true; + } else if (v=="0" || v=="false" || v=="FALSE" || v=="False") { + val = false; + } else { + de.decode_exception("parse bool fail.", NULL); + } + return true; + } + template + typename x_enable_if::is_integer, bool>::type Get(decoder&de, T &val, const Extend*ext){ + (void)ext; + std::string v = get_val(Extend::XmlContent(ext)); + if (Util::atoi(v, val)) { + return true; + } else { + de.decode_exception("parse int fail. not integer or overflow", NULL); + return false; + } + } + template + typename x_enable_if::is_float, bool>::type Get(decoder&de, T &val, const Extend*ext){ + (void)ext; + std::string v = get_val(Extend::XmlContent(ext)); + if (1==v.length() && v[0]=='-') { + return false; + } + + const char *data = v.c_str(); + char *end; + double d = strtod(data, &end); + if ((size_t)(end-data) == v.length()) { + val = (T)d; + return true; + } + de.decode_exception("parse double fail.", NULL); + return false; + } + +private: + typedef std::map node_index; // index of _childs + + void init() { + inited = true; + if (NULL != node) { + Node *tmp = node->first_node(); + for (size_t i=0; tmp; tmp=tmp->next_sibling(), ++i) { + _childs.push_back(tmp); + _childs_index[tmp->name()] = i; + } + } + } + void initsbs(const XmlNode&parent, const char *key) { + inited = true; + for (size_t i=0; iname())) { + _childs.push_back(parent._childs[i]); + } + } + } + + std::string get_val(bool forceContent=false) { + if (forceContent || attr==NULL) { + return node->value(); + } else if (attr != NULL) { + return attr->value(); + } else { + return ""; + } + } + + const Node* node; // current node + rapidxml::xml_attribute *attr; + bool inited; // delay init to avoid copy _childs and _childs_index + + std::vector _childs; // childs + node_index _childs_index; +}; + + +class XmlDecoder { +public: + template + bool decode(const std::string&str, T&val, bool with_root=false) { + std::string tmp = str; + return this->decode_indata(tmp, val, with_root); + } + template + bool decode_file(const std::string&fname, T&val, bool with_root=false) { + std::string data; + bool ret = Util::readfile(fname, data); + if (ret) { + ret = this->decode_indata(data, val, with_root); + } + return ret; + } +private: + template + bool decode_indata(std::string&str, T&val, bool with_root=false) { + rapidxml::xml_document<> de; + std::string err; + try { + de.parse<0>((char*)str.c_str()); + } catch (const rapidxml::parse_error&e) { + err = std::string("parse xml fail. err=")+e.what()+". "+std::string(e.where()).substr(0, 32); + } catch (const std::exception&e) { + err = std::string("parse xml fail. unknow exception. err=")+e.what(); + } + + if (!err.empty()) { + throw std::runtime_error(err); + } + + if (!with_root) { + XmlNode node(de.first_node()); + return XDecoder(NULL, (const char*)NULL, node).decode(val, NULL); + } else { + XmlNode node(&de); + return XDecoder(NULL, (const char*)NULL, node).decode(val, NULL); + } + } +}; + +} + +#endif diff --git a/util/xpack/xml_encoder.h b/util/xpack/xml_encoder.h new file mode 100755 index 0000000..1f5f385 --- /dev/null +++ b/util/xpack/xml_encoder.h @@ -0,0 +1,371 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_XML_ENCODER_H +#define __X_PACK_XML_ENCODER_H + +#include +#include +#include "xencoder.h" + + +namespace xpack { + +class XmlWriter { + struct Attr { + const char* key; + std::string val; + Attr(const char*_key, const std::string&_val):key(_key), val(_val){} + }; + struct Node { + Node(const char*_key=NULL, const Extend *ext=NULL) { + (void)ext; + if (_key != NULL) { + key = _key; + } + } + ~Node() { + std::list::iterator it; + for (it = childs.begin(); it!=childs.end(); ++it) { + delete *it; + } + } + + std::string key; // key for this node + std::string val; // value for this node, only for leaf node + std::list attrs; // attribute + std::list childs;// child nodes + + std::string vec_key; // key for vector + }; + + friend class XEncoder; + friend class XmlEncoder; + const static bool support_null = false; +public: + XmlWriter(int indentCount = -1, char indentChar = ' ', int decimalPlaces = 324):_indentCount(indentCount),_indentChar(indentChar),_decimalPlaces(decimalPlaces) { + if (_indentCount > 0) { + if (_indentChar!=' ' && _indentChar!='\t') { + throw std::runtime_error("indentChar must be space or tab"); + } + } + _cur = &_root; + } +private: + std::string String() { + merge(); + return _output; + } + inline static const char *Name() { + return "xml"; + } + inline const char *IndexKey(size_t index) { + (void)index; + return _cur->vec_key.c_str(); + } + void ArrayBegin(const char *key, const Extend *ext) { + Node *n = new Node(key, ext); + + if (NULL!=_cur && !_cur->vec_key.empty()) { // vector> + n->vec_key = n->key; + } else if (NULL != ext && NULL != ext->alias) { + if (!ext->alias->Flag("xml", "vl", &n->vec_key)) { // forward compatible, support vector label + if (ext->alias->Flag("xml", "sbs")) { // no top label, vector item will side by side + n->vec_key.swap(n->key); // n->vec_key=key and n->key=""; + } else { + n->vec_key = n->key; // has top level + } + } + } else { + n->vec_key = n->key; + } + + _cur->childs.push_back(n); + + _stack.push_back(_cur); + _cur = n; + } + void ArrayEnd(const char *key, const Extend *ext) { + (void)key; + (void)ext; + _cur = _stack.back(); + _stack.pop_back(); + } + void ObjectBegin(const char *key, const Extend *ext) { + Node *n = new Node(key, ext); + _cur->childs.push_back(n); + + _stack.push_back(_cur); + _cur = n; + } + void ObjectEnd(const char *key, const Extend *ext) { + (void)key; + (void)ext; + _cur = _stack.back(); + _stack.pop_back(); + } + bool WriteNull(const char*key, const Extend *ext) { + static std::string empty; + return this->encode_string(key, empty, ext); + } + // string + bool encode_string(const char*key, const std::string &val, const Extend *ext) { + if (Extend::Attribute(ext)) { + _cur->attrs.push_back(Attr(key, string_quote(val))); + } else if (Extend::XmlContent(ext)) { + _cur->val = val; + } else if (!Extend::AliasFlag(ext, "xml", "cdata")) { + Node *n = new Node(key); + n->val = string_quote(val); + _cur->childs.push_back(n); + } else { + Node *n = new Node(key); + n->val = ""; + _cur->childs.push_back(n); + } + return true; + } + // bool + bool encode_bool(const char*key, const bool &val, const Extend *ext) { + std::string bval; + if (val) { + bval = "true"; + } else { + bval = "false"; + } + + if (Extend::Attribute(ext)) { + _cur->attrs.push_back(Attr(key, bval)); + } else if (Extend::XmlContent(ext)) { + _cur->val = bval; + } else { + Node *n = new Node(key); + n->val = bval; + _cur->childs.push_back(n); + } + return true; + } + // integer + template + typename x_enable_if::is_integer, bool>::type encode_number(const char*key, const T& val, const Extend *ext) { + if (val==0 && Extend::OmitEmpty(ext)) { + return false; + } else if (Extend::Attribute(ext)) { + _cur->attrs.push_back(Attr(key, Util::itoa(val))); + } else if (Extend::XmlContent(ext)) { + _cur->val = Util::itoa(val); + } else { + Node *n = new Node(key); + n->val = Util::itoa(val); + _cur->childs.push_back(n); + } + return true; + } + + // float + template + typename x_enable_if::is_float, bool>::type encode_number(const char*key, const T &val, const Extend *ext) { + if (val==0 && Extend::OmitEmpty(ext)) { + return false; + } else { + std::ostringstream os; + os.precision(_decimalPlaces+1); + os<attrs.push_back(Attr(key, fval)); + } else if (Extend::XmlContent(ext)) { + _cur->val = fval; + } else { + Node *n = new Node(key); + n->val = fval; + _cur->childs.push_back(n); + } + } + return true; + } + + // escape string + static std::string string_quote(const std::string &val) { + std::string ret; + ret.reserve(val.length()*2); + + for (size_t i=0; i': + ret += ">"; + break; + case '&': + ret += "&"; + break; + case '\'': + ret += "'"; + break; + case '\"': + ret += """; + break; + default: + if(c >= ' ') { + ret.push_back(val[i]); + } else { + ret += "\\x"; + unsigned char uch = (unsigned char)val[i]; + unsigned char h = (uch>>4)&0xf; + unsigned char l = uch&0xf; + if (h < 10) { + ret.push_back(h+'0'); + } else { + ret.push_back(h-10+'A'); + } + if (l < 10) { + ret.push_back(l+'0'); + } else { + ret.push_back(l-10+'A'); + } + } + } + } + + return ret; + } + + + + void appendNode(const Node *nd, int depth) { + bool indentEnd = true; + + if (!nd->key.empty()) { // for array that without label + indent(depth); + _output.push_back('<'); + _output += nd->key; + } else if (depth > 0) { + depth--; + } + + // output attribute + std::list::const_iterator it; + for (it=nd->attrs.begin(); it!=nd->attrs.end(); ++it) { + _output.push_back(' '); + _output += it->key; + _output += "=\""; + _output += it->val; + _output.push_back('"'); + } + + // no content, end object + if (nd->val.empty() && nd->childs.size()==0) { + if (!nd->key.empty()) { + _output += "/>"; + } + return; + } + + // key finished + if (!nd->key.empty()) { + _output.push_back('>'); + } + if (!nd->val.empty()) { + _output += nd->val; + indentEnd = false; + } else { + std::list::const_iterator it; + for (it=nd->childs.begin(); it!=nd->childs.end(); ++it) { + appendNode(*it, depth+1); + } + } + + if (!nd->key.empty()) { + if (indentEnd) { + indent(depth); + } + _output += "key; + _output.push_back('>'); + } + } + void merge() { + if (_output.length() > 0 || _root.childs.size()==0) { + return; + } + + appendNode(_root.childs.front(), 0); + } + + void indent(int depth) { + if (_indentCount < 0) { + return; + } + _output.push_back('\n'); + if (_indentCount == 0) { + return; + } + for (int i=0; i _stack; + + std::string _output; + + int _indentCount; + char _indentChar; + + int _decimalPlaces; +}; + + +class XmlEncoder { +public: + XmlEncoder() { + indentCount = -1; + indentChar = ' '; + maxDecimalPlaces = 324; + } + XmlEncoder(int _indentCount, char _indentChar, int _maxDecimalPlaces = 324) { // compat + indentCount = _indentCount; + indentChar = _indentChar; + maxDecimalPlaces = _maxDecimalPlaces; + } + + void SetMaxDecimalPlaces(int _maxDecimalPlaces) { + maxDecimalPlaces = _maxDecimalPlaces; + } + + template + std::string encode(const T&val, const std::string&root) { + XmlWriter wr(indentCount, indentChar, maxDecimalPlaces); + XEncoder en(wr); + en.encode(root.c_str(), val, NULL); + return wr.String(); + } + +private: + int indentCount; + char indentChar; + int maxDecimalPlaces; +}; + +} + +#endif diff --git a/util/xpack/xpack.h b/util/xpack/xpack.h new file mode 100755 index 0000000..7bc9d69 --- /dev/null +++ b/util/xpack/xpack.h @@ -0,0 +1,201 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +#ifndef __X_PACK_H +#define __X_PACK_H + +#include "extend.h" +#include "l1l2_expand.h" +#include "traits.h" + +/* + work with X_PACK_N to expand macro + struct MyX { + int a; + std::string b; + double c; + XPACK(A(a,"_id"), O(b,c)); + }; + + macro expand order: + XPACK(A(a,"_id"), O(b,c)) + --> X_PACK_N(X_PACK_L1, X_PACK_L1_DECODE, , A(a, "_id"), O(b,c)) + --> X_PACK_L1_2(X_PACK_L1_DECODE, A(a, "_id"), O(b,c)) + --> X_PACK_L1_DECODE(A(a, "_id")) X_PACK_L1_DECODE(O(b,c)) + --> X_PACK_L1_DECODE_A(a, "_id") X_PACK_L1_DECODE_O(b,c) + --> X_PACK_ACT_DECODE_A(a, "_id") X_PACK_N2(X_PACK_L2, X_PACK_ACT_DECODE_O, b, c) // https://gcc.gnu.org/onlinedocs/cpp/Self-Referential-Macros.html so we need define X_PACK_N2. if use X_PACK_N preprocessor will treat is as Self-Referential-Macros + --> X_PACK_ACT_DECODE_A(a, "_id") X_PACK_L2_2(X_PACK_ACT_DECODE_O, b, c) + --> X_PACK_ACT_DECODE_A(a, "_id") X_PACK_ACT_DECODE_O(b) X_PACK_ACT_DECODE_O(c) + --> // expand to convert code +*/ + +// flag only work for this member +#define X_EXPAND_FLAG_F(...) int __x_pack_flag = 0 X_PACK_N2(X_PACK_L2, X_PACK_ACT_FLAG, 0, __VA_ARGS__) ; +#define X_PACK_ACT_FLAG(ARG, F) | X_PACK_FLAG_##F + +/* + X(F(x,y,z), member1, member2, ....) + O same as X(F(0), member1, member2, ...) + M same as X(F(M), member1, member2, ...) + A Alias. A(member1, alias1, member2, alias2, ...) + AF Alias with Flag. A(F(x,y,z), member1, alias1, member2, alias2 ...) + I Inherit + B bitfield +*/ + +/////////////////////////// XPACK ///////////////////////////// +//=======DECODE +#define X_PACK_L1_DECODE(x) { X_PACK_L1_DECODE_##x } +//---- +#define X_PACK_L1_DECODE_X(FLAG, ...) X_EXPAND_FLAG_##FLAG xpack::Extend __x_pack_ext(__x_pack_flag, NULL); X_PACK_N2(X_PACK_L2, X_PACK_DECODE_ACT_O, 0, __VA_ARGS__) +#define X_PACK_L1_DECODE_E(FLAG, ...) X_EXPAND_FLAG_##FLAG xpack::Extend __x_pack_ext(__x_pack_flag, NULL); X_PACK_N2(X_PACK_L2, X_PACK_DECODE_ACT_E, 0, __VA_ARGS__) +#define X_PACK_L1_DECODE_B(FLAG, ...) X_EXPAND_FLAG_##FLAG xpack::Extend __x_pack_ext(__x_pack_flag, NULL); X_PACK_N2(X_PACK_L2, X_PACK_DECODE_ACT_B, 0, __VA_ARGS__) +#define X_PACK_L1_DECODE_AF(FLAG, ...) X_EXPAND_FLAG_##FLAG X_PACK_N2(X_PACK_L2_2, X_PACK_DECODE_ACT_A, 0, __VA_ARGS__) // extend define in ACTION +//-----customer decoder--------- +#define X_PACK_L1_DECODE_C(CUSTOM, FLAG, ...) X_EXPAND_FLAG_##FLAG xpack::Extend __x_pack_ext(__x_pack_flag, NULL); X_PACK_N2(X_PACK_L2, X_PACK_DECODE_ACT_C, CUSTOM, __VA_ARGS__) + +#define X_PACK_L1_DECODE_O(...) X_PACK_L1_DECODE_X(F(0), __VA_ARGS__) +#define X_PACK_L1_DECODE_M(...) X_PACK_L1_DECODE_X(F(M), __VA_ARGS__) +#define X_PACK_L1_DECODE_A(...) X_PACK_L1_DECODE_AF(F(0), __VA_ARGS__) + +#define X_PACK_L1_DECODE_I(...) X_PACK_N2(X_PACK_L2, X_PACK_DECODE_ACT_I, 0, __VA_ARGS__) +//=======ENCODE +#define X_PACK_L1_ENCODE(x) { X_PACK_L1_ENCODE_##x } +//----- +#define X_PACK_L1_ENCODE_X(FLAG, ...) X_EXPAND_FLAG_##FLAG xpack::Extend __x_pack_ext(__x_pack_flag, NULL); X_PACK_N2(X_PACK_L2, X_PACK_ENCODE_ACT_O, 0, __VA_ARGS__) +#define X_PACK_L1_ENCODE_E(FLAG, ...) X_EXPAND_FLAG_##FLAG xpack::Extend __x_pack_ext(__x_pack_flag, NULL); X_PACK_N2(X_PACK_L2, X_PACK_ENCODE_ACT_E, 0, __VA_ARGS__) +#define X_PACK_L1_ENCODE_B(FLAG, ...) X_EXPAND_FLAG_##FLAG xpack::Extend __x_pack_ext(__x_pack_flag, NULL); X_PACK_N2(X_PACK_L2, X_PACK_ENCODE_ACT_B, 0, __VA_ARGS__) +#define X_PACK_L1_ENCODE_AF(FLAG, ...) X_EXPAND_FLAG_##FLAG X_PACK_N2(X_PACK_L2_2, X_PACK_ENCODE_ACT_A, 0, __VA_ARGS__) // extend define in ACTION +#define X_PACK_L1_ENCODE_C(CUSTOM, FLAG, ...) X_EXPAND_FLAG_##FLAG xpack::Extend __x_pack_ext(__x_pack_flag, NULL); X_PACK_N2(X_PACK_L2, X_PACK_ENCODE_ACT_C, CUSTOM, __VA_ARGS__) + +#define X_PACK_L1_ENCODE_O(...) X_PACK_L1_ENCODE_X(F(0), __VA_ARGS__) +#define X_PACK_L1_ENCODE_M(...) X_PACK_L1_ENCODE_X(F(M), __VA_ARGS__) +#define X_PACK_L1_ENCODE_A(...) X_PACK_L1_ENCODE_AF(F(0), __VA_ARGS__) +//----- +#define X_PACK_L1_ENCODE_I(...) X_PACK_N2(X_PACK_L2, X_PACK_ENCODE_ACT_I, 0, __VA_ARGS__) + + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ decode act ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +#define X_PACK_DECODE_ACT_O(ARG, M) \ + __x_pack_ret |= __x_pack_obj.decode(#M, __x_pack_self.M, &__x_pack_ext); + +#define X_PACK_DECODE_ACT_C(CUSTOM, M) \ + __x_pack_ret |= CUSTOM##_decode(__x_pack_obj, __x_pack_self, #M, __x_pack_self.M, &__x_pack_ext); + +// enum for not support c++11 +#ifndef X_PACK_SUPPORT_CXX0X +#define X_PACK_DECODE_ACT_E(ARG, M) \ + __x_pack_ret |= __x_pack_obj.decode(#M, *((int*)&__x_pack_self.M), &__x_pack_ext); +#else +#define X_PACK_DECODE_ACT_E X_PACK_DECODE_ACT_O +#endif + +#define X_PACK_DECODE_ACT_A(ARG, M, NAME) \ + { \ + static xpack::Alias __x_pack_alias(#M, NAME); \ + xpack::Extend __x_pack_ext(__x_pack_flag, &__x_pack_alias); \ + const char *__new_name = __x_pack_alias.Name(__x_pack_obj.Name());\ + __x_pack_ret |= __x_pack_obj.decode(__new_name, __x_pack_self.M, &__x_pack_ext); \ + } + +// Inheritance B::__x_pack_decode(__x_pack_obj) +#define X_PACK_DECODE_ACT_I(ARG, P) \ + { \ + xpack::Extend __x_pack_tmp_ext(0,NULL); __x_pack_tmp_ext.ctrl_flag |= X_PACK_CTRL_FLAG_INHERIT;\ + __x_pack_ret |= __x_pack_obj.decode(static_cast(__x_pack_self), &__x_pack_tmp_ext); \ + } + +// bitfield, not support alias +#define X_PACK_DECODE_ACT_B(ARG, B) \ + { \ + x_pack_decltype(__x_pack_self.B) __x_pack_tmp = 0; \ + __x_pack_ret |= __x_pack_obj.decode(#B, __x_pack_tmp, &__x_pack_ext); \ + __x_pack_self.B = __x_pack_tmp;\ + } + +// ~~~~~~~~~~~~~~~~~~~~~~~ encode act ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +#define X_PACK_ENCODE_ACT_O(ARG, M) \ + __x_pack_ret |= __x_pack_obj.encode(#M, __x_pack_self.M, &__x_pack_ext); +#define X_PACK_ENCODE_ACT_C(CUSTOM, M) \ + __x_pack_ret |= CUSTOM##_encode(__x_pack_obj, __x_pack_self, #M, __x_pack_self.M, &__x_pack_ext); + +#ifndef X_PACK_SUPPORT_CXX0X +#define X_PACK_ENCODE_ACT_E(ARG, M) \ + __x_pack_ret |= __x_pack_obj.encode(#M, (const int&)__x_pack_self.M, &__x_pack_ext); +#else +#define X_PACK_ENCODE_ACT_E X_PACK_ENCODE_ACT_O +#endif + +#define X_PACK_ENCODE_ACT_A(ARG, M, NAME) \ + { \ + static xpack::Alias __x_pack_alias(#M, NAME); \ + xpack::Extend __x_pack_ext(__x_pack_flag, &__x_pack_alias); \ + const char *__new_name = __x_pack_alias.Name(__x_pack_obj.Name());\ + __x_pack_ret |= __x_pack_obj.encode(__new_name, __x_pack_self.M, &__x_pack_ext); \ + } + +#define X_PACK_ENCODE_ACT_B(ARG, M) \ + __x_pack_obj.encode(#M, __x_pack_self.M, &__x_pack_ext); + +#define X_PACK_ENCODE_ACT_I(ARG, P) \ + { \ + xpack::Extend __x_pack_tmp_ext(0,NULL); __x_pack_tmp_ext.ctrl_flag |= X_PACK_CTRL_FLAG_INHERIT;\ + __x_pack_ret |= __x_pack_obj.encode(NULL, static_cast(__x_pack_self), &__x_pack_tmp_ext); \ + } + + +// for mark defined XPACK +#define X_PACK_COMMON \ +public: \ + static bool const __x_pack_value = true; + +// decode function +#define X_PACK_DECODE_BEGIN \ + template \ + bool __x_pack_decode(__X_PACK_DOC& __x_pack_obj, __X_PACK_ME &__x_pack_self, const xpack::Extend *__x_pack_extp) {(void)__x_pack_extp; bool __x_pack_ret = false; + +// encode function +#define X_PACK_ENCODE_BEGIN \ + template \ + bool __x_pack_encode(__X_PACK_DOC& __x_pack_obj, const __X_PACK_ME &__x_pack_self, const xpack::Extend *__x_pack_extp) const {(void)__x_pack_extp; bool __x_pack_ret = false; + + +// out decode function +#define X_PACK_DECODE_BEGIN_OUT(NAME) \ + template \ + bool __x_pack_decode_out(__X_PACK_DOC& __x_pack_obj, NAME & __x_pack_self, const xpack::Extend *__x_pack_extp) {(void)__x_pack_extp; bool __x_pack_ret = false; + +// out encode function +#define X_PACK_ENCODE_BEGIN_OUT(NAME) \ + template \ + bool __x_pack_encode_out(__X_PACK_DOC& __x_pack_obj, const NAME &__x_pack_self, const xpack::Extend *__x_pack_extp) {(void)__x_pack_extp; bool __x_pack_ret = false; + + +#define XPACK(...) \ + X_PACK_COMMON \ + X_PACK_DECODE_BEGIN X_PACK_N(X_PACK_L1, X_PACK_L1_DECODE, __VA_ARGS__) return __x_pack_ret; } \ + X_PACK_ENCODE_BEGIN X_PACK_N(X_PACK_L1, X_PACK_L1_ENCODE, __VA_ARGS__) return __x_pack_ret; } + +#define XPACK_OUT(NAME, ...) \ +namespace xpack { \ + template<> struct is_xpack_out {static bool const value = true;}; \ + X_PACK_DECODE_BEGIN_OUT(NAME) X_PACK_N(X_PACK_L1, X_PACK_L1_DECODE, __VA_ARGS__) return __x_pack_ret; } \ + X_PACK_ENCODE_BEGIN_OUT(NAME) X_PACK_N(X_PACK_L1, X_PACK_L1_ENCODE, __VA_ARGS__) return __x_pack_ret; } \ +} + +#endif + diff --git a/util/xpack/xpack.pri b/util/xpack/xpack.pri new file mode 100755 index 0000000..68c916b --- /dev/null +++ b/util/xpack/xpack.pri @@ -0,0 +1,3 @@ +INCLUDEPATH += $$PWD +DEFINES += XPACK_SUPPORT_QT +HEADERS += xpack.h diff --git a/util/xpack/yaml.h b/util/xpack/yaml.h new file mode 100755 index 0000000..78e0d8c --- /dev/null +++ b/util/xpack/yaml.h @@ -0,0 +1,48 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_YAML_H +#define __X_PACK_YAML_H + +#include "yaml_decoder.h" +#include "yaml_encoder.h" +#include "xpack.h" + +namespace xpack { + +class yaml { +public: + template + static void decode(const std::string &data, T &val) { + YamlDecoder de; + de.decode(data, val); + } + template + static void decode_file(const std::string &file_name, T &val) { + YamlDecoder de; + de.decode_file(file_name, val); + } + + template + static std::string encode(const T &val) { + YamlEncoder en; + return en.encode(val); + } +}; + +} + +#endif diff --git a/util/xpack/yaml_decoder.h b/util/xpack/yaml_decoder.h new file mode 100755 index 0000000..41a8640 --- /dev/null +++ b/util/xpack/yaml_decoder.h @@ -0,0 +1,123 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_YAML_DECODER_H +#define __X_PACK_YAML_DECODER_H + +#include "yaml-cpp/yaml.h" +#include "xdecoder.h" + +namespace xpack { + + +class YamlNode { + typedef XDecoder decoder; +public: + typedef YAML::const_iterator Iterator; + + YamlNode(const YAML::Node& node):n(node),valid(true){} + YamlNode():valid(false){} + + inline static const char * Name() { + return "yaml"; + } + operator bool() const { + return valid; + } + bool IsNull() const { + return valid && n.IsNull(); + } + YamlNode Find(decoder&de, const char*key, const Extend *ext) const { + (void)ext; + if (n.IsNull()) { + return YamlNode(); + } else if (!n.IsMap()) { + de.decode_exception("not map", NULL); + } + return YamlNode(n[key]); + } + size_t Size(decoder&de) const { + if (!n.IsSequence()) { + de.decode_exception("not sequence", NULL); + } + return (size_t)n.size(); + } + YamlNode At(size_t index) const { // no exception + return YamlNode(n[index]); + } + YamlNode Next(decoder&de, const YamlNode&parent, Iterator&iter, std::string&key) const { + if (parent.n.IsMap()) { + if ((void*)this != (void*)(&parent)) { + ++iter; + } else { + iter = parent.n.begin(); + } + if (iter != parent.n.end()) { + key = iter->first.as(); + return YamlNode(iter->second); + } + } else { + de.decode_exception("not map", NULL); + } + return YamlNode(); + } + + template + bool Get(decoder&de, T &val, const Extend*ext){ + (void)de; + (void)ext; + try { + val = n.as(); + return true; + } catch (const std::exception&e) { + de.decode_exception(e.what(), NULL); + } catch (...) { + de.decode_exception("type unmatch", NULL); + } + return false; + } + +private: + YAML::Node n; + bool valid; +}; + +class YamlDecoder { +public: + template + bool decode(const std::string&str, T&val) { + YAML::Node n = YAML::Load(str); + if (n) { + YamlNode node(n); + return XDecoder(NULL, (const char*)NULL, node).decode(val, NULL); + } + return false; + } + template + bool decode_file(const std::string&fname, T&val) { + YAML::Node n = YAML::LoadFile(fname); + if (n) { + YamlNode node(n); + return XDecoder(NULL, (const char*)NULL, node).decode(val, NULL); + } + return false; + } +}; + + +} + +#endif diff --git a/util/xpack/yaml_encoder.h b/util/xpack/yaml_encoder.h new file mode 100755 index 0000000..7fe44a6 --- /dev/null +++ b/util/xpack/yaml_encoder.h @@ -0,0 +1,146 @@ +/* +* Copyright (C) 2021 Duowan Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef __X_PACK_YAML_ENCODER_H +#define __X_PACK_YAML_ENCODER_H + +#include + +#include "yaml-cpp/yaml.h" + +#include "xencoder.h" + +namespace xpack { + +class YamlWriter:private noncopyable { + friend class XEncoder; + friend class YamlEncoder; + + const static bool support_null = true; +public: + YamlWriter(size_t indent = 2, int maxDecimalPlaces = -1) { + e.SetIndent(indent); + if (maxDecimalPlaces > 0) { + e.SetFloatPrecision((size_t)maxDecimalPlaces); + e.SetDoublePrecision((size_t)maxDecimalPlaces); + } + } + ~YamlWriter() { + } +private: + inline static const char *Name() { + return "yaml"; + } + inline const char *IndexKey(size_t index) { + (void)index; + return NULL; + } + std::string String() { + return std::string(e.c_str(), e.size()); + } + + void ArrayBegin(const char *key, const Extend *ext) { + (void)ext; + xpack_set_key(key); + e << YAML::Value << YAML::BeginSeq; + } + void ArrayEnd(const char *key, const Extend *ext) { + (void)key; + (void)ext; + e << YAML::EndSeq; + } + void ObjectBegin(const char *key, const Extend *ext) { + (void)ext; + xpack_set_key(key); + e << YAML::Value << YAML::BeginMap; + } + void ObjectEnd(const char *key, const Extend *ext) { + (void)key; + (void)ext; + e << YAML::EndMap; + } + bool WriteNull(const char*key, const Extend *ext) { + (void)ext; + xpack_set_key(key); + e << YAML::Value << YAML::Null; + return true; + } + bool encode_bool(const char*key, const bool&val, const Extend *ext) { + (void)ext; + xpack_set_key(key); + e << YAML::Value << val; + return true; + } + bool encode_string(const char*key, const std::string&val, const Extend *ext) { + (void)ext; + xpack_set_key(key); + if (!val.empty()) { + e << YAML::Value << val; + } else { + e << YAML::Value << YAML::Null; + } + return true; + } + template + typename x_enable_if::value, bool>::type encode_number(const char*key, const T&val, const Extend *ext) { + (void)ext; + xpack_set_key(key); + e << YAML::Value << val; + return true; + } + + void xpack_set_key(const char*key) { // openssl defined set_key macro, so we named it xpack_set_key + if (NULL!=key && key[0]!='\0') { + e << YAML::Key << key; + } + } + + YAML::Emitter e; +}; + +class YamlEncoder { +public: + YamlEncoder() { + indentCount = 2; + maxDecimalPlaces = -1; + } + + YamlEncoder& SetIndent(size_t indent) { + indentCount = indent; + return *this; + } + YamlEncoder& SetMaxDecimalPlaces(int _maxDecimalPlaces) { + maxDecimalPlaces = _maxDecimalPlaces; + return *this; + } + + template + std::string encode(const T&val) { + YamlWriter wr(indentCount, maxDecimalPlaces); + XEncoder en(wr); + en.encode(NULL, val, NULL); + return wr.String(); + } + +private: + size_t indentCount; + int maxDecimalPlaces; +}; + + +} + +#endif diff --git a/video/CMakeLists.txt b/video/CMakeLists.txt new file mode 100644 index 0000000..c01b3db --- /dev/null +++ b/video/CMakeLists.txt @@ -0,0 +1,139 @@ +set(target video) + +add_compile_definitions( + # 0d8c:0016: 板载声卡 + -DBOARD_SOUND_CARD_ID="USB0d8c:0016" +) + +aux_source_directory(. VIDEO_SOURCES) + +aux_source_directory(core CORE_SOURCES) +list(APPEND VIDEO_SOURCES ${CORE_SOURCES}) + +aux_source_directory(v4l2 CORE_V4L2_SOURCES) +list(APPEND VIDEO_SOURCES ${CORE_V4L2_SOURCES}) + +aux_source_directory(alsa ALSA_SOURCES) +list(APPEND VIDEO_SOURCES ${ALSA_SOURCES}) + +if (USING_HISIMPP5) + aux_source_directory(hisi/mpp5 HISI_SOURCES) + aux_source_directory(hisi/mpp5/audio-adp HISI_ADP_SOURCES) + list(APPEND VIDEO_SOURCES ${HISI_SOURCES}) + list(APPEND VIDEO_SOURCES ${HISI_ADP_SOURCES}) +endif() + +if (USING_ITE_FRONTEND) + aux_source_directory(ite ITE_SOURCES) + list(APPEND VIDEO_SOURCES ${ITE_SOURCES}) +endif() + +if (USING_LIBOPUS) +aux_source_directory(libopus LIBOPUS_SOURCES) +list(APPEND VIDEO_SOURCES ${LIBOPUS_SOURCES}) +endif() + +if (USING_SHINE) +aux_source_directory(shine SHINE_SOURCES) +list(APPEND VIDEO_SOURCES ${SHINE_SOURCES}) +endif() + +aux_source_directory(ffmpeg FFMPEG_SOURCES) +list(APPEND VIDEO_SOURCES ${FFMPEG_SOURCES}) + +aux_source_directory(user/alsa ALSA_USER_SOURCES) +list(APPEND VIDEO_SOURCES ${ALSA_USER_SOURCES}) + +aux_source_directory(mediaserver MEDIA_SERVER_USER_SOURCES) +list(APPEND VIDEO_SOURCES ${MEDIA_SERVER_USER_SOURCES}) + +aux_source_directory(user USER_SOURCES) + +list(APPEND CMAKE_PREFIX_PATH $ENV{ALSALIB_PATH}) +find_package(ALSA REQUIRED) + +include_directories( + $ENV{MPP_PATH}/include + $ENV{FFMPEG_PATH}/include + $ENV{CIMG_PATH} + $ENV{LIBJPEG_PATH}/include + $ENV{LIBPNG_PATH}/include + $ENV{FREETYPE_PATH}/include/freetype2 + $ENV{OSIP_PATH}/include + $ENV{EXOSIP_PATH}/include + $ENV{UCHARDET_PATH}/include + $ENV{LIBOPUS_PATH}/include + $ENV{SHINE_PATH}/include + $ENV{MEDIA_SERVER_PATH}/include + $ENV{MEDIA_SERVER_PATH}/include/avcodec/h264 + ${ALSA_INCLUDE_DIR} +) + +add_library(${target} STATIC + ${VIDEO_SOURCES} + ${USER_SOURCES} +) +add_definitions( + -Dcimg_display=0 + -Dcimg_use_jpeg + -Dcimg_use_png +) + +target_link_libraries(${target} PRIVATE + $ENV{LIBJPEG_PATH}/lib/libjpeg.a + $ENV{LIBPNG_PATH}/lib/libpng.a + $ENV{FREETYPE_PATH}/lib/libfreetype.a + $ENV{EXOSIP_PATH}/lib/libeXosip2.a + $ENV{OSIP_PATH}/lib/libosip2.a + $ENV{OSIP_PATH}/lib/libosipparser2.a + $ENV{UCHARDET_PATH}/lib/libuchardet.a + $ENV{OPENJPEG_PATH}/lib/libopenjp2.a + ${ALSA_LIBRARIES} +) + +if (USING_ZLMEDIAKIT) + message("Using ZLMediaKit...") + add_subdirectory(zlmediakit) + add_definitions(-DUSING_ZLMEDIAKIT) + target_include_directories(${target} PUBLIC zlmediakit) + target_link_libraries(${target} PRIVATE zlmediakit) +endif() + +if (USING_FFMPEG) + add_compile_definitions(-DUSING_FFMPEG -D__STDC_CONSTANT_MACROS) + target_link_libraries(${target} PRIVATE + $ENV{FFMPEG_PATH}/lib/libavdevice.a + $ENV{FFMPEG_PATH}/lib/libavfilter.a + $ENV{FFMPEG_PATH}/lib/libavformat.a + $ENV{FFMPEG_PATH}/lib/libavcodec.a + $ENV{FFMPEG_PATH}/lib/libavutil.a + $ENV{FFMPEG_PATH}/lib/libswscale.a + $ENV{FFMPEG_PATH}/lib/libswresample.a + $ENV{X264_PATH}/lib/libx264.a + $ENV{X265_PATH}/lib/libx265.a + $ENV{LIBMP3LAME_PATH}/lib/libmp3lame.a + dl + ) +endif() + +if (USING_MEDIASERVER) +target_link_libraries(${target} PRIVATE + $ENV{MEDIA_SERVER_PATH}/librtmp.a + $ENV{MEDIA_SERVER_PATH}/libflv.a + $ENV{MEDIA_SERVER_PATH}/libmpeg.a + $ENV{MEDIA_SERVER_PATH}/libh264.a + $ENV{MEDIA_SERVER_PATH}/libh265.a +) +endif() + +if (USING_LIBOPUS) +target_link_libraries(${target} PRIVATE + $ENV{LIBOPUS_PATH}/lib/libopus.a +) +endif() + +if (USING_SHINE) +target_link_libraries(${target} PRIVATE + $ENV{SHINE_PATH}/lib/libshine.a +) +endif() diff --git a/video/VideoSubsystem.cpp b/video/VideoSubsystem.cpp new file mode 100644 index 0000000..190deb6 --- /dev/null +++ b/video/VideoSubsystem.cpp @@ -0,0 +1,179 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Logger.h" +#include "VideoSubsystem.h" + +#include "core/Demuxer.h" +#include "core/AudioDecoder.h" +#include "core/AudioMixer.h" + +#include "v4l2/Input.h" +#include "alsa/Output.h" + +#include "libopus/AudioEncoder.h" +#include "libopus/AudioDecodeHandler.h" +#include "shine/AudioEncoder.h" + +#include "hisi/mpp5/System.h" +#include "hisi/mpp5/VideoInput.h" +#include "hisi/mpp5/VideoEncoder.h" +#include "hisi/mpp5/VideoDecoder.h" +#include "hisi/mpp5/VideoProcessor.h" +#include "hisi/mpp5/VideoMixer.h" +#include "hisi/mpp5/VideoOverlay.h" +#include "hisi/mpp5/AudioEncoder.h" +#include "hisi/mpp5/HDMIAudioOutput.h" +#include "hisi/mpp5/HDMIVideoOutput.h" + +#if defined(USING_FFMPEG) +#include "ffmpeg/Muxer.h" +#include "ffmpeg/DemuxHandler.h" +#include "ffmpeg/AudioEncoder.h" +#include "ffmpeg/AudioDecodeHandler.h" +#include "ffmpeg/AudioResample.h" +extern "C" +{ +#include +} +#endif + +#include "mediaserver/RTMPMuxer.h" +#include "mediaserver/MPEGMuxer.h" +#include "mediaserver/RTMPDemuxHandler.h" +#include + +namespace Video +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoSubsystem::VideoSubsystem() +{ +} + +VideoSubsystem::~VideoSubsystem() +{ +} + +const char* VideoSubsystem::name() const +{ + return "Video"; +} + +void VideoSubsystem::initialize(Poco::Util::Application& app) +{ + socket_init(); + + Core::System::registerClass(); + + Core::VideoEncoder::registerClass(Core::CodecID::H264); + Core::VideoEncoder::registerClass(Core::CodecID::H265); + Core::VideoDecoder::registerClass(0); + Core::VideoProcessor::registerClass(0); + Core::VideoMixer::registerClass(0); + Core::VideoOverlay::registerClass(Core::VideoOverlayType::CANVAS); + Core::VideoOverlay::registerClass(Core::VideoOverlayType::MOSAIC); + Core::VideoOutput::registerClass("HDMI"); + + Core::AudioEncoder::registerClass(Core::CodecID::AAC); + Core::AudioDecoder::registerClass(0); + Core::AudioMixer::registerClass(0); + Core::AudioOutput::registerClass("ALSA"); + Core::AudioOutput::registerClass("HDMI"); + + Core::AudioEncoder::registerClass(Core::CodecID::OPUS); + Core::AudioDecodeHandler::registerClass(Core::CodecID::OPUS); + Core::AudioEncoder::registerClass(Core::CodecID::MP3); + +#if defined(USING_FFMPEG) + + Core::Muxer::registerClass("rtsp"); + + Core::DemuxHandler::registerClass("rtmp"); + Core::DemuxHandler::registerClass("rtsp"); + Core::DemuxHandler::registerClass("file"); + + Core::AudioEncoder::registerClass(Core::CodecID::G711A); + Core::AudioEncoder::registerClass(Core::CodecID::G711U); + + Core::AudioResample::registerClass(0); + + Core::AudioDecodeHandler::registerClass(Core::CodecID::AAC); + Core::AudioDecodeHandler::registerClass(Core::CodecID::G711A); + Core::AudioDecodeHandler::registerClass(Core::CodecID::G711U); +#endif + + Core::Muxer::registerClass("rtmp"); + Core::Muxer::registerClass("udp"); + Core::DemuxHandler::registerClass("rtmp"); + + MPP5::VideoInput::initModule(); + MPP5::VideoEncoder::initModule(); + MPP5::VideoDecoder::initModule(); + MPP5::AudioEncoder::initModule(); +} + +void VideoSubsystem::uninitialize() +{ + stopAll(); +#ifdef USING_ONVIF + ONVIF::ONVIFService::getIns()->stop(); +#endif + MPP5::AudioEncoder::deinitModule(); + MPP5::VideoInput::deinitModule(); + MPP5::VideoEncoder::deinitModule(); + + socket_cleanup(); +} + +#if defined(USING_FFMPEG) +void VideoSubsystem::ffmpegLogCallback(void* ptr, int level, const char* fmt, va_list args) +{ + if (level > av_log_get_level()) + return; + + char str[4096]; + int len = sizeof(str); + + int n = vsnprintf(str, len, fmt, args); + if (n >= len) + n = len - 1; + + switch (level) + { + case AV_LOG_TRACE: + logger().trace("FFMPEG: " + std::string(str, n)); + break; + + case AV_LOG_VERBOSE: + case AV_LOG_DEBUG: + logger().debug("FFMPEG: " + std::string(str, n)); + break; + + case AV_LOG_INFO: + logger().information("FFMPEG: " + std::string(str, n)); + break; + + case AV_LOG_WARNING: + logger().warning("FFMPEG: " + std::string(str, n)); + break; + + case AV_LOG_ERROR: + logger().error("FFMPEG: " + std::string(str, n)); + break; + + case AV_LOG_FATAL: + logger().fatal("FFMPEG: " + std::string(str, n)); + break; + + default: + break; + } +} +#endif +} diff --git a/video/VideoSubsystem.h b/video/VideoSubsystem.h new file mode 100644 index 0000000..0a0a163 --- /dev/null +++ b/video/VideoSubsystem.h @@ -0,0 +1,41 @@ +#ifndef __VIDEO_SUBSYSTEM__H__ +#define __VIDEO_SUBSYSTEM__H__ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Poco/Util/Application.h" +#include "Poco/Util/Subsystem.h" +#include "Application.h" +#include "util/Subsystem.h" +#include + +namespace Video +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class VideoSubsystem: public Util::Subsystem +{ +public: + VideoSubsystem(); + ~VideoSubsystem(); + + virtual const char* name() const; + +protected: + virtual void initialize(Poco::Util::Application& app) override; + virtual void uninitialize(); + +protected: +#if defined(USING_FFMPEG) + static void ffmpegLogCallback(void* ptr, int level, const char* fmt, va_list vl); +#endif + +private: +}; +} +#endif diff --git a/video/alsa/Input.cpp b/video/alsa/Input.cpp new file mode 100644 index 0000000..db9fbc2 --- /dev/null +++ b/video/alsa/Input.cpp @@ -0,0 +1,144 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Input.h" +#include "Util.h" +#include "../core/System.h" +#include "Logger.h" + +namespace Video +{ +namespace ALSA +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Input::Input(const std::string& name): Core::AbstractAudioInput(name) +{ + setSoundCard(nullptr); +} + +Input::~Input() +{ +} + +void Input::start() +{ + _established = tryEstablishSoundCard(); + run(); +} + +void Input::stop() +{ + cancel(); + if (_established) + { + _pcm.close(); + _established = false; + } +} + +void Input::onConfigChanged(ParameterNames& names) +{ + if (!started()) + return; + + stop(); + start(); + AbstractAudioInput::onConfigChanged(names); +} + +Core::Frame::Ptr Input::readFrame() +{ + if (!_established) + _established = tryEstablishSoundCard(); + + if (!_established) + { + _traffic = 0; + return nullptr; + } + + Core::Frame::Ptr pFrame = new Core::Frame; + pFrame->length = _linesize; + pFrame->timestamp = Core::System::timestamp(); + int rc; + + if (SND_PCM_ACCESS_RW_NONINTERLEAVED == _access) + { + uint8_t* ptrs[_channels]; + for (int i = 0; i < _channels; i++) + { + pFrame->data[i] = new uint8_t[_linesize]; + ptrs[i] = pFrame->data[i].get(); + } + + rc = _pcm.read(ptrs, _samples); + } + else + { + pFrame->data[0] = new uint8_t[_linesize]; + uint8_t* ptr = pFrame->data[0].get(); + rc = _pcm.read(ptr, _samples); + } + + if (rc < 0) + { + _established = false; + _pcm.close(); + _traffic = 0; + return nullptr; + } + + _traffic++; + return pFrame; +} + +bool Input::tryEstablishSoundCard() +{ + auto pCard = getSoundCard(); + if (pCard) + { + int rc = _pcm.open(pCard, SND_PCM_STREAM_CAPTURE); + if (rc >= 0) + { + const auto sampleRate = Util::getSuitableSampleRate(pCard, getSampleRate()); + const auto access = Util::getSuitableAccess(pCard, sampleFormatIsPlanar(getSampleFormat())); + const auto format = Util::getSuitableSampleFormat(pCard, getSampleFormat()); + const auto sampleFormat = Util::convertSampleFormat(format, access == SND_PCM_ACCESS_RW_INTERLEAVED); + + logger().debug("%s: Suitable config found: %s %s %uHz", name(), std::string(snd_pcm_access_name(access)), + std::string(snd_pcm_format_name(format)), sampleRate); + + rc = _pcm.config(access, format, pCard->info().channels, sampleRate, getFrameSize(), + sampleFormatBytesPerSample(sampleFormat)); + if (rc >= 0) + { + if (pCard->info().channels == 1 && getChannelLayout() != Core::ChannelLayout::MONO) + setChannelLayout(Core::ChannelLayout::MONO); + + setSampleRate(sampleRate); + setSampleFormat(sampleFormat); + + _linesize = sampleFormatCalcLineSize(sampleFormat, getChannelLayout(), getFrameSize()); + _channels = channelLayoutChannels(getChannelLayout()); + _samples = getFrameSize(); + _access = access; + + serialCodeManager().increment(); + + return true; + } + + _pcm.close(); + } + } + + return false; +} +} +} diff --git a/video/alsa/Input.h b/video/alsa/Input.h new file mode 100644 index 0000000..58af1fb --- /dev/null +++ b/video/alsa/Input.h @@ -0,0 +1,53 @@ +#ifndef C0535B0C_9BD8_4FDC_9429_8FEF7E58BFCE +#define C0535B0C_9BD8_4FDC_9429_8FEF7E58BFCE +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../core/AbstractAudioInput.h" +#include "SoundCard.h" +#include "PCM.h" +#include "util/SimpleFactory.h" + +namespace Video +{ +namespace ALSA +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Input: public Core::AbstractAudioInput, public ::Util::SimpleFactory +{ +public: + using Ptr = Poco::AutoPtr; + +public: + Input(const std::string& name); + ~Input(); + +public: + CONFIGURABLE_DECLARE_PARAMETER(SoundCard, SoundCard::Ptr); + +protected: + void start() override; + void stop() override; + void onConfigChanged(ParameterNames& names) override; + + Core::Frame::Ptr readFrame() override; + + bool tryEstablishSoundCard(); + +private: + PCM _pcm; + std::atomic _established; + int _linesize; + int _channels; + int _samples; + snd_pcm_access_t _access; +}; +} +} +#endif diff --git a/video/alsa/Output.cpp b/video/alsa/Output.cpp new file mode 100644 index 0000000..fa42528 --- /dev/null +++ b/video/alsa/Output.cpp @@ -0,0 +1,162 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Output.h" +#include "PCM.h" +#include "SoundCard.h" +#include "Util.h" +#include "Logger.h" +#include "../core/System.h" + +namespace Video +{ +namespace ALSA +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Output::Output(const std::string& arg): Core::AudioOutput("ALSA::Output(" + arg + ")") +{ + auto pos = arg.find(':'); + _soundCardID = (pos != std::string::npos) ? arg.substr(pos + 1) : arg; +} + +Output::~Output() +{ +} + +void Output::initialize() +{ + setSoundCardEstablished(tryEstablishSoundCard()); + if (!getSoundCardEstablished()) + runSearchTask(); +} + +void Output::deinitialize() +{ + _searchTask.cancel(); + if (getSoundCardEstablished()) + _pcm.close(); +} + +void Output::onConfigChanged(ParameterNames& names) +{ +} + +void Output::writeFrame(Core::Frame::Ptr pFrame) +{ + if (!getSoundCardEstablished()) + return; + + int rc; + + if (SND_PCM_ACCESS_RW_NONINTERLEAVED == _access) + { + uint8_t* ptrs[_channels]; + for (int i = 0; i < _channels; i++) + ptrs[i] = pFrame->data[i].get(); + + rc = _pcm.write(ptrs, _samples); + } + else + { + rc = _pcm.write(pFrame->data[0].get(), _samples); + } + + if (rc < 0) + { + setSoundCardEstablished(false); + _pcm.close(); + } +} + +SoundCard::Ptr Output::getSoundCard() +{ + auto cards = SoundCard::getSoundCards([this](SoundCard::Ptr pCard) -> bool + { return (pCard->info().components == _soundCardID); }); + + if (cards.size() > 0) + return *(cards.begin()); + + return nullptr; +} + +bool Output::tryEstablishSoundCard() +{ + try + { + auto pCard = getSoundCard(); + if (pCard) + { + int rc = _pcm.open(pCard, SND_PCM_STREAM_PLAYBACK); + if (rc >= 0) + { + const auto sampleRate = Util::getSuitableSampleRate(pCard, getSampleRate()); + const auto access = Util::getSuitableAccess(pCard, sampleFormatIsPlanar(getSampleFormat())); + const auto format = Util::getSuitableSampleFormat(pCard, getSampleFormat()); + const auto sampleFormat = Util::convertSampleFormat(format, access == SND_PCM_ACCESS_RW_INTERLEAVED); + + logger().debug("%s: Suitable config found: %s %s %uHz", name(), + std::string(snd_pcm_access_name(access)), std::string(snd_pcm_format_name(format)), + sampleRate); + + rc = _pcm.config(access, format, pCard->info().channels, sampleRate, getFrameSize(), + sampleFormatBytesPerSample(sampleFormat)); + if (rc >= 0) + { + if (pCard->info().channels == 1 && getChannelLayout() != Core::ChannelLayout::MONO) + setChannelLayout(Core::ChannelLayout::MONO); + + setSampleRate(sampleRate); + setSampleFormat(sampleFormat); + + _channels = channelLayoutChannels(getChannelLayout()); + _samples = getFrameSize(); + _access = access; + + serialCodeManager().increment(); + return true; + } + + _pcm.close(); + } + } + } + catch (Poco::Exception& e) + { + logger().error("%s: %s", name(), e.displayText()); + } + catch (std::exception& e) + { + logger().error("%s: %s", name(), std::string(e.what())); + } + + return false; +} + +void Output::runSearchTask() +{ + if (_searchTask.isRunning()) + return; + + _searchTask.run( + [this](std::atomic& isCancelled) -> void + { + while (!isCancelled) + { + if (tryEstablishSoundCard()) + { + setSoundCardEstablished(true); + break; + } + + Poco::Thread::sleep(2000); + } + }); +} +} +} diff --git a/video/alsa/Output.h b/video/alsa/Output.h new file mode 100644 index 0000000..41fa92f --- /dev/null +++ b/video/alsa/Output.h @@ -0,0 +1,50 @@ +#ifndef A7586F47_A0A8_4B55_9701_8E79240D6736 +#define A7586F47_A0A8_4B55_9701_8E79240D6736 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../core/AudioOutput.h" +#include "PCM.h" +#include "SoundCard.h" +#include "util/Task.h" + +namespace Video +{ +namespace ALSA +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Output: public Core::AudioOutput +{ +public: + Output(const std::string& arg); + ~Output(); + +protected: + void initialize() override; + void deinitialize() override; + void onConfigChanged(ParameterNames& names) override; + void writeFrame(Core::Frame::Ptr pFrame) override; + + SoundCard::Ptr getSoundCard(); + bool tryEstablishSoundCard(); + void runSearchTask(); + + PROPERTY_DECLARE(SoundCardEstablished, bool, false, protected, protected) + +private: + std::string _soundCardID; + ::Util::Task _searchTask; + PCM _pcm; + snd_pcm_access_t _access; + int _samples; + int _channels; +}; +} +} +#endif diff --git a/video/alsa/PCM.cpp b/video/alsa/PCM.cpp new file mode 100644 index 0000000..bed0670 --- /dev/null +++ b/video/alsa/PCM.cpp @@ -0,0 +1,144 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "PCM.h" +#include +#include + +namespace Video +{ +namespace ALSA +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +PCM::PCM() +{ +} + +PCM::~PCM() +{ +} + +int PCM::open(SoundCard::Ptr pSoundCard, snd_pcm_stream_t stream) +{ + _stream = stream; + return snd_pcm_open((snd_pcm_t**)&_pPCM, pSoundCard->info().device.c_str(), stream, 0); +} + +int PCM::close() +{ + snd_pcm_drain((snd_pcm_t*)_pPCM); + snd_pcm_close((snd_pcm_t*)_pPCM); + return 0; +} + +int PCM::config(snd_pcm_access_t access, snd_pcm_format_t format, int channels, int rate, int framesz, int samplesz) +{ +#define __check_error(x) \ + rc = x; \ + if (rc < 0) \ + { \ + std::cout << "Error: " << snd_strerror(rc) << "@" << #x << std::endl; \ + goto out; \ + } + + snd_pcm_hw_params_t* hwParams = nullptr; + snd_pcm_sw_params_t* swParams = nullptr; + int rc; + + __check_error(snd_pcm_hw_params_malloc(&hwParams)); + + __check_error(snd_pcm_hw_params_any((snd_pcm_t*)_pPCM, hwParams)); + + __check_error(snd_pcm_hw_params_set_access((snd_pcm_t*)_pPCM, hwParams, access)); + + __check_error(snd_pcm_hw_params_set_format((snd_pcm_t*)_pPCM, hwParams, format)); + + __check_error(snd_pcm_hw_params_set_channels((snd_pcm_t*)_pPCM, hwParams, channels)); + + __check_error(snd_pcm_hw_params_set_rate((snd_pcm_t*)_pPCM, hwParams, rate, 0)); + + __check_error(snd_pcm_hw_params_set_period_size((snd_pcm_t*)_pPCM, hwParams, framesz, 0)); + + __check_error(snd_pcm_hw_params_set_periods((snd_pcm_t*)_pPCM, hwParams, samplesz, 0)); + + __check_error(snd_pcm_hw_params_set_buffer_size((snd_pcm_t*)_pPCM, hwParams, samplesz * framesz)); + + __check_error(snd_pcm_hw_params((snd_pcm_t*)_pPCM, hwParams)); + + if (_stream == SND_PCM_STREAM_PLAYBACK) + { + __check_error(snd_pcm_sw_params_malloc(&swParams)); + __check_error(snd_pcm_sw_params_current((snd_pcm_t*)_pPCM, swParams)); + __check_error(snd_pcm_sw_params_set_avail_min((snd_pcm_t*)_pPCM, swParams, framesz)); + + __check_error(snd_pcm_sw_params_set_start_threshold((snd_pcm_t*)_pPCM, swParams, framesz * samplesz)); + __check_error(snd_pcm_sw_params((snd_pcm_t*)_pPCM, swParams)); + } + +out: + if (hwParams) + snd_pcm_hw_params_free(hwParams); + + if (swParams) + snd_pcm_sw_params_free(swParams); + + return rc; +} + +int PCM::write(const void* data, uint32_t framesz) +{ + int rc = snd_pcm_writei((snd_pcm_t*)_pPCM, data, framesz); + if (rc < 0) + { + if (rc == -EPIPE) + rc = snd_pcm_prepare((snd_pcm_t*)_pPCM); + } + + return rc; +} + +int PCM::write(void** datas, uint32_t framesz) +{ + int rc = snd_pcm_writen((snd_pcm_t*)_pPCM, datas, framesz); + if (rc < 0) + { + if (rc == -EPIPE) + rc = snd_pcm_prepare((snd_pcm_t*)_pPCM); + } + + return rc; +} + +int PCM::read(void* buffer, uint32_t framesz) +{ + int rc = snd_pcm_readi((snd_pcm_t*)_pPCM, buffer, (snd_pcm_uframes_t)framesz); + if (rc < 0) + { + std::cout << "ALSA error: " << rc << " -> " << snd_strerror(rc) << "@" << framesz << std::endl; + if (rc == -EPIPE) + rc = snd_pcm_prepare((snd_pcm_t*)_pPCM); + } + + return rc; +} + +int PCM::read(void** buffers, uint32_t framesz) +{ + int rc = snd_pcm_readn((snd_pcm_t*)_pPCM, buffers, (snd_pcm_uframes_t)framesz); + if (rc < 0) + { + std::cout << "ALSA error: " << rc << " -> " << snd_strerror(rc) << "@" << framesz << std::endl; + if (rc == -EPIPE) + rc = snd_pcm_prepare((snd_pcm_t*)_pPCM); + } + + return rc; +} +} +} diff --git a/video/alsa/PCM.h b/video/alsa/PCM.h new file mode 100644 index 0000000..28c589d --- /dev/null +++ b/video/alsa/PCM.h @@ -0,0 +1,43 @@ +#ifndef F1145346_5CCE_48F8_8398_073AFB8758C6 +#define F1145346_5CCE_48F8_8398_073AFB8758C6 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "SoundCard.h" +#include + +namespace Video +{ +namespace ALSA +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class PCM +{ +public: + PCM(); + ~PCM(); + +public: + int open(SoundCard::Ptr pSoundCard, snd_pcm_stream_t st); + int close(); + + int config(snd_pcm_access_t access, snd_pcm_format_t format, int channels, int rate, int framesz, int samplesz); + + int write(const void* data, uint32_t framesz); + int write(void** datas, uint32_t framesz); + int read(void* buffer, uint32_t framesz); + int read(void** buffers, uint32_t framesz); + +private: + void* _pPCM; + snd_pcm_stream_t _stream; +}; +} +} +#endif diff --git a/video/alsa/SoundCard.cpp b/video/alsa/SoundCard.cpp new file mode 100644 index 0000000..055283b --- /dev/null +++ b/video/alsa/SoundCard.cpp @@ -0,0 +1,124 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "SoundCard.h" +#include + +namespace Video +{ +namespace ALSA +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +SoundCard::SoundCard(int index): _index(index) +{ + initialize(); +} + +SoundCard::~SoundCard() +{ +} + +void SoundCard::initialize() +{ + _info.device = Poco::format("hw:%d", _index); + int rc; + snd_ctl_t* handle; + if ((rc = snd_ctl_open(&handle, _info.device.c_str(), 0)) < 0) + throw Poco::InvalidAccessException("SoundCard: Cannot open device"); + + snd_ctl_card_info_t* info; + snd_ctl_card_info_alloca(&info); + if ((rc = snd_ctl_card_info(handle, info)) < 0) + throw Poco::InvalidAccessException("SoundCard: Cannot get card info"); + + _info.id = snd_ctl_card_info_get_id(info); + _info.name = snd_ctl_card_info_get_name(info); + _info.longname = snd_ctl_card_info_get_longname(info); + _info.driver = snd_ctl_card_info_get_driver(info); + _info.components = snd_ctl_card_info_get_components(info); + _info.mixername = snd_ctl_card_info_get_mixername(info); + + snd_ctl_close(handle); + + auto enumerateCapabilities = [](snd_pcm_t* pcm, Info& info) + { + snd_pcm_hw_params_t* params; + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(pcm, params); + + for (int access = 0; access <= SND_PCM_ACCESS_LAST; access++) + info.access[access] = (snd_pcm_hw_params_test_access(pcm, params, (snd_pcm_access_t)access) == 0); + + for (int format = 0; format <= SND_PCM_FORMAT_LAST; format++) + { + if (snd_pcm_hw_params_test_format(pcm, params, (snd_pcm_format_t)format) == 0) + info.formats.push_back((snd_pcm_format_t)format); + } + + for (unsigned int rate = 1000; rate <= 384000; rate += 1000) + { + if (snd_pcm_hw_params_test_rate(pcm, params, rate, 0) == 0) + info.sampleRates.push_back(rate); + } + + for (int channels = 1; channels <= 16; channels++) + { + if (snd_pcm_hw_params_test_channels(pcm, params, channels) == 0) + { + info.channels = channels; + break; + } + } + + for (snd_pcm_uframes_t val = 16; val <= 1048576; val *= 2) + { + if (snd_pcm_hw_params_test_buffer_size(pcm, params, val) == 0) + info.bufferSizes.push_back(val); + } + }; + + snd_pcm_t* pcm; + int err = snd_pcm_open(&pcm, _info.device.c_str(), SND_PCM_STREAM_CAPTURE, 0); + if (err < 0) + _info.supportCapture = false; + else + { + _info.supportCapture = true; + enumerateCapabilities(pcm, _info); + snd_pcm_close(pcm); + } + + err = snd_pcm_open(&pcm, _info.device.c_str(), SND_PCM_STREAM_PLAYBACK, 0); + if (err < 0) + _info.supportPlayback = false; + else + { + _info.supportPlayback = true; + if (!_info.supportCapture) + enumerateCapabilities(pcm, _info); + snd_pcm_close(pcm); + } +} + +SoundCard::Info& SoundCard::info() +{ + return _info; +} + +SoundCard::SoundCards SoundCard::getSoundCards() +{ + SoundCards cards; + int id = -1; + while (snd_card_next(&id), id >= 0) + cards.push_back(new SoundCard(id)); + + return cards; +} +} +} diff --git a/video/alsa/SoundCard.h b/video/alsa/SoundCard.h new file mode 100644 index 0000000..0a3a210 --- /dev/null +++ b/video/alsa/SoundCard.h @@ -0,0 +1,83 @@ +#ifndef A83739DB_6BC9_4B7C_8FA0_11DC60C05AA3 +#define A83739DB_6BC9_4B7C_8FA0_11DC60C05AA3 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include +#include +#include + +namespace Video +{ +namespace ALSA +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class SoundCard +{ +public: + struct Info + { + std::string id; + std::string device; + std::string name; + std::string longname; + std::string driver; + std::string components; + std::string mixername; + bool access[SND_PCM_ACCESS_LAST + 1]; + std::vector formats; + int channels; + std::vector sampleRates; + std::vector bufferSizes; + bool supportCapture; + bool supportPlayback; + + bool operator==(const Info& other) + { + return components == other.components; + } + }; + + using Ptr = Poco::SharedPtr; + using SoundCards = std::vector; + +private: + SoundCard(int index); + void initialize(); + +public: + ~SoundCard(); + + Info& info(); + +private: + int _index; + Info _info; + +public: + static SoundCards getSoundCards(); + + template static SoundCards getSoundCards(F&& filter); +}; + +template inline SoundCard::SoundCards SoundCard::getSoundCards(F&& filter) +{ + SoundCard::SoundCards cards; + for (auto& card : ALSA::SoundCard::getSoundCards()) + { + if (filter(card)) + cards.push_back(card); + } + + return cards; +} +} +} +#endif diff --git a/video/alsa/Util.cpp b/video/alsa/Util.cpp new file mode 100644 index 0000000..16b1188 --- /dev/null +++ b/video/alsa/Util.cpp @@ -0,0 +1,154 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Util.h" +#include "Logger.h" +#include + +namespace Video +{ +namespace ALSA +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +uint32_t Util::getSuitableSampleRate(SoundCard::Ptr pCard, uint32_t target) +{ + uint32_t suitable = target; + if (!pCard->info().sampleRates.empty()) + { + for (auto sr : pCard->info().sampleRates) + { + if (sr % target == 0 || target % sr == 0) + { + suitable = sr; + goto out; + } + } + + int minDiff = __INT32_MAX__; + for (auto sr : pCard->info().sampleRates) + { + int diff = std::abs(static_cast(sr - target)); + if (diff < minDiff || (diff == minDiff && sr > suitable)) + { + minDiff = diff; + suitable = sr; + } + } + } + +out: + return suitable; +} + +snd_pcm_format_t Util::getSuitableSampleFormat(SoundCard::Ptr pCard, Core::SampleFormat refer) +{ + auto target = convertSampleFormat(refer); + if (pCard->info().formats.empty()) + return SND_PCM_FORMAT_S16_LE; + + for (auto& fmt : pCard->info().formats) + { + if (fmt == (int)target) + return target; + } + + target = *(pCard->info().formats.begin()); + + return target; +} + +snd_pcm_access_t Util::getSuitableAccess(SoundCard::Ptr pCard, bool planar) +{ + if (planar && (pCard->info().access[SND_PCM_ACCESS_RW_NONINTERLEAVED])) + return SND_PCM_ACCESS_RW_NONINTERLEAVED; + + return SND_PCM_ACCESS_RW_INTERLEAVED; +} + +snd_pcm_format_t Util::convertSampleFormat(Core::SampleFormat format) +{ + switch (sampleFormatGetPlanarFormat(format)) + { + case Core::SampleFormat::U8P: + return SND_PCM_FORMAT_U8; + case Core::SampleFormat::S16P: + return SND_PCM_FORMAT_S16_LE; + case Core::SampleFormat::S32P: + return SND_PCM_FORMAT_S32_LE; + case Core::SampleFormat::FLTP: + return SND_PCM_FORMAT_FLOAT_LE; + case Core::SampleFormat::DBLP: + return SND_PCM_FORMAT_FLOAT64_LE; + default: + return SND_PCM_FORMAT_S16_LE; + } +} + +Core::SampleFormat Util::convertSampleFormat(snd_pcm_format_t format, bool interleaved) +{ + Core::SampleFormat result; + + switch (format) + { + case SND_PCM_FORMAT_U8: + result = Core::SampleFormat::U8P; + break; + case SND_PCM_FORMAT_S8: + result = Core::SampleFormat::U8P; + break; + case SND_PCM_FORMAT_S16_LE: + result = Core::SampleFormat::S16P; + break; + case SND_PCM_FORMAT_S16_BE: + result = Core::SampleFormat::S16P; + break; + case SND_PCM_FORMAT_S32_LE: + result = Core::SampleFormat::S32P; + break; + case SND_PCM_FORMAT_S32_BE: + result = Core::SampleFormat::S32P; + break; + case SND_PCM_FORMAT_FLOAT_LE: + result = Core::SampleFormat::FLTP; + break; + case SND_PCM_FORMAT_FLOAT_BE: + result = Core::SampleFormat::FLTP; + break; + case SND_PCM_FORMAT_FLOAT64_LE: + result = Core::SampleFormat::DBLP; + break; + case SND_PCM_FORMAT_FLOAT64_BE: + result = Core::SampleFormat::DBLP; + break; + + case SND_PCM_FORMAT_SPECIAL: + case SND_PCM_FORMAT_S24_3LE: + case SND_PCM_FORMAT_S24_3BE: + case SND_PCM_FORMAT_U24_3LE: + case SND_PCM_FORMAT_U24_3BE: + case SND_PCM_FORMAT_S20_3LE: + case SND_PCM_FORMAT_S20_3BE: + case SND_PCM_FORMAT_U20_3LE: + case SND_PCM_FORMAT_U20_3BE: + case SND_PCM_FORMAT_S18_3LE: + case SND_PCM_FORMAT_S18_3BE: + case SND_PCM_FORMAT_U18_3LE: + default: + logger().warning(": Unsupported format: %s", std::string(snd_pcm_format_name(format))); + result = Core::SampleFormat::S32P; + break; + } + + if (interleaved) + result = Core::sampleFormatGetPackedFormat(result); + + return result; +} +} +} diff --git a/video/alsa/Util.h b/video/alsa/Util.h new file mode 100644 index 0000000..45eb6b5 --- /dev/null +++ b/video/alsa/Util.h @@ -0,0 +1,31 @@ +#ifndef C0D42E11_A47F_4E62_820B_17AD3C7FE57C +#define C0D42E11_A47F_4E62_820B_17AD3C7FE57C +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "SoundCard.h" +#include "../core/SampleFormat.h" + +namespace Video +{ +namespace ALSA +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Util +{ +public: + static uint32_t getSuitableSampleRate(SoundCard::Ptr pCard, uint32_t target); + static snd_pcm_format_t getSuitableSampleFormat(SoundCard::Ptr pCard, Core::SampleFormat refer); + static snd_pcm_access_t getSuitableAccess(SoundCard::Ptr pCard, bool planar); + static snd_pcm_format_t convertSampleFormat(Core::SampleFormat format); + static Core::SampleFormat convertSampleFormat(snd_pcm_format_t format, bool interleaved); +}; +} +} +#endif diff --git a/video/core/AbstractAudioInput.cpp b/video/core/AbstractAudioInput.cpp new file mode 100644 index 0000000..4e58e43 --- /dev/null +++ b/video/core/AbstractAudioInput.cpp @@ -0,0 +1,284 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AbstractAudioInput.h" +#include "System.h" +#include "Logger.h" +#include "util/Math.h" +#include +#include +#include +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +AbstractAudioInput::AbstractAudioInput(const std::string& name) + : AudioComponent(name) +{ + const auto len = 1024 * 32; + _pSilentData = new uint8_t[len]; + memset(_pSilentData, 0x00, len); + + _silentDataCode = serialCodeManager().code(); + _pSilentFrame = new Frame; + + for (int i = 0; i < Frame::DATA_LINES; i++) + _pSilentFrame->data[i] = _pSilentData; +} + +AbstractAudioInput::~AbstractAudioInput() +{ +} + +void AbstractAudioInput::run() +{ + updateSilentFrame(); + + _task.run( + [this](std::atomic& isCancelled) -> void + { + Util::Task probeTask; + std::atomic attached(true); + setSignalLost(false); + + Frame::Ptr pFrame; + SerialCodeWatcher watcher; + uint64_t timeline = 0; + while (!isCancelled) + { + if (attached) + { + pFrame = tryReadFrame(true); + if (pFrame.isNull()) + { + probeTask.run( + [&](std::atomic& isCancelled) -> void + { + while (!isCancelled) + { + if (!tryReadFrame(false).isNull()) + { + attached = true; + setSignalLost(false); + break; + } + + Poco::Thread::sleep(2000); + } + }); + + attached = false; + setSignalLost(true); + continue; + } + + const int gain = _gain.load(std::memory_order_relaxed); + if (gain != 0) + applyGain(pFrame, gain); + + const int64_t ptsOffset = _ptsOffsetUs.load(std::memory_order_relaxed); + if (ptsOffset != 0) + applyPTSOffset(pFrame, ptsOffset); + + pFrame->code = serialCodeManager().code(); + if (watcher.isChanged(pFrame->code, true)) + { + updateSilentFrame(); + } + } + else + { + pFrame = readSilent(false); + } + + if (pFrame->timestamp < timeline) + pFrame->timestamp = timeline; + else + timeline = pFrame->timestamp; + + if (_isMute) + pFrame = readSilent(true); + + postToNextComponents(sharedFromThis(), pFrame, MediaType::AUDIO, isCancelled); + } + + probeTask.cancel(); + }); +} + +void AbstractAudioInput::cancel() +{ + _task.cancel(); +} + +void AbstractAudioInput::onConfigChanged(ParameterNames& names) +{ + updateSilentFrame(); + if (names.exists(_Gain)) + _gain.store(getGain(), std::memory_order_relaxed); + + if (names.exists(_PTSOffset)) + _ptsOffsetUs.store(getPTSOffset(), std::memory_order_relaxed); +} + +Frame::Ptr AbstractAudioInput::tryReadFrame(bool shouldLog) +{ + try + { + return readFrame(); + } + catch (Poco::Exception& e) + { + if (shouldLog) + logger().error("%s: %s", name(), e.displayText()); + } + catch (std::exception& e) + { + if (shouldLog) + logger().error("%s: %s", name(), std::string(e.what())); + } + + return nullptr; +} + +Frame::Ptr AbstractAudioInput::readSilent(bool freezeChannel) +{ + uint64_t now = System::timestamp(); + _pSilentFrame->timestamp = now; + _pSilentFrame->code = _silentDataCode; + if (!freezeChannel) + { + std::this_thread::sleep_for(std::chrono::microseconds(_duration)); + } + + return _pSilentFrame; +} + +void AbstractAudioInput::updateSilentFrame() +{ + _pSilentFrame->length = Core::sampleFormatCalcLineSize(getSampleFormat(), getChannelLayout(), getFrameSize()); + _duration = (float)1000000 / getSampleRate() * getFrameSize() * 0.85; + _silentDataCode = serialCodeManager().code(); +} + +void AbstractAudioInput::applyGain(Frame::Ptr pFrame, int gain) +{ + gain = ::Util::Math::clamp(gain, -50, 20); + if (gain == 0) + return; + + const auto format = getSampleFormat(); + if (format == SampleFormat::NONE) + return; + + const uint32_t channels = (uint32_t)channelLayoutChannels(getChannelLayout()); + const uint32_t samplesPerChannel = getFrameSize(); + const double scale = std::pow(10.0, (double)gain / 20.0); + + auto apply = [&](uint8_t* base, uint32_t n) + { + switch (format) + { + case SampleFormat::U8: + case SampleFormat::U8P: + { + auto p = (uint8_t*)base; + for (uint32_t i = 0; i < n; i++) + { + const int64_t v = (int64_t)(p[i] * scale + 0.5); + p[i] = (uint8_t)::Util::Math::clamp(v, (int64_t)0, (int64_t)255); + } + break; + } + case SampleFormat::S16: + case SampleFormat::S16P: + { + auto p = (int16_t*)base; + for (uint32_t i = 0; i < n; i++) + { + const int64_t v = (int64_t)(p[i] * scale + 0.5); + p[i] = (int16_t)::Util::Math::clamp(v, (int64_t)INT16_MIN, (int64_t)INT16_MAX); + } + break; + } + case SampleFormat::S32: + case SampleFormat::S32P: + { + auto p = (int32_t*)base; + for (uint32_t i = 0; i < n; i++) + { + const int64_t v = (int64_t)(p[i] * scale + 0.5); + p[i] = (int32_t)::Util::Math::clamp(v, (int64_t)INT32_MIN, (int64_t)INT32_MAX); + } + break; + } + case SampleFormat::S64: + case SampleFormat::S64P: + { + auto p = (int64_t*)base; + for (uint32_t i = 0; i < n; i++) + p[i] = (int64_t)(p[i] * scale); + break; + } + case SampleFormat::FLT: + case SampleFormat::FLTP: + { + auto p = (float*)base; + for (uint32_t i = 0; i < n; i++) + p[i] = (float)(p[i] * scale); + break; + } + case SampleFormat::DBL: + case SampleFormat::DBLP: + { + auto p = (double*)base; + for (uint32_t i = 0; i < n; i++) + p[i] *= scale; + break; + } + default: + break; + } + }; + + if (sampleFormatIsPlanar(format)) + { + for (uint32_t ch = 0; ch < channels; ch++) + { + if (!pFrame->data[ch]) + continue; + apply(pFrame->data[ch].get(), samplesPerChannel); + } + } + else + { + if (!pFrame->data[0]) + return; + apply(pFrame->data[0].get(), samplesPerChannel * channels); + } +} + +void AbstractAudioInput::applyPTSOffset(Frame::Ptr pFrame, int64_t ptsOffset) +{ + if (ptsOffset != 0) + { + const int64_t ts = (int64_t)pFrame->timestamp + ptsOffset; + pFrame->timestamp = (ts > 0) ? (uint64_t)ts : 0ULL; + } +} + +void AbstractAudioInput::reset() +{ +} + +} +} diff --git a/video/core/AbstractAudioInput.h b/video/core/AbstractAudioInput.h new file mode 100644 index 0000000..ff021ae --- /dev/null +++ b/video/core/AbstractAudioInput.h @@ -0,0 +1,56 @@ +#ifndef E2E4239C_C93E_478C_AD4C_D74AC3D4ED3E +#define E2E4239C_C93E_478C_AD4C_D74AC3D4ED3E +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioComponent.h" +#include "Frame.h" +#include "util/Task.h" +#include "util/Broadcast.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class AbstractAudioInput: public AudioComponent +{ +public: + AbstractAudioInput(const std::string& name); + ~AbstractAudioInput(); + void reset() override; + +protected: + virtual Frame::Ptr readFrame() = 0; + void run(); + void cancel(); + void onConfigChanged(ParameterNames& names) override; + void applyGain(Frame::Ptr pFrame, int gain); + void applyPTSOffset(Frame::Ptr pFrame, int64_t ptsOffset); + +private: + Frame::Ptr tryReadFrame(bool shuldLog = false); + Frame::Ptr readSilent(bool freezeChannel); + void updateSilentFrame(); + +private: + Util::Task _task; + Frame::Ptr _pSilentFrame; + Poco::SharedPtr _pSilentData; + SerialCode _silentDataCode; + uint64_t _duration; + uint32_t _audioChannelId; + bool _isMute; + std::atomic _gain{0}; + std::atomic _ptsOffsetUs{0}; +}; +} +} +#endif diff --git a/video/core/AudioAdaptablePipe.cpp b/video/core/AudioAdaptablePipe.cpp new file mode 100644 index 0000000..b3dc60a --- /dev/null +++ b/video/core/AudioAdaptablePipe.cpp @@ -0,0 +1,83 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioAdaptablePipe.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +void AudioAdaptablePipe::onLink(Core::Component::Ptr pPrevious) +{ + if (!frameQueues().empty() && frameQueues().firstComponent() != pPrevious) + throw Poco::LogicException(name() + ": Support only one previous component, conflict with: " + + pPrevious->name() + ", Current: " + frameQueues().firstComponent()->name()); + + frameQueues().add(pPrevious, name(), MediaType::AUDIO); +} + +void AudioAdaptablePipe::onUnlink(Core::Component::Ptr pPrevious) +{ + frameQueues().remove(pPrevious); +} + +Frame::Ptr AudioAdaptablePipe::readFrame(uint32_t timeout) +{ + if (previousComponents().size() == 0) + throw Poco::NotFoundException(name() + ": No previous components"); + + auto pFrameQueue = frameQueues().first(); + while (true) + { + Frame::Ptr pFrame = nullptr; + if (!_pFrameAdapter.isNull()) + { + pFrame = _pFrameAdapter->get(); + if (!pFrame.isNull()) + return pFrame; + } + + pFrame = pFrameQueue->dequeue(timeout); + if (serialCodeWatcher().isChanged(pFrame->code, true)) + { + auto pPrevious = previousComponents().first().cast(); + + auto inLayout = pPrevious->getChannelLayout(); + auto inFormat = pPrevious->getSampleFormat(); + auto inRate = pPrevious->getSampleRate(); + auto outLayout = getChannelLayout(); + auto outFormat = getSampleFormat(); + auto outRate = getSampleRate(); + + if (inLayout != outLayout || inFormat != outFormat || inRate != outRate) + _pResample = AudioResample::create(inLayout, inFormat, inRate, outLayout, outFormat, outRate); + else + _pResample.reset(); + + if (inRate != outRate || pPrevious->getFrameSize() != getFrameSize()) + _pFrameAdapter = new AudioFrameAdapter(outLayout, outFormat, getFrameSize(), outRate); + else + _pFrameAdapter.reset(); + } + + if (_pFrameAdapter.isNull() && _pResample.isNull()) + return pFrame; + + if (!_pResample.isNull()) + pFrame = _pResample->process(pFrame); + + if (!_pFrameAdapter.isNull()) + _pFrameAdapter->put(pFrame); + else + return pFrame; + } +} +} +} diff --git a/video/core/AudioAdaptablePipe.h b/video/core/AudioAdaptablePipe.h new file mode 100644 index 0000000..0993add --- /dev/null +++ b/video/core/AudioAdaptablePipe.h @@ -0,0 +1,38 @@ +#ifndef BCE1B4DD_976E_456D_AE9A_87A17E300579 +#define BCE1B4DD_976E_456D_AE9A_87A17E300579 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioComponent.h" +#include "AudioFrameAdapter.h" +#include "AudioResample.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class AudioAdaptablePipe: public AudioComponent +{ +public: + using AudioComponent::AudioComponent; + +protected: + void onLink(Core::Component::Ptr pPrevious) override; + void onUnlink(Core::Component::Ptr pPrevious) override; + + virtual Frame::Ptr readFrame(uint32_t timeout = 1000); + +private: + AudioResample::Ptr _pResample; + AudioFrameAdapter::Ptr _pFrameAdapter; +}; +} +} +#endif diff --git a/video/core/AudioComponent.cpp b/video/core/AudioComponent.cpp new file mode 100644 index 0000000..aa88160 --- /dev/null +++ b/video/core/AudioComponent.cpp @@ -0,0 +1,32 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioComponent.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +AudioComponent::AudioComponent(const std::string& name): Component(name) +{ + setSampleRate(48000); + setChannelLayout(ChannelLayout::STEREO); + setSampleFormat(SampleFormat::S16P); + setFrameSize(1024); + setGain(0); + setPTSOffset(0); + setSignalLost(true); +} + +AudioComponent::~AudioComponent() +{ +} +} +} diff --git a/video/core/AudioComponent.h b/video/core/AudioComponent.h new file mode 100644 index 0000000..a252f82 --- /dev/null +++ b/video/core/AudioComponent.h @@ -0,0 +1,43 @@ +#ifndef D770D9A1_7014_4DBA_99C9_708BEDAA10F6 +#define D770D9A1_7014_4DBA_99C9_708BEDAA10F6 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Component.h" +#include "CodecID.h" +#include "ChannelLayout.h" +#include "SampleFormat.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class AudioComponent: public Component +{ +public: + using Ptr = Poco::AutoPtr; + +public: + AudioComponent(const std::string& name); + virtual ~AudioComponent(); + virtual void reset() {}; + +public: + CONFIGURABLE_DECLARE_PARAMETER(SampleRate, int); + CONFIGURABLE_DECLARE_PARAMETER(ChannelLayout, ChannelLayout); + CONFIGURABLE_DECLARE_PARAMETER(SampleFormat, SampleFormat); + CONFIGURABLE_DECLARE_PARAMETER(FrameSize, int); + CONFIGURABLE_DECLARE_PARAMETER(Gain, int); + CONFIGURABLE_DECLARE_PARAMETER(PTSOffset, int64_t); + CONFIGURABLE_DECLARE_PARAMETER(SignalLost, bool); +}; +} +} +#endif diff --git a/video/core/AudioDecoder.cpp b/video/core/AudioDecoder.cpp new file mode 100644 index 0000000..86edf65 --- /dev/null +++ b/video/core/AudioDecoder.cpp @@ -0,0 +1,239 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioDecoder.h" +#include "Logger.h" +#include "Demuxer.h" +#include "System.h" +#include +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * 追帧(catch-up)阈值配置 + * 说明:所有阈值均以"队列中待解码包个数"为单位。 + * 音频帧典型时长约 20ms(AAC),队列上限 300,约 6s 缓冲。 + ******************************************************************************* + */ +namespace +{ +constexpr int CATCH_UP_ENTER = 10; +constexpr int CATCH_UP_EXIT = 2; + +constexpr int DROP_LEVEL_1 = 6; +constexpr int DROP_LEVEL_2 = 15; +constexpr int DROP_LEVEL_3 = 30; +} +/* + ******************************************************************************* + * * CLASS DEFINE + ******************************************************************************* + */ +AudioDecodeHandler::AudioDecodeHandler(const std::string& name, CodecID codecID, AudioComponent::Ptr pPrevious) + : _pPrevious(pPrevious) +{ +} + +AudioDecodeHandler::~AudioDecodeHandler() +{ +} + +void AudioDecodeHandler::conceal(Packet::Ptr pPacket) +{ + (void)pPacket; +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +AudioDecoder::AudioDecoder(const std::string& name) + : AbstractAudioInput(name), _synchronizer(name + "/sync"), + + _catchUpEnabled(name.find("NET") != std::string::npos ? true : false), _isCatchingUp(false), _dropPhase(0) +{ +} + +AudioDecoder::~AudioDecoder() +{ +} + +void AudioDecoder::start() +{ + run(); +} + +void AudioDecoder::stop() +{ + cancel(); + setTimeline(0); + + _isCatchingUp = false; + _dropPhase = 0; +} + +Frame::Ptr AudioDecoder::readFrame() +{ + if (previousComponents().size() == 0 || packetQueues().empty()) + throw Poco::NotFoundException("No previous components"); + + updateCatchUpState(); + + Frame::Ptr pFrame = nullptr; + + if (!_pHandler.isNull()) + { + pFrame = _pHandler->read(); + if (!pFrame.isNull()) + { + syncFrame(pFrame, _isCatchingUp); + return pFrame; + } + } + + while (pFrame.isNull()) + { + Packet::Ptr pPacket; + + try + { + long timeOut = _isCatchingUp ? 1 : 1000; + pPacket = packetQueues().first()->dequeue(timeOut); + } + catch (std::exception& e) + { + if (packetQueues().first()->size() == 0) + { + if (_isCatchingUp) + { + _isCatchingUp = false; + _dropPhase = 0; + _synchronizer.reset(); + logger().information("%s: catch-up exit (queue drained)", name()); + } + } + setTimeline(0); + throw; + } + + if (serialCodeWatcher().isChanged(pPacket->serialCode(), true)) + { + auto pPrevious = previousComponents().first(); + auto pDemuxer = pPrevious.cast(); + if (pDemuxer) + { + auto pEncoder = pDemuxer->audioEncoder(); + pEncoder->config().copyTo(config()); + serialCodeManager().increment(); + + _pHandler = AudioDecodeHandler::create(pEncoder->getCodecID(), pEncoder); + + if (_catchUpEnabled) + { + while (packetQueues().first()->size() > 2) + { + try + { + packetQueues().first()->dequeue(0); + } + catch (...) + { + break; + } + } + } + } + else + throw Poco::NotImplementedException( + Poco::format("Unsupported previous component: %s", pPrevious->name())); + } + + if (_isCatchingUp && shouldDropPacket()) + { + _pHandler->conceal(pPacket); + continue; + } + + _pHandler->write(pPacket); + pFrame = _pHandler->read(); + } + + syncFrame(pFrame, _isCatchingUp); + return pFrame; +} + +void AudioDecoder::onLink(Core::Component::Ptr pPrevious) +{ + if (!packetQueues().empty() && packetQueues().firstComponent() != pPrevious) + throw Poco::LogicException(name() + ": Support only one previous component, conflict with: " + + pPrevious->name() + ", Current: " + packetQueues().firstComponent()->name()); + + packetQueues().add(pPrevious, name(), MediaType::AUDIO); +} + +void AudioDecoder::onUnlink(Core::Component::Ptr pPrevious) +{ + packetQueues().remove(pPrevious); +} + +void AudioDecoder::syncFrame(Frame::Ptr pFrame, bool isCatchingUp) +{ + if (!isCatchingUp) + _synchronizer.syncFrame(pFrame->timestamp); + setTimeline(pFrame->timestamp); + pFrame->timestamp = System::timestamp(); +} + +void AudioDecoder::updateCatchUpState() +{ + if (!_catchUpEnabled) + return; + + const int queueSize = packetQueues().first()->size(); + + if (!_isCatchingUp && queueSize > CATCH_UP_ENTER) + { + _isCatchingUp = true; + _dropPhase = 0; + logger().information("%s: catch-up enter (queue=%d)", name(), queueSize); + } + else if (_isCatchingUp && queueSize <= CATCH_UP_EXIT) + { + _isCatchingUp = false; + _dropPhase = 0; + + _synchronizer.reset(); + logger().information("%s: catch-up exit (queue=%d)", name(), queueSize); + } +} + +bool AudioDecoder::shouldDropPacket() +{ + const int queueSize = packetQueues().first()->size(); + bool drop = false; + + if (queueSize >= DROP_LEVEL_3) + { + drop = (_dropPhase % 3) != 2; + } + else if (queueSize >= DROP_LEVEL_2) + { + drop = (_dropPhase % 2) == 0; + } + else + { + drop = (_dropPhase % 3) == 0; + } + + _dropPhase++; + return drop; +} +} +} diff --git a/video/core/AudioDecoder.h b/video/core/AudioDecoder.h new file mode 100644 index 0000000..7aba255 --- /dev/null +++ b/video/core/AudioDecoder.h @@ -0,0 +1,119 @@ +#ifndef D60BD16B_B6AC_40EF_9E0E_D70A203EA49C +#define D60BD16B_B6AC_40EF_9E0E_D70A203EA49C +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AbstractAudioInput.h" +#include "util/FactoryManager.h" +#include "util/Task.h" +#include "System.h" +#include "FrameSynchronizer.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class AudioDecodeHandlerSelector +{ +public: + static CodecID select(CodecID codec, AudioComponent::Ptr) + { + return codec; + } +}; + +class AudioDecodeHandler: public ::Util::FactoryManager +{ +public: + using Ptr = Poco::SharedPtr; + +public: + AudioDecodeHandler(const std::string& name, CodecID codecID, AudioComponent::Ptr pPrevious); + virtual ~AudioDecodeHandler(); + +public: + const std::string& name() const; + AudioComponent::Ptr previous() const; + + virtual void write(Packet::Ptr pPacket) = 0; + virtual Frame::Ptr read() = 0; + + virtual void conceal(Packet::Ptr pPacket); + +private: + const std::string _name; + AudioComponent::Ptr _pPrevious; +}; + +inline const std::string& AudioDecodeHandler::name() const +{ + return _name; +} + +inline AudioComponent::Ptr AudioDecodeHandler::previous() const +{ + return _pPrevious; +} + +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class AudioDecoderSelector +{ +public: + static int select(const std::string&) + { + return 0; + } +}; + +class AudioDecoder: public AbstractAudioInput, + public ::Util::FactoryManager +{ +public: + using Ptr = Poco::AutoPtr; + +public: + AudioDecoder(const std::string& name); + virtual ~AudioDecoder(); + +protected: + void start() override; + void stop() override; + + Frame::Ptr readFrame() override; + + void onLink(Core::Component::Ptr pPrevious) override; + void onUnlink(Core::Component::Ptr pPrevious) override; + + void syncFrame(Frame::Ptr pFrame, bool isCatchingUp = false); + + PROPERTY_DECLARE(Timeline, uint64_t, 0, public, public) + +private: + void updateCatchUpState(); + + bool shouldDropPacket(); + + AudioDecodeHandler::Ptr _pHandler; + FrameSynchronizer _synchronizer; + + bool _catchUpEnabled; + + bool _isCatchingUp; + + uint32_t _dropPhase; +}; +} +} +#endif diff --git a/video/core/AudioEncoder.cpp b/video/core/AudioEncoder.cpp new file mode 100644 index 0000000..4b8371c --- /dev/null +++ b/video/core/AudioEncoder.cpp @@ -0,0 +1,85 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioEncoder.h" +#include "Logger.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +AudioEncoder::AudioEncoder(const std::string& name, CodecID codecID): AudioAdaptablePipe(name) +{ + setCodecID(codecID); + + setBitrate(128000); +} + +AudioEncoder::~AudioEncoder() +{ +} + +void AudioEncoder::start() +{ + _task.run( + [this](std::atomic& isCancelled) -> void + { + Packet::Ptr pPacket; + while (!isCancelled) + { + try + { + pPacket = encode(); + if (!pPacket) + continue; + + postToNextComponents(sharedFromThis(), pPacket, MediaType::AUDIO); + } + catch (Poco::Exception& e) + { + logger().error("%s: %s", name(), e.displayText()); + Poco::Thread::sleep(1000); + } + catch (std::exception& e) + { + logger().error("%s: %s", name(), std::string(e.what())); + Poco::Thread::sleep(1000); + } + } + }); +} + +void AudioEncoder::stop() +{ + _task.cancel(); +} + +Frame::Ptr AudioEncoder::readFrame(uint32_t timeout) +{ + Frame::Ptr pFrame = AudioAdaptablePipe::readFrame(timeout); + + const int64_t off = _ptsOffsetUs.load(std::memory_order_relaxed); + if (off != 0) + { + const int64_t ts = (int64_t)pFrame->timestamp + off; + pFrame->timestamp = (ts > 0) ? (uint64_t)ts : 0ULL; + } + + return pFrame; +} + +void AudioEncoder::onConfigChanged(ParameterNames& names) +{ + if (names.exists(_PTSOffset)) + _ptsOffsetUs.store(getPTSOffset(), std::memory_order_relaxed); +} +} +} diff --git a/video/core/AudioEncoder.h b/video/core/AudioEncoder.h new file mode 100644 index 0000000..0e2d24f --- /dev/null +++ b/video/core/AudioEncoder.h @@ -0,0 +1,66 @@ +#ifndef E7CCDC55_4323_4B0A_A08E_1C11B939AA61 +#define E7CCDC55_4323_4B0A_A08E_1C11B939AA61 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioAdaptablePipe.h" +#include "CodecID.h" +#include "util/FactoryManager.h" +#include "util/Task.h" +#include +#include +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class AudioEncoderSelector +{ +public: + static CodecID select(CodecID codecID) + { + return codecID; + } +}; + +class AudioEncoder: public AudioAdaptablePipe, + public ::Util::FactoryManager +{ +public: + using Ptr = Poco::AutoPtr; + +public: + AudioEncoder(const std::string& name, CodecID codecID); + virtual ~AudioEncoder(); + +public: + CONFIGURABLE_DECLARE_PARAMETER(CodecID, CodecID); + CONFIGURABLE_DECLARE_PARAMETER(Bitrate, int); + +protected: + void start() override; + void stop() override; + Frame::Ptr readFrame(uint32_t timeout = 1000) override; + void onConfigChanged(ParameterNames& names) override; + + virtual Packet::Ptr encode() = 0; + +protected: + std::atomic _ptsOffsetUs{0}; + +private: + Util::Task _task; + AudioResample::Ptr _pResample; + AudioFrameAdapter::Ptr _pFrameAdapter; +}; +} +} +#endif diff --git a/video/core/AudioFrameAdapter.cpp b/video/core/AudioFrameAdapter.cpp new file mode 100644 index 0000000..acc6582 --- /dev/null +++ b/video/core/AudioFrameAdapter.cpp @@ -0,0 +1,122 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioFrameAdapter.h" +#include "System.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +AudioFrameAdapter::AudioFrameAdapter(ChannelLayout layout, SampleFormat format, int frameSize, int sampleRate) +{ + int channels = channelLayoutChannels(layout); + _linesize = sampleFormatCalcLineSize(format, layout, frameSize); + _lines = sampleFormatIsPlanar(format) ? channels : 1; + _sampleDuration = 1000000000ul / sampleRate; + _sampleSize = sampleFormatBytesPerSample(format); + if (!sampleFormatIsPlanar(format)) + _sampleSize *= channels; + + _readIndex = 0; + + _sampleRate = sampleRate; + _frameSize = frameSize; + _encodeClock = 0; +} + +void AudioFrameAdapter::put(Frame::Ptr pFrame) +{ + if (!_pInFrame.isNull()) + throw Poco::InvalidAccessException(); + + _pInFrame = pFrame; +} + +Frame::Ptr AudioFrameAdapter::get() +{ + int inRemain = remainOfReadFrame(); + if (inRemain == 0) + return nullptr; + + if (_pOutFrame.isNull()) + { + _pOutFrame = new Frame(); + + for (int i = 0; i < _lines; i++) + _pOutFrame->data[i] = new uint8_t[_linesize]; + _pOutFrame->length = _linesize; + + _writeIndex = 0; + } + + if (0 == _pOutFrame->timestamp) + { + _pOutFrame->timestamp = getInFrameTimestamp(); + + const uint64_t wall = System::timestamp(); + if (0 == _encodeClock) + _encodeClock = wall; + else + { + const int64_t diff = (int64_t)(wall - _encodeClock); + if (diff > 300000 || diff < -300000) + _encodeClock = wall; + else + _encodeClock += (uint64_t)(diff / 8); + } + _pOutFrame->timestamp = _encodeClock; + _encodeClock += 1000000ull * _frameSize / _sampleRate; + } + int bytes = std::min(inRemain, spaceOfOutFrame()); + for (int i = 0; i < _lines; i++) + memcpy(_pOutFrame->data[i] + _writeIndex, _pInFrame->data[i] + _readIndex, bytes); + + _writeIndex += bytes; + _readIndex += bytes; + + if (remainOfReadFrame() == 0) + { + _pInFrame.reset(); + _readIndex = 0; + } + + Frame::Ptr pFrame = nullptr; + if (spaceOfOutFrame() == 0) + { + pFrame = _pOutFrame; + _pOutFrame.reset(); + } + + return pFrame; +} + +int AudioFrameAdapter::remainOfReadFrame() +{ + if (_pInFrame.isNull()) + return 0; + + return _pInFrame->length - _readIndex; +} + +int AudioFrameAdapter::spaceOfOutFrame() +{ + if (_pOutFrame.isNull()) + return 0; + + return _pOutFrame->length - _writeIndex; +} + +uint64_t AudioFrameAdapter::getInFrameTimestamp() +{ + return _pInFrame->timestamp + _readIndex / _sampleSize * _sampleDuration / 1000; +} +} +} diff --git a/video/core/AudioFrameAdapter.h b/video/core/AudioFrameAdapter.h new file mode 100644 index 0000000..da4ac49 --- /dev/null +++ b/video/core/AudioFrameAdapter.h @@ -0,0 +1,58 @@ +#ifndef B0BCC994_AC90_4B90_9CD2_10586E958794 +#define B0BCC994_AC90_4B90_9CD2_10586E958794 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Frame.h" +#include "SampleFormat.h" +#include "ChannelLayout.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class AudioFrameAdapter +{ +public: + using Ptr = Poco::SharedPtr; + +public: + AudioFrameAdapter(ChannelLayout layout, SampleFormat format, int frameSize, int sampleRate); + +public: + void put(Frame::Ptr pFrame); + Frame::Ptr get(); + +protected: + int remainOfReadFrame(); + int spaceOfOutFrame(); + uint64_t getInFrameTimestamp(); + +private: + int _linesize; + int _lines; + int _sampleSize; + Frame::Ptr _pInFrame; + Frame::Ptr _pOutFrame; + uint32_t _readIndex; + uint32_t _writeIndex; + + uint32_t _sampleDuration; + + int _sampleRate; + + uint32_t _frameSize; + + uint64_t _encodeClock; +}; +} +} +#endif diff --git a/video/core/AudioMixer.cpp b/video/core/AudioMixer.cpp new file mode 100644 index 0000000..329f8fd --- /dev/null +++ b/video/core/AudioMixer.cpp @@ -0,0 +1,244 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioMixer.h" +#include "System.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +AudioMixer::Channel::Channel(AudioMixer::Ptr pMixer, uint32_t id) + : AudioAdaptablePipe(Poco::format(pMixer->name() + "/CH#%u", id)), _pMixer(pMixer), _id(id) +{ + setAttach(false); + setInput(nullptr); +} + +AudioMixer::Channel::~Channel() +{ +} + +void AudioMixer::Channel::start() +{ +} + +void AudioMixer::Channel::stop() +{ +} + +void AudioMixer::Channel::onConfigChanged(ParameterNames& names) +{ + if (names.exists(_Input)) + unlink(true, false); + + if (names.exists(_Attach)) + { + auto pInput = getInput(); + if (pInput) + { + if (getAttach()) + { + getInput()->link(sharedFromThis()); + link(mixer()); + } + else + getInput()->unlink(sharedFromThis()); + } + } + + serialCodeWatcher().reset(); +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +AudioMixer::AudioMixer(const std::string& name): AbstractAudioInput(name) +{ + _mixFrame = new Frame(); + for (int i = 0; i < Frame::DATA_LINES; i++) + _mixFrame->data[i] = new uint8_t[1024 * 32]; +} + +AudioMixer::~AudioMixer() +{ +} + +AudioMixer::Channel::Ptr AudioMixer::getChannel(uint32_t id) +{ + if (_channels.find(id) == _channels.end()) + { + Channel::Ptr pChannel = new Channel(Ptr(this, true), id); + syncConfigToChannel(pChannel); + _channels[id] = pChannel; + } + + return _channels[id]; +} + +void AudioMixer::syncConfigToChannel(AudioMixer::Channel::Ptr pChannel) +{ + pChannel->setSampleRate(getSampleRate()); + pChannel->setChannelLayout(getChannelLayout()); + pChannel->setSampleFormat(getSampleFormat()); + pChannel->setFrameSize(getFrameSize()); +} + +template +static inline void mix(const S MIN, const S MAX, uint8_t* i0, uint8_t* i1, uint8_t* o, uint32_t bytes) +{ + T* p0 = reinterpret_cast(i0); + T* p1 = reinterpret_cast(i1); + T* output = reinterpret_cast(o); + + const size_t count = static_cast(bytes / sizeof(T)); + for (size_t i = 0; i < count; ++i) + { + S a = static_cast(p0[i]); + S b = static_cast(p1[i]); + + const int64_t product = static_cast(a) * b; + S c = a + b - static_cast(product >> 0x10); + + c = (c > MAX) ? MAX : ((c < MIN) ? MIN : c); + output[i] = static_cast(c); + } +} + +void AudioMixer::start() +{ + refreshOutputParams(); + run(); +} + +void AudioMixer::stop() +{ + cancel(); +} + +void AudioMixer::onConfigChanged(ParameterNames& names) +{ + for (auto& m : _channels) + { + auto pChannel = m.second; + syncConfigToChannel(pChannel); + pChannel->commit(); + } + + AbstractAudioInput::onConfigChanged(names); +} + +Frame::Ptr AudioMixer::readFrame() +{ + Frame::Ptr pFrame = nullptr; + uint64_t pts = 0; + uint32_t index = 0; + for (auto& m : _channels) + { + auto pChannel = m.second; + Frame::Ptr pReadFrame; + + try + { + pReadFrame = pChannel->readFrame(32); + } + catch (...) + { + pReadFrame = nullptr; + } + + if (!pReadFrame.isNull()) + { + if (pts == 0 || pts > pReadFrame->timestamp) + pts = pReadFrame->timestamp; + + if (pFrame == nullptr) + pFrame = pReadFrame; + else + { +#define AUDIO_DATA(i) index > 0 ? _mixFrame->data[i].get() : pFrame->data[i].get() + switch (getSampleFormat()) + { + case SampleFormat::U8: + case SampleFormat::U8P: + for (uint32_t i = 0; i < getChannels(); i++) + mix(0, UINT8_MAX, AUDIO_DATA(i), pReadFrame->data[i].get(), + _mixFrame->data[i].get(), pFrame->length); + break; + + case SampleFormat::S16: + case SampleFormat::S16P: + for (uint32_t i = 0; i < getChannels(); i++) + mix(INT16_MIN, INT16_MAX, AUDIO_DATA(i), pReadFrame->data[i].get(), + _mixFrame->data[i].get(), pFrame->length); + break; + + case SampleFormat::S32: + case SampleFormat::S32P: + for (uint32_t i = 0; i < getChannels(); i++) + mix(INT32_MIN, INT32_MAX, AUDIO_DATA(i), pReadFrame->data[i].get(), + _mixFrame->data[i].get(), pFrame->length); + break; + + case SampleFormat::S64: + case SampleFormat::S64P: + for (uint32_t i = 0; i < getChannels(); i++) + mix(INT64_MIN, INT64_MAX, AUDIO_DATA(i), pReadFrame->data[i].get(), + _mixFrame->data[i].get(), pFrame->length); + break; + + case SampleFormat::FLT: + case SampleFormat::FLTP: + for (uint32_t i = 0; i < getChannels(); i++) + mix(-1.0f, 1.0f, AUDIO_DATA(i), pReadFrame->data[i].get(), + _mixFrame->data[i].get(), pFrame->length); + break; + + case SampleFormat::DBL: + case SampleFormat::DBLP: + for (uint32_t i = 0; i < getChannels(); i++) + mix(-1.0, 1.0, AUDIO_DATA(i), pReadFrame->data[i].get(), + _mixFrame->data[i].get(), pFrame->length); + break; + + default: + break; + } + index++; + } + } + } + + if (pFrame.isNull()) + return nullptr; + + _traffic++; + if (index > 0) + { + _mixFrame->timestamp = pts; + _mixFrame->length = pFrame->length; + _mixFrame->code = pFrame->code; + return _mixFrame; + } + else + return pFrame; +} + +void AudioMixer::refreshOutputParams() +{ + setFrameDuration((float)1000000 / getSampleRate() * getFrameSize()); + setChannels(channelLayoutChannels(getChannelLayout())); + setLineSize(sampleFormatCalcLineSize(getSampleFormat(), getChannelLayout(), getFrameSize())); +} +} +} diff --git a/video/core/AudioMixer.h b/video/core/AudioMixer.h new file mode 100644 index 0000000..0ed69f5 --- /dev/null +++ b/video/core/AudioMixer.h @@ -0,0 +1,102 @@ +#ifndef AC195091_8442_4268_B843_700AB594416A +#define AC195091_8442_4268_B843_700AB594416A +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioAdaptablePipe.h" +#include "AbstractAudioInput.h" +#include "util/FactoryManager.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class AudioMixerSelector +{ +public: + static int select(const std::string&) + { + return 0; + } +}; + +class AudioMixer: public AbstractAudioInput, + public ::Util::FactoryManager +{ +public: + using Ptr = Poco::AutoPtr; + class Channel: public AudioAdaptablePipe + { + public: + using Ptr = Poco::AutoPtr; + + protected: + Channel(AudioMixer::Ptr, uint32_t id); + + public: + ~Channel(); + + CONFIGURABLE_DECLARE_PARAMETER(Attach, bool) + CONFIGURABLE_DECLARE_PARAMETER(Input, AudioComponent::Ptr) + + uint32_t id() const; + + protected: + virtual void start() override; + virtual void stop() override; + void onConfigChanged(ParameterNames& names) override; + + AudioMixer::Ptr mixer(); + + private: + AudioMixer::Ptr _pMixer; + uint32_t _id; + + friend AudioMixer; + }; + +public: + AudioMixer(const std::string& name); + ~AudioMixer(); + + Channel::Ptr getChannel(uint32_t id); + + void syncConfigToChannel(Channel::Ptr pChannel); + +protected: + void start() override; + void stop() override; + void onConfigChanged(ParameterNames& names) override; + Frame::Ptr readFrame() override; + + void refreshOutputParams(); + + PROPERTY_DECLARE(FrameDuration, uint32_t, 0, protected, protected) + PROPERTY_DECLARE(Channels, uint32_t, 0, protected, protected) + PROPERTY_DECLARE(LineSize, uint32_t, 0, protected, protected) + +private: + std::unordered_map _channels; + Frame::Ptr _mixFrame; +}; + +inline uint32_t AudioMixer::Channel::id() const +{ + return _id; +} + +inline AudioMixer::Ptr AudioMixer::Channel::mixer() +{ + return _pMixer; +} +} +} +#endif diff --git a/video/core/AudioOutput.cpp b/video/core/AudioOutput.cpp new file mode 100644 index 0000000..159c666 --- /dev/null +++ b/video/core/AudioOutput.cpp @@ -0,0 +1,58 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioOutput.h" +#include "Logger.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +void AudioOutput::start() +{ + _task.run( + [this](std::atomic& isCancelled) -> void + { + while (!isCancelled) + { + try + { + Frame::Ptr pFrame = readFrame(1000); + writeFrame(pFrame); + } + catch (Poco::Exception& e) + { + logger().error("%s: %s", name(), e.displayText()); + Poco::Thread::sleep(1000); + } + catch (std::exception& e) + { + logger().error("%s: %s", name(), std::string(e.what())); + Poco::Thread::sleep(1000); + } + } + }); +} + +void AudioOutput::stop() +{ + _task.cancel(); +} + +void AudioOutput::onLink(Core::Component::Ptr pPrevious) +{ + if (!frameQueues().empty() && frameQueues().firstComponent() != pPrevious) + throw Poco::LogicException(name() + ": Support only one previous component, conflict with: " + + pPrevious->name() + ", Current: " + frameQueues().firstComponent()->name()); + + frameQueues().add(pPrevious, name(), MediaType::AUDIO, 6); +} +} +} diff --git a/video/core/AudioOutput.h b/video/core/AudioOutput.h new file mode 100644 index 0000000..5c844d7 --- /dev/null +++ b/video/core/AudioOutput.h @@ -0,0 +1,52 @@ +#ifndef F16FAA29_322A_4A89_945E_E56FFADAC2CD +#define F16FAA29_322A_4A89_945E_E56FFADAC2CD +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioAdaptablePipe.h" +#include "util/FactoryManager.h" +#include "util/Task.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ + +class AudioOutputSelector +{ +public: + static const std::string select(const std::string& arg) + { + auto pos = arg.find(':'); + return (pos != std::string::npos) ? arg.substr(0, pos) : arg; + } +}; + +class AudioOutput + : public AudioAdaptablePipe, + public ::Util::FactoryManager +{ +public: + using AudioAdaptablePipe::AudioAdaptablePipe; + +protected: + void start() override; + void stop() override; + + virtual void writeFrame(Frame::Ptr pFrame) = 0; + void onLink(Core::Component::Ptr pPrevious) override; + +private: + Util::Task _task; +}; +} +} +#endif diff --git a/video/core/AudioResample.cpp b/video/core/AudioResample.cpp new file mode 100644 index 0000000..b6e24ea --- /dev/null +++ b/video/core/AudioResample.cpp @@ -0,0 +1,28 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioResample.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +AudioResample::AudioResample(const std::string& name, Core::ChannelLayout ilayout, Core::SampleFormat iformat, + int irate, Core::ChannelLayout olayout, Core::SampleFormat oformat, int orate) + : _name(name), _ilayout(ilayout), _iformat(iformat), _irate(irate), _olayout(olayout), _oformat(oformat), + _orate(orate) +{ +} + +AudioResample::~AudioResample() +{ +} +} +} diff --git a/video/core/AudioResample.h b/video/core/AudioResample.h new file mode 100644 index 0000000..ae5f033 --- /dev/null +++ b/video/core/AudioResample.h @@ -0,0 +1,100 @@ +#ifndef F62FEA52_888A_474C_B58C_B83007D75F89 +#define F62FEA52_888A_474C_B58C_B83007D75F89 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "ChannelLayout.h" +#include "SampleFormat.h" +#include "Frame.h" +#include "util/FactoryManager.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class AudioResampleSelector +{ +public: + static int select(ChannelLayout, SampleFormat, int, ChannelLayout, SampleFormat, int) + { + return 0; + } +}; + +class AudioResample: public ::Util::FactoryManager +{ +public: + using Ptr = Poco::SharedPtr; + +public: + AudioResample(const std::string& name, Core::ChannelLayout ilayout, Core::SampleFormat iformat, int irate, + Core::ChannelLayout olayout, Core::SampleFormat oformat, int orate); + virtual ~AudioResample(); + +public: + virtual Frame::Ptr process(Frame::Ptr in) = 0; + + const std::string name() const; + const Core::ChannelLayout inLayout() const; + const Core::SampleFormat inFormat() const; + const int inRate() const; + const Core::ChannelLayout outLayout() const; + const Core::SampleFormat outFormat() const; + const int outRate() const; + +private: + const std::string _name; + Core::ChannelLayout _ilayout; + Core::SampleFormat _iformat; + int _irate; + Core::ChannelLayout _olayout; + Core::SampleFormat _oformat; + int _orate; +}; + +inline const std::string AudioResample::name() const +{ + return _name; +} + +inline const Core::ChannelLayout AudioResample::inLayout() const +{ + return _ilayout; +} + +inline const Core::SampleFormat AudioResample::inFormat() const +{ + return _iformat; +} + +inline const int AudioResample::inRate() const +{ + return _irate; +} + +inline const Core::ChannelLayout AudioResample::outLayout() const +{ + return _olayout; +} + +inline const Core::SampleFormat AudioResample::outFormat() const +{ + return _oformat; +} + +inline const int AudioResample::outRate() const +{ + return _orate; +} +} +} +#endif diff --git a/video/core/BitrateControl.cpp b/video/core/BitrateControl.cpp new file mode 100644 index 0000000..0b2e825 --- /dev/null +++ b/video/core/BitrateControl.cpp @@ -0,0 +1,37 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "BitrateControl.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * LOCAL VARIABLES + ******************************************************************************* + */ +static const std::string _bcStrings[] = { + "CBR", "VBR", "AVBR", "QVBR", "CVBR", "FIXQP", "QPMAP", +}; + +/* + ******************************************************************************* + * FUNCTIONS + ******************************************************************************* + */ +const std::string bitrateControlToString(BitrateControl bc) +{ + unsigned index = (unsigned)bc; + + if (index >= (unsigned)BitrateControl::NB) + return Poco::format("UNKNOWN(%u)", index); + + return _bcStrings[index]; +} +} +} diff --git a/video/core/BitrateControl.h b/video/core/BitrateControl.h new file mode 100644 index 0000000..fb84a34 --- /dev/null +++ b/video/core/BitrateControl.h @@ -0,0 +1,40 @@ +#ifndef BE785709_C62A_4233_A050_C24D88194CE4 +#define BE785709_C62A_4233_A050_C24D88194CE4 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +enum class BitrateControl +{ + CBR, + VBR, + AVBR, + QVBR, + CVBR, + FIXQP, + QPMAP, + + NB, +}; + +/* + ******************************************************************************* + * FUNCTIONS + ******************************************************************************* + */ +const std::string bitrateControlToString(BitrateControl bc); +} +} +#endif diff --git a/video/core/ChannelLayout.cpp b/video/core/ChannelLayout.cpp new file mode 100644 index 0000000..bd5b5c7 --- /dev/null +++ b/video/core/ChannelLayout.cpp @@ -0,0 +1,79 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "ChannelLayout.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * TYPEDEFS + ******************************************************************************* + */ +struct ChannelLayoutInfo +{ + std::string name; + int channels; +}; + +/* + ******************************************************************************* + * LOCAL VARIABLES + ******************************************************************************* + */ +static const ChannelLayoutInfo channelLayoutInfo[(int)ChannelLayout::NB] = { + { + "L", + 1, + }, + { + "R", + 1, + }, + { + "MONO", + 1, + }, + { + "STEREO", + 2, + }, +}; + +/* + ******************************************************************************* + * LOCAL FUNCTIONS + ******************************************************************************* + */ +static inline bool isChannelLayoutValid(ChannelLayout layout) +{ + return ((int)layout >= 0) && (layout < ChannelLayout::NB); +} + +/* + ******************************************************************************* + * FUNCTIONS + ******************************************************************************* + */ +int channelLayoutChannels(ChannelLayout layout) +{ + if (isChannelLayoutValid(layout)) + return channelLayoutInfo[(int)layout].channels; + + return 0; +} + +const std::string channelLayoutToString(const ChannelLayout layout) +{ + if (isChannelLayoutValid(layout)) + return channelLayoutInfo[(int)layout].name; + + return Poco::format("UNKNOWN(%u)", (uint32_t)layout); +} +} +} diff --git a/video/core/ChannelLayout.h b/video/core/ChannelLayout.h new file mode 100644 index 0000000..741efd3 --- /dev/null +++ b/video/core/ChannelLayout.h @@ -0,0 +1,34 @@ +#ifndef CD9FD626_A6CF_41FF_B2B2_050F7BEA7195 +#define CD9FD626_A6CF_41FF_B2B2_050F7BEA7195 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +enum class ChannelLayout +{ + L, + R, + + MONO, + STEREO, + + NB, +}; + +int channelLayoutChannels(ChannelLayout layout); +const std::string channelLayoutToString(const ChannelLayout layout); +} +} +#endif diff --git a/video/core/CodecCapabilityManager.cpp b/video/core/CodecCapabilityManager.cpp new file mode 100644 index 0000000..306b3af --- /dev/null +++ b/video/core/CodecCapabilityManager.cpp @@ -0,0 +1,61 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "CodecCapabilityManager.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +const int CodecCapabilityManager::INVALID_HANDLE = -1; + +CodecCapabilityManager::CodecCapabilityManager(uint64_t total): _total(total), _handles(1024) +{ +} + +CodecCapabilityManager::Handle CodecCapabilityManager::alloc(uint32_t width, uint32_t height, uint32_t frameRate) +{ + uint64_t v = width * height * frameRate; + Poco::FastMutex::ScopedLock lock(_lock); + if (v <= _total) + { + try + { + Handle h = _handles.allocHandle(); + _allocRecord[h] = v; + _total -= v; + return h; + } + catch (...) + { + } + } + + return INVALID_HANDLE; +} + +void CodecCapabilityManager::free(Handle handle) +{ + if (handle != INVALID_HANDLE) + { + Poco::FastMutex::ScopedLock lock(_lock); + try + { + _handles.freeHandle(handle); + _total += _allocRecord[handle]; + _allocRecord.erase(handle); + } + catch (...) + { + } + } +} +} +} diff --git a/video/core/CodecCapabilityManager.h b/video/core/CodecCapabilityManager.h new file mode 100644 index 0000000..8408b12 --- /dev/null +++ b/video/core/CodecCapabilityManager.h @@ -0,0 +1,42 @@ +#ifndef B88D46AC_14A3_4C6F_ADE1_2AF8BB03F51A +#define B88D46AC_14A3_4C6F_ADE1_2AF8BB03F51A +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "util/HandlesManager.h" +#include +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class CodecCapabilityManager +{ +public: + using Handle = int; + static const int INVALID_HANDLE; + +public: + CodecCapabilityManager(uint64_t total); + +public: + Handle alloc(uint32_t width, uint32_t height, uint32_t frameRate); + void free(Handle handle); + +private: + uint64_t _total; + Util::HandlesManager _handles; + std::map _allocRecord; + Poco::FastMutex _lock; +}; +} +} +#endif diff --git a/video/core/CodecID.cpp b/video/core/CodecID.cpp new file mode 100644 index 0000000..8320bc9 --- /dev/null +++ b/video/core/CodecID.cpp @@ -0,0 +1,39 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "CodecID.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * LOCAL VARIABLES + ******************************************************************************* + */ +static const std::string _strings[] = { + "H264", "H265", "MJPEG", + + "MP3", "AAC", "OPUS", "G711A", "G711U", +}; + +/* + ******************************************************************************* + * FUNCTIONS + ******************************************************************************* + */ +const std::string codecIDToString(const CodecID id) +{ + unsigned index = (unsigned)id; + + if (index >= (unsigned)CodecID::NB) + return Poco::format("UNKNOWN(%u)", index); + + return _strings[index]; +} +} +} diff --git a/video/core/CodecID.h b/video/core/CodecID.h new file mode 100644 index 0000000..3953bd7 --- /dev/null +++ b/video/core/CodecID.h @@ -0,0 +1,42 @@ +#ifndef BB532961_7DA1_415B_A98F_EB80C5C9DC17 +#define BB532961_7DA1_415B_A98F_EB80C5C9DC17 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +enum class CodecID +{ + H264, + H265, + MJPEG, + + MP3, + AAC, + OPUS, + G711A, + G711U, + + NB, +}; + +/* + ******************************************************************************* + * FUNCTIONS + ******************************************************************************* + */ +const std::string codecIDToString(const CodecID codecID); +} +} +#endif diff --git a/video/core/CodecProfile.h b/video/core/CodecProfile.h new file mode 100644 index 0000000..1779740 --- /dev/null +++ b/video/core/CodecProfile.h @@ -0,0 +1,35 @@ +#ifndef D1AC8D55_FFF4_4CDD_9A78_3DD77F9417B4 +#define D1AC8D55_FFF4_4CDD_9A78_3DD77F9417B4 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +enum class CodecProfile +{ + H264_BASELINE, + H264_MAIN, + H264_HIGH, + H264_EXTENDED, + H264_SVC_T, + H264_SVC_Q, + H264_SVC_S, + + H265_MAIN, + H265_MAIN_10, + + NB, +}; +} +} +#endif diff --git a/video/core/Color.cpp b/video/core/Color.cpp new file mode 100755 index 0000000..a5907cc --- /dev/null +++ b/video/core/Color.cpp @@ -0,0 +1,82 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Color.h" +#include "Poco/NumberParser.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Color::Color(std::string s) +{ + uint32_t value = 0xffffff; + try + { + int pos = s.find('#'); + if (pos >= 0) + value = Poco::NumberParser::parseHex(s.substr(pos + 1)); + else + value = Poco::NumberParser::parse(s); + } + catch (...) + { + } + + new (this) Color(value); +} + +Color::Color(uint32_t n) +{ + new (this) Color((n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff); +} + +Color::Color() +{ + new (this) Color(0); +} + +void Color::toARGB1555Pixel(uint8_t v[2]) +{ + uint32_t alpha = (a + 127) / 255; + uint32_t red = (r * 31 + 127) / 255; + uint32_t green = (g * 31 + 127) / 255; + uint32_t blue = (b * 31 + 127) / 255; + + uint16_t pixel = (alpha << 15) | (red << 10) | (green << 5) | blue; + + v[0] = pixel; + v[1] = pixel >> 8; +} + +void Color::toARGB4444Pixel(uint8_t v[2]) +{ + uint16_t pixel = (a >> 4) << 12 | (r >> 4) << 8 | (g >> 4) << 4 | (b >> 4); + + v[0] = pixel; + v[1] = pixel >> 8; +} + +void Color::toRGB888Pixel(uint8_t v[3]) +{ + v[0] = r; + v[1] = g; + v[2] = b; +} + +void Color::toARGB8888Pixel(uint8_t v[4]) +{ + v[0] = b; + v[1] = g; + v[2] = r; + v[3] = a; +} +} +} diff --git a/video/core/Color.h b/video/core/Color.h new file mode 100755 index 0000000..3205e7b --- /dev/null +++ b/video/core/Color.h @@ -0,0 +1,47 @@ +#ifndef D9CEFFBC_A4C8_4C8D_9D61_5A2E533AFA42 +#define D9CEFFBC_A4C8_4C8D_9D61_5A2E533AFA42 + +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +struct Color +{ + Color(uint8_t a, uint8_t r, uint8_t g, uint8_t b): a(a), r(r), g(g), b(b) + { + } + + Color(uint8_t r, uint8_t g, uint8_t b): Color(255, r, g, b) + { + } + + Color(std::string s); + Color(uint32_t n); + Color(); + + void toARGB1555Pixel(uint8_t v[2]); + void toARGB4444Pixel(uint8_t v[2]); + void toRGB888Pixel(uint8_t v[3]); + void toARGB8888Pixel(uint8_t v[4]); + + uint8_t a; + uint8_t r; + uint8_t g; + uint8_t b; +}; +} +} +#endif diff --git a/video/core/Component.cpp b/video/core/Component.cpp new file mode 100644 index 0000000..2e2dba2 --- /dev/null +++ b/video/core/Component.cpp @@ -0,0 +1,350 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Component.h" +#include "System.h" +#include "util/Math.h" +#include "Logger.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +bool Component::Collection::add(Ptr pObject) +{ + Poco::FastMutex::ScopedLock lock(_lock); + auto iter = std::find(_list.begin(), _list.end(), pObject); + if (iter == _list.end()) + { + _list.push_back(pObject); + return true; + } + + return false; +} + +bool Component::Collection::remove(Ptr pObject) +{ + Poco::FastMutex::ScopedLock lock(_lock); + auto iter = std::find(_list.begin(), _list.end(), pObject); + if (iter != _list.end()) + { + _list.remove(pObject); + return true; + } + + return false; +} + +void Component::Collection::foreach (std::function func) +{ + std::vector v; + { + Poco::FastMutex::ScopedLock lock(_lock); + for (auto& ptr : _list) + v.push_back(ptr); + } + + for (auto& ptr : v) + func(ptr); +} + +uint32_t Component::Collection::size() +{ + Poco::FastMutex::ScopedLock lock(_lock); + return _list.size(); +} + +Component::Ptr Component::Collection::first() +{ + return *(_list.begin()); +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Component::Component(const std::string& name): _name(name), _frameQueues(this), _packetQueues(this), _started(false) +{ + logger().debug("%s(%#x): CREATE.", name, reinterpret_cast(this)); +} + +Component::~Component() +{ + logger().debug("%s(%#x): RELEASE.", name(), reinterpret_cast(this)); +} + +Component::Ptr Component::link(Ptr pNext) +{ + if (pNext.isNull()) + return nullptr; + + if (nextComponents().add(pNext)) + { + logger().debug("%s(%#x): ===> %s(%#x)", name(), reinterpret_cast(this), pNext->name(), + reinterpret_cast(pNext.get())); + pNext->doLink(sharedFromThis()); + } + + return pNext; +} + +void Component::unlink(Component::Ptr pNext) +{ + if (pNext.isNull()) + return; + + if (nextComponents().remove(pNext)) + { + logger().debug("%s(%#x): =X=> %s(%#x)", name(), reinterpret_cast(this), pNext->name(), + reinterpret_cast(pNext.get())); + pNext->doUnlink(sharedFromThis()); + } +} + +void Component::unlink(bool unlinkAllPrevious, bool unlinkAllNext) +{ + if (unlinkAllPrevious) + { + previousComponents().foreach ([this](Ptr pObject) -> void { pObject->unlink(sharedFromThis()); }); + } + + if (unlinkAllNext) + { + nextComponents().foreach ([this](Ptr pObject) -> void { unlink(pObject); }); + } +} + +void Component::enable(const void* pUser, const std::string& desc) +{ + logger().debug("%s(%#x): Enabled by %s(%#x)", name(), reinterpret_cast(this), desc, + reinterpret_cast(pUser)); + Poco::Mutex::ScopedLock lock(_componentsLock); + bool shouldStart = (userCount() == 0); + incrementUserRefCount(pUser); + if (shouldStart) + { + frameQueues().flush(); + packetQueues().flush(); + + initialize(); + + previousComponents().foreach ([&](Ptr ptr) -> void { ptr->enable(this); }); + + logger().debug("%s(%#x): START.", name(), reinterpret_cast(this)); + start(); + _traffic = 0; + _started = true; + } +} + +void Component::disable(const void* pUser, const std::string& desc, bool ignoreRefCount) +{ + logger().debug("%s(%#x): Disabled by %s(%#x)", name(), reinterpret_cast(this), desc, + reinterpret_cast(pUser)); + Poco::Mutex::ScopedLock lock(_componentsLock); + if (!started()) + { + logger().debug("%s(%#x): Stop already.", name(), reinterpret_cast(this)); + return; + } + + if (ignoreRefCount) + eraseUser(pUser); + else + decrementUserRefCount(pUser); + + if (userCount() == 0) + { + logger().debug("%s(%#x): STOP.", name(), reinterpret_cast(this)); + stop(); + deinitialize(); + _traffic = 0; + _started = false; + + previousComponents().foreach ([&](Component::Ptr ptr) -> void { ptr->disable(this); }); + + frameQueues().flush(); + packetQueues().flush(); + } +} + +int Component::userCount() +{ + Poco::Mutex::ScopedLock lock(_componentsLock); + return _users.size(); +} + +int Component::nextComponentCount() +{ + return nextComponents().size(); +} + +int Component::previousComponentCount() +{ + return previousComponents().size(); +} + +QueueStatus Component::queueStatus(Ptr who) +{ + { + auto pQueue = packetQueues().find(who); + if (pQueue) + return pQueue->status(); + } + + { + auto pQueue = frameQueues().find(who); + if (pQueue) + return pQueue->status(); + } + + return QueueStatus::UNKNOWN; +} + +void Component::initialize() +{ +} + +void Component::deinitialize() +{ +} + +void Component::doLink(Ptr pPrevious) +{ + if (previousComponents().add(pPrevious)) + { + onLink(pPrevious); + if (started()) + pPrevious->enable(this); + } +} + +void Component::onLink(Ptr pPrevious) +{ +} + +void Component::doUnlink(Ptr pPrevious) +{ + if (previousComponents().remove(pPrevious)) + { + onUnlink(pPrevious); + pPrevious->disable(this, true); + } +} + +void Component::onUnlink(Ptr pPrevious) +{ +} + +void Component::post(Ptr pSender, Frame::Ptr pFrame, MediaType type, bool force) +{ + post(pSender, frameQueues(), pFrame, type, force); +} + +void Component::post(Ptr pSender, Packet::Ptr pPacket, MediaType type, bool force) +{ + post(pSender, packetQueues(), pPacket->clone(), type, force); +} + +void Component::notify(Notification::Ptr pNotification, bool shouldPropagate) +{ + _notificationCenter.postNotification(pNotification); + if (shouldPropagate) + { + logger().debug("%s(Sender is '%s'): Notify '%s' to next components", name(), pNotification->getSender()->name(), + pNotification->name()); + nextComponents().foreach ( + [&](Component::Ptr ptr) -> void + { + if (ptr->started()) + ptr->notify(pNotification, shouldPropagate); + }); + } +} + +void Component::notifyNextComponents(Ptr pSender, Notification::Ptr pNotification, bool shouldPropagate) +{ + logger().debug("%s: Notify '%s' to next components", pSender->name(), pNotification->name()); + pNotification->setSender(pSender); + nextComponents().foreach ( + [&](Component::Ptr ptr) -> void + { + if (ptr->started()) + ptr->notify(pNotification, shouldPropagate); + }); +} + +void Component::incrementUserRefCount(const void* pUser) +{ + if (_users.find(pUser) == _users.end()) + _users[pUser] = 0; + + _users[pUser]++; +} + +void Component::decrementUserRefCount(const void* pUser) +{ + if (_users.find(pUser) != _users.end()) + { + _users[pUser]--; + if (_users[pUser] <= 0) + _users.erase(pUser); + } +} + +void Component::eraseUser(const void* pUser) +{ + _users.erase(pUser); +} + +bool Component::syncWait(uint64_t ts, std::atomic& isCancelled) +{ + uint64_t now = System::timestamp(); + while (ts > now && !isCancelled) + { + now = System::timestamp(); + usleep(100); + } + + return (now >= ts); +} + +bool Component::syncWait(uint64_t ts, uint32_t maxWaitTime) +{ + const uint64_t startTime = System::timestamp(); + const uint64_t timeout = maxWaitTime * 1000; + while (true) + { + uint64_t now = System::timestamp(); + + if (now >= ts) + return true; + + if (now - startTime >= timeout) + return false; + + const uint64_t remaining = ::Util::Math::min(ts - now, timeout - (now - startTime)); + const useconds_t us = ::Util::Math::clamp(remaining / 10, 1000ULL, 100000ULL); + usleep(us); + } +} + +void Component::publishEvent(Event event, bool sync) +{ + if (sync) + EventOccurred.notify(this, event); + else + EventOccurred.notifyAsync(this, event); +} +} +} diff --git a/video/core/Component.h b/video/core/Component.h new file mode 100644 index 0000000..f1c805f --- /dev/null +++ b/video/core/Component.h @@ -0,0 +1,408 @@ +#ifndef AB01F7C9_DADD_4ADA_88F6_ED371CF36813 +#define AB01F7C9_DADD_4ADA_88F6_ED371CF36813 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "util/Configurable.h" +#include "util/ClassAssistant.h" +#include "Frame.h" +#include "Packet.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Component: public Poco::RefCountedObject, public Util::Configurable +{ +public: + enum class Event + { + IMAGE_SIZE_CHANGED, + }; + + using Ptr = Poco::AutoPtr; + class Collection + { + public: + bool add(Ptr); + bool remove(Ptr); + void foreach (std::function func); + uint32_t size(); + Ptr first(); + + private: + std::list _list; + Poco::FastMutex _lock; + }; + + class Notification: public Poco::Notification + { + public: + using Ptr = Poco::AutoPtr; + + public: + PROPERTY_DECLARE(Sender, Component::Ptr, nullptr, public, public); + + std::string name() const override + { + return Util::ClassAssistant::demangle(*this); + } + }; + + template class Queues + { + public: + Queues(Ptr pOwner); + + void add(Ptr, const std::string& name, MediaType, int limit = 300); + void remove(Ptr); + void clear(); + + bool exists(Ptr); + bool empty(); + typename QT::Ptr first(); + typename QT::Ptr find(Ptr); + Ptr firstComponent(); + void flush(); + + template void foreach (const F&& func); + + private: + Ptr _pOwner; + Poco::FastMutex _mutex; + std::map _queues; + }; + +public: + Component(const std::string& name); + Component(const Component&) = delete; + virtual ~Component(); + +public: + const std::string& name() const; + + Ptr link(Ptr pNext); + void unlink(Ptr pNext); + void unlink(bool unlinkAllPrevious, bool unlinkAllNext); + + template void enable(T* pObject); + template void disable(T* pObject, bool ignoreRefCount = false); + template bool enabled(T* pObject); + + Ptr sharedFromThis(); + void enable(const void* pUser, const std::string& desc); + void disable(const void* pUser, const std::string& desc, bool ignoreRefCount = false); + + int userCount(); + int nextComponentCount(); + int previousComponentCount(); + const SerialCode& code(); + + QueueStatus queueStatus(Ptr who); + + virtual uint32_t traffic(); + + bool started() const; + + Poco::BasicEvent EventOccurred; + +protected: + virtual void start() = 0; + virtual void stop() = 0; + virtual void initialize(); + virtual void deinitialize(); + virtual void doLink(Ptr pPrevious); + virtual void onLink(Ptr pPrevious); + virtual void doUnlink(Ptr pPrevious); + virtual void onUnlink(Ptr pPrevious); + + Collection& previousComponents(); + Collection& nextComponents(); + + SerialCodeManager& serialCodeManager(); + SerialCodeWatcher& serialCodeWatcher(); + + void post(Ptr pSender, Frame::Ptr pFrame, MediaType type, bool force); + void post(Ptr pSender, Packet::Ptr pPacket, MediaType type, bool force); + + template + void post(Ptr pSender, TQueue& queues, TObjectPtr pObject, MediaType type, bool force); + + template void postToNextComponents(Ptr pSender, TObjectPtr pObject, MediaType type); + + template + bool postToNextComponents(Ptr pSender, TObjectPtr pObject, MediaType type, std::atomic& isCancelled); + + template void registerNotificationHandle(C& object, const H& handle); + template void unregisterNotificationHandle(C& object, const H& handle); + + void notify(Notification::Ptr pNotification, bool shouldPropagate); + void notifyNextComponents(Ptr pSender, Notification::Ptr pNotification, bool shouldPropagate = false); + + Queues& frameQueues(); + Queues& packetQueues(); + + void incrementUserRefCount(const void* pUser); + void decrementUserRefCount(const void* pUser); + void eraseUser(const void* pUser); + + bool syncWait(uint64_t ts, std::atomic& isCancelled); + bool syncWait(uint64_t ts, uint32_t maxWaitTime); + + void publishEvent(Event event, bool sync = true); + +protected: + uint32_t _traffic; + +private: + const std::string _name; + Collection _previousComponents; + Collection _nextComponents; + Poco::Mutex _componentsLock; + Queues _frameQueues; + Queues _packetQueues; + std::unordered_map _users; + bool _started; + SerialCodeManager _serialCodeManager; + SerialCodeWatcher _serialCodeWatcher; + Poco::NotificationCenter _notificationCenter; +}; + +template inline Component::Queues::Queues(Ptr pOwner): _pOwner(pOwner) +{ +} + +template void Component::Queues::add(Ptr pComponent, const std::string& name, MediaType type, int limit) +{ + if (exists(pComponent)) + throw Poco::ExistsException(_pOwner->name() + "'s queue: " + pComponent->name() + " EXISTS!"); + + Poco::FastMutex::ScopedLock lock(_mutex); + _queues[pComponent] = new QT(name, type, limit); +} + +template void Component::Queues::remove(Ptr pComponent) +{ + Poco::FastMutex::ScopedLock lock(_mutex); + _queues.erase(pComponent); +} + +template inline void Component::Queues::clear() +{ + Poco::FastMutex::ScopedLock lock(_mutex); + return _queues.clear(); +} + +template inline bool Component::Queues::exists(Ptr pComponent) +{ + Poco::FastMutex::ScopedLock lock(_mutex); + return _queues.find(pComponent) != _queues.end(); +} + +template inline bool Component::Queues::empty() +{ + Poco::FastMutex::ScopedLock lock(_mutex); + return _queues.empty(); +} + +template inline typename QT::Ptr Component::Queues::first() +{ + Poco::FastMutex::ScopedLock lock(_mutex); + if (_queues.empty()) + return nullptr; + + return _queues.begin()->second; +} + +template inline typename QT::Ptr Component::Queues::find(Ptr pComponent) +{ + Poco::FastMutex::ScopedLock lock(_mutex); + const auto it = _queues.find(pComponent); + if (it != _queues.end()) + return it->second; + + return nullptr; +} + +template inline Component::Ptr Component::Queues::firstComponent() +{ + Poco::FastMutex::ScopedLock lock(_mutex); + if (_queues.empty()) + return nullptr; + + return _queues.begin()->first; +} + +template inline void Component::Queues::flush() +{ + Poco::FastMutex::ScopedLock lock(_mutex); + for (auto& q : _queues) + q.second->clear(); +} + +template template inline void Component::Queues::foreach (const F&& func) +{ + Poco::FastMutex::ScopedLock lock(_mutex); + for (auto& q : _queues) + func(q.first, q.second); +} + +inline const std::string& Component::name() const +{ + return _name; +} + +inline bool Component::started() const +{ + return _started; +} + +inline const SerialCode& Component::code() +{ + return serialCodeManager().code(); +} + +inline uint32_t Component::traffic() +{ + return _traffic; +} + +inline Component::Collection& Component::previousComponents() +{ + return _previousComponents; +} + +inline Component::Collection& Component::nextComponents() +{ + return _nextComponents; +} + +inline SerialCodeManager& Component::serialCodeManager() +{ + return _serialCodeManager; +} + +inline SerialCodeWatcher& Component::serialCodeWatcher() +{ + return _serialCodeWatcher; +} + +inline Component::Queues& Component::frameQueues() +{ + return _frameQueues; +} + +inline Component::Queues& Component::packetQueues() +{ + return _packetQueues; +} + +template inline void Component::enable(T* pObject) +{ + enable(pObject, std::is_base_of::value ? reinterpret_cast(pObject)->name() + : Util::ClassAssistant::demangle()); +} + +template inline void Component::disable(T* pObject, bool ignoreRefCount) +{ + disable(pObject, + std::is_base_of::value ? reinterpret_cast(pObject)->name() + : Util::ClassAssistant::demangle(), + ignoreRefCount); +} + +template inline bool Video::Core::Component::enabled(T* pUser) +{ + Poco::Mutex::ScopedLock lock(_componentsLock); + return (_users.find(pUser) != _users.end()); +} + +template +inline void Component::post(Ptr pSender, TQueue& queues, TObjectPtr pObject, MediaType type, bool force) +{ + auto pQueue = queues.find(pSender); + if (pQueue && pQueue->type() == type) + { + pQueue->enqueue(pObject, force); + _traffic++; + } +} + +template inline void Component::postToNextComponents(Ptr pSender, TObjectPtr pObject, MediaType type) +{ + nextComponents().foreach ( + [&](Ptr ptr) -> void + { + if (ptr->started()) + ptr->post(pSender, pObject, type, true); + }); +} + +template +inline bool Component::postToNextComponents(Ptr pSender, TObjectPtr pObject, MediaType type, + std::atomic& isCancelled) +{ + int count = 0; + int num = 0; + nextComponents().foreach ( + [&](Ptr ptr) -> void + { + if (ptr->started()) + { + num++; + do + { + try + { + ptr->post(pSender, pObject, type, false); + count++; + break; + } + catch (...) + { + if (!isCancelled) + ::usleep(1000); + } + } while (!isCancelled); + } + }); + + return count == num; +} + +template inline void Component::registerNotificationHandle(C& object, const H& handle) +{ + _notificationCenter.addObserver(Poco::NObserver(object, handle)); +} + +template inline void Component::unregisterNotificationHandle(C& object, const H& handle) +{ + _notificationCenter.removeObserver(Poco::NObserver(object, handle)); +} + +inline Component::Ptr Component::sharedFromThis() +{ + return Ptr(this, true); +} +} +} +#endif diff --git a/video/core/Demuxer.cpp b/video/core/Demuxer.cpp new file mode 100644 index 0000000..fa8bcd9 --- /dev/null +++ b/video/core/Demuxer.cpp @@ -0,0 +1,312 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Demuxer.h" +#include "System.h" +#include "Logger.h" +#include "util/Broadcast.h" +#include +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +class DummyVideoEncoder: public VideoEncoder +{ +public: + DummyVideoEncoder(const std::string& name): VideoEncoder(name, CodecID::H264) + { + } + +protected: + void onConfigChanged(ParameterNames& names) override + { + if (names.exists(_CodecID)) + serialCodeManager().put(_CodecID, getCodecID()); + + if (names.exists(_Width)) + serialCodeManager().put(_Width, getWidth()); + + if (names.exists(_Height)) + serialCodeManager().put(_Height, getHeight()); + + if (names.exists(_FrameRate)) + serialCodeManager().put(_FrameRate, getFrameRate()); + } +}; + +class DummyAudioEncoder: public AudioEncoder +{ +public: + DummyAudioEncoder(const std::string& name): AudioEncoder(name, CodecID::AAC) + { + } + +protected: + Core::Packet::Ptr encode() override + { + return nullptr; + } + + void onConfigChanged(ParameterNames& names) override + { + if (names.exists(_CodecID)) + serialCodeManager().put(_CodecID, getCodecID()); + + if (names.exists(_SampleRate)) + serialCodeManager().put(_SampleRate, getSampleRate()); + + if (names.exists(_SampleFormat)) + serialCodeManager().put(_SampleFormat, getSampleFormat()); + } +}; + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +DemuxHandler::DemuxHandler(const std::string& name, const std::string& uri) + : _name(name), _uri(uri), _scheme(Poco::URI(uri).getScheme()), _timeDrift(0) +{ +} + +DemuxHandler::~DemuxHandler() +{ +} + +int64_t DemuxHandler::getTimeDrift(uint64_t ts) +{ + if (0 == _timeDrift) + _timeDrift = getBaseline() - ts; + + return _timeDrift; +} + +void DemuxHandler::resetTimeDrift() +{ + _timeDrift = 0; +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +class DemuxerHandlerController: public Util::BroadcastReceiver, public Util::BroadcastReceiverRegistry +{ +public: + DemuxerHandlerController(DemuxHandler::Ptr pHandler, int index, std::atomic& isCancelled) + : _skip(false), _newIndex(0), _newPosition(0), _pHandler(pHandler), _index(index), _isCancelled(isCancelled) + { + Util::IntentFilter* pFilter = new Util::IntentFilter; + pFilter->addAction("carousel.position"); + registerReceiver(*this, pFilter); + } + + ~DemuxerHandlerController() + { + unregisterReceiver(*this); + } + +protected: + void onReceive(Util::Intent::Ptr pIntent) override + { + int index = pIntent->getExtra("index"); + int position = pIntent->getExtra("position"); + if (index < 0 || position < 0) + return; + + uint64_t ts = position * 1000000u; + + if (index != _index) + { + _skip = true; + _newIndex = index; + _newPosition = ts; + + _isCancelled = true; + + return; + } + + _pHandler->seek(ts); + } + + READONLY_PROPERTY_DECLARE(skip, bool) + READONLY_PROPERTY_DECLARE(newIndex, int) + READONLY_PROPERTY_DECLARE(newPosition, uint64_t) + +private: + DemuxHandler::Ptr _pHandler; + int _index; + std::atomic& _isCancelled; +}; + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Demuxer::Demuxer(const std::string& name) + : Component(name), _pVideoEncoder(new DummyVideoEncoder(name + "/VideoEncoder")), + _pAudioEncoder(new DummyAudioEncoder(name + "/AudioEncoder")) +{ + setUsingTCPTransport(false); + setURICollection({}); +} + +Demuxer::~Demuxer() +{ +} + +#define __try_run(x) \ + try \ + { \ + x \ + } \ + catch (Poco::Exception & e) \ + { \ + logger().error("%s: %s", name(), e.displayText()); \ + Poco::Thread::sleep(2000); \ + } \ + catch (std::exception & e) \ + { \ + logger().error("%s: %s", name(), std::string(e.what())); \ + Poco::Thread::sleep(2000); \ + } + +void Demuxer::start() +{ + if (getURICollection().size() == 0) + { + logger().warning("%s: URI collection is empty!", name()); + return; + } + + _task.run( + [this](std::atomic& isCancelled) -> void + { + _playIndex = 0; + PlayIndexChanged.notify(this); + + int index = 0; + + uint64_t position = 0; + + uint64_t timeline = 1000000; + while (!isCancelled) + { + std::string uri; + DemuxHandler::Ptr pHandler; + + __try_run({ uri = getURICollection().at(index); }); + __try_run({ pHandler = DemuxHandler::create(uri); }); + + if (!pHandler.isNull()) + { + __try_run({ + _playPosition = 0; + + if (Poco::URI(uri).getScheme() == "rtsp") + pHandler->setUsingTCPTransport(getUsingTCPTransport()); + pHandler->setBaseline(timeline); + + Poco::SharedPtr pController; + if (Poco::URI(uri).getScheme() == "file") + { + pController = new DemuxerHandlerController(pHandler, index, isCancelled); + if (position) + { + pHandler->seek(position); + position = 0; + } + } + + pHandler->run( + videoEncoder(), audioEncoder(), + [&](Packet::Ptr pPacket, MediaType type) -> void + { + if (postToNextComponents(sharedFromThis(), pPacket, type, isCancelled)) + { + int64_t pos = pHandler->getPosition(); + int64_t diff = std::abs(pos - _playPosition); + if (diff >= 1000000) + { + _playPosition = pos; + PlayPositionChanged.notify(this); + } + } + }, + isCancelled); + + timeline = pHandler->getTimeline(); + + if (pController && pController->skip()) + { + isCancelled = false; + + index = pController->newIndex(); + position = pController->newPosition(); + + _playIndex = index; + _playPosition = position; + + PlayIndexChanged.notify(this); + PlayPositionChanged.notify(this); + + continue; + } + }); + } + else + logger().error("%s: Not implemented URI scheme '%s'", name(), Poco::URI(uri).getScheme()); + + if (++index >= (int)getURICollection().size()) + index = 0; + + if (_playIndex != index) + { + _playIndex = index; + PlayIndexChanged.notify(this); + } + } + }); +} + +void Demuxer::stop() +{ + _task.cancel(); +} + +void Demuxer::onLink(Component::Ptr pPrevious) +{ + throw Poco::LogicException("Not supported for link"); +} + +void Demuxer::onUnlink(Component::Ptr pPrevious) +{ + throw Poco::LogicException("Not supported for link"); +} + +void Demuxer::onConfigChanged(ParameterNames& names) +{ + if (!started()) + return; + + if (names.exists(_UsingTCPTransport) || names.exists(_URICollection)) + { + stop(); + start(); + } +} +} +} diff --git a/video/core/Demuxer.h b/video/core/Demuxer.h new file mode 100644 index 0000000..06a0c6c --- /dev/null +++ b/video/core/Demuxer.h @@ -0,0 +1,149 @@ +#ifndef ADECE10E_B193_46F7_B377_651677F4AD89 +#define ADECE10E_B193_46F7_B377_651677F4AD89 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Component.h" +#include "util/FactoryManager.h" +#include "util/SimpleFactory.h" +#include "VideoEncoder.h" +#include "AudioEncoder.h" +#include "util/Task.h" +#include "util/ClassAssistant.h" +#include +#include +#include +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class DemuxHandlerSelector +{ +public: + static const std::string select(const std::string& uri) + { + return Poco::URI(uri).getScheme(); + } +}; + +class DemuxHandler + : public ::Util::FactoryManager +{ +public: + using Ptr = Poco::SharedPtr; + using Post = std::function; + using ShouldWait = std::function; + +public: + DemuxHandler(const std::string& name, const std::string& uri); + virtual ~DemuxHandler(); + +public: + virtual void run(VideoEncoder::Ptr pVideoEncoder, AudioEncoder::Ptr pAudioEncoder, const Post& post, + std::atomic& isCancelled) = 0; + + const std::string& name() const; + const std::string& uri() const; + const std::string& scheme() const; + + virtual void seek(uint64_t ts) = 0; + + PROPERTY_DECLARE(UsingTCPTransport, bool, false, public, public) + PROPERTY_DECLARE(Baseline, uint64_t, 0, public, public) + PROPERTY_DECLARE(Timeline, uint64_t, 0, public, public) + PROPERTY_DECLARE(Position, int64_t, 0, protected, public) + +protected: + int64_t getTimeDrift(uint64_t ts); + void resetTimeDrift(); + +private: + const std::string _name; + const std::string _uri; + const std::string _scheme; + int64_t _timeDrift; +}; + +inline const std::string& DemuxHandler::name() const +{ + return _name; +} + +inline const std::string& DemuxHandler::uri() const +{ + return _uri; +} + +inline const std::string& DemuxHandler::scheme() const +{ + return _scheme; +} + +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Demuxer: public Component, public ::Util::SimpleFactory +{ +public: + using Ptr = Poco::AutoPtr; + using URICollection = std::vector; + +public: + Demuxer(const std::string& name); + ~Demuxer(); + +public: + CONFIGURABLE_DECLARE_PARAMETER(UsingTCPTransport, bool) + CONFIGURABLE_DECLARE_PARAMETER(URICollection, URICollection&) + + Poco::BasicEvent PlayIndexChanged; + Poco::BasicEvent PlayPositionChanged; + + const int playIndex() const; + VideoEncoder::Ptr videoEncoder(); + AudioEncoder::Ptr audioEncoder(); + +protected: + void start() override; + void stop() override; + void onLink(Component::Ptr pPrevious) override; + void onUnlink(Component::Ptr pPrevious) override; + void onConfigChanged(ParameterNames& names) override; + + READONLY_PROPERTY_DECLARE(playIndex, int) + READONLY_PROPERTY_DECLARE(playPosition, int64_t) + +private: + Util::Task _task; + VideoEncoder::Ptr _pVideoEncoder; + AudioEncoder::Ptr _pAudioEncoder; +}; + +inline const int Demuxer::playIndex() const +{ + return _playIndex; +} + +inline VideoEncoder::Ptr Demuxer::videoEncoder() +{ + return _pVideoEncoder; +} + +inline AudioEncoder::Ptr Demuxer::audioEncoder() +{ + return _pAudioEncoder; +} +} +} +#endif diff --git a/video/core/Frame.cpp b/video/core/Frame.cpp new file mode 100644 index 0000000..3c9dbd2 --- /dev/null +++ b/video/core/Frame.cpp @@ -0,0 +1,19 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Frame.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +const int Frame::DATA_LINES = NUM_DATA_POINTERS; +} +} diff --git a/video/core/Frame.h b/video/core/Frame.h new file mode 100644 index 0000000..41a7710 --- /dev/null +++ b/video/core/Frame.h @@ -0,0 +1,42 @@ +#ifndef D8F82C97_DBD1_40D1_9ABD_E78F9B225CF5 +#define D8F82C97_DBD1_40D1_9ABD_E78F9B225CF5 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "NotificationQueue.h" +#include "SerialCode.h" +#include + +/* + ******************************************************************************* + * CONSTANTS + ******************************************************************************* + */ +#define NUM_DATA_POINTERS 8 + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +struct Frame +{ + using Ptr = Poco::SharedPtr; + static const int DATA_LINES; + + Poco::SharedPtr data[NUM_DATA_POINTERS]; + uint32_t length; + uint64_t timestamp; + SerialCode code; +}; + +using FrameQueue = NotificationQueue; +} +} +#endif diff --git a/video/core/FrameSynchronizer.cpp b/video/core/FrameSynchronizer.cpp new file mode 100644 index 0000000..32971de --- /dev/null +++ b/video/core/FrameSynchronizer.cpp @@ -0,0 +1,111 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "FrameSynchronizer.h" +#include "util/Math.h" +#include +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +FrameSynchronizer::DriftPredictor::DriftPredictor(size_t windowSize): _windowSize(windowSize), _lastPrediction(0) +{ +} + +void FrameSynchronizer::DriftPredictor::update(int64_t drift) +{ + _samples.push_back(drift); + + if (_samples.size() > _windowSize) + _samples.pop_front(); + + constexpr double alpha = 0.2; + _lastPrediction = alpha * drift + (1 - alpha) * _lastPrediction; +} + +int64_t FrameSynchronizer::DriftPredictor::predict() +{ + if (_samples.empty()) + return _lastPrediction; + + if (_samples.size() < 5) + return _lastPrediction; + + double sum = 0; + for (auto v : _samples) + sum += v; + double mean = sum / _samples.size(); + + constexpr int64_t MAX_DRIFT = 30000; + + int64_t result = static_cast(0.7 * mean + 0.3 * _lastPrediction); + return Util::Math::clamp(result, -MAX_DRIFT, MAX_DRIFT); +} + +void FrameSynchronizer::DriftPredictor::reset() +{ + _samples.clear(); + _lastPrediction = 0; +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +FrameSynchronizer::FrameSynchronizer(const std::string& name): _name(name), _baseClock(0), _lastSysTS(0) +{ + _predictor.reset(); +} + +FrameSynchronizer::~FrameSynchronizer() +{ +} + +void FrameSynchronizer::reset() +{ + _baseClock = 0; + _lastSysTS = 0; + _predictor.reset(); +} + +void FrameSynchronizer::syncFrame(uint64_t frameTS) +{ + const uint64_t now = System::timestamp(); + + if (_baseClock == 0) + { + _baseClock = now - frameTS; + _lastSysTS = now; + return; + } + + uint64_t ts = frameTS + _baseClock; + if (ts + 500000 < _lastSysTS || ts > _lastSysTS + 10000000) + { + _predictor.reset(); + _baseClock = now - frameTS; + _lastSysTS = now; + return; + } + + const uint64_t targetTime = frameTS + _baseClock + _predictor.predict(); + int64_t waitTime = static_cast(targetTime) - static_cast(now); + if (waitTime > 0) + std::this_thread::sleep_for(std::chrono::microseconds(waitTime)); + + const uint64_t actualTime = System::timestamp(); + _predictor.update(static_cast(actualTime - targetTime)); + _lastSysTS = actualTime; +} +} +} diff --git a/video/core/FrameSynchronizer.h b/video/core/FrameSynchronizer.h new file mode 100644 index 0000000..bc9b81c --- /dev/null +++ b/video/core/FrameSynchronizer.h @@ -0,0 +1,58 @@ +#ifndef B6D2EB85_A2D9_4037_BDBB_5ADCDA3E3B01 +#define B6D2EB85_A2D9_4037_BDBB_5ADCDA3E3B01 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "System.h" +#include "util/ClassAssistant.h" +#include +#include +#include +#include +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class FrameSynchronizer +{ +public: + FrameSynchronizer(const std::string& name); + ~FrameSynchronizer(); + + void syncFrame(uint64_t frameTs); + + void reset(); + +private: + class DriftPredictor + { + public: + DriftPredictor(size_t windowSize = 10); + void update(int64_t drift); + int64_t predict(); + void reset(); + + private: + std::deque _samples; + size_t _windowSize; + double _lastPrediction; + }; + + READONLY_PROPERTY_DECLARE(name, std::string) + + uint64_t _baseClock; + uint64_t _lastSysTS; + DriftPredictor _predictor; +}; +} +} +#endif diff --git a/video/core/Frontend.cpp b/video/core/Frontend.cpp new file mode 100644 index 0000000..57eb5e3 --- /dev/null +++ b/video/core/Frontend.cpp @@ -0,0 +1,54 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Frontend.h" +#include "Logger.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Frontend::Frontend(std::string name): _name(name) +{ +} + +Frontend::~Frontend() +{ +} + +std::string Frontend::name() +{ + return _name; +} + +void Frontend::notifyStatusChanged() +{ + MediaInfo info; + read(info); + notifyStatusChanged(info); +} + +void Frontend::notifyStatusChanged(MediaInfo& info) +{ + try + { + StatusChanged.notify(this, info); + } + catch (Poco::Exception& e) + { + logger().error("%s: status changed: %s", name(), e.displayText()); + } + catch (std::exception& e) + { + logger().error("%s: status changed: %s", name(), std::string(e.what())); + } +} +} +} diff --git a/video/core/Frontend.h b/video/core/Frontend.h new file mode 100644 index 0000000..27ac8ab --- /dev/null +++ b/video/core/Frontend.h @@ -0,0 +1,63 @@ +#ifndef F4486E13_26A4_47A9_B4C0_193369C1A700 +#define F4486E13_26A4_47A9_B4C0_193369C1A700 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoScanMode.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Frontend +{ +public: + using Ptr = Poco::SharedPtr; + + struct MediaInfo + { + bool haveVideo; + bool haveAudio; + + struct + { + uint32_t width; + uint32_t height; + uint32_t frameRate; + VideoScanMode scanMode; + } video; + + struct + { + uint32_t sampleRate; + } audio; + }; + +public: + Poco::BasicEvent StatusChanged; + +public: + Frontend(std::string name); + virtual ~Frontend(); + + std::string name(); + virtual void read(MediaInfo& info) = 0; + +protected: + void notifyStatusChanged(); + void notifyStatusChanged(MediaInfo& info); + +private: + std::string _name; +}; +} +} +#endif diff --git a/video/core/GOPMode.cpp b/video/core/GOPMode.cpp new file mode 100644 index 0000000..ec526e8 --- /dev/null +++ b/video/core/GOPMode.cpp @@ -0,0 +1,40 @@ +#ifndef BB0BDE8C_97B1_4665_97FE_505C703706CD +#define BB0BDE8C_97B1_4665_97FE_505C703706CD +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "GOPMode.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * LOCAL VARIABLES + ******************************************************************************* + */ +static const std::string _gmString[] = { + "NORMAL_P", "DUAL_P", "SMART_P", "ADV_SMART_P", "BIPRED_B", "LOWDELAY_B", +}; + +/* + ******************************************************************************* + * FUNCTIONS + ******************************************************************************* + */ +const std::string gopModeToString(GOPMode gm) +{ + unsigned index = (unsigned)gm; + + if (index >= (unsigned)GOPMode::NB) + return Poco::format("UNKNOWN(%u)", index); + + return _gmString[index]; +} +} +} +#endif diff --git a/video/core/GOPMode.h b/video/core/GOPMode.h new file mode 100644 index 0000000..e23a216 --- /dev/null +++ b/video/core/GOPMode.h @@ -0,0 +1,39 @@ +#ifndef E0A1BF3F_980A_41BE_A542_2066821A64EB +#define E0A1BF3F_980A_41BE_A542_2066821A64EB +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +enum class GOPMode +{ + NORMAL_P, + DUAL_P, + SMART_P, + ADV_SMART_P, + BIPRED_B, + LOWDELAY_B, + + NB +}; + +/* + ******************************************************************************* + * FUNCTIONS + ******************************************************************************* + */ +const std::string gopModeToString(GOPMode gm); +} +} +#endif diff --git a/video/core/GeometryTypes.cpp b/video/core/GeometryTypes.cpp new file mode 100644 index 0000000..bdc210a --- /dev/null +++ b/video/core/GeometryTypes.cpp @@ -0,0 +1,27 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "GeometryTypes.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +bool Rect::operator!=(const Rect& other) +{ + return x != other.x || y != other.y || width != other.width || height != other.height; +} + +bool Size::operator!=(const Size& other) +{ + return width != other.width || height != other.height; +} +} +} diff --git a/video/core/GeometryTypes.h b/video/core/GeometryTypes.h new file mode 100644 index 0000000..3f6d946 --- /dev/null +++ b/video/core/GeometryTypes.h @@ -0,0 +1,37 @@ +#ifndef B9536093_A419_43A3_9220_7B1A65A2B31F +#define B9536093_A419_43A3_9220_7B1A65A2B31F +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +struct Rect +{ + bool operator!=(const Rect& other); + + int x; + int y; + int width; + int height; +}; + +struct Size +{ + bool operator!=(const Size& other); + + int width; + int height; +}; +} +} +#endif diff --git a/video/core/Image.cpp b/video/core/Image.cpp new file mode 100644 index 0000000..bc02842 --- /dev/null +++ b/video/core/Image.cpp @@ -0,0 +1,279 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Image.h" +#include "Logger.h" +#include "util/Math.h" +#include +#include +#include +#include +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +void Image::drawText(const std::string& text, const std::string& font, int size, Color foreColor, Color bgColor) +{ + get_shared_channel(0).fill(bgColor.r); + get_shared_channel(1).fill(bgColor.g); + get_shared_channel(2).fill(bgColor.b); + get_shared_channel(3).fill(bgColor.a); + + int x = 0, y = 0; + + y = height() - y; + + std::wstring wtext; + convertText(text, wtext); + runOnFreeType(font, size, + [&](FT_Face& face) -> void + { + FT_BBox bbox; + + computeStringBBox(wtext, face, bbox); + + FT_Vector pen; + pen.x = (x - bbox.xMin) * 64; + pen.y = (y - bbox.yMax) * 64; + + for (size_t i = 0; i < wtext.length(); i++) + { + FT_Set_Transform(face, 0, &pen); + FT_UInt glyph_index = FT_Get_Char_Index(face, wtext.at(i)); + int error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); + if (error) + { + throw Poco::InvalidAccessException("字形加载失败", error); + } + error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); + if (error) + { + throw Poco::InvalidAccessException("字形渲染失败", error); + } + + int offsetX = face->glyph->bitmap_left; + int offsetY = height() - face->glyph->bitmap_top; + for (size_t y = 0; y < face->glyph->bitmap.rows; y++) + { + for (size_t x = 0; x < face->glyph->bitmap.width; x++) + { + uint8_t c = face->glyph->bitmap.buffer[y * face->glyph->bitmap.width + x]; + if (c < 50) + continue; + + (*this)(x + offsetX, y + offsetY, 0) = foreColor.r * c / 255; + (*this)(x + offsetX, y + offsetY, 1) = foreColor.g * c / 255; + (*this)(x + offsetX, y + offsetY, 2) = foreColor.b * c / 255; + (*this)(x + offsetX, y + offsetY, 3) = foreColor.a * c / 255; + } + } + + pen.x += face->glyph->advance.x; + pen.y += face->glyph->advance.y; + } + }); +} + +void Image::drawRectangle(const Rect& rect, Color& color, int border) +{ + if (border <= 0) + { + get_shared_channel(0).fill(color.r); + get_shared_channel(1).fill(color.g); + get_shared_channel(2).fill(color.b); + get_shared_channel(3).fill(color.a); + } + else + { + get_shared_channel(3).fill(0); + + uint8_t c[4] = {color.r, color.g, color.b, color.a}; + + draw_rectangle(rect.x, rect.y, rect.x + rect.width, rect.y + border, c); + draw_rectangle(rect.x, rect.y + border, rect.x + border, rect.y + rect.height, c); + draw_rectangle(rect.x + rect.width - border, rect.y + border, rect.x + rect.width, rect.y + rect.height, c); + draw_rectangle(rect.x + border, rect.y + rect.height - border, rect.x + rect.width - border, + rect.y + rect.height, c); + } +} + +void Image::drawImage(int x, int y, Ptr pImage) +{ + const int width = pImage->width(); + const int height = pImage->height(); + + for (int sy = 0; sy < height; ++sy) + { + for (int sx = 0; sx < width; ++sx) + { + const int dx = x + sx; + const int dy = y + sy; + + if (dx >= 0 && dx < this->width() && dy >= 0 && dy < this->height()) + { + Color color; + pImage->getPixel(sx, sy, color); + + (*this)(dx, dy, 0, 0) = color.r; + (*this)(dx, dy, 0, 1) = color.g; + (*this)(dx, dy, 0, 2) = color.b; + (*this)(dx, dy, 0, 3) = color.a; + } + } + } +} + +void Image::getPixel(int x, int y, Color& color) +{ + uint8_t originAlpha = 0xff; + + if (spectrum() == 1) + { + color.r = (*this)(x, y, 0, 0); + color.g = color.r; + color.b = color.r; + } + else if (spectrum() == 2) + { + color.r = (*this)(x, y, 0, 0); + color.g = (*this)(x, y, 0, 1); + color.b = 0; + } + + else if (spectrum() == 3) + { + color.r = (*this)(x, y, 0, 0); + color.g = (*this)(x, y, 0, 1); + color.b = (*this)(x, y, 0, 2); + } + else if (spectrum() == 4) + { + color.r = (*this)(x, y, 0, 0); + color.g = (*this)(x, y, 0, 1); + color.b = (*this)(x, y, 0, 2); + originAlpha = (*this)(x, y, 0, 3); + } + + if (_forceAlphaEnabled && originAlpha > _forceAlphaValue) + color.a = _forceAlphaValue; + else + color.a = originAlpha; +} + +void Image::forceAlpha(uint8_t alpha) +{ + _forceAlphaEnabled = true; + _forceAlphaValue = alpha; +} + +void Image::computeStringSize(const std::string& text, const std::string& font, int fontsz, Size& size) +{ + runOnFreeType(font, fontsz, + [&](FT_Face& face) -> void + { + FT_BBox bbox; + std::wstring wstr; + convertText(text, wstr); + + computeStringBBox(wstr, face, bbox); + size.width = bbox.xMax - bbox.xMin; + size.height = bbox.yMax - bbox.yMin; + + size.width = Util::Math::alignup(size.width, 2); + size.height = Util::Math::alignup(size.height, 2); + }); +} + +int Image::computeStringBBox(std::wstring text, FT_Face face, FT_BBox& bbox) +{ + size_t i; + int error; + FT_BBox glyph_bbox; + FT_Vector pen; + FT_Glyph glyph; + FT_GlyphSlot slot = face->glyph; + + bbox.xMin = bbox.yMin = 32000; + bbox.xMax = bbox.yMax = -32000; + + pen.x = 0; + pen.y = 0; + + for (i = 0; i < text.length(); i++) + { + FT_Set_Transform(face, 0, &pen); + + error = FT_Load_Char(face, text.at(i), FT_LOAD_RENDER); + if (error) + { + logger().error("FT_Load_Char error: %d(%s)", error, std::string(FT_Error_String(error))); + return -1; + } + + error = FT_Get_Glyph(face->glyph, &glyph); + if (error) + { + logger().error("FT_Get_Glyph error: %d(%s)", error, std::string(FT_Error_String(error))); + return -1; + } + + FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &glyph_bbox); + + if (glyph_bbox.xMin < bbox.xMin) + bbox.xMin = glyph_bbox.xMin; + + if (glyph_bbox.yMin < bbox.yMin) + bbox.yMin = glyph_bbox.yMin; + + if (glyph_bbox.xMax > bbox.xMax) + bbox.xMax = glyph_bbox.xMax; + + if (glyph_bbox.yMax > bbox.yMax) + bbox.yMax = glyph_bbox.yMax; + + FT_Done_Glyph(glyph); + + pen.x += slot->advance.x; + pen.y += slot->advance.y; + } + + return 0; +} + +void Image::convertText(const std::string& from, std::wstring& to) +{ + Poco::UnicodeConverter::toUTF16(from, to); +} + +void Image::runOnFreeType(const std::string& font, int size, std::function func) +{ + FT_Library library; + FT_Face face; + FT_Error error; + + error = FT_Init_FreeType(&library); + if (error) + throw Poco::InvalidAccessException( + Poco::format("FreeType init error: %d(%s)", error, std::string(FT_Error_String(error))), error); + error = FT_New_Face(library, font.c_str(), 0, &face); + if (error) + throw Poco::InvalidAccessException( + Poco::format("FreeType new face error: %d(%s)", error, std::string(FT_Error_String(error))), error); + + FT_Set_Pixel_Sizes(face, 0, size); + func(face); + + FT_Done_Face(face); + FT_Done_FreeType(library); +} +} +} diff --git a/video/core/Image.h b/video/core/Image.h new file mode 100644 index 0000000..c204438 --- /dev/null +++ b/video/core/Image.h @@ -0,0 +1,52 @@ +#ifndef CF80473A_40DD_43E5_B0D7_73F2205E9DF6 +#define CF80473A_40DD_43E5_B0D7_73F2205E9DF6 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Color.h" +#include "GeometryTypes.h" +#include +#include +#include FT_FREETYPE_H +#include +#include + +using namespace cimg_library; + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Image: public CImg +{ +public: + using Ptr = Poco::SharedPtr; + using CImg::CImg; + + void drawText(const std::string& text, const std::string& font, int size, Color foreColor, Color bgColor); + void drawRectangle(const Rect& rect, Color& color, int border = 0); + void drawImage(int x, int y, Ptr pImage); + void getPixel(int x, int y, Color& color); + void forceAlpha(uint8_t alpha); + + static void computeStringSize(const std::string& text, const std::string& font, int fontsz, Size& size); + +protected: + static int computeStringBBox(std::wstring text, FT_Face face, FT_BBox& bbox); + static void convertText(const std::string& from, std::wstring& to); + static void runOnFreeType(const std::string& font, int size, std::function func); + +private: + bool _forceAlphaEnabled = false; + uint8_t _forceAlphaValue; +}; +} +} +#endif diff --git a/video/core/MediaType.h b/video/core/MediaType.h new file mode 100644 index 0000000..e339593 --- /dev/null +++ b/video/core/MediaType.h @@ -0,0 +1,28 @@ +#ifndef D3D5F4A2_6250_4C2B_A293_7D6F3A83F95F +#define D3D5F4A2_6250_4C2B_A293_7D6F3A83F95F + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +enum class MediaType +{ + UNKNOWN = -1, + + VIDEO, + AUDIO, + + DATA, + SUBTITLE, + ATTACHMENT, + + NB +}; +} +} +#endif diff --git a/video/core/Muxer.cpp b/video/core/Muxer.cpp new file mode 100644 index 0000000..610be03 --- /dev/null +++ b/video/core/Muxer.cpp @@ -0,0 +1,95 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Muxer.h" +#include "AudioComponent.h" +#include "VideoComponent.h" +#include "Logger.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Muxer::Muxer(const std::string& name, const std::string& url): Component(name), _url(url) +{ +} + +Muxer::~Muxer() +{ +} + +void Muxer::start() +{ + _task.run( + [this](std::atomic& isCancelled) -> void + { + while (!isCancelled) + { + try + { + connect(); + while (!isCancelled) + mux(); + } + catch (Poco::Exception& e) + { + logger().error("%s(CONNECT/MUX): %s", name(), e.displayText()); + } + catch (std::exception& e) + { + logger().error("%s(CONNECT/MUX): %s", name(), std::string(e.what())); + } + + try + { + disconnect(); + } + catch (Poco::Exception& e) + { + logger().error("%s(DISCONNECT): %s", name(), e.displayText()); + } + catch (std::exception& e) + { + logger().error("%s(DISCONNECT): %s", name(), std::string(e.what())); + } + + if (!isCancelled) + Poco::Thread::sleep(1000); + } + }); +} + +void Muxer::stop() +{ + _task.cancel(); +} + +void Muxer::onLink(Component::Ptr pPrevious) +{ + if (dynamic_cast(pPrevious.get())) + packetQueues().add(pPrevious, name(), MediaType::AUDIO); + else if (dynamic_cast(pPrevious.get())) + packetQueues().add(pPrevious, name(), MediaType::VIDEO); + else + { + logger().error("%s: Unsupport class '%s'", name(), std::string(typeid(*pPrevious).name())); + } +} + +void Muxer::onUnlink(Component::Ptr pPrevious) +{ + packetQueues().remove(pPrevious); +} + +void Muxer::onConfigChanged(ParameterNames& names) +{ +} +} +} diff --git a/video/core/Muxer.h b/video/core/Muxer.h new file mode 100644 index 0000000..fe3c257 --- /dev/null +++ b/video/core/Muxer.h @@ -0,0 +1,72 @@ +#ifndef B61EDCD6_1C1C_4355_AD2C_4A09C55099D0 +#define B61EDCD6_1C1C_4355_AD2C_4A09C55099D0 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Component.h" +#include "util/FactoryManager.h" +#include "util/Task.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class MuxerSelector +{ +public: + static const std::string select(const std::string& url) + { + return Poco::URI(url).getScheme(); + } +}; + +class Muxer: public Component, + public ::Util::FactoryManager +{ +public: + using Ptr = Poco::AutoPtr; + +public: + Muxer(const std::string& name, const std::string& url); + virtual ~Muxer(); + +public: + const std::string& url() const; + std::string scheme() const; + +protected: + void start() override; + void stop() override; + void onLink(Component::Ptr pPrevious) override; + void onUnlink(Component::Ptr pPrevious) override; + void onConfigChanged(ParameterNames& names) override; + + virtual void connect() = 0; + virtual void mux() = 0; + virtual void disconnect() = 0; + +private: + std::string _url; + Util::Task _task; +}; + +inline const std::string& Muxer::url() const +{ + return _url; +} + +inline std::string Muxer::scheme() const +{ + return Poco::URI(url()).getScheme(); +} +} +} +#endif diff --git a/video/core/NotificationQueue.cpp b/video/core/NotificationQueue.cpp new file mode 100644 index 0000000..a44f782 --- /dev/null +++ b/video/core/NotificationQueue.cpp @@ -0,0 +1,18 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "NotificationQueue.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +} +} diff --git a/video/core/NotificationQueue.h b/video/core/NotificationQueue.h new file mode 100644 index 0000000..b7c7df1 --- /dev/null +++ b/video/core/NotificationQueue.h @@ -0,0 +1,132 @@ +#ifndef D93EB1DD_2BCB_47B7_9361_91841576013D +#define D93EB1DD_2BCB_47B7_9361_91841576013D +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "MediaType.h" +#include +#include +#include +#include +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +template class Notification: public Poco::Notification +{ +public: + Notification(typename T::Ptr pObject): _pObject(pObject) + { + } + +public: + typename T::Ptr get() + { + return _pObject; + } + +private: + typename T::Ptr _pObject; +}; + +enum class QueueStatus +{ + UNKNOWN = -1, + NORMAL, + OVERHALF, + FULL, +}; + +template class NotificationQueue +{ +public: + using Ptr = Poco::SharedPtr>; + +public: + NotificationQueue(const std::string& name, MediaType type, int limit = 300); + +public: + MediaType type() const; + + void enqueue(typename T::Ptr pObject, bool force = true); + typename T::Ptr dequeue(long timeout); + void clear(); + QueueStatus status(); + int size(); + +private: + Poco::NotificationQueue _queue; + std::string _name; + MediaType _type; + int _limit; +}; + +template +inline NotificationQueue::NotificationQueue(const std::string& name, MediaType type, int limit) + : _name(name), _type(type), _limit(limit) +{ +} + +template inline MediaType NotificationQueue::type() const +{ + return _type; +} + +template inline void NotificationQueue::enqueue(typename T::Ptr pObject, bool force) +{ + if (_queue.size() >= _limit) + { + if (!force) + throw std::overflow_error("Queue overflow!"); + + std::cout << "NotificationQueue(" + _name + "[" << (type() == MediaType::VIDEO ? "Video" : "Audio") + << "]): OVERFLOW!" << std::endl; + _queue.dequeueNotification()->release(); + } + + _queue.enqueueNotification(new Notification(pObject)); +} + +template inline typename T::Ptr NotificationQueue::dequeue(long timeout) +{ + Poco::Notification::Ptr p = _queue.waitDequeueNotification(timeout); + if (!p) + throw Poco::TimeoutException("Dequeue timeout: " + std::string(type() == MediaType::VIDEO ? "Video" : "Audio")); + + return p.cast>()->get(); +} + +template inline void NotificationQueue::clear() +{ + _queue.clear(); +} + +template inline QueueStatus NotificationQueue::status() +{ + QueueStatus s = QueueStatus::NORMAL; + const auto size = _queue.size(); + + if (size >= _limit) + s = QueueStatus::FULL; + else if (size >= (_limit >> 1)) + s = QueueStatus::OVERHALF; + + return s; +} + +template inline int NotificationQueue::size() +{ + return _queue.size(); +} +} +} +#endif diff --git a/video/core/Notifications.cpp b/video/core/Notifications.cpp new file mode 100644 index 0000000..f05ae45 --- /dev/null +++ b/video/core/Notifications.cpp @@ -0,0 +1,18 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Notifications.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +} +} diff --git a/video/core/Notifications.h b/video/core/Notifications.h new file mode 100644 index 0000000..4d4ca63 --- /dev/null +++ b/video/core/Notifications.h @@ -0,0 +1,39 @@ +#ifndef EE166245_4519_4C28_AD01_A52B7613E95A +#define EE166245_4519_4C28_AD01_A52B7613E95A +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Component.h" +#include "util/Configuration.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class ParameterChangedNotification: public Component::Notification +{ +public: + using Parameters = Util::Configuration::ParameterNames; + + ParameterChangedNotification(const Parameters& parameters): _parameters(parameters) + { + } + + const Parameters& parameters() const + { + return _parameters; + } + +private: + Parameters _parameters; +}; +} +} +#endif diff --git a/video/core/Packet.cpp b/video/core/Packet.cpp new file mode 100644 index 0000000..7d8d183 --- /dev/null +++ b/video/core/Packet.cpp @@ -0,0 +1,137 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Packet.h" +#include "Logger.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +const uint32_t Packet::FLAG_IS_VIDEO = 0x80000000; +const uint32_t Packet::FLAG_IS_KEY = 0x00000001; +const uint32_t Packet::FLAG_IS_AAC_HEADER = 0x00000002; +const uint32_t Packet::FLAG_IS_SEI = 0x04000000; + +Packet::Packet(): _flags(0), _pts(0), _dts(0), _pData(nullptr), _length(0), _pAVPacket(nullptr) +{ +} + +Packet::Packet(const SerialCode& code, uint32_t flags, uint8_t* data, uint32_t length, int64_t pts, int64_t dts) + : _code(code), _flags(flags), _pts(pts), _dts(dts), _pData(data), _length(length), _pAVPacket(nullptr) +{ +} + +#if defined(USING_FFMPEG) +Packet::Packet(const SerialCode& code, bool isVideo, AVPacket** packet) + : _code(code), _pts((*packet)->pts), _dts((*packet)->dts), _pData(nullptr), _length((*packet)->size), + _pAVPacket(*packet) +{ + _flags = 0; + if ((*packet)->flags & AV_PKT_FLAG_KEY) + _flags |= Packet::FLAG_IS_KEY; + if (isVideo) + _flags |= Packet::FLAG_IS_VIDEO; + + *packet = nullptr; +} +#endif + +Packet::Packet(const Packet& other) +{ + _code = other._code; + _flags = other._flags; + _pData = other._pData; + _length = other._length; + _pts = other._pts; + _dts = other._dts; + +#if defined(USING_FFMPEG) + if (other._pAVPacket) + _pAVPacket = av_packet_clone(other._pAVPacket); + else + _pAVPacket = nullptr; +#endif +} + +Packet::~Packet() +{ +#if defined(USING_FFMPEG) + if (_pAVPacket) + av_packet_free(&_pAVPacket); +#endif +} + +#if defined(USING_FFMPEG) +void Packet::moveTo(AVPacket** packet) +{ + if (_pAVPacket) + { + *packet = _pAVPacket; + _pData = nullptr; + _length = 0; + _pAVPacket = nullptr; + } + else + { + *packet = buildAVPacket(); + _pData = nullptr; + _length = 0; + } +} +#endif + +const uint8_t* Packet::data() +{ +#if defined(USING_FFMPEG) + if (_pAVPacket) + return _pAVPacket->data; +#endif + + return _pData.get(); +} + +#if defined(USING_FFMPEG) +AVPacket* Packet::buildAVPacket() +{ + AVPacket* pPacket = av_packet_alloc(); + pPacket->pts = _pts; + pPacket->dts = _dts; + if (_flags & FLAG_IS_KEY) + pPacket->flags |= AV_PKT_FLAG_KEY; + else + pPacket->flags &= ~AV_PKT_FLAG_KEY; + + convertToAVPacket(pPacket); + return pPacket; +} + +void Packet::convertToAVPacket(AVPacket* pPacket) +{ + if (_pData != nullptr) + { + uint8_t* data = (uint8_t*)av_malloc(_length); + memcpy(data, _pData, _length); + int rc = av_packet_from_data(pPacket, data, _length); + if (rc < 0) + { + logger().error("Create av-packet FAIL!"); + av_free(data); + } + + _pData.reset(); + } + else + throw Poco::DataException("Media::Packet::buildAVPacket: No source data"); +} +#endif +} +} diff --git a/video/core/Packet.h b/video/core/Packet.h new file mode 100644 index 0000000..037734e --- /dev/null +++ b/video/core/Packet.h @@ -0,0 +1,126 @@ +#ifndef AE3F35DC_04CA_417F_8D57_CADC8913678F +#define AE3F35DC_04CA_417F_8D57_CADC8913678F +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "NotificationQueue.h" +#include +#include "SerialCode.h" +#if defined(USING_FFMPEG) +extern "C" +{ +#include +#include +} +#endif + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Packet +{ +public: + using Ptr = Poco::SharedPtr; + + static const uint32_t FLAG_IS_VIDEO; + static const uint32_t FLAG_IS_KEY; + static const uint32_t FLAG_IS_AAC_HEADER; + static const uint32_t FLAG_IS_SEI; + +public: + Packet(); + Packet(const SerialCode& code, uint32_t flags, uint8_t* data, uint32_t length, int64_t pts, int64_t dts); +#if defined(USING_FFMPEG) + Packet(const SerialCode& code, bool isVideo, AVPacket** packet); +#endif + Packet(const Packet& other); + virtual ~Packet(); + + virtual void moveTo(AVPacket** packet); + bool isVideo() const; + bool isKeyFrame() const; + Ptr clone(); + + const SerialCode& serialCode(); + const uint8_t* data(); + uint32_t length(); + uint64_t pts(); + uint64_t dts(); + uint32_t flags(); + + Poco::SharedPtr& sharedData(); + +protected: +#if defined(USING_FFMPEG) + AVPacket* buildAVPacket(); + virtual void convertToAVPacket(AVPacket* pPacket); +#endif + +private: + SerialCode _code; + uint32_t _flags; + int64_t _pts, _dts; + Poco::SharedPtr _pData; + uint32_t _length; +#if defined(USING_FFMPEG) + AVPacket* _pAVPacket; +#endif +}; + +using PacketQueue = NotificationQueue; + +inline bool Packet::isVideo() const +{ + return (_flags & FLAG_IS_VIDEO) > 0; +} + +inline bool Packet::isKeyFrame() const +{ + return (_flags & FLAG_IS_KEY) > 0; +} + +inline Packet::Ptr Packet::clone() +{ + return new Packet(*this); +} + +inline uint32_t Packet::length() +{ + return _length; +} + +inline uint64_t Packet::pts() +{ + return _pts; +} + +inline uint64_t Packet::dts() +{ + return _dts; +} + +inline uint32_t Packet::flags() +{ + return _flags; +} + +inline const SerialCode& Packet::serialCode() +{ + return _code; +} + +inline Poco::SharedPtr& Packet::sharedData() +{ + return _pData; +} +} +} +#endif diff --git a/video/core/SampleFormat.cpp b/video/core/SampleFormat.cpp new file mode 100644 index 0000000..bfa2aa2 --- /dev/null +++ b/video/core/SampleFormat.cpp @@ -0,0 +1,119 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "SampleFormat.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * TYPEDEFS + ******************************************************************************* + */ +struct SampleFormatInfo +{ + std::string name; + int bits; + bool isPlanar; + SampleFormat altFormat; +}; + +/* + ******************************************************************************* + * LOCAL VARIABLES + ******************************************************************************* + */ +static const SampleFormatInfo sampleFormatInfo[(int)SampleFormat::NB] = { + {"u8", 8, false, SampleFormat::U8P}, {"s16", 16, false, SampleFormat::S16P}, + {"s32", 32, false, SampleFormat::S32P}, {"s64", 64, false, SampleFormat::S64P}, + {"flt", 32, false, SampleFormat::FLTP}, {"dbl", 64, false, SampleFormat::DBLP}, + {"u8p", 8, true, SampleFormat::U8}, {"s16p", 16, true, SampleFormat::S16}, + {"s32p", 32, true, SampleFormat::S32}, {"s64p", 64, true, SampleFormat::S64}, + {"fltp", 32, true, SampleFormat::FLT}, {"dblp", 64, true, SampleFormat::DBL}, +}; + +static const std::string UNKNOWN = "UNKNOWN"; + +/* + ******************************************************************************* + * LOCAL FUNCTIONS + ******************************************************************************* + */ +static inline bool isSampleFormatValid(SampleFormat format) +{ + return ((int)format >= 0 && format < SampleFormat::NB); +} + +/* + ******************************************************************************* + * FUNCTIONS + ******************************************************************************* + */ +const std::string& sampleFormatName(SampleFormat format) +{ + if (isSampleFormatValid(format)) + return sampleFormatInfo[(int)format].name; + + return UNKNOWN; +} + +int sampleFormatBitWidth(SampleFormat format) +{ + if (isSampleFormatValid(format)) + return sampleFormatInfo[(int)format].bits; + + return 0; +} + +int sampleFormatBytesPerSample(SampleFormat format) +{ + if (isSampleFormatValid(format)) + return sampleFormatInfo[(int)format].bits >> 3; + + return 0; +} + +bool sampleFormatIsPlanar(SampleFormat format) +{ + if (isSampleFormatValid(format)) + return sampleFormatInfo[(int)format].isPlanar; + + return false; +} + +int sampleFormatCalcLineSize(SampleFormat format, ChannelLayout layout, int samples) +{ + int size = sampleFormatBytesPerSample(format) * samples; + if (!sampleFormatIsPlanar(format)) + size *= channelLayoutChannels(layout); + + return size; +} + +SampleFormat sampleFormatGetPackedFormat(SampleFormat format) +{ + if (!isSampleFormatValid(format)) + return SampleFormat::NONE; + + if (!sampleFormatIsPlanar(format)) + return format; + + return sampleFormatInfo[(int)format].altFormat; +} + +SampleFormat sampleFormatGetPlanarFormat(SampleFormat format) +{ + if (!isSampleFormatValid(format)) + return SampleFormat::NONE; + + if (sampleFormatIsPlanar(format)) + return format; + + return sampleFormatInfo[(int)format].altFormat; +} +} +} diff --git a/video/core/SampleFormat.h b/video/core/SampleFormat.h new file mode 100644 index 0000000..c25c0f1 --- /dev/null +++ b/video/core/SampleFormat.h @@ -0,0 +1,50 @@ +#ifndef D05E30E1_E0D8_43BB_A05A_20ABEF7E2072 +#define D05E30E1_E0D8_43BB_A05A_20ABEF7E2072 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "ChannelLayout.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +enum class SampleFormat +{ + NONE = -1, + + U8, + S16, + S32, + S64, + FLT, + DBL, + + U8P, + S16P, + S32P, + S64P, + FLTP, + DBLP, + + NB, +}; + +const std::string& sampleFormatName(SampleFormat format); +int sampleFormatBitWidth(SampleFormat format); +int sampleFormatBytesPerSample(SampleFormat format); +bool sampleFormatIsPlanar(SampleFormat format); +int sampleFormatCalcLineSize(SampleFormat format, ChannelLayout layout, int samples); +SampleFormat sampleFormatGetPackedFormat(SampleFormat format); +SampleFormat sampleFormatGetPlanarFormat(SampleFormat format); +} +} +#endif diff --git a/video/core/SerialCode.cpp b/video/core/SerialCode.cpp new file mode 100644 index 0000000..3007719 --- /dev/null +++ b/video/core/SerialCode.cpp @@ -0,0 +1,107 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "SerialCode.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +bool SerialCode::empty() const +{ + return _value.isNull(); +} + +void SerialCode::reset() +{ + return _value.clear(); +} + +const SerialCode& SerialCode::operator++() +{ + if (empty()) + _value = 0; + + ++_value; + return *this; +} + +const SerialCode& SerialCode::operator++(int) +{ + if (empty()) + _value = 0; + + _value++; + return *this; +} + +bool SerialCode::operator!=(const SerialCode& other) +{ + return _value != other._value; +} + +const SerialCode& SerialCode::operator=(const SerialCode& other) +{ + _value = other._value; + return *this; +} + +const SerialCode& SerialCode::operator=(const uint32_t value) +{ + _value = value; + return *this; +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +SerialCodeManager::SerialCodeManager() +{ + Poco::Random r; + r.seed(); + _code = r.next(); +} + +void SerialCodeManager::increment() +{ + _code++; +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +bool SerialCodeWatcher::isChanged(const SerialCode& code, bool firstAsChanged) +{ + if (_code.empty()) + { + _code = code; + return firstAsChanged; + } + + if (_code != code) + { + _code = code; + return true; + } + + return false; +} + +void SerialCodeWatcher::reset() +{ + _code.reset(); +} +} +} diff --git a/video/core/SerialCode.h b/video/core/SerialCode.h new file mode 100644 index 0000000..b2c05ce --- /dev/null +++ b/video/core/SerialCode.h @@ -0,0 +1,77 @@ +#ifndef F54A2617_3BA7_4779_88A5_AB040FD9B002 +#define F54A2617_3BA7_4779_88A5_AB040FD9B002 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "CodecID.h" +#include +#include +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class SerialCode +{ +public: + bool empty() const; + void reset(); + const SerialCode& operator++(); + const SerialCode& operator++(int); + bool operator!=(const SerialCode& other); + const SerialCode& operator=(const SerialCode& other); + const SerialCode& operator=(const uint32_t other); + +private: + Poco::Nullable _value; +}; + +class SerialCodeManager +{ +public: + SerialCodeManager(); + + template void put(const std::string& name, const T& value); + void increment(); + + const SerialCode& code() const; + +private: + SerialCode _code; + std::map _values; +}; + +template inline void SerialCodeManager::put(const std::string& name, const T& value) +{ + if (_values.find(name) == _values.end() || Poco::AnyCast(_values[name]) != value) + { + _values[name] = value; + _code++; + } +} + +inline const SerialCode& SerialCodeManager::code() const +{ + return _code; +} + +class SerialCodeWatcher +{ +public: + bool isChanged(const SerialCode& code, bool firstAsChanged = false); + void reset(); + +private: + SerialCode _code; +}; +} +} +#endif diff --git a/video/core/System.cpp b/video/core/System.cpp new file mode 100644 index 0000000..b22e84c --- /dev/null +++ b/video/core/System.cpp @@ -0,0 +1,37 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "System.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +System* System::_pInstance = nullptr; +System::System() +{ +} + +System::~System() +{ + delete _pInstance; + _pInstance = nullptr; +} + +uint64_t System::timestamp() +{ + if (!_pInstance) + throw Poco::IllegalStateException("Unregister implement class."); + + return _pInstance->getTimestamp(); +} +} +} diff --git a/video/core/System.h b/video/core/System.h new file mode 100644 index 0000000..d0079bc --- /dev/null +++ b/video/core/System.h @@ -0,0 +1,45 @@ +#ifndef BC5898D8_C525_4FD9_9609_B2112CB1BE92 +#define BC5898D8_C525_4FD9_9609_B2112CB1BE92 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class System +{ +protected: + System(); + +public: + virtual ~System(); + +public: + static uint64_t timestamp(); + + template static void registerClass(); + +protected: + virtual uint64_t getTimestamp() = 0; + +private: + static System* _pInstance; +}; + +template inline void System::registerClass() +{ + _pInstance = new T(); +} +} +} +#endif diff --git a/video/core/VideoComponent.cpp b/video/core/VideoComponent.cpp new file mode 100644 index 0000000..442124c --- /dev/null +++ b/video/core/VideoComponent.cpp @@ -0,0 +1,31 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioComponent.h" +#include "VideoComponent.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Core::VideoComponent::VideoComponent(const std::string& name): Component(name) +{ + setWidth(1920); + setHeight(1080); + setFrameRate(30); + setVideoScanMode(VideoScanMode::PROGRESSIVE); + setSignalLost(true); +} + +VideoComponent::~VideoComponent() +{ +} +} +} diff --git a/video/core/VideoComponent.h b/video/core/VideoComponent.h new file mode 100644 index 0000000..8ed2e6e --- /dev/null +++ b/video/core/VideoComponent.h @@ -0,0 +1,45 @@ +#ifndef CAD9434C_3708_4C44_8A9D_2E146CFECEAC +#define CAD9434C_3708_4C44_8A9D_2E146CFECEAC +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Component.h" +#include "CodecID.h" +#include "ChannelLayout.h" +#include "CodecProfile.h" +#include "BitrateControl.h" +#include "GOPMode.h" +#include "SampleFormat.h" +#include "VideoScanMode.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class VideoComponent: public Component +{ +public: + using Ptr = Poco::AutoPtr; + +public: + VideoComponent(const std::string& name); + virtual ~VideoComponent(); + virtual void reset() {}; + +public: + CONFIGURABLE_DECLARE_PARAMETER(Width, uint32_t); + CONFIGURABLE_DECLARE_PARAMETER(Height, uint32_t); + CONFIGURABLE_DECLARE_PARAMETER(FrameRate, int); + CONFIGURABLE_DECLARE_PARAMETER(VideoScanMode, VideoScanMode); + CONFIGURABLE_DECLARE_PARAMETER(SignalLost, bool); +}; +} +} +#endif diff --git a/video/core/VideoDecoder.cpp b/video/core/VideoDecoder.cpp new file mode 100644 index 0000000..3100816 --- /dev/null +++ b/video/core/VideoDecoder.cpp @@ -0,0 +1,125 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoDecoder.h" +#include "Logger.h" +#include "Demuxer.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoDecodeHandler::VideoDecodeHandler(const std::string& name, CodecID codecID, VideoComponent::Ptr pPrevious) + : _name(name), _pPrevious(pPrevious) +{ +} + +VideoDecodeHandler::~VideoDecodeHandler() +{ +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoDecoder::VideoDecoder(const std::string& name): VideoComponent(name) +{ +} + +VideoDecoder::~VideoDecoder() +{ +} + +void VideoDecoder::start() +{ + _task.run( + [this](std::atomic& isCancelled) -> void + { + VideoDecodeHandler::Ptr pHandler; + while (!isCancelled) + { + try + { + Frame::Ptr pFrame = nullptr; + do + { + if (pHandler.isNull()) + { + pFrame = pHandler->read(); + if (!pFrame.isNull()) + break; + } + + Packet::Ptr pPacket = readPacket(1000); + if (serialCodeWatcher().isChanged(pPacket->serialCode(), true)) + { + auto pPrevious = previousComponents().first(); + auto pDemuxer = pPrevious.cast(); + if (pDemuxer) + { + auto pEncoder = pDemuxer->videoEncoder(); + pEncoder->config().copyTo(config()); + serialCodeManager().increment(); + + pHandler = VideoDecodeHandler::create(pEncoder->getCodecID(), pEncoder); + } + else + throw Poco::NotImplementedException( + Poco::format("Unsupported previous component: %s", pPrevious->name())); + } + + pHandler->write(pPacket); + pFrame = pHandler->read(); + } while (false); + + if (!pFrame) + continue; + + postToNextComponents(sharedFromThis(), pFrame, MediaType::VIDEO); + } + catch (Poco::Exception& e) + { + logger().error("%s: %s", name(), e.displayText()); + Poco::Thread::sleep(1000); + } + catch (std::exception& e) + { + logger().error("%s: %s", name(), std::string(e.what())); + Poco::Thread::sleep(1000); + } + } + }); +} + +void VideoDecoder::stop() +{ + _task.cancel(); +} + +Packet::Ptr VideoDecoder::readPacket(uint32_t timeout) +{ + if (packetQueues().empty()) + throw Poco::NotFoundException("Packet queues is empty."); + + return packetQueues().first()->dequeue(timeout); +} + +void VideoDecoder::onLink(Core::Component::Ptr pPrevious) +{ + packetQueues().add(pPrevious, name(), MediaType::VIDEO); +} + +void VideoDecoder::onUnlink(Core::Component::Ptr pPrevious) +{ + packetQueues().remove(pPrevious); +} +} +} diff --git a/video/core/VideoDecoder.h b/video/core/VideoDecoder.h new file mode 100644 index 0000000..9635c8e --- /dev/null +++ b/video/core/VideoDecoder.h @@ -0,0 +1,115 @@ +#ifndef ACE5DB6F_B3E6_42E2_8DAE_6D520F93DC50 +#define ACE5DB6F_B3E6_42E2_8DAE_6D520F93DC50 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoComponent.h" +#include "AudioDecoder.h" +#include "CodecID.h" +#include "util/FactoryManager.h" +#include "util/Task.h" +#include +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class VideoDecodeHandlerSelector +{ +public: + static CodecID select(CodecID codec, VideoComponent::Ptr) + { + return codec; + } +}; + +class VideoDecodeHandler: public ::Util::FactoryManager +{ +public: + using Ptr = Poco::SharedPtr; + +public: + VideoDecodeHandler(const std::string& name, CodecID codecID, VideoComponent::Ptr pPrevious); + virtual ~VideoDecodeHandler(); + +public: + const std::string& name() const; + VideoComponent::Ptr previous() const; + + virtual void write(Packet::Ptr pPacket) = 0; + virtual Frame::Ptr read() = 0; + +private: + const std::string _name; + VideoComponent::Ptr _pPrevious; +}; + +inline const std::string& VideoDecodeHandler::name() const +{ + return _name; +} + +inline VideoComponent::Ptr VideoDecodeHandler::previous() const +{ + return _pPrevious; +} + +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class VideoDecoderSelector +{ +public: + static int select(const std::string&) + { + return 0; + } +}; + +class VideoDecoder: public VideoComponent, + public ::Util::FactoryManager +{ +public: + using Ptr = Poco::AutoPtr; + +public: + VideoDecoder(const std::string& name); + virtual ~VideoDecoder(); + virtual void reset() {}; + +protected: + void start() override; + void stop() override; + + virtual Frame::Ptr decode() = 0; + Packet::Ptr readPacket(uint32_t timeout = 4000); + + void onLink(Core::Component::Ptr pPrevious) override; + void onUnlink(Core::Component::Ptr pPrevious) override; + + Util::Task& task(); + + PROPERTY_DECLARE(AssociatedAudio, AudioDecoder::Ptr, nullptr, public, public) + +private: + Util::Task _task; +}; + +inline Util::Task& VideoDecoder::task() +{ + return _task; +} +} +} +#endif diff --git a/video/core/VideoEncoder.cpp b/video/core/VideoEncoder.cpp new file mode 100644 index 0000000..b703c2e --- /dev/null +++ b/video/core/VideoEncoder.cpp @@ -0,0 +1,87 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoEncoder.h" +#include "Logger.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoEncoder::VideoEncoder(const std::string& name, CodecID codecID): VideoComponent(name) +{ + setCodecID(codecID); + + setCodecProfile(CodecProfile::H264_MAIN); + setBitrate(4000000); + setGOP(30); + setGOPMode(GOPMode::NORMAL_P); + setBitrateControl(BitrateControl::VBR); + setAdaptable(false); +} + +VideoEncoder::~VideoEncoder() +{ +} + +void VideoEncoder::start() +{ + _task.run( + [this](std::atomic& isCancelled) -> void + { + while (!isCancelled) + { + try + { + postToNextComponents(sharedFromThis(), encode(readFrame()), MediaType::VIDEO); + } + catch (Poco::Exception& e) + { + logger().error("%s: %s", name(), e.displayText()); + Poco::Thread::sleep(100); + } + catch (std::exception& e) + { + logger().error("%s: %s", name(), std::string(e.what())); + Poco::Thread::sleep(100); + } + } + }); +} + +void VideoEncoder::stop() +{ + _task.cancel(); +} + +Packet::Ptr VideoEncoder::encode(Frame::Ptr pFrame) +{ + return nullptr; +} + +Frame::Ptr VideoEncoder::readFrame(uint32_t timeout) +{ + if (previousComponents().size() == 0) + throw Poco::NotFoundException("No previous components"); + + return frameQueues().first()->dequeue(timeout); +} + +void VideoEncoder::onLink(Core::Component::Ptr pPrevious) +{ + frameQueues().add(pPrevious, name(), MediaType::VIDEO); +} + +void VideoEncoder::onUnlink(Core::Component::Ptr pPrevious) +{ + frameQueues().remove(pPrevious); +} +} +} diff --git a/video/core/VideoEncoder.h b/video/core/VideoEncoder.h new file mode 100644 index 0000000..4a74483 --- /dev/null +++ b/video/core/VideoEncoder.h @@ -0,0 +1,75 @@ +#ifndef ED3217CD_F637_44D5_A6D9_E7AE0B9C3A4D +#define ED3217CD_F637_44D5_A6D9_E7AE0B9C3A4D +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoComponent.h" +#include "CodecID.h" +#include "util/FactoryManager.h" +#include "util/Task.h" +#include +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class VideoEncoderSelector +{ +public: + static CodecID select(CodecID codecID) + { + return codecID; + } +}; + +class VideoEncoder: public VideoComponent, + public ::Util::FactoryManager +{ +public: + using Ptr = Poco::AutoPtr; + +public: + VideoEncoder(const std::string& name, CodecID codecID); + virtual ~VideoEncoder(); + +public: + CONFIGURABLE_DECLARE_PARAMETER(CodecID, CodecID); + CONFIGURABLE_DECLARE_PARAMETER(CodecProfile, CodecProfile); + CONFIGURABLE_DECLARE_PARAMETER(Bitrate, int64_t); + CONFIGURABLE_DECLARE_PARAMETER(GOP, int); + CONFIGURABLE_DECLARE_PARAMETER(GOPMode, GOPMode); + CONFIGURABLE_DECLARE_PARAMETER(BitrateControl, BitrateControl); + + CONFIGURABLE_DECLARE_PARAMETER(Adaptable, bool); + +protected: + void start() override; + void stop() override; + + virtual Packet::Ptr encode(Frame::Ptr pFrame); + Frame::Ptr readFrame(uint32_t timeout = 4000); + + void onLink(Core::Component::Ptr pPrevious) override; + void onUnlink(Core::Component::Ptr pPrevious) override; + + Util::Task& task(); + +private: + Util::Task _task; +}; + +inline Util::Task& VideoEncoder::task() +{ + return _task; +} +} +} +#endif diff --git a/video/core/VideoMixer.cpp b/video/core/VideoMixer.cpp new file mode 100644 index 0000000..c0aafa6 --- /dev/null +++ b/video/core/VideoMixer.cpp @@ -0,0 +1,99 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoMixer.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoMixer::Channel::Channel(const std::string& name, VideoMixer::Ptr pMixer, uint32_t id) + : VideoComponent(Poco::format(name + "/CH#%u", id)), _pMixer(pMixer), _id(id) +{ + Region region = { + 0.0, + 0.0f, + 1.0f, + 1.0f, + }; + setRegion(region); + setAttach(false); + setInput(nullptr); + setZOrder(0); +} + +VideoMixer::Channel::~Channel() +{ +} + +void VideoMixer::Channel::onConfigChanged(ParameterNames& names) +{ + if (names.exists(_Input)) + { + unlink(true, false); + if (getAttach()) + { + auto pInput = getInput(); + if (pInput) + { + getInput()->link(sharedFromThis()); + link(mixer()); + } + + return; + } + } + + if (names.exists(_Attach)) + { + auto pInput = getInput(); + if (pInput) + { + if (getAttach()) + { + getInput()->link(sharedFromThis()); + link(mixer()); + } + else + getInput()->unlink(sharedFromThis()); + } + } +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoMixer::VideoMixer(const std::string& name): VideoComponent(name) +{ +} + +VideoMixer::~VideoMixer() +{ +} + +VideoMixer::Channel::Ptr VideoMixer::getChannel(uint32_t id) +{ + if (_channels.find(id) == _channels.end()) + _channels[id] = createChannel(id); + + return _channels[id]; +} + +void VideoMixer::start() +{ +} + +void VideoMixer::stop() +{ +} +} +} diff --git a/video/core/VideoMixer.h b/video/core/VideoMixer.h new file mode 100644 index 0000000..369b21d --- /dev/null +++ b/video/core/VideoMixer.h @@ -0,0 +1,118 @@ +#ifndef D84CC9FA_711B_4439_A0F8_424C43E082CB +#define D84CC9FA_711B_4439_A0F8_424C43E082CB +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoComponent.h" +#include "util/FactoryManager.h" +#include + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class VideoMixerSelector +{ +public: + static int select(const std::string&) + { + return 0; + } +}; + +class VideoMixer: public VideoComponent, + public ::Util::FactoryManager +{ +public: + using Ptr = Poco::AutoPtr; + class Channel: public VideoComponent + { + public: + using Ptr = Poco::AutoPtr; + + struct Region + { + double x; + double y; + double width; + double height; + + bool operator==(const Region& other) const + { + return std::abs(x - other.x) < 1e-9 && std::abs(y - other.y) < 1e-9 && + std::abs(width - other.width) < 1e-9 && std::abs(height - other.height) < 1e-9; + } + + bool operator!=(const Region& other) const + { + return !(this->operator==(other)); + } + }; + + protected: + Channel(const std::string& name, VideoMixer::Ptr, uint32_t id); + + public: + ~Channel(); + + CONFIGURABLE_DECLARE_PARAMETER(Region, Region) + CONFIGURABLE_DECLARE_PARAMETER(ZOrder, int) + CONFIGURABLE_DECLARE_PARAMETER(Attach, bool) + CONFIGURABLE_DECLARE_PARAMETER(Input, VideoComponent::Ptr) + + uint32_t id() const; + + protected: + void onConfigChanged(ParameterNames& names) override; + VideoMixer::Ptr mixer(); + + private: + VideoMixer::Ptr _pMixer; + uint32_t _id; + + friend VideoMixer; + }; + +public: + VideoMixer(const std::string& name); + ~VideoMixer(); + + Channel::Ptr getChannel(uint32_t id); + +protected: + void start() override; + void stop() override; + + virtual Channel::Ptr createChannel(uint32_t id) = 0; + + template void foreachChannel(F&&); + +private: + std::unordered_map _channels; +}; + +inline uint32_t VideoMixer::Channel::id() const +{ + return _id; +} + +inline VideoMixer::Ptr VideoMixer::Channel::mixer() +{ + return _pMixer; +} + +template inline void VideoMixer::foreachChannel(F&& exec) +{ + for (auto m : _channels) + exec(m.second.cast()); +} +} +} +#endif diff --git a/video/core/VideoOutput.cpp b/video/core/VideoOutput.cpp new file mode 100644 index 0000000..d546c75 --- /dev/null +++ b/video/core/VideoOutput.cpp @@ -0,0 +1,25 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoOutput.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoOutput::VideoOutput(const std::string& name): VideoComponent(name) +{ +} + +VideoOutput::~VideoOutput() +{ +} +} +} diff --git a/video/core/VideoOutput.h b/video/core/VideoOutput.h new file mode 100644 index 0000000..c1d6ece --- /dev/null +++ b/video/core/VideoOutput.h @@ -0,0 +1,62 @@ +#ifndef BB15C48A_9D47_46F6_B428_E9355AD79570 +#define BB15C48A_9D47_46F6_B428_E9355AD79570 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoComponent.h" +#include "util/FactoryManager.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class VideoOutput: public VideoComponent, + public ::Util::FactoryManager, const std::string&> +{ +public: + using Ptr = Poco::AutoPtr; + + enum class ColorType + { + RGB, + YUV, + }; + + enum class Mode + { + HDMI, + DVI, + }; + + VideoOutput(const std::string& name); + ~VideoOutput(); + + CONFIGURABLE_DECLARE_PARAMETER(Timing, std::string) + + CONFIGURABLE_DECLARE_PARAMETER(Mode, Mode) + + CONFIGURABLE_DECLARE_PARAMETER(Luma, int) + + CONFIGURABLE_DECLARE_PARAMETER(Contrast, int) + + CONFIGURABLE_DECLARE_PARAMETER(Hue, int) + + CONFIGURABLE_DECLARE_PARAMETER(Saturation, int) + + CONFIGURABLE_DECLARE_PARAMETER(Mirror, int) + + CONFIGURABLE_DECLARE_PARAMETER(Rotation, int) + + CONFIGURABLE_DECLARE_PARAMETER(ColorType, ColorType) +}; +} +} +#endif diff --git a/video/core/VideoOverlay.cpp b/video/core/VideoOverlay.cpp new file mode 100644 index 0000000..3f4e61d --- /dev/null +++ b/video/core/VideoOverlay.cpp @@ -0,0 +1,36 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoOverlay.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoOverlay::VideoOverlay(VideoOverlayType type, const std::string& name, const Rect& region) + : _type(type), _name(name), _region(region) +{ + setZOrder(0); + setBlock(4); +} + +VideoOverlay::~VideoOverlay() +{ +} + +void VideoOverlay::attach(VideoComponent::Ptr target) +{ + if (target.isNull()) + throw Poco::BugcheckException(name() + ": Target is NULL"); + + _target = target; +} +} +} diff --git a/video/core/VideoOverlay.h b/video/core/VideoOverlay.h new file mode 100644 index 0000000..e3a6434 --- /dev/null +++ b/video/core/VideoOverlay.h @@ -0,0 +1,64 @@ +#ifndef CBC5C20D_24D0_4D1F_B00E_8299B36FD74E +#define CBC5C20D_24D0_4D1F_B00E_8299B36FD74E +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoComponent.h" +#include "GeometryTypes.h" +#include "Image.h" +#include "util/FactoryManager.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +enum class VideoOverlayType +{ + CANVAS, + MOSAIC, +}; + +class VideoOverlaySelector +{ +public: + static VideoOverlayType select(VideoOverlayType type, const std::string&, const Rect&) + { + return type; + } +}; + +class VideoOverlay: public ::Util::FactoryManager, + public ::Util::Configurable +{ +public: + using Ptr = Poco::SharedPtr; + +protected: + VideoOverlay(VideoOverlayType type, const std::string& name, const Rect& region); + +public: + virtual ~VideoOverlay(); + + virtual void render(Image::Ptr pImage) = 0; + virtual void scrollRender(Image::Ptr pImage, int hscroll) = 0; + virtual void attach(VideoComponent::Ptr target); + + CONFIGURABLE_DECLARE_PARAMETER(ZOrder, int) + CONFIGURABLE_DECLARE_PARAMETER(Block, int) + + READONLY_PROPERTY_DECLARE(type, VideoOverlayType) + READONLY_PROPERTY_DECLARE(name, std::string) + READONLY_PROPERTY_DECLARE(target, VideoComponent::Ptr) + READONLY_PROPERTY_DECLARE(region, Rect) +}; +} +} +#endif diff --git a/video/core/VideoProcessor.cpp b/video/core/VideoProcessor.cpp new file mode 100644 index 0000000..8a6db6a --- /dev/null +++ b/video/core/VideoProcessor.cpp @@ -0,0 +1,29 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoProcessor.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoProcessor::VideoProcessor(const std::string& name): Core::VideoComponent(name) +{ + setRotation(0); + setCrop(Rect{0, 0, 0, 0}); + setMirror(false); + setFlip(false); +} + +VideoProcessor::~VideoProcessor() +{ +} +} +} diff --git a/video/core/VideoProcessor.h b/video/core/VideoProcessor.h new file mode 100644 index 0000000..c8d5dfa --- /dev/null +++ b/video/core/VideoProcessor.h @@ -0,0 +1,52 @@ +#ifndef D5B26384_2A3D_4FBE_95FD_606CBAD0DA82 +#define D5B26384_2A3D_4FBE_95FD_606CBAD0DA82 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoComponent.h" +#include "GeometryTypes.h" +#include "util/FactoryManager.h" + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +class VideoProcessorSelector +{ +public: + static int select() + { + return 0; + } +}; + +class VideoProcessor: public VideoComponent, public ::Util::FactoryManager +{ +public: + using Ptr = Poco::AutoPtr; + +public: + VideoProcessor(const std::string& name); + virtual ~VideoProcessor(); + +public: + CONFIGURABLE_DECLARE_PARAMETER(Rotation, int); + + CONFIGURABLE_DECLARE_PARAMETER(Crop, Rect); + + CONFIGURABLE_DECLARE_PARAMETER(Mirror, bool); + + CONFIGURABLE_DECLARE_PARAMETER(Flip, bool); + + CONFIGURABLE_DECLARE_PARAMETER(VideoDirection, int); +}; +} +} +#endif diff --git a/video/core/VideoScanMode.h b/video/core/VideoScanMode.h new file mode 100644 index 0000000..db93710 --- /dev/null +++ b/video/core/VideoScanMode.h @@ -0,0 +1,22 @@ +#ifndef A4F9D6E2_E09A_42F1_AE90_74D89A97557F +#define A4F9D6E2_E09A_42F1_AE90_74D89A97557F + +namespace Video +{ +namespace Core +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +enum class VideoScanMode +{ + PROGRESSIVE, + INTERLACED, + + NB, +}; +} +} +#endif diff --git a/video/fdk/AudioEncoder.cpp b/video/fdk/AudioEncoder.cpp new file mode 100644 index 0000000..a48298f --- /dev/null +++ b/video/fdk/AudioEncoder.cpp @@ -0,0 +1,128 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioEncoder.h" +#include "Logger.h" +#include "../core/System.h" +#include "fdk-aac/FDK_audio.h" + +namespace Video +{ +namespace Fdk +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +AudioEncoder::AudioEncoder(Core::CodecID codecID) + : Core::AudioEncoder(Poco::format("Fdk::AudioEncoder(%s)", Core::codecIDToString(codecID)), codecID), + _pEncoder(nullptr) +{ +} + +AudioEncoder::~AudioEncoder() +{ +} + +void AudioEncoder::initialize() +{ + setSampleFormat(Core::SampleFormat::S16); + + int channels = Core::channelLayoutChannels(getChannelLayout()); + int samplerate = getSampleRate(); + + if (aacEncOpen(&_pEncoder, 0, channels) != AACENC_OK) + { + logger().error("Unable to open _pEncoder"); + return; + } + + if (aacEncoder_SetParam(_pEncoder, AACENC_CHANNELMODE, (channels == 1) ? MODE_1 : MODE_2) != AACENC_OK) + { + logger().error("Unable to set the channel mode"); + return; + } + + if (aacEncoder_SetParam(_pEncoder, AACENC_AOT, 2) != AACENC_OK) + { + logger().error("Unable to set the AOT"); + return; + } + + if (aacEncoder_SetParam(_pEncoder, AACENC_SAMPLERATE, samplerate) != AACENC_OK) + { + logger().error("Unable to set the SAMPLERATE"); + return; + } + + if (aacEncoder_SetParam(_pEncoder, AACENC_BITRATE, getBitrate()) != AACENC_OK) + { + logger().error("Unable to set the BITRATE"); + return; + } +} + +void AudioEncoder::deinitialize() +{ + if (_pEncoder) + { + aacEncClose(&_pEncoder); + _pEncoder = nullptr; + } +} + +Core::Packet::Ptr AudioEncoder::encode() +{ + if (!_pEncoder) + return nullptr; + + auto pIn = readFrame(); + + AACENC_BufDesc inBuf = {0}, outBuf = {0}; + AACENC_InArgs inArgs = {0}; + AACENC_OutArgs outArgs = {0}; + uint8_t* pBuffer = new uint8_t[2048]; + + int inIdentifier = IN_AUDIO_DATA; + int inSize = pIn->length; + int inElemSize = 2; + int outIdentifier = OUT_BITSTREAM_DATA; + int outEemSize = 1; + int outSize = 2048; + + inBuf.numBufs = 1; + uint8_t* data = pIn->data[0].get(); + inBuf.bufs = (void**)&data; + inBuf.bufferIdentifiers = &inIdentifier; + inBuf.bufSizes = &inSize; + inBuf.bufElSizes = &inElemSize; + + outBuf.numBufs = 1; + outBuf.bufs = (void**)&pBuffer; + outBuf.bufferIdentifiers = &outIdentifier; + outBuf.bufSizes = &outSize; + outBuf.bufElSizes = &outEemSize; + + inArgs.numInSamples = pIn->length / inElemSize; + + inArgs.numAncBytes = 0; + + if (AACENC_OK != aacEncEncode(_pEncoder, &inBuf, &outBuf, &inArgs, &outArgs)) + { + delete[] pBuffer; + return nullptr; + } + else + return new Core::Packet(serialCodeManager().code(), 0, pBuffer, outArgs.numOutBytes, pIn->timestampEncode, + pIn->timestampEncode); +} + +void AudioEncoder::onConfigChanged(ParameterNames& names) +{ + Core::AudioEncoder::onConfigChanged(names); +} +} +} diff --git a/video/ffmpeg/AudioDecodeHandler.cpp b/video/ffmpeg/AudioDecodeHandler.cpp new file mode 100644 index 0000000..82b5b05 --- /dev/null +++ b/video/ffmpeg/AudioDecodeHandler.cpp @@ -0,0 +1,119 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioDecodeHandler.h" +#include "Util.h" + +namespace Video +{ +namespace FFmpeg +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +AudioDecodeHandler::AudioDecodeHandler(Core::CodecID codecID, Core::AudioComponent::Ptr pPrevious) + : Core::AudioDecodeHandler(Poco::format("FFmpeg::AudioDecodeHandler(%s)", Core::codecIDToString(codecID)), codecID, + pPrevious) +{ + const AVCodec* pCodec = Util::getDecoder(codecID); + if (!pCodec) + throw Poco::NotImplementedException( + Poco::format("%s: Unsupported codec: %s", name(), Core::codecIDToString(codecID)), -EINVAL); + + _pCodecCtx = avcodec_alloc_context3(pCodec); + if (!_pCodecCtx) + throw Poco::InvalidAccessException(Poco::format("%s: Could not alloc an encoding context", name()), -EACCES); + + _pCodecCtx->ch_layout = Util::getChannelLayout(pPrevious->getChannelLayout()); + _pCodecCtx->sample_rate = pPrevious->getSampleRate(); + _pCodecCtx->sample_fmt = Util::getSampleFormat(pPrevious->getSampleFormat()); + + int rc = avcodec_open2(_pCodecCtx, pCodec, nullptr); + if (rc < 0) + throw Poco::InvalidAccessException( + Poco::format("%s: Open %s FAIL: %d(%s)", name(), std::string(pCodec->name), rc, Util::getErrorString(rc)), + rc); + + _pFrame = av_frame_alloc(); + if (Core::sampleFormatIsPlanar(pPrevious->getSampleFormat())) + { + const int c = Core::channelLayoutChannels(pPrevious->getChannelLayout()); + switch (c) + { + case 1: + _frameConvertor = [](AVFrame* from, Core::Frame::Ptr to) -> void + { memcpy(to->data[0], from->data[0], to->length); }; + break; + + case 2: + _frameConvertor = [](AVFrame* from, Core::Frame::Ptr to) -> void + { + memcpy(to->data[0], from->data[0], to->length); + memcpy(to->data[1], from->data[1], to->length); + }; + break; + + default: + throw Poco::BugcheckException(Poco::format("%s: Unsupported channle-count: %d", name(), c), -EINVAL); + } + } + else + { + _frameConvertor = [](AVFrame* from, Core::Frame::Ptr to) -> void + { memcpy(to->data[0], from->data[0], to->length); }; + } +} + +AudioDecodeHandler::~AudioDecodeHandler() +{ + av_frame_free(&_pFrame); + + avcodec_free_context(&_pCodecCtx); +} + +void AudioDecodeHandler::write(Core::Packet::Ptr pPacket) +{ + AVPacket* pAVPacket; + pPacket->moveTo(&pAVPacket); + + int rc = avcodec_send_packet(_pCodecCtx, pAVPacket); + av_packet_free(&pAVPacket); + if (rc < 0) + throw Poco::RuntimeException(Poco::format("%s: Error submitting the packet to the decoder: %d(%s)", name(), rc, + Util::getErrorString(rc)), + rc); +} + +Core::Frame::Ptr AudioDecodeHandler::read() +{ + int rc = avcodec_receive_frame(_pCodecCtx, _pFrame); + if (rc == AVERROR(EAGAIN) || rc == AVERROR_EOF) + return nullptr; + + if (rc < 0) + throw Poco::RuntimeException( + Poco::format("%s: Error during decoding: %d(%s)", name(), rc, Util::getErrorString(rc)), rc); + + return convert(_pFrame); +} + +Core::Frame::Ptr AudioDecodeHandler::convert(AVFrame* pAVFrame) +{ + int length = av_get_bytes_per_sample(_pCodecCtx->sample_fmt) * _pFrame->nb_samples; + + Core::Frame::Ptr pFrame = new Core::Frame(); + pFrame->length = length; + pFrame->timestamp = pAVFrame->pts; + + for (int i = 0; i < pAVFrame->ch_layout.nb_channels; i++) + pFrame->data[i] = new uint8_t[length]; + + _frameConvertor(pAVFrame, pFrame); + return pFrame; +} +} +} diff --git a/video/ffmpeg/AudioDecodeHandler.h b/video/ffmpeg/AudioDecodeHandler.h new file mode 100644 index 0000000..65a6648 --- /dev/null +++ b/video/ffmpeg/AudioDecodeHandler.h @@ -0,0 +1,44 @@ +#ifndef AD6CD053_01C0_4CD4_B6DE_4F9D3940E437 +#define AD6CD053_01C0_4CD4_B6DE_4F9D3940E437 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../core/AudioDecoder.h" +extern "C" +{ +#include +} +#include + +namespace Video +{ +namespace FFmpeg +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class AudioDecodeHandler: public Core::AudioDecodeHandler +{ +public: + AudioDecodeHandler(Core::CodecID codecID, Core::AudioComponent::Ptr pPrevious); + ~AudioDecodeHandler(); + +protected: + void write(Core::Packet::Ptr pPacket) override; + Core::Frame::Ptr read() override; + + Core::Frame::Ptr convert(AVFrame* pFrame); + +private: + AVCodecContext* _pCodecCtx; + AVFrame* _pFrame; + AVPacket* _pPacket; + std::function _frameConvertor; +}; +} +} +#endif diff --git a/video/ffmpeg/AudioEncoder.cpp b/video/ffmpeg/AudioEncoder.cpp new file mode 100644 index 0000000..376cca0 --- /dev/null +++ b/video/ffmpeg/AudioEncoder.cpp @@ -0,0 +1,179 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioEncoder.h" +#include "Util.h" +#include "Logger.h" +#include "../core/System.h" + +namespace Video +{ +namespace FFmpeg +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +AudioEncoder::AudioEncoder(Core::CodecID codecID) + : Core::AudioEncoder(Poco::format("FFmpeg::AudioEncoder(%s)", Core::codecIDToString(codecID)), codecID), + _pCodecCtx(nullptr), _pFrame(nullptr), _pPacket(nullptr) +{ +} + +AudioEncoder::~AudioEncoder() +{ +} + +void AudioEncoder::initialize() +{ + auto codecID = getCodecID(); + const AVCodec* pCodec = Util::getEncoder(codecID); + if (!pCodec) + throw Poco::NotImplementedException( + Poco::format("%s: Unsupported codec: %s", name(), Core::codecIDToString(codecID)), -EINVAL); + + _pCodecCtx = avcodec_alloc_context3(pCodec); + if (!_pCodecCtx) + throw Poco::InvalidAccessException(Poco::format("%s: Could not alloc an encoding context", name()), -EACCES); + + setChannelLayout(Core::ChannelLayout::STEREO); + switch (pCodec->id) + { + case AV_CODEC_ID_PCM_ALAW: + case AV_CODEC_ID_PCM_MULAW: + setSampleFormat(Core::SampleFormat::S16P); + setSampleRate(8000); + + if (Core::channelLayoutChannels(getChannelLayout()) != 1) + setChannelLayout(Core::ChannelLayout::MONO); + setFrameSize(320); + break; + + case AV_CODEC_ID_AAC: + setSampleFormat(Core::SampleFormat::FLTP); + setFrameSize(1024); + break; + + case AV_CODEC_ID_MP3: + setSampleFormat(Core::SampleFormat::FLTP); + setFrameSize(1152); + break; + + case AV_CODEC_ID_OPUS: + _pCodecCtx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; + setSampleFormat(Core::SampleFormat::S16); + setFrameSize(960); + break; + + default: + throw Poco::BugcheckException( + Poco::format("%s: Unspecified encoder(%s) parameters", name(), Core::codecIDToString(codecID)), -EINVAL); + } + + _pCodecCtx->ch_layout = Util::getChannelLayout(getChannelLayout()); + _pCodecCtx->sample_rate = getSampleRate(); + _pCodecCtx->bit_rate = getBitrate(); + _pCodecCtx->sample_fmt = Util::getSampleFormat(getSampleFormat()); + _pCodecCtx->time_base = (AVRational){1, getSampleRate()}; + int rc = avcodec_open2(_pCodecCtx, pCodec, nullptr); + if (rc < 0) + throw Poco::InvalidAccessException( + Poco::format("%s: Open %s FAIL: %d(%s)", name(), std::string(pCodec->name), rc, Util::getErrorString(rc)), + rc); + + if (_pCodecCtx->frame_size == 0) + _pCodecCtx->frame_size = getFrameSize(); + + _pFrame = av_frame_alloc(); + _pFrame->format = _pCodecCtx->sample_fmt; + _pFrame->sample_rate = _pCodecCtx->sample_rate; + _pFrame->nb_samples = _pCodecCtx->frame_size; + av_channel_layout_copy(&_pFrame->ch_layout, &_pCodecCtx->ch_layout); + av_frame_get_buffer(_pFrame, 0); + + if (Core::sampleFormatIsPlanar(getSampleFormat())) + { + const int c = Core::channelLayoutChannels(getChannelLayout()); + switch (c) + { + case 1: + _frameConvertor = [](Core::Frame::Ptr from, AVFrame* to) -> void + { memcpy(to->data[0], from->data[0], from->length); }; + break; + + case 2: + _frameConvertor = [](Core::Frame::Ptr from, AVFrame* to) -> void + { + memcpy(to->data[0], from->data[0], from->length); + memcpy(to->data[1], from->data[1], from->length); + }; + break; + + default: + throw Poco::BugcheckException(Poco::format("%s: Unsupported channle-count: %d", name(), c), -EINVAL); + } + } + else + { + _frameConvertor = [](Core::Frame::Ptr from, AVFrame* to) -> void + { memcpy(to->data[0], from->data[0], from->length); }; + } +} + +void AudioEncoder::deinitialize() +{ + av_frame_free(&_pFrame); + + avcodec_free_context(&_pCodecCtx); +} + +Core::Packet::Ptr AudioEncoder::encode() +{ + if (_pPacket == nullptr) + _pPacket = av_packet_alloc(); + + int rc = avcodec_receive_packet(_pCodecCtx, _pPacket); + if (rc > 0) + { + return new Core::Packet(serialCodeManager().code(), false, &_pPacket); + } + + while (true) + { + auto pIn = readFrame(); + ts = pIn->timestamp; + rc = av_frame_make_writable(_pFrame); + if (rc < 0) + throw Poco::InvalidAccessException( + Poco::format("%s: Make frame writable failed: %d(%s)", name(), rc, Util::getErrorString(rc)), rc); + + _frameConvertor(pIn, _pFrame); + _pFrame->pts = pIn->timestamp; + + int rc = avcodec_send_frame(_pCodecCtx, _pFrame); + if (rc < 0) + throw Poco::InvalidAccessException(Poco::format("%s: Sending the frame to the encoder failed: %d(%s)", + name(), rc, Util::getErrorString(rc)), + rc); + + rc = avcodec_receive_packet(_pCodecCtx, _pPacket); + if (rc == AVERROR(EAGAIN) || rc == AVERROR_EOF) + continue; + + if (rc < 0) + throw Poco::InvalidAccessException( + Poco::format("%s: Error encoding audio frame: %d(%s)", name(), rc, Util::getErrorString(rc)), rc); + + return new Core::Packet(serialCodeManager().code(), false, &_pPacket); + } +} + +void AudioEncoder::onConfigChanged(ParameterNames& names) +{ + Core::AudioEncoder::onConfigChanged(names); +} +} +} diff --git a/video/ffmpeg/AudioEncoder.h b/video/ffmpeg/AudioEncoder.h new file mode 100644 index 0000000..a82df64 --- /dev/null +++ b/video/ffmpeg/AudioEncoder.h @@ -0,0 +1,45 @@ +#ifndef D5F58729_9077_452E_ADC5_2D75E17601EA +#define D5F58729_9077_452E_ADC5_2D75E17601EA +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../core/AudioEncoder.h" +extern "C" +{ +#include +} +#include + +namespace Video +{ +namespace FFmpeg +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class AudioEncoder: public Core::AudioEncoder +{ +public: + AudioEncoder(Core::CodecID codecID); + ~AudioEncoder(); + +protected: + void initialize() override; + void deinitialize() override; + Core::Packet::Ptr encode() override; + void onConfigChanged(ParameterNames& names) override; + uint64_t ts = 0; + +private: + AVCodecContext* _pCodecCtx; + AVFrame* _pFrame; + AVPacket* _pPacket; + std::function _frameConvertor; +}; +} +} +#endif diff --git a/video/ffmpeg/AudioResample.cpp b/video/ffmpeg/AudioResample.cpp new file mode 100644 index 0000000..dc5da43 --- /dev/null +++ b/video/ffmpeg/AudioResample.cpp @@ -0,0 +1,181 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioResample.h" +#include "Util.h" +#include +#include + +namespace Video +{ +namespace FFmpeg +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +AudioResample::AudioResample(Core::ChannelLayout ilayout, Core::SampleFormat iformat, int irate, + Core::ChannelLayout olayout, Core::SampleFormat oformat, int orate) + : Core::AudioResample(Poco::format("FFmpeg::AudioResample(%s:%s:%d->%s:%s:%d)", + Core::channelLayoutToString(ilayout), Core::sampleFormatName(iformat), irate, + Core::channelLayoutToString(olayout), Core::sampleFormatName(oformat), orate), + ilayout, iformat, irate, olayout, oformat, orate), + _pSwrCtx(nullptr) +{ +} + +AudioResample::~AudioResample() +{ + if (_pSwrCtx) + { + swr_close(_pSwrCtx); + swr_free(&_pSwrCtx); + } +} + +Core::Frame::Ptr AudioResample::process(Core::Frame::Ptr in) +{ + if (!_pSwrCtx) + { + auto iAVLayout = Util::getChannelLayout(inLayout()); + auto oAVLayout = Util::getChannelLayout(outLayout()); + + auto iAVFormat = Util::getSampleFormat(inFormat()); + auto oAVFormat = Util::getSampleFormat(outFormat()); + + int rc = swr_alloc_set_opts2(&_pSwrCtx, &oAVLayout, oAVFormat, outRate(), &iAVLayout, iAVFormat, inRate(), 0, + nullptr); + if (rc < 0) + throw Poco::InvalidArgumentException( + Poco::format("%s: Could not allocate SwrContext: %d(%s)", name(), rc, Util::getErrorString(rc)), rc); + + if (outLayout() == Core::ChannelLayout::L || outLayout() == Core::ChannelLayout::R) + { + const int inCh = iAVLayout.nb_channels; + const int outCh = (int)oAVLayout.nb_channels; + const int srcCh = (outLayout() == Core::ChannelLayout::L) ? 0 : inCh - 1; + + std::vector matrix(inCh * outCh, 0.0); + matrix[srcCh] = 1.0; + + rc = swr_set_matrix(_pSwrCtx, matrix.data(), inCh); + if (rc < 0) + throw Poco::InvalidAccessException( + Poco::format("%s: Could not set SwrContext matrix: %d(%s)", name(), rc, Util::getErrorString(rc)), + rc); + } + + rc = swr_init(_pSwrCtx); + if (rc < 0) + throw Poco::InvalidAccessException( + Poco::format("%s: Could not init SwrContext: %d(%s)", name(), rc, Util::getErrorString(rc)), rc); + + setAssignPointersMethod(_assignInPointers, inFormat(), inLayout()); + setAssignPointersMethod(_assignOutPointers, outFormat(), outLayout()); + + if (av_sample_fmt_is_planar(iAVFormat)) + _iFrameSize = in->length / av_get_bytes_per_sample(iAVFormat); + else + _iFrameSize = in->length / (av_get_bytes_per_sample(iAVFormat) * iAVLayout.nb_channels); + + _oSampleSize = av_get_bytes_per_sample(oAVFormat); + if (!av_sample_fmt_is_planar(oAVFormat)) + _oSampleSize *= oAVLayout.nb_channels; + + _oFrameSize = av_rescale_rnd(swr_get_delay(_pSwrCtx, inRate()) + _iFrameSize, outRate(), inRate(), AV_ROUND_UP); + const int c = Core::channelLayoutChannels(outLayout()); + if (Core::sampleFormatIsPlanar(outFormat())) + { + _oBytesPerChannel = av_samples_get_buffer_size(NULL, 1, _oFrameSize, oAVFormat, 1); + switch (c) + { + case 1: + _frameCreator = [this]() -> Core::Frame::Ptr + { + Core::Frame::Ptr pFrame = new Core::Frame; + pFrame->length = _oBytesPerChannel; + pFrame->data[0] = new uint8_t[_oBytesPerChannel << 1]; + return pFrame; + }; + break; + + case 2: + _frameCreator = [this]() -> Core::Frame::Ptr + { + Core::Frame::Ptr pFrame = new Core::Frame; + pFrame->length = _oBytesPerChannel; + pFrame->data[0] = new uint8_t[_oBytesPerChannel << 1]; + pFrame->data[1] = new uint8_t[_oBytesPerChannel << 1]; + return pFrame; + }; + break; + + default: + throw Poco::BugcheckException(Poco::format("%s: Unsupported channle-count: %d", name(), c), -EINVAL); + } + } + else + { + _oBytesPerChannel = av_samples_get_buffer_size(NULL, c, _oFrameSize, oAVFormat, 1); + _frameCreator = [this]() -> Core::Frame::Ptr + { + Core::Frame::Ptr pFrame = new Core::Frame; + pFrame->length = _oBytesPerChannel; + pFrame->data[0] = new uint8_t[_oBytesPerChannel << 1]; + return pFrame; + }; + } + } + + Core::Frame::Ptr out = _frameCreator(); + out->timestamp = in->timestamp; + + uint8_t* iptrs[Core::Frame::DATA_LINES] = {nullptr}; + uint8_t* optrs[Core::Frame::DATA_LINES] = {nullptr}; + + _assignInPointers(iptrs, in); + _assignOutPointers(optrs, out); + + int rc = swr_convert(_pSwrCtx, optrs, _oFrameSize, (const uint8_t**)iptrs, _iFrameSize); + if (rc < 0) + throw Poco::InvalidAccessException( + Poco::format("%s: Convert FAIL: %d(%s)", name(), rc, Util::getErrorString(rc)), rc); + + out->length = rc * _oSampleSize; + return out; +} + +void AudioResample::setAssignPointersMethod(AssignPointersMethod& method, Video::Core::SampleFormat iformat, + Video::Core::ChannelLayout ilayout) +{ + if (Core::sampleFormatIsPlanar(iformat)) + { + const int c = Core::channelLayoutChannels(ilayout); + switch (c) + { + case 1: + method = [](uint8_t* ptrs[], Core::Frame::Ptr pFrame) -> void { ptrs[0] = pFrame->data[0].get(); }; + break; + + case 2: + method = [](uint8_t* ptrs[], Core::Frame::Ptr pFrame) -> void + { + ptrs[0] = pFrame->data[0].get(); + ptrs[1] = pFrame->data[1].get(); + }; + break; + + default: + throw Poco::BugcheckException(Poco::format("%s: Unsupported channle-count: %d", name(), c), -EINVAL); + } + } + else + { + method = [](uint8_t* ptrs[], Core::Frame::Ptr pFrame) -> void { ptrs[0] = pFrame->data[0].get(); }; + } +} +} +} diff --git a/video/ffmpeg/AudioResample.h b/video/ffmpeg/AudioResample.h new file mode 100644 index 0000000..08ca003 --- /dev/null +++ b/video/ffmpeg/AudioResample.h @@ -0,0 +1,52 @@ +#ifndef B4406D43_D00D_4DB9_91AB_1428F3FB104C +#define B4406D43_D00D_4DB9_91AB_1428F3FB104C +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../core/AudioResample.h" +extern "C" +{ +#include +} +#include + +namespace Video +{ +namespace FFmpeg +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class AudioResample: public Core::AudioResample +{ +protected: + using AssignPointersMethod = std::function; + using FrameCreator = std::function; + +public: + AudioResample(Core::ChannelLayout ilayout, Core::SampleFormat iformat, int irate, Core::ChannelLayout olayout, + Core::SampleFormat oformat, int orate); + ~AudioResample(); + +public: + Core::Frame::Ptr process(Core::Frame::Ptr in) override; + void setAssignPointersMethod(AssignPointersMethod& method, Video::Core::SampleFormat iformat, + Video::Core::ChannelLayout ilayout); + +private: + SwrContext* _pSwrCtx; + AssignPointersMethod _assignInPointers; + AssignPointersMethod _assignOutPointers; + FrameCreator _frameCreator; + int _oBytesPerChannel; + int _iFrameSize; + int _oFrameSize; + int _oSampleSize; +}; +} +} +#endif diff --git a/video/ffmpeg/DemuxHandler.cpp b/video/ffmpeg/DemuxHandler.cpp new file mode 100644 index 0000000..8ca00ef --- /dev/null +++ b/video/ffmpeg/DemuxHandler.cpp @@ -0,0 +1,311 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "DemuxHandler.h" +#include "Util.h" +#include "Logger.h" +#include "../core/System.h" +#include "util/Math.h" +#include + +namespace Video +{ +namespace FFmpeg +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +DemuxHandler::DemuxHandler(const std::string& uri) + : Core::DemuxHandler(Poco::format("FFmpeg::DemuxHandler(%s)", uri), uri) +{ +} + +DemuxHandler::~DemuxHandler() +{ +} + +void DemuxHandler::run(Core::VideoEncoder::Ptr pVideoEncoder, Core::AudioEncoder::Ptr pAudioEncoder, const Post& post, + std::atomic& isCancelled) +{ + _pFormatCtx = avformat_alloc_context(); + _pFormatCtx->interrupt_callback.callback = DemuxHandler::interruptCallback; + _pFormatCtx->interrupt_callback.opaque = this; + + std::string url = uri(); + bool isFileScheme = (scheme() == "file"); + if (isFileScheme) + url = uri().substr(std::string("file:").size()); + + AVDictionary* pOptions = nullptr; + if (scheme() == "rtsp" && getUsingTCPTransport()) + av_dict_set(&pOptions, "rtsp_transport", "tcp", 0); + + av_dict_set(&pOptions, "stimeout", "2000000", 0); + + setAnchorTime(Core::System::timestamp()); + int rc = avformat_open_input(&_pFormatCtx, url.c_str(), nullptr, &pOptions); + if (rc < 0) + throw Poco::InvalidAccessException( + Poco::format("%s: Open input FAIL: %d(%s)", name(), rc, Util::getErrorString(rc)), rc); + + if (!isFileScheme) + { + _pFormatCtx->probesize = 256 * 1024; + _pFormatCtx->max_analyze_duration = 300 * 1000; + } + logger().debug("avformat_find_stream_info enter"); + rc = avformat_find_stream_info(_pFormatCtx, nullptr); + logger().debug("avformat_find_stream_info exit"); + if (rc < 0) + { + avformat_close_input(&_pFormatCtx); + throw Poco::NotFoundException( + Poco::format("%s: Find stream FAIL: %d(%s)", name(), rc, Util::getErrorString(rc)), rc); + } + + av_dump_format(_pFormatCtx, 0, url.c_str(), 0); + + int vIndex = -1; + AVRational vTimebase; + AVCodecContext* pVidecCodecCtx = nullptr; + if (openCodec(AVMEDIA_TYPE_VIDEO, vIndex, &pVidecCodecCtx) >= 0) + { + auto pStream = _pFormatCtx->streams[vIndex]; + int fr = av_q2d(pStream->r_frame_rate); + + vTimebase = pStream->time_base; + pVideoEncoder->setCodecID(Util::getCodecID(pVidecCodecCtx->codec_id)); + pVideoEncoder->setWidth(pVidecCodecCtx->width); + pVideoEncoder->setHeight(pVidecCodecCtx->height); + pVideoEncoder->setFrameRate(fr); + pVideoEncoder->setGOP(pVidecCodecCtx->gop_size); + pVideoEncoder->setBitrate(pVidecCodecCtx->bit_rate); + } + pVideoEncoder->setSignalLost(vIndex < 0); + + int aIndex = -1; + AVRational aTimebase; + AVCodecContext* pAudioCodecCtx = nullptr; + if (openCodec(AVMEDIA_TYPE_AUDIO, aIndex, &pAudioCodecCtx) >= 0) + { + auto pStream = _pFormatCtx->streams[aIndex]; + logger().information("%s: Audio found: %s, %dHz, frame-size: %d, %dKbps", name(), + Util::getCodecIDString(pAudioCodecCtx->codec_id), pAudioCodecCtx->sample_rate, + pAudioCodecCtx->frame_size, (int)((float)pAudioCodecCtx->bit_rate / 1000)); + + aTimebase = pStream->time_base; + pAudioEncoder->setCodecID(Util::getCodecID(pAudioCodecCtx->codec_id)); + pAudioEncoder->setSampleFormat(Util::getSampleFormat(pAudioCodecCtx->sample_fmt)); + pAudioEncoder->setBitrate(pAudioCodecCtx->bit_rate); + pAudioEncoder->setFrameSize(pAudioCodecCtx->frame_size); + pAudioEncoder->setChannelLayout(Util::getChannelLayout(pAudioCodecCtx->ch_layout)); + pAudioEncoder->setSampleRate(pAudioCodecCtx->sample_rate); + } + pAudioEncoder->setSignalLost(aIndex < 0); + pAudioEncoder->commit(); + + if (vIndex < 0 && aIndex < 0) + { + avformat_close_input(&_pFormatCtx); + throw Poco::NotFoundException(Poco::format("%s: Could NOT find any stream", name()), -ENOSYS); + } + + int primaryStream = vIndex >= 0 ? vIndex : aIndex; + AVBSFContext* pAVBSFContext = nullptr; + AVBitStreamFilter* pAVBitStreamFilter = nullptr; + if (vIndex >= 0) + { + if (pVidecCodecCtx->codec_id == AV_CODEC_ID_H264) + pAVBitStreamFilter = (AVBitStreamFilter*)av_bsf_get_by_name("h264_mp4toannexb"); + else if (pVidecCodecCtx->codec_id == AV_CODEC_ID_H265) + pAVBitStreamFilter = (AVBitStreamFilter*)av_bsf_get_by_name("hevc_mp4toannexb"); + + if (pAVBitStreamFilter) + { + av_bsf_alloc(pAVBitStreamFilter, &pAVBSFContext); + avcodec_parameters_copy(pAVBSFContext->par_in, _pFormatCtx->streams[vIndex]->codecpar); + av_bsf_init(pAVBSFContext); + } + + if (!pAVBSFContext) + { + avcodec_free_context(&pVidecCodecCtx); + avcodec_free_context(&pAudioCodecCtx); + avformat_close_input(&_pFormatCtx); + throw Poco::NullValueException(Poco::format("%s: Init 'AVBSFContext' FAIL", name()), -ENOSYS); + } + } + + if (_seekReqeust) + { + if (isFileScheme && _seekTo < _pFormatCtx->duration) + { + rc = avformat_seek_file(_pFormatCtx, -1, INT64_MIN, _seekTo, INT64_MAX, AVSEEK_FLAG_FRAME); + if (rc < 0) + logger().error("%s: Seek file FAIL: %d(%s)", name(), rc, Util::getErrorString(rc)); + else + logger().debug("%s: Seek file SUCCESS", name()); + } + + _seekReqeust = false; + } + + AVPacket* pPacket = nullptr; + + pVideoEncoder->commit(); + logger().information("%s: Video found: %s, %dx%d@%d, gop: %d, %dKbps", name(), + Util::getCodecIDString(pVidecCodecCtx->codec_id), pVidecCodecCtx->width, + pVidecCodecCtx->height, pVideoEncoder->getFrameRate(), pVidecCodecCtx->gop_size, + (int)((float)pVidecCodecCtx->bit_rate / 1000)); + + auto postPacket = [&](AVPacket*& pAVPacket) -> void + { + if (pAVPacket->stream_index == primaryStream) + setPosition( + av_rescale_q(pAVPacket->pts, _pFormatCtx->streams[pAVPacket->stream_index]->time_base, {1, 1000000})); + + if (pAVPacket->stream_index == vIndex) + { + rc = av_bsf_send_packet(pAVBSFContext, pAVPacket); + av_packet_unref(pAVPacket); + if (rc < 0) + { + logger().error("%s: Send BSF packet FAIL: %d(%s)", name(), rc, Util::getErrorString(rc)); + return; + } + + while (av_bsf_receive_packet(pAVBSFContext, pAVPacket) >= 0) + { + adjustTimestamp(pAVPacket, vTimebase); + updateTimeline(pAVPacket->pts); + post(new Core::Packet(pVideoEncoder->code(), true, &pAVPacket), Core::MediaType::VIDEO); + } + } + else if (pAVPacket->stream_index == aIndex) + { + adjustTimestamp(pAVPacket, aTimebase); + updateTimeline(pAVPacket->pts); + post(new Core::Packet(pAudioEncoder->code(), false, &pAVPacket), Core::MediaType::AUDIO); + } + else + av_packet_unref(pAVPacket); + }; + + while (!isCancelled) + { + setAnchorTime(Core::System::timestamp()); + + if (!pPacket) + pPacket = av_packet_alloc(); + + if (_seekReqeust) + { + if (isFileScheme && _seekTo < _pFormatCtx->duration) + { + rc = avformat_seek_file(_pFormatCtx, -1, INT64_MIN, _seekTo, INT64_MAX, AVSEEK_FLAG_FRAME); + if (rc < 0) + logger().error("%s: Seek file FAIL: %d(%s)", name(), rc, Util::getErrorString(rc)); + else + { + logger().debug("%s: Seek file SUCCESS", name()); + setBaseline(getTimeline()); + resetTimeDrift(); + } + } + + _seekReqeust = false; + } + + rc = av_read_frame(_pFormatCtx, pPacket); + if (rc < 0) + { + logger().error("%s: Read FAIL: %d(%s)", name(), rc, Util::getErrorString(rc)); + break; + } + + postPacket(pPacket); + } + + av_bsf_free(&pAVBSFContext); + av_packet_free(&pPacket); + + avcodec_free_context(&pVidecCodecCtx); + avcodec_free_context(&pAudioCodecCtx); + avformat_close_input(&_pFormatCtx); +} + +int DemuxHandler::openCodec(AVMediaType mediaType, int& index, AVCodecContext** pCodecCtx) +{ + index = -1; + + int rc = av_find_best_stream(_pFormatCtx, mediaType, -1, -1, NULL, 0); + if (rc < 0) + { + logger().warning("%s: Could not find %s stream in input: %d(%s)", name(), Util::getMediaTypeString(mediaType), + rc, Util::getErrorString(rc)); + return rc; + } + + index = rc; + AVStream* pStream = _pFormatCtx->streams[index]; + const AVCodec* pCodec = avcodec_find_decoder(pStream->codecpar->codec_id); + if (!pCodec) + { + logger().error("%s: Failed to find %s codec", name(), Util::getMediaTypeString(mediaType)); + return AVERROR(EINVAL); + } + + *pCodecCtx = avcodec_alloc_context3(pCodec); + if (!*pCodecCtx) + { + logger().error("%s: Failed to allocate the %s codec context", name(), Util::getMediaTypeString(mediaType)); + return AVERROR(ENOMEM); + } + + if ((rc = avcodec_parameters_to_context(*pCodecCtx, pStream->codecpar)) < 0) + { + logger().error("%s: Failed to copy %s codec parameters to decoder context: %d(%s)", name(), + Util::getMediaTypeString(mediaType), rc, Util::getErrorString(rc)); + return rc; + } + + if ((rc = avcodec_open2(*pCodecCtx, pCodec, NULL)) < 0) + { + logger().error("%s: Failed to open %s codec: %d(%s)", name(), Util::getMediaTypeString(mediaType), rc, + Util::getErrorString(rc)); + return rc; + } + + return 0; +} + +void DemuxHandler::adjustTimestamp(AVPacket* pPacket, AVRational& timebase) +{ + av_packet_rescale_ts(pPacket, timebase, {1, 1000000}); + auto drift = getTimeDrift(pPacket->pts); + pPacket->pts += drift; + pPacket->dts += drift; +} + +void DemuxHandler::updateTimeline(uint64_t ts) +{ + if (getTimeline() < ts) + setTimeline(ts); +} + +void DemuxHandler::seek(uint64_t ts) +{ + _seekReqeust = true; + _seekTo = ts; +} + +int DemuxHandler::interruptCallback(void* pCtx) +{ + return 0; +} +} +} diff --git a/video/ffmpeg/DemuxHandler.h b/video/ffmpeg/DemuxHandler.h new file mode 100644 index 0000000..4cac37d --- /dev/null +++ b/video/ffmpeg/DemuxHandler.h @@ -0,0 +1,55 @@ +#ifndef B6199A70_B55C_41FF_ABDD_96D9A657AA29 +#define B6199A70_B55C_41FF_ABDD_96D9A657AA29 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../core/Demuxer.h" +extern "C" +{ +#include +#include +#include +#include +} + +namespace Video +{ +namespace FFmpeg +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class DemuxHandler: public Core::DemuxHandler +{ +public: + DemuxHandler(const std::string& url); + ~DemuxHandler(); + +public: + void run(Core::VideoEncoder::Ptr pVideoEncoder, Core::AudioEncoder::Ptr pAudioEncoder, const Post& post, + std::atomic& isCancelled) override; + +protected: + PROPERTY_DECLARE(AnchorTime, uint64_t, 0, protected, protected) + +protected: + int openCodec(AVMediaType type, int& index, AVCodecContext** pCodecCtx); + void adjustTimestamp(AVPacket* pPacket, AVRational& timebase); + void updateTimeline(uint64_t ts); + void seek(uint64_t) override; + +private: + AVFormatContext* _pFormatCtx; + std::atomic _seekReqeust; + int64_t _seekTo; + +protected: + static int interruptCallback(void* pCtx); +}; +} +} +#endif diff --git a/video/ffmpeg/Muxer.cpp b/video/ffmpeg/Muxer.cpp new file mode 100644 index 0000000..afe3774 --- /dev/null +++ b/video/ffmpeg/Muxer.cpp @@ -0,0 +1,295 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Muxer.h" +#include "Util.h" +#include "Logger.h" +#include + +namespace Video +{ +namespace FFmpeg +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Muxer::Stream::Stream(AVFormatContext& format, Core::VideoEncoder::Ptr pEncoder, AVRational tb, + Core::PacketQueue::Ptr pQueue) + : _pQueue(pQueue), _timebase(tb), _timeline(0), _pEncoder(nullptr) +{ + open(format, pEncoder->getCodecID(), + [&](AVCodecContext* pCodecCtx, AVStream* pStream) -> void + { + int fps = pEncoder->getFrameRate(); + pCodecCtx->width = pEncoder->getWidth(); + pCodecCtx->height = pEncoder->getHeight(); + pCodecCtx->time_base = {1, fps}; + pCodecCtx->framerate = {fps, 1}; + pCodecCtx->gop_size = pEncoder->getGOP(); + pCodecCtx->max_b_frames = 0; + pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P; + pCodecCtx->bit_rate = pEncoder->getBitrate(); + + pStream->time_base = pCodecCtx->time_base; + pStream->r_frame_rate = pCodecCtx->framerate; + }); +} + +Muxer::Stream::Stream(AVFormatContext& format, Core::AudioEncoder::Ptr pEncoder, AVRational tb, + Core::PacketQueue::Ptr pQueue) + : _pQueue(pQueue), _timebase(tb), _timeline(0), _pEncoder(nullptr) +{ + const Core::CodecID codecID = pEncoder->getCodecID(); + open(format, codecID, + [&](AVCodecContext* pCodecCtx, AVStream* pStream) -> void + { + switch (codecID) + { + case Core::CodecID::G711A: + case Core::CodecID::G711U: + pCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16; + pCodecCtx->ch_layout = AV_CHANNEL_LAYOUT_MONO; + break; + + case Core::CodecID::MP3: + case Core::CodecID::AAC: + pCodecCtx->sample_fmt = AV_SAMPLE_FMT_FLTP; + pCodecCtx->ch_layout = AV_CHANNEL_LAYOUT_STEREO; + break; + + case Core::CodecID::OPUS: + pCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16; + pCodecCtx->ch_layout = AV_CHANNEL_LAYOUT_STEREO; + break; + + default: + throw Poco::NotImplementedException( + Poco::format("Unsupported codec: %s", Core::codecIDToString(codecID)), -EINVAL); + } + + pCodecCtx->sample_rate = pEncoder->getSampleRate(); + pCodecCtx->bit_rate = pEncoder->getBitrate(); + pStream->time_base = (AVRational){1, pCodecCtx->sample_rate}; + }); +} + +Muxer::Stream::~Stream() +{ + close(); +} + +void Muxer::Stream::open(AVFormatContext& format, const Core::CodecID codec, + std::function init) +{ + _pEncoder = nullptr; + + const AVCodec* pCodec = Util::getEncoder(codec); + if (!pCodec) + throw Poco::NotImplementedException(Poco::format("Unsupported codec: %s", Core::codecIDToString(codec)), + -EINVAL); + + _pStream = avformat_new_stream(&format, pCodec); + if (!_pStream) + throw Poco::OutOfMemoryException("Alloc stream object FAIL", -ENOMEM); + + _pStream->id = format.nb_streams - 1; + _pEncoder = avcodec_alloc_context3(pCodec); + if (!_pEncoder) + throw Poco::NotImplementedException(Poco::format("Unsupported codec: %s", Core::codecIDToString(codec)), + -EINVAL); + + _pEncoder->codec_id = pCodec->id; + init(_pEncoder, _pStream); + + if (format.oformat->flags & AVFMT_GLOBALHEADER) + _pEncoder->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + + int rc = avcodec_open2(_pEncoder, pCodec, NULL); + if (rc < 0) + throw Poco::RuntimeException( + Poco::format("Open codec(%s) FAIL: %d(%s)", Core::codecIDToString(codec), rc, Util::getErrorString(rc)), + rc); + + avcodec_parameters_from_context(_pStream->codecpar, _pEncoder); +} + +void Muxer::Stream::close() +{ + avcodec_free_context(&_pEncoder); +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Muxer::Muxer(const std::string& url): Core::Muxer(Poco::format("FFmpeg::Muxer(%s)", url), url) +{ +} + +Muxer::~Muxer() +{ +} + +void Muxer::connect() +{ + const std::string format = getFormatName(); + int rc = + avformat_alloc_output_context2(&_pFormatCtx, nullptr, format.empty() ? nullptr : format.c_str(), url().c_str()); + if (rc < 0) + throw Poco::NotFoundException(Poco::format("%s: Create output format FAIL: %s: %s", name(), scheme(), format), + -EINVAL); + + AVRational vtb, atb; + getSuitableTimebase(vtb, atb); + + previousComponents().foreach ( + [&](Core::Component::Ptr pPrevious) -> void + { + auto pVideoEncoder = pPrevious.cast(); + if (!pVideoEncoder.isNull()) + { + _streams.push_back(new Stream(*_pFormatCtx, pVideoEncoder, vtb, packetQueues().find(pPrevious))); + return; + } + + auto pAudioEncoder = pPrevious.cast(); + if (!pAudioEncoder.isNull()) + { + _streams.push_back(new Stream(*_pFormatCtx, pAudioEncoder, atb, packetQueues().find(pPrevious))); + } + }); + + if (!(_pFormatCtx->oformat->flags & AVFMT_NOFILE)) + { + AVDictionary* pOptions = nullptr; + av_dict_set(&pOptions, "rw_timeout", "10000", 0); + if ("udp" == scheme()) + { + av_dict_set(&pOptions, "pkt_size", "1316", 0); + } + + rc = avio_open2(&_pFormatCtx->pb, url().c_str(), AVIO_FLAG_WRITE, nullptr, &pOptions); + if (rc < 0) + throw Poco::RuntimeException( + Poco::format("%s: Open: '%s' FAIL: %s", name(), url(), Util::getErrorString(rc)), rc); + } + + AVDictionary* pOptions = NULL; + if ("rtsp" == scheme()) + { + av_dict_set(&pOptions, "rtsp_transport", "tcp", 0); + av_dict_set(&pOptions, "buffer_size", "1024000", 0); + } + rc = avformat_write_header(_pFormatCtx, &pOptions); + if (rc < 0) + throw Poco::RuntimeException( + Poco::format("%s: Open output: '%s' FAIL: %s", name(), url(), Util::getErrorString(rc)), rc); +} + +void Muxer::mux() +{ + static const AVRational timebase = {1, 1000000}; + + std::sort(_streams.begin(), _streams.end(), [](auto a, auto b) { return *a < *b; }); + auto& pStream = *_streams.begin(); + + Core::Packet::Ptr pPacket = pStream->queue()->dequeue(4000); + if (pStream->serialCodeWatcher().isChanged(pPacket->serialCode())) + throw Poco::ApplicationException(Poco::format("%s: Stream changed, restart...", name())); + + AVPacket* pAVPacket; + pPacket->moveTo(&pAVPacket); + pAVPacket->stream_index = pStream->id(); + + pStream->setTimeline(pAVPacket->pts); + + av_packet_rescale_ts(pAVPacket, timebase, pStream->timebase()); + + int rc = av_interleaved_write_frame(_pFormatCtx, pAVPacket); + av_packet_free(&pAVPacket); + + if (rc < 0) + { + logger().warning("%s: mux FAIL: %d (%s)", name(), rc, Util::getErrorString(rc)); + + if (rc != -22) + throw Poco::RuntimeException(Poco::format("%s: mux FAIL: %d (%s)", name(), rc, Util::getErrorString(rc)), + rc); + } +} + +void Muxer::disconnect() +{ + if (_pFormatCtx && _pFormatCtx->pb) + { + av_write_trailer(_pFormatCtx); + if (!(_pFormatCtx->oformat->flags & AVFMT_NOFILE)) + avio_close(_pFormatCtx->pb); + } + + _streams.clear(); + avformat_free_context(_pFormatCtx); +} + +const std::string Muxer::getFormatName() +{ + const std::string s = scheme(); + if (s == "rtmp") + return "flv"; + if (s == "rtsp") + return "rtsp"; + if (s == "udp") + return "mpegts"; + return ""; +} + +void Muxer::getSuitableTimebase(AVRational& vtb, AVRational& atb) +{ + const std::string s = scheme(); + if ("rtmp" == s) + { + atb = {1, 1000}; + vtb = {1, 1000}; + } + else if ("rtsp" == s) + { + previousComponents().foreach ( + [&](Core::Component::Ptr pPrevious) -> void + { + auto pEncoder = pPrevious.cast(); + if (!pEncoder.isNull()) + { + switch (pEncoder->getCodecID()) + { + case Core::CodecID::AAC: + case Core::CodecID::OPUS: + atb = {1, pEncoder->getSampleRate()}; + break; + + case Core::CodecID::G711A: + case Core::CodecID::G711U: + atb = {1, 8000}; + break; + + default: + atb = {1, 90000}; + break; + } + } + }); + + vtb = {1, 90000}; + } + else if ("udp" == s) + { + atb = {1, 90000}; + vtb = {1, 90000}; + } +} +} +} diff --git a/video/ffmpeg/Muxer.h b/video/ffmpeg/Muxer.h new file mode 100644 index 0000000..c27d9c5 --- /dev/null +++ b/video/ffmpeg/Muxer.h @@ -0,0 +1,121 @@ +#ifndef DE90A7C0_21FB_4520_99C8_4A6D864A7F12 +#define DE90A7C0_21FB_4520_99C8_4A6D864A7F12 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../core/Muxer.h" +#include "../core/VideoEncoder.h" +#include "../core/AudioEncoder.h" +extern "C" +{ +#include +#include +} + +#include +#include + +namespace Video +{ +namespace FFmpeg +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Muxer: public Core::Muxer +{ +protected: + class Stream + { + public: + using Ptr = Poco::SharedPtr; + + public: + Stream(AVFormatContext& format, Core::VideoEncoder::Ptr pEncoder, AVRational tb, Core::PacketQueue::Ptr pQueue); + Stream(AVFormatContext& format, Core::AudioEncoder::Ptr pEncoder, AVRational tb, Core::PacketQueue::Ptr pQueue); + ~Stream(); + + public: + int id() const; + bool operator<(const Stream& other); + Core::PacketQueue::Ptr queue(); + + AVRational timebase(); + + uint64_t getTimeline(); + void setTimeline(uint64_t t); + + Core::SerialCodeWatcher& serialCodeWatcher(); + + protected: + void open(AVFormatContext& format, const Core::CodecID codec, + std::function init); + void close(); + + private: + AVStream* _pStream; + Core::PacketQueue::Ptr _pQueue; + AVRational _timebase; + uint64_t _timeline; + AVCodecContext* _pEncoder; + Core::SerialCodeWatcher _serialCodeWatcher; + }; + +public: + Muxer(const std::string& url); + ~Muxer(); + +protected: + void connect() override; + void mux() override; + void disconnect() override; + + const std::string getFormatName(); + void getSuitableTimebase(AVRational& vtb, AVRational& atb); + +private: + AVFormatContext* _pFormatCtx; + std::vector _streams; +}; + +inline bool Muxer::Stream::operator<(const Stream& other) +{ + return _timeline < other._timeline; +} + +inline int Muxer::Stream::id() const +{ + return _pStream->id; +} + +inline Core::PacketQueue::Ptr Muxer::Stream::queue() +{ + return _pQueue; +} + +inline AVRational Muxer::Stream::timebase() +{ + return _timebase; +} + +inline uint64_t Muxer::Stream::getTimeline() +{ + return _timeline; +} + +inline void Muxer::Stream::setTimeline(uint64_t t) +{ + _timeline = t; +} + +inline Core::SerialCodeWatcher& Muxer::Stream::serialCodeWatcher() +{ + return _serialCodeWatcher; +} +} +} +#endif diff --git a/video/ffmpeg/Util.cpp b/video/ffmpeg/Util.cpp new file mode 100644 index 0000000..7efcd21 --- /dev/null +++ b/video/ffmpeg/Util.cpp @@ -0,0 +1,423 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Util.h" +#include +#include +#include + +namespace Video +{ +namespace FFmpeg +{ +/* + ******************************************************************************* + * TYPEDEFS + ******************************************************************************* + */ +struct CodecIDItem +{ + Core::CodecID cid; + AVCodecID avid; +}; + +struct SampleFormatItem +{ + Core::SampleFormat cfmt; + AVSampleFormat avfmt; +}; + +/* + ******************************************************************************* + * LOCAL VARIABLES + ******************************************************************************* + */ +static const std::array codecMap = {{ + {Core::CodecID::H264, AV_CODEC_ID_H264}, + {Core::CodecID::H265, AV_CODEC_ID_H265}, + {Core::CodecID::MJPEG, AV_CODEC_ID_MJPEG}, + {Core::CodecID::MP3, AV_CODEC_ID_MP3}, + {Core::CodecID::AAC, AV_CODEC_ID_AAC}, + {Core::CodecID::OPUS, AV_CODEC_ID_OPUS}, + {Core::CodecID::G711A, AV_CODEC_ID_PCM_ALAW}, + {Core::CodecID::G711U, AV_CODEC_ID_PCM_MULAW}, +}}; + +static const std::array formatMap = {{ + { + Core::SampleFormat::U8, + AV_SAMPLE_FMT_U8, + }, + { + Core::SampleFormat::S16, + AV_SAMPLE_FMT_S16, + }, + { + Core::SampleFormat::S32, + AV_SAMPLE_FMT_S32, + }, + { + Core::SampleFormat::S64, + AV_SAMPLE_FMT_S64, + }, + { + Core::SampleFormat::FLT, + AV_SAMPLE_FMT_FLT, + }, + { + Core::SampleFormat::DBL, + AV_SAMPLE_FMT_DBL, + }, + { + Core::SampleFormat::U8P, + AV_SAMPLE_FMT_U8P, + }, + { + Core::SampleFormat::S16P, + AV_SAMPLE_FMT_S16P, + }, + { + Core::SampleFormat::S32P, + AV_SAMPLE_FMT_S32P, + }, + { + Core::SampleFormat::S64P, + AV_SAMPLE_FMT_S64P, + }, + { + Core::SampleFormat::FLTP, + AV_SAMPLE_FMT_FLTP, + }, + { + Core::SampleFormat::DBLP, + AV_SAMPLE_FMT_DBLP, + }, +}}; + +/* + ******************************************************************************* + * FUNCTIONS + ******************************************************************************* + */ +static Core::CodecID findCodecID(AVCodecID id) +{ + for (const auto& m : codecMap) + { + if (id == m.avid) + return m.cid; + } + + throw Poco::NotFoundException( + Poco::format("Video::FFmpeg: Can't find %s in codec-map", Util::getCodecIDString(id))); +} + +static AVCodecID findCodecID(Core::CodecID id) +{ + for (const auto& m : codecMap) + { + if (id == m.cid) + return m.avid; + } + + throw Poco::NotFoundException(Poco::format("Video::FFmpeg: Can't find %s in codec-map", Core::codecIDToString(id))); +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +std::string Util::getMediaTypeString(AVMediaType mediaType) +{ + return av_get_media_type_string(mediaType); +} + +std::string Util::getErrorString(int err) +{ + char buf[AV_ERROR_MAX_STRING_SIZE]; + return av_make_error_string(buf, AV_ERROR_MAX_STRING_SIZE, err); +} + +std::string Util::getCodecIDString(AVCodecID id) +{ + const char* name = avcodec_get_name(id); + if (name != nullptr) + return name; + + return Poco::format("UNKNOWN(%u)", (uint32_t)id); +} + +std::string Util::getSampleFormatString(AVSampleFormat fmt) +{ + const char* name = av_get_sample_fmt_name(fmt); + if (name != nullptr) + return name; + + return Poco::format("UNKNOWN(%u)", (uint32_t)fmt); +} + +const AVCodec* Util::getEncoder(Core::CodecID codec) +{ + return avcodec_find_encoder(findCodecID(codec)); +} + +const AVCodec* Util::getDecoder(Core::CodecID codec) +{ + return avcodec_find_decoder(findCodecID(codec)); +} + +const Core::CodecID Util::getCodecID(AVCodecID id) +{ + return findCodecID(id); +} + +AVChannelLayout Util::getChannelLayout(Core::ChannelLayout layout) +{ + switch (layout) + { + case Core::ChannelLayout::MONO: + case Core::ChannelLayout::L: + case Core::ChannelLayout::R: + return AV_CHANNEL_LAYOUT_MONO; + + default: + case Core::ChannelLayout::STEREO: + return AV_CHANNEL_LAYOUT_STEREO; + } +} + +Core::ChannelLayout Util::getChannelLayout(AVChannelLayout layout) +{ + AVChannelLayout other = AV_CHANNEL_LAYOUT_STEREO; + if (av_channel_layout_compare(&layout, &other) == 0) + return Core::ChannelLayout::STEREO; + + return Core::ChannelLayout::MONO; +} + +AVSampleFormat Util::getSampleFormat(Core::SampleFormat format) +{ + for (const auto& m : formatMap) + { + if (format == m.cfmt) + return m.avfmt; + } + + throw Poco::NotFoundException( + Poco::format("Video::FFmpeg: Can't find %s in format-map", Core::sampleFormatName(format))); +} + +Core::SampleFormat Util::getSampleFormat(AVSampleFormat format) +{ + for (const auto& m : formatMap) + { + if (format == m.avfmt) + return m.cfmt; + } + + throw Poco::NotFoundException( + Poco::format("Video::FFmpeg: Can't find %s in format-map", getSampleFormatString(format))); +} + +int Util::convertJPG2NV16(std::string srcPath, std::string dstPath) +{ +#define TARGET_WIDTH 1920 +#define TARGET_HEIGHT 1080 + const char* input_filename = srcPath.c_str(); + const char* output_filename = dstPath.c_str(); + + AVFormatContext* fmt_ctx = NULL; + AVCodecContext* codec_ctx = NULL; + const AVCodec* codec = NULL; + AVFrame * frame = NULL, *scaled_frame = NULL; + AVPacket* pkt = NULL; + SwsContext* sws_ctx = NULL; + FILE* outfile = NULL; + int ret = 0; + int video_stream_idx = -1; + + if ((ret = avformat_open_input(&fmt_ctx, input_filename, NULL, NULL)) < 0) + { + fprintf(stderr, "Cannot open input file\n"); + return -1; + } + + if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) + { + fprintf(stderr, "Cannot find stream information\n"); + return -1; + } + + for (int i = 0; i < fmt_ctx->nb_streams; i++) + { + if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) + { + video_stream_idx = i; + break; + } + } + + if (video_stream_idx == -1) + { + fprintf(stderr, "No video stream found\n"); + ret = -1; + return -1; + } + + AVCodecParameters* codecpar = fmt_ctx->streams[video_stream_idx]->codecpar; + codec = avcodec_find_decoder(codecpar->codec_id); + if (!codec) + { + fprintf(stderr, "Unsupported codec\n"); + ret = -1; + return -1; + } + + codec_ctx = avcodec_alloc_context3(codec); + if (!codec_ctx) + { + fprintf(stderr, "Cannot allocate codec context\n"); + ret = -1; + return -1; + } + + if ((ret = avcodec_parameters_to_context(codec_ctx, codecpar)) < 0) + { + fprintf(stderr, "Cannot copy codec parameters\n"); + return -1; + } + + if ((ret = avcodec_open2(codec_ctx, codec, NULL)) < 0) + { + fprintf(stderr, "Cannot open codec\n"); + return -1; + } + + frame = av_frame_alloc(); + if (!frame) + { + fprintf(stderr, "Cannot allocate frame\n"); + ret = -1; + return -1; + } + + scaled_frame = av_frame_alloc(); + if (!scaled_frame) + { + fprintf(stderr, "Cannot allocate scaled frame\n"); + ret = -1; + return -1; + } + + scaled_frame->width = TARGET_WIDTH; + scaled_frame->height = TARGET_HEIGHT; + scaled_frame->format = AV_PIX_FMT_YUV422P; + + if ((ret = av_frame_get_buffer(scaled_frame, 32)) < 0) + { + fprintf(stderr, "Cannot allocate scaled frame buffer\n"); + return -1; + } + + sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt, TARGET_WIDTH, TARGET_HEIGHT, + AV_PIX_FMT_YUV422P, SWS_BILINEAR, NULL, NULL, NULL); + if (!sws_ctx) + { + fprintf(stderr, "Cannot create sws context\n"); + ret = -1; + return -1; + } + + outfile = fopen(output_filename, "wb"); + if (!outfile) + { + fprintf(stderr, "Cannot open output file\n"); + ret = -1; + return -1; + } + + pkt = av_packet_alloc(); + if (!pkt) + { + fprintf(stderr, "Cannot allocate packet\n"); + ret = -1; + return -1; + } + + while (av_read_frame(fmt_ctx, pkt) >= 0) + { + if (pkt->stream_index == video_stream_idx) + { + ret = avcodec_send_packet(codec_ctx, pkt); + if (ret < 0) + { + fprintf(stderr, "Error sending packet\n"); + break; + } + + while (ret >= 0) + { + ret = avcodec_receive_frame(codec_ctx, frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + break; + else if (ret < 0) + { + fprintf(stderr, "Error receiving frame\n"); + break; + } + + sws_scale(sws_ctx, (const uint8_t* const*)frame->data, frame->linesize, 0, frame->height, + scaled_frame->data, scaled_frame->linesize); + + int width = scaled_frame->width; + int height = scaled_frame->height; + + for (int i = 0; i < height; i++) + { + fwrite(scaled_frame->data[0] + i * scaled_frame->linesize[0], 1, width, outfile); + } + + for (int i = 0; i < height; i++) + { + uint8_t* u_line = scaled_frame->data[1] + i * scaled_frame->linesize[1]; + uint8_t* v_line = scaled_frame->data[2] + i * scaled_frame->linesize[2]; + + for (int j = 0; j < width / 2; j++) + { + fwrite(&v_line[j], 1, 1, outfile); + fwrite(&u_line[j], 1, 1, outfile); + } + } + + printf("Image decoded and converted: %dx%d -> %dx%d\n", frame->width, frame->height, TARGET_WIDTH, + TARGET_HEIGHT); + } + } + av_packet_unref(pkt); + + break; + } + + printf("Conversion completed. Output saved to %s\n", output_filename); + + if (outfile) + fclose(outfile); + if (sws_ctx) + sws_freeContext(sws_ctx); + if (pkt) + av_packet_free(&pkt); + if (frame) + av_frame_free(&frame); + if (scaled_frame) + av_frame_free(&scaled_frame); + if (codec_ctx) + avcodec_free_context(&codec_ctx); + if (fmt_ctx) + avformat_close_input(&fmt_ctx); + + return ret < 0 ? -1 : 0; +} +} +} diff --git a/video/ffmpeg/Util.h b/video/ffmpeg/Util.h new file mode 100644 index 0000000..5e76e97 --- /dev/null +++ b/video/ffmpeg/Util.h @@ -0,0 +1,52 @@ +#ifndef CAEDF84E_DEDF_40BF_A293_86ED2D0A044A +#define CAEDF84E_DEDF_40BF_A293_86ED2D0A044A +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../core/CodecID.h" +#include "../core/ChannelLayout.h" +#include "../core/SampleFormat.h" +extern "C" +{ +#include +#include +#include +#include +#include +} +#include + +namespace Video +{ +namespace FFmpeg +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Util +{ +public: + static std::string getMediaTypeString(enum AVMediaType mediaType); + static std::string getErrorString(int err); + static std::string getCodecIDString(AVCodecID id); + static std::string getSampleFormatString(AVSampleFormat fmt); + + static const AVCodec* getEncoder(Core::CodecID codec); + static const AVCodec* getDecoder(Core::CodecID codec); + + static const Core::CodecID getCodecID(AVCodecID id); + + static AVChannelLayout getChannelLayout(Core::ChannelLayout layout); + static Core::ChannelLayout getChannelLayout(AVChannelLayout layout); + + static AVSampleFormat getSampleFormat(Core::SampleFormat format); + static Core::SampleFormat getSampleFormat(AVSampleFormat format); + static int convertJPG2NV16(std::string srcPath, std::string dstPath); +}; +} +} +#endif diff --git a/video/hisi/mpp5/AudioEncoder.cpp b/video/hisi/mpp5/AudioEncoder.cpp new file mode 100644 index 0000000..85d4558 --- /dev/null +++ b/video/hisi/mpp5/AudioEncoder.cpp @@ -0,0 +1,136 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioEncoder.h" +#include "ResourceManager.h" +#include "Util.h" +#include "util/Math.h" +#include +extern "C" +{ +#include "audio-adp/audio_aac_adp.h" +} + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +AudioEncoder::AudioEncoder(Core::CodecID codecID) + : MPPChannel((uint32_t)HI_ID_AENC, 0, ResourceManager::allocAENCChannelID()), + Core::AudioEncoder(Poco::format("MPP5::AENC#%u(%s)", channelID(), Core::codecIDToString(codecID)), codecID) +{ +} + +AudioEncoder::~AudioEncoder() +{ + ResourceManager::freeAENCChannelID(channelID()); +} + +void AudioEncoder::initialize() +{ + hi_aenc_chn_attr attr; + hi_aenc_attr_aac aac; + switch (getCodecID()) + { + case Core::CodecID::AAC: + setFrameSize(1024); + setSampleFormat(Core::SampleFormat::S16P); + + attr.value = &aac; + attr.type = HI_PT_AAC; + + aac.aac_type = HI_AAC_TYPE_AACLC; + aac.band_width = 0; + aac.transport_type = HI_AAC_TRANSPORT_TYPE_ADTS; + aac.sample_rate = (hi_audio_sample_rate)getSampleRate(); + aac.bit_rate = (hi_aac_bps)getBitrate(); + aac.bit_width = Util::getBitWidth(getSampleFormat()); + aac.snd_mode = Util::getSndMode(getChannelLayout()); + break; + + default: + throw Poco::BugcheckException( + Poco::format("%s: Not implemented codec-id: %s", name(), Core::codecIDToString(getCodecID())), -EINVAL); + } + + attr.buf_size = 30; + attr.point_num_per_frame = getFrameSize(); + auto rc = hi_mpi_aenc_create_chn(channelID(), &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Create channel FAIL: %#x", name(), (uint32_t)rc), rc); + + _fd = hi_mpi_aenc_get_fd(channelID()); + + _frame.bit_width = Util::getBitWidth(getSampleFormat()); + _frame.snd_mode = Util::getSndMode(getChannelLayout()); +} + +void AudioEncoder::deinitialize() +{ + hi_mpi_aenc_destroy_chn(channelID()); +} + +Core::Packet::Ptr AudioEncoder::encode() +{ + auto pIn = readFrame(); + _frame.virt_addr[0] = pIn->data[0].get(); + _frame.virt_addr[1] = pIn->data[1].get(); + _frame.time_stamp = pIn->timestamp; + _frame.len = pIn->length; + auto rc = hi_mpi_aenc_send_frame(channelID(), &_frame, nullptr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Write frame FAIL: %#x!", name(), (uint32_t)rc), rc); + + struct timeval tm = { + .tv_sec = 1, + .tv_usec = 0, + }; + + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(_fd, &read_fds); + + rc = select(_fd + 1, &read_fds, NULL, NULL, &tm); + if (rc < 0) + throw Poco::InvalidAccessException(Poco::format("%s: Read stream FAIL: %s", name(), std::string(strerror(rc))), + rc); + else if (0 == rc) + throw Poco::TimeoutException(Poco::format("%s: Read encoder stream timeout!", name()), rc); + + hi_audio_stream stream; + rc = hi_mpi_aenc_get_stream(channelID(), &stream, 1000); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Get encoder stream FAIL: %#x", name(), (uint32_t)rc), rc); + + uint8_t* pData = new uint8_t[stream.len]; + memcpy(pData, stream.stream, stream.len); + Core::Packet::Ptr pPacket = + new Core::Packet(serialCodeManager().code(), 0, pData, stream.len, stream.time_stamp, stream.time_stamp); + hi_mpi_aenc_release_stream(channelID(), &stream); + + return pPacket; +} + +void AudioEncoder::onConfigChanged(ParameterNames& names) +{ + Core::AudioEncoder::onConfigChanged(names); +} + +void AudioEncoder::initModule() +{ + hi_mpi_aenc_aac_init(); +} + +void AudioEncoder::deinitModule() +{ + hi_mpi_aenc_aac_deinit(); +} +} +} diff --git a/video/hisi/mpp5/AudioEncoder.h b/video/hisi/mpp5/AudioEncoder.h new file mode 100644 index 0000000..4781345 --- /dev/null +++ b/video/hisi/mpp5/AudioEncoder.h @@ -0,0 +1,43 @@ +#ifndef D8737294_8E5E_4514_9AD1_CFD956F0E262 +#define D8737294_8E5E_4514_9AD1_CFD956F0E262 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "MPPChannel.h" +#include "../core/AudioEncoder.h" +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class AudioEncoder: public MPPChannel, public Core::AudioEncoder +{ +public: + AudioEncoder(Core::CodecID codecID); + ~AudioEncoder(); + +protected: + void initialize() override; + void deinitialize() override; + Core::Packet::Ptr encode() override; + void onConfigChanged(ParameterNames& names) override; + +private: + int _fd; + hi_audio_frame _frame; + +public: + static void initModule(); + static void deinitModule(); +}; +} +} +#endif diff --git a/video/hisi/mpp5/AudioInput.cpp b/video/hisi/mpp5/AudioInput.cpp new file mode 100644 index 0000000..655095a --- /dev/null +++ b/video/hisi/mpp5/AudioInput.cpp @@ -0,0 +1,125 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioInput.h" +#include "Util.h" + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * LOCAL FUNCTIONS + ******************************************************************************* + */ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +AudioInput::AudioInput(uint32_t deviceID) + : Core::AbstractAudioInput(Poco::format("MPP5::AI#%u", deviceID)), MPPChannel((uint32_t)HI_ID_AI, deviceID, 0) +{ + setSampleRate(48000); + setFrameSize(1024); + setSampleFormat(Core::SampleFormat::S16P); +} + +AudioInput::~AudioInput() +{ +} + +void AudioInput::start() +{ + hi_aio_attr attr; + attr.sample_rate = (hi_audio_sample_rate)getSampleRate(); + attr.bit_width = Util::getBitWidth(getSampleFormat()); + attr.work_mode = HI_AIO_MODE_I2S_SLAVE; + attr.i2s_type = HI_AIO_I2STYPE_EXTERN; + attr.snd_mode = HI_AUDIO_SOUND_MODE_STEREO; + attr.expand_flag = 0; + attr.frame_num = 8; + attr.point_num_per_frame = getFrameSize(); + attr.chn_cnt = 2; + + attr.clk_share = 0; + int32_t rc = hi_mpi_ai_set_pub_attr(deviceID(), &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: set PUB-ATTR FAIL: %d(%#x)", name(), rc, (uint32_t)rc), + rc); + + rc = hi_mpi_ai_enable(deviceID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: enable FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + rc = hi_mpi_ai_enable_chn(deviceID(), channelID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: enable channel FAIL: %d(%#x)", name(), rc, (uint32_t)rc), + rc); + + hi_ai_chn_param param; + param.usr_frame_depth = 8; + hi_mpi_ai_set_chn_param(deviceID(), channelID(), ¶m); + + run(); +} + +void AudioInput::stop() +{ + cancel(); + + hi_mpi_ai_disable_chn(deviceID(), channelID()); + hi_mpi_ai_disable(deviceID()); +} + +void AudioInput::onConfigChanged(ParameterNames& names) +{ + if (!started()) + return; + + auto code = serialCodeManager().code(); + if (names.exists(_SampleRate)) + serialCodeManager().put(_SampleRate, getSampleRate()); + + if (names.exists(_FrameSize)) + serialCodeManager().put(_FrameSize, getFrameSize()); + + if (code != serialCodeManager().code()) + { + stop(); + start(); + } + + AbstractAudioInput::onConfigChanged(names); +} + +Core::Frame::Ptr AudioInput::readFrame() +{ + hi_audio_frame frame; + hi_aec_frame aecFrame; + + int rc = hi_mpi_ai_get_frame(deviceID(), 0, &frame, &aecFrame, 1000); + if (rc != HI_SUCCESS) + throw Poco::TimeoutException(Poco::format("%s: Get frame error: %u(%#x)!", name(), (uint32_t)rc, (uint32_t)rc)); + + Core::Frame::Ptr pFrame = nullptr; + if (frame.len > 0) + { + pFrame = new Core::Frame; + pFrame->data[0] = new uint8_t[frame.len]; + memcpy(pFrame->data[0], frame.virt_addr[0], frame.len); + pFrame->data[1] = new uint8_t[frame.len]; + memcpy(pFrame->data[1], frame.virt_addr[1], frame.len); + pFrame->length = frame.len; + pFrame->timestamp = frame.time_stamp; + } + + hi_mpi_ai_release_frame(deviceID(), 0, &frame, &aecFrame); + _traffic++; + return pFrame; +} +} +} diff --git a/video/hisi/mpp5/AudioInput.h b/video/hisi/mpp5/AudioInput.h new file mode 100644 index 0000000..1852c40 --- /dev/null +++ b/video/hisi/mpp5/AudioInput.h @@ -0,0 +1,40 @@ +#ifndef C7DEE8B3_9816_4937_B2A6_0591DEF0ED45 +#define C7DEE8B3_9816_4937_B2A6_0591DEF0ED45 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "MPPChannel.h" +#include "../../core/AbstractAudioInput.h" +#include "util/Task.h" +#include +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class AudioInput: public Core::AbstractAudioInput, public MPPChannel +{ +public: + using Ptr = Poco::AutoPtr; + +public: + AudioInput(uint32_t deviceID); + ~AudioInput(); + +protected: + void start() override; + void stop() override; + void onConfigChanged(ParameterNames& names) override; + Core::Frame::Ptr readFrame() override; +}; +} +} +#endif diff --git a/video/hisi/mpp5/HDMIAudioOutput.cpp b/video/hisi/mpp5/HDMIAudioOutput.cpp new file mode 100644 index 0000000..91c5cb5 --- /dev/null +++ b/video/hisi/mpp5/HDMIAudioOutput.cpp @@ -0,0 +1,122 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "HDMIAudioOutput.h" +#include "Util.h" +#include "util/Math.h" + +/* + ******************************************************************************* + * CONSTANTS + ******************************************************************************* + */ + +#define AO_DEV_ID 1 + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +HDMIAudioOutput::HDMIAudioOutput(const std::string& arg) + : Core::AudioOutput("MPP5::AudioOutput(" + arg + ")"), MPPChannel((uint32_t)HI_ID_AO, AO_DEV_ID, 0) +{ + setSampleRate(48000); + setSampleFormat(Core::SampleFormat::S16P); + setChannelLayout(Core::ChannelLayout::STEREO); + setFrameSize(1024); +} + +HDMIAudioOutput::~HDMIAudioOutput() +{ +} + +void HDMIAudioOutput::initialize() +{ + _bitWidth = Util::getBitWidth(getSampleFormat()); + _sndMode = Util::getSndMode(getChannelLayout()); + + hi_aio_attr attr; + attr.sample_rate = (hi_audio_sample_rate)getSampleRate(); + attr.bit_width = _bitWidth; + attr.work_mode = HI_AIO_MODE_I2S_MASTER; + attr.i2s_type = HI_AIO_I2STYPE_INNERHDMI; + attr.snd_mode = _sndMode; + attr.expand_flag = 0; + attr.frame_num = 8; + attr.point_num_per_frame = getFrameSize(); + attr.chn_cnt = Core::channelLayoutChannels(getChannelLayout()); + + attr.clk_share = 0; + + int rc = hi_mpi_ao_set_pub_attr(deviceID(), &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: Configure pub-attr FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + rc = hi_mpi_ao_enable(deviceID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Enable FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + rc = hi_mpi_ao_set_volume(deviceID(), getVolumeValue()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Configure volume FAIL: %d(%#x)", name(), rc, (uint32_t)rc), + rc); + + rc = hi_mpi_ao_enable_chn(deviceID(), channelID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: Enable CHM#%u FAIL: %d(%#x)", name(), channelID(), rc, (uint32_t)rc), rc); +} + +void HDMIAudioOutput::deinitialize() +{ + hi_mpi_ao_disable_chn(deviceID(), channelID()); + hi_mpi_ao_disable(deviceID()); +} + +void HDMIAudioOutput::onConfigChanged(ParameterNames& names) +{ + if (!started()) + return; + + int rc = hi_mpi_ao_set_volume(deviceID(), getVolumeValue()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Configure volume FAIL: %d(%#x)", name(), rc, (uint32_t)rc), + rc); +} + +void HDMIAudioOutput::writeFrame(Core::Frame::Ptr pFrame) +{ + hi_audio_frame frame = {}; + frame.bit_width = HI_AUDIO_BIT_WIDTH_16; + frame.len = pFrame->length; + frame.time_stamp = pFrame->timestamp; + frame.snd_mode = HI_AUDIO_SOUND_MODE_MONO; + frame.virt_addr[0] = pFrame->data[0].get(); + if (HI_AUDIO_SOUND_MODE_STEREO == _sndMode) + frame.virt_addr[1] = pFrame->data[1].get(); + int rc = hi_mpi_ao_send_frame(deviceID(), channelID(), &frame, 100); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Send frame FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); +} + +int HDMIAudioOutput::getVolumeValue() +{ + auto lineMapping = [](int inputMin, int inputMax, int outMin, int outMax, int input) -> int + { return (input - inputMin) * (outMax - outMin) / (inputMax - inputMin) + outMin; }; + + int gain = ::Util::Math::clamp(getGain(), -100, 100); + if (gain < 0) + return lineMapping(-100, -1, -121, -1, gain); + + return lineMapping(0, 100, 0, 6, gain); +} +} +} diff --git a/video/hisi/mpp5/HDMIAudioOutput.h b/video/hisi/mpp5/HDMIAudioOutput.h new file mode 100644 index 0000000..e94e69d --- /dev/null +++ b/video/hisi/mpp5/HDMIAudioOutput.h @@ -0,0 +1,41 @@ +#ifndef CCC7E17D_FF7C_44B5_8DCC_BFCE92AAFC87 +#define CCC7E17D_FF7C_44B5_8DCC_BFCE92AAFC87 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../core/AudioOutput.h" +#include "MPPChannel.h" +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class HDMIAudioOutput: public Core::AudioOutput, public MPPChannel +{ +public: + HDMIAudioOutput(const std::string& arg); + ~HDMIAudioOutput(); + +protected: + void initialize() override; + void deinitialize() override; + void onConfigChanged(ParameterNames& names) override; + void writeFrame(Core::Frame::Ptr pFrame) override; + + int getVolumeValue(); + +private: + hi_audio_bit_width _bitWidth; + hi_audio_snd_mode _sndMode; +}; +} +} +#endif diff --git a/video/hisi/mpp5/HDMIVideoOutput.cpp b/video/hisi/mpp5/HDMIVideoOutput.cpp new file mode 100644 index 0000000..aa18053 --- /dev/null +++ b/video/hisi/mpp5/HDMIVideoOutput.cpp @@ -0,0 +1,403 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "HDMIVideoOutput.h" +#include "Logger.h" +#include "util/Math.h" +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CONSTANTS + ******************************************************************************* + */ + +#define VO_DEVICE_ID 0 + +#define VIDEO_LAYER_ID 0 + +#define HDMI_ID HI_HDMI_ID_0 + +/* + ******************************************************************************* + * TYPEDEFS + ******************************************************************************* + */ +struct TimingDesc +{ + std::string desc; + uint32_t width; + uint32_t height; + uint32_t frameRate; + hi_vo_intf_sync intf; + hi_hdmi_video_format format; +}; + +/* + ******************************************************************************* + * LOCAL VARIABLES + ******************************************************************************* + */ +static const std::array timingDescTable = {{ + {"3840x2160P30", 3840, 2160, 30, HI_VO_OUT_3840x2160_30, HI_HDMI_VIDEO_FORMAT_3840X2160P_30}, + {"2560x1600P60", 2560, 1600, 60, HI_VO_OUT_2560x1600_60, HI_HDMI_VIDEO_FORMAT_2560x1600_60}, + {"2560x1440P60", 2560, 1440, 60, HI_VO_OUT_2560x1440_60, HI_HDMI_VIDEO_FORMAT_2560x1440_60}, + {"2560x1440P30", 2560, 1440, 30, HI_VO_OUT_2560x1440_30, HI_HDMI_VIDEO_FORMAT_2560x1440_30}, + {"1920x1080P60", 1920, 1080, 60, HI_VO_OUT_1080P60, HI_HDMI_VIDEO_FORMAT_1080P_60}, + {"1920x1080I60", 1920, 1080, 60, HI_VO_OUT_1080I60, HI_HDMI_VIDEO_FORMAT_1080i_60}, + {"1920x1080P50", 1920, 1080, 50, HI_VO_OUT_1080P50, HI_HDMI_VIDEO_FORMAT_1080P_50}, + {"1920x1080I50", 1920, 1080, 50, HI_VO_OUT_1080I50, HI_HDMI_VIDEO_FORMAT_1080i_50}, + {"1920x1080P30", 1920, 1080, 30, HI_VO_OUT_1080P30, HI_HDMI_VIDEO_FORMAT_1080P_30}, + {"1920x1080P25", 1920, 1080, 25, HI_VO_OUT_1080P25, HI_HDMI_VIDEO_FORMAT_1080P_25}, + {"1920x1080P24", 1920, 1080, 24, HI_VO_OUT_1080P24, HI_HDMI_VIDEO_FORMAT_1080P_24}, + {"1280x720P60", 1280, 720, 60, HI_VO_OUT_720P60, HI_HDMI_VIDEO_FORMAT_720P_60}, + {"1280x720P50", 1280, 720, 50, HI_VO_OUT_720P50, HI_HDMI_VIDEO_FORMAT_720P_50}, + {"720x576P50", 720, 576, 50, HI_VO_OUT_576P50, HI_HDMI_VIDEO_FORMAT_576P_50}, + {"720x480P50", 720, 480, 60, HI_VO_OUT_480P60, HI_HDMI_VIDEO_FORMAT_480P_60}, + {"1920x2160P30", 1920, 2160, 30, HI_VO_OUT_1920x2160_30, HI_HDMI_VIDEO_FORMAT_1920x2160_30}, + {"1920x1200P60", 1920, 1200, 60, HI_VO_OUT_1920x1200_60, HI_HDMI_VIDEO_FORMAT_VESA_1920X1200_60}, + {"1680x1050P60", 1680, 1050, 60, HI_VO_OUT_1680x1050_60, HI_HDMI_VIDEO_FORMAT_VESA_1680X1050_60}, + {"1600x1200P60", 1600, 1200, 60, HI_VO_OUT_1600x1200_60, HI_HDMI_VIDEO_FORMAT_VESA_1600X1200_60}, + {"1440x900P60", 1440, 900, 60, HI_VO_OUT_1440x900_60, HI_HDMI_VIDEO_FORMAT_VESA_1440X900_60}, + {"1366x768P60", 1366, 768, 60, HI_VO_OUT_1366x768_60, HI_HDMI_VIDEO_FORMAT_VESA_1366X768_60}, + {"1280x1024P60", 1280, 1024, 60, HI_VO_OUT_1280x1024_60, HI_HDMI_VIDEO_FORMAT_VESA_1280X1024_60}, + {"1280x800P60", 1280, 800, 60, HI_VO_OUT_1280x800_60, HI_HDMI_VIDEO_FORMAT_VESA_1280X800_60}, + {"1024x768P60", 1024, 768, 60, HI_VO_OUT_1024x768_60, HI_HDMI_VIDEO_FORMAT_VESA_1024X768_60}, + {"800x600P60", 800, 600, 60, HI_VO_OUT_800x600_60, HI_HDMI_VIDEO_FORMAT_VESA_800X600_60}, +}}; + +const TimingDesc& defaultTime = timingDescTable[4]; + +/* + ******************************************************************************* + * LOCAL FUNCTIONS + ******************************************************************************* + */ +static const TimingDesc& getTimingDesc(const std::string& desc) +{ + for (const auto& t : timingDescTable) + { + if (t.desc == desc) + return t; + } + + logger().warning("HDMIVideoOutput: Could NOT found '%s', using %s", desc, defaultTime.desc); + return defaultTime; +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +HDMIVideoOutput::HDMIVideoOutput(const std::string& name) + : Core::VideoOutput(name + "(MPP5::HDMI)"), MPPChannel((uint32_t)HI_ID_VO, VO_DEVICE_ID, 0) +{ + hi_mpi_hdmi_init(); + stopOutput(); +} + +HDMIVideoOutput::~HDMIVideoOutput() +{ + stopOutput(); + hi_mpi_hdmi_deinit(); +} + +void HDMIVideoOutput::start() +{ + hi_mpi_hdmi_open(HDMI_ID); + + onHotPlug(); +} + +void HDMIVideoOutput::stop() +{ + onNoPlug(); + + hi_mpi_hdmi_close(HDMI_ID); +} + +void HDMIVideoOutput::onConfigChanged(ParameterNames& names) +{ + if (!getHDMIStarted()) + return; + + if (names.exists(_Timing)) + { + try + { + stopOutput(); + startOutput(); + } + catch (Poco::Exception& e) + { + logger().error(e.displayText()); + stopOutput(); + } + } + + if (names.exists(_Mode)) + { + hi_mpi_hdmi_stop(HDMI_ID); + + hi_hdmi_attr attr; + + int rc = hi_mpi_hdmi_get_attr(HDMI_ID, &attr); + if (rc != HI_SUCCESS) + logger().error("%s: get HDMI attr FAIL: %d(%#x)", name(), rc, (uint32_t)rc); + + attr.hdmi_en = getUsingHDMIMode(); + rc = hi_mpi_hdmi_set_attr(HDMI_ID, &attr); + if (rc != HI_SUCCESS) + logger().error("%s: set HDMI attr FAIL: %d(%#x)", name(), rc, (uint32_t)rc); + + rc = hi_mpi_hdmi_start(HDMI_ID); + if (rc != HI_SUCCESS) + logger().error("%s: start HDMI FAIL: %d(%#x)", name(), rc, (uint32_t)rc); + } + + if (names.exists(_Luma) || names.exists(_Contrast) || names.exists(_Hue) || names.exists(_Saturation) || + names.exists(_ColorType)) + { + hi_vo_hdmi_param param; + + if (names.exists(_ColorType)) + hi_mpi_hdmi_stop(HDMI_ID); + + hi_mpi_vo_get_hdmi_param(deviceID(), ¶m); + + if (names.exists(_ColorType)) + param.csc.csc_matrix = getCSCMatrix(); + + param.csc.luma = ::Util::Math::clamp(getLuma(), 0, 100); + param.csc.contrast = ::Util::Math::clamp(getContrast(), 0, 100); + param.csc.hue = ::Util::Math::clamp(getHue(), 0, 100); + param.csc.saturation = ::Util::Math::clamp(getSaturation(), 0, 100); + hi_mpi_vo_set_hdmi_param(deviceID(), ¶m); + + if (names.exists(_ColorType)) + hi_mpi_hdmi_start(HDMI_ID); + } + + if (names.exists(_Mirror)) + hi_mpi_vo_set_chn_mirror(VIDEO_LAYER_ID, channelID(), getMirror() ? HI_VO_MIRROR_HOR : HI_VO_MIRROR_NONE); + + if (names.exists(_Rotation)) + hi_mpi_vo_set_chn_rotation(VIDEO_LAYER_ID, channelID(), getRotationValue()); +} + +void HDMIVideoOutput::onLink(Core::Component::Ptr pPrevious) +{ + MPPChannel::doLink(pPrevious); +} + +void HDMIVideoOutput::onUnlink(Core::Component::Ptr pPrevious) +{ + MPPChannel::doUnlink(pPrevious); +} + +void HDMIVideoOutput::onHotPlug() +{ + logger().debug("%s: HDMI HOTPLUG", name()); + setPluged(true); + + try + { + startOutput(); + } + catch (Poco::Exception& e) + { + logger().error(e.displayText()); + } +} + +void HDMIVideoOutput::onNoPlug() +{ + logger().debug("%s: HDMI NOPLUG", name()); + setPluged(false); + stopOutput(); +} + +void HDMIVideoOutput::onEDIDFail() +{ + logger().debug("EDID FAIL"); +} + +void HDMIVideoOutput::startOutput() +{ + const TimingDesc& timing = getTimingDesc(getTiming()); + + hi_vo_pub_attr attr; + attr.bg_color = 0x000000; + attr.intf_type = HI_VO_INTF_HDMI; + attr.intf_sync = timing.intf; + int rc = hi_mpi_vo_set_pub_attr(deviceID(), &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: set-pub FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + rc = hi_mpi_vo_enable(deviceID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: enable VO FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + { + hi_vo_video_layer_attr attr; + + attr.display_rect.x = 0; + attr.display_rect.y = 0; + attr.display_rect.width = timing.width; + attr.display_rect.height = timing.height; + attr.img_size.width = timing.width; + attr.img_size.height = timing.height; + attr.display_frame_rate = timing.frameRate; + attr.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_422; + + attr.double_frame_en = HI_FALSE; + attr.cluster_mode_en = HI_FALSE; + attr.dst_dynamic_range = HI_DYNAMIC_RANGE_SDR8; + attr.display_buf_len = 4; + + attr.partition_mode = HI_VO_PARTITION_MODE_SINGLE; + attr.compress_mode = HI_COMPRESS_MODE_NONE; + rc = hi_mpi_vo_set_video_layer_attr(VIDEO_LAYER_ID, &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: set video layer attr FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + rc = hi_mpi_vo_enable_video_layer(VIDEO_LAYER_ID); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: enable video layer FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + } + + { + hi_vo_chn_attr attr; + attr.priority = 0; + attr.rect.x = 0; + attr.rect.y = 0; + attr.rect.width = timing.width; + attr.rect.height = timing.height; + attr.deflicker_en = HI_FALSE; + rc = hi_mpi_vo_set_chn_attr(VIDEO_LAYER_ID, channelID(), &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: set chn attr FAIL: %d(%#x)", name(), rc, (uint32_t)rc), + rc); + + rc = hi_mpi_vo_enable_chn(VIDEO_LAYER_ID, channelID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: enable FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + hi_mpi_vo_set_chn_mirror(VIDEO_LAYER_ID, channelID(), getMirror() ? HI_VO_MIRROR_HOR : HI_VO_MIRROR_NONE); + hi_mpi_vo_set_chn_rotation(VIDEO_LAYER_ID, channelID(), getRotationValue()); + } + + { + hi_hdmi_attr attr; + rc = hi_mpi_hdmi_get_attr(HDMI_ID, &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: get HDMI attr FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + attr.hdmi_en = getUsingHDMIMode(); + attr.video_format = timing.format; + attr.deep_color_mode = HI_HDMI_DEEP_COLOR_24BIT; + attr.audio_en = attr.hdmi_en ? HI_TRUE : HI_FALSE; + attr.sample_rate = HI_HDMI_SAMPLE_RATE_48K; + attr.bit_depth = HI_HDMI_BIT_DEPTH_16; + attr.auth_mode_en = HI_TRUE; + attr.deep_color_adapt_en = HI_TRUE; + rc = hi_mpi_hdmi_set_attr(HDMI_ID, &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: set HDMI attr FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + hi_vo_hdmi_param param; + hi_mpi_vo_get_hdmi_param(deviceID(), ¶m); + param.csc.csc_matrix = getCSCMatrix(); + param.csc.luma = ::Util::Math::clamp(getLuma(), 0, 100); + param.csc.contrast = ::Util::Math::clamp(getContrast(), 0, 100); + param.csc.hue = ::Util::Math::clamp(getHue(), 0, 100); + param.csc.saturation = ::Util::Math::clamp(getSaturation(), 0, 100); + hi_mpi_vo_set_hdmi_param(deviceID(), ¶m); + + rc = hi_mpi_hdmi_start(HDMI_ID); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: start HDMI FAIL: %d(%#x)", name(), rc, (uint32_t)rc), + rc); + } + + setHDMIStarted(true); +} + +void HDMIVideoOutput::stopOutput() +{ + hi_mpi_hdmi_stop(HDMI_ID); + + hi_mpi_vo_disable_chn(VIDEO_LAYER_ID, channelID()); + + hi_mpi_vo_disable_video_layer(VIDEO_LAYER_ID); + + hi_mpi_vo_disable(VO_DEVICE_ID); + + setHDMIStarted(false); +} + +hi_bool HDMIVideoOutput::getUsingHDMIMode() +{ + return getMode() == Core::VideoOutput::Mode::HDMI ? HI_TRUE : HI_FALSE; +} + +hi_vo_csc_matrix HDMIVideoOutput::getCSCMatrix() +{ + if (getMode() == Core::VideoOutput::Mode::DVI || getColorType() == Core::VideoOutput::ColorType::RGB) + return HI_VO_CSC_MATRIX_BT709LIMIT_TO_RGBFULL; + + return HI_VO_CSC_MATRIX_BT709LIMIT_TO_BT709FULL; +} + +hi_rotation HDMIVideoOutput::getRotationValue() +{ + switch (getRotation()) + { + default: + case 0: + return HI_ROTATION_0; + + case 90: + return HI_ROTATION_90; + + case 180: + return HI_ROTATION_180; + + case 270: + return HI_ROTATION_270; + } +} + +void HDMIVideoOutput::eventCallback(hi_hdmi_event_type event, td_void* p) +{ + switch (event) + { + case HI_HDMI_EVENT_HOTPLUG: + static_cast(p)->onHotPlug(); + break; + + case HI_HDMI_EVENT_NO_PLUG: + static_cast(p)->onNoPlug(); + break; + + case HI_HDMI_EVENT_EDID_FAIL: + static_cast(p)->onEDIDFail(); + break; + + default: + break; + } +} +} +} diff --git a/video/hisi/mpp5/HDMIVideoOutput.h b/video/hisi/mpp5/HDMIVideoOutput.h new file mode 100644 index 0000000..cfae313 --- /dev/null +++ b/video/hisi/mpp5/HDMIVideoOutput.h @@ -0,0 +1,53 @@ +#ifndef AB83CB54_F14E_4C1B_B2AE_6AF47090444C +#define AB83CB54_F14E_4C1B_B2AE_6AF47090444C +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../../core/VideoOutput.h" +#include "MPPChannel.h" +#include +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class HDMIVideoOutput: public Core::VideoOutput, MPPChannel +{ +public: + HDMIVideoOutput(const std::string& name); + ~HDMIVideoOutput(); + +protected: + void start() override; + void stop() override; + void onConfigChanged(ParameterNames& names) override; + void onLink(Core::Component::Ptr pPrevious) override; + void onUnlink(Core::Component::Ptr pPrevious) override; + + void onHotPlug(); + void onNoPlug(); + void onEDIDFail(); + + void startOutput(); + void stopOutput(); + + hi_bool getUsingHDMIMode(); + hi_vo_csc_matrix getCSCMatrix(); + hi_rotation getRotationValue(); + + PROPERTY_DECLARE(Pluged, bool, false, protected, protected) + PROPERTY_DECLARE(HDMIStarted, bool, false, protected, protected) + + static void eventCallback(hi_hdmi_event_type event, td_void* p); +}; +} +} +#endif diff --git a/video/hisi/mpp5/IVE.cpp b/video/hisi/mpp5/IVE.cpp new file mode 100644 index 0000000..dc2b512 --- /dev/null +++ b/video/hisi/mpp5/IVE.cpp @@ -0,0 +1,498 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "IVE.h" +#include "Logger.h" +#include "util/Math.h" +#include +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CONSTANTS + ******************************************************************************* + */ + +#define HI__IVE_IMAGE_CHN_ONE 1 +#define HI__IVE_IMAGE_CHN_TWO 2 +#define HI__IVE_IMAGE_CHN_THREE 3 +#define HI__IVE_IMAGE_CHN_FOUR 4 +#define HI__IVE_MAX_CCL_REGION_NUM 254 +#define HI__IVE_DIV_TWO 2 +#define HI_SAMPLE_POINT_IDX_ZERO 0 +#define HI_SAMPLE_POINT_IDX_ONE 1 +#define HI_SAMPLE_POINT_IDX_TWO 2 +#define HI_SAMPLE_POINT_IDX_THREE 3 + +#define HI_SAMPLE_RESIZE_MAX_TILE_METRIC_8BIT 2032 +#define HI_SAMPLE_RESIZE_MAX_TILE_METRIC_16BIT 1016 +#define HI_SAMPLE_RESIZE_TILE_ADJUST 2 +#define HI_SAMPLE_RESIZE_ASSIST_UNIT_SIZE 48 +#define HI_SAMPLE_RESIZE_CBCR_NUM_2 2 +#define HI_SAMPLE_RESIZE_CBCR_NUM_3 3 + +#define HI_IVE_ALIGN 16 + +/* + ******************************************************************************* + * MACROS + ******************************************************************************* + */ +#define _convert_ptr_to_addr(type, addr) ((type)(hi_uintptr_t)(addr)) +#define _convert_addr_to_ptr(type, addr) ((type*)(hi_uintptr_t)(addr)) + +/* + ******************************************************************************* + * TYPEDEFS + ******************************************************************************* + */ +struct _img_loop_info +{ + uint32_t ele_size; + uint32_t loop_c; + uint32_t loop_h[HI_SVP_IMG_ADDR_NUM]; +}; +typedef struct _img_loop_info _img_loop_info_t; + +/* + ******************************************************************************* + * LOCAL FUNCTIONS + ******************************************************************************* + */ +static void _ive_get_loop_info(const hi_svp_img* img, _img_loop_info_t* loop_info) +{ + loop_info->ele_size = 1; + loop_info->loop_c = 1; + loop_info->loop_h[0] = img->height; + switch (img->type) + { + case HI_SVP_IMG_TYPE_U8C1: + case HI_SVP_IMG_TYPE_S8C1: + break; + + case HI_SVP_IMG_TYPE_YUV420SP: + loop_info->ele_size = 1; + loop_info->loop_c = HI__IVE_IMAGE_CHN_TWO; + loop_info->loop_h[1] = img->height / HI__IVE_DIV_TWO; + break; + + case HI_SVP_IMG_TYPE_YUV422SP: + loop_info->loop_c = HI__IVE_IMAGE_CHN_TWO; + loop_info->loop_h[1] = img->height; + break; + + case HI_SVP_IMG_TYPE_U8C3_PACKAGE: + loop_info->ele_size = (uint32_t)(sizeof(uint8_t) + sizeof(uint16_t)); + break; + + case HI_SVP_IMG_TYPE_U8C3_PLANAR: + loop_info->loop_c = HI__IVE_IMAGE_CHN_THREE; + loop_info->loop_h[1] = img->height; + loop_info->loop_h[HI__IVE_IMAGE_CHN_TWO] = img->height; + break; + + case HI_SVP_IMG_TYPE_S16C1: + case HI_SVP_IMG_TYPE_U16C1: + loop_info->ele_size = (uint32_t)sizeof(uint16_t); + break; + + case HI_SVP_IMG_TYPE_U32C1: + case HI_SVP_IMG_TYPE_S32C1: + loop_info->ele_size = (uint32_t)sizeof(uint32_t); + break; + + case HI_SVP_IMG_TYPE_S64C1: + case HI_SVP_IMG_TYPE_U64C1: + loop_info->ele_size = (uint32_t)sizeof(uint64_t); + break; + + default: + break; + } +} + +#define HI_SAMPLE_ADDR_IDX_ZERO 0 +#define HI_SAMPLE_ADDR_IDX_ONE 1 +#define HI_SAMPLE_ADDR_IDX_TWO 2 +#define HI_MAX_LOOP_IMG_H 3 + +static int _ive_set_image_addr(hi_svp_img* img, const _img_loop_info_t* loop_info, hi_bool is_mmz_cached) +{ + uint32_t c; + int size = 0; + int rc; + hi_void* virt_addr = HI_NULL; + + for (c = 0; (c < loop_info->loop_c) && (c < HI_MAX_LOOP_IMG_H) && (c < HI_SVP_IMG_STRIDE_NUM); c++) + { + size += img->stride[0] * loop_info->loop_h[c] * loop_info->ele_size; + img->stride[c] = img->stride[0]; + } + + if (is_mmz_cached) + { + rc = hi_mpi_sys_mmz_alloc_cached((hi_phys_addr_t*)&img->phys_addr[0], (hi_void**)&virt_addr, HI_NULL, HI_NULL, + size); + } + else + { + rc = hi_mpi_sys_mmz_alloc((hi_phys_addr_t*)&img->phys_addr[0], (hi_void**)&virt_addr, HI_NULL, HI_NULL, size); + } + + if (rc != HI_SUCCESS) + { + logger().error("IVE: mmz malloc FAIL: %d(%08X)", rc, (uint32_t)rc); + return rc; + } + + img->virt_addr[HI_SAMPLE_ADDR_IDX_ZERO] = _convert_ptr_to_addr(uint64_t, virt_addr); + + if (img->type != HI_SVP_IMG_TYPE_U8C3_PACKAGE) + { + for (c = 1; (c < loop_info->loop_c) && (c < HI_MAX_LOOP_IMG_H) && (c < HI_SVP_IMG_STRIDE_NUM); c++) + { + img->phys_addr[c] = img->phys_addr[c - 1] + img->stride[c - 1] * img->height; + img->virt_addr[c] = img->virt_addr[c - 1] + img->stride[c - 1] * img->height; + } + } + else + { + img->virt_addr[HI_SAMPLE_ADDR_IDX_ONE] = img->virt_addr[HI_SAMPLE_ADDR_IDX_ZERO] + 1; + img->virt_addr[HI_SAMPLE_ADDR_IDX_TWO] = img->virt_addr[HI_SAMPLE_ADDR_IDX_ONE] + 1; + img->phys_addr[HI_SAMPLE_ADDR_IDX_ONE] = img->phys_addr[HI_SAMPLE_ADDR_IDX_ZERO] + 1; + img->phys_addr[HI_SAMPLE_ADDR_IDX_TWO] = img->phys_addr[HI_SAMPLE_ADDR_IDX_ONE] + 1; + } + + return size; +} + +static int _ive_img_init(hi_svp_img* img, hi_svp_img_type type, uint32_t width, uint32_t height) +{ + int rc = 0; + _img_loop_info_t loop_info; + + img->type = type; + img->width = width; + img->height = height; + img->stride[0] = Util::Math::alignup(width, HI_IVE_ALIGN); + + switch (type) + { + case HI_SVP_IMG_TYPE_U8C1: + case HI_SVP_IMG_TYPE_S8C1: + case HI_SVP_IMG_TYPE_YUV420SP: + case HI_SVP_IMG_TYPE_YUV422SP: + case HI_SVP_IMG_TYPE_S16C1: + case HI_SVP_IMG_TYPE_U16C1: + case HI_SVP_IMG_TYPE_U8C3_PACKAGE: + case HI_SVP_IMG_TYPE_S32C1: + case HI_SVP_IMG_TYPE_U32C1: + case HI_SVP_IMG_TYPE_S64C1: + case HI_SVP_IMG_TYPE_U64C1: + _ive_get_loop_info(img, &loop_info); + rc = _ive_set_image_addr(img, &loop_info, HI_FALSE); + break; + + case HI_SVP_IMG_TYPE_YUV420P: + break; + + case HI_SVP_IMG_TYPE_YUV422P: + break; + + case HI_SVP_IMG_TYPE_S8C2_PACKAGE: + break; + + case HI_SVP_IMG_TYPE_S8C2_PLANAR: + break; + + case HI_SVP_IMG_TYPE_U8C3_PLANAR: + break; + + default: + break; + } + + return rc; +} + +static uint32_t _ive_resize_get_tile_metric(uint32_t src_metric, uint32_t dst_metric, hi_svp_img_type img_type) +{ + int tmp_metric = HI_SAMPLE_RESIZE_MAX_TILE_METRIC_8BIT; + uint32_t src_tile_num, scale, dst_tmp_tile, src_tmp_tile, tile_remainder; + switch (img_type) + { + case HI_SVP_IMG_TYPE_YUV420SP: + case HI_SVP_IMG_TYPE_YUV422SP: + case HI_SVP_IMG_TYPE_U8C3_PLANAR: + case HI_SVP_IMG_TYPE_U8C1: + case HI_SVP_IMG_TYPE_S8C1: + tmp_metric = HI_SAMPLE_RESIZE_MAX_TILE_METRIC_8BIT; + break; + + case HI_SVP_IMG_TYPE_U16C1: + case HI_SVP_IMG_TYPE_S16C1: + + tmp_metric = HI_SAMPLE_RESIZE_MAX_TILE_METRIC_16BIT; + break; + + default: + break; + } + + scale = (src_metric / dst_metric) + ((src_metric % dst_metric == 0) ? 0 : 1); + if (dst_metric < src_metric) + { + src_tile_num = (src_metric / tmp_metric) + ((src_metric % tmp_metric == 0) ? 0 : 1); + dst_tmp_tile = dst_metric / src_tile_num; + src_tmp_tile = src_metric / src_tile_num + ((src_metric % src_tile_num == 0) ? 0 : 1); + tile_remainder = (tmp_metric - src_tmp_tile) / scale; + tmp_metric = (dst_tmp_tile + tile_remainder) & ~0x1; + if (tile_remainder == 0) + { + tmp_metric -= HI_SAMPLE_RESIZE_TILE_ADJUST; + } + } + else if (dst_metric == src_metric || scale == 1) + { + tmp_metric -= HI_SAMPLE_RESIZE_TILE_ADJUST; + } + return (uint32_t)tmp_metric; +} + +static uint32_t _ive_resize_get_tile_num(uint32_t width, uint32_t height, uint32_t tile_w, uint32_t tile_h) +{ + uint32_t x_num, y_num; + if ((tile_w == 0) || (tile_h == 0)) + return 0; + + x_num = (width / tile_w) + ((width % tile_w) == 0 ? 0 : 1); + y_num = (height / tile_h) + ((height % tile_h) == 0 ? 0 : 1); + return (x_num * y_num); +} + +#define HI_SAMPLE_RESIZE_CBCR_NUM_2 2 +static uint32_t _ive_resize_get_yuv420sp_tile_num(uint32_t src_width, uint32_t dst_width, uint32_t src_height, + uint32_t dst_height) +{ + uint32_t j, tile_width, tile_height; + uint32_t num = 0; + + tile_width = _ive_resize_get_tile_metric(src_width, dst_width, HI_SVP_IMG_TYPE_YUV420SP); + for (j = 0; j < HI_SAMPLE_RESIZE_CBCR_NUM_2; j++) + { + tile_height = _ive_resize_get_tile_metric(src_height >> j, dst_height >> j, HI_SVP_IMG_TYPE_YUV420SP); + num += _ive_resize_get_tile_num(dst_width, dst_height >> j, tile_width, tile_height); + } + + return num; +} + +static uint32_t _ive_resize_get_yuv422sp_tile_num(uint32_t src_width, uint32_t dst_width, uint32_t src_height, + uint32_t dst_height) +{ + uint32_t j, tile_width, tile_height; + uint32_t num = 0; + + tile_width = _ive_resize_get_tile_metric(src_width, dst_width, HI_SVP_IMG_TYPE_YUV422SP); + tile_height = _ive_resize_get_tile_metric(src_height, dst_height, HI_SVP_IMG_TYPE_YUV422SP); + + for (j = 0; j < HI_SAMPLE_RESIZE_CBCR_NUM_2; j++) + num += _ive_resize_get_tile_num(dst_width, dst_height, tile_width, tile_height); + + return num; +} + +static uint32_t _ive_resize_get_u8c3_planar_tile_num(uint32_t src_width, uint32_t dst_width, uint32_t src_height, + uint32_t dst_height) +{ + uint32_t j, tile_width, tile_height; + uint32_t num = 0; + + tile_width = _ive_resize_get_tile_metric(src_width, dst_width, HI_SVP_IMG_TYPE_U8C3_PLANAR); + tile_height = _ive_resize_get_tile_metric(src_height, dst_height, HI_SVP_IMG_TYPE_U8C3_PLANAR); + for (j = 0; j < HI_SAMPLE_RESIZE_CBCR_NUM_3; j++) + num += _ive_resize_get_tile_num(dst_width, dst_height, tile_width, tile_height); + + return num; +} + +static uint32_t _ive_resize_get_u8c1_tile_num(uint32_t src_width, uint32_t dst_width, uint32_t src_height, + uint32_t dst_height) +{ + uint32_t tile_width, tile_height; + uint32_t num = 0; + + tile_width = _ive_resize_get_tile_metric(src_width, dst_width, HI_SVP_IMG_TYPE_U8C1); + tile_height = _ive_resize_get_tile_metric(src_height, dst_height, HI_SVP_IMG_TYPE_U8C1); + num = _ive_resize_get_tile_num(dst_width, dst_height, tile_width, tile_height); + return num; +} + +static uint32_t _ive_resize_get_u16c1_tile_num(uint32_t src_width, uint32_t dst_width, uint32_t src_height, + uint32_t dst_height) +{ + uint32_t tile_width, tile_height; + uint32_t num = 0; + + tile_width = _ive_resize_get_tile_metric(src_width, dst_width, HI_SVP_IMG_TYPE_U16C1); + tile_height = _ive_resize_get_tile_metric(src_height, dst_height, HI_SVP_IMG_TYPE_U16C1); + num = _ive_resize_get_tile_num(dst_width, dst_height, tile_width, tile_height); + return num; +} + +static int _ive_resize_get_assist_buf_size(const hi_svp_src_img src[], const hi_svp_dst_img dst[], int count) +{ + int i; + int num = 0; + for (i = 0; i < count; i++) + { + switch (src[i].type) + { + case HI_SVP_IMG_TYPE_YUV420SP: + num += _ive_resize_get_yuv420sp_tile_num(src[i].width, dst[i].width, src[i].height, dst[i].height); + break; + + case HI_SVP_IMG_TYPE_YUV422SP: + num += _ive_resize_get_yuv422sp_tile_num(src[i].width, dst[i].width, src[i].height, dst[i].height); + break; + + case HI_SVP_IMG_TYPE_U8C3_PLANAR: + num += _ive_resize_get_u8c3_planar_tile_num(src[i].width, dst[i].width, src[i].height, dst[i].height); + break; + + case HI_SVP_IMG_TYPE_U8C1: + case HI_SVP_IMG_TYPE_S8C1: + num += _ive_resize_get_u8c1_tile_num(src[i].width, dst[i].width, src[i].height, dst[i].height); + break; + + case HI_SVP_IMG_TYPE_U16C1: + case HI_SVP_IMG_TYPE_S16C1: + num += _ive_resize_get_u16c1_tile_num(src[i].width, dst[i].width, src[i].height, dst[i].height); + break; + + default: + break; + } + } + + return num * HI_SAMPLE_RESIZE_ASSIST_UNIT_SIZE; +} + +static int _ive_mem_info_init(hi_svp_mem_info* mem_info, hi_u32 size) +{ + int rc = HI_ERR_IVE_NULL_PTR; + hi_void* virt_addr = HI_NULL; + + mem_info->size = size; + rc = hi_mpi_sys_mmz_alloc((hi_phys_addr_t*)&mem_info->phys_addr, (hi_void**)&virt_addr, HI_NULL, HI_NULL, size); + if (rc != HI_SUCCESS) + return rc; + + mem_info->virt_addr = _convert_ptr_to_addr(uint64_t, virt_addr); + return rc; +} + +static int _ive_img_load(hi_svp_img* img, uint8_t* pData) +{ + uint8_t* ptr_tmp = HI_NULL; + uint16_t c, h; + int rc = 0; + _img_loop_info_t loop_info = {0}; + int index = 0; + + _ive_get_loop_info(img, &loop_info); + for (c = 0; (c < loop_info.loop_c) && (c < HI_SVP_IMG_STRIDE_NUM) && (c < HI_SVP_IMG_ADDR_NUM); c++) + { + ptr_tmp = _convert_addr_to_ptr(uint8_t, img->virt_addr[c]); + if (!ptr_tmp) + { + rc = HI_ERR_IVE_BAD_ADDR; + goto exit; + } + + for (h = 0; h < loop_info.loop_h[c]; h++) + { + int bytes = img->width * loop_info.ele_size; + memcpy(ptr_tmp, &pData[index], bytes); + index += bytes; + ptr_tmp += img->stride[c] * loop_info.ele_size; + } + } + +exit: + return rc; +} + +static int _ive_img_store(hi_svp_img* img, uint8_t* pBuffer) +{ + uint16_t c, h; + uint8_t* ptr_tmp = HI_NULL; + int rc = 0; + _img_loop_info_t loop_info = {0}; + int index = 0; + + if (img->phys_addr == 0 || img->virt_addr == 0) + return HI_ERR_BAD_ADDR; + + _ive_get_loop_info(img, &loop_info); + for (c = 0; (c < loop_info.loop_c) && (c < HI_SVP_IMG_STRIDE_NUM) && (c < HI_SVP_IMG_ADDR_NUM); c++) + { + ptr_tmp = _convert_addr_to_ptr(uint8_t, img->virt_addr[c]); + if (!ptr_tmp) + { + rc = HI_ERR_IVE_BAD_ADDR; + goto exit; + } + + for (h = 0; h < loop_info.loop_h[c]; h++) + { + int bytes = img->width * loop_info.ele_size; + memcpy(&pBuffer[index], ptr_tmp, bytes); + index += bytes; + + ptr_tmp += img->stride[c] * loop_info.ele_size; + } + } + +exit: + return rc; +} + +#define _mmz_free(phys, virt) \ + do \ + { \ + if (((phys) != 0) && ((virt) != 0)) \ + { \ + hi_mpi_sys_mmz_free((hi_phys_addr_t)(phys), (hi_void*)(hi_uintptr_t)(virt)); \ + (phys) = 0; \ + (virt) = 0; \ + } \ + } while (0) + +static void _ive_img_free(hi_svp_img* img) +{ + _mmz_free(img->phys_addr[0], img->virt_addr[0]); +} + +static void _ive_mem_info_free(hi_svp_mem_info* info) +{ + _mmz_free(info->phys_addr, info->virt_addr); +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoFrame::Ptr MPP5::IVE::resize(VideoFrame::Ptr from, uint32_t width, uint32_t height) +{ + return VideoFrame::Ptr(); +} +} +} diff --git a/video/hisi/mpp5/IVE.h b/video/hisi/mpp5/IVE.h new file mode 100644 index 0000000..65e1bc8 --- /dev/null +++ b/video/hisi/mpp5/IVE.h @@ -0,0 +1,27 @@ +#ifndef F479D816_531D_4084_A289_1B5C5A2A6CE7 +#define F479D816_531D_4084_A289_1B5C5A2A6CE7 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoFrame.h" +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class IVE +{ +public: + VideoFrame::Ptr resize(VideoFrame::Ptr from, uint32_t width, uint32_t height); +}; +} +} +#endif diff --git a/video/hisi/mpp5/MPPChannel.cpp b/video/hisi/mpp5/MPPChannel.cpp new file mode 100644 index 0000000..a08d1fd --- /dev/null +++ b/video/hisi/mpp5/MPPChannel.cpp @@ -0,0 +1,135 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "MPPChannel.h" +#include "Logger.h" +#include +#include +#include + +/* + ******************************************************************************* + * MACROS + ******************************************************************************* + */ +#define HI_MPP_CHN_DECLARE(name, ref) \ + hi_mpp_chn name \ + { \ + (hi_mod_id)(ref).moduleID(), (hi_s32)(ref).deviceID(), (hi_s32)(ref).channelID(), \ + } + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +static const char* moduleNames[] = { + "CMPI", "VB", "SYS", + "RGN", "CHNL", "VDEC", + "AVS", "VPSS", "VENC", + "SVP", "H264E", "JPEGE", + "UNKNOWN", "H265E", "JPEGD", + "VO", "VI", "DIS", + "VALG", "RC", "AIO", + "AI", "AO", "AENC", + "ADEC", "VPU", "PCIV", + "PCIVFMW", "ISP", "IVE", + "USER", "UNKNOWN", "UNKNOWN", + "PROC", "LOG", "VFMW", + "UNKNOWN", "GDC", "PHOTO", + "FB", "HDMI", "VOIE", + "TDE", "HDR", "PRORES", + "VGS", "UNKNOWN", "FD", + "OD", "UNKNOWN", "LPR", + "SVP_NNIE", "SVP_DSP", "DPU_RECT", + "DPU_MATCH", "MOTIONSENSOR", "MOTIONFUSION", + "GYRODIS", "PM", "SVP_ALG", + "IVP", "MCF", "SVP_MAU", + "VDA", "VPP", "KCF", + "PQP", "NPUDEV", "AICPU", + "NPUDFX", "TSFW", "CIPHER", + "KLAD", "KEYSLOT", "OTP", + "VDEC_ADAPT", "DCC", "VDEC_SERVER", + "VFMW_MDC", "VB_LOG", "MCF_CALIBRATION", + "SVP_NPU", "HNR", "SNAP", + "LOG_MDC", "UVC", "FISHEYE_CALIBRATION", +}; + +MPPChannel::MPPChannel(uint32_t moduleID, uint32_t deviceID, uint32_t channelID) + : _moduleID(moduleID), _deviceID(deviceID), _channelID(channelID) +{ +} + +std::string MPPChannel::toString() const +{ + const char* name = "UNKNOWN"; + if (moduleID() < HI_ID_FISHEYE_CALIBRATION) + name = moduleNames[moduleID()]; + + return Poco::format("%s(%u:%u)", std::string(name), deviceID(), channelID()); +} + +void MPPChannel::bind(const MPPChannel& next) +{ + HI_MPP_CHN_DECLARE(from, *this); + HI_MPP_CHN_DECLARE(to, next); + + int rc = hi_mpi_sys_bind(&from, &to); + if (rc < 0) + logger().error("Bind: %s -> %s: %#x", this->toString(), next.toString(), (uint32_t)rc); + else + logger().debug("Bind: %s -> %s SUCCESS", this->toString(), next.toString()); +} + +void MPPChannel::unbind(const MPPChannel& next) +{ + HI_MPP_CHN_DECLARE(from, *this); + HI_MPP_CHN_DECLARE(to, next); + + int rc = hi_mpi_sys_unbind(&from, &to); + if (rc < 0) + logger().error("Unbind: %s -> %s: %#x", this->toString(), next.toString(), (uint32_t)rc); + else + logger().debug("Unbind: %s -> %s SUCCESS", this->toString(), next.toString()); +} + +void MPPChannel::doLink(Core::Component::Ptr pPrevious) +{ + auto pFrom = dynamic_cast(pPrevious.get()); + if (pFrom) + { + pFrom->bind(*this); + return; + } + + logger().error("MPPChannel: dolink FAIL: %s is not 'MPPChannel'", pPrevious->name()); +} + +void MPPChannel::doUnlink(Core::Component::Ptr pPrevious) +{ + auto pFrom = dynamic_cast(pPrevious.get()); + if (pFrom) + { + pFrom->unbind(*this); + return; + } + + logger().error("MPPChannel: doUnlink FAIL: %s is not 'MPPChannel'", pPrevious->name()); +} + +hi_mpp_chn MPPChannel::raw() +{ + return hi_mpp_chn{ + (hi_mod_id)moduleID(), + (hi_s32)deviceID(), + (hi_s32)channelID(), + }; +} +} +} diff --git a/video/hisi/mpp5/MPPChannel.h b/video/hisi/mpp5/MPPChannel.h new file mode 100644 index 0000000..c0e0797 --- /dev/null +++ b/video/hisi/mpp5/MPPChannel.h @@ -0,0 +1,70 @@ +#ifndef EEDA2366_7E77_49AC_8716_526A3A63113F +#define EEDA2366_7E77_49AC_8716_526A3A63113F +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include "../core/Component.h" +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class MPPChannel +{ +public: + MPPChannel(uint32_t moduleID, uint32_t deviceID, uint32_t channelID); + +public: + bool operator<(const MPPChannel& other) const; + + uint32_t moduleID() const; + uint32_t deviceID() const; + uint32_t channelID() const; + + std::string toString() const; + + virtual void bind(const MPPChannel& next); + virtual void unbind(const MPPChannel& next); + + void doLink(Core::Component::Ptr pPrevious); + void doUnlink(Core::Component::Ptr pPrevious); + + hi_mpp_chn raw(); + +private: + uint32_t _moduleID; + uint32_t _deviceID; + uint32_t _channelID; +}; + +inline bool MPPChannel::operator<(const MPPChannel& other) const +{ + return moduleID() < other.moduleID() || deviceID() < other.deviceID() || channelID() < other.channelID(); +} + +inline uint32_t MPPChannel::moduleID() const +{ + return _moduleID; +} + +inline uint32_t MPPChannel::deviceID() const +{ + return _deviceID; +} + +inline uint32_t MPPChannel::channelID() const +{ + return _channelID; +} +} +} +#endif diff --git a/video/hisi/mpp5/Notifications.cpp b/video/hisi/mpp5/Notifications.cpp new file mode 100644 index 0000000..e3acb69 --- /dev/null +++ b/video/hisi/mpp5/Notifications.cpp @@ -0,0 +1,18 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Notifications.h" + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +} +} diff --git a/video/hisi/mpp5/Notifications.h b/video/hisi/mpp5/Notifications.h new file mode 100644 index 0000000..1731c0a --- /dev/null +++ b/video/hisi/mpp5/Notifications.h @@ -0,0 +1,24 @@ +#ifndef DF1404E9_430D_4F31_989F_4C5EB4BE26D4 +#define DF1404E9_430D_4F31_989F_4C5EB4BE26D4 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../../core/Component.h" + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class VideoDecoderResetNotification: public Core::Component::Notification +{ +}; +} +} +#endif diff --git a/video/hisi/mpp5/ResourceManager.cpp b/video/hisi/mpp5/ResourceManager.cpp new file mode 100644 index 0000000..e5ba3c2 --- /dev/null +++ b/video/hisi/mpp5/ResourceManager.cpp @@ -0,0 +1,75 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "ResourceManager.h" +#include +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Util::HandlesManager ResourceManager::_vpssGroupIDManager(HI_VPSS_MAX_GRP_NUM); +Util::HandlesManager ResourceManager::_vencChannelIDManager(HI_VENC_MAX_CHN_NUM); +Util::HandlesManager ResourceManager::_vdecChannelIDManager(HI_VDEC_MAX_CHN_NUM); +Util::HandlesManager ResourceManager::_aencChannelIDManager(HI_AENC_MAX_CHN_NUM); +Util::HandlesManager ResourceManager::_rgnHandleManager(HI_RGN_HANDLE_MAX); + +uint32_t ResourceManager::allocVPSSGroupID() +{ + return _vpssGroupIDManager.allocHandle(); +} + +void ResourceManager::freeVPSSGroupID(uint32_t id) +{ + _vpssGroupIDManager.freeHandle(id); +} + +uint32_t ResourceManager::allocVENCChannelID() +{ + return _vencChannelIDManager.allocHandle(); +} + +void ResourceManager::freeVENCChannelID(uint32_t id) +{ + _vencChannelIDManager.freeHandle(id); +} + +uint32_t ResourceManager::allocVDECChannelID() +{ + return _vdecChannelIDManager.allocHandle(); +} + +void ResourceManager::freeVDECChannelID(uint32_t id) +{ + _vdecChannelIDManager.freeHandle(id); +} + +uint32_t ResourceManager::allocAENCChannelID() +{ + return _aencChannelIDManager.allocHandle(); +} + +void ResourceManager::freeAENCChannelID(uint32_t id) +{ + _aencChannelIDManager.freeHandle(id); +} + +uint32_t ResourceManager::allocRGNHandle() +{ + return _rgnHandleManager.allocHandle(); +} + +void ResourceManager::freeRGNHandle(uint32_t handle) +{ + _rgnHandleManager.freeHandle(handle); +} +} +} diff --git a/video/hisi/mpp5/ResourceManager.h b/video/hisi/mpp5/ResourceManager.h new file mode 100644 index 0000000..9c384ce --- /dev/null +++ b/video/hisi/mpp5/ResourceManager.h @@ -0,0 +1,60 @@ +#ifndef CAE4D712_1E71_4191_9693_2F31854D9087 +#define CAE4D712_1E71_4191_9693_2F31854D9087 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "util/HandlesManager.h" + +/* + ******************************************************************************* + * CONSTANTS + ******************************************************************************* + */ +#define NO_SIGNAL_YUV_PATH "/customer/no-signal-1080.yvu422sp" +#define NO_SIGNAL_YUV_FORMAT HI_PIXEL_FORMAT_YVU_SEMIPLANAR_422 +#define NO_SIGNAL_YUV_WIDTH 1920 +#define NO_SIGNAL_YUV_HEIGHT 1080 + +#define NO_SIGNAL_JPEG_PATH "/customer/no-signal-1080.jpg" +#define NO_SIGNAL_JPEG_WIDTH 1920 +#define NO_SIGNAL_JPEG_HEIGHT 1080 + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class ResourceManager +{ +public: + static uint32_t allocVPSSGroupID(); + static void freeVPSSGroupID(uint32_t id); + + static uint32_t allocVENCChannelID(); + static void freeVENCChannelID(uint32_t id); + + static uint32_t allocVDECChannelID(); + static void freeVDECChannelID(uint32_t id); + + static uint32_t allocAENCChannelID(); + static void freeAENCChannelID(uint32_t id); + + static uint32_t allocRGNHandle(); + static void freeRGNHandle(uint32_t handle); + +private: + static Util::HandlesManager _vpssGroupIDManager; + static Util::HandlesManager _vencChannelIDManager; + static Util::HandlesManager _vdecChannelIDManager; + static Util::HandlesManager _aencChannelIDManager; + static Util::HandlesManager _rgnHandleManager; +}; +} +} +#endif diff --git a/video/hisi/mpp5/System.cpp b/video/hisi/mpp5/System.cpp new file mode 100644 index 0000000..d515a41 --- /dev/null +++ b/video/hisi/mpp5/System.cpp @@ -0,0 +1,110 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "System.h" +#include "Logger.h" +#include +#include +#include +#include +#include + +/* + ******************************************************************************* + * CONSTANTS + ******************************************************************************* + */ +#ifndef INPUT_IMAGE_WIDTH_MAX +#define INPUT_IMAGE_WIDTH_MAX 3840 +#endif +#ifndef INPUT_IMAGE_HEIGHT_MAX +#define INPUT_IMAGE_HEIGHT_MAX 2160 +#endif + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +System::System() +{ + hi_mpi_sys_exit(); + hi_mpi_vb_exit(); + + hi_vb_cfg vbcfg; + memset(&vbcfg, 0, sizeof(vbcfg)); + vbcfg.max_pool_cnt = 16; + + hi_pic_buf_attr attr; + attr.width = INPUT_IMAGE_WIDTH_MAX; + attr.height = INPUT_IMAGE_HEIGHT_MAX; + attr.align = HI_DEFAULT_ALIGN; + attr.bit_width = OT_DATA_BIT_WIDTH_8; + attr.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_422; + attr.compress_mode = HI_COMPRESS_MODE_FRAME; + uint32_t size = hi_common_get_pic_buf_size(&attr); + + sprintf(vbcfg.common_pool[0].mmz_name, "%s", "anonymous"); + vbcfg.common_pool[0].remap_mode = OT_VB_REMAP_MODE_NONE; + vbcfg.common_pool[0].blk_cnt = 32; + vbcfg.common_pool[0].blk_size = size; + + attr.width = 1920; + attr.height = 1080; + attr.align = HI_DEFAULT_ALIGN; + attr.bit_width = OT_DATA_BIT_WIDTH_8; + attr.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_422; + attr.compress_mode = HI_COMPRESS_MODE_FRAME; + size = hi_common_get_pic_buf_size(&attr); + + sprintf(vbcfg.common_pool[1].mmz_name, "%s", "anonymous"); + vbcfg.common_pool[1].remap_mode = OT_VB_REMAP_MODE_NONE; + vbcfg.common_pool[1].blk_cnt = 32; + vbcfg.common_pool[1].blk_size = size; + + int rc = hi_mpi_vb_set_cfg(&vbcfg); + if (rc != HI_SUCCESS) + { + logger().error("VB Set config FAIL: %#x", (uint32_t)rc); + return; + } + + rc = hi_mpi_vb_init(); + if (rc != HI_SUCCESS) + { + logger().error("VB Initialize FAIL: %#x", (uint32_t)rc); + return; + } + + rc = hi_mpi_sys_init(); + if (rc != HI_SUCCESS) + { + logger().error("SYS Initialize FAIL: %#x", (uint32_t)rc); + return; + } + + hi_mpi_sys_init_pts_base(0); + hi_tde_open(); +} + +System::~System() +{ + hi_tde_close(); + hi_mpi_sys_exit(); + hi_mpi_vb_exit(); +} + +uint64_t System::getTimestamp() +{ + hi_u64 now; + hi_mpi_sys_get_cur_pts(&now); + return now; +} +} +} diff --git a/video/hisi/mpp5/System.h b/video/hisi/mpp5/System.h new file mode 100644 index 0000000..d840365 --- /dev/null +++ b/video/hisi/mpp5/System.h @@ -0,0 +1,31 @@ +#ifndef D30EB83D_9A50_4F5F_8549_B0B9F16AE7AD +#define D30EB83D_9A50_4F5F_8549_B0B9F16AE7AD + +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../../core/System.h" + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class System: public Core::System +{ +public: + System(); + ~System(); + +protected: + uint64_t getTimestamp() override; +}; +} +} +#endif diff --git a/video/hisi/mpp5/Util.cpp b/video/hisi/mpp5/Util.cpp new file mode 100644 index 0000000..978e810 --- /dev/null +++ b/video/hisi/mpp5/Util.cpp @@ -0,0 +1,100 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Util.h" +#include +#include +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * TYPEDEFS + ******************************************************************************* + */ +struct CodecIDMapItem +{ + Core::CodecID id; + hi_payload_type pt; +}; + +/* + ******************************************************************************* + * LOCAL VARIABLES + ******************************************************************************* + */ +static const std::array codecMap = {{ + {Core::CodecID::H264, HI_PT_H264}, + {Core::CodecID::H265, HI_PT_H265}, + {Core::CodecID::MJPEG, HI_PT_MJPEG}, + {Core::CodecID::MP3, HI_PT_MP3}, + {Core::CodecID::AAC, HI_PT_AAC}, + {Core::CodecID::G711A, HI_PT_G711A}, + {Core::CodecID::G711U, HI_PT_G711U}, +}}; + +/* + ******************************************************************************* + * FUNCTIONS + ******************************************************************************* + */ + +static hi_payload_type findPayloadType(Core::CodecID id) +{ + for (const auto& m : codecMap) + { + if (id == m.id) + return m.pt; + } + + throw Poco::NotFoundException(Poco::format("Video::MPP5: Can't find %s in codec-map", Core::codecIDToString(id))); +} + +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +hi_audio_bit_width Util::getBitWidth(Core::SampleFormat format) +{ + if (16 == Core::sampleFormatBitWidth(format)) + return HI_AUDIO_BIT_WIDTH_16; + + throw Poco::InvalidArgumentException("MPP5: Only support 16 bit-width", -EINVAL); +} + +hi_audio_snd_mode Util::getSndMode(Core::ChannelLayout layout) +{ + if (Core::ChannelLayout::STEREO == layout) + return HI_AUDIO_SOUND_MODE_STEREO; + + return HI_AUDIO_SOUND_MODE_MONO; +} + +hi_payload_type Util::getPayloadType(Core::CodecID id) +{ + return findPayloadType(id); +} + +uint32_t Util::getChannelId(const std::string& name) +{ + if (name.find("NET0") != std::string::npos) + return 2; + else if (name.find("NET1") != std::string::npos) + return 3; + else if (name.find("NET2") != std::string::npos) + return 4; + else if (name.find("NET3") != std::string::npos) + return 5; + else if (name.find("USB") != std::string::npos) + return 6; + else if (name.find("File") != std::string::npos) + return 7; +} +} +} diff --git a/video/hisi/mpp5/Util.h b/video/hisi/mpp5/Util.h new file mode 100644 index 0000000..d1b989a --- /dev/null +++ b/video/hisi/mpp5/Util.h @@ -0,0 +1,32 @@ +#ifndef CFE2FF99_0E17_46AD_B839_023CAED87EE5 +#define CFE2FF99_0E17_46AD_B839_023CAED87EE5 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../../core/SampleFormat.h" +#include "../../core/CodecID.h" +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Util +{ +public: + static hi_audio_bit_width getBitWidth(Core::SampleFormat format); + static hi_audio_snd_mode getSndMode(Core::ChannelLayout layout); + + static hi_payload_type getPayloadType(Core::CodecID id); + static uint32_t getChannelId(const std::string& name); +}; +} +} +#endif diff --git a/video/hisi/mpp5/VideoDecoder.cpp b/video/hisi/mpp5/VideoDecoder.cpp new file mode 100644 index 0000000..1fa3099 --- /dev/null +++ b/video/hisi/mpp5/VideoDecoder.cpp @@ -0,0 +1,1000 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoDecoder.h" +#include "ResourceManager.h" +#include "Util.h" +#include "Logger.h" +#include "util/Task.h" +#include "util/Math.h" +#include "../../core/Demuxer.h" +#include "../../core/System.h" +#include "../../core/Notifications.h" +#include "../v4l2/Input.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * 追帧(catch-up)阈值配置 + * 说明:所有阈值均以"队列中待解码视频包个数"为单位。 + * 视频典型帧率 30fps,每包约 33ms,队列上限 300,约 10s 缓冲。 + ******************************************************************************* + */ +namespace +{ +constexpr int V_CATCH_UP_ENTER = 10; +constexpr int V_CATCH_UP_EXIT = 2; +} +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoDecoder::Pipe::Pipe(const std::string& name, uint32_t id) + : MPPChannel((uint32_t)HI_ID_VPSS, id, 0), _name(Poco::format("%s(VPSS#%u)", name, id)), _width(0), _height(0) +{ +} + +VideoDecoder::Pipe::~Pipe() +{ +} + +void VideoDecoder::Pipe::start(uint32_t width, uint32_t height) +{ + _width = width; + _height = height; + + hi_vpss_grp_attr grpAttr = {}; + grpAttr.nr_en = HI_FALSE; + grpAttr.ie_en = HI_FALSE; + grpAttr.dci_en = HI_FALSE; + grpAttr.buf_share_en = HI_FALSE; + grpAttr.buf_share_chn = 0; + grpAttr.mcf_en = HI_FALSE; + grpAttr.max_width = width; + grpAttr.max_height = height; + grpAttr.max_dei_width = width; + grpAttr.max_dei_height = height; + grpAttr.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420; + grpAttr.dynamic_range = HI_DYNAMIC_RANGE_SDR8; + grpAttr.frame_rate.src_frame_rate = -1; + grpAttr.frame_rate.dst_frame_rate = -1; + grpAttr.dei_mode = HI_VPSS_DEI_MODE_OFF; + + int rc = hi_mpi_vpss_create_grp(groupID(), &grpAttr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Create group FAIL: %d(%#x)", name(), rc, (uint32_t)rc), + rc); + + hi_mpi_vpss_reset_grp(groupID()); + rc = hi_mpi_vpss_start_grp(groupID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Start group FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + hi_vpss_chn_attr chnAttr = {}; + chnAttr.chn_mode = HI_VPSS_CHN_MODE_USER; + + chnAttr.width = width; + chnAttr.height = height; + chnAttr.video_format = HI_VIDEO_FORMAT_LINEAR; + chnAttr.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_422; + chnAttr.dynamic_range = HI_DYNAMIC_RANGE_SDR8; + chnAttr.compress_mode = HI_COMPRESS_MODE_NONE; + + chnAttr.frame_rate.src_frame_rate = -1; + chnAttr.frame_rate.dst_frame_rate = -1; + chnAttr.mirror_en = HI_FALSE; + chnAttr.flip_en = HI_FALSE; + chnAttr.depth = 0; + chnAttr.aspect_ratio.mode = HI_ASPECT_RATIO_NONE; + rc = hi_mpi_vpss_set_chn_attr(groupID(), channelID(), &chnAttr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: Set channel#%u attr FAIL: %d(%#x)", name(), channelID(), rc, (uint32_t)rc), rc); + + rc = hi_mpi_vpss_enable_chn(groupID(), channelID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: Enable channel#%u FAIL: %d(%#x)", name(), channelID(), rc, (uint32_t)rc), rc); +} + +void VideoDecoder::Pipe::stop() +{ + int rc = hi_mpi_vpss_disable_chn(groupID(), channelID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: Disable channel#%u FAIL: %d(%#x)", name(), channelID(), rc, (uint32_t)rc), rc); + + rc = hi_mpi_vpss_stop_grp(groupID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Stop group FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + rc = hi_mpi_vpss_destroy_grp(groupID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Destroy group FAIL: %d(%#x)", name(), rc, (uint32_t)rc), + rc); +} + +void VideoDecoder::Pipe::write(VideoFrame& frame, uint64_t pts, uint32_t timeref, bool isFrozen, + hi_video_frame_info& frameX) +{ + hi_video_frame_info* pInfo = isFrozen ? (hi_video_frame_info*)&frameX : (hi_video_frame_info*)frame.info(); + pInfo->video_frame.pts = pts; + pInfo->video_frame.time_ref = timeref; + int rc = hi_mpi_vpss_send_frame(groupID(), pInfo, 100); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Send frame FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); +} + +uint32_t VideoDecoder::Pipe::groupID() const +{ + return deviceID(); +} + +const std::string& VideoDecoder::Pipe::name() const +{ + return _name; +} + +const uint32_t VideoDecoder::Pipe::width() +{ + return _width; +} + +const uint32_t VideoDecoder::Pipe::height() +{ + return _height; +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Core::CodecCapabilityManager VideoDecoder::_vdhCapabilityManager(1920 * 1080 * 30 * 6); +Core::CodecCapabilityManager VideoDecoder::_jpgdCapabilityManager(4096 * 2160 * 30); +static const int poolId = 0; +VideoDecoder::VideoDecoder(const std::string& name) + : MPPChannel((uint32_t)HI_ID_VDEC, 0, ResourceManager::allocVDECChannelID()), + Core::VideoDecoder(Poco::format("%s(MPP5::VDEC#%u)", name, channelID())), + _handle(Core::CodecCapabilityManager::INVALID_HANDLE), + _pipe(Core::VideoDecoder::name(), ResourceManager::allocVPSSGroupID()), _synchronizer(name + "/sync"), + _channelId(Util::getChannelId(name)), + + _lastStatus(false), _catchUpEnabled(name.find("NET") != std::string::npos ? true : false), _isCatchingUp(false) +{ + setWidth(NO_SIGNAL_YUV_WIDTH); + setHeight(NO_SIGNAL_YUV_HEIGHT); + setFrameRate(30); +} + +VideoDecoder::~VideoDecoder() +{ + ResourceManager::freeVPSSGroupID(_pipe.groupID()); + ResourceManager::freeVENCChannelID(channelID()); + releaseCapabilityHandle(); +} + +void VideoDecoder::start() +{ + task().run( + [this](std::atomic& isCancelled) -> void + { + _pipe.start(NO_SIGNAL_YUV_WIDTH, NO_SIGNAL_YUV_HEIGHT); + serialCodeWatcher().reset(); + while (!isCancelled) + { + try + { + Core::Packet::Ptr pPacket = tryReadPacket(3000, true); + + if (pPacket.isNull()) + { + serialCodeWatcher().reset(); + pPacket = runExceptionProcedure(isCancelled); + } + + while (!isCancelled && !pPacket.isNull() && !handleSerialCode(pPacket->serialCode())) + { + serialCodeWatcher().reset(); + pPacket = runExceptionProcedure(isCancelled); + } + + if (!isCancelled && !pPacket.isNull()) + decodePacket(pPacket, isCancelled); + } + catch (Poco::Exception& e) + { + logger().error("%s: %s", name(), e.displayText()); + } + catch (std::exception& e) + { + logger().error("%s: %s", name(), std::string(e.what())); + } + } + + releaseDecoder(true); + _pipe.stop(); + }); +} + +void VideoDecoder::stop() +{ + task().cancel(); +} + +void VideoDecoder::initialize() +{ +} + +void VideoDecoder::deinitialize() +{ +} + +void VideoDecoder::onConfigChanged(ParameterNames& names) +{ + if (names.exists(_SignalLost)) + { + } +} + +Core::Frame::Ptr VideoDecoder::decode() +{ + return Core::Frame::Ptr(); +} + +void VideoDecoder::bind(const MPPChannel& next) +{ + _pipe.bind(next); +} + +void VideoDecoder::unbind(const MPPChannel& next) +{ + _pipe.unbind(next); +} + +void VideoDecoder::sendUserFrame(VideoFrame::Ptr pFrame, uint64_t duration, uint32_t& timeref) +{ + uint64_t now = Core::System::timestamp(); + _pipe.write(*pFrame, now, timeref, _frozenPlay, _frameX); + timeref += 2; + while (Core::System::timestamp() - now < duration) + ::usleep(1000); +} + +void VideoDecoder::runSendUserFrame(VideoFrame::Ptr pFrame, std::function condition) +{ + uint64_t duration = (float)1000000 / getFrameRate(); + + while (condition()) + { + sendUserFrame(pFrame, duration, _frameTimeRef); + _frameTimeRef += 2; + } +} + +void VideoDecoder::runSignalLost(std::atomic& isCancelled) +{ + releaseDecoder(); + + std::atomic isSignalLost(true); + ::Util::Task probeTask; + probeTask.run( + [&](std::atomic& isCancelled) -> void + { + while (!isCancelled) + { + if (!tryReadPacket(1000, false).isNull()) + { + isSignalLost = false; + break; + } + } + }); + + VideoFrame::Ptr pUserFrame = + VideoFrame::create(NO_SIGNAL_YUV_FORMAT, NO_SIGNAL_YUV_WIDTH, NO_SIGNAL_YUV_HEIGHT, NO_SIGNAL_YUV_PATH); + runSendUserFrame(pUserFrame, [&]() -> bool { return (!isCancelled && isSignalLost); }); + probeTask.cancel(); +} + +void VideoDecoder::runFault(std::atomic& isCancelled) +{ + releaseDecoder(); + + std::atomic isFaultClean(false); + ::Util::Task tryTask; + tryTask.run( + [&](std::atomic& isCancelled) -> void + { + while (!isCancelled) + { + Core::Packet::Ptr pPacket = tryReadPacket(1000, true); + if (pPacket.isNull()) + continue; + + if (handleSerialCode(pPacket->serialCode())) + { + isFaultClean = true; + break; + } + } + }); + + VideoFrame::Ptr pUserFrame = + VideoFrame::create(NO_SIGNAL_YUV_FORMAT, NO_SIGNAL_YUV_WIDTH, NO_SIGNAL_YUV_HEIGHT, NO_SIGNAL_YUV_PATH); + runSendUserFrame(pUserFrame, [&]() -> bool { return (!isCancelled && !isFaultClean); }); + tryTask.cancel(); +} + +Core::Packet::Ptr VideoDecoder::runExceptionProcedure(std::atomic& isCancelled) +{ + releaseDecoder(); + + std::atomic isSignalLost(true); + Core::Packet::Ptr pPacket = nullptr; + ::Util::Task task; + task.run( + [&](std::atomic& isCancelled) -> void + { + while (!isCancelled) + { + pPacket = tryReadPacket(1000, false); + if (!pPacket.isNull()) + { + isSignalLost = false; + break; + } + } + }); + + VideoFrame::Ptr pUserFrame = + VideoFrame::create(NO_SIGNAL_YUV_FORMAT, NO_SIGNAL_YUV_WIDTH, NO_SIGNAL_YUV_HEIGHT, NO_SIGNAL_YUV_PATH); + runSendUserFrame(pUserFrame, [&]() -> bool { return (!isCancelled && isSignalLost); }); + task.cancel(); + + return pPacket; +} + +bool VideoDecoder::handleSerialCode(const Core::SerialCode& code) +{ + bool errorOccurred = false; + + if (serialCodeWatcher().isChanged(code, true)) + { + try + { + handlePreviousChanged(); + } + catch (Poco::Exception& e) + { + logger().error("%s: %s", name(), e.displayText()); + errorOccurred = true; + } + catch (std::exception& e) + { + logger().error("%s: %s", name(), std::string(e.what())); + errorOccurred = true; + } + } + + if (errorOccurred) + releaseDecoder(); + + return !errorOccurred; +} + +void VideoDecoder::handlePreviousChanged() +{ + VideoComponent::Ptr pPreviousVideo = nullptr; + Core::CodecID codecID = Core::CodecID::NB; + + auto pPrevious = previousComponents().first(); + auto pDemuxer = pPrevious.cast(); + if (pDemuxer) + { + auto pEncoder = pDemuxer->videoEncoder(); + if (pEncoder) + { + pPreviousVideo = pEncoder; + codecID = pEncoder->getCodecID(); + } + } + + if (!pPreviousVideo) + { + auto V4L2Input = pPrevious.cast(); + if (V4L2Input) + { + pPreviousVideo = V4L2Input; + codecID = V4L2Input->getCodecID(); + } + } + + if (!pPreviousVideo) + throw Poco::NotImplementedException(Poco::format("Unsupported previous component: %s", pPrevious->name())); + + const auto width = pPreviousVideo->getWidth(); + const auto height = pPreviousVideo->getHeight(); + const auto framteRate = pPreviousVideo->getFrameRate(); + + setWidth(width); + setHeight(height); + setFrameRate(framteRate); + + ParameterNames parameters; + config().getChangedParameters(parameters); + + if (_pipe.width() != width || _pipe.height() != height) + { + _pipe.stop(); + _pipe.start(width, height); + serialCodeManager().increment(); + } + + hi_vdec_chn_status status; + int rc = hi_mpi_vdec_query_status(channelID(), &status); + logger().debug("%s: Query status: %#x, parameters changed:%d, %d, %d", name(), (uint32_t)rc, + (int)!parameters.empty(), (int)status.type, (int)MPP5::Util::getPayloadType(codecID)); + if (HI_ERR_VDEC_UNEXIST == rc || !parameters.empty() || status.type != MPP5::Util::getPayloadType(codecID)) + { + releaseDecoder(); + createDecoder(width, height, framteRate, codecID); + notifyNextComponents(sharedFromThis(), new Core::ParameterChangedNotification(parameters)); + } +} + +void VideoDecoder::decodePacket(Core::Packet::Ptr pPacket, std::atomic& isCancelled) +{ + hi_vdec_stream st = {}; + st.end_of_frame = HI_FALSE; + st.end_of_stream = HI_FALSE; + st.need_display = HI_TRUE; + st.addr = (hi_u8*)pPacket->data(); + st.len = pPacket->length(); + st.pts = pPacket->pts(); + int rc; + do + { + rc = hi_mpi_vdec_send_stream(channelID(), &st, 1000); + } while (!isCancelled && rc == HI_ERR_VDEC_BUF_FULL); +} + +void VideoDecoder::createDecoder(uint32_t width, uint32_t height, uint32_t frameRate, Core::CodecID codecID) +{ + logger().debug("%s: Initialize decoder: %s %ux%u@%u", name(), Core::codecIDToString(codecID), width, height, + frameRate); + + hi_pic_buf_attr pic; + pic.width = width; + pic.height = height; + pic.align = HI_DEFAULT_ALIGN; + pic.bit_width = HI_DATA_BIT_WIDTH_8; + pic.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420; + pic.compress_mode = HI_COMPRESS_MODE_NONE; + + hi_vdec_chn_attr attr; + attr.type = MPP5::Util::getPayloadType(codecID); + attr.mode = HI_VDEC_SEND_MODE_FRAME; + attr.pic_width = width; + attr.pic_height = height; + attr.stream_buf_size = attr.pic_width * attr.pic_height * 3 / 2; + attr.frame_buf_size = hi_vdec_get_pic_buf_size(attr.type, &pic); + attr.frame_buf_cnt = 33; + attr.video_attr.ref_frame_num = 16; + attr.video_attr.temporal_mvp_en = HI_TRUE; + attr.video_attr.tmv_buf_size = hi_vdec_get_tmv_buf_size(attr.type, width, height); + int rc = hi_mpi_vdec_create_chn(channelID(), &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Create chn FAIL: %#x", name(), (uint32_t)rc), rc); + + hi_mpi_vdec_reset_chn(channelID()); + + hi_vdec_chn_param param; + hi_mpi_vdec_get_chn_param(channelID(), ¶m); + param.type = attr.type; + param.display_frame_num = 2; + switch (param.type) + { + case HI_PT_H264: + case HI_PT_H265: + param.video_param.composite_dec_en = HI_FALSE; + param.video_param.slice_input_en = HI_FALSE; + param.video_param.dec_mode = HI_VIDEO_DEC_MODE_IPB; + param.video_param.out_order = HI_VIDEO_OUT_ORDER_DISPLAY; + param.video_param.compress_mode = HI_COMPRESS_MODE_NONE; + param.video_param.video_format = HI_VIDEO_FORMAT_TILE_64x16; + break; + + case HI_PT_JPEG: + case HI_PT_MJPEG: + + param.pic_param.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420; + + param.pic_param.alpha = 255; + break; + + default: + break; + } + + rc = hi_mpi_vdec_set_chn_param(channelID(), ¶m); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Set param FAIL: %#x", name(), (uint32_t)rc), rc); + + rc = hi_mpi_vdec_set_display_mode(channelID(), HI_VIDEO_DISPLAY_MODE_PLAYBACK); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Set display mode FAIL: %#x", name(), (uint32_t)rc), rc); + + rc = hi_mpi_vdec_start_recv_stream(channelID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Start recv stream FAIL: %#x", name(), (uint32_t)rc), rc); + + _postTaskControl = PostTaskControl::RUNNING; + _postTask.run( + [this](std::atomic& isCancelled) -> void + { + _isCatchingUp = false; + while (!isCancelled) + { + hi_video_frame_info frame; + bool noAudio = false; + int rc = hi_mpi_vdec_get_frame(channelID(), &frame, NULL, 100); + if (rc < 0) + { + logger().warning("%s: Read decoded frame FAIL: %#x", name(), (uint32_t)rc); + if (_postTaskControl == PostTaskControl::EXIT || _postTaskControl == PostTaskControl::INSTANT_EXIT) + break; + + if (!_frozenPlay) + continue; + else + noAudio = true; + } + + if (_buttonPressed != _frozenPlay) + { + _buttonPressed = _frozenPlay; + if (_frozenPlay) + { + saveSingleFrame(frame, "/customer/resources/frozen" + std::to_string(_channelId)); + loadVideoFrame("/customer/resources/frozen" + std::to_string(_channelId), _frameX); + } + else + { + frameRelease(_frameX); + std::string dir = "/customer/resources/frozen" + std::to_string(_channelId); + std::string cmd = "rm -rf " + dir; + if (system(cmd.c_str()) != 0) + logger().warning("rm -rf %s failed", dir.c_str()); + } + } + + if (_postTaskControl == PostTaskControl::EXIT) + { + hi_vdec_chn_status status; + if (hi_mpi_vdec_query_status(channelID(), &status) == HI_SUCCESS && status.left_decoded_frames == 0) + { + logger().debug("%s: No decoded frame, exit.", name()); + isCancelled = true; + } + } + else if (_postTaskControl == PostTaskControl::INSTANT_EXIT) + { + logger().debug("%s: Instant exit.", name()); + isCancelled = true; + } + + auto pAudio = getAssociatedAudio(); + if (pAudio.isNull() || pAudio->getTimeline() == 0 || !pAudio->started() || noAudio) + { + if (!noAudio) + { + updateCatchUpState(); + if (!_isCatchingUp) + _synchronizer.syncFrame(frame.video_frame.pts); + } + } + else + { + int64_t diff = frame.video_frame.pts - pAudio->getTimeline(); + + if (diff > 0) + { + if (diff > 1000000000000) + diff = 1; + else if (diff > 2000000) + { + logger().warning("%s: Video exceeds audio timeline by %Ld microseconds (%.1f seconds)", + name(), diff, diff / 1000000.0); + diff = 2000000; + } + std::this_thread::sleep_for(std::chrono::microseconds(diff)); + } + } + + frame.video_frame.pts = Core::System::timestamp(); + frame.video_frame.time_ref = _frameTimeRef; + _frameTimeRef += 2; + _frameX.video_frame.pts = Core::System::timestamp(); + _frameX.video_frame.time_ref = _frameTimeRef; + + if (_frozenPlay) + hi_mpi_vpss_send_frame(_pipe.groupID(), &_frameX, 0); + else + { + hi_mpi_vpss_send_frame(_pipe.groupID(), &frame, 0); + } + hi_mpi_vdec_release_frame(channelID(), &frame); + } + }); + setSignalLost(false); + commit(); +} + +void VideoDecoder::releaseDecoder(bool instant) +{ + logger().debug("%s: Deinitialize decoder...", name()); + hi_vdec_chn_status status; + if (hi_mpi_vdec_query_status(channelID(), &status) == HI_SUCCESS) + { + logger().debug("%s: Decoded frame left: %u", name(), status.left_decoded_frames); + } + + _postTaskControl = instant ? PostTaskControl::INSTANT_EXIT : PostTaskControl::EXIT; + + int wait = 0; + while (_postTask.isRunning()) + { + Poco::Thread::sleep(500); + if (wait++ >= 10) + { + _postTask.cancel(); + break; + } + } + + releaseCapabilityHandle(); + + hi_mpi_vdec_stop_recv_stream(channelID()); + hi_mpi_vdec_destroy_chn(channelID()); + + setSignalLost(true); + commit(); + logger().debug("%s: Deinitialize decoder complete", name()); +} + +Core::Packet::Ptr VideoDecoder::tryReadPacket(uint32_t timeout, bool shuldLog) +{ + Core::Packet::Ptr pPacket; + try + { + pPacket = readPacket(timeout); + } + catch (Poco::Exception& e) + { + if (shuldLog) + logger().error("%s: %s", name(), e.displayText()); + } + catch (std::exception& e) + { + if (shuldLog) + logger().error("%s: %s", name(), std::string(e.what())); + } + + return pPacket; +} + +void VideoDecoder::releaseCapabilityHandle() +{ +} + +void VideoDecoder::initModule() +{ + hi_vdec_mod_param param; + hi_mpi_vdec_get_mod_param(¶m); + + param.vb_src = HI_VB_SRC_PRIVATE; + + param.mini_buf_mode = 0; + + param.pic_mod_param.capacity_strategy = HI_VDEC_CAPACITY_STRATEGY_BY_CHN; + auto rc = hi_mpi_vdec_set_mod_param(¶m); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("MPP5::VDEC: Set module param FAIL: %u(%#x)", (uint32_t)rc, (uint32_t)rc)); +} + +void VideoDecoder::deinitModule() +{ +} + +void VideoDecoder::reset() +{ +} +void VideoDecoder::frameCopy(hi_video_frame_info& from, hi_video_frame_info& to) +{ + uint32_t imageBytes, lumaBytes, chromaBytes; + uint32_t stride = from.video_frame.stride[0]; + uint32_t height = from.video_frame.height; + + switch (from.video_frame.pixel_format) + { + case HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420: + case HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420: + imageBytes = stride * ::Util::Math::alignup(height, 16) * 3 / 2; + lumaBytes = stride * ::Util::Math::alignup(height, 16); + chromaBytes = lumaBytes >> 1; + break; + + case HI_PIXEL_FORMAT_YVU_SEMIPLANAR_422: + case HI_PIXEL_FORMAT_YUV_SEMIPLANAR_422: + imageBytes = stride * ::Util::Math::alignup(height, 16) * 2; + lumaBytes = stride * ::Util::Math::alignup(height, 16); + chromaBytes = lumaBytes; + break; + + default: + throw Poco::NotImplementedException("Unsupported pixel format"); + } + + hi_vb_blk handle = hi_mpi_vb_get_blk(poolId, imageBytes, "anonymous"); + if (HI_VB_INVALID_HANDLE == handle) + throw Poco::RuntimeException( + Poco::format("VideoFrame: Failed to get VB block: %u bytes @POOL#%d", imageBytes, (int)poolId)); + + hi_phys_addr_t phyAddr = hi_mpi_vb_handle_to_phys_addr(handle); + + if (!phyAddr) + throw Poco::RuntimeException("VideoFrame: Failed to get VB block phys-addr"); + + uint8_t* pVirAddr = (uint8_t*)hi_mpi_sys_mmap(phyAddr, imageBytes); + if (!pVirAddr) + throw Poco::RuntimeException("VideoFrame: Failed to map VB block"); + + memset(&to, 0, sizeof(hi_video_frame_info)); + to.mod_id = HI_ID_USER; + to.pool_id = poolId; + + memcpy(&to.video_frame, &from.video_frame, sizeof(hi_video_frame)); + + to.video_frame.phys_addr[0] = phyAddr; + to.video_frame.phys_addr[1] = (phyAddr + lumaBytes); + + to.video_frame.virt_addr[0] = (hi_void*)pVirAddr; + to.video_frame.virt_addr[1] = (hi_void*)(pVirAddr + lumaBytes); + + to.video_frame.time_ref = 0; + to.video_frame.pts = 0; + to.video_frame.frame_flag = 0; + + to.video_frame.user_data[0] = imageBytes; + to.video_frame.user_data[1] = (hi_u64)pVirAddr; + + if (lumaBytes > 0) + { + hi_void* pFromVirAddr = hi_mpi_sys_mmap(from.video_frame.phys_addr[0], lumaBytes); + memcpy(to.video_frame.virt_addr[0], pFromVirAddr, lumaBytes); + hi_mpi_sys_munmap(pFromVirAddr, lumaBytes); + } + + if (chromaBytes > 0) + { + hi_void* pFromVirAddr = hi_mpi_sys_mmap(from.video_frame.phys_addr[1], chromaBytes); + memcpy(to.video_frame.virt_addr[1], pFromVirAddr, chromaBytes); + hi_mpi_sys_munmap(pFromVirAddr, chromaBytes); + } +} + +void VideoDecoder::frameRelease(hi_video_frame_info& frame) +{ + uint32_t imageBytes = frame.video_frame.user_data[0]; + hi_void* pVirAddr = (hi_void*)frame.video_frame.user_data[1]; + + if (imageBytes && pVirAddr) + { + hi_sys_virt_mem_info info; + + if (hi_mpi_sys_get_virt_mem_info(pVirAddr, &info) == HI_SUCCESS) + { + hi_mpi_sys_munmap(pVirAddr, imageBytes); + hi_mpi_vb_release_blk(hi_mpi_vb_phys_addr_to_handle(info.phys_addr)); + } + + frame.video_frame.user_data[0] = 0; + frame.video_frame.user_data[1] = 0; + } +} + +bool VideoDecoder::saveSingleFrame(hi_video_frame_info& frame, const std::string& dir_path) +{ + mkdir("/customer/resources", 0755); + mkdir(dir_path.c_str(), 0755); + + uint32_t stride = frame.video_frame.stride[0]; + uint32_t width = frame.video_frame.width; + uint32_t height = frame.video_frame.height; + uint32_t pixel_format = frame.video_frame.pixel_format; + uint32_t lumaBytes, chromaBytes, imageBytes; + uint32_t aligned_height = ::Util::Math::alignup(height, 16); + + switch (pixel_format) + { + case HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420: + case HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420: + lumaBytes = stride * aligned_height; + chromaBytes = lumaBytes >> 1; + imageBytes = lumaBytes + chromaBytes; + break; + case HI_PIXEL_FORMAT_YVU_SEMIPLANAR_422: + case HI_PIXEL_FORMAT_YUV_SEMIPLANAR_422: + lumaBytes = stride * aligned_height; + chromaBytes = lumaBytes; + imageBytes = lumaBytes + chromaBytes; + break; + default: + throw Poco::NotImplementedException("Unsupported pixel format"); + } + + std::string yuv_path = dir_path + "/frame.yuv"; + FILE* fp_yuv = fopen(yuv_path.c_str(), "wb"); + if (!fp_yuv) + return false; + + hi_void* pLuma = hi_mpi_sys_mmap(frame.video_frame.phys_addr[0], lumaBytes); + if (pLuma) + { + fwrite(pLuma, 1, lumaBytes, fp_yuv); + hi_mpi_sys_munmap(pLuma, lumaBytes); + } + + hi_void* pChroma = hi_mpi_sys_mmap(frame.video_frame.phys_addr[1], chromaBytes); + if (pChroma) + { + fwrite(pChroma, 1, chromaBytes, fp_yuv); + hi_mpi_sys_munmap(pChroma, chromaBytes); + } + fclose(fp_yuv); + + std::string info_path = dir_path + "/frame.bin"; + int fd = open(info_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) + return false; + + hi_video_frame_info frame_to_save = frame; + + for (int i = 0; i < OT_MAX_COLOR_COMPONENT; i++) + { + frame_to_save.video_frame.virt_addr[i] = NULL; + frame_to_save.video_frame.header_virt_addr[i] = NULL; + } + + uint32_t extra_info[4] = {lumaBytes, chromaBytes, imageBytes, aligned_height}; + + write(fd, extra_info, sizeof(extra_info)); + write(fd, &frame_to_save, sizeof(hi_video_frame_info)); + close(fd); + sync(); + logger().debug("Saved: %s and %s", yuv_path, info_path); + return true; +} + +bool VideoDecoder::loadVideoFrame(const std::string& dir_path, hi_video_frame_info& frame) +{ + std::string info_path = dir_path + "/frame.bin"; + int fd = open(info_path.c_str(), O_RDONLY); + if (fd < 0) + return false; + + uint32_t extra_info[4] = {0}; + read(fd, extra_info, sizeof(extra_info)); + + uint32_t lumaBytes = extra_info[0]; + uint32_t chromaBytes = extra_info[1]; + uint32_t imageBytes = extra_info[2]; + uint32_t aligned_height = extra_info[3]; + + hi_video_frame_info loaded_frame; + memset(&loaded_frame, 0, sizeof(loaded_frame)); + read(fd, &loaded_frame, sizeof(hi_video_frame_info)); + close(fd); + + hi_vb_blk handle = hi_mpi_vb_get_blk(poolId, imageBytes, "loaded_frame"); + if (HI_VB_INVALID_HANDLE == handle) + { + logger().warning("Failed to get VB block: %u bytes @POOL#%d", imageBytes, poolId); + return false; + } + + hi_phys_addr_t phyAddr = hi_mpi_vb_handle_to_phys_addr(handle); + if (!phyAddr) + { + logger().warning("Failed to get VB block phys-addr\n"); + hi_mpi_vb_release_blk(handle); + return false; + } + + uint8_t* pVirAddr = (uint8_t*)hi_mpi_sys_mmap(phyAddr, imageBytes); + if (!pVirAddr) + { + logger().warning("Failed to map VB block"); + hi_mpi_vb_release_blk(handle); + return false; + } + + std::string yuv_path = dir_path + "/frame.yuv"; + FILE* fp_yuv = fopen(yuv_path.c_str(), "rb"); + if (!fp_yuv) + { + logger().warning("Failed to open: %s", yuv_path); + hi_mpi_sys_munmap(pVirAddr, imageBytes); + hi_mpi_vb_release_blk(handle); + return false; + } + + size_t bytes_read = fread(pVirAddr, 1, imageBytes, fp_yuv); + fclose(fp_yuv); + + if (bytes_read != imageBytes) + { + logger().warning("Read incomplete: %zu/%u bytes", bytes_read, imageBytes); + hi_mpi_sys_munmap(pVirAddr, imageBytes); + hi_mpi_vb_release_blk(handle); + return false; + } + + memset(&frame, 0, sizeof(hi_video_frame_info)); + memcpy(&frame.video_frame, &loaded_frame.video_frame, sizeof(hi_video_frame)); + + frame.video_frame.phys_addr[0] = phyAddr; + frame.video_frame.phys_addr[1] = phyAddr + lumaBytes; + frame.video_frame.virt_addr[0] = pVirAddr; + frame.video_frame.virt_addr[1] = (hi_void*)(pVirAddr + lumaBytes); + + frame.video_frame.user_data[0] = imageBytes; + frame.video_frame.user_data[1] = (hi_u64)pVirAddr; + + logger().debug("Loaded frame: %ux%u, pts=%Lu", frame.video_frame.width, frame.video_frame.height, + loaded_frame.video_frame.pts); + + return true; +} + +void VideoDecoder::updateCatchUpState() +{ + if (!_catchUpEnabled) + return; + + hi_vdec_chn_status status; + hi_mpi_vdec_query_status(channelID(), &status); + const int queueSize = status.left_decoded_frames; + + if (!_isCatchingUp && queueSize > V_CATCH_UP_ENTER) + { + _isCatchingUp = true; + logger().information("%s: video catch-up enter (queue=%d)", name(), queueSize); + } + else if (_isCatchingUp && queueSize <= V_CATCH_UP_EXIT) + { + _isCatchingUp = false; + + _synchronizer.reset(); + logger().information("%s: video catch-up exit (queue=%d)", name(), queueSize); + } +} +} +} diff --git a/video/hisi/mpp5/VideoDecoder.h b/video/hisi/mpp5/VideoDecoder.h new file mode 100644 index 0000000..edfcbd1 --- /dev/null +++ b/video/hisi/mpp5/VideoDecoder.h @@ -0,0 +1,123 @@ +#ifndef CF63EDDC_B464_4F43_9D68_4B200F07DD82 +#define CF63EDDC_B464_4F43_9D68_4B200F07DD82 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "MPPChannel.h" +#include "VideoFrame.h" +#include "../../core/VideoDecoder.h" +#include "../../core/CodecCapabilityManager.h" +#include "../../core/FrameSynchronizer.h" +#include +#include "util/Broadcast.h" + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class VideoDecoder: public MPPChannel, + public Core::VideoDecoder +{ +private: + class Pipe: public MPPChannel + { + public: + Pipe(const std::string& name, uint32_t id); + ~Pipe(); + + public: + void start(uint32_t width, uint32_t height); + void stop(); + void write(VideoFrame& frame, uint64_t pts, uint32_t timeref, bool isFrozen, hi_video_frame_info& frameX); + + uint32_t groupID() const; + const std::string& name() const; + + const uint32_t width(); + const uint32_t height(); + + private: + const std::string _name; + uint32_t _width; + uint32_t _height; + }; + + enum class PostTaskControl + { + RUNNING, + EXIT, + INSTANT_EXIT, + }; + +public: + VideoDecoder(const std::string& name); + ~VideoDecoder(); + void reset() override; + +protected: + void start() override; + void stop() override; + void initialize() override; + void deinitialize() override; + void onConfigChanged(ParameterNames& names) override; + + Core::Frame::Ptr decode() override; + + void bind(const MPPChannel& next) override; + void unbind(const MPPChannel& next) override; + + void sendUserFrame(VideoFrame::Ptr pFrame, uint64_t duration, uint32_t& timeref); + void runSendUserFrame(VideoFrame::Ptr pFrame, std::function condition); + void runSignalLost(std::atomic& isCancelled); + void runFault(std::atomic& isCancelled); + Core::Packet::Ptr runExceptionProcedure(std::atomic& isCancelled); + bool handleSerialCode(const Core::SerialCode& code); + void handlePreviousChanged(); + void decodePacket(Core::Packet::Ptr pPacket, std::atomic& isCancelled); + void createDecoder(uint32_t width, uint32_t height, uint32_t frameRate, Core::CodecID codecID); + void releaseDecoder(bool instant = false); + Core::Packet::Ptr tryReadPacket(uint32_t timeout, bool shuldLog); + void releaseCapabilityHandle(); + void frameCopy(hi_video_frame_info& from, hi_video_frame_info& to); + void frameRelease(hi_video_frame_info& frame); + bool saveSingleFrame(hi_video_frame_info& frame, const std::string& dir_path); + bool loadVideoFrame(const std::string& dir_path, hi_video_frame_info& frame); + +private: + Core::CodecCapabilityManager::Handle _handle; + Pipe _pipe; + ::Util::Task _postTask; + std::atomic _postTaskControl; + uint32_t _frameTimeRef = 2; + Core::FrameSynchronizer _synchronizer; + const uint32_t _channelId; + hi_video_frame_info _frameX; + bool _frozenPlay; + bool _firstPic; + bool _buttonPressed; + bool _lastStatus; + + bool _catchUpEnabled; + bool _isCatchingUp; + void updateCatchUpState(); + + PROPERTY_DECLARE(UsingCapability, Core::CodecID, Core::CodecID::NB, private, private) + +public: + static void initModule(); + static void deinitModule(); + +private: + static Core::CodecCapabilityManager _vdhCapabilityManager; + static Core::CodecCapabilityManager _jpgdCapabilityManager; +}; +} +} +#endif diff --git a/video/hisi/mpp5/VideoEncoder.cpp b/video/hisi/mpp5/VideoEncoder.cpp new file mode 100644 index 0000000..778969d --- /dev/null +++ b/video/hisi/mpp5/VideoEncoder.cpp @@ -0,0 +1,682 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoEncoder.h" +#include "ResourceManager.h" +#include "Logger.h" +#include "util/Math.h" +#include + +using namespace Util; + +/* + ******************************************************************************* + * CONSTANTS + ******************************************************************************* + */ +#define MIN_ALIGN 2 + +/* + ******************************************************************************* + * MACROS + ******************************************************************************* + */ +#define __set_basic(attr, gop, sfr, dfr) \ + attr.gop = (gop); \ + attr.src_frame_rate = (sfr); \ + attr.dst_frame_rate = (dfr); + +#define __set_cbr(attr, gop, st, sfr, dfr, br) \ + __set_basic(attr, gop, sfr, dfr) attr.stats_time = (st); \ + attr.bit_rate = (br); + +#define __set_vbr(attr, gop, st, sfr, dfr, br) \ + __set_basic(attr, gop, sfr, dfr) attr.stats_time = (st); \ + attr.max_bit_rate = (br); + +#define __set_avbr __set_vbr + +#define __set_qvbr(attr, gop, st, sfr, dfr, br) \ + __set_basic(attr, gop, sfr, dfr) attr.stats_time = (st); \ + attr.target_bit_rate = (br); + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoEncoder::Preprocessor::Preprocessor(const std::string& name, uint32_t id) + : MPPChannel((uint32_t)HI_ID_VPSS, id, 0), _name(Poco::format("%s(VPSS#%u)", name, id)) +{ +} + +VideoEncoder::Preprocessor::~Preprocessor() +{ +} + +void VideoEncoder::Preprocessor::start(uint32_t iwidth, uint32_t iheight, uint32_t owidth, uint32_t oheight) +{ + hi_vpss_grp_attr attr = {}; + attr.nr_en = HI_FALSE; + attr.ie_en = HI_FALSE; + attr.dci_en = HI_FALSE; + attr.buf_share_en = HI_FALSE; + attr.buf_share_chn = 0; + attr.mcf_en = HI_FALSE; + attr.max_width = iwidth; + attr.max_height = iheight; + attr.max_dei_width = iwidth; + attr.max_dei_height = iheight; + attr.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420; + attr.dynamic_range = HI_DYNAMIC_RANGE_SDR8; + attr.frame_rate.src_frame_rate = -1; + attr.frame_rate.dst_frame_rate = -1; + attr.dei_mode = HI_VPSS_DEI_MODE_OFF; + + int rc = hi_mpi_vpss_create_grp(groupID(), &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Create group FAIL: %d(%#x)", name(), rc, (uint32_t)rc), + rc); + + hi_mpi_vpss_reset_grp(groupID()); + rc = hi_mpi_vpss_start_grp(groupID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Start group FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + hi_vpss_chn_attr chnAttr = {}; + chnAttr.chn_mode = HI_VPSS_CHN_MODE_USER; + + chnAttr.width = owidth; + chnAttr.height = oheight; + chnAttr.video_format = HI_VIDEO_FORMAT_LINEAR; + chnAttr.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420; + chnAttr.dynamic_range = HI_DYNAMIC_RANGE_SDR8; + chnAttr.compress_mode = HI_COMPRESS_MODE_NONE; + + chnAttr.frame_rate.src_frame_rate = -1; + chnAttr.frame_rate.dst_frame_rate = -1; + chnAttr.mirror_en = HI_FALSE; + chnAttr.flip_en = HI_FALSE; + chnAttr.depth = 0; + chnAttr.aspect_ratio.mode = HI_ASPECT_RATIO_NONE; + rc = hi_mpi_vpss_set_chn_attr(groupID(), channelID(), &chnAttr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: Set channel#%u attr FAIL: %d(%#x)", name(), channelID(), rc, (uint32_t)rc), rc); + + rc = hi_mpi_vpss_enable_chn(groupID(), channelID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: Enable channel#%u FAIL: %d(%#x)", name(), channelID(), rc, (uint32_t)rc), rc); +} + +void VideoEncoder::Preprocessor::stop() +{ + int rc = hi_mpi_vpss_disable_chn(groupID(), channelID()); + if (rc != HI_SUCCESS) + logger().error("%s: Disable channel#%u FAIL: %d(%#x)", name(), channelID(), rc, (uint32_t)rc); + + rc = hi_mpi_vpss_stop_grp(groupID()); + if (rc != HI_SUCCESS) + logger().error("%s: Stop group FAIL: %d(%#x)", name(), rc, (uint32_t)rc); + + rc = hi_mpi_vpss_destroy_grp(groupID()); + if (rc != HI_SUCCESS) + logger().error("%s: Destroy group FAIL: %d(%#x)", name(), rc, (uint32_t)rc); +} + +uint32_t VideoEncoder::Preprocessor::groupID() const +{ + return deviceID(); +} + +const std::string& VideoEncoder::Preprocessor::name() const +{ + return _name; +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Core::CodecCapabilityManager VideoEncoder::_capabilityManager(1920 * 1080 * 30 * 4 + 720 * 480 * 30 * 4); + +VideoEncoder::VideoEncoder(Core::CodecID codecID) + : MPPChannel((uint32_t)HI_ID_VENC, 0, ResourceManager::allocVENCChannelID()), + Core::VideoEncoder(Poco::format("MPP5::VENC#%u(%s)", channelID(), Core::codecIDToString(codecID)), codecID), + _handle(Core::CodecCapabilityManager::INVALID_HANDLE), _preprocessor(name(), ResourceManager::allocVPSSGroupID()) +{ + registerNotificationHandle(*this, + &VideoEncoder::handlePreviousParameterChanged); +} + +VideoEncoder::~VideoEncoder() +{ + ResourceManager::freeVPSSGroupID(_preprocessor.groupID()); + ResourceManager::freeVENCChannelID(channelID()); + releaseCapabilityHandle(); +} + +void VideoEncoder::start() +{ + if (previousComponents().size() == 0) + throw Poco::BugcheckException(Poco::format("%s: No previous component", name())); + + auto pPrevious = previousComponents().first().cast(); + if (!pPrevious) + throw Poco::BugcheckException(Poco::format("%s: First previous component '%s' convert to 'VideoComponent' FAIL", + name(), previousComponents().first()->name())); + + uint32_t width; + uint32_t height; + getEncodeResolution(pPrevious, width, height); + _preprocessor.start(pPrevious->getWidth(), pPrevious->getHeight(), width, height); + + hi_venc_chn_attr attr = {}; + + attr.venc_attr.max_pic_width = width; + + attr.venc_attr.max_pic_height = height; + + attr.venc_attr.pic_width = width; + + attr.venc_attr.pic_height = height; + + attr.venc_attr.buf_size = + Util::Math::alignup(attr.venc_attr.max_pic_width * attr.venc_attr.max_pic_height * 3 / 2, 64); + + attr.venc_attr.is_by_frame = HI_TRUE; + + switch (getCodecID()) + { + default: + case Core::CodecID::H264: + attr.venc_attr.type = HI_PT_H264; + + switch (getCodecProfile()) + { + case Core::CodecProfile::H264_BASELINE: + attr.venc_attr.profile = 0; + break; + + default: + case Core::CodecProfile::H264_MAIN: + attr.venc_attr.profile = 1; + break; + + case Core::CodecProfile::H264_HIGH: + attr.venc_attr.profile = 2; + break; + + case Core::CodecProfile::H264_SVC_T: + attr.venc_attr.profile = 3; + break; + } + + attr.venc_attr.h264_attr.rcn_ref_share_buf_en = HI_FALSE; + + attr.venc_attr.h264_attr.frame_buf_ratio = 100; + break; + + case Core::CodecID::H265: + attr.venc_attr.type = HI_PT_H265; + + switch (getCodecProfile()) + { + default: + case Core::CodecProfile::H265_MAIN: + attr.venc_attr.profile = 0; + break; + + case Core::CodecProfile::H265_MAIN_10: + attr.venc_attr.profile = 1; + break; + } + + attr.venc_attr.h265_attr.rcn_ref_share_buf_en = HI_FALSE; + + attr.venc_attr.h265_attr.frame_buf_ratio = 100; + } + + setChnAttrRateControl(attr, getCodecID(), getBitrateControl(), getSuitableGOP(), getSuitableSFR(), getSuitableDFR(), + getSuitableBitrate()); + setChnAttrGOPMode(attr, getGOPMode(), getSuitableGOP()); + + int rc = hi_mpi_venc_create_chn(channelID(), &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Create channel FAIL: %#x", name(), (uint32_t)rc), rc); + + rc = hi_mpi_venc_reset_chn(channelID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Reset channel recv FAIL: %#x", name(), (uint32_t)rc), rc); + + hi_venc_start_param param; + + param.recv_pic_num = -1; + rc = hi_mpi_venc_start_chn(channelID(), ¶m); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Start channel recv FAIL: %#x", name(), (uint32_t)rc), rc); + + task().run( + [this](std::atomic& isCancelled) -> void + { + while (!isCancelled) + { + try + { + postToNextComponents(sharedFromThis(), readPacket(1000), Core::MediaType::VIDEO); + } + catch (Poco::Exception& e) + { + logger().error("%s: %s", name(), e.displayText()); + Poco::Thread::sleep(1000); + } + } + }); +} + +void VideoEncoder::stop() +{ + task().cancel(); + + hi_mpi_venc_stop_chn(channelID()); + hi_mpi_venc_reset_chn(channelID()); + hi_mpi_venc_destroy_chn(channelID()); + + _preprocessor.stop(); +} + +void VideoEncoder::initialize() +{ + uint32_t width; + uint32_t height; + getEncodeResolution(getPrevious(), width, height); + + setWidth(width); + setHeight(height); +} + +void VideoEncoder::deinitialize() +{ + releaseCapabilityHandle(); +} + +void VideoEncoder::onConfigChanged(ParameterNames& names) +{ + if (!started()) + return; + + if (names.exists(_CodecProfile) || names.exists(_Width) || names.exists(_Height) || names.exists(_FrameRate)) + { + serialCodeManager().put(_Width, getWidth()); + serialCodeManager().put(_Height, getHeight()); + + stop(); + deinitialize(); + initialize(); + start(); + return; + } + + hi_venc_chn_attr attr; + hi_mpi_venc_get_chn_attr(channelID(), &attr); + if (names.exists(_GOPMode) || names.exists(_GOP) || names.exists(_BitrateControl) || names.exists(_Bitrate)) + { + setChnAttrGOPMode(attr, getGOPMode(), getGOP()); + setChnAttrRateControl(attr, getCodecID(), getBitrateControl(), getSuitableGOP(), getSuitableSFR(), + getSuitableDFR(), getSuitableBitrate()); + } + hi_mpi_venc_set_chn_attr(channelID(), &attr); +} + +void VideoEncoder::onLink(Core::Component::Ptr pPrevious) +{ + _preprocessor.doLink(pPrevious); + _preprocessor.bind(*this); +} + +void VideoEncoder::onUnlink(Core::Component::Ptr pPrevious) +{ + _preprocessor.doUnlink(pPrevious); + _preprocessor.unbind(*this); +} + +void VideoEncoder::handlePreviousParameterChanged(const Poco::AutoPtr& pNotication) +{ + if (!started()) + return; + + const auto& parameters = pNotication->parameters(); + auto pPrevious = getPrevious(); + if (parameters.exists(_Width) || parameters.exists(_Height)) + { + uint32_t width; + uint32_t height; + getEncodeResolution(pPrevious, width, height); + + if (getWidth() == width && getHeight() == height) + { + _preprocessor.stop(); + _preprocessor.start(pPrevious->getWidth(), pPrevious->getHeight(), width, height); + } + else + { + setWidth(width); + setHeight(height); + commit(); + } + } + + if (parameters.exists(_FrameRate)) + { + hi_venc_chn_attr attr; + hi_mpi_venc_get_chn_attr(channelID(), &attr); + setChnAttrRateControl(attr, getCodecID(), getBitrateControl(), getSuitableGOP(), getSuitableSFR(), + getSuitableDFR(), getSuitableBitrate()); + hi_mpi_venc_set_chn_attr(channelID(), &attr); + } +} + +Core::VideoComponent::Ptr VideoEncoder::getPrevious() +{ + auto pPrevious = previousComponents().first().cast(); + if (!pPrevious) + throw Poco::BugcheckException(Poco::format("%s: First previous component '%s' convert to 'VideoComponent' FAIL", + name(), previousComponents().first()->name())); + + return pPrevious; +} + +int VideoEncoder::getSuitableGOP() +{ + return Math::clamp(getGOP(), 1, 65535); +} + +int VideoEncoder::getSuitableSFR() +{ + return Math::clamp(getPrevious()->getFrameRate(), HI_VENC_MIN_INPUT_FRAME_RATE, HI_VENC_MAX_INPUT_FRAME_RATE); +} + +int VideoEncoder::getSuitableDFR() +{ + return Math::clamp(Math::min(getFrameRate(), getSuitableSFR()), HI_VENC_MIN_INPUT_FRAME_RATE, + HI_VENC_MAX_INPUT_FRAME_RATE); + ; +} + +uint32_t VideoEncoder::getSuitableBitrate() +{ + return Math::clamp(getBitrate() / 1000, (int64_t)HI_VENC_MIN_BITRATE, (int64_t)HI_VENC_MAX_BITRATE); +} + +void VideoEncoder::getEncodeResolution(Core::VideoComponent::Ptr pPrevious, uint32_t& width, uint32_t& height) +{ + if (getAdaptable() && !pPrevious.isNull()) + { + width = pPrevious->getWidth(); + height = pPrevious->getHeight(); + } + else + { + width = getWidth(); + height = getHeight(); + } + + width = Math::alignup(Math::clamp(width, (uint32_t)HI_VENC_H264_MIN_WIDTH, (uint32_t)HI_VENC_H264_MAX_WIDTH), + MIN_ALIGN); + height = Math::alignup(Math::clamp(height, (uint32_t)HI_VENC_H264_MIN_HEIGHT, (uint32_t)HI_VENC_H264_MAX_HEIGHT), + MIN_ALIGN); + if (width == 0 || height == 0) + { + width = 1920; + height = 1080; + } +} + +Core::Packet::Ptr VideoEncoder::readPacket(uint32_t timeout) +{ + static const int PACKET_DATA_SIZE_MAX = (1024 * 1024 * 2); + + Core::Packet::Ptr pPacket = nullptr; + + const long tvsec = timeout / 1000; + const long tvusec = 0; + + int fd = hi_mpi_venc_get_fd(channelID()); + while (true) + { + struct timeval tm = { + .tv_sec = tvsec, + .tv_usec = tvusec, + }; + + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(fd, &read_fds); + + int rc = select(fd + 1, &read_fds, NULL, NULL, &tm); + if (rc < 0) + throw Poco::InvalidAccessException( + Poco::format("%s: Read stream FAIL: %s", name(), std::string(strerror(rc))), rc); + else if (0 == rc) + throw Poco::TimeoutException(Poco::format("%s: Read encoder stream timeout!", name()), rc); + + if (!FD_ISSET(fd, &read_fds)) + continue; + + hi_venc_chn_status status; + hi_mpi_venc_query_status(channelID(), &status); + + hi_venc_stream stream; + stream.pack = (hi_venc_pack*)malloc(sizeof(hi_venc_pack) * status.cur_packs); + if (!stream.pack) + continue; + + stream.pack_cnt = status.cur_packs; + rc = hi_mpi_venc_get_stream(channelID(), &stream, timeout); + if (rc != HI_SUCCESS) + { + free(stream.pack); + throw Poco::InvalidAccessException(Poco::format("%s: Get encoder stream FAIL: %#x", name(), (uint32_t)rc), + rc); + } + + bool done = false; + uint8_t* pBuffer = new uint8_t[PACKET_DATA_SIZE_MAX]; + if (!pBuffer) + { + free(stream.pack); + throw Poco::OutOfMemoryException(Poco::format("%s: Alloc memory for encoded data: FAIL", name()), 0); + } + + uint32_t length = 0; + uint32_t flags = Core::Packet::FLAG_IS_VIDEO; + for (uint32_t i = 0; i < stream.pack_cnt; i++) + { + hi_venc_pack* pack = &stream.pack[i]; + uint32_t chucksize = pack->len - pack->offset; + if (chucksize < PACKET_DATA_SIZE_MAX - length) + { + memcpy(&pBuffer[length], pack->addr + pack->offset, chucksize); + length += chucksize; + } + else + logger().error("%s: Buffer size(%d) is insufficient to hold the encoded data!", PACKET_DATA_SIZE_MAX); + + if (pack->is_frame_end) + { + switch (getCodecID()) + { + default: + case Core::CodecID::H264: + if (stream.h264_info.ref_type == HI_VENC_BASE_IDR_SLICE) + flags |= Core::Packet::FLAG_IS_KEY; + break; + + case Core::CodecID::H265: + if (stream.h265_info.ref_type == HI_VENC_BASE_IDR_SLICE) + flags |= Core::Packet::FLAG_IS_KEY; + break; + } + + pPacket = new Core::Packet(serialCodeManager().code(), flags, pBuffer, length, pack->pts, pack->pts); + done = true; + break; + } + } + + rc = hi_mpi_venc_release_stream(channelID(), &stream); + free(stream.pack); + stream.pack = NULL; + + if (done) + break; + } + + return pPacket; +} + +void VideoEncoder::setChnAttrGOPMode(hi_venc_chn_attr& attr, const Core::GOPMode mode, int gop) +{ + switch (mode) + { + case Core::GOPMode::NORMAL_P: + attr.gop_attr.gop_mode = HI_VENC_GOP_MODE_NORMAL_P; + + attr.gop_attr.normal_p.ip_qp_delta = 2; + break; + + case Core::GOPMode::SMART_P: + attr.gop_attr.gop_mode = HI_VENC_GOP_MODE_SMART_P; + + attr.gop_attr.smart_p.bg_interval = gop; + + attr.gop_attr.smart_p.bg_qp_delta = 4; + + attr.gop_attr.smart_p.vi_qp_delta = 2; + break; + + default: + throw Poco::InvalidArgumentException( + Poco::format("%s: Invalid gop-mode: %s", name(), Core::gopModeToString(getGOPMode())), -1); + } +} + +void VideoEncoder::setChnAttrRateControl(hi_venc_chn_attr& attr, Core::CodecID codec, Core::BitrateControl bc, int gop, + int sfr, int dfr, uint32_t bitrate) +{ + logger().debug("%s: confiure: %s, %s, GOP:%d, sfr:%d, dfr:%d, %uKbps", name(), Core::codecIDToString(codec), + Core::bitrateControlToString(bc), gop, sfr, dfr, bitrate); + if (Core::CodecID::H264 == codec) + { + switch (bc) + { + case Core::BitrateControl::CBR: + attr.rc_attr.rc_mode = HI_VENC_RC_MODE_H264_CBR; + __set_cbr(attr.rc_attr.h264_cbr, gop, 1, sfr, dfr, bitrate); + break; + + case Core::BitrateControl::VBR: + attr.rc_attr.rc_mode = HI_VENC_RC_MODE_H264_VBR; + __set_vbr(attr.rc_attr.h264_vbr, gop, 1, sfr, dfr, bitrate); + break; + + case Core::BitrateControl::AVBR: + attr.rc_attr.rc_mode = HI_VENC_RC_MODE_H264_AVBR; + __set_avbr(attr.rc_attr.h264_avbr, gop, 1, sfr, dfr, bitrate); + break; + + case Core::BitrateControl::QVBR: + attr.rc_attr.rc_mode = HI_VENC_RC_MODE_H264_QVBR; + __set_qvbr(attr.rc_attr.h264_qvbr, gop, 1, sfr, dfr, bitrate); + break; + + case Core::BitrateControl::CVBR: + case Core::BitrateControl::FIXQP: + case Core::BitrateControl::QPMAP: + default: + throw Poco::NotImplementedException(Poco::format("%s: Not implemented H.264 bitrate-control: %s", name(), + Core::bitrateControlToString(getBitrateControl())), + -1); + } + } + else if (Core::CodecID::H265 == codec) + { + switch (bc) + { + case Core::BitrateControl::CBR: + attr.rc_attr.rc_mode = HI_VENC_RC_MODE_H265_CBR; + __set_cbr(attr.rc_attr.h265_cbr, gop, 1, sfr, dfr, bitrate); + break; + + case Core::BitrateControl::VBR: + attr.rc_attr.rc_mode = HI_VENC_RC_MODE_H265_VBR; + __set_vbr(attr.rc_attr.h265_vbr, gop, 1, sfr, dfr, bitrate); + break; + + case Core::BitrateControl::AVBR: + attr.rc_attr.rc_mode = HI_VENC_RC_MODE_H265_AVBR; + __set_avbr(attr.rc_attr.h265_avbr, gop, 1, sfr, dfr, bitrate); + break; + + case Core::BitrateControl::QVBR: + attr.rc_attr.rc_mode = HI_VENC_RC_MODE_H265_QVBR; + __set_qvbr(attr.rc_attr.h265_qvbr, gop, 1, sfr, dfr, bitrate); + break; + + case Core::BitrateControl::CVBR: + case Core::BitrateControl::FIXQP: + case Core::BitrateControl::QPMAP: + default: + throw Poco::NotImplementedException(Poco::format("%s: Not implemented H.265 bitrate-control: %s", name(), + Core::bitrateControlToString(getBitrateControl())), + -1); + } + } +} + +void VideoEncoder::releaseCapabilityHandle() +{ +} + +void VideoEncoder::initModule() +{ + hi_venc_mod_param param = {}; + + param.mod_type = HI_VENC_MOD_H264; + param.h264_mod_param.one_stream_buf = 0; + param.h264_mod_param.mini_buf_mode = 0; + param.h264_mod_param.low_power_mode = 0; + param.h264_mod_param.vb_src = HI_VB_SRC_PRIVATE; + param.h264_mod_param.qp_hist_en = HI_FALSE; + param.h264_mod_param.max_user_data_len = 1024; + int rc = hi_mpi_venc_set_mod_param(¶m); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("MPP5::VENC: Set module H264 param FAIL: %u(%#x)", (uint32_t)rc, (uint32_t)rc)); + + param.mod_type = HI_VENC_MOD_H265; + param.h265_mod_param.one_stream_buf = 0; + param.h265_mod_param.mini_buf_mode = 0; + param.h265_mod_param.low_power_mode = 0; + param.h265_mod_param.vb_src = HI_VB_SRC_PRIVATE; + param.h265_mod_param.qp_hist_en = HI_FALSE; + param.h265_mod_param.max_user_data_len = 32768; + rc = hi_mpi_venc_set_mod_param(¶m); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("MPP5::VENC: Set module H265 param FAIL: %u(%#x)", (uint32_t)rc, (uint32_t)rc)); +} + +void VideoEncoder::deinitModule() +{ +} +} +} diff --git a/video/hisi/mpp5/VideoEncoder.h b/video/hisi/mpp5/VideoEncoder.h new file mode 100644 index 0000000..6a8e8e3 --- /dev/null +++ b/video/hisi/mpp5/VideoEncoder.h @@ -0,0 +1,91 @@ +#ifndef F3099295_0B78_4FE9_A07D_1748943D2805 +#define F3099295_0B78_4FE9_A07D_1748943D2805 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "MPPChannel.h" +#include "../core/VideoEncoder.h" +#include "../core/CodecCapabilityManager.h" +#include "../../core/Notifications.h" +#include "util/HandlesManager.h" +#include +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class VideoEncoder: public MPPChannel, public Core::VideoEncoder +{ +private: + class Preprocessor: public MPPChannel + { + public: + using Ptr = Poco::SharedPtr; + + public: + Preprocessor(const std::string& name, uint32_t id); + ~Preprocessor(); + + public: + void start(uint32_t iwidth, uint32_t iheight, uint32_t owidth, uint32_t oheight); + void stop(); + + uint32_t groupID() const; + const std::string& name() const; + + private: + const std::string _name; + }; + +public: + VideoEncoder(Core::CodecID codecID); + ~VideoEncoder(); + +protected: + void start() override; + void stop() override; + void initialize() override; + void deinitialize() override; + + void onConfigChanged(ParameterNames& names) override; + void onLink(Core::Component::Ptr pPrevious) override; + void onUnlink(Core::Component::Ptr pPrevious) override; + + void handlePreviousParameterChanged(const Poco::AutoPtr& pNotication); + + Core::VideoComponent::Ptr getPrevious(); + int getSuitableGOP(); + int getSuitableSFR(); + int getSuitableDFR(); + uint32_t getSuitableBitrate(); + void getEncodeResolution(Core::VideoComponent::Ptr pPrevious, uint32_t& width, uint32_t& height); + + void setChnAttrGOPMode(hi_venc_chn_attr& attr, Core::GOPMode mode, int gop); + void setChnAttrRateControl(hi_venc_chn_attr& attr, Core::CodecID codec, Core::BitrateControl bc, int gop, int sfr, + int dfr, uint32_t bitrate); + Core::Packet::Ptr readPacket(uint32_t timeout); + + void releaseCapabilityHandle(); + +private: + Core::CodecCapabilityManager::Handle _handle; + Preprocessor _preprocessor; + +public: + static void initModule(); + static void deinitModule(); + +private: + static Core::CodecCapabilityManager _capabilityManager; +}; +} +} +#endif diff --git a/video/hisi/mpp5/VideoFrame.cpp b/video/hisi/mpp5/VideoFrame.cpp new file mode 100644 index 0000000..5a87985 --- /dev/null +++ b/video/hisi/mpp5/VideoFrame.cpp @@ -0,0 +1,194 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoFrame.h" +#include "util/Math.h" +#include +#include +#include +#include +#include +#include + +/* + ******************************************************************************* + * CONSTANTS + ******************************************************************************* + */ +#define POOL_ID 0 + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoFrame::VideoFrame(hi_video_frame_info* pInfo, uint32_t size, uint8_t* pVirAddr, uint32_t phyAddr, uint32_t handle, + uint32_t chn) + : _pInfo(pInfo), _size(size), _pVirAddr(pVirAddr), _phyAddr(phyAddr), _handle(handle), _chn(chn) +{ +} + +VideoFrame::~VideoFrame() +{ + if (!_pInfo) + return; + + switch (_pInfo->mod_id) + { + case HI_ID_USER: + if (_pVirAddr) + { + hi_mpi_sys_munmap(_pVirAddr, _size); + hi_mpi_vb_release_blk(_handle); + } + break; + + case HI_ID_VDEC: + hi_mpi_vdec_release_frame(_chn, _pInfo); + break; + + default: + std::cout << Poco::format("error: VideoFrame: Unsupported mod id: %d", (int)_pInfo->mod_id) << std::endl; + break; + } + + free(_pInfo); +} + +VideoFrame::Ptr VideoFrame::create(hi_pixel_format format, uint32_t width, uint32_t height, const uint8_t* pData) +{ + uint32_t stride = Util::Math::alignup(width, 16); + uint32_t imagesz, lumasz, chromasz; + + switch (format) + { + case HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420: + case HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420: + imagesz = stride * Util::Math::alignup(height, 16) * 3 / 2; + lumasz = stride * height; + chromasz = lumasz >> 1; + break; + + case HI_PIXEL_FORMAT_YVU_SEMIPLANAR_422: + case HI_PIXEL_FORMAT_YUV_SEMIPLANAR_422: + imagesz = stride * Util::Math::alignup(height, 16) * 2; + lumasz = stride * height; + chromasz = lumasz; + break; + + default: + throw Poco::NotImplementedException("Unsupported pixel format"); + } + + hi_vb_blk handle = hi_mpi_vb_get_blk(POOL_ID, imagesz, "anonymous"); + if (HI_VB_INVALID_HANDLE == handle) + throw Poco::RuntimeException( + Poco::format("VideoFrame: Failed to get VB block: %u bytes @POOL#%d", imagesz, (int)POOL_ID)); + + hi_phys_addr_t phyAddr = hi_mpi_vb_handle_to_phys_addr(handle); + + if (!phyAddr) + throw Poco::RuntimeException("VideoFrame: Failed to get VB block phys-addr"); + + uint8_t* pVirAddr = (uint8_t*)hi_mpi_sys_mmap(phyAddr, imagesz); + if (!pVirAddr) + throw Poco::RuntimeException("VideoFrame: Failed to map VB block"); + + hi_video_frame_info* pInfo = (hi_video_frame_info*)malloc(sizeof(hi_video_frame_info)); + if (!pInfo) + throw Poco::OutOfMemoryException("VideoFrame: Failed to allocate frame info"); + + memset(pInfo, 0, sizeof(*pInfo)); + pInfo->mod_id = HI_ID_USER; + pInfo->pool_id = POOL_ID; + hi_video_frame* pVideoFrame = &pInfo->video_frame; + + pVideoFrame->width = width; + pVideoFrame->height = height; + pVideoFrame->field = HI_VIDEO_FIELD_FRAME; + pVideoFrame->pixel_format = (hi_pixel_format)format; + pVideoFrame->video_format = HI_VIDEO_FORMAT_LINEAR; + pVideoFrame->compress_mode = HI_COMPRESS_MODE_NONE; + pVideoFrame->dynamic_range = HI_DYNAMIC_RANGE_SDR8; + pVideoFrame->color_gamut = HI_COLOR_GAMUT_BT601; + + switch (format) + { + case HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420: + case HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420: + pVideoFrame->stride[0] = stride; + pVideoFrame->stride[1] = stride; + + pVideoFrame->phys_addr[0] = phyAddr; + pVideoFrame->phys_addr[1] = (phyAddr + lumasz); + + pVideoFrame->virt_addr[0] = (hi_void*)pVirAddr; + pVideoFrame->virt_addr[1] = (hi_void*)(pVirAddr + lumasz); + + pVideoFrame->time_ref = 0; + pVideoFrame->pts = 0; + pVideoFrame->frame_flag = 0; + + memcpy(pVideoFrame->virt_addr[0], pData, lumasz); + memcpy(pVideoFrame->virt_addr[1], pData + lumasz, chromasz); + break; + + case HI_PIXEL_FORMAT_YVU_SEMIPLANAR_422: + case HI_PIXEL_FORMAT_YUV_SEMIPLANAR_422: + pVideoFrame->stride[0] = stride; + pVideoFrame->stride[1] = stride; + + pVideoFrame->phys_addr[0] = phyAddr; + pVideoFrame->phys_addr[1] = (phyAddr + lumasz); + + pVideoFrame->virt_addr[0] = (hi_void*)pVirAddr; + pVideoFrame->virt_addr[1] = (hi_void*)(pVirAddr + lumasz); + + pVideoFrame->time_ref = 0; + pVideoFrame->pts = 0; + pVideoFrame->frame_flag = 0; + + memcpy(pVideoFrame->virt_addr[0], pData, lumasz); + memcpy(pVideoFrame->virt_addr[1], pData + lumasz, chromasz); + break; + + default: + throw Poco::NotImplementedException("VideoFrame: Unsupported pixel format"); + } + + return new VideoFrame(pInfo, imagesz, pVirAddr, phyAddr, handle, 0); +} + +VideoFrame::Ptr VideoFrame::create(hi_pixel_format format, uint32_t width, uint32_t height, const std::string& path) +{ + uint32_t size; + Poco::SharedPtr pData; + + try + { + Poco::FileInputStream fist(path); + + fist.seekg(0, fist.end); + size = fist.tellg(); + fist.seekg(0, fist.beg); + + pData = new uint8_t[size]; + fist.read((char*)pData.get(), size); + fist.close(); + } + catch (Poco::Exception& e) + { + size = width * height * 3; + pData = new uint8_t[size]; + } + + return create(format, width, height, pData); +} +} +} diff --git a/video/hisi/mpp5/VideoFrame.h b/video/hisi/mpp5/VideoFrame.h new file mode 100644 index 0000000..adf14e7 --- /dev/null +++ b/video/hisi/mpp5/VideoFrame.h @@ -0,0 +1,73 @@ +#ifndef B9B55BBA_D4EA_459D_AB14_BEB06E1FB74A +#define B9B55BBA_D4EA_459D_AB14_BEB06E1FB74A +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class VideoFrame +{ +public: + using Ptr = Poco::SharedPtr; + +private: + VideoFrame(hi_video_frame_info* pInfo, uint32_t size, uint8_t* pVirAddr, uint32_t phyAddr, uint32_t handle, + uint32_t chn); + +public: + ~VideoFrame(); + +public: + const hi_video_frame_info* info() const; + const uint32_t width() const; + const uint32_t height() const; + const hi_pixel_format pixelFormat() const; + +private: + hi_video_frame_info* _pInfo; + uint32_t _size; + uint8_t* _pVirAddr; + uint32_t _phyAddr; + uint32_t _handle; + uint32_t _chn; + +public: + static Ptr create(hi_pixel_format format, uint32_t width, uint32_t height, const uint8_t* pData); + static Ptr create(hi_pixel_format format, uint32_t width, uint32_t height, const std::string& path); +}; + +inline const hi_video_frame_info* VideoFrame::info() const +{ + return _pInfo; +} + +inline const uint32_t VideoFrame::width() const +{ + return _pInfo->video_frame.width; +} + +inline const uint32_t VideoFrame::height() const +{ + return _pInfo->video_frame.height; +} + +inline const hi_pixel_format VideoFrame::pixelFormat() const +{ + return _pInfo->video_frame.pixel_format; +} +} +} +#endif diff --git a/video/hisi/mpp5/VideoInput.cpp b/video/hisi/mpp5/VideoInput.cpp new file mode 100644 index 0000000..df4f024 --- /dev/null +++ b/video/hisi/mpp5/VideoInput.cpp @@ -0,0 +1,202 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoInput.h" +#include "IVE.h" +#include "ResourceManager.h" +#include "../../core/Notifications.h" +#include "Application.h" +#include "Logger.h" + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoFrame::Ptr VideoInput::_pUserVideoFrame = nullptr; + +VideoInput::VideoInput(uint32_t deviceID, uint32_t channelID, bool dataReverseEnable) + : Core::VideoComponent(Poco::format("MPP5::VI#%u", deviceID)), MPPChannel((uint32_t)HI_ID_VI, deviceID, channelID), + _dataReverseEnable(dataReverseEnable), _channelId(deviceID == 2 ? 0 : 1) +{ + setWidth(1920); + setHeight(1080); + setFrameRate(30); + + setVideoScanMode(Core::VideoScanMode::PROGRESSIVE); +} + +VideoInput::~VideoInput() +{ +} + +void VideoInput::start() +{ + uint32_t width = getWidth(); + uint32_t height = getHeight(); + + hi_vi_dev_attr devAttr; + devAttr.intf_mode = HI_VI_MODE_BT1120_STANDARD; + devAttr.work_mode = HI_VI_WORK_MODE_MULTIPLEX_1; + devAttr.component_mask[0] = 0x00000ff0; + devAttr.component_mask[1] = 0x000ff000; + devAttr.clk_edge = HI_VI_CLK_EDGE_SINGLE_UP; + devAttr.data_seq = HI_VI_IN_DATA_VUVU; + + devAttr.sync_cfg.vsync = HI_VI_VSYNC_PULSE; + devAttr.sync_cfg.vsync_neg = HI_VI_VSYNC_NEG_LOW; + devAttr.sync_cfg.hsync = HI_VI_HSYNC_VALID_SIG; + devAttr.sync_cfg.hsync_neg = HI_VI_HSYNC_NEG_HIGH; + devAttr.sync_cfg.vsync_valid = HI_VI_VSYNC_VALID_SIG; + devAttr.sync_cfg.vsync_valid_neg = HI_VI_VSYNC_VALID_NEG_HIGH; + + devAttr.sync_cfg.timing_blank.hsync_hfb = 0; + devAttr.sync_cfg.timing_blank.hsync_act = width; + devAttr.sync_cfg.timing_blank.hsync_hbb = 0; + devAttr.sync_cfg.timing_blank.vsync_vfb = 0; + devAttr.sync_cfg.timing_blank.vsync_vact = height; + devAttr.sync_cfg.timing_blank.vsync_vbb = 0; + devAttr.sync_cfg.timing_blank.vsync_vbfb = 0; + devAttr.sync_cfg.timing_blank.vsync_vbact = 0; + devAttr.sync_cfg.timing_blank.vsync_vbbb = 0; + + devAttr.data_reverse_en = (hi_bool)_dataReverseEnable; + devAttr.mixed_capture_en = HI_FALSE; + + auto rc = hi_mpi_vi_set_dev_attr(deviceID(), &devAttr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Set attr FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + rc = hi_mpi_vi_enable_dev(deviceID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Enable FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + hi_vi_chn_attr chnAttr; + chnAttr.capture_rect.x = 0; + chnAttr.capture_rect.y = 0; + chnAttr.capture_rect.width = width; + chnAttr.capture_rect.height = height; + chnAttr.dst_size.width = width; + chnAttr.dst_size.height = height; + chnAttr.capture_select = HI_VI_CAPTURE_SELECT_BOTH; + chnAttr.scan_mode = getVIScanMode(); + chnAttr.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_422; + chnAttr.mirror_en = HI_FALSE; + chnAttr.flip_en = HI_FALSE; + chnAttr.compress_mode = HI_COMPRESS_MODE_NONE; + chnAttr.src_frame_rate = -1; + chnAttr.dst_frame_rate = -1; + + auto channel = channelID(); + rc = hi_mpi_vi_set_chn_attr(channel, &chnAttr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: Configure channel FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + rc = hi_mpi_vi_set_frame_depth(channel, 4); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: Configure frame depth FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + rc = hi_mpi_vi_enable_chn(channel); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Enable channel FAIL: %d(%#x)", name(), rc, (uint32_t)rc), + rc); + + enableUserPic(getSignalLost()); +} + +void VideoInput::stop() +{ + enableUserPic(false); + hi_mpi_vi_disable_chn(channelID()); + hi_mpi_vi_disable_dev(deviceID()); +} + +void VideoInput::onConfigChanged(ParameterNames& names) +{ + if (!started()) + return; + + notifyNextComponents(sharedFromThis(), new Core::ParameterChangedNotification(names)); + + if (names.exists(_Width) || names.exists(_Height)) + { + stop(); + start(); + return; + } + + if (names.exists(_VideoScanMode)) + { + hi_vi_chn_attr attr; + hi_mpi_vi_get_chn_attr(channelID(), &attr); + attr.scan_mode = getVIScanMode(); + int rc = hi_mpi_vi_set_chn_attr(channelID(), &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: Configure channel FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + } + + if (names.exists(_SignalLost)) + enableUserPic(getSignalLost()); +} + +uint32_t VideoInput::traffic() +{ + hi_vi_chn_status s; + return hi_mpi_vi_query(channelID(), &s) == HI_SUCCESS ? s.interrupt_cnt : 0; +} + +void VideoInput::enableUserPic(bool enable) +{ + if (enable) + hi_mpi_vi_enable_user_pic(channelID()); + else + hi_mpi_vi_disable_user_pic(channelID()); +} + +hi_vi_scan_mode VideoInput::getVIScanMode() +{ + return getVideoScanMode() == Core::VideoScanMode::PROGRESSIVE ? HI_VI_SCAN_PROGRESSIVE : HI_VI_SCAN_INTERLACED; +} + +void VideoInput::initModule() +{ + hi_vi_mod_param param; + hi_mpi_vi_get_mod_param(¶m); + param.yc_reverse = 0; + hi_mpi_vi_set_mod_param(¶m); + + if (!_pUserVideoFrame) + _pUserVideoFrame = + VideoFrame::create(NO_SIGNAL_YUV_FORMAT, NO_SIGNAL_YUV_WIDTH, NO_SIGNAL_YUV_HEIGHT, NO_SIGNAL_YUV_PATH); + + hi_vi_user_pic_attr attr = {}; + + attr.pub_en = HI_TRUE; + attr.user_pic_mode = HI_VI_USER_PIC_MODE_PIC; + attr.user_pic_info.user_pic_frame_info.mod_id = HI_ID_VI; + attr.user_pic_info.user_pic_frame_info.pool_id = 0; + attr.user_pic_info.user_pic_frame_info = *_pUserVideoFrame->info(); + int rc = hi_mpi_vi_set_user_pic(0, &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("MPP::VI: Set user pic FAIL: %d(%#x)", rc, (uint32_t)rc), rc); +} + +void VideoInput::deinitModule() +{ + _pUserVideoFrame.reset(); +} + +void VideoInput::reset() +{ +} +} +} diff --git a/video/hisi/mpp5/VideoInput.h b/video/hisi/mpp5/VideoInput.h new file mode 100644 index 0000000..030873d --- /dev/null +++ b/video/hisi/mpp5/VideoInput.h @@ -0,0 +1,55 @@ +#ifndef FA05C272_A837_4EE6_A95D_55486BEA1F99 +#define FA05C272_A837_4EE6_A95D_55486BEA1F99 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../../core/VideoComponent.h" +#include "MPPChannel.h" +#include "VideoFrame.h" +#include +#include "util/Broadcast.h" + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class VideoInput: public Core::VideoComponent, public MPPChannel +{ +public: + using Ptr = Poco::AutoPtr; + +public: + VideoInput(uint32_t deviceID, uint32_t channelID, bool dataReverseEnable); + ~VideoInput(); + void reset() override; + +protected: + void start() override; + void stop() override; + void onConfigChanged(ParameterNames& names) override; + uint32_t traffic() override; + + void enableUserPic(bool enable); + hi_vi_scan_mode getVIScanMode(); + +private: + bool _dataReverseEnable; + const uint32_t _channelId; + +public: + static void initModule(); + static void deinitModule(); + +private: + static VideoFrame::Ptr _pUserVideoFrame; +}; +} +} +#endif diff --git a/video/hisi/mpp5/VideoMixer.cpp b/video/hisi/mpp5/VideoMixer.cpp new file mode 100644 index 0000000..793c9b4 --- /dev/null +++ b/video/hisi/mpp5/VideoMixer.cpp @@ -0,0 +1,295 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoMixer.h" +#include "util/Math.h" +#include +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CONSTANTS + ******************************************************************************* + */ + +#define WIDTH 1920 +#define HEIGHT 1080 +#define FRAMTE_RATE 60 + +#define VO_DEVICE_ID 1 + +#define VIDEO_LAYER_ID 1 + +#define VO_WBC_ID 0 + +#define VO_MAX_CHN_NUM (HI_VO_MAX_CHN_NUM - 1) +#define VO_CHN_PLACEHOLDER_ID (VO_MAX_CHN_NUM - 1) + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoMixer::Channel::Channel(VideoMixer::Ptr pMixer, uint32_t id) + : MPPChannel((uint32_t)HI_ID_VO, VIDEO_LAYER_ID, id), Core::VideoMixer::Channel("MPP5::VHD1", pMixer, id) +{ +} + +VideoMixer::Channel::~Channel() +{ +} + +void VideoMixer::Channel::start() +{ + if (mixer().cast()->started()) + activate(); +} + +void VideoMixer::Channel::stop() +{ + if (getActivated()) + deactivate(); +} + +void VideoMixer::Channel::onConfigChanged(ParameterNames& names) +{ + Core::VideoMixer::Channel::onConfigChanged(names); + + if (!started()) + return; + + if (names.exists(_Attach)) + { + if (!getAttach()) + deactivate(); + else if (mixer().cast()->started()) + activate(); + } + + if ((names.exists(_Region) || names.exists(_ZOrder)) && getActivated()) + configureChannel(); +} + +void VideoMixer::Channel::onLink(Core::Component::Ptr pPrevious) +{ + MPPChannel::doLink(pPrevious); +} + +void VideoMixer::Channel::onUnlink(Core::Component::Ptr pPrevious) +{ + MPPChannel::doUnlink(pPrevious); +} + +void VideoMixer::Channel::activate() +{ + configureChannel(); + + int rc = hi_mpi_vo_enable_chn(VIDEO_LAYER_ID, channelID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: enable FAIL: %d(%#x)", mixer()->name() + "/" + name(), rc, (uint32_t)rc), rc); + + setActivated(true); +} + +void VideoMixer::Channel::deactivate() +{ + hi_mpi_vo_disable_chn(VIDEO_LAYER_ID, channelID()); + setActivated(false); +} + +void VideoMixer::Channel::configureChannel() +{ + const auto region = getRegion(); + hi_vo_chn_attr attr; + + attr.priority = ::Util::Math::clamp(getZOrder(), 0, VO_MAX_CHN_NUM - 2) + 1; + + attr.rect.x = + ::Util::Math::aligndown(::Util::Math::clamp(static_cast(std::round(region.x * WIDTH)), 0, WIDTH - 32), 2); + attr.rect.y = ::Util::Math::aligndown( + ::Util::Math::clamp(static_cast(std::round(region.y * HEIGHT)), 0, HEIGHT - 32), 2); + + attr.rect.width = ::Util::Math::aligndown( + ::Util::Math::clamp(static_cast(std::round(region.width * WIDTH)), 32, WIDTH - attr.rect.x), 2); + attr.rect.height = ::Util::Math::aligndown( + ::Util::Math::clamp(static_cast(std::round(region.height * HEIGHT)), 32, HEIGHT - attr.rect.y), 2); + ; + attr.deflicker_en = HI_FALSE; + int rc = hi_mpi_vo_set_chn_attr(VIDEO_LAYER_ID, channelID(), &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: set chn attr FAIL: %d(%#x)", mixer()->name() + "/" + name(), rc, (uint32_t)rc), rc); +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoMixer::VideoMixer(const std::string& name) + : MPPChannel((uint32_t)HI_ID_VO, VO_DEVICE_ID, 0), Core::VideoMixer(Poco::format("%s(MPP5::VO::DHD1)", name)) +{ + setWidth(WIDTH); + setHeight(HEIGHT); + setFrameRate(FRAMTE_RATE); + setSignalLost(false); +} + +VideoMixer::~VideoMixer() +{ +} + +void VideoMixer::start() +{ + int rc = hi_mpi_vo_enable(deviceID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: enable VO FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + { + hi_vo_video_layer_attr attr; + + attr.display_rect.x = 0; + attr.display_rect.y = 0; + attr.display_rect.width = WIDTH; + attr.display_rect.height = HEIGHT; + attr.img_size.width = WIDTH; + attr.img_size.height = HEIGHT; + attr.display_frame_rate = FRAMTE_RATE; + attr.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_422; + + attr.double_frame_en = HI_FALSE; + attr.cluster_mode_en = HI_FALSE; + attr.dst_dynamic_range = HI_DYNAMIC_RANGE_SDR8; + attr.display_buf_len = 4; + + attr.partition_mode = HI_VO_PARTITION_MODE_SINGLE; + attr.compress_mode = HI_COMPRESS_MODE_NONE; + rc = hi_mpi_vo_set_video_layer_attr(VIDEO_LAYER_ID, &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: set video layer attr FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + rc = hi_mpi_vo_enable_video_layer(VIDEO_LAYER_ID); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: enable video layer FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + } + + { + hi_vo_wbc_src src; + src.src_type = HI_VO_WBC_SRC_VIDEO; + + src.src_id = VIDEO_LAYER_ID; + rc = hi_mpi_vo_set_wbc_src(VO_WBC_ID, &src); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: set WBC src FAIL: %d(%#x)", name(), rc, (uint32_t)rc), + rc); + + rc = hi_mpi_vo_set_wbc_mode(VO_WBC_ID, HI_VO_WBC_MODE_NORM); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: set WBC mode FAIL: %d(%#x)", name(), rc, (uint32_t)rc), + rc); + + hi_vo_wbc_attr attr; + attr.target_size.width = WIDTH; + attr.target_size.height = HEIGHT; + attr.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_422; + attr.frame_rate = -1; + attr.dynamic_range = HI_DYNAMIC_RANGE_SDR8; + attr.compress_mode = HI_COMPRESS_MODE_NONE; + + rc = hi_mpi_vo_set_wbc_attr(VO_WBC_ID, &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: set WBC attr FAIL: %d(%#x)", name(), rc, (uint32_t)rc), + rc); + + rc = hi_mpi_vo_enable_wbc(VO_WBC_ID); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: enable WBC FAIL: %d(%#x)", name(), rc, (uint32_t)rc), + rc); + } + + foreachChannel( + [](Channel::Ptr pChannel) -> void + { + if (pChannel->getAttach()) + pChannel->activate(); + }); + + { + hi_vo_chn_attr attr; + attr.priority = 0; + attr.rect.x = 0; + attr.rect.y = 0; + attr.rect.width = 32; + attr.rect.height = 32; + attr.deflicker_en = HI_FALSE; + hi_mpi_vo_set_chn_attr(VIDEO_LAYER_ID, VO_CHN_PLACEHOLDER_ID, &attr); + hi_mpi_vo_enable_chn(VIDEO_LAYER_ID, VO_CHN_PLACEHOLDER_ID); + } + + Core::VideoMixer::start(); +} + +void VideoMixer::stop() +{ + hi_mpi_vo_disable_chn(VIDEO_LAYER_ID, VO_CHN_PLACEHOLDER_ID); + foreachChannel( + [this](Channel::Ptr pChannel) -> void + { + if (pChannel->getActivated()) + pChannel->deactivate(); + }); + + Core::VideoMixer::stop(); + int rc = hi_mpi_vo_disable_wbc(VO_WBC_ID); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: disable WBC FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + rc = hi_mpi_vo_disable_video_layer(VIDEO_LAYER_ID); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: disable video layer FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + rc = hi_mpi_vo_disable(VO_DEVICE_ID); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: disable VO FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); +} + +void VideoMixer::initialize() +{ + hi_vo_pub_attr attr; + attr.bg_color = 0x000000; + attr.intf_type = HI_VO_INTF_BT1120; + attr.intf_sync = HI_VO_OUT_1080P60; + int rc = hi_mpi_vo_set_pub_attr(deviceID(), &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: set-pub FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); +} + +void VideoMixer::deinitialize() +{ +} + +void VideoMixer::onConfigChanged(ParameterNames& names) +{ +} + +Core::VideoMixer::Channel::Ptr VideoMixer::createChannel(uint32_t id) +{ + if (id >= VO_CHN_PLACEHOLDER_ID) + throw Poco::InvalidArgumentException( + Poco::format("%s: channel id %u overrage: [0,%u]", name(), id, (uint32_t)(VO_CHN_PLACEHOLDER_ID - 1)), + -EINVAL); + + return new Channel(Ptr(this, true), id); +} +} +} diff --git a/video/hisi/mpp5/VideoMixer.h b/video/hisi/mpp5/VideoMixer.h new file mode 100644 index 0000000..24381e2 --- /dev/null +++ b/video/hisi/mpp5/VideoMixer.h @@ -0,0 +1,68 @@ +#ifndef B351F133_EBBC_42F5_9C48_5BA22D16BB53 +#define B351F133_EBBC_42F5_9C48_5BA22D16BB53 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "MPPChannel.h" +#include "../../core/VideoMixer.h" + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class VideoMixer: public MPPChannel, public Core::VideoMixer +{ +public: + class Channel: public MPPChannel, public Core::VideoMixer::Channel + { + public: + using Ptr = Poco::AutoPtr; + + protected: + Channel(VideoMixer::Ptr, uint32_t id); + + public: + ~Channel(); + + protected: + virtual void start() override; + virtual void stop() override; + void onConfigChanged(ParameterNames& names) override; + void onLink(Core::Component::Ptr pPrevious) override; + void onUnlink(Core::Component::Ptr pPrevious) override; + + void activate(); + void deactivate(); + + PROPERTY_DECLARE(Activated, bool, false, protected, protected) + + void configureChannel(); + + friend class VideoMixer; + }; + +public: + VideoMixer(const std::string& name); + ~VideoMixer(); + +protected: + void start() override; + void stop() override; + void initialize() override; + void deinitialize() override; + void onConfigChanged(ParameterNames& names) override; + + Core::VideoMixer::Channel::Ptr createChannel(uint32_t id) override; + + friend class Channel; +}; +} +} +#endif diff --git a/video/hisi/mpp5/VideoOverlay.cpp b/video/hisi/mpp5/VideoOverlay.cpp new file mode 100644 index 0000000..45a44d5 --- /dev/null +++ b/video/hisi/mpp5/VideoOverlay.cpp @@ -0,0 +1,317 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoOverlay.h" +#include "MPPChannel.h" +#include "ResourceManager.h" +#include "util/Math.h" +#include "Logger.h" +#include +#include +#include +#include +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoOverlay::VideoOverlay(Core::VideoOverlayType type, const std::string& name, const Core::Rect& region) + : Core::VideoOverlay(type, name, region), _handle(ResourceManager::allocRGNHandle()), _imagePhyAddr(0), + _pImageVirAddr(nullptr) +{ + hi_rgn_attr attr = {}; + switch (type) + { + case Core::VideoOverlayType::CANVAS: + attr.type = HI_RGN_OVERLAY; + attr.attr.overlay.pixel_format = HI_PIXEL_FORMAT_ARGB_8888; + _bytesPerPixel = 4; + attr.attr.overlay.size.width = ::Util::Math::alignup(region.width, 2); + attr.attr.overlay.size.height = ::Util::Math::alignup(region.height, 2); + attr.attr.overlay.bg_color = 0; + attr.attr.overlay.canvas_num = 2; + break; + + case Core::VideoOverlayType::MOSAIC: + attr.type = HI_RGN_MOSAIC; + break; + + default: + throw Poco::LogicException(name + ": Unimplemented type: " + std::to_string((int)type)); + } + + int rc = hi_mpi_rgn_create(_handle, &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidArgumentException(Poco::format("%s: Create FAIL: %#x", name, (uint32_t)rc), rc); +} + +VideoOverlay::~VideoOverlay() +{ + _timer.cancel(true); + if (_pImageVirAddr != nullptr && _imagePhyAddr != 0) + hi_mpi_sys_mmz_free(_imagePhyAddr, _pImageVirAddr); + + Core::VideoComponent::Ptr o = target(); + auto chn = dynamic_cast(o.get())->raw(); + hi_mpi_rgn_detach_from_chn(_handle, &chn); + hi_mpi_rgn_destroy(_handle); + ResourceManager::freeRGNHandle(_handle); +} + +void VideoOverlay::render(Core::Image::Ptr pImage) +{ + hi_rgn_canvas_info info; + hi_mpi_rgn_get_canvas_info(_handle, &info); + + uint32_t height = ::Util::Math::min((uint32_t)pImage->height(), (uint32_t)region().height); + for (uint32_t y = 0; y < height; y++) + { + int i = 0; + Core::Color color; + uint8_t* pLine = (uint8_t*)info.virt_addr + y * info.stride; + cimg_forX(*pImage, x) + { + if (x >= region().width) + continue; + + pImage->getPixel(x, y, color); + color.toARGB8888Pixel(&pLine[i]); + i += _bytesPerPixel; + } + } + hi_mpi_rgn_update_canvas(_handle); +} + +void VideoOverlay::scrollRender(Core::Image::Ptr pImage, int hscroll) +{ + const int W = (int)region().width; + const int H = (int)::Util::Math::min((uint32_t)pImage->height(), (uint32_t)region().height); + const int textWidth = (int)pImage->width(); + + const int winW = (int)::Util::Math::min((uint32_t)W, 4096u); + const int period = 2 * W + textWidth; + + const uint32_t stride = ::Util::Math::alignup((uint32_t)winW * _bytesPerPixel, 8); + int rc = hi_mpi_sys_mmz_alloc(&_imagePhyAddr, (void**)&_pImageVirAddr, nullptr, nullptr, (uint32_t)H * stride); + if (rc != HI_SUCCESS) + throw Poco::OutOfMemoryException(Poco::format("%s: Alloc MMZ FAIL: %#x", name(), (uint32_t)rc), rc); + + _pTextImage = pImage; + _textWidth = textWidth; + _winW = winW; + _winH = H; + _stride = stride; + _period = period; + + if (hscroll > 0) + _progressX = 0; + else + _progressX = period - W; + + const int movePixels = hscroll; + const long interval = 30; + _timer.schedule(Poco::Util::Timer::func( + [this, W, movePixels]() + { + const int H = _winH; + const int winW = _winW; + const int textW = _textWidth; + const uint32_t stride = _stride; + const int period = _period; + + ::memset(_pImageVirAddr, 0, (uint32_t)H * stride); + + int startStrip = (_progressX > W) ? _progressX : W; + int endStrip = ((_progressX + winW) < (W + textW)) ? (_progressX + winW) : (W + textW); + if (startStrip < endStrip) + { + int textStart = startStrip - W; + int winStart = startStrip - _progressX; + int count = endStrip - startStrip; + for (int y = 0; y < H; y++) + { + uint8_t* pWin = + _pImageVirAddr + (uint32_t)y * stride + (uint32_t)winStart * _bytesPerPixel; + Core::Color color; + for (int x = 0; x < count; x++) + { + _pTextImage->getPixel(textStart + x, y, color); + color.toARGB8888Pixel(&pWin[x * (int)_bytesPerPixel]); + } + } + } + + hi_tde_surface srcSurface = {}; + srcSurface.phys_addr = _imagePhyAddr; + srcSurface.color_format = HI_TDE_COLOR_FORMAT_ARGB8888; + srcSurface.height = (uint32_t)H; + srcSurface.width = (uint32_t)winW; + srcSurface.stride = stride; + srcSurface.is_ycbcr_clut = HI_FALSE; + srcSurface.alpha_max_is_255 = HI_TRUE; + srcSurface.support_alpha_ex_1555 = HI_FALSE; + srcSurface.alpha0 = 0XFF; + srcSurface.alpha1 = 0XFF; + + hi_rgn_canvas_info info; + hi_mpi_rgn_get_canvas_info(_handle, &info); + + const uint32_t dstW = ::Util::Math::min((uint32_t)region().width, 4096u); + const uint32_t dstH = ::Util::Math::min((uint32_t)region().height, 4096u); + + hi_tde_surface dstSurface = {}; + dstSurface.phys_addr = info.phys_addr; + dstSurface.color_format = HI_TDE_COLOR_FORMAT_ARGB8888; + dstSurface.height = dstH; + dstSurface.width = dstW; + dstSurface.stride = info.stride; + dstSurface.is_ycbcr_clut = HI_FALSE; + dstSurface.support_alpha_ex_1555 = HI_FALSE; + dstSurface.alpha_max_is_255 = HI_TRUE; + dstSurface.alpha0 = 0XFF; + dstSurface.alpha1 = 0XFF; + + hi_tde_rect srcRect; + srcRect.pos_x = 0; + srcRect.pos_y = 0; + srcRect.width = (uint32_t)winW; + srcRect.height = (uint32_t)H; + + hi_tde_rect dstRect; + dstRect.pos_x = 0; + dstRect.pos_y = 0; + dstRect.width = (uint32_t)winW; + dstRect.height = (uint32_t)H; + + hi_s32 handle = hi_tde_begin_job(); + hi_tde_single_src src; + src.src_surface = (hi_tde_surface*)&srcSurface; + src.dst_surface = (hi_tde_surface*)&dstSurface; + src.src_rect = (hi_tde_rect*)&srcRect; + src.dst_rect = (hi_tde_rect*)&dstRect; + int rc = hi_tde_quick_copy(handle, &src); + if (rc != HI_SUCCESS) + { + logger().error("%s: TDE quick copy FAIL: %#x", name(), (uint32_t)rc); + _timer.cancel(false); + return; + } + + hi_tde_end_job(handle, HI_FALSE, HI_TRUE, 10); + hi_mpi_rgn_update_canvas(_handle); + + _progressX += movePixels; + if (movePixels > 0) + { + if (_progressX >= period - W) + _progressX = 0; + } + else + { + if (_progressX <= 0) + _progressX = period - W; + } + }), + interval, interval); +} + +void VideoOverlay::attach(Core::VideoComponent::Ptr pVideoComponent) +{ + Core::VideoOverlay::attach(pVideoComponent); + + hi_rgn_chn_attr attr = {}; + attr.is_show = HI_TRUE; + switch (type()) + { + case Core::VideoOverlayType::CANVAS: + attr.type = HI_RGN_OVERLAY; + attr.attr.overlay_chn.point.x = ::Util::Math::alignup(region().x, 2); + attr.attr.overlay_chn.point.y = ::Util::Math::alignup(region().y, 2); + attr.attr.overlay_chn.fg_alpha = 0; + attr.attr.overlay_chn.bg_alpha = 0; + attr.attr.overlay_chn.layer = getLayer(); + attr.attr.overlay_chn.qp_info.enable = HI_FALSE; + attr.attr.overlay_chn.qp_info.is_abs_qp = HI_FALSE; + attr.attr.overlay_chn.qp_info.qp_val = 0; + attr.attr.overlay_chn.dst = HI_RGN_ATTACH_JPEG_MAIN; + break; + + case Core::VideoOverlayType::MOSAIC: + attr.type = HI_RGN_MOSAIC; + attr.attr.mosaic_chn.rect.x = ::Util::Math::alignup(region().x, 4); + ; + attr.attr.mosaic_chn.rect.y = ::Util::Math::alignup(region().y, 2); + attr.attr.mosaic_chn.rect.width = ::Util::Math::alignup(region().width, 4); + attr.attr.mosaic_chn.rect.height = ::Util::Math::alignup(region().height, 2); + attr.attr.mosaic_chn.blk_size = getBlockSize(); + attr.attr.mosaic_chn.layer = getLayer(); + break; + + default: + throw Poco::LogicException(name() + ": Unimplemented type: " + std::to_string((int)type())); + } + + auto t = target(); + auto pChannel = dynamic_cast(t.get()); + if (!pChannel) + throw Poco::LogicException(name() + ": Unsupported target: " + target()->name()); + + auto chn = pChannel->raw(); + int rc = hi_mpi_rgn_attach_to_chn(_handle, &chn, &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidArgumentException(Poco::format("%s: Attach FAIL: %#x", name(), (uint32_t)rc), rc); +} + +void VideoOverlay::onConfigChanged(ParameterNames& names) +{ + if (names.exists(_ZOrder)) + { + auto t = target(); + auto pChannel = dynamic_cast(t.get()); + auto chn = pChannel->raw(); + hi_rgn_chn_attr attr; + hi_mpi_rgn_get_display_attr(_handle, &chn, &attr); + + switch (attr.type) + { + case HI_RGN_OVERLAY: + attr.attr.overlay_chn.layer = getZOrder(); + break; + + default: + break; + } + + hi_mpi_rgn_set_display_attr(_handle, &chn, &attr); + } +} + +hi_mosaic_blk_size VideoOverlay::getBlockSize() +{ + switch (getBlock()) + { + default: + + case 8: + return HI_MOSAIC_BLK_SIZE_8; + case 16: + return HI_MOSAIC_BLK_SIZE_16; + case 32: + return HI_MOSAIC_BLK_SIZE_32; + } +} + +hi_u32 VideoOverlay::getLayer() +{ + return ::Util::Math::clamp(getZOrder(), 0, 4); +} +} +} diff --git a/video/hisi/mpp5/VideoOverlay.h b/video/hisi/mpp5/VideoOverlay.h new file mode 100644 index 0000000..1aa0fa7 --- /dev/null +++ b/video/hisi/mpp5/VideoOverlay.h @@ -0,0 +1,53 @@ +#ifndef ECE73282_2093_4767_B1C9_C38188A584FF +#define ECE73282_2093_4767_B1C9_C38188A584FF +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../../core/VideoOverlay.h" +#include +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class VideoOverlay: public Core::VideoOverlay +{ +public: + VideoOverlay(Core::VideoOverlayType type, const std::string& name, const Core::Rect& region); + ~VideoOverlay(); + + void render(Core::Image::Ptr pImage) override; + void scrollRender(Core::Image::Ptr pImage, int hscroll) override; + void attach(Core::VideoComponent::Ptr pVideoComponent) override; + +protected: + void onConfigChanged(ParameterNames& names) override; + hi_mosaic_blk_size getBlockSize(); + hi_u32 getLayer(); + +private: + hi_rgn_handle _handle; + unsigned _bytesPerPixel; + uint32_t _imagePhyAddr; + uint8_t* _pImageVirAddr; + Poco::Util::Timer _timer; + int _progressX; + + Core::Image::Ptr _pTextImage; + int _textWidth; + int _winW; + int _winH; + uint32_t _stride; + int _period; +}; +} +} +#endif diff --git a/video/hisi/mpp5/VideoProcessor.cpp b/video/hisi/mpp5/VideoProcessor.cpp new file mode 100644 index 0000000..b6f1a34 --- /dev/null +++ b/video/hisi/mpp5/VideoProcessor.cpp @@ -0,0 +1,289 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "VideoProcessor.h" +#include "Logger.h" +#include "ResourceManager.h" +#include "VideoInput.h" +#include "util/Math.h" + + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +VideoProcessor::VideoProcessor() + : MPPChannel((uint32_t)HI_ID_VPSS, ResourceManager::allocVPSSGroupID(), 0), + Core::VideoProcessor(Poco::format("VideoProcessor(VPSS#%u)", deviceID())) +{ + registerNotificationHandle(*this, + &VideoProcessor::handlePreviousParameterChanged); +} + +VideoProcessor::~VideoProcessor() +{ + ResourceManager::freeVPSSGroupID(deviceID()); +} + +void VideoProcessor::start() +{ + auto pPrevious = previousComponents().first().cast(); + if (!pPrevious) + throw Poco::BugcheckException(Poco::format("%s: First previous component '%s' convert to 'VideoComponent' FAIL", + name(), previousComponents().first()->name())); + + setWidth(pPrevious->getWidth()); + setHeight(pPrevious->getHeight()); + setVideoScanMode(pPrevious->getVideoScanMode()); + + uint32_t width = getWidth(); + uint32_t height = getHeight(); + + hi_vpss_grp_attr attr = {}; + attr.nr_en = HI_FALSE; + attr.ie_en = HI_FALSE; + attr.dci_en = HI_FALSE; + attr.buf_share_en = HI_FALSE; + attr.buf_share_chn = 0; + attr.mcf_en = HI_FALSE; + attr.max_width = width; + attr.max_height = height; + attr.max_dei_width = width; + attr.max_dei_height = height; + attr.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_422; + attr.dynamic_range = HI_DYNAMIC_RANGE_SDR8; + attr.frame_rate.src_frame_rate = -1; + attr.frame_rate.dst_frame_rate = -1; + + if (!pPrevious.cast().isNull() && getVideoScanMode() == Core::VideoScanMode::INTERLACED) + { + attr.dei_mode = HI_VPSS_DEI_MODE_AUTO; + setFrameRate(pPrevious->getFrameRate() / 2); + } + else + { + attr.dei_mode = HI_VPSS_DEI_MODE_OFF; + setFrameRate(pPrevious->getFrameRate()); + } + + int rc = hi_mpi_vpss_create_grp(groupID(), &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Create group FAIL: %d(%#x)", name(), rc, (uint32_t)rc), + rc); + + hi_mpi_vpss_reset_grp(groupID()); + rc = hi_mpi_vpss_start_grp(groupID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException(Poco::format("%s: Start group FAIL: %d(%#x)", name(), rc, (uint32_t)rc), rc); + + bool isPreviousMJPEG = false; + uint32_t direction = 0; + hi_vpss_chn_attr chnAttr = {}; + chnAttr.chn_mode = HI_VPSS_CHN_MODE_USER; + + chnAttr.width = width; + chnAttr.height = height; + chnAttr.video_format = HI_VIDEO_FORMAT_LINEAR; + chnAttr.pixel_format = HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420; + chnAttr.dynamic_range = HI_DYNAMIC_RANGE_SDR8; + chnAttr.compress_mode = HI_COMPRESS_MODE_NONE; + + chnAttr.frame_rate.src_frame_rate = -1; + chnAttr.frame_rate.dst_frame_rate = -1; + chnAttr.mirror_en = (hi_bool)getMirror(); + chnAttr.flip_en = (hi_bool)getFlip(); + chnAttr.depth = 0; + setVPSSVideoDirection(isPreviousMJPEG && direction, &chnAttr); + + rc = hi_mpi_vpss_set_chn_attr(groupID(), channelID(), &chnAttr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: Set channel#%u attr FAIL: %d(%#x)", name(), channelID(), rc, (uint32_t)rc), rc); + + setVPSSChannelRotation(getRotation(), channelID()); + setVPSSGroupCrop(getCrop()); + + rc = hi_mpi_vpss_enable_chn(groupID(), channelID()); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: Enable channel#%u FAIL: %d(%#x)", name(), channelID(), rc, (uint32_t)rc), rc); +} + +void VideoProcessor::stop() +{ + hi_mpi_vpss_disable_chn(groupID(), channelID()); + hi_mpi_vpss_stop_grp(groupID()); + hi_mpi_vpss_destroy_grp(groupID()); +} + +void VideoProcessor::onConfigChanged(ParameterNames& names) +{ + if (!started()) + return; + + if (names.exists(_Width) || names.exists(_Height)) + { + stop(); + start(); + publishEvent(Core::Component::Event::IMAGE_SIZE_CHANGED); + return; + } + + if (names.exists(_Rotation)) + setVPSSChannelRotation(getRotation(), channelID()); + + if (names.exists(_Crop)) + setVPSSGroupCrop(getCrop()); + + if (names.exists(_Mirror) || names.exists(_Flip)) + { + hi_vpss_chn_attr attr; + hi_mpi_vpss_get_chn_attr(groupID(), channelID(), &attr); + attr.mirror_en = (hi_bool)getMirror(); + attr.flip_en = (hi_bool)getFlip(); + hi_mpi_vpss_set_chn_attr(groupID(), channelID(), &attr); + } + + if (names.exists(_VideoDirection)) + { + hi_vpss_chn_attr attr; + hi_mpi_vpss_get_chn_attr(groupID(), channelID(), &attr); + setVPSSVideoDirection(attr.aspect_ratio.mode == HI_ASPECT_RATIO_NONE, &attr); + hi_mpi_vpss_set_chn_attr(groupID(), channelID(), &attr); + } +} + +void VideoProcessor::onLink(Core::Component::Ptr pPrevious) +{ + MPPChannel::doLink(pPrevious); +} + +void VideoProcessor::onUnlink(Core::Component::Ptr pPrevious) +{ + MPPChannel::doUnlink(pPrevious); +} + +void VideoProcessor::handlePreviousParameterChanged( + const Poco::AutoPtr& pNotication) +{ + auto pPrevious = previousComponents().first().cast(); + if (!pPrevious) + throw Poco::BugcheckException(Poco::format("%s: First previous component '%s' convert to 'VideoComponent' FAIL", + name(), previousComponents().first()->name())); + + auto parameters = pNotication->parameters(); + if (parameters.exists(_Width)) + setWidth(pPrevious->getWidth()); + + if (parameters.exists(_Height)) + setHeight(pPrevious->getHeight()); + + if (parameters.exists(_FrameRate)) + setFrameRate(pPrevious->getFrameRate()); + + if (parameters.exists(_VideoScanMode)) + { + setVideoScanMode(pPrevious->getVideoScanMode()); + if (started()) + { + hi_vpss_grp_attr attr; + hi_mpi_vpss_get_grp_attr(groupID(), &attr); + if (!pPrevious.cast().isNull() && getVideoScanMode() == Core::VideoScanMode::INTERLACED) + { + attr.dei_mode = HI_VPSS_DEI_MODE_AUTO; + setFrameRate(pPrevious->getFrameRate() / 2); + } + else + { + attr.dei_mode = HI_VPSS_DEI_MODE_OFF; + setFrameRate(pPrevious->getFrameRate()); + } + hi_mpi_vpss_set_grp_attr(groupID(), &attr); + } + + parameters.insert(_FrameRate); + } + + commit(); + notifyNextComponents(sharedFromThis(), new Core::ParameterChangedNotification(parameters)); +} + +void VideoProcessor::setVPSSGroupCrop(const Core::Rect& rect) +{ + int x = Util::Math::clamp(rect.x, 0, HI_VPSS_MAX_IMG_WIDTH - 1); + int y = Util::Math::clamp(rect.y, 0, HI_VPSS_MAX_IMG_HEIGHT - 1); + int width = Util::Math::clamp(rect.width, HI_VPSS_MIN_IMG_WIDTH, + Util::Math::max(HI_VPSS_MIN_IMG_WIDTH, HI_VPSS_MAX_IMG_WIDTH - rect.x)); + int height = Util::Math::clamp(rect.height, HI_VPSS_MIN_IMG_HEIGHT, + Util::Math::max(HI_VPSS_MIN_IMG_HEIGHT, HI_VPSS_MAX_IMG_HEIGHT - rect.y)); + + hi_vpss_crop_info crop = {}; + crop.enable = (hi_bool) !(rect.x == 0 && rect.y == 0 && rect.width == 0 && rect.height == 0); + crop.crop_mode = HI_COORD_ABS; + crop.crop_rect.x = x; + crop.crop_rect.y = y; + crop.crop_rect.width = width; + crop.crop_rect.height = height; + hi_mpi_vpss_set_grp_crop(groupID(), &crop); +} + +void VideoProcessor::setVPSSChannelRotation(int rotation, uint32_t channelID) +{ + hi_rotation_attr attr; + attr.enable = HI_TRUE; + attr.rotation_type = HI_ROTATION_ANG_FIXED; + switch (rotation) + { + default: + case 0: + attr.enable = HI_FALSE; + attr.rotation_fixed = HI_ROTATION_0; + break; + + case 90: + attr.rotation_fixed = HI_ROTATION_90; + break; + + case 180: + attr.rotation_fixed = HI_ROTATION_180; + break; + + case 270: + attr.rotation_fixed = HI_ROTATION_270; + break; + } + + int rc = hi_mpi_vpss_set_chn_rotation(groupID(), channelID, &attr); + if (rc != HI_SUCCESS) + throw Poco::InvalidAccessException( + Poco::format("%s: Set channel#%u rotation FAIL: %d(%#x)", name(), channelID, rc, (uint32_t)rc), rc); +} + +void VideoProcessor::setVPSSVideoDirection(int direction, hi_vpss_chn_attr* attr) +{ + if (!attr) + return; + + if (direction) + { + uint32_t width = getWidth(); + uint32_t height = getHeight(); + attr->aspect_ratio.mode = HI_ASPECT_RATIO_MANUAL; + attr->aspect_ratio.bg_color = 0x000000; + attr->aspect_ratio.video_rect.width = height * height / width; + attr->aspect_ratio.video_rect.height = height; + attr->aspect_ratio.video_rect.x = (width - attr->aspect_ratio.video_rect.width) / 2; + attr->aspect_ratio.video_rect.y = 0; + } + else + attr->aspect_ratio.mode = HI_ASPECT_RATIO_NONE; +} +} +} diff --git a/video/hisi/mpp5/VideoProcessor.h b/video/hisi/mpp5/VideoProcessor.h new file mode 100644 index 0000000..a499969 --- /dev/null +++ b/video/hisi/mpp5/VideoProcessor.h @@ -0,0 +1,49 @@ +#ifndef FD7AD900_2C3C_41D7_A5C2_6B4F2B79CE9E +#define FD7AD900_2C3C_41D7_A5C2_6B4F2B79CE9E +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "MPPChannel.h" +#include "../core/VideoProcessor.h" +#include "../../core/Notifications.h" +#include + +namespace Video +{ +namespace MPP5 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class VideoProcessor: public MPPChannel, public Core::VideoProcessor +{ +public: + VideoProcessor(); + ~VideoProcessor(); + +protected: + void start() override; + void stop() override; + void onConfigChanged(ParameterNames& names) override; + void onLink(Core::Component::Ptr pPrevious) override; + void onUnlink(Core::Component::Ptr pPrevious) override; + + void handlePreviousParameterChanged(const Poco::AutoPtr& pNotication); + + uint32_t groupID(); + void setVPSSGroupCrop(const Core::Rect& rect); + void setVPSSChannelRotation(int rotation, uint32_t channelID); + void setVPSSVideoDirection(int direction, hi_vpss_chn_attr* attr); +}; + +inline uint32_t VideoProcessor::groupID() +{ + return deviceID(); +} +} +} +#endif diff --git a/video/hisi/mpp5/audio-adp/audio_aac_adp.c b/video/hisi/mpp5/audio-adp/audio_aac_adp.c new file mode 100755 index 0000000..23080f0 --- /dev/null +++ b/video/hisi/mpp5/audio-adp/audio_aac_adp.c @@ -0,0 +1,1586 @@ +/* + Copyright (c), 2001-2022, Shenshu Tech. Co., Ltd. + */ + +#include "audio_aac_adp.h" +#include +#include +#include +#include "audio_dl_adp.h" +#include "securec.h" + +#define aac_check_false_return(x) \ + do { \ + if ((x) != HI_TRUE) { \ + return (-1); \ + } \ + } while (0) + +#define aac_bitrate_sel(is_mono, mono_bitrate, stereo_bitrate) ((is_mono) ? (mono_bitrate) : (stereo_bitrate)) + +#define AAC_ENC_LIB_NAME "libaac_enc.so" +#define AAC_DEC_LIB_NAME "libaac_dec.so" + +#define AACENC_BITRATE_16K 16000 +#define AACENC_BITRATE_24K 24000 +#define AACENC_BITRATE_32K 32000 +#define AACENC_BITRATE_44K 44000 +#define AACENC_BITRATE_48K 48000 +#define AACENC_BITRATE_64K 64000 +#define AACENC_BITRATE_96K 96000 +#define AACENC_BITRATE_128K 128000 +#define AACENC_BITRATE_132K 132000 +#define AACENC_BITRATE_144K 144000 +#define AACENC_BITRATE_192K 192000 +#define AACENC_BITRATE_256K 256000 +#define AACENC_BITRATE_265K 265000 +#define AACENC_BITRATE_288K 288000 +#define AACENC_BITRATE_320K 320000 + +#define AACENC_CHANNEL_SINGLE 1 +#define AACENC_CHANNEL_STEREO 2 + +#define DUMP_PATH_NAME_MAX_BYTES 256 +#define DUMP_MAX_TIMES 10000 + +/* aac enc lib */ +typedef hi_s32 (*aacenc_get_version_callback)(hi_aacenc_version *version); +typedef hi_s32 (*aacenc_init_default_config_callback)(hi_aacenc_config *config); +typedef hi_s32 (*aac_encoder_open_callback)(hi_aac_encoder **ph_aac_plus_enc, const hi_aacenc_config *config); +typedef hi_s32 (*aac_encoder_frame_callback)(hi_aac_encoder *aac_plus_enc, hi_s16 *pcm_buf, + hi_u8 *outbuf, hi_s32 *num_out_bytes); +typedef hi_void (*aac_encoder_close_callback)(hi_aac_encoder *aac_plus_enc); + +/* aac dec lib */ +typedef hi_s32 (*aacdec_get_version_callback)(hi_aacdec_version *version); +typedef hi_aac_decoder (*aac_init_decoder_callback)(hi_aacdec_transport_type tran_type); +typedef hi_void (*aac_free_decoder_callback)(hi_aac_decoder aac_decoder); +typedef hi_s32 (*aac_set_raw_mode_callback)(hi_aac_decoder aac_decoder, hi_s32 chans, hi_s32 samp_rate); +typedef hi_s32 (*aac_decode_find_sync_header_callback)( + hi_aac_decoder aac_decoder, hi_u8 **pp_inbuf_ptr, hi_s32 *bytes_left); +typedef hi_s32 (*aac_decode_frame_callback)(hi_aac_decoder aac_decoder, + hi_u8 **pp_inbuf_ptr, hi_s32 *bytes_left, hi_s16 *out_pcm); +typedef hi_s32 (*aac_get_last_frame_info_callback)(hi_aac_decoder aac_decoder, hi_aacdec_frame_info *aac_frame_info); +typedef hi_s32 (*aac_decoder_set_eos_flag_callback)(hi_aac_decoder aac_decoder, hi_s32 eosflag); +typedef hi_s32 (*aac_flush_codec_callback)(hi_aac_decoder aac_decoder); + +typedef struct { + hi_s32 open_cnt; + hi_void *lib_handle; + + aacenc_get_version_callback hi_aacenc_get_version; + aacenc_init_default_config_callback hi_aacenc_init_default_config; + aac_encoder_open_callback hi_aacenc_open; + aac_encoder_frame_callback hi_aacenc_frame; + aac_encoder_close_callback hi_aacenc_close; +} aacenc_fun; + +typedef struct { + hi_s32 open_cnt; + hi_void *lib_handle; + + aacdec_get_version_callback hi_aacdec_get_version; + aac_init_decoder_callback hi_aacdec_init_decoder; + aac_free_decoder_callback hi_aacdec_free_decoder; + aac_set_raw_mode_callback hi_aacdec_set_raw_mode; + aac_decode_find_sync_header_callback hi_aacdec_find_sync_header; + aac_decode_frame_callback hi_aacdec_frame; + aac_get_last_frame_info_callback hi_aacdec_get_last_frame_info; + aac_decoder_set_eos_flag_callback hi_aacdec_set_eos_flag; + aac_flush_codec_callback hi_aacdec_flush_codec; +} aacdec_fun; + +/* if need, define DUMP_AACENC */ +#ifdef DUMP_AACENC +FILE *g_in_enc_fd = NULL; +FILE *g_out_enc_fd = NULL; +#endif + +/* if need, define DUMP_AACDEC */ +#ifdef DUMP_AACDEC +FILE *g_in_dec_fd = NULL; +FILE *g_out_dec_fd = NULL; +FILE *g_out_dec_left_fd = NULL; +#endif + +int g_cnt_aenc = 100000; +int g_cnt_adec = 100000; + +static hi_s32 g_aac_enc_handle = 0; +static hi_s32 g_aac_dec_handle = 0; + +#if defined(OT_AAC_USE_STATIC_MODULE_REGISTER) && defined(OT_AAC_HAVE_SBR_LIB) +static hi_bool g_aac_enc_static_reg = HI_FALSE; +static hi_bool g_aac_dec_static_reg = HI_FALSE; +#endif + +static aacenc_fun g_aac_enc_func = {0}; +static aacdec_fun g_aac_dec_func = {0}; + +#if defined(OT_AAC_USE_STATIC_MODULE_REGISTER) && defined(OT_AAC_HAVE_SBR_LIB) +static hi_s32 aac_init_sbr_enc_lib(void) +{ + hi_s32 ret; + hi_void *sbr_enc_handle = hi_aac_sbrenc_get_handle(); + + if (g_aac_enc_static_reg == HI_TRUE) { + return HI_SUCCESS; + } + + ret = hi_aacenc_register_mod(sbr_enc_handle); + if (ret != HI_SUCCESS) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "init sbr_enc lib fail!\n"); + return HI_ERR_AENC_NOT_SUPPORT; + } + + g_aac_enc_static_reg = HI_TRUE; + + return HI_SUCCESS; +} + +static hi_s32 aac_init_sbr_dec_lib(void) +{ + hi_s32 ret; + hi_void *sbr_dec_handle = hi_aac_sbrdec_get_handle(); + + if (g_aac_dec_static_reg == HI_TRUE) { + return HI_SUCCESS; + } + + ret = hi_aacdec_register_mod(sbr_dec_handle); + if (ret != HI_SUCCESS) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "init sbr_dec lib fail!\n"); + return HI_ERR_ADEC_NOT_SUPPORT; + } + + g_aac_dec_static_reg = HI_TRUE; + + return HI_SUCCESS; +} +#endif + +#ifdef OT_AAC_USE_STATIC_MODULE_REGISTER +static hi_s32 aac_init_enc_lib(void) +{ + g_aac_enc_func.hi_aacenc_get_version = hi_aacenc_get_version; + g_aac_enc_func.hi_aacenc_init_default_config = hi_aacenc_init_default_config; + g_aac_enc_func.hi_aacenc_open = hi_aacenc_open; + g_aac_enc_func.hi_aacenc_frame = hi_aacenc_frame; + g_aac_enc_func.hi_aacenc_close = hi_aacenc_close; +#ifdef OT_AAC_HAVE_SBR_LIB + return aac_init_sbr_enc_lib(); +#endif + return HI_SUCCESS; +} +#else +static hi_s32 aac_enc_lib_dlsym(aacenc_fun *aac_enc_func) +{ + hi_s32 ret; + + ret = audio_dlsym((hi_void **)&(aac_enc_func->hi_aacenc_get_version), + aac_enc_func->lib_handle, "hi_aacenc_get_version"); + if (ret != HI_SUCCESS) { + return HI_ERR_AENC_NOT_SUPPORT; + } + + ret = audio_dlsym((hi_void **)&(aac_enc_func->hi_aacenc_init_default_config), + aac_enc_func->lib_handle, "hi_aacenc_init_default_config"); + if (ret != HI_SUCCESS) { + return HI_ERR_AENC_NOT_SUPPORT; + } + + ret = audio_dlsym((hi_void **)&(aac_enc_func->hi_aacenc_open), + aac_enc_func->lib_handle, "hi_aacenc_open"); + if (ret != HI_SUCCESS) { + return HI_ERR_AENC_NOT_SUPPORT; + } + + ret = audio_dlsym((hi_void **)&(aac_enc_func->hi_aacenc_frame), + aac_enc_func->lib_handle, "hi_aacenc_frame"); + if (ret != HI_SUCCESS) { + return HI_ERR_AENC_NOT_SUPPORT; + } + + ret = audio_dlsym((hi_void **)&(aac_enc_func->hi_aacenc_close), + aac_enc_func->lib_handle, "hi_aacenc_close"); + if (ret != HI_SUCCESS) { + return HI_ERR_AENC_NOT_SUPPORT; + } + + return HI_SUCCESS; +} + +static hi_s32 aac_init_enc_lib(void) +{ + if (g_aac_enc_func.open_cnt == 0) { + hi_s32 ret; + aacenc_fun aac_enc_func = {0}; + + ret = audio_dlopen(&(aac_enc_func.lib_handle), AAC_ENC_LIB_NAME); + if (ret != HI_SUCCESS) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "load aenc lib fail!\n"); + return HI_ERR_AENC_NOT_SUPPORT; + } + + ret = aac_enc_lib_dlsym(&aac_enc_func); + if (ret != HI_SUCCESS) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "find symbol error!\n"); + audio_dlclose(aac_enc_func.lib_handle); + aac_enc_func.lib_handle = HI_NULL; + return HI_ERR_AENC_NOT_SUPPORT; + } + + (hi_void)memcpy_s(&g_aac_enc_func, sizeof(g_aac_enc_func), &aac_enc_func, sizeof(aac_enc_func)); + } + g_aac_enc_func.open_cnt++; + return HI_SUCCESS; +} +#endif + +hi_void aac_deinit_enc_lib(hi_void) +{ + hi_s32 ret; + if (g_aac_enc_func.open_cnt != 0) { + g_aac_enc_func.open_cnt--; + } + + if (g_aac_enc_func.open_cnt == 0) { +#ifndef OT_AAC_USE_STATIC_MODULE_REGISTER + if (g_aac_enc_func.lib_handle != HI_NULL) { + audio_dlclose(g_aac_enc_func.lib_handle); + } +#endif + ret = memset_s(&g_aac_enc_func, sizeof(aacenc_fun), 0, sizeof(aacenc_fun)); + if (ret != EOK) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "memset_s aenc fun fail!\n"); + return; + } + } + + return; +} + +hi_s32 aacenc_get_version_adp(hi_aacenc_version *version) +{ + if (g_aac_enc_func.hi_aacenc_get_version == NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "call aac function fail!\n"); + return HI_ERR_AENC_NOT_SUPPORT; + } + return g_aac_enc_func.hi_aacenc_get_version(version); +} + +hi_s32 aac_init_default_config_adp(hi_aacenc_config *config) +{ + if (g_aac_enc_func.hi_aacenc_init_default_config == NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "call aac function fail!\n"); + return HI_ERR_AENC_NOT_SUPPORT; + } + return g_aac_enc_func.hi_aacenc_init_default_config(config); +} + +hi_s32 aac_encoder_open_adp(hi_aac_encoder **ph_aac_plus_enc, const hi_aacenc_config *config) +{ + if (g_aac_enc_func.hi_aacenc_open == NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "call aac function fail!\n"); + return HI_ERR_AENC_NOT_SUPPORT; + } + return g_aac_enc_func.hi_aacenc_open(ph_aac_plus_enc, config); +} + +hi_s32 aac_encoder_frame_adp(hi_aac_encoder *aac_plus_enc, hi_s16 *pcm_buf, hi_u8 *outbuf, hi_s32 *num_out_bytes) +{ + if (g_aac_enc_func.hi_aacenc_frame == NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "call aac function fail!\n"); + return HI_ERR_AENC_NOT_SUPPORT; + } + return g_aac_enc_func.hi_aacenc_frame(aac_plus_enc, pcm_buf, outbuf, num_out_bytes); +} + +hi_void aac_encoder_close_adp(hi_aac_encoder *aac_plus_enc) +{ + if (g_aac_enc_func.hi_aacenc_close == NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "call aac function fail!\n"); + return; + } + return g_aac_enc_func.hi_aacenc_close(aac_plus_enc); +} + +#ifdef OT_AAC_USE_STATIC_MODULE_REGISTER +static hi_s32 aac_init_dec_lib(void) +{ + g_aac_dec_func.hi_aacdec_get_version = hi_aacdec_get_version; + g_aac_dec_func.hi_aacdec_init_decoder = hi_aacdec_init_decoder; + g_aac_dec_func.hi_aacdec_free_decoder = hi_aacdec_free_decoder; + g_aac_dec_func.hi_aacdec_set_raw_mode = hi_aacdec_set_raw_mode; + g_aac_dec_func.hi_aacdec_find_sync_header = hi_aacdec_find_sync_header; + g_aac_dec_func.hi_aacdec_frame = hi_aacdec_frame; + g_aac_dec_func.hi_aacdec_get_last_frame_info = hi_aacdec_get_last_frame_info; + g_aac_dec_func.hi_aacdec_set_eos_flag = hi_aacdec_set_eos_flag; + g_aac_dec_func.hi_aacdec_flush_codec = hi_aacdec_flush_codec; +#ifdef OT_AAC_HAVE_SBR_LIB + return aac_init_sbr_dec_lib(); +#endif + return HI_SUCCESS; +} +#else +static hi_s32 aac_dec_lib_dlsym(aacdec_fun *aac_dec_func) +{ + hi_s32 ret; + ret = audio_dlsym((hi_void **)&(aac_dec_func->hi_aacdec_get_version), + aac_dec_func->lib_handle, "hi_aacdec_get_version"); + if (ret != HI_SUCCESS) { + return HI_ERR_ADEC_NOT_SUPPORT; + } + + ret = audio_dlsym((hi_void **)&(aac_dec_func->hi_aacdec_init_decoder), + aac_dec_func->lib_handle, "hi_aacdec_init_decoder"); + if (ret != HI_SUCCESS) { + return HI_ERR_ADEC_NOT_SUPPORT; + } + + ret = audio_dlsym((hi_void **)&(aac_dec_func->hi_aacdec_free_decoder), + aac_dec_func->lib_handle, "hi_aacdec_free_decoder"); + if (ret != HI_SUCCESS) { + return HI_ERR_ADEC_NOT_SUPPORT; + } + + ret = audio_dlsym((hi_void **)&(aac_dec_func->hi_aacdec_set_raw_mode), + aac_dec_func->lib_handle, "hi_aacdec_set_raw_mode"); + if (ret != HI_SUCCESS) { + return HI_ERR_ADEC_NOT_SUPPORT; + } + + ret = audio_dlsym((hi_void **)&(aac_dec_func->hi_aacdec_find_sync_header), + aac_dec_func->lib_handle, "hi_aacdec_find_sync_header"); + if (ret != HI_SUCCESS) { + return HI_ERR_ADEC_NOT_SUPPORT; + } + + ret = audio_dlsym((hi_void **)&(aac_dec_func->hi_aacdec_frame), aac_dec_func->lib_handle, "hi_aacdec_frame"); + if (ret != HI_SUCCESS) { + return HI_ERR_ADEC_NOT_SUPPORT; + } + + ret = audio_dlsym((hi_void **)&(aac_dec_func->hi_aacdec_get_last_frame_info), + aac_dec_func->lib_handle, "hi_aacdec_get_last_frame_info"); + if (ret != HI_SUCCESS) { + return HI_ERR_ADEC_NOT_SUPPORT; + } + + ret = audio_dlsym((hi_void **)&(aac_dec_func->hi_aacdec_set_eos_flag), + aac_dec_func->lib_handle, "hi_aacdec_set_eos_flag"); + if (ret != HI_SUCCESS) { + return HI_ERR_ADEC_NOT_SUPPORT; + } + + ret = audio_dlsym((hi_void **)&(aac_dec_func->hi_aacdec_flush_codec), + aac_dec_func->lib_handle, "hi_aacdec_flush_codec"); + if (ret != HI_SUCCESS) { + return HI_ERR_ADEC_NOT_SUPPORT; + } + + return HI_SUCCESS; +} + +static hi_s32 aac_init_dec_lib(void) +{ + if (g_aac_dec_func.open_cnt == 0) { + hi_s32 ret; + aacdec_fun aac_dec_func = {0}; + + ret = audio_dlopen(&(aac_dec_func.lib_handle), AAC_DEC_LIB_NAME); + if (ret != HI_SUCCESS) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "load aenc lib fail!\n"); + return HI_ERR_ADEC_NOT_SUPPORT; + } + + ret = aac_dec_lib_dlsym(&aac_dec_func); + if (ret != HI_SUCCESS) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "find symbol error!\n"); + audio_dlclose(aac_dec_func.lib_handle); + aac_dec_func.lib_handle = HI_NULL; + return HI_ERR_ADEC_NOT_SUPPORT; + } + + (hi_void)memcpy_s(&g_aac_dec_func, sizeof(g_aac_dec_func), &aac_dec_func, sizeof(aac_dec_func)); + } + g_aac_dec_func.open_cnt++; + + return HI_SUCCESS; +} +#endif + +hi_void aac_deinit_dec_lib(hi_void) +{ + hi_s32 ret; + if (g_aac_dec_func.open_cnt != 0) { + g_aac_dec_func.open_cnt--; + } + + if (g_aac_dec_func.open_cnt == 0) { +#ifndef OT_AAC_USE_STATIC_MODULE_REGISTER + if (g_aac_dec_func.lib_handle != HI_NULL) { + audio_dlclose(g_aac_dec_func.lib_handle); + } +#endif + ret = memset_s(&g_aac_dec_func, sizeof(aacdec_fun), 0, sizeof(aacdec_fun)); + if (ret != EOK) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "memset_s adec lib fail!\n"); + return; + } + } + + return; +} + +hi_s32 aacdec_get_version_adp(hi_aacdec_version *version) +{ + if (g_aac_dec_func.hi_aacdec_get_version == NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "call aac function fail!\n"); + return HI_ERR_ADEC_NOT_SUPPORT; + } + return g_aac_dec_func.hi_aacdec_get_version(version); +} + +hi_aac_decoder aac_init_decoder_adp(hi_aacdec_transport_type tran_type) +{ + if (g_aac_dec_func.hi_aacdec_init_decoder == NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "call aac function fail!\n"); + return HI_NULL; + } + return g_aac_dec_func.hi_aacdec_init_decoder(tran_type); +} + +hi_void aac_free_decoder_adp(hi_aac_decoder aac_decoder) +{ + if (g_aac_dec_func.hi_aacdec_free_decoder == NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "call aac function fail!\n"); + return; + } + return g_aac_dec_func.hi_aacdec_free_decoder(aac_decoder); +} + +hi_s32 aac_set_raw_mode_adp(hi_aac_decoder aac_decoder, hi_s32 chans, hi_s32 samp_rate) +{ + if (g_aac_dec_func.hi_aacdec_set_raw_mode == NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "call aac function fail!\n"); + return HI_ERR_ADEC_NOT_SUPPORT; + } + return g_aac_dec_func.hi_aacdec_set_raw_mode(aac_decoder, chans, samp_rate); +} + +hi_s32 aac_decode_find_sync_header_adp(hi_aac_decoder aac_decoder, hi_u8 **pp_inbuf_ptr, hi_s32 *bytes_left) +{ + if (g_aac_dec_func.hi_aacdec_find_sync_header == NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "call aac function fail!\n"); + return HI_ERR_ADEC_NOT_SUPPORT; + } + return g_aac_dec_func.hi_aacdec_find_sync_header(aac_decoder, pp_inbuf_ptr, bytes_left); +} + +hi_s32 aac_decode_frame_adp(hi_aac_decoder aac_decoder, hi_u8 **pp_inbuf_ptr, hi_s32 *bytes_left, hi_s16 *out_pcm) +{ + if (g_aac_dec_func.hi_aacdec_frame == NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "call aac function fail!\n"); + return HI_ERR_ADEC_NOT_SUPPORT; + } + return g_aac_dec_func.hi_aacdec_frame(aac_decoder, pp_inbuf_ptr, bytes_left, out_pcm); +} + +hi_s32 aac_get_last_frame_info_adp(hi_aac_decoder aac_decoder, hi_aacdec_frame_info *aac_frame_info) +{ + if (g_aac_dec_func.hi_aacdec_get_last_frame_info == NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "call aac function fail!\n"); + return HI_ERR_ADEC_NOT_SUPPORT; + } + return g_aac_dec_func.hi_aacdec_get_last_frame_info(aac_decoder, aac_frame_info); +} + +hi_s32 aac_decoder_set_eos_flag_adp(hi_aac_decoder aac_decoder, hi_s32 eosflag) +{ + if (g_aac_dec_func.hi_aacdec_set_eos_flag == NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "call aac function fail!\n"); + return HI_ERR_ADEC_NOT_SUPPORT; + } + return g_aac_dec_func.hi_aacdec_set_eos_flag(aac_decoder, eosflag); +} + +hi_s32 aac_flush_codec_adp(hi_aac_decoder aac_decoder) +{ + if (g_aac_dec_func.hi_aacdec_flush_codec == NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "call aac function fail!\n"); + return HI_ERR_ADEC_NOT_SUPPORT; + } + return g_aac_dec_func.hi_aacdec_flush_codec(aac_decoder); +} + +static hi_s32 aenc_check_aac_attr(const hi_aenc_attr_aac *aac_attr) +{ + if (aac_attr->bit_width != HI_AUDIO_BIT_WIDTH_16) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "invalid bitwidth for AAC encoder"); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + if (aac_attr->snd_mode >= HI_AUDIO_SOUND_MODE_BUTT) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "invalid sound mode for AAC encoder"); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + if ((aac_attr->aac_type == HI_AAC_TYPE_EAACPLUS) && (aac_attr->snd_mode != HI_AUDIO_SOUND_MODE_STEREO)) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "invalid sound mode for AAC encoder"); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + if (aac_attr->transport_type == HI_AAC_TRANSPORT_TYPE_ADTS) { + if ((aac_attr->aac_type == HI_AAC_TYPE_AACLD) || + (aac_attr->aac_type == HI_AAC_TYPE_AACELD)) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "HI_AACLD or HI_AACELD not support HI_AAC_TRANSPORT_TYPE_ADTS"); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + } + + return HI_SUCCESS; +} + +static hi_s32 aenc_get_aaclc_bitrate(const hi_aacenc_config *config, hi_s32 *min_rate, + hi_s32 *max_rate, hi_s32 *recommemd_rate) +{ + hi_bool chn_single = (config->num_channels_in == AACENC_CHANNEL_SINGLE) ? HI_TRUE : HI_FALSE; + + if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_32000) { + *min_rate = AACENC_BITRATE_32K; + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_192K, AACENC_BITRATE_320K); + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_48K, AACENC_BITRATE_128K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_44100) { + *min_rate = AACENC_BITRATE_48K; + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_265K, AACENC_BITRATE_320K); + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_64K, AACENC_BITRATE_128K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_48000) { + *min_rate = AACENC_BITRATE_48K; + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_288K, AACENC_BITRATE_320K); + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_64K, AACENC_BITRATE_128K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_16000) { + *min_rate = AACENC_BITRATE_24K; + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_96K, AACENC_BITRATE_192K); + *recommemd_rate = AACENC_BITRATE_48K; + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_8000) { + *min_rate = AACENC_BITRATE_16K; + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_48K, AACENC_BITRATE_96K); + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_24K, AACENC_BITRATE_32K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_24000) { + *min_rate = AACENC_BITRATE_32K; + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_144K, AACENC_BITRATE_288K); + *recommemd_rate = AACENC_BITRATE_48K; + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_22050) { + *min_rate = AACENC_BITRATE_32K; + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_132K, AACENC_BITRATE_265K); + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_64K, AACENC_BITRATE_48K); + } else { + printf("HI_AACLC invalid samplerate(%d)\n", config->sample_rate); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + return HI_SUCCESS; +} + +static hi_s32 aenc_check_aaclc_config(const hi_aacenc_config *config) +{ + hi_s32 min_bit_rate = 0; + hi_s32 max_bit_rate = 0; + hi_s32 recommemd_rate = 0; + hi_s32 ret; + + if (config->coder_format == HI_AACLC) { + if (config->num_channels_out != config->num_channels_in) { + printf("HI_AACLC num_channels_out(%d) in not equal to num_channels_in(%d)\n", + config->num_channels_out, config->num_channels_in); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + ret = aenc_get_aaclc_bitrate(config, &min_bit_rate, &max_bit_rate, &recommemd_rate); + if (ret != HI_SUCCESS) { + return ret; + } + + if ((config->bit_rate < min_bit_rate) || (config->bit_rate > max_bit_rate)) { + printf("HI_AACLC %d Hz bit_rate(%d) should be %d ~ %d, recommed %d\n", + config->sample_rate, config->bit_rate, min_bit_rate, max_bit_rate, recommemd_rate); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + } else { + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + return HI_SUCCESS; +} + +static hi_s32 aenc_get_eaac_bitrate(const hi_aacenc_config *config, hi_s32 *min_rate, + hi_s32 *max_rate, hi_s32 *recommemd_rate) +{ + hi_bool chn_single = (config->num_channels_in == AACENC_CHANNEL_SINGLE) ? HI_TRUE : HI_FALSE; + + if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_32000) { + *min_rate = AACENC_BITRATE_32K; + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_64K, AACENC_BITRATE_128K); + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_48K, AACENC_BITRATE_64K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_44100) { + *min_rate = AACENC_BITRATE_32K; + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_64K, AACENC_BITRATE_128K); + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_48K, AACENC_BITRATE_64K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_48000) { + *min_rate = AACENC_BITRATE_32K; + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_64K, AACENC_BITRATE_128K); + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_48K, AACENC_BITRATE_64K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_16000) { + *min_rate = AACENC_BITRATE_24K; + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_48K, AACENC_BITRATE_96K); + *recommemd_rate = AACENC_BITRATE_32K; + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_22050) { + *min_rate = AACENC_BITRATE_32K; + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_64K, AACENC_BITRATE_128K); + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_48K, AACENC_BITRATE_64K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_24000) { + *min_rate = AACENC_BITRATE_32K; + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_64K, AACENC_BITRATE_128K); + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_48K, AACENC_BITRATE_64K); + } else { + printf("HI_EAAC invalid samplerate(%d)\n", config->sample_rate); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + return HI_SUCCESS; +} + +static hi_s32 aenc_check_eaac_config(const hi_aacenc_config *config) +{ + hi_s32 min_bit_rate = 0; + hi_s32 max_bit_rate = 0; + hi_s32 recommemd_rate = 0; + hi_s32 ret; + + if (config->coder_format == HI_EAAC) { + if (config->num_channels_out != config->num_channels_in) { + printf("HI_EAAC num_channels_out(%d) is not equal to num_channels_in(%d)\n", + config->num_channels_out, config->num_channels_in); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + ret = aenc_get_eaac_bitrate(config, &min_bit_rate, &max_bit_rate, &recommemd_rate); + if (ret != HI_SUCCESS) { + return ret; + } + + if ((config->bit_rate < min_bit_rate) || (config->bit_rate > max_bit_rate)) { + printf("HI_EAAC %d Hz bit_rate(%d) should be %d ~ %d, recommed %d\n", + config->sample_rate, config->bit_rate, min_bit_rate, max_bit_rate, recommemd_rate); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + } else { + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + return HI_SUCCESS; +} + +static hi_s32 aenc_get_eaacplus_bitrate(const hi_aacenc_config *config, hi_s32 *min_rate, + hi_s32 *max_rate, hi_s32 *recommemd_rate) +{ + if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_32000) { + *min_rate = AACENC_BITRATE_16K; + *max_rate = AACENC_BITRATE_64K; + *recommemd_rate = AACENC_BITRATE_32K; + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_44100) { + *min_rate = AACENC_BITRATE_16K; + *max_rate = AACENC_BITRATE_64K; + *recommemd_rate = AACENC_BITRATE_48K; + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_48000) { + *min_rate = AACENC_BITRATE_16K; + *max_rate = AACENC_BITRATE_64K; + *recommemd_rate = AACENC_BITRATE_48K; + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_16000) { + *min_rate = AACENC_BITRATE_16K; + *max_rate = AACENC_BITRATE_48K; + *recommemd_rate = AACENC_BITRATE_32K; + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_22050) { + *min_rate = AACENC_BITRATE_16K; + *max_rate = AACENC_BITRATE_64K; + *recommemd_rate = AACENC_BITRATE_32K; + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_24000) { + *min_rate = AACENC_BITRATE_16K; + *max_rate = AACENC_BITRATE_64K; + *recommemd_rate = AACENC_BITRATE_32K; + } else { + printf("HI_EAACPLUS invalid samplerate(%d)\n", config->sample_rate); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + return HI_SUCCESS; +} + +static hi_s32 aenc_check_eaacplus_config(const hi_aacenc_config *config) +{ + hi_s32 min_bit_rate = 0; + hi_s32 max_bit_rate = 0; + hi_s32 recommemd_rate = 0; + hi_s32 ret; + + if (config->coder_format == HI_EAACPLUS) { + if ((config->num_channels_out != AACENC_CHANNEL_STEREO) || + (config->num_channels_in != AACENC_CHANNEL_STEREO)) { + printf("HI_EAACPLUS num_channels_out(%d) and num_channels_in(%d) should be 2\n", + config->num_channels_out, config->num_channels_in); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + ret = aenc_get_eaacplus_bitrate(config, &min_bit_rate, &max_bit_rate, &recommemd_rate); + if (ret != HI_SUCCESS) { + return ret; + } + + if ((config->bit_rate < min_bit_rate) || (config->bit_rate > max_bit_rate)) { + printf("HI_EAACPLUS %d Hz bit_rate(%d) should be %d ~ %d, recommed %d\n", + config->sample_rate, config->bit_rate, min_bit_rate, max_bit_rate, recommemd_rate); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + } else { + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + return HI_SUCCESS; +} + +static hi_s32 aenc_get_aacld_bitrate(const hi_aacenc_config *config, hi_s32 *min_rate, + hi_s32 *max_rate, hi_s32 *recommemd_rate) +{ + hi_bool chn_single = (config->num_channels_in == AACENC_CHANNEL_SINGLE) ? HI_TRUE : HI_FALSE; + + if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_32000) { + *min_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_48K, AACENC_BITRATE_64K); + *max_rate = AACENC_BITRATE_320K; + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_64K, AACENC_BITRATE_128K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_44100) { + *min_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_64K, AACENC_BITRATE_44K); + *max_rate = AACENC_BITRATE_320K; + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_128K, AACENC_BITRATE_256K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_48000) { + *min_rate = AACENC_BITRATE_64K; + *max_rate = AACENC_BITRATE_320K; + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_128K, AACENC_BITRATE_256K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_16000) { + *min_rate = (chn_single) ? AACENC_BITRATE_24K : AACENC_BITRATE_32K; + *max_rate = (chn_single) ? AACENC_BITRATE_192K : AACENC_BITRATE_320K; + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_48K, AACENC_BITRATE_96K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_8000) { + *min_rate = AACENC_BITRATE_16K; + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_96K, AACENC_BITRATE_192K); + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_24K, AACENC_BITRATE_48K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_24000) { + *min_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_32K, AACENC_BITRATE_48K); + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_256K, AACENC_BITRATE_320K); + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_64K, AACENC_BITRATE_128K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_22050) { + *min_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_32K, AACENC_BITRATE_48K); + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_256K, AACENC_BITRATE_320K); + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_48K, AACENC_BITRATE_96K); + } else { + printf("HI_AACLD invalid samplerate(%d)\n", config->sample_rate); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + return HI_SUCCESS; +} + +static hi_s32 aenc_check_aacld_config(const hi_aacenc_config *config) +{ + hi_s32 min_bit_rate = 0; + hi_s32 max_bit_rate = 0; + hi_s32 recommemd_rate = 0; + hi_s32 ret; + + if (config->coder_format == HI_AACLD) { + if (config->num_channels_out != config->num_channels_in) { + printf("HI_AACLD num_channels_out(%d) in not equal to num_channels_in(%d)\n", + config->num_channels_out, config->num_channels_in); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + ret = aenc_get_aacld_bitrate(config, &min_bit_rate, &max_bit_rate, &recommemd_rate); + if (ret != HI_SUCCESS) { + return ret; + } + + if ((config->bit_rate < min_bit_rate) || (config->bit_rate > max_bit_rate)) { + printf("HI_AACLD %d Hz bit_rate(%d) should be %d ~ %d, recommed %d\n", + config->sample_rate, config->bit_rate, min_bit_rate, max_bit_rate, recommemd_rate); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + } else { + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + return HI_SUCCESS; +} + +static hi_s32 aenc_get_aaceld_bitrate(const hi_aacenc_config *config, hi_s32 *min_rate, + hi_s32 *max_rate, hi_s32 *recommemd_rate) +{ + hi_bool chn_single = (config->num_channels_in == AACENC_CHANNEL_SINGLE) ? HI_TRUE : HI_FALSE; + + if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_32000) { + *min_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_32K, AACENC_BITRATE_64K); + *max_rate = AACENC_BITRATE_320K; + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_64K, AACENC_BITRATE_128K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_44100) { + *min_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_96K, AACENC_BITRATE_192K); + *max_rate = AACENC_BITRATE_320K; + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_128K, AACENC_BITRATE_256K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_48000) { + *min_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_96K, AACENC_BITRATE_192K); + *max_rate = AACENC_BITRATE_320K; + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_128K, AACENC_BITRATE_256K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_16000) { + *min_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_16K, AACENC_BITRATE_32K); + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_256K, AACENC_BITRATE_320K); + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_48K, AACENC_BITRATE_96K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_8000) { + *min_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_32K, AACENC_BITRATE_64K); + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_96K, AACENC_BITRATE_192K); + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_32K, AACENC_BITRATE_64K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_24000) { + *min_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_24K, AACENC_BITRATE_32K); + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_256K, AACENC_BITRATE_320K); + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_64K, AACENC_BITRATE_128K); + } else if (config->sample_rate == HI_AUDIO_SAMPLE_RATE_22050) { + *min_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_24K, AACENC_BITRATE_32K); + *max_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_256K, AACENC_BITRATE_320K); + *recommemd_rate = aac_bitrate_sel(chn_single, AACENC_BITRATE_48K, AACENC_BITRATE_96K); + } else { + printf("HI_AACELD invalid samplerate(%d)\n", config->sample_rate); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + return HI_SUCCESS; +} + +static hi_s32 aenc_check_aaceld_config(const hi_aacenc_config *config) +{ + hi_s32 min_bit_rate = 0; + hi_s32 max_bit_rate = 0; + hi_s32 recommemd_rate = 0; + hi_s32 ret; + + if (config->coder_format == HI_AACELD) { + if (config->num_channels_out != config->num_channels_in) { + printf("HI_AACELD num_channels_out(%d) in not equal to num_channels_in(%d)\n", + config->num_channels_out, config->num_channels_in); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + ret = aenc_get_aaceld_bitrate(config, &min_bit_rate, &max_bit_rate, &recommemd_rate); + if (ret != HI_SUCCESS) { + return ret; + } + + if ((config->bit_rate < min_bit_rate) || (config->bit_rate > max_bit_rate)) { + printf("AACELD %d Hz bit_rate(%d) should be %d ~ %d, recommed %d\n", + config->sample_rate, config->bit_rate, min_bit_rate, max_bit_rate, recommemd_rate); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + } else { + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + return HI_SUCCESS; +} + +static hi_s32 aenc_aac_check_coder_format(hi_aacenc_format coder_format) +{ + if (coder_format != HI_AACLC && coder_format != HI_EAAC && + coder_format != HI_EAACPLUS && coder_format != HI_AACLD && + coder_format != HI_AACELD) { + printf("aacenc coder_format(%d) invalid\n", coder_format); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + return HI_SUCCESS; +} + +hi_s32 aenc_aac_check_config(const hi_aacenc_config *config) +{ + hi_s32 ret = 0; + + if (config == NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "config is null"); + return HI_ERR_AENC_NULL_PTR; + } + + if (aenc_aac_check_coder_format(config->coder_format) != HI_SUCCESS) { + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + if (config->quality != HI_AACENC_QUALITY_EXCELLENT && config->quality != HI_AACENC_QUALITY_HIGH && + config->quality != HI_AACENC_QUALITY_MEDIUM && config->quality != HI_AACENC_QUALITY_LOW) { + printf("aacenc quality(%d) invalid\n", config->quality); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + if (config->bits_per_sample != 16) { /* 16: 16bit */ + printf("aacenc bits_per_sample(%d) should be 16\n", config->bits_per_sample); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + if ((config->trans_type < 0) || (config->trans_type > HI_AACENC_LATM_MCP1)) { + printf("invalid trans_type(%d), not in [0, 2]\n", config->trans_type); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + if ((config->band_width != 0) && ((config->band_width < 1000) || /* 1000: band width */ + (config->band_width > config->sample_rate / 2))) { /* 2: half */ + printf("AAC band_width(%d) should be 0, or 1000 ~ %d\n", + config->band_width, config->sample_rate / 2); /* 2: half */ + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + if (config->coder_format == HI_AACLC) { + ret = aenc_check_aaclc_config(config); + } else if (config->coder_format == HI_EAAC) { + ret = aenc_check_eaac_config(config); + } else if (config->coder_format == HI_EAACPLUS) { + ret = aenc_check_eaacplus_config(config); + } else if (config->coder_format == HI_AACLD) { + ret = aenc_check_aacld_config(config); + } else if (config->coder_format == HI_AACELD) { + ret = aenc_check_aaceld_config(config); + } + + return ret; +} + +static hi_void aenc_aac_init_config(const hi_aenc_attr_aac *attr, hi_aacenc_config *config) +{ + config->coder_format = (hi_aacenc_format)attr->aac_type; + config->bit_rate = attr->bit_rate; + config->bits_per_sample = 8 * (1 << ((hi_u32)attr->bit_width)); /* 8: 8bit */ + config->sample_rate = attr->sample_rate; + config->band_width = attr->band_width; + config->trans_type = (hi_aacenc_transport_type)attr->transport_type; + + if ((attr->snd_mode == HI_AUDIO_SOUND_MODE_MONO) && (attr->aac_type != HI_AAC_TYPE_EAACPLUS)) { + config->num_channels_in = AACENC_CHANNEL_SINGLE; + config->num_channels_out = AACENC_CHANNEL_SINGLE; + } else { + config->num_channels_in = AACENC_CHANNEL_STEREO; + config->num_channels_out = AACENC_CHANNEL_STEREO; + } + + config->quality = HI_AACENC_QUALITY_HIGH; +} + +#ifdef DUMP_AACENC +static hi_void init_encoder_dump_file(const hi_aacenc_config *config) +{ + hi_s32 ret; + hi_char name_in[DUMP_PATH_NAME_MAX_BYTES] = {'\0'}; + hi_char name_out[DUMP_PATH_NAME_MAX_BYTES] = {'\0'}; + + ret = snprintf_s(name_in, DUMP_PATH_NAME_MAX_BYTES, DUMP_PATH_NAME_MAX_BYTES - 1, + "aacenc_sin_t%d_t%d.raw", config->coder_format, config->trans_type); + if (ret < 0) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "get encoder in dump name failed"); + return; + } + + ret = snprintf_s(name_out, DUMP_PATH_NAME_MAX_BYTES, DUMP_PATH_NAME_MAX_BYTES - 1, + "aacenc_sout_t%d_t%d.aac", config->coder_format, config->trans_type); + if (ret < 0) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "get encoder in dump name failed"); + return; + } + + g_in_enc_fd = fopen(name_in, "w+"); + if (g_in_enc_fd == HI_NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "open encoder in dump name failed"); + } + + g_out_enc_fd = fopen(name_out, "w+"); + if (g_out_enc_fd == HI_NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "open encoder out dump name failed"); + } + + g_cnt_aenc = DUMP_MAX_TIMES; +} +#endif + +static hi_s32 open_aac_encoder_check_and_init(hi_aenc_attr_aac *attr, hi_aacenc_config *config) +{ + hi_s32 ret; + + /* check attribute of encoder */ + ret = aenc_check_aac_attr(attr); + if (ret) { + return ret; + } + + /* set default config to encoder */ + ret = aac_init_default_config_adp(config); + if (ret) { + printf("[func]:%s [line]:%d ret:0x%x.#########\n", __FUNCTION__, __LINE__, ret); + return ret; + } + + aenc_aac_init_config(attr, config); + + ret = aenc_aac_check_config(config); + if (ret) { + printf("[func]:%s [line]:%d ret:0x%x.#########\n", __FUNCTION__, __LINE__, ret); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + return HI_SUCCESS; +} + +hi_s32 open_aac_encoder(hi_void *encoder_attr, hi_void **pp_encoder) +{ + aenc_aac_encoder *encoder = NULL; + hi_aenc_attr_aac *attr = (hi_aenc_attr_aac *)encoder_attr; + hi_s32 ret; + hi_aacenc_config config = {0}; + + aac_check_false_return(encoder_attr != NULL); + aac_check_false_return(pp_encoder != NULL); + + ret = open_aac_encoder_check_and_init(attr, &config); + if (ret != HI_SUCCESS) { + return ret; + } + + /* allocate memory for encoder */ + encoder = (aenc_aac_encoder *)malloc(sizeof(aenc_aac_encoder)); + if (encoder == NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "no memory"); + return HI_ERR_AENC_NO_MEM; + } + ret = memset_s(encoder, sizeof(aenc_aac_encoder), 0, sizeof(aenc_aac_encoder)); + if (ret != EOK) { + free(encoder); + printf("[func]:%s [line]:%d memset_s fail, ret:0x%x.#########\n", __FUNCTION__, __LINE__, ret); + return HI_ERR_AENC_NOT_PERM; + } + + *pp_encoder = (hi_void *)encoder; + + /* create encoder */ + ret = aac_encoder_open_adp(&encoder->aac_state, &config); + if (ret) { + free(encoder); + printf("[func]:%s [line]:%d ret:0x%x.#########\n", __FUNCTION__, __LINE__, ret); + return ret; + } + + ret = memcpy_s(&encoder->aac_attr, sizeof(encoder->aac_attr), attr, sizeof(*attr)); + if (ret != EOK) { + aac_encoder_close_adp(encoder->aac_state); + free(encoder); + printf("[func]:%s [line]:%d memcpy_s ret:0x%x.#########\n", __FUNCTION__, __LINE__, ret); + return HI_ERR_AENC_NOT_PERM; + } + +#ifdef DUMP_AACENC + init_encoder_dump_file(&config); +#endif + + return HI_SUCCESS; +} + +static hi_s32 encode_frm_check_and_init(aenc_aac_encoder *aac_encoder, const hi_audio_frame *frame, + hi_s16 *in_data, hi_s16 in_len) +{ + hi_s32 i; + hi_u32 pt_nums, water_line; + + if (aac_encoder->aac_attr.snd_mode == HI_AUDIO_SOUND_MODE_STEREO) { + /* whether the sound mode of frame and channel is match */ + if (frame->snd_mode != HI_AUDIO_SOUND_MODE_STEREO) { + printf("[func]:%s [line]:%d [info]:%s\n", + __FUNCTION__, __LINE__, "AAC encode receive a frame which not match its soundmode"); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + } + + /* water_line, equals to the frame sample frame of protocol */ + if (aac_encoder->aac_attr.aac_type == HI_AAC_TYPE_AACLC) { + water_line = AACLC_SAMPLES_PER_FRAME; + } else if ((aac_encoder->aac_attr.aac_type == HI_AAC_TYPE_EAAC) || + (aac_encoder->aac_attr.aac_type == HI_AAC_TYPE_EAACPLUS)) { + water_line = AACPLUS_SAMPLES_PER_FRAME; + } else if ((aac_encoder->aac_attr.aac_type == HI_AAC_TYPE_AACLD) || + (aac_encoder->aac_attr.aac_type == HI_AAC_TYPE_AACELD)) { + water_line = AACLD_SAMPLES_PER_FRAME; + } else { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "invalid AAC coder type"); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + /* calculate point number */ + pt_nums = frame->len / (frame->bit_width + 1); + + /* if frame sample larger than protocol sample, reject to receive, or buffer will be overflow */ + if (pt_nums != water_line) { + printf("[func]:%s [line]:%d [info]:invalid pt_nums%d for aac_type:%d\n", + __FUNCTION__, __LINE__, pt_nums, aac_encoder->aac_attr.aac_type); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + + /* + * AAC encoder need interleaved data,here change LLLRRR to LRLRLR. + * HI_AACLC will encode 1024*2 point, and aa_cplus encode 2048*2 point + */ + if (aac_encoder->aac_attr.snd_mode == HI_AUDIO_SOUND_MODE_STEREO) { + if ((hi_u32)(in_len / 2) < (water_line * 2)) { /* 2: 16bit */ + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "invalid in_len"); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + for (i = water_line - 1; i >= 0; i--) { + in_data[2 * i] = *((hi_s16 *)frame->virt_addr[0] + i); /* 2: stereo */ + in_data[2 * i + 1] = *((hi_s16 *)frame->virt_addr[1] + i); /* 2: stereo */ + } + } else { + /* if inbuf is momo, copy left to right */ + if ((hi_u32)(in_len / 2) < water_line) { /* 2: 16bit */ + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "invalid in_len"); + return HI_ERR_AENC_ILLEGAL_PARAM; + } + for (i = water_line - 1; i >= 0; i--) { + in_data[i] = *((hi_s16 *)frame->virt_addr[0] + i); + } + } + + return HI_SUCCESS; +} + +hi_s32 encode_aac_frm(hi_void *encoder, const hi_audio_frame *data, + hi_u8 *outbuf, hi_u32 *out_len) +{ + hi_s32 ret; + aenc_aac_encoder *encoder_tmp = NULL; + hi_s16 data_tmp[HI_AACENC_BLOCK_SIZE * 2 * HI_AACENC_MAX_CHANNELS]; /* 2: 16bit = 2byte */ + + aac_check_false_return(encoder != NULL); + aac_check_false_return(data != NULL); + aac_check_false_return(outbuf != NULL); + aac_check_false_return(out_len != NULL); + + encoder_tmp = (aenc_aac_encoder *)encoder; + + ret = encode_frm_check_and_init(encoder_tmp, data, data_tmp, sizeof(data_tmp)); + if (ret != HI_SUCCESS) { + return ret; + } + +#ifdef DUMP_AACENC + if ((g_cnt_aenc > 0) && (g_in_enc_fd != HI_NULL)) { + fwrite((hi_u8 *)data_tmp, 1, (encoder_tmp->aac_attr.snd_mode == HI_AUDIO_SOUND_MODE_STEREO) ? + (data->len * 2) : data->len, g_in_enc_fd); /* 2:stereo */ + } +#endif + + ret = aac_encoder_frame_adp(encoder_tmp->aac_state, data_tmp, outbuf, (hi_s32*)out_len); + if (ret != HI_SUCCESS) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "AAC encode failed"); + } + +#ifdef DUMP_AACENC + if (g_cnt_aenc > 0) { + if (g_out_enc_fd != HI_NULL) { + fwrite((hi_u8 *)outbuf, 1, *((hi_s32*)out_len), g_out_enc_fd); + } + g_cnt_aenc--; + } +#endif + return ret; +} + +hi_s32 close_aac_encoder(hi_void *encoder) +{ + aenc_aac_encoder *encoder_tmp = NULL; + + aac_check_false_return(encoder != NULL); + encoder_tmp = (aenc_aac_encoder *)encoder; + + aac_encoder_close_adp(encoder_tmp->aac_state); + + free(encoder_tmp); + +#ifdef DUMP_AACENC + if (g_in_enc_fd != HI_NULL) { + fclose(g_in_enc_fd); + g_in_enc_fd = HI_NULL; + } + + if (g_out_enc_fd != HI_NULL) { + fclose(g_out_enc_fd); + g_out_enc_fd = HI_NULL; + } +#endif + return HI_SUCCESS; +} + +#ifdef DUMP_AACDEC +static hi_void init_decoder_dump_file(const hi_adec_attr_aac *attr) +{ + hi_s32 ret; + hi_char name_in[DUMP_PATH_NAME_MAX_BYTES] = {'\0'}; + hi_char name_out[DUMP_PATH_NAME_MAX_BYTES] = {'\0'}; + hi_char name_out_l[DUMP_PATH_NAME_MAX_BYTES] = {'\0'}; + + ret = snprintf_s(name_in, DUMP_PATH_NAME_MAX_BYTES, DUMP_PATH_NAME_MAX_BYTES - 1, + "aacdec_sin_t%d.aac", attr->transport_type); + if (ret < 0) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "get decoder in dump name failed"); + return; + } + + ret = snprintf_s(name_out, DUMP_PATH_NAME_MAX_BYTES, DUMP_PATH_NAME_MAX_BYTES - 1, + "aacdec_sout_t%d.raw", attr->transport_type); + if (ret < 0) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "get decoder out dump name failed"); + return; + } + + ret = snprintf_s(name_out_l, DUMP_PATH_NAME_MAX_BYTES, DUMP_PATH_NAME_MAX_BYTES - 1, + "aacdec_sout_t%d_l.raw", attr->transport_type); + if (ret < 0) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "get decoder out left dump name failed"); + return; + } + + g_in_dec_fd = fopen(name_in, "w+"); + if (g_in_dec_fd == HI_NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "open decoder in dump name failed"); + } + + g_out_dec_fd = fopen(name_out, "w+"); + if (g_out_dec_fd == HI_NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "open decoder out dump name failed"); + } + + g_out_dec_left_fd = fopen(name_out_l, "w+"); + if (g_out_dec_left_fd == HI_NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "open decoder out left dump name failed"); + } + + g_cnt_adec = DUMP_MAX_TIMES; +} + +static hi_void write_decoder_dump_file(hi_u8 *inbuf, hi_s32 in_len, + hi_u16 *outbuf, hi_u32 out_len, hi_u32 chns) +{ + if (g_cnt_adec > 0) { + if (g_in_dec_fd != HI_NULL) { + fwrite((hi_u8 *)inbuf, 1, in_len, g_in_dec_fd); + } + if (g_out_dec_fd != HI_NULL) { + fwrite((hi_u8 *)outbuf, 1, out_len * chns, g_out_dec_fd); + } + if (g_out_dec_left_fd != HI_NULL) { + fwrite((hi_u8 *)outbuf, 1, out_len, g_out_dec_left_fd); + } + g_cnt_adec--; + } +} +#endif + +hi_s32 open_aac_decoder(hi_void *decoder_attr, hi_void **pp_decoder) +{ + adec_aac_decoder *decoder = NULL; + hi_adec_attr_aac *attr = NULL; + hi_s32 ret; + + aac_check_false_return(decoder_attr != NULL); + aac_check_false_return(pp_decoder != NULL); + + attr = (hi_adec_attr_aac *)decoder_attr; + + /* allocate memory for decoder */ + decoder = (adec_aac_decoder *)malloc(sizeof(adec_aac_decoder)); + if (decoder == NULL) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "no memory"); + return HI_ERR_ADEC_NO_MEM; + } + ret = memset_s(decoder, sizeof(adec_aac_decoder), 0, sizeof(adec_aac_decoder)); + if (ret != EOK) { + free(decoder); + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "memset_s fail"); + return HI_ERR_ADEC_NOT_PERM; + } + + *pp_decoder = (hi_void *)decoder; + + /* create decoder */ + decoder->aac_state = aac_init_decoder_adp((hi_aacdec_transport_type)attr->transport_type); + if (!decoder->aac_state) { + free(decoder); + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "aac_init_decoder failed"); + return HI_ERR_ADEC_DECODER_ERR; + } + + ret = memcpy_s(&decoder->aac_attr, sizeof(decoder->aac_attr), attr, sizeof(*attr)); + if (ret != EOK) { + aac_free_decoder_adp(decoder->aac_state); + free(decoder); + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "memcpy_s failed"); + return HI_ERR_ADEC_NOT_PERM; + } + +#ifdef DUMP_AACDEC + init_decoder_dump_file(attr); +#endif + return HI_SUCCESS; +} + +hi_s32 decode_aac_frm(hi_void *decoder, hi_u8 **inbuf, hi_s32 *left_byte, + hi_u16 *outbuf, hi_u32 *out_len, hi_u32 *chns) +{ + hi_s32 ret; + adec_aac_decoder *decoder_tmp = NULL; + hi_s32 samples, frm_len, sample_bytes; + hi_aacdec_frame_info aac_frame_info = { 0 }; + + aac_check_false_return(decoder != NULL); + aac_check_false_return(inbuf != NULL); + aac_check_false_return(left_byte != NULL); + aac_check_false_return(outbuf != NULL); + aac_check_false_return(out_len != NULL); + aac_check_false_return(chns != NULL); + + *chns = 1; /* voice encoder only one channel */ + + decoder_tmp = (adec_aac_decoder *)decoder; + + frm_len = aac_decode_find_sync_header_adp(decoder_tmp->aac_state, inbuf, left_byte); + if (frm_len < 0) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "AAC decoder can't find sync header"); + return HI_ERR_ADEC_BUF_LACK; + } + +#ifdef DUMP_AACDEC + hi_u32 pre_len = *left_byte; +#endif + + /* notes: inbuf will updated */ + ret = aac_decode_frame_adp(decoder_tmp->aac_state, inbuf, left_byte, (hi_s16 *)outbuf); + if (ret) { + printf("aac decoder failed!, ret:0x%x\n", ret); + return ret; + } + + ret = aac_get_last_frame_info_adp(decoder_tmp->aac_state, &aac_frame_info); + if (ret != HI_SUCCESS) { + printf("aac get last frame info failed!\n"); + return HI_ERR_ADEC_DECODER_ERR; + } + + aac_frame_info.chn_num = ((aac_frame_info.chn_num != 0) ? aac_frame_info.chn_num : 1); + /* samples per frame of one sound track */ + samples = aac_frame_info.output_samples / aac_frame_info.chn_num; + + if ((samples != AACLC_SAMPLES_PER_FRAME) && (samples != AACPLUS_SAMPLES_PER_FRAME) && + (samples != AACLD_SAMPLES_PER_FRAME)) { + printf("aac decoder failed!\n"); + return HI_ERR_ADEC_DECODER_ERR; + } + + sample_bytes = samples * sizeof(hi_u16); + *chns = aac_frame_info.chn_num; + *out_len = sample_bytes; + +#ifdef DUMP_AACDEC + write_decoder_dump_file(*inbuf, pre_len - (*left_byte), outbuf, *out_len, *chns); +#endif + + return ret; +} + +hi_s32 get_aac_frm_info(hi_void *decoder, hi_void *info) +{ + hi_s32 ret; + adec_aac_decoder *decoder_tmp = NULL; + hi_aacdec_frame_info aac_frame_info = { 0 }; + aacdec_frame_info *aac_frm = NULL; + + aac_check_false_return(decoder != NULL); + aac_check_false_return(info != NULL); + + decoder_tmp = (adec_aac_decoder *)decoder; + aac_frm = (aacdec_frame_info *)info; + + ret = aac_get_last_frame_info_adp(decoder_tmp->aac_state, &aac_frame_info); + if (ret != HI_SUCCESS) { + printf("aac_get_last_frame_info failed with ret = %d!\n", ret); + return HI_ERR_ADEC_DECODER_ERR; + } + + aac_frm->sample_rate = aac_frame_info.sample_rate_out; + aac_frm->bit_rate = aac_frame_info.bit_rate; + aac_frm->profile = aac_frame_info.profile; + aac_frm->tns_used = aac_frame_info.tns_used; + aac_frm->pns_used = aac_frame_info.pns_used; + aac_frm->chn_num = aac_frame_info.chn_num; + + return HI_SUCCESS; +} + + +hi_s32 close_aac_decoder(hi_void *decoder) +{ + adec_aac_decoder *decoder_tmp = NULL; + + aac_check_false_return(decoder != NULL); + decoder_tmp = (adec_aac_decoder *)decoder; + + aac_free_decoder_adp(decoder_tmp->aac_state); + + free(decoder_tmp); + +#ifdef DUMP_AACDEC + if (g_in_dec_fd != HI_NULL) { + fclose(g_in_dec_fd); + g_in_dec_fd = HI_NULL; + } + + if (g_out_dec_fd != HI_NULL) { + fclose(g_out_dec_fd); + g_out_dec_fd = HI_NULL; + } + + if (g_out_dec_left_fd != HI_NULL) { + fclose(g_out_dec_left_fd); + g_out_dec_left_fd = HI_NULL; + } +#endif + + return HI_SUCCESS; +} + +hi_s32 reset_aac_decoder(hi_void *decoder) +{ + adec_aac_decoder *decoder_tmp = NULL; + + aac_check_false_return(decoder != NULL); + decoder_tmp = (adec_aac_decoder *)decoder; + + aac_free_decoder_adp(decoder_tmp->aac_state); + + /* create decoder */ + decoder_tmp->aac_state = aac_init_decoder_adp((hi_aacdec_transport_type)decoder_tmp->aac_attr.transport_type); + if (!decoder_tmp->aac_state) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "aac_reset_decoder failed"); + return HI_ERR_ADEC_DECODER_ERR; + } + + return HI_SUCCESS; +} + +hi_s32 hi_mpi_aenc_aac_init(hi_void) +{ + hi_s32 handle, ret; + hi_aenc_encoder aac; + ret = aac_init_enc_lib(); + if (ret) { + return ret; + } + + aac.type = HI_PT_AAC; + ret = snprintf_s(aac.name, HI_MAX_ENCODER_NAME_LEN, HI_MAX_ENCODER_NAME_LEN - 1, "aac"); + if (ret < 0) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "get encoder name failed"); + return ret; + } + aac.max_frame_len = MAX_AAC_MAINBUF_SIZE; + aac.func_open_encoder = open_aac_encoder; + aac.func_enc_frame = encode_aac_frm; + aac.func_close_encoder = close_aac_encoder; + ret = hi_mpi_aenc_register_encoder(&handle, &aac); + if (ret) { + return ret; + } + g_aac_enc_handle = handle; + + return HI_SUCCESS; +} + +hi_s32 hi_mpi_aenc_aac_deinit(hi_void) +{ + hi_s32 ret; + ret = hi_mpi_aenc_unregister_encoder(g_aac_enc_handle); + if (ret) { + return ret; + } + + aac_deinit_enc_lib(); + + return HI_SUCCESS; +} + +hi_s32 hi_mpi_adec_aac_init(hi_void) +{ + hi_s32 handle, ret; + hi_adec_decoder aac; + + ret = aac_init_dec_lib(); + if (ret) { + return ret; + } + + aac.type = HI_PT_AAC; + ret = snprintf_s(aac.name, HI_MAX_DECODER_NAME_LEN, HI_MAX_DECODER_NAME_LEN - 1, "aac"); + if (ret < 0) { + printf("[func]:%s [line]:%d [info]:%s\n", __FUNCTION__, __LINE__, "get decoder name failed"); + return ret; + } + aac.func_open_decoder = open_aac_decoder; + aac.func_dec_frame = decode_aac_frm; + aac.func_get_frame_info = get_aac_frm_info; + aac.func_close_decoder = close_aac_decoder; + aac.func_reset_decoder = reset_aac_decoder; + ret = hi_mpi_adec_register_decoder(&handle, &aac); + if (ret) { + return ret; + } + g_aac_dec_handle = handle; + + return HI_SUCCESS; +} + +hi_s32 hi_mpi_adec_aac_deinit(hi_void) +{ + hi_s32 ret; + ret = hi_mpi_adec_unregister_decoder(g_aac_dec_handle); + if (ret) { + return ret; + } + + aac_deinit_dec_lib(); + + return HI_SUCCESS; +} diff --git a/video/hisi/mpp5/audio-adp/audio_aac_adp.h b/video/hisi/mpp5/audio-adp/audio_aac_adp.h new file mode 100755 index 0000000..d5c30b4 --- /dev/null +++ b/video/hisi/mpp5/audio-adp/audio_aac_adp.h @@ -0,0 +1,107 @@ +/* + Copyright (c), 2001-2022, Shenshu Tech. Co., Ltd. + */ + +#ifndef AUDIO_AAC_ADP_H +#define AUDIO_AAC_ADP_H + +#include + +#include "hi_mpi_audio.h" +#include "hi_common_aenc.h" +#include "hi_common_adec.h" +#include "hi_common_aio.h" +#include "hi_aacdec.h" +#include "hi_aacenc.h" + +/* samples per frame for AACLC and aacPlus */ +#define AACLD_SAMPLES_PER_FRAME 512 +#define AACLC_SAMPLES_PER_FRAME 1024 +#define AACPLUS_SAMPLES_PER_FRAME 2048 + +/* max length of AAC stream by bytes */ +#define MAX_AAC_MAINBUF_SIZE (768 * 2) + +typedef enum { + HI_AAC_TYPE_AACLC = 0, /* AAC LC */ + HI_AAC_TYPE_EAAC = 1, /* EAAC(HEAAC or AAC+ or aac_plus_v1) */ + HI_AAC_TYPE_EAACPLUS = 2, /* EAAC+(AAC++ or aac_plus_v2) */ + HI_AAC_TYPE_AACLD = 3, + HI_AAC_TYPE_AACELD = 4, + HI_AAC_TYPE_BUTT, +} hi_aac_type; + +typedef enum { + HI_AAC_BPS_8K = 8000, + HI_AAC_BPS_16K = 16000, + HI_AAC_BPS_22K = 22000, + HI_AAC_BPS_24K = 24000, + HI_AAC_BPS_32K = 32000, + HI_AAC_BPS_48K = 48000, + HI_AAC_BPS_64K = 64000, + HI_AAC_BPS_96K = 96000, + HI_AAC_BPS_128K = 128000, + HI_AAC_BPS_256K = 256000, + HI_AAC_BPS_320K = 320000, + HI_AAC_BPS_BUTT +} hi_aac_bps; + +typedef enum { + HI_AAC_TRANSPORT_TYPE_ADTS = 0, + HI_AAC_TRANSPORT_TYPE_LOAS = 1, + HI_AAC_TRANSPORT_TYPE_LATM_MCP1 = 2, + HI_AAC_TRANSPORT_TYPE_BUTT +} hi_aac_transport_type; + +typedef struct { + hi_s32 sample_rate; /* sample rate */ + hi_s32 bit_rate; /* bitrate */ + hi_s32 profile; /* profile */ + hi_s32 tns_used; /* TNS tools */ + hi_s32 pns_used; /* PNS tools */ + hi_s32 chn_num; /* channel num */ +} aacdec_frame_info; + +/* + * AAC commendatory parameter: + * sampling rate(HZ) LC bit_rate(kbit/s) EAAC bit_rate (kbit/s) EAAC+ bit_rate (kbit/s) + * 48000 128 48 32,24 + * 44100 128 48 32,24 + * 32000 96 22 16 + * 24000 64 + * 22050 64 + * 16000 48 + */ +typedef struct { + hi_aac_type aac_type; /* AAC profile type */ + hi_aac_bps bit_rate; /* AAC bitrate(LC:16~320, EAAC:24~128, EAAC+:16~64, AACLD:16~320, AACELD:32~320) */ + hi_audio_sample_rate sample_rate; /* AAC sample rate(LC:8~48, EAAC:16~48, EAAC+:16~48, AACLD:8~48, AACELD:8~48) */ + hi_audio_bit_width bit_width; /* AAC bit width (only support 16bit) */ + hi_audio_snd_mode snd_mode; /* sound mode of inferent audio frame */ + hi_aac_transport_type transport_type; + hi_s16 band_width; /* targeted audio bandwidth in hz (0 or 1000~smp_rate/2), the default is 0 */ +} hi_aenc_attr_aac; + +typedef struct { + hi_aac_encoder *aac_state; + hi_aenc_attr_aac aac_attr; +} aenc_aac_encoder; + +typedef struct { + hi_aac_transport_type transport_type; +} hi_adec_attr_aac; + +typedef struct { + hi_aac_decoder aac_state; + hi_adec_attr_aac aac_attr; +} adec_aac_decoder; + +hi_s32 hi_mpi_aenc_aac_init(hi_void); + +hi_s32 hi_mpi_aenc_aac_deinit(hi_void); + +hi_s32 hi_mpi_adec_aac_init(hi_void); + +hi_s32 hi_mpi_adec_aac_deinit(hi_void); + +#endif diff --git a/video/hisi/mpp5/audio-adp/audio_dl_adp.c b/video/hisi/mpp5/audio-adp/audio_dl_adp.c new file mode 100755 index 0000000..0fb0be3 --- /dev/null +++ b/video/hisi/mpp5/audio-adp/audio_dl_adp.c @@ -0,0 +1,88 @@ +/* + Copyright (c), 2001-2022, Shenshu Tech. Co., Ltd. + */ + +#include "audio_dl_adp.h" +#include +#ifndef __LITEOS__ +#include +#else +#include +#endif + +hi_s32 audio_dlpath(hi_char *lib_path) +{ +#ifndef __LITEOS__ + (hi_void)lib_path; +#else + if (LOS_PathAdd(lib_path) != HI_SUCCESS) { + printf("add path %s failed!\n", lib_path); + return HI_FAILURE; + } +#endif + + return HI_SUCCESS; +} + +hi_s32 audio_dlopen(hi_void **lib_handle, hi_char *lib_name) +{ + if ((lib_handle == HI_NULL) || (lib_name == HI_NULL)) { + return HI_FAILURE; + } + + *lib_handle = HI_NULL; +#ifndef __LITEOS__ + *lib_handle = dlopen(lib_name, RTLD_LAZY | RTLD_LOCAL); +#else + *lib_handle = LOS_SoLoad(lib_name); +#endif + + if (*lib_handle == HI_NULL) { + printf("dlopen %s failed!\n", lib_name); + return HI_FAILURE; + } + + return HI_SUCCESS; +} + +hi_s32 audio_dlsym(hi_void **func_handle, hi_void *lib_handle, hi_char *func_name) +{ + if (lib_handle == HI_NULL) { + printf("LibHandle is empty!"); + return HI_FAILURE; + } + + *func_handle = HI_NULL; +#ifndef __LITEOS__ + *func_handle = dlsym(lib_handle, func_name); +#else + *func_handle = LOS_FindSymByName(lib_handle, func_name); +#endif + + if (*func_handle == HI_NULL) { +#ifndef __LITEOS__ + printf("dlsym %s fail, error msg is %s!\n", func_name, dlerror()); +#else + printf("dlsym %s fail!\n", func_name); +#endif + return HI_FAILURE; + } + + return HI_SUCCESS; +} + +hi_s32 audio_dlclose(hi_void *lib_handle) +{ + if (lib_handle == HI_NULL) { + printf("LibHandle is empty!"); + return HI_FAILURE; + } + +#ifndef __LITEOS__ + dlclose(lib_handle); +#else + LOS_ModuleUnload(lib_handle); +#endif + + return HI_SUCCESS; +} diff --git a/video/hisi/mpp5/audio-adp/audio_dl_adp.h b/video/hisi/mpp5/audio-adp/audio_dl_adp.h new file mode 100755 index 0000000..d27a0d4 --- /dev/null +++ b/video/hisi/mpp5/audio-adp/audio_dl_adp.h @@ -0,0 +1,18 @@ +/* + Copyright (c), 2001-2022, Shenshu Tech. Co., Ltd. + */ + +#ifndef __AUDIO_DL_ADP_H__ +#define __AUDIO_DL_ADP_H__ + +#include "hi_type.h" + +hi_s32 audio_dlpath(hi_char *lib_path); + +hi_s32 audio_dlopen(hi_void **lib_handle, hi_char *lib_name); + +hi_s32 audio_dlsym(hi_void **func_handle, hi_void *lib_handle, hi_char *func_name); + +hi_s32 audio_dlclose(hi_void *lib_handle); + +#endif diff --git a/video/ite/HDMIFrontend.cpp b/video/ite/HDMIFrontend.cpp new file mode 100644 index 0000000..54299c2 --- /dev/null +++ b/video/ite/HDMIFrontend.cpp @@ -0,0 +1,73 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "HDMIFrontend.h" +#include +#include +#include +#include + +namespace Video +{ +namespace ITE +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +HDMIFrontend::HDMIFrontend(std::string device): Core::Frontend("HDMIFrontend"), _it66021(device), _path(device) +{ + _hdmiInfo.have_audio = false; + _hdmiInfo.have_video = false; + + _it66021.readInfo(_hdmiInfo); + + _pollCancelled = false; + _pollThread.startFunc( + [&]() -> void + { + while (!_pollCancelled) + { + unsigned int revents = 0; + if (_it66021.poll(revents, 1000)) + { + if (revents & POLLIN) + { + _it66021.readInfo(_hdmiInfo); + notifyStatusChanged(); + } + } + } + }); +} + +HDMIFrontend::~HDMIFrontend() +{ + if (_pollThread.isRunning()) + { + _pollCancelled = true; + _pollThread.join(); + } +} + +void HDMIFrontend::read(MediaInfo& info) +{ + info.haveVideo = _hdmiInfo.have_video; + info.haveAudio = _hdmiInfo.have_audio; + + info.video.width = _hdmiInfo.hactive; + info.video.height = _hdmiInfo.vactive; + info.video.frameRate = _hdmiInfo.frame_rate; + + if (_hdmiInfo.scanmode == 0) + info.video.scanMode = Core::VideoScanMode::PROGRESSIVE; + else + info.video.scanMode = Core::VideoScanMode::INTERLACED; + + info.audio.sampleRate = _hdmiInfo.sample_rate; +} +} +} diff --git a/video/ite/HDMIFrontend.h b/video/ite/HDMIFrontend.h new file mode 100644 index 0000000..5c26dd1 --- /dev/null +++ b/video/ite/HDMIFrontend.h @@ -0,0 +1,40 @@ +#ifndef FEFC9D0D_1536_4204_B19D_636AC379D2CA +#define FEFC9D0D_1536_4204_B19D_636AC379D2CA +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../core/Frontend.h" +#include "IT66021.h" +#include +#include + +namespace Video +{ +namespace ITE +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class HDMIFrontend: public Core::Frontend +{ +public: + HDMIFrontend(std::string device); + ~HDMIFrontend(); + +public: + virtual void read(MediaInfo& info) override; + +private: + IT66021 _it66021; + std::string _path; + Poco::Thread _pollThread; + std::atomic _pollCancelled; + IT66021::Info _hdmiInfo; +}; +} +} +#endif diff --git a/video/ite/IT66021.cpp b/video/ite/IT66021.cpp new file mode 100644 index 0000000..7fe6692 --- /dev/null +++ b/video/ite/IT66021.cpp @@ -0,0 +1,29 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "IT66021.h" + + +namespace Video +{ +namespace ITE +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +IT66021::IT66021(std::string device): Util::Device(device, Util::Device::Mode::RDWR) +{ + open(); +} + +void IT66021::readInfo(Info& info) +{ + read((uint8_t*)&info, sizeof(info)); +} + +} +} diff --git a/video/ite/IT66021.h b/video/ite/IT66021.h new file mode 100644 index 0000000..0d4bda0 --- /dev/null +++ b/video/ite/IT66021.h @@ -0,0 +1,51 @@ +#ifndef C9FA4497_8D5C_418E_9024_63D07C7F6E95 +#define C9FA4497_8D5C_418E_9024_63D07C7F6E95 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "util/Device.h" + +namespace Video +{ +namespace ITE +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class IT66021: public Util::Device +{ +public: + struct Info + { + bool have_video; + uint32_t hactive; + uint32_t vactive; + uint32_t htotal; + uint32_t vtotal; + uint64_t pclk; + uint32_t hfront_porch; + uint32_t hsync_width; + uint32_t hback_porch; + uint32_t vfront_porch; + uint32_t vsync_width; + uint32_t vback_porch; + uint32_t scanmode; + uint32_t vpolarity; + uint32_t hpolarity; + uint32_t frame_rate; + + bool have_audio; + uint32_t sample_rate; + }; + +public: + IT66021(std::string device = "/dev/it66021"); + void readInfo(Info& info); +}; +} +} +#endif diff --git a/video/libopus/AudioDecodeHandler.cpp b/video/libopus/AudioDecodeHandler.cpp new file mode 100644 index 0000000..cece88b --- /dev/null +++ b/video/libopus/AudioDecodeHandler.cpp @@ -0,0 +1,92 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioDecodeHandler.h" +#include "Logger.h" +#include + +namespace Video +{ +namespace LibOPUS +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +AudioDecodeHandler::AudioDecodeHandler(Core::CodecID codecID, Core::AudioComponent::Ptr pPrevious) + : Core::AudioDecodeHandler(Poco::format("LibOPUS::AudioDecodeHandler(%s)", Core::codecIDToString(codecID)), codecID, + pPrevious), + _pDecoder(nullptr), _queue("LibOPUS::AudioDecodeHandler", Core::MediaType::AUDIO, 300) +{ + int err; + _pDecoder = opus_decoder_create(pPrevious->getSampleRate(), + Core::channelLayoutChannels(pPrevious->getChannelLayout()), &err); + if (err != OPUS_OK) + throw Poco::InvalidAccessException( + Poco::format("%s: Create decoder FAIL: %d(%s)", name(), err, std::string(opus_strerror(err))), err); +} + +AudioDecodeHandler::~AudioDecodeHandler() +{ + if (_pDecoder) + { + opus_decoder_destroy(_pDecoder); + _pDecoder = nullptr; + } +} + +void AudioDecodeHandler::write(Core::Packet::Ptr pPacket) +{ + const int channels = Core::channelLayoutChannels(previous()->getChannelLayout()); + int n = opus_decode(_pDecoder, pPacket->data(), (opus_int32)pPacket->length(), _pcm, 5760, 0); + if (n <= 0) + { + logger().error("%s: Decode FAIL: %d(%s)", name(), n, std::string(opus_strerror(n))); + + conceal(pPacket); + return; + } + + const int bytes = n * channels * (int)sizeof(int16_t); + Core::Frame::Ptr pFrame = new Core::Frame(); + pFrame->data[0] = new uint8_t[bytes]; + memcpy(pFrame->data[0].get(), _pcm, bytes); + pFrame->length = bytes; + pFrame->timestamp = pPacket->pts(); + + _queue.enqueue(pFrame, true); +} + +void AudioDecodeHandler::conceal(Core::Packet::Ptr pPacket) +{ + const int channels = Core::channelLayoutChannels(previous()->getChannelLayout()); + int n = opus_decode(_pDecoder, NULL, 0, _pcm, 5760, 0); + if (n <= 0) + return; + + const int bytes = n * channels * (int)sizeof(int16_t); + Core::Frame::Ptr pFrame = new Core::Frame(); + pFrame->data[0] = new uint8_t[bytes]; + memcpy(pFrame->data[0].get(), _pcm, bytes); + pFrame->length = bytes; + pFrame->timestamp = pPacket->pts(); + + _queue.enqueue(pFrame, true); +} + +Core::Frame::Ptr AudioDecodeHandler::read() +{ + try + { + return _queue.dequeue(0); + } + catch (Poco::TimeoutException&) + { + return nullptr; + } +} +} +} diff --git a/video/libopus/AudioDecodeHandler.h b/video/libopus/AudioDecodeHandler.h new file mode 100644 index 0000000..d936c70 --- /dev/null +++ b/video/libopus/AudioDecodeHandler.h @@ -0,0 +1,38 @@ +#ifndef F7A4D851_0C9D_4A3E_8B52_39B6E1F2C7D1 +#define F7A4D851_0C9D_4A3E_8B52_39B6E1F2C7D1 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../core/AudioDecoder.h" +#include + +namespace Video +{ +namespace LibOPUS +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class AudioDecodeHandler: public Core::AudioDecodeHandler +{ +public: + AudioDecodeHandler(Core::CodecID codecID, Core::AudioComponent::Ptr pPrevious); + ~AudioDecodeHandler(); + +protected: + void write(Core::Packet::Ptr pPacket) override; + void conceal(Core::Packet::Ptr pPacket) override; + Core::Frame::Ptr read() override; + +private: + OpusDecoder* _pDecoder; + Core::FrameQueue _queue; + int16_t _pcm[5760 * 2]; +}; +} +} +#endif diff --git a/video/libopus/AudioEncoder.cpp b/video/libopus/AudioEncoder.cpp new file mode 100644 index 0000000..1fd422f --- /dev/null +++ b/video/libopus/AudioEncoder.cpp @@ -0,0 +1,86 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioEncoder.h" +#include "Logger.h" + +namespace Video +{ +namespace LibOPUS +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +AudioEncoder::AudioEncoder(Core::CodecID codecID) + : Core::AudioEncoder(Poco::format("LibOPUS::AudioEncoder(%s)", Core::codecIDToString(codecID)), codecID), + _pEncoder(nullptr) +{ +} + +AudioEncoder::~AudioEncoder() +{ +} + +void AudioEncoder::initialize() +{ + setSampleRate(48000); + setFrameSize(960); + setSampleFormat(Core::SampleFormat::S16); + + int channels = Core::channelLayoutChannels(getChannelLayout()); + int err; + _pEncoder = opus_encoder_create(getSampleRate(), channels, OPUS_APPLICATION_AUDIO, &err); + if (err != OPUS_OK) + throw Poco::InvalidAccessException( + Poco::format("%s: Create encoder FAIL: %d(%s)", name(), err, std::string(opus_strerror(err))), err); + + opus_encoder_ctl(_pEncoder, OPUS_SET_BITRATE(getBitrate())); + opus_encoder_ctl(_pEncoder, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND)); + opus_encoder_ctl(_pEncoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC)); + opus_encoder_ctl(_pEncoder, OPUS_SET_VBR(1)); + opus_encoder_ctl(_pEncoder, OPUS_SET_LSB_DEPTH(Core::sampleFormatBitWidth(getSampleFormat()))); + opus_encoder_ctl(_pEncoder, OPUS_SET_DTX(0)); + opus_encoder_ctl(_pEncoder, OPUS_SET_COMPLEXITY(1)); +} + +void AudioEncoder::deinitialize() +{ + if (_pEncoder) + { + opus_encoder_destroy(_pEncoder); + _pEncoder = nullptr; + } +} + +#define PACKET_MAX_LEN 8192 +Core::Packet::Ptr AudioEncoder::encode() +{ + if (!_pEncoder) + return nullptr; + + auto pIn = readFrame(); + if (pIn.isNull()) + return nullptr; + + uint8_t* pBuffer = new uint8_t[PACKET_MAX_LEN]; + int32_t len = opus_encode(_pEncoder, (int16_t*)pIn->data[0].get(), getFrameSize(), pBuffer, PACKET_MAX_LEN); + if (len < 0 || len > PACKET_MAX_LEN) + { + delete[] pBuffer; + logger().error("%s: Encode FAIL: %d(%s)", name(), len, std::string(opus_strerror(len))); + return nullptr; + } + + return new Core::Packet(serialCodeManager().code(), 0, pBuffer, len, pIn->timestamp, pIn->timestamp); +} + +void AudioEncoder::onConfigChanged(ParameterNames& names) +{ + Core::AudioEncoder::onConfigChanged(names); +} +} +} diff --git a/video/libopus/AudioEncoder.h b/video/libopus/AudioEncoder.h new file mode 100644 index 0000000..f7a32f5 --- /dev/null +++ b/video/libopus/AudioEncoder.h @@ -0,0 +1,37 @@ +#ifndef F1683C08_D3DF_45F8_A40D_C37516C0EEB7 +#define F1683C08_D3DF_45F8_A40D_C37516C0EEB7 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../core/AudioEncoder.h" +#include + +namespace Video +{ +namespace LibOPUS +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class AudioEncoder: public Core::AudioEncoder +{ +public: + AudioEncoder(Core::CodecID codecID); + ~AudioEncoder(); + +protected: + void initialize() override; + void deinitialize() override; + Core::Packet::Ptr encode() override; + void onConfigChanged(ParameterNames& names) override; + +private: + OpusEncoder* _pEncoder; +}; +} +} +#endif diff --git a/video/mediaserver/MPEGMuxer.cpp b/video/mediaserver/MPEGMuxer.cpp new file mode 100644 index 0000000..b0f5c14 --- /dev/null +++ b/video/mediaserver/MPEGMuxer.cpp @@ -0,0 +1,237 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "MPEGMuxer.h" +#include "../core/VideoEncoder.h" +#include "../core/AudioEncoder.h" +#include "../core/SampleFormat.h" +#include "../core/System.h" + +namespace Video +{ +namespace MediaServer +{ +/* + ******************************************************************************* + * TYPEDEFS + ******************************************************************************* + */ +struct CodecIDItem +{ + Core::CodecID id; + int type; +}; + +/* + ******************************************************************************* + * LOCAL VARIABLES + ******************************************************************************* + */ +static const std::array codecMap = {{ + {Core::CodecID::H264, PSI_STREAM_H264}, + {Core::CodecID::H265, PSI_STREAM_H265}, + {Core::CodecID::MJPEG, PSI_STREAM_JPEG_2000}, + {Core::CodecID::MP3, PSI_STREAM_MP3}, + {Core::CodecID::AAC, PSI_STREAM_AAC}, + {Core::CodecID::OPUS, PSI_STREAM_AUDIO_OPUS}, + {Core::CodecID::G711A, PSI_STREAM_AUDIO_G711A}, + {Core::CodecID::G711U, PSI_STREAM_AUDIO_G711U}, +}}; + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +MPEGMuxer::Stream::Stream(const std::string& name, int id, Core::PacketQueue::Ptr pQueue) + : _name(name), _id(id), _pQueue(pQueue) +{ +} + +MPEGMuxer::Stream::~Stream() +{ +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +static int isMulticastAddr(const char* ip) +{ + int r; + struct addrinfo* ai; + r = getaddrinfo(ip, 0, NULL, &ai); + if (0 != r) + return 0; + + if (AF_INET == ai->ai_addr->sa_family) + { + const struct sockaddr_in* in = (const struct sockaddr_in*)ai->ai_addr; + assert(sizeof(struct sockaddr_in) == ai->ai_addrlen); + r = (ntohl(in->sin_addr.s_addr) & 0xf0000000) == 0xe0000000 ? 1 : 0; + } + else if (AF_INET6 == ai->ai_addr->sa_family) + { + const struct sockaddr_in6* in6 = (const struct sockaddr_in6*)ai->ai_addr; + assert(sizeof(struct sockaddr_in6) == ai->ai_addrlen); + r = in6->sin6_addr.s6_addr[0] == 0xff ? 1 : 0; + } + else + { + assert(0); + r = 0; + } + + freeaddrinfo(ai); + return r; +} + +Video::MediaServer::MPEGMuxer::MPEGMuxer(const std::string& url) + : Core::Muxer(Poco::format("MediaServer::MPEGMuxer(%s)", url), url) +{ +} + +MPEGMuxer::~MPEGMuxer() +{ +} + +void MPEGMuxer::connect() +{ + Poco::URI uri(url()); + + std::string scheme = uri.getScheme(); + std::string host = uri.getHost(); + short port = uri.getPort(); + + _socket = socket_udp_bind_ipv4(nullptr, 50609); + if (socket_invalid == _socket) + throw Poco::InvalidAccessException(Poco::format("%s: Create socket FAIL", name())); + + if (isMulticastAddr(host.c_str())) + socket_udp_multicast(_socket, host.c_str(), nullptr, 16); + + memset(&_destAddr, 0, sizeof(_destAddr)); + socket_addr_from_ipv4(&_destAddr, host.c_str(), port); + + static mpeg_muxer_func_t func = {[](void* param, size_t bytes) -> void* + { + MPEGMuxer* pMuxer = (MPEGMuxer*)param; + if (pMuxer->_writeIndex + bytes > sizeof(pMuxer->_buffer)) + pMuxer->flush(); + + return &pMuxer->_buffer[pMuxer->_writeIndex]; + }, + [](void* param, void* packet) -> void {}, + [](void* param, int stream, void* packet, size_t bytes) -> int + { + MPEGMuxer* pMuxer = (MPEGMuxer*)param; + pMuxer->_writeIndex += bytes; + return 0; + }}; + + _pMuxer = mpeg_muxer_create(0, &func, this); + if (!_pMuxer) + throw Poco::InvalidAccessException(Poco::format("%s: Create MPEG-Muxer FAIL!", name())); + + // mpeg_muxer_add_program(_pMuxer, 1, "XXXX", 4); /* 多节目方式 */ + previousComponents().foreach ( + [&](Core::Component::Ptr pPrevious) -> void + { + auto pVideoEncoder = pPrevious.cast(); + if (!pVideoEncoder.isNull()) + { + int id = mpeg_muxer_add_stream(_pMuxer, getCodecID(pVideoEncoder->getCodecID()), nullptr, 0); + + _streams.push_back(new Stream(Poco::format("%s/Stream(%s)", name(), pVideoEncoder->name()), id, + packetQueues().find(pPrevious))); + return; + } + + auto pAudioEncoder = pPrevious.cast(); + if (!pAudioEncoder.isNull()) + { + int id = mpeg_muxer_add_stream(_pMuxer, getCodecID(pAudioEncoder->getCodecID()), nullptr, 0); + + _streams.push_back(new Stream(Poco::format("%s/Stream(%s)", name(), pAudioEncoder->name()), id, + packetQueues().find(pPrevious))); + } + }); +} + +void MPEGMuxer::mux() +{ + std::sort(_streams.begin(), _streams.end(), [](auto a, auto b) { return *a < *b; }); + auto& pStream = *_streams.begin(); + + Core::Packet::Ptr pPacket = pStream->queue()->dequeue(4000); + if (pStream->serialCodeWatcher().isChanged(pPacket->serialCode())) + throw Poco::ApplicationException(Poco::format("%s: Stream changed, restart...", name())); + + pStream->setTimeline(pPacket->pts()); + + int flags = 0; + if (pPacket->isVideo()) + flags |= pPacket->isKeyFrame() ? MPEG_FLAG_IDR_FRAME : 0; + + mpeg_muxer_input(_pMuxer, pStream->id(), flags, pPacket->pts() * 90000 / 1000000, pPacket->dts() * 90000 / 1000000, + pPacket->data(), pPacket->length()); + +#if (TRACE_VIDEO_FRAME_RATE) + if (pPacket->isVideo()) + { + count++; + uint64_t now = Core::System::timestamp(); + int64_t delta = now - timeline; + if (delta >= 1000000) + { + timeline = now; + std::cout << " ==> " << name() << " fps: " << (double)count / (delta / 1000000) << std::endl; + count = 0; + } + } +#endif +} + +void MPEGMuxer::disconnect() +{ + if (_writeIndex > 0 && _socket != socket_invalid) + flush(); + + if (_pMuxer) + { + mpeg_muxer_destroy(_pMuxer); + _pMuxer = nullptr; + } + + if (_socket != socket_invalid) + { + socket_close(_socket); + _socket = socket_invalid; + } + + _streams.clear(); +} + +void MPEGMuxer::flush() +{ + socket_sendto_by_time(_socket, _buffer, _writeIndex, 0, (const struct sockaddr*)&_destAddr, sizeof(_destAddr), + 1000); + _writeIndex = 0; +} + +const int MPEGMuxer::getCodecID(Core::CodecID id) +{ + for (const auto& m : codecMap) + { + if (id == m.id) + return m.type; + } + + throw Poco::NotFoundException( + Poco::format("Video::MediaServer: Can't find %s in codec-map", Core::codecIDToString(id))); +} +} +} diff --git a/video/mediaserver/MPEGMuxer.h b/video/mediaserver/MPEGMuxer.h new file mode 100644 index 0000000..5b82c2e --- /dev/null +++ b/video/mediaserver/MPEGMuxer.h @@ -0,0 +1,98 @@ +#ifndef F9CED6CA_74FC_49C3_8D58_95A62F2EA1EC +#define F9CED6CA_74FC_49C3_8D58_95A62F2EA1EC +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../core/Muxer.h" +#include "util/ClassAssistant.h" +#include +#include +#include + +/* + ******************************************************************************* + * MACROS + ******************************************************************************* + */ +#define TRACE_VIDEO_FRAME_RATE (0) + +namespace Video +{ +namespace MediaServer +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class MPEGMuxer: public Core::Muxer +{ +private: + class Stream + { + public: + using Ptr = Poco::SharedPtr; + + public: + Stream(const std::string& name, int id, Core::PacketQueue::Ptr pQueue); + ~Stream(); + + public: + bool operator<(const Stream& other); + Core::PacketQueue::Ptr queue(); + Core::SerialCodeWatcher& serialCodeWatcher(); + + PROPERTY_DECLARE(Timeline, uint64_t, 0, public, public); + READONLY_PROPERTY_DECLARE(name, std::string) + READONLY_PROPERTY_DECLARE(id, int) + + private: + Core::PacketQueue::Ptr _pQueue; + Core::SerialCodeWatcher _serialCodeWatcher; + }; + +public: + MPEGMuxer(const std::string& url); + ~MPEGMuxer(); + +protected: + void connect() override; + void mux() override; + void disconnect() override; + void flush(); + + const int getCodecID(Core::CodecID id); + +private: + socket_t _socket; + struct sockaddr_in _destAddr; + mpeg_muxer_t* _pMuxer; + std::vector _streams; + + uint8_t _buffer[1316]; + int _writeIndex; +#if (TRACE_VIDEO_FRAME_RATE) + int count = 0; + uint64_t timeline = 0; +#endif +}; + +inline bool MPEGMuxer::Stream::operator<(const Stream& other) +{ + return _Timeline < other._Timeline; +} + +inline Core::PacketQueue::Ptr MPEGMuxer::Stream::queue() +{ + return _pQueue; +} + +inline Core::SerialCodeWatcher& MPEGMuxer::Stream::serialCodeWatcher() +{ + return _serialCodeWatcher; +} +} +} +#endif diff --git a/video/mediaserver/RTMPDemuxHandler.cpp b/video/mediaserver/RTMPDemuxHandler.cpp new file mode 100644 index 0000000..a7d65af --- /dev/null +++ b/video/mediaserver/RTMPDemuxHandler.cpp @@ -0,0 +1,554 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "RTMPDemuxHandler.h" +#include "Logger.h" +#include "../core/System.h" +#include "util/Math.h" +#include + +namespace Video +{ +namespace MediaServer +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +RTMPDemuxHandler::RTMPDemuxHandler(const std::string& uri) + : Core::DemuxHandler(Poco::format("MediaServer::DemuxHandler(%s)", uri), uri) +{ +} + +RTMPDemuxHandler::~RTMPDemuxHandler() +{ +} + +void RTMPDemuxHandler::run(Core::VideoEncoder::Ptr pVideoEncoder, Core::AudioEncoder::Ptr pAudioEncoder, + const Post& post, std::atomic& isCancelled) +{ + _pVideoEncoder = pVideoEncoder; + _pAudioEncoder = pAudioEncoder; + _post = post; + + Poco::URI uriParser(uri()); + std::string scheme = uriParser.getScheme(); + std::string host = uriParser.getHost(); + short port = uriParser.getPort(); + port = port <= 0 ? 1935 : port; + std::string path = uriParser.getPath(); + + if (!path.empty() && path[0] == '/') + path = path.substr(1); + + size_t pos = path.find_first_of('/'); + if (pos == std::string::npos) + { + logger().error("%s: Invalid URL: %s", name(), uri()); + Poco::Thread::sleep(2000); + return; + } + + std::string turl = scheme + "://" + host + ":" + std::to_string(port) + "/" + path.substr(0, pos); + std::string appname = path.substr(0, pos); + std::string playpath = path.substr(pos + 1); + + struct rtmp_client_handler_t handler; + handler.send = RTMPDemuxHandler::clientSend; + handler.onaudio = RTMPDemuxHandler::clientOnAudio; + handler.onvideo = RTMPDemuxHandler::clientOnVideo; + handler.onscript = RTMPDemuxHandler::clientOnScript; + + const size_t buffsz = 2 * 1024 * 1024; + Poco::SharedPtr pBuffer = new uint8_t[buffsz]; + int rc; + while (!isCancelled) + { + _audioInfoParsed = false; + _videoInfoParsed = false; + + while (!isCancelled) + { + _socket = socket_connect_host(host.c_str(), port, 2000); + if (_socket != socket_invalid) + { + socket_setnonblock(_socket, 0); + break; + } + + Poco::Thread::sleep(2000); + continue; + } + + _pCient = rtmp_client_create(appname.c_str(), playpath.c_str(), turl.c_str(), this, &handler); + if (!_pCient) + { + logger().error("%s: Create RTMP-Client FAIL!(URL: %s)", name(), uri()); + Poco::Thread::sleep(2000); + goto rtmp_create_fail; + } + + _pDemuxer = flv_demuxer_create(RTMPDemuxHandler::flvDemuxerHandler, this); + if (!_pDemuxer) + { + logger().error("%s: Create FLV Debuxer FAIL!(URL: %s)", name(), uri()); + Poco::Thread::sleep(2000); + goto demuxer_fail; + } + + rc = rtmp_client_start(_pCient, 1); + if (rc != 0) + { + logger().error("%s: Start RTMP-Client FAIL!(URL: %s)", name(), uri()); + Poco::Thread::sleep(2000); + goto rmtp_start_fail; + } + + while (!isCancelled) + { + rc = socket_recv(_socket, pBuffer.get(), buffsz, 0); + if (rc <= 0) + { + logger().error("%s: Socket recv FAIL: %d(URL: %s)", name(), rc, uri()); + break; + } + + rc = rtmp_client_input(_pCient, pBuffer.get(), rc); + if (rc != 0) + { + logger().error("%s: RTMP client input FAIL: %d(URL: %s)", name(), rc, uri()); + break; + } + } + + rtmp_client_stop(_pCient); + + rmtp_start_fail: + flv_demuxer_destroy(_pDemuxer); + + demuxer_fail: + rtmp_client_destroy(_pCient); + + rtmp_create_fail: + socket_close(_socket); + _socket = socket_invalid; + Poco::Thread::sleep(2000); + } +} + +int RTMPDemuxHandler::parseFLVMetaData(const uint8_t* data, size_t bytes) +{ + double audiocodecid = 0; + double audiodatarate = 0; + double audiodelay = 0; + double audiosamplerate = 0; + double audiosamplesize = 0; + int stereo = 0; + double videocodecid = 0; + double videodatarate = 0; + double framerate = 0; + double height = 0; + double width = 0; + + struct amf_object_item_t prop[16]; + struct amf_object_item_t items[1]; + char buffer[64]; + +#define AMF_OBJECT_ITEM_VALUE(v, amf_type, amf_name, amf_value, amf_size) \ + { \ + v.type = amf_type; \ + v.name = amf_name; \ + v.value = amf_value; \ + v.size = amf_size; \ + } + int i = 0; + AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "audiocodecid", &audiocodecid, sizeof(audiocodecid)); + i++; + AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "audiodatarate", &audiodatarate, sizeof(audiodatarate)); + i++; + AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "audiodelay", &audiodelay, sizeof(audiodelay)); + i++; + AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "audiosamplerate", &audiosamplerate, sizeof(audiosamplerate)); + i++; + AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "audiosamplesize", &audiosamplesize, sizeof(audiosamplesize)); + i++; + AMF_OBJECT_ITEM_VALUE(prop[i], AMF_BOOLEAN, "stereo", &stereo, sizeof(stereo)); + i++; + AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "videocodecid", &videocodecid, sizeof(videocodecid)); + i++; + AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "videodatarate", &videodatarate, sizeof(videodatarate)); + i++; + AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "framerate", &framerate, sizeof(framerate)); + i++; + AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "height", &height, sizeof(height)); + i++; + AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "width", &width, sizeof(width)); + i++; + + AMF_OBJECT_ITEM_VALUE(items[0], AMF_OBJECT, "onMetaData", prop, i); + + const uint8_t* end = data + bytes; + if (AMF_STRING != data[0] || NULL == (data = AMFReadString(data + 1, end, 0, buffer, sizeof(buffer) - 1))) + return -1; + + if (0 == strcmp(buffer, "@setDataFrame")) + { + if (AMF_STRING != data[0] || NULL == (data = AMFReadString(data + 1, end, 0, buffer, sizeof(buffer) - 1))) + return -1; + } + + if (0 != strcmp(buffer, "onMetaData")) + return 0; + + if (amf_read_items(data, end, items, sizeof(items) / sizeof(items[0]))) + { + if (framerate > 0) + _pVideoEncoder->setFrameRate(framerate > 60 ? 60 : framerate); + + if (videodatarate > 0) + _pVideoEncoder->setBitrate((int64_t)videodatarate); + + if (audiodatarate > 0) + _pAudioEncoder->setBitrate((int64_t)audiodatarate); + } + + return 0; +} + +inline const uint8_t* RTMPDemuxHandler::annexBStartcode(const uint8_t* data, size_t bytes) +{ + size_t i; + for (i = 2; i + 1 < bytes; i++) + { + if (0x01 == data[i] && 0x00 == data[i - 1] && 0x00 == data[i - 2]) + return data + i + 1; + } + + return nullptr; +} + +bool RTMPDemuxHandler::isH264BFrame(const void* h264, size_t bytes) +{ + ptrdiff_t n; + const uint8_t *p, *next, *end; + + end = (const uint8_t*)h264 + bytes; + p = annexBStartcode((const uint8_t*)h264, bytes); + + while (p) + { + next = annexBStartcode(p, end - p); + if (next) + n = next - p - 3; + else + n = end - p; + + while (n > 0 && 0 == p[n - 1]) + n--; + + assert(n > 0); + if (n > 0) + { + if ((p[0] & 0x1f) == 1) + { + bitstream_t stream; + bitstream_init(&stream, (const uint8_t*)(p + 1), n - 1); + bitstream_read_ue(&stream); + int32_t sliceType = bitstream_read_ue(&stream); + if (sliceType == 1) + return true; + } + } + p = next; + } + return false; +} + +int RTMPDemuxHandler::processH264Data(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags) +{ + if (!_videoInfoParsed) + mpeg4_h264_annexb_nalu(data, bytes, RTMPDemuxHandler::h264NALhandler, this); + + enqueueVideoPacket(data, bytes, pts, dts, flags); + return 0; +} + +int RTMPDemuxHandler::processH265Data(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags) +{ + if (!_videoInfoParsed) + mpeg4_h264_annexb_nalu(data, bytes, RTMPDemuxHandler::h265NALhandler, this); + + enqueueVideoPacket(data, bytes, pts, dts, flags); + return 0; +} + +int RTMPDemuxHandler::processASCData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags) +{ + struct mpeg4_aac_t aac; + if (mpeg4_aac_audio_specific_config_load((const uint8_t*)data, bytes, &aac) >= 0) + { + _pAudioEncoder->setCodecID(Core::CodecID::AAC); + _pAudioEncoder->setFrameSize(1024); + _pAudioEncoder->setSampleFormat(Core::SampleFormat::FLTP); + _pAudioEncoder->setSampleRate((int)aac.sampling_frequency); + _pAudioEncoder->setChannelLayout(aac.channels == 2 ? Core::ChannelLayout::STEREO : Core::ChannelLayout::MONO); + _pAudioEncoder->commit(); + logger().information("%s: Audio found: AAC %dHz %s", name(), (int)aac.sampling_frequency, + Core::channelLayoutToString(_pAudioEncoder->getChannelLayout())); + ; + } + + return 0; +} + +int RTMPDemuxHandler::processAACData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags) +{ + enqueueAudioPacket(data, bytes, pts, dts, flags); + return 0; +} + +int RTMPDemuxHandler::processOpusHeadData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags) +{ + struct opus_head_t opus; + if (opus_head_load((const uint8_t*)data, bytes, &opus) >= 0) + { + _pAudioEncoder->setCodecID(Core::CodecID::OPUS); + _pAudioEncoder->setFrameSize(960); + _pAudioEncoder->setSampleFormat(Core::SampleFormat::S16); + _pAudioEncoder->setSampleRate((int)opus.input_sample_rate); + _pAudioEncoder->setChannelLayout(opus_head_channels(&opus) == 2 ? Core::ChannelLayout::STEREO + : Core::ChannelLayout::MONO); + _pAudioEncoder->commit(); + logger().information("%s: Audio found: OPUS %dHz %s", name(), (int)opus.input_sample_rate, + Core::channelLayoutToString(_pAudioEncoder->getChannelLayout())); + } + + return 0; +} + +int RTMPDemuxHandler::processOpusData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags) +{ + enqueueAudioPacket(data, bytes, pts, dts, flags); + return 0; +} + +int RTMPDemuxHandler::processG711AData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags) +{ + if (!_audioInfoParsed) + { + _pAudioEncoder->setCodecID(Core::CodecID::G711A); + _pAudioEncoder->setFrameSize(1024); + _pAudioEncoder->setSampleFormat(Core::SampleFormat::S16P); + _pAudioEncoder->setSampleRate((int)8000); + _pAudioEncoder->setChannelLayout(Core::ChannelLayout::MONO); + _pAudioEncoder->commit(); + logger().information("%s: Audio found: G711A %dHz %s", name(), 8000, std::string("MONO")); + + _audioInfoParsed = true; + } + + enqueueAudioPacket(data, bytes, pts, dts, flags); + return 0; +} + +int RTMPDemuxHandler::processG711UData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags) +{ + if (!_audioInfoParsed) + { + _pAudioEncoder->setCodecID(Core::CodecID::G711U); + _pAudioEncoder->setFrameSize(1024); + _pAudioEncoder->setSampleFormat(Core::SampleFormat::S16P); + _pAudioEncoder->setSampleRate((int)8000); + _pAudioEncoder->setChannelLayout(Core::ChannelLayout::MONO); + _pAudioEncoder->commit(); + logger().information("%s: Audio found: G711U %dHz %s", name(), 8000, std::string("MONO")); + + _audioInfoParsed = true; + } + + enqueueAudioPacket(data, bytes, pts, dts, flags); + return 0; +} + +void RTMPDemuxHandler::enqueueVideoPacket(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags) +{ + uint8_t* pBuffer = new uint8_t[bytes]; + memcpy(pBuffer, data, bytes); + + uint64_t localPTS = pts; + uint64_t localDTS = dts; + + adjustTimestamp(localPTS, localDTS); + updateTimeline(localPTS); + + _post(new Core::Packet(_pVideoEncoder->code(), + Core::Packet::FLAG_IS_VIDEO | (flags ? Core::Packet::FLAG_IS_KEY : 0), pBuffer, bytes, + localPTS, localDTS), + Core::MediaType::VIDEO); +} + +void RTMPDemuxHandler::enqueueAudioPacket(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags) +{ + uint8_t* pBuffer = new uint8_t[bytes]; + memcpy(pBuffer, data, bytes); + + uint64_t localPTS = pts; + uint64_t localDTS = dts; + + adjustTimestamp(localPTS, localDTS); + updateTimeline(localPTS); + _post(new Core::Packet(_pAudioEncoder->code(), flags, pBuffer, bytes, localPTS, localDTS), Core::MediaType::AUDIO); +} + +int RTMPDemuxHandler::clientSend(void* param, const void* header, size_t len, const void* data, size_t bytes) +{ + socket_bufvec_t vec[2]; + socket_setbufvec(vec, 0, (void*)header, len); + socket_setbufvec(vec, 1, (void*)data, bytes); + return socket_send_v_all_by_time(((RTMPDemuxHandler*)param)->_socket, vec, bytes ? 2 : 1, 0, 2000); +} + +int RTMPDemuxHandler::clientOnAudio(void* param, const void* data, size_t bytes, uint32_t timestamp) +{ + return flv_demuxer_input(((RTMPDemuxHandler*)param)->_pDemuxer, FLV_TYPE_AUDIO, data, bytes, timestamp); +} + +int RTMPDemuxHandler::clientOnVideo(void* param, const void* data, size_t bytes, uint32_t timestamp) +{ + return flv_demuxer_input(((RTMPDemuxHandler*)param)->_pDemuxer, FLV_TYPE_VIDEO, data, bytes, timestamp); +} + +int RTMPDemuxHandler::clientOnScript(void* param, const void* data, size_t bytes, uint32_t timestamp) +{ + return flv_demuxer_input(((RTMPDemuxHandler*)param)->_pDemuxer, FLV_TYPE_SCRIPT, data, bytes, timestamp); +} + +int RTMPDemuxHandler::flvDemuxerHandler(void* param, int codec, const void* data, size_t bytes, uint32_t pts, + uint32_t dts, int flags) +{ + int rc; + RTMPDemuxHandler* pHandler = (RTMPDemuxHandler*)param; + + switch (codec) + { + case FLV_SCRIPT_METADATA: + rc = pHandler->parseFLVMetaData((const uint8_t*)data, bytes); + break; + + case FLV_VIDEO_AVCC: + break; + + case FLV_VIDEO_H264: + rc = pHandler->processH264Data(data, bytes, pts, dts, flags); + break; + + case FLV_VIDEO_HVCC: + break; + + case FLV_VIDEO_H265: + rc = pHandler->processH265Data(data, bytes, pts, dts, flags); + break; + + case FLV_AUDIO_ASC: + rc = pHandler->processASCData(data, bytes, pts, dts, flags); + break; + + case FLV_AUDIO_AAC: + rc = pHandler->processAACData(data, bytes, pts, dts, flags); + break; + + case FLV_AUDIO_OPUS_HEAD: + rc = pHandler->processOpusHeadData(data, bytes, pts, dts, flags); + break; + + case FLV_AUDIO_OPUS: + rc = pHandler->processOpusData(data, bytes, pts, dts, flags); + break; + + case FLV_AUDIO_G711A: + rc = pHandler->processG711AData(data, bytes, pts, dts, flags); + break; + + case FLV_AUDIO_G711U: + rc = pHandler->processG711UData(data, bytes, pts, dts, flags); + break; + + default: + logger().warning("%s: UNIMPLEMENTED FLV-CODEC type: %d(%#x)", pHandler->name(), codec, (uint32_t)codec); + break; + } + + (void)rc; + return 0; +} + +void RTMPDemuxHandler::h264NALhandler(void* param, const uint8_t* nalu, size_t bytes) +{ + RTMPDemuxHandler* pHandler = (RTMPDemuxHandler*)param; + + struct h264_sps_t sps; + if (h264_sps_parse(nalu, bytes, &sps) == 0) + { + int x, y, width, height; + + h264_display_rect(&sps, &x, &y, &width, &height); + + auto pEncoder = pHandler->_pVideoEncoder; + pEncoder->setCodecID(Core::CodecID::H264); + pEncoder->setWidth(width); + pEncoder->setHeight(height); + pEncoder->commit(); + + pHandler->_videoInfoParsed = true; + + logger().information("%s: Video found: H.264 %dx%d", pHandler->name(), width, height); + } +} + +void RTMPDemuxHandler::h265NALhandler(void* param, const uint8_t* nalu, size_t bytes) +{ + RTMPDemuxHandler* pHandler = (RTMPDemuxHandler*)param; + + struct h265_sps_t sps; + if (h265_sps_parse(nalu, bytes, &sps) == 0) + { + int x, y, width, height; + + h265_display_rect(&sps, &x, &y, &width, &height); + + auto pEncoder = pHandler->_pVideoEncoder; + pEncoder->setCodecID(Core::CodecID::H265); + pEncoder->setWidth(width); + pEncoder->setHeight(height); + pEncoder->commit(); + + pHandler->_videoInfoParsed = true; + + logger().information("%s: Video found: H.265 %dx%d", pHandler->name(), width, height); + } +} + +void RTMPDemuxHandler::adjustTimestamp(uint64_t& pts, uint64_t& dts) +{ + pts *= 1000ull; + dts *= 1000ull; + + auto drift = getTimeDrift(pts); + pts += drift; + dts += drift; +} + +void RTMPDemuxHandler::updateTimeline(uint64_t ts) +{ + if (getTimeline() < ts) + setTimeline(ts); +} + +void RTMPDemuxHandler::seek(uint64_t ts) +{ +} +} +} diff --git a/video/mediaserver/RTMPDemuxHandler.h b/video/mediaserver/RTMPDemuxHandler.h new file mode 100644 index 0000000..3eb82fb --- /dev/null +++ b/video/mediaserver/RTMPDemuxHandler.h @@ -0,0 +1,97 @@ +#ifndef E3F40CBC_D6F1_4B8F_B90E_6765659CF90F +#define E3F40CBC_D6F1_4B8F_B90E_6765659CF90F +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../core/Demuxer.h" +extern "C" +{ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +} + +namespace Video +{ +namespace MediaServer +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class RTMPDemuxHandler: public Core::DemuxHandler +{ +public: + RTMPDemuxHandler(const std::string& url); + ~RTMPDemuxHandler(); + +public: + void run(Core::VideoEncoder::Ptr pVideoEncoder, Core::AudioEncoder::Ptr pAudioEncoder, const Post& post, + std::atomic& isCancelled) override; + +protected: + int parseFLVMetaData(const uint8_t* pData, size_t length); + int processH264Data(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags); + int processH265Data(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags); + int processASCData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags); + int processAACData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags); + int processOpusHeadData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags); + int processOpusData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags); + int processG711AData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags); + int processG711UData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags); + + void enqueueVideoPacket(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags); + void enqueueAudioPacket(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags); + + inline const uint8_t* annexBStartcode(const uint8_t* data, size_t bytes); + bool isH264BFrame(const void* h264, size_t bytes); + void adjustTimestamp(uint64_t& pts, uint64_t& dts); + void updateTimeline(uint64_t ts); + void seek(uint64_t) override; + +private: + socket_t _socket; + rtmp_client_t* _pCient; + flv_demuxer_t* _pDemuxer; + bool _audioInfoParsed; + bool _videoInfoParsed; + uint32_t _iFrameLength; + Core::VideoEncoder::Ptr _pVideoEncoder; + Core::AudioEncoder::Ptr _pAudioEncoder; + Post _post; + union + { + struct mpeg4_avc_t avc; + struct mpeg4_hevc_t hevc; + } _v; + +protected: + static int clientSend(void* param, const void* header, size_t len, const void* data, size_t bytes); + static int clientOnAudio(void* param, const void* data, size_t bytes, uint32_t timestamp); + static int clientOnVideo(void* param, const void* data, size_t bytes, uint32_t timestamp); + static int clientOnScript(void* param, const void* data, size_t bytes, uint32_t timestamp); + + static int flvDemuxerHandler(void* param, int codec, const void* data, size_t bytes, uint32_t pts, uint32_t dts, + int flags); + static void h264NALhandler(void* param, const uint8_t* nalu, size_t bytes); + static void h265NALhandler(void* param, const uint8_t* nalu, size_t bytes); +}; +} +} +#endif diff --git a/video/mediaserver/RTMPMuxer.cpp b/video/mediaserver/RTMPMuxer.cpp new file mode 100644 index 0000000..cba2dbd --- /dev/null +++ b/video/mediaserver/RTMPMuxer.cpp @@ -0,0 +1,369 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "RTMPMuxer.h" +#include "../core/VideoEncoder.h" +#include "../core/AudioEncoder.h" +#include "../core/SampleFormat.h" +#include "../core/System.h" +#include + +namespace Video +{ +namespace MediaServer +{ +/* + ******************************************************************************* + * TYPEDEFS + ******************************************************************************* + */ +struct CodecIDItem +{ + Core::CodecID id; + int type; +}; + +/* + ******************************************************************************* + * LOCAL VARIABLES + ******************************************************************************* + */ +static const std::array codecMap = {{ + {Core::CodecID::H264, FLV_VIDEO_H264}, + {Core::CodecID::H265, FLV_VIDEO_H265}, + + {Core::CodecID::MP3, FLV_AUDIO_MP3}, + {Core::CodecID::AAC, FLV_AUDIO_AAC}, + {Core::CodecID::OPUS, FLV_AUDIO_OPUS}, + {Core::CodecID::G711A, FLV_AUDIO_G711A}, + {Core::CodecID::G711U, FLV_AUDIO_G711U}, +}}; + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +RTMPMuxer::Stream::Stream(const std::string& name, Core::CodecID codecID, Core::PacketQueue::Ptr pQueue) + : _name(name), _codecID(codecID), _pQueue(pQueue) +{ + switch (codecID) + { + case Core::CodecID::H264: + _pMuxerMethod = flv_muxer_avc; + break; + + case Core::CodecID::H265: + _pMuxerMethod = flv_muxer_hevc; + break; + + case Core::CodecID::MP3: + _pMuxerMethod = flv_muxer_mp3; + break; + + case Core::CodecID::AAC: + _pMuxerMethod = flv_muxer_aac; + break; + + case Core::CodecID::OPUS: + _pMuxerMethod = flv_muxer_opus; + break; + + case Core::CodecID::G711A: + _pMuxerMethod = flv_muxer_g711a; + break; + + case Core::CodecID::G711U: + _pMuxerMethod = flv_muxer_g711u; + break; + + default: + _pMuxerMethod = nullptr; + throw Poco::NotImplementedException( + Poco::format("%s: Unsupported codec: %s", name, Core::codecIDToString(codecID))); + } +} + +RTMPMuxer::Stream::~Stream() +{ +} + +inline int Video::MediaServer::RTMPMuxer::Stream::muxer(flv_muxer_t* muxer, const void* data, size_t bytes, + uint32_t pts, uint32_t dts) +{ + if (!_pMuxerMethod) + throw Poco::BugcheckException(Poco::format("%s: muxer method is NULL!", name())); + + return _pMuxerMethod(muxer, data, bytes, pts, dts); +} + +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Video::MediaServer::RTMPMuxer::RTMPMuxer(const std::string& url) + : Core::Muxer(Poco::format("MediaServer::RTMPMuxer(%s)", url), url) +{ +} + +RTMPMuxer::~RTMPMuxer() +{ +} + +void RTMPMuxer::connect() +{ + Poco::URI uri(url()); + + std::string scheme = uri.getScheme(); + std::string host = uri.getHost(); + short port = uri.getPort(); + if (port <= 0) + port = 1935; + + std::string path = uri.getPath(); + + if (!path.empty() && path[0] == '/') + path = path.substr(1); + + size_t pos = path.find_first_of('/'); + if (pos == std::string::npos) + throw Poco::InvalidArgumentException(Poco::format("%s: Invalid URL: %s", name(), url())); + + _socket = socket_connect_host(host.c_str(), port, 2000); + if (socket_invalid == _socket) + { + throw Poco::InvalidAccessException(Poco::format("%s: Connect FAIL", name())); + } + + socket_setnonblock(_socket, 0); + + std::string turl = scheme + "://" + host + ":" + std::to_string(port) + "/" + path.substr(0, pos); + std::string appname = path.substr(0, pos); + std::string playpath = path.substr(pos + 1); + + struct rtmp_client_handler_t handler = {}; + handler.send = RTMPMuxer::clientSend; + _pClient = rtmp_client_create(appname.c_str(), playpath.c_str(), turl.c_str(), this, &handler); + if (!_pClient) + throw Poco::InvalidAccessException(Poco::format("%s: Create RTMP-Client FAIL!", name())); + + _pMuxer = flv_muxer_create(RTMPMuxer::muxerHandler, this); + if (!_pMuxer) + throw Poco::InvalidAccessException(Poco::format("%s: Create RTMP-Muxer FAIL!", name())); + + int rc = rtmp_client_start(_pClient, 0); + if (rc != 0) + throw Poco::InvalidAccessException(Poco::format("%s: Start RTMP-Client FAIL: %d", name(), rc)); + + int limit = 50; + uint8_t buffer[4096]; + while (--limit > 0 && 4 != rtmp_client_getstate(_pClient) && + (rc = socket_recv(_socket, buffer, sizeof(buffer), 0)) > 0) + { + if (rtmp_client_input(_pClient, buffer, rc) != 0) + throw Poco::InvalidAccessException(Poco::format("%s: RTMP-Client input handle FAIL!", name())); + } + + struct flv_metadata_t metadata = {}; + uint8_t opusHead[19] = {0}; + bool hasOpus = false; + previousComponents().foreach ( + [&](Core::Component::Ptr pPrevious) -> void + { + auto pVideoEncoder = pPrevious.cast(); + if (!pVideoEncoder.isNull()) + { + _streams.push_back(new Stream(Poco::format("%s/Stream(%s)", name(), pVideoEncoder->name()), + pVideoEncoder->getCodecID(), packetQueues().find(pPrevious))); + + metadata.videocodecid = getCodecID(pVideoEncoder->getCodecID()); + metadata.videodatarate = pVideoEncoder->getBitrate() / 1000; + metadata.framerate = pVideoEncoder->getFrameRate(); + metadata.width = pVideoEncoder->getWidth(); + metadata.height = pVideoEncoder->getHeight(); + return; + } + + auto pAudioEncoder = pPrevious.cast(); + if (!pAudioEncoder.isNull()) + { + _streams.push_back(new Stream(Poco::format("%s/Stream(%s)", name(), pAudioEncoder->name()), + pAudioEncoder->getCodecID(), packetQueues().find(pPrevious))); + + metadata.audiocodecid = getCodecID(pAudioEncoder->getCodecID()); + metadata.audiocodecid >>= 4; + metadata.audiosamplerate = pAudioEncoder->getSampleRate(); + metadata.audiodatarate = pAudioEncoder->getBitrate() / 1000; + metadata.audiosamplesize = Core::sampleFormatBitWidth(pAudioEncoder->getSampleFormat()); + metadata.stereo = pAudioEncoder->getChannelLayout() == Core::ChannelLayout::STEREO ? true : false; + + if (pAudioEncoder->getCodecID() == Core::CodecID::OPUS) + { + memcpy(opusHead, "OpusHead", 8); + opusHead[8] = 1; + opusHead[9] = (uint8_t)(metadata.stereo ? 2 : 1); + uint32_t sr = (uint32_t)metadata.audiosamplerate; + opusHead[12] = (uint8_t)(sr & 0xFF); + opusHead[13] = (uint8_t)((sr >> 8) & 0xFF); + opusHead[14] = (uint8_t)((sr >> 16) & 0xFF); + opusHead[15] = (uint8_t)((sr >> 24) & 0xFF); + hasOpus = true; + } + } + }); + + flv_muxer_metadata(_pMuxer, &metadata); + + if (hasOpus) + { + for (auto& pStream : _streams) + { + if (pStream->codecID() == Core::CodecID::OPUS) + { + pStream->muxer(_pMuxer, opusHead, 19, 0, 0); + break; + } + } + } +} + +void RTMPMuxer::mux() +{ + drainInput(); + + std::sort(_streams.begin(), _streams.end(), [](auto a, auto b) { return *a < *b; }); + auto& pStream = *_streams.begin(); + + Core::Packet::Ptr pPacket = pStream->queue()->dequeue(4000); + if (pStream->serialCodeWatcher().isChanged(pPacket->serialCode())) + throw Poco::ApplicationException(Poco::format("%s: Stream changed, restart...", name())); + + pStream->setTimeline(pPacket->pts()); + int rc = pStream->muxer(_pMuxer, pPacket->data(), pPacket->length(), pPacket->pts() / 1000, pPacket->dts() / 1000); + if (rc < 0) + throw Poco::ProtocolException(Poco::format("%s: mux %s FAIL: %d (len:%u, pts:%Lu, dts:%Lu)", name(), + (pPacket->isVideo() ? std::string("video") : std::string("audio")), + rc, pPacket->length(), pPacket->pts(), pPacket->dts())); + +#if (TRACE_VIDEO_FRAME_RATE) + if (pPacket->isVideo()) + { + count++; + uint64_t now = Core::System::timestamp(); + int64_t delta = now - timeline; + if (delta >= 1000000) + { + timeline = now; + std::cout << " ==> " << name() << " fps: " << (double)count / (delta / 1000000) << std::endl; + count = 0; + } + } +#endif +} + +void RTMPMuxer::disconnect() +{ + if (_pClient) + { + rtmp_client_stop(_pClient); + rtmp_client_destroy(_pClient); + _pClient = nullptr; + } + + Poco::Thread::sleep(100); + if (_pMuxer) + { + flv_muxer_destroy(_pMuxer); + _pMuxer = nullptr; + } + + if (_socket != socket_invalid) + { + socket_close(_socket); + _socket = socket_invalid; + } + + _streams.clear(); +} + +void RTMPMuxer::drainInput() +{ + uint8_t buffer[2048]; + + while (true) + { + int rc = socket_recv_by_time(_socket, buffer, sizeof(buffer), 0, 0); + + if (SOCKET_TIMEDOUT == rc) + break; + + if (0 == rc) + throw Poco::IOException(Poco::format("%s: Connection closed by peer", name())); + + if (rc < 0) + throw Poco::IOException(Poco::format("%s: socket_recv FAIL: %d", name(), rc)); + + if (rtmp_client_input(_pClient, buffer, (size_t)rc) != 0) + throw Poco::ProtocolException(Poco::format("%s: RTMP-Client input handle FAIL!", name())); + } +} + +int RTMPMuxer::clientSend(void* param, const void* header, size_t len, const void* data, size_t bytes) +{ + RTMPMuxer* pPusher = (RTMPMuxer*)param; + + socket_bufvec_t vec[2]; + socket_setbufvec(vec, 0, (void*)header, len); + socket_setbufvec(vec, 1, (void*)data, bytes); + +#if defined(CORRUPT_RTMP_CHUNK_DATA) + + rtmp_corrupt_data(header, len); + rtmp_corrupt_data(data, bytes); +#endif + + return socket_send_v_all_by_time(pPusher->_socket, vec, bytes > 0 ? 2 : 1, 0, 5000); +} + +int RTMPMuxer::muxerHandler(void* param, int type, const void* data, size_t bytes, uint32_t timestamp) +{ + RTMPMuxer* pPusher = (RTMPMuxer*)param; + int rc; + switch (type) + { + case FLV_TYPE_SCRIPT: + rc = rtmp_client_push_script(pPusher->_pClient, data, bytes, timestamp); + break; + + case FLV_TYPE_VIDEO: + rc = rtmp_client_push_video(pPusher->_pClient, data, bytes, timestamp); + break; + + case FLV_TYPE_AUDIO: + rc = rtmp_client_push_audio(pPusher->_pClient, data, bytes, timestamp); + break; + + default: + rc = -EINVAL; + break; + } + + return rc; +} + +const int RTMPMuxer::getCodecID(Core::CodecID id) +{ + for (const auto& m : codecMap) + { + if (id == m.id) + return m.type; + } + + throw Poco::NotFoundException( + Poco::format("Video::MediaServer: Can't find %s in codec-map", Core::codecIDToString(id))); +} +} +} diff --git a/video/mediaserver/RTMPMuxer.h b/video/mediaserver/RTMPMuxer.h new file mode 100644 index 0000000..d57b15b --- /dev/null +++ b/video/mediaserver/RTMPMuxer.h @@ -0,0 +1,115 @@ +#ifndef F2B5C47C_73F6_4CFD_A4D6_9C8A1CFB55F5 +#define F2B5C47C_73F6_4CFD_A4D6_9C8A1CFB55F5 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../core/Muxer.h" +#include "util/ClassAssistant.h" +#include +#include +#include + +/* + ******************************************************************************* + * MACROS + ******************************************************************************* + */ +#define TRACE_VIDEO_FRAME_RATE (0) + +namespace Video +{ +namespace MediaServer +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class RTMPMuxer: public Core::Muxer +{ +private: + class Stream + { + public: + using Ptr = Poco::SharedPtr; + + public: + Stream(const std::string& name, Core::CodecID codecID, Core::PacketQueue::Ptr pQueue); + ~Stream(); + + public: + bool operator<(const Stream& other); + + const std::string name() const; + + Core::PacketQueue::Ptr queue(); + Core::SerialCodeWatcher& serialCodeWatcher(); + Core::CodecID codecID() const; + + int muxer(flv_muxer_t* muxer, const void* data, size_t bytes, uint32_t pts, uint32_t dts); + + PROPERTY_DECLARE(Timeline, uint64_t, 0, public, public); + + private: + const std::string _name; + Core::CodecID _codecID; + Core::PacketQueue::Ptr _pQueue; + Core::SerialCodeWatcher _serialCodeWatcher; + int (*_pMuxerMethod)(flv_muxer_t*, const void*, size_t, uint32_t, uint32_t); + }; + +public: + RTMPMuxer(const std::string& url); + ~RTMPMuxer(); + +protected: + void connect() override; + void mux() override; + void disconnect() override; + +protected: + void drainInput(); + const int getCodecID(Core::CodecID id); + static int clientSend(void* param, const void* header, size_t len, const void* data, size_t bytes); + static int muxerHandler(void* param, int type, const void* data, size_t bytes, uint32_t timestamp); + +private: + socket_t _socket; + rtmp_client_t* _pClient; + flv_muxer_t* _pMuxer; + std::vector _streams; +#if (TRACE_VIDEO_FRAME_RATE) + int count = 0; + uint64_t timeline = 0; +#endif +}; + +inline bool RTMPMuxer::Stream::operator<(const Stream& other) +{ + return _Timeline < other._Timeline; +} + +inline const std::string RTMPMuxer::Stream::name() const +{ + return _name; +} + +inline Core::PacketQueue::Ptr RTMPMuxer::Stream::queue() +{ + return _pQueue; +} + +inline Core::SerialCodeWatcher& RTMPMuxer::Stream::serialCodeWatcher() +{ + return _serialCodeWatcher; +} + +inline Core::CodecID RTMPMuxer::Stream::codecID() const +{ + return _codecID; +} +} +} +#endif diff --git a/video/shine/AudioEncoder.cpp b/video/shine/AudioEncoder.cpp new file mode 100644 index 0000000..000f207 --- /dev/null +++ b/video/shine/AudioEncoder.cpp @@ -0,0 +1,99 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "AudioEncoder.h" +#include "Logger.h" +#include "../core/System.h" + +namespace Video +{ +namespace Shine +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +AudioEncoder::AudioEncoder(Core::CodecID codecID) + : Core::AudioEncoder(Poco::format("Shine::AudioEncoder(%s)", Core::codecIDToString(codecID)), codecID), + _pEncoder(nullptr) +{ + shine_set_config_mpeg_defaults(&_config.mpeg); +} + +AudioEncoder::~AudioEncoder() +{ +} + +void AudioEncoder::initialize() +{ + setSampleFormat(Core::SampleFormat::S16); + + int channels = Core::channelLayoutChannels(getChannelLayout()); + + _config.wave.channels = channels > 1 ? PCM_STEREO : PCM_MONO; + _config.wave.samplerate = getSampleRate(); + _config.mpeg.bitr = (int)(getBitrate() / 1000); + + if (shine_check_config(_config.wave.samplerate, _config.mpeg.bitr) < 0) + throw Poco::InvalidAccessException(Poco::format("%s: Config error.", name()), -1); + + _config.mpeg.mode = _config.wave.channels > 1 ? STEREO : MONO; + + _pEncoder = shine_initialise(&_config); + dummyConfig(&_config); + + setFrameSize(shine_samples_per_pass(_pEncoder)); +} + +void AudioEncoder::deinitialize() +{ + if (_pEncoder) + { + shine_close(_pEncoder); + _pEncoder = nullptr; + } +} + +Core::Packet::Ptr AudioEncoder::encode() +{ + if (!_pEncoder) + return nullptr; + + auto pIn = readFrame(); + int len; + uint8_t* pData = shine_encode_buffer_interleaved(_pEncoder, (int16_t*)pIn->data[0].get(), &len); + if (!pData || len <= 0) + { + logger().error("%s: Encode FAIL", name()); + return nullptr; + } + + uint8_t* pBuffer = new uint8_t[len]; + memcpy(pBuffer, pData, len); + + return new Core::Packet(serialCodeManager().code(), 0, pBuffer, len, pIn->timestamp, pIn->timestamp); +} + +void AudioEncoder::onConfigChanged(ParameterNames& names) +{ + Core::AudioEncoder::onConfigChanged(names); +} + +void AudioEncoder::dummyConfig(shine_config_t* pConfig) +{ + static const char* version_names[4] = {"2.5", "reserved", "II", "I"}; + static const char* mode_names[4] = {"stereo", "joint-stereo", "dual-channel", "mono"}; + static const char* demp_names[4] = {"none", "50/15us", "", "CITT"}; + + printf("MPEG-%s layer III, %s Psychoacoustic Model: Shine\n", + version_names[shine_check_config(pConfig->wave.samplerate, pConfig->mpeg.bitr)], + mode_names[pConfig->mpeg.mode]); + printf("Bitrate: %d kbps ", pConfig->mpeg.bitr); + printf("De-emphasis: %s %s %s\n", demp_names[pConfig->mpeg.emph], ((pConfig->mpeg.original) ? "Original" : ""), + ((pConfig->mpeg.copyright) ? "(C)" : "")); +} +} +} diff --git a/video/shine/AudioEncoder.h b/video/shine/AudioEncoder.h new file mode 100644 index 0000000..02e9f0a --- /dev/null +++ b/video/shine/AudioEncoder.h @@ -0,0 +1,43 @@ +#ifndef FAAFB16A_5BB2_43F7_9F92_991E8328451D +#define FAAFB16A_5BB2_43F7_9F92_991E8328451D +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../core/AudioEncoder.h" +extern "C" +{ +#include +} + +namespace Video +{ +namespace Shine +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class AudioEncoder: public Core::AudioEncoder +{ +public: + AudioEncoder(Core::CodecID codecID); + ~AudioEncoder(); + +protected: + void initialize() override; + void deinitialize() override; + Core::Packet::Ptr encode() override; + void onConfigChanged(ParameterNames& names) override; + + void dummyConfig(shine_config_t* pConfig); + +private: + shine_t _pEncoder; + shine_config_t _config; +}; +} +} +#endif diff --git a/video/v4l2/Access.cpp b/video/v4l2/Access.cpp new file mode 100755 index 0000000..96e5e85 --- /dev/null +++ b/video/v4l2/Access.cpp @@ -0,0 +1,16 @@ +#include "Access.h" + +namespace Video +{ +namespace V4L2 +{ +Access::Access(Device* device): m_device(device) +{ +} + +Access::~Access() +{ + delete m_device; +} +} +} diff --git a/video/v4l2/Access.h b/video/v4l2/Access.h new file mode 100755 index 0000000..4fbe0a2 --- /dev/null +++ b/video/v4l2/Access.h @@ -0,0 +1,73 @@ +#pragma once +#include "Device.h" + +namespace Video +{ +namespace V4L2 +{ +class Access +{ +public: + explicit Access(Device* device); + virtual ~Access(); + + int getFd() + { + return m_device->getFd(); + } + unsigned int getBufferSize() + { + return m_device->getBufferSize(); + } + unsigned int getFormat() + { + return m_device->getFormat(); + } + unsigned int getWidth() + { + return m_device->getWidth(); + } + unsigned int getHeight() + { + return m_device->getHeight(); + } + const std::string getName() + { + return m_device->getName(); + } + + void queryFormat() + { + m_device->queryFormat(); + } + int setFormat(unsigned int format, unsigned int width, unsigned int height) + { + return m_device->setFormat(format, width, height); + } + int setFps(int fps) + { + return m_device->setFps(fps); + } + + int isReady() + { + return m_device->isReady(); + } + int start() + { + return m_device->start(); + } + int stop() + { + return m_device->stop(); + } + +private: + Access(const Access&); + Access& operator=(const Access&); + +protected: + Device* m_device; +}; +} +} diff --git a/video/v4l2/Capture.cpp b/video/v4l2/Capture.cpp new file mode 100755 index 0000000..dcd95a6 --- /dev/null +++ b/video/v4l2/Capture.cpp @@ -0,0 +1,63 @@ +#include + +#include "Capture.h" +#include "MmapDevice.h" +#include "ReadWriteDevice.h" + +namespace Video +{ +namespace V4L2 +{ +Capture* Capture::create(const DeviceParameters& param) +{ + Capture* videoCapture = NULL; + Device* videoDevice = NULL; + int caps = V4L2_CAP_VIDEO_CAPTURE; + switch (param.m_iotype) + { + case IOTYPE_MMAP: + videoDevice = new MmapDevice(param, V4L2_BUF_TYPE_VIDEO_CAPTURE); + caps |= V4L2_CAP_STREAMING; + break; + case IOTYPE_READWRITE: + videoDevice = new ReadWriteDevice(param, V4L2_BUF_TYPE_VIDEO_CAPTURE); + caps |= V4L2_CAP_READWRITE; + break; + } + + if (videoDevice && !videoDevice->init(caps)) + { + delete videoDevice; + videoDevice = NULL; + } + + if (videoDevice) + { + videoCapture = new Capture(videoDevice); + } + return videoCapture; +} + +Capture::Capture(Device* device): Access(device) +{ +} + +Capture::~Capture() +{ +} + +bool Capture::isReadable(timeval* tv) +{ + int fd = m_device->getFd(); + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(fd, &fdset); + return (select(fd + 1, &fdset, NULL, NULL, tv) == 1); +} + +size_t Capture::read(char* buffer, size_t bufferSize) +{ + return m_device->readInternal(buffer, bufferSize); +} +} +} diff --git a/video/v4l2/Capture.h b/video/v4l2/Capture.h new file mode 100755 index 0000000..aab8cf7 --- /dev/null +++ b/video/v4l2/Capture.h @@ -0,0 +1,22 @@ +#pragma once + +#include "Access.h" + +namespace Video +{ +namespace V4L2 +{ +class Capture: public Access +{ +protected: + explicit Capture(Device* device); + +public: + static Capture* create(const DeviceParameters& param); + virtual ~Capture(); + + size_t read(char* buffer, size_t bufferSize); + bool isReadable(timeval* tv); +}; +} +} diff --git a/video/v4l2/Device.cpp b/video/v4l2/Device.cpp new file mode 100755 index 0000000..b010509 --- /dev/null +++ b/video/v4l2/Device.cpp @@ -0,0 +1,254 @@ +#include "Device.h" +#include "Logger.h" + +#include +#include +#include +#include +#include + +#include + +namespace Video +{ +namespace V4L2 +{ +std::string Device::fourcc(unsigned int format) +{ + char formatArray[] = {(char)(format & 0xff), (char)((format >> 8) & 0xff), (char)((format >> 16) & 0xff), + (char)((format >> 24) & 0xff), 0}; + return std::string(formatArray, strlen(formatArray)); +} + +unsigned int Device::fourcc(const char* format) +{ + char fourcc[4]; + memset(&fourcc, 0, sizeof(fourcc)); + if (format != NULL) + { + strncpy(fourcc, format, 4); + } + return v4l2_fourcc(fourcc[0], fourcc[1], fourcc[2], fourcc[3]); +} + +Device::Device(const DeviceParameters& params, v4l2_buf_type deviceType) + : m_params(params), m_fd(-1), m_deviceType(deviceType), m_bufferSize(0), m_format(0) +{ +} + +Device::~Device() +{ + this->close(); +} + +void Device::close() +{ + if (m_fd != -1) + ::close(m_fd); + + m_fd = -1; +} + +void Device::queryFormat() +{ + struct v4l2_format fmt; + memset(&fmt, 0, sizeof(fmt)); + fmt.type = m_deviceType; + if (0 == ioctl(m_fd, VIDIOC_G_FMT, &fmt)) + { + m_format = fmt.fmt.pix.pixelformat; + m_width = fmt.fmt.pix.width; + m_height = fmt.fmt.pix.height; + m_bufferSize = fmt.fmt.pix.sizeimage; + + logger().debug("%s: %s size: %ux%u bufferSize: %u", m_params.m_devName, fourcc(m_format), (uint32_t)m_width, + (uint32_t)m_height, (uint32_t)m_bufferSize); + } +} + +bool Device::init(unsigned int mandatoryCapabilities) +{ + struct stat sb; + if ((stat(m_params.m_devName.c_str(), &sb) == 0) && ((sb.st_mode & S_IFMT) == S_IFCHR)) + { + if (initdevice(m_params.m_devName.c_str(), mandatoryCapabilities) == -1) + { + logger().error("%s: Cannot init device", m_params.m_devName); + } + } + else + { + m_fd = open(m_params.m_devName.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU); + } + return (m_fd != -1); +} + +int Device::initdevice(const char* dev_name, unsigned int mandatoryCapabilities) +{ + m_fd = open(dev_name, m_params.m_openFlags); + if (m_fd < 0) + { + logger().error("%s: Cannot open device", m_params.m_devName); + this->close(); + return -1; + } + if (checkCapabilities(m_fd, mandatoryCapabilities) != 0) + { + this->close(); + return -1; + } + if (configureFormat(m_fd) != 0) + { + this->close(); + return -1; + } + if (configureParam(m_fd, m_params.m_fps) != 0) + { + this->close(); + return -1; + } + + return m_fd; +} + +int Device::checkCapabilities(int fd, unsigned int mandatoryCapabilities) +{ + struct v4l2_capability cap; + memset(&(cap), 0, sizeof(cap)); + if (-1 == ioctl(fd, VIDIOC_QUERYCAP, &cap)) + { + logger().error("%s: Cannot get capabilities: %d(%s)", m_params.m_devName, errno, std::string(strerror(errno))); + return -1; + } + + std::string str = ""; + if ((cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)) + str += "output,"; + + if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) + str += "capture,"; + + if ((cap.capabilities & V4L2_CAP_READWRITE)) + str += "read/write,"; + + if ((cap.capabilities & V4L2_CAP_STREAMING)) + str += "streaming,"; + + if ((cap.capabilities & V4L2_CAP_TIMEPERFRAME)) + str += "timeperframe"; + + m_name = (char*)cap.card; + logger().debug("%s(%s): support: %s", m_params.m_devName, m_name, str); + + if ((cap.capabilities & mandatoryCapabilities) != mandatoryCapabilities) + { + logger().error("%s: Mandatory capability not available for device", m_params.m_devName); + return -1; + } + + return 0; +} + +int Device::configureFormat(int fd) +{ + this->queryFormat(); + + unsigned int width = m_width; + unsigned int height = m_height; + if (m_params.m_width != 0) + { + width = m_params.m_width; + } + if (m_params.m_height != 0) + { + height = m_params.m_height; + } + if ((m_params.m_formatList.size() == 0) && (m_format != 0)) + { + m_params.m_formatList.push_back(m_format); + } + + std::list::iterator it; + for (it = m_params.m_formatList.begin(); it != m_params.m_formatList.end(); ++it) + { + unsigned int format = *it; + if (this->configureFormat(fd, format, width, height) == 0) + { + this->queryFormat(); + return 0; + } + } + return -1; +} + +int Device::configureFormat(int fd, unsigned int format, unsigned int width, unsigned int height) +{ + struct v4l2_format fmt; + memset(&(fmt), 0, sizeof(fmt)); + fmt.type = m_deviceType; + if (ioctl(m_fd, VIDIOC_G_FMT, &fmt) == -1) + { + logger().error("%s: Cannot get format: %d(%s)", m_params.m_devName, errno, std::string(strerror(errno))); + return -1; + } + if (width != 0) + { + fmt.fmt.pix.width = width; + } + if (height != 0) + { + fmt.fmt.pix.height = height; + } + if (format != 0) + { + fmt.fmt.pix.pixelformat = format; + } + + if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) + { + logger().error("%s: Cannot set format: %d(%s)", m_params.m_devName, errno, std::string(strerror(errno))); + return -1; + } + + if (fmt.fmt.pix.pixelformat != format) + { + logger().error("%s: Cannot set pixelformat to: %s format is ", m_params.m_devName, fourcc(format), + fourcc(fmt.fmt.pix.pixelformat)); + return -1; + } + + if ((fmt.fmt.pix.width != width) || (fmt.fmt.pix.height != height)) + { + logger().warning("%s: Cannot set size to: %ux%u size is: %ux%u", m_params.m_devName, (uint32_t)width, + (uint32_t)height, (uint32_t)fmt.fmt.pix.width, (uint32_t)fmt.fmt.pix.height); + } + + m_format = fmt.fmt.pix.pixelformat; + m_width = fmt.fmt.pix.width; + m_height = fmt.fmt.pix.height; + m_bufferSize = fmt.fmt.pix.sizeimage; + + return 0; +} + +int Device::configureParam(int fd, int fps) +{ + if (fps != 0) + { + struct v4l2_streamparm param; + memset(&(param), 0, sizeof(param)); + param.type = m_deviceType; + param.parm.capture.timeperframe.numerator = 1; + param.parm.capture.timeperframe.denominator = fps; + + if (ioctl(fd, VIDIOC_S_PARM, ¶m) == -1) + { + logger().warning("%s: Cannot set param for device: %d(%s)", m_params.m_devName, errno, + std::string(strerror(errno))); + } + } + + return 0; +} +} +} diff --git a/video/v4l2/Device.h b/video/v4l2/Device.h new file mode 100755 index 0000000..e1beabf --- /dev/null +++ b/video/v4l2/Device.h @@ -0,0 +1,165 @@ +#pragma once + +#include +#include +#include +#include + +#ifndef V4L2_PIX_FMT_VP8 +#define V4L2_PIX_FMT_VP8 v4l2_fourcc('V', 'P', '8', '0') +#endif +#ifndef V4L2_PIX_FMT_VP9 +#define V4L2_PIX_FMT_VP9 v4l2_fourcc('V', 'P', '9', '0') +#endif +#ifndef V4L2_PIX_FMT_HEVC +#define V4L2_PIX_FMT_HEVC v4l2_fourcc('H', 'E', 'V', 'C') +#endif + +namespace Video +{ +namespace V4L2 +{ +enum V4l2IoType +{ + IOTYPE_READWRITE, + IOTYPE_MMAP +}; + +struct DeviceParameters +{ + DeviceParameters(const char* devname, const std::list& formatList, unsigned int width, + unsigned int height, int fps, V4l2IoType ioType = IOTYPE_MMAP, int verbose = 0, + int openFlags = O_RDWR | O_NONBLOCK) + : m_devName(devname), m_formatList(formatList), m_width(width), m_height(height), m_fps(fps), m_iotype(ioType), + m_verbose(verbose), m_openFlags(openFlags) + { + } + + DeviceParameters(const char* devname, unsigned int format, unsigned int width, unsigned int height, int fps, + V4l2IoType ioType = IOTYPE_MMAP, int verbose = 0, int openFlags = O_RDWR | O_NONBLOCK) + : m_devName(devname), m_width(width), m_height(height), m_fps(fps), m_iotype(ioType), m_verbose(verbose), + m_openFlags(openFlags) + { + if (format) + { + m_formatList.push_back(format); + } + } + + std::string m_devName; + std::list m_formatList; + unsigned int m_width; + unsigned int m_height; + int m_fps; + V4l2IoType m_iotype; + int m_verbose; + int m_openFlags; +}; + +class Device +{ + friend class Capture; + friend class Output; + +protected: + void close(); + + int initdevice(const char* dev_name, unsigned int mandatoryCapabilities); + int checkCapabilities(int fd, unsigned int mandatoryCapabilities); + int configureFormat(int fd); + int configureFormat(int fd, unsigned int format, unsigned int width, unsigned int height); + int configureParam(int fd, int fps); + + virtual bool init(unsigned int mandatoryCapabilities); + virtual size_t writeInternal(char*, size_t) + { + return -1; + } + virtual bool startPartialWrite() + { + return false; + } + virtual size_t writePartialInternal(char*, size_t) + { + return -1; + } + virtual bool endPartialWrite() + { + return false; + } + virtual size_t readInternal(char*, size_t) + { + return -1; + } + +public: + Device(const DeviceParameters& params, v4l2_buf_type deviceType); + virtual ~Device(); + + virtual bool isReady() + { + return (m_fd != -1); + } + virtual bool start() + { + return true; + } + virtual bool stop() + { + return true; + } + + std::string getName() + { + return m_name; + } + unsigned int getBufferSize() + { + return m_bufferSize; + } + unsigned int getFormat() + { + return m_format; + } + unsigned int getWidth() + { + return m_width; + } + unsigned int getHeight() + { + return m_height; + } + int getFd() + { + return m_fd; + } + void queryFormat(); + + int setFormat(unsigned int format, unsigned int width, unsigned int height) + { + return this->configureFormat(m_fd, format, width, height); + } + int setFps(int fps) + { + return this->configureParam(m_fd, fps); + } + + static std::string fourcc(unsigned int format); + static unsigned int fourcc(const char* format); + +protected: + DeviceParameters m_params; + int m_fd; + v4l2_buf_type m_deviceType; + + unsigned int m_bufferSize; + unsigned int m_format; + unsigned int m_width; + unsigned int m_height; + + struct v4l2_buffer m_partialWriteBuf; + bool m_partialWriteInProgress; + std::string m_name; +}; +} +} diff --git a/video/v4l2/Input.cpp b/video/v4l2/Input.cpp new file mode 100644 index 0000000..79afa08 --- /dev/null +++ b/video/v4l2/Input.cpp @@ -0,0 +1,158 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Input.h" +#include "Capture.h" +#include "InputFormat.h" +#include "../core/System.h" +#include "Logger.h" + +namespace Video +{ +namespace V4L2 +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +Input::Input(const std::string& name): VideoComponent(name), _task(name) +{ +} + +Input::~Input() +{ +} + +void Input::start() +{ + _task.run( + [this](std::atomic& isCancelled) -> void + { + const std::string path = getPath(); + + setCodecID(Core::CodecID::MJPEG); + setSignalLost(true); + + while (!isCancelled) + { + Poco::SharedPtr pCapture; + InputFormat::Collection formats; + try + { + InputFormat::enumerate(path, formats); + + if (formats.empty()) + { + Poco::Thread::sleep(2000); + continue; + } + + for (auto& c : formats) + { + if (V4L2_PIX_FMT_MJPEG == c.pixelFormat && !c.resolutions.empty()) + { + auto p = c.resolutions.begin(); + uint64_t maxProduct = 0; + for (auto it = c.resolutions.begin(); it != c.resolutions.end(); ++it) + { + uint32_t fps = it->width * it->height > 3686400 ? 30 : 60; + if (it->width <= 2560 && it->height <= 1440 && it->fps <= fps) + { + uint64_t product = static_cast(it->width) * it->height * it->fps; + if (product > maxProduct) + { + logger().debug("%s: ", name()); + maxProduct = product; + p = it; + } + } + } + + DeviceParameters param(path.c_str(), c.pixelFormat, p->width, p->height, p->fps); + pCapture = Capture::create(param); + if (pCapture.isNull()) + continue; + + logger().information("%s: Camera [%s] found, %s, %ux%u@%u", name(), pCapture->getName(), + c.description, p->width, p->height, p->fps); + + setWidth(p->width); + setHeight(p->height); + setFrameRate(p->fps); + setSignalLost(false); + serialCodeManager().increment(); + break; + } + } + } + catch (...) + { + Poco::Thread::sleep(2000); + continue; + } + + uint8_t* pBuffer = nullptr; + ; + while (!isCancelled) + { + try + { + struct timeval tv = {1, 0}; + if (!pCapture->isReadable(&tv)) + break; + + int len = pCapture->getBufferSize(); + pBuffer = new uint8_t[len]; + int rsize = pCapture->read((char*)pBuffer, len); + if (rsize < 0) + { + logger().error("%s: Failed to read frame!", pCapture->getName()); + delete pBuffer; + pBuffer = nullptr; + break; + } + + int64_t ts = Core::System::timestamp(); + Core::Packet::Ptr pPacket = new Core::Packet( + serialCodeManager().code(), Core::Packet::FLAG_IS_VIDEO, pBuffer, rsize, ts, ts); + + pBuffer = nullptr; + postToNextComponents(sharedFromThis(), pPacket, Core::MediaType::VIDEO); + } + catch (...) + { + if (pBuffer) + { + delete pBuffer; + pBuffer = nullptr; + } + } + } + + setSignalLost(true); + logger().warning("%s: Camera [%s] lost!", name(), pCapture->getName()); + } + }); +} + +void Input::stop() +{ + _task.cancel(); +} + +void Input::onConfigChanged(ParameterNames& names) +{ + if (!started()) + return; + + if (names.exists(_Path)) + { + stop(); + start(); + } +} +} +} diff --git a/video/v4l2/Input.h b/video/v4l2/Input.h new file mode 100644 index 0000000..2b351f8 --- /dev/null +++ b/video/v4l2/Input.h @@ -0,0 +1,44 @@ +#ifndef A523395E_7DEF_449B_B4DD_5FEE9147BE39 +#define A523395E_7DEF_449B_B4DD_5FEE9147BE39 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "../core/VideoComponent.h" +#include "util/SimpleFactory.h" +#include "util/Task.h" + +namespace Video +{ +namespace V4L2 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Input: public Core::VideoComponent, public ::Util::SimpleFactory +{ +public: + using Ptr = Poco::AutoPtr; + +public: + Input(const std::string& name); + ~Input(); + +public: + CONFIGURABLE_DECLARE_PARAMETER(CodecID, Core::CodecID); + CONFIGURABLE_DECLARE_PARAMETER(Path, std::string); + +protected: + void start() override; + void stop() override; + void onConfigChanged(ParameterNames& names) override; + +private: + Util::Task _task; +}; +} +} +#endif diff --git a/video/v4l2/InputFormat.cpp b/video/v4l2/InputFormat.cpp new file mode 100644 index 0000000..acfaf75 --- /dev/null +++ b/video/v4l2/InputFormat.cpp @@ -0,0 +1,83 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "InputFormat.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Video +{ +namespace V4L2 +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +void InputFormat::enumerate(const std::string& deviceName, InputFormat::Collection& c) +{ + int fd = open(deviceName.c_str(), O_RDWR); + if (fd < 0) + throw Poco::InvalidAccessException(Poco::format("Video::V4l2::InputFormat: Open %s FAIL: %d", deviceName, fd)); + + struct v4l2_fmtdesc fmt; + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.index = 0; + while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) == 0) + { + InputFormat format; + format.pixelFormat = fmt.pixelformat; + format.description = (char*)fmt.description; + + struct v4l2_frmsizeenum frmsize; + frmsize.index = 0; + frmsize.pixel_format = fmt.pixelformat; + while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0) + { + switch (frmsize.type) + { + case V4L2_FRMSIZE_TYPE_DISCRETE: + { + struct v4l2_frmivalenum frameinterval; + + frameinterval.width = frmsize.discrete.width; + frameinterval.height = frmsize.discrete.height; + frameinterval.pixel_format = fmt.pixelformat; + frameinterval.type = V4L2_FRMIVAL_TYPE_DISCRETE; + frameinterval.index = 0; + while (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frameinterval) == 0) + { + InputFormat::Resolution resolution; + resolution.width = frameinterval.width; + resolution.height = frameinterval.height; + resolution.fps = frameinterval.discrete.denominator / frameinterval.discrete.numerator; + format.resolutions.push_back(resolution); + + frameinterval.index++; + } + } + break; + } + + frmsize.index++; + } + + format.resolutions.sort([](auto& a, auto& b) { return a.width * a.height > b.width * b.height; }); + + c.push_back(format); + fmt.index++; + } + + close(fd); +} +} +} diff --git a/video/v4l2/InputFormat.h b/video/v4l2/InputFormat.h new file mode 100644 index 0000000..6208b96 --- /dev/null +++ b/video/v4l2/InputFormat.h @@ -0,0 +1,39 @@ +#ifndef D1EA41B1_FDDE_44D4_BDD5_2BA3EEBAC3B5 +#define D1EA41B1_FDDE_44D4_BDD5_2BA3EEBAC3B5 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Capture.h" + +namespace Video +{ +namespace V4L2 +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +struct InputFormat +{ + struct Resolution + { + uint32_t width; + uint32_t height; + uint32_t fps; + }; + + using Collection = std::list; + + uint32_t pixelFormat; + std::string description; + std::list resolutions; + +public: + static void enumerate(const std::string& deviceName, Collection& c); +}; +} +} +#endif diff --git a/video/v4l2/MmapDevice.cpp b/video/v4l2/MmapDevice.cpp new file mode 100755 index 0000000..f93b2be --- /dev/null +++ b/video/v4l2/MmapDevice.cpp @@ -0,0 +1,299 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "MmapDevice.h" +#include "Logger.h" + +namespace Video +{ +namespace V4L2 +{ +MmapDevice::MmapDevice(const DeviceParameters& params, v4l2_buf_type deviceType) + : Device(params, deviceType), n_buffers(0) +{ + memset(&m_buffer, 0, sizeof(m_buffer)); +} + +bool MmapDevice::init(unsigned int mandatoryCapabilities) +{ + bool ret = Device::init(mandatoryCapabilities); + if (ret) + { + ret = this->start(); + } + return ret; +} + +MmapDevice::~MmapDevice() +{ + this->stop(); +} + +bool MmapDevice::start() +{ + bool success = true; + struct v4l2_requestbuffers req; + memset(&req, 0, sizeof(req)); + req.count = V4L2MMAP_NBBUFFER; + req.type = m_deviceType; + req.memory = V4L2_MEMORY_MMAP; + + if (-1 == ioctl(m_fd, VIDIOC_REQBUFS, &req)) + { + if (EINVAL == errno) + { + logger().error("%s: does not support memory mapping", m_params.m_devName); + success = false; + } + else + { + logger().error("VIDIOC_REQBUFS"); + success = false; + } + } + else + { + memset(&m_buffer, 0, sizeof(m_buffer)); + for (n_buffers = 0; n_buffers < req.count; ++n_buffers) + { + struct v4l2_buffer buf; + memset(&buf, 0, sizeof(buf)); + buf.type = m_deviceType; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = n_buffers; + + if (-1 == ioctl(m_fd, VIDIOC_QUERYBUF, &buf)) + { + logger().error("VIDIOC_QUERYBUF"); + success = false; + } + else + { + logger().debug("%s: [%u]: size: %u offset: %u", m_params.m_devName, (uint32_t)n_buffers, + (uint32_t)buf.length, (uint32_t)buf.m.offset); + + m_buffer[n_buffers].length = buf.length; + if (!m_buffer[n_buffers].length) + { + m_buffer[n_buffers].length = buf.bytesused; + } + m_buffer[n_buffers].start = + mmap(NULL, m_buffer[n_buffers].length, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, buf.m.offset); + + if (MAP_FAILED == m_buffer[n_buffers].start) + { + logger().error("mmap"); + success = false; + } + } + } + + for (unsigned int i = 0; i < n_buffers; ++i) + { + struct v4l2_buffer buf; + memset(&buf, 0, sizeof(buf)); + buf.type = m_deviceType; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + if (-1 == ioctl(m_fd, VIDIOC_QBUF, &buf)) + { + logger().error("VIDIOC_QBUF"); + success = false; + } + } + + int type = m_deviceType; + if (-1 == ioctl(m_fd, VIDIOC_STREAMON, &type)) + { + logger().error("VIDIOC_STREAMON"); + success = false; + } + } + return success; +} + +bool MmapDevice::stop() +{ + bool success = true; + + int type = m_deviceType; + if (-1 == ioctl(m_fd, VIDIOC_STREAMOFF, &type)) + { + logger().error("VIDIOC_STREAMOFF"); + success = false; + } + + for (unsigned int i = 0; i < n_buffers; ++i) + { + if (-1 == munmap(m_buffer[i].start, m_buffer[i].length)) + { + logger().error("munmap"); + success = false; + } + } + + struct v4l2_requestbuffers req; + memset(&req, 0, sizeof(req)); + req.count = 0; + req.type = m_deviceType; + req.memory = V4L2_MEMORY_MMAP; + if (-1 == ioctl(m_fd, VIDIOC_REQBUFS, &req)) + { + logger().error("VIDIOC_REQBUFS"); + success = false; + } + + n_buffers = 0; + return success; +} + +size_t MmapDevice::readInternal(char* buffer, size_t bufferSize) +{ + size_t size = 0; + if (n_buffers > 0) + { + struct v4l2_buffer buf; + memset(&buf, 0, sizeof(buf)); + buf.type = m_deviceType; + buf.memory = V4L2_MEMORY_MMAP; + + if (-1 == ioctl(m_fd, VIDIOC_DQBUF, &buf)) + { + if (errno == EAGAIN) + { + size = 0; + } + else + { + logger().error("VIDIOC_DQBUF"); + size = -1; + } + } + else if (buf.index < n_buffers) + { + size = buf.bytesused; + if (size > bufferSize) + { + size = bufferSize; + logger().warning("%s: buffer truncated available: %u needed: %u", m_params.m_devName, + (uint32_t)bufferSize, (uint32_t)buf.bytesused); + } + memcpy(buffer, m_buffer[buf.index].start, size); + + if (-1 == ioctl(m_fd, VIDIOC_QBUF, &buf)) + { + logger().error("VIDIOC_QBUF"); + size = -1; + } + } + } + return size; +} + +size_t MmapDevice::writeInternal(char* buffer, size_t bufferSize) +{ + size_t size = 0; + if (n_buffers > 0) + { + struct v4l2_buffer buf; + memset(&buf, 0, sizeof(buf)); + buf.type = m_deviceType; + buf.memory = V4L2_MEMORY_MMAP; + + if (-1 == ioctl(m_fd, VIDIOC_DQBUF, &buf)) + { + logger().error("VIDIOC_DQBUF"); + size = -1; + } + else if (buf.index < n_buffers) + { + size = bufferSize; + if (size > buf.length) + { + logger().warning("%s: buffer truncated available: %u needed: %u", m_params.m_devName, + (uint32_t)buf.length, (uint32_t)size); + size = buf.length; + } + memcpy(m_buffer[buf.index].start, buffer, size); + buf.bytesused = size; + + if (-1 == ioctl(m_fd, VIDIOC_QBUF, &buf)) + { + logger().error("VIDIOC_QBUF"); + size = -1; + } + } + } + return size; +} + +bool MmapDevice::startPartialWrite() +{ + if (n_buffers <= 0) + return false; + if (m_partialWriteInProgress) + return false; + memset(&m_partialWriteBuf, 0, sizeof(m_partialWriteBuf)); + m_partialWriteBuf.type = m_deviceType; + m_partialWriteBuf.memory = V4L2_MEMORY_MMAP; + if (-1 == ioctl(m_fd, VIDIOC_DQBUF, &m_partialWriteBuf)) + { + logger().error("VIDIOC_DQBUF"); + return false; + } + m_partialWriteBuf.bytesused = 0; + m_partialWriteInProgress = true; + return true; +} + +size_t MmapDevice::writePartialInternal(char* buffer, size_t bufferSize) +{ + size_t new_size = 0; + size_t size = 0; + if ((n_buffers > 0) && m_partialWriteInProgress) + { + if (m_partialWriteBuf.index < n_buffers) + { + new_size = m_partialWriteBuf.bytesused + bufferSize; + if (new_size > m_partialWriteBuf.length) + { + logger().warning("%s: buffer truncated available: %u needed: %u", m_params.m_devName, + (uint32_t)m_partialWriteBuf.length, (uint32_t)new_size); + new_size = m_partialWriteBuf.length; + } + size = new_size - m_partialWriteBuf.bytesused; + memcpy(&((char*)m_buffer[m_partialWriteBuf.index].start)[m_partialWriteBuf.bytesused], buffer, size); + + m_partialWriteBuf.bytesused += size; + } + } + return size; +} + +bool MmapDevice::endPartialWrite() +{ + if (!m_partialWriteInProgress) + return false; + if (n_buffers <= 0) + { + m_partialWriteInProgress = false; + return true; + } + if (-1 == ioctl(m_fd, VIDIOC_QBUF, &m_partialWriteBuf)) + { + logger().error("VIDIOC_QBUF"); + m_partialWriteInProgress = false; + return true; + } + m_partialWriteInProgress = false; + return true; +} +} +} diff --git a/video/v4l2/MmapDevice.h b/video/v4l2/MmapDevice.h new file mode 100755 index 0000000..51c2640 --- /dev/null +++ b/video/v4l2/MmapDevice.h @@ -0,0 +1,43 @@ +#pragma once + +#include "Device.h" + +namespace Video +{ +namespace V4L2 +{ +#define V4L2MMAP_NBBUFFER 10 + +class MmapDevice: public Device +{ +protected: + size_t writeInternal(char* buffer, size_t bufferSize); + bool startPartialWrite(); + size_t writePartialInternal(char*, size_t); + bool endPartialWrite(); + size_t readInternal(char* buffer, size_t bufferSize); + +public: + MmapDevice(const DeviceParameters& params, v4l2_buf_type deviceType); + virtual ~MmapDevice(); + + virtual bool init(unsigned int mandatoryCapabilities); + virtual bool isReady() + { + return ((m_fd != -1) && (n_buffers != 0)); + } + virtual bool start(); + virtual bool stop(); + +protected: + unsigned int n_buffers; + + struct buffer + { + void* start; + size_t length; + }; + buffer m_buffer[V4L2MMAP_NBBUFFER]; +}; +} +} diff --git a/video/v4l2/Output.cpp b/video/v4l2/Output.cpp new file mode 100755 index 0000000..13dd3f1 --- /dev/null +++ b/video/v4l2/Output.cpp @@ -0,0 +1,81 @@ +#include "Output.h" +#include "MmapDevice.h" +#include "ReadWriteDevice.h" + +#include + +#include + +namespace Video +{ +namespace V4L2 +{ +Output* Output::create(const DeviceParameters& param) +{ + Output* videoOutput = NULL; + Device* videoDevice = NULL; + int caps = V4L2_CAP_VIDEO_OUTPUT; + switch (param.m_iotype) + { + case IOTYPE_MMAP: + videoDevice = new MmapDevice(param, V4L2_BUF_TYPE_VIDEO_OUTPUT); + caps |= V4L2_CAP_STREAMING; + break; + + case IOTYPE_READWRITE: + videoDevice = new ReadWriteDevice(param, V4L2_BUF_TYPE_VIDEO_OUTPUT); + caps |= V4L2_CAP_READWRITE; + break; + } + + if (videoDevice && !videoDevice->init(caps)) + { + delete videoDevice; + videoDevice = NULL; + } + + if (videoDevice) + { + videoOutput = new Output(videoDevice); + } + return videoOutput; +} + +Output::Output(Device* device): Access(device) +{ +} + +Output::~Output() +{ +} + +bool Output::isWritable(timeval* tv) +{ + int fd = m_device->getFd(); + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(fd, &fdset); + return (select(fd + 1, NULL, &fdset, NULL, tv) == 1); +} + +size_t Output::write(char* buffer, size_t bufferSize) +{ + return m_device->writeInternal(buffer, bufferSize); +} + +bool Output::startPartialWrite() +{ + return m_device->startPartialWrite(); +} + +size_t Output::writePartial(char* buffer, size_t bufferSize) +{ + return m_device->writePartialInternal(buffer, bufferSize); +} + +bool Output::endPartialWrite() +{ + return m_device->endPartialWrite(); +} +} +} diff --git a/video/v4l2/Output.h b/video/v4l2/Output.h new file mode 100755 index 0000000..7612437 --- /dev/null +++ b/video/v4l2/Output.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Access.h" + +namespace Video +{ +namespace V4L2 +{ +class Output: public Access +{ +protected: + explicit Output(Device* device); + +public: + static Output* create(const DeviceParameters& param); + virtual ~Output(); + + size_t write(char* buffer, size_t bufferSize); + bool isWritable(timeval* tv); + bool startPartialWrite(); + size_t writePartial(char* buffer, size_t bufferSize); + bool endPartialWrite(); +}; +} +} diff --git a/video/v4l2/ReadWriteDevice.cpp b/video/v4l2/ReadWriteDevice.cpp new file mode 100755 index 0000000..3924caa --- /dev/null +++ b/video/v4l2/ReadWriteDevice.cpp @@ -0,0 +1,23 @@ +#include + +#include "ReadWriteDevice.h" + +namespace Video +{ +namespace V4L2 +{ +ReadWriteDevice::ReadWriteDevice(const DeviceParameters& params, v4l2_buf_type deviceType): Device(params, deviceType) +{ +} + +size_t ReadWriteDevice::writeInternal(char* buffer, size_t bufferSize) +{ + return ::write(m_fd, buffer, bufferSize); +} + +size_t ReadWriteDevice::readInternal(char* buffer, size_t bufferSize) +{ + return ::read(m_fd, buffer, bufferSize); +} +} +} diff --git a/video/v4l2/ReadWriteDevice.h b/video/v4l2/ReadWriteDevice.h new file mode 100755 index 0000000..10cdeb9 --- /dev/null +++ b/video/v4l2/ReadWriteDevice.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Device.h" + +namespace Video +{ +namespace V4L2 +{ +class ReadWriteDevice: public Device +{ +protected: + virtual size_t writeInternal(char* buffer, size_t bufferSize); + virtual size_t readInternal(char* buffer, size_t bufferSize); + +public: + ReadWriteDevice(const DeviceParameters& params, v4l2_buf_type deviceType); +}; +} +} diff --git a/video/video.cmake b/video/video.cmake new file mode 100644 index 0000000..7f8e4e4 --- /dev/null +++ b/video/video.cmake @@ -0,0 +1,3 @@ +add_subdirectory(video) +include_directories(video) +target_link_libraries(${PROJNAME} PRIVATE video) diff --git a/video/zlmediakit/CMakeLists.txt b/video/zlmediakit/CMakeLists.txt new file mode 100644 index 0000000..7f992ad --- /dev/null +++ b/video/zlmediakit/CMakeLists.txt @@ -0,0 +1,17 @@ +set(target zlmediakit) + +include_directories( + zlmediakit +) + +aux_source_directory(. ZL_SOURCES) + +add_library(${target} STATIC ${ZL_SOURCES}) + +target_link_libraries(${target} PRIVATE + $ENV{OPENSSL_PATH}/lib/libssl.a + $ENV{OPENSSL_PATH}/lib/libcrypto.a + pthread + dl + resolv +) \ No newline at end of file diff --git a/video/zlmediakit/Service.cpp b/video/zlmediakit/Service.cpp new file mode 100644 index 0000000..865a802 --- /dev/null +++ b/video/zlmediakit/Service.cpp @@ -0,0 +1,604 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "Service.h" +#include +#include +#include +#include +#include +#include +#include +#include "Application.h" +#include "Logger.h" +#include "Poco/Dynamic/Struct.h" +#include "Poco/Dynamic/Var.h" +#include "Poco/Exception.h" +#include "Poco/JSON/JSONException.h" +#include "Poco/JSON/Object.h" +#include "Poco/JSON/Parser.h" +#include "Poco/JSON/Query.h" +#include "Poco/Net/HTTPClientSession.h" +#include "Poco/Net/HTTPRequest.h" +#include "Poco/Net/HTTPServerResponse.h" +#include "Poco/URI.h" + +namespace Video +{ +namespace ZLMediaKit +{ +static const std::string iniConfigFilePath = "/customer/config.ini"; +static int zlHttpPort = 0; + +static Poco::JSON::Object::Ptr currZlConfig; +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ + +Poco::Dynamic::Array Service::getMediaList(std::string schema, std::string app, std::string stream) +{ + std::string _schema = schema == "srt" ? "ts" : schema; + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/getMediaList"); + uri.addQueryParameter("vhost", "__defaultVhost__"); + uri.addQueryParameter("schema", _schema); + uri.addQueryParameter("app", app); + uri.addQueryParameter("stream", stream); + + Poco::DynamicStruct json = sendRequest(uri.toString()); + Poco::Dynamic::Array data; + + try + { + int code = json["code"]; + if (0 == code) + { + if (json["data"].isArray()) + { + data = json["data"].extract(); + } + else + { + } + } + } + catch (Poco::Exception& e) + { + logger().information(e.displayText()); + } + + return data; +} + +bool Service::isMediaOnline(std::string schema, std::string app, std::string stream) +{ + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/isMediaOnline"); + uri.addQueryParameter("vhost", "__defaultVhost__"); + uri.addQueryParameter("schema", schema); + uri.addQueryParameter("app", app); + uri.addQueryParameter("stream", stream); + Poco::DynamicStruct json = sendRequest(uri.toString()); + try + { + int code = json["code"]; + if (0 == code) + return json["online"]; + } + catch (Poco::Exception& e) + { + logger().information(e.displayText()); + } + return false; +} + +Poco::Tuple Service::addStreamPusherProxy(std::string schema, std::string app, std::string stream, + std::string url, std::string rtp_type) +{ + std::string _schema = schema == "srt" ? "ts" : schema; + _schema = (_schema == "webrtc" || _schema == "webrtcs") ? "rtsp" : _schema; + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/addStreamPusherProxy"); + uri.addQueryParameter("vhost", "__defaultVhost__"); + uri.addQueryParameter("schema", _schema); + uri.addQueryParameter("app", app); + uri.addQueryParameter("stream", stream); + uri.addQueryParameter("dst_url", url); + uri.addQueryParameter("rtp_type", rtp_type); + auto json = sendRequest(uri.toString()); + + Poco::Tuple rv; + try + { + int code = json["code"]; + rv.set<0>(code); + if (0 == code) + { + rv.set<1>(json["data"]["key"]); + } + else + { + logger().warning("addStreamPusherProxy %s %s/%s, error msg: " + json["msg"], _schema, app, stream); + } + } + catch (Poco::Exception& e) + { + logger().warning("addStreamPusherProxy error:%s %s/%s %s", _schema, app, stream, e.displayText()); + } + + return rv; +} + +int Service::delStreamPusherProxy(std::string key) +{ + if (key.empty()) + { + return 1; + } + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/delStreamPusherProxy"); + uri.addQueryParameter("key", key); + try + { + auto json = sendRequest(uri.toString()); + return json["code"]; + } + catch (Poco::Exception& e) + { + logger().error("zl delStreamPusherProxy:%s", std::string(e.what())); + } + return 1; +} + +int Service::getStremPusherProxyInfo(std::string key, StremPusherProxyInfo& info) +{ + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/getProxyPusherInfo"); + uri.addQueryParameter("key", key); + auto json = sendRequest(uri.toString()); + + Poco::Tuple rv; + try + { + int code = json["code"]; + info.code = code; + if (0 == code) + { + info.status = json["data"]["status"]; + info.liveSeconds = json["data"]["liveSecs"]; + info.republishCount = json["data"]["rePublishCount"]; + } + else + { + info.msg = json["msg"].toString(); + } + } + catch (Poco::Exception& e) + { + logger().information(e.displayText()); + } + + return json["code"]; +} + +int Service::startSendRtp(std::string app, std::string stream, std::string ssrc, std::string url, std::string port, + std::string udp) +{ + Poco::URI uri; + + addSecurityParameter(uri); + uri.setPath("/index/api/startSendRtp"); + uri.addQueryParameter("vhost", "__defaultVhost__"); + uri.addQueryParameter("app", app); + uri.addQueryParameter("stream", stream); + uri.addQueryParameter("ssrc", ssrc); + uri.addQueryParameter("dst_url", url); + uri.addQueryParameter("dst_port", port); + uri.addQueryParameter("src_port", "6000"); + uri.addQueryParameter("is_udp", udp); + auto json = sendRequest(uri.toString()); + return json["code"]; +} + +int Service::startSendRtpPassive(std::string app, std::string stream, std::string ssrc, std::string udp) +{ + Poco::URI uri; + + addSecurityParameter(uri); + uri.setPath("/index/api/startSendRtpPassive"); + uri.addQueryParameter("vhost", "__defaultVhost__"); + uri.addQueryParameter("app", app); + uri.addQueryParameter("stream", stream); + uri.addQueryParameter("ssrc", ssrc); + uri.addQueryParameter("src_port", "6000"); + uri.addQueryParameter("is_udp", udp); + auto json = sendRequest(uri.toString()); + return json["code"]; +} +int Service::stopSendRtp(std::string app, std::string stream, std::string ssrc) +{ + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/stopSendRtp"); + uri.addQueryParameter("vhost", "__defaultVhost__"); + uri.addQueryParameter("app", app); + uri.addQueryParameter("stream", stream); + uri.addQueryParameter("ssrc", ssrc); + auto json = sendRequest(uri.toString()); + return json["code"]; +} +bool Service::isRecording(std::string app, std::string stream, int type) +{ + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/isRecording"); + uri.addQueryParameter("vhost", "__defaultVhost__"); + uri.addQueryParameter("app", app); + uri.addQueryParameter("stream", stream); + uri.addQueryParameter("type", std::to_string(type)); + auto json = sendRequest(uri.toString()); + if (json["code"] == 0) + { + return json["status"]; + } + return false; +} +bool Service::startRecord(std::string app, std::string stream, int type, std::string customized_path, int max_second) +{ + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/startRecord"); + uri.addQueryParameter("vhost", "__defaultVhost__"); + uri.addQueryParameter("app", app); + uri.addQueryParameter("stream", stream); + uri.addQueryParameter("type", std::to_string(type)); + uri.addQueryParameter("customized_path", customized_path); + uri.addQueryParameter("max_second", std::to_string(max_second)); + auto json = sendRequest(uri.toString()); + logger().information("startRecord app=%s,stream=%s, zl return: %s", app, stream, json.toString()); + if (json["code"] == 0) + { + return json["result"]; + } + return false; +} +bool Service::stopRecord(std::string app, std::string stream, int type) +{ + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/stopRecord"); + uri.addQueryParameter("vhost", "__defaultVhost__"); + uri.addQueryParameter("app", app); + uri.addQueryParameter("stream", stream); + uri.addQueryParameter("type", std::to_string(type)); + auto json = sendRequest(uri.toString()); + logger().information("stopRecord app=%s,stream=%s, zl return: %s", app, stream, json.toString()); + if (json["code"] == 0) + { + return json["result"]; + } + return true; +} +std::string Service::getMp4RecordFile(std::string app, std::string stream, std::string period, + std::string customized_path) +{ + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/getMp4RecordFile"); + uri.addQueryParameter("vhost", "__defaultVhost__"); + uri.addQueryParameter("app", app); + uri.addQueryParameter("stream", stream); + uri.addQueryParameter("period", period); + uri.addQueryParameter("customized_path", customized_path); + auto json = sendRequest(uri.toString()); + if (json["code"] == 0) + { + return json["data"]; + } + return "{}"; +} + +Poco::Tuple Service::addStreamProxy(std::string app, std::string stream, std::string url, + int auto_close, int rtp_type) +{ + std::string _url = url; + if (Poco::startsWith(Poco::toLower(_url), std::string("srt://"))) + { + Poco::replaceInPlace(_url, ",m=publish", ""); + } + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/addStreamProxy"); + uri.addQueryParameter("vhost", "__defaultVhost__"); + uri.addQueryParameter("app", app); + uri.addQueryParameter("stream", stream); + uri.addQueryParameter("url", _url); + uri.addQueryParameter("rtp_type", std::to_string(rtp_type)); + uri.addQueryParameter("auto_close", std::to_string(auto_close)); + auto json = sendRequest(uri.toString()); + + Poco::Tuple rv; + try + { + int code = json["code"]; + rv.set<0>(code); + if (0 == code) + rv.set<1>(json["data"]["key"]); + } + catch (Poco::Exception& e) + { + logger().information(e.displayText()); + } + return rv; +} +bool Service::delStreamProxy(std::string key) +{ + if (key.empty()) + { + logger().warning("delStreamProxy key is empty"); + return true; + } + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/delStreamProxy"); + uri.addQueryParameter("key", key); + auto json = sendRequestReturnJson(uri.toString()); + int code = json->get("code"); + try + { + if (0 == code) + { + return json->getObject("data")->getValue("flag"); + } + } + catch (Poco::Exception& e) + { + logger().error(e.displayText()); + } + return false; +} +int Service::getProxyInfo(std::string key, StreamProxyInfo& info) +{ + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/getProxyInfo"); + uri.addQueryParameter("key", key); + auto json = sendRequest(uri.toString()); + int code = json["code"]; + try + { + info.code = code; + if (0 == code) + { + info.status = json["data"]["status"]; + info.liveSecs = json["data"]["liveSecs"]; + info.rePullCount = json["data"]["rePullCount"]; + } + else + { + info.msg = json["msg"].toString(); + } + } + catch (Poco::Exception& e) + { + logger().information(e.displayText()); + } + return code; +} + +Poco::Tuple Service::close_streams(std::string app, std::string stream, int force) +{ + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/close_streams"); + uri.addQueryParameter("vhost", "__defaultVhost__"); + uri.addQueryParameter("app", app); + uri.addQueryParameter("stream", stream); + uri.addQueryParameter("force", std::to_string(force)); + auto json = sendRequest(uri.toString()); + + Poco::Tuple rv; + try + { + int code = json["code"]; + rv.set<0>(code); + if (0 == code) + { + } + } + catch (Poco::Exception& e) + { + logger().information(e.displayText()); + } + return rv; +} + +Poco::Dynamic::Array Service::listStreamProxy() +{ + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/listStreamProxy"); + + Poco::DynamicStruct json = sendRequest(uri.toString()); + Poco::Dynamic::Array data; + + try + { + int code = json["code"]; + if (0 == code) + { + if (json["data"].isArray()) + { + data = json["data"].extract(); + } + else + { + } + } + } + catch (Poco::Exception& e) + { + logger().information(e.displayText()); + } + + return data; +} + +Poco::Dynamic::Array Service::listStreamPusherProxy() +{ + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/listStreamPusherProxy"); + + Poco::DynamicStruct json = sendRequest(uri.toString()); + Poco::Dynamic::Array data; + + try + { + int code = json["code"]; + if (0 == code) + { + if (json["data"].isArray()) + { + data = json["data"].extract(); + } + else + { + } + } + } + catch (Poco::Exception& e) + { + logger().information(e.displayText()); + } + + return data; +} + +Poco::JSON::Object::Ptr Service::getServerConfig() +{ + if (!currZlConfig.isNull()) + { + return currZlConfig; + } + + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/getServerConfig"); + try + { + auto json = sendRequestReturnJson(uri.toString()); + + int code = json->get("code"); + if (0 == code) + { + currZlConfig = json->getArray("data")->getObject(0); + } + } + catch (std::exception& e) + { + logger().error("getServerConfig==========" + std::string(e.what())); + } + return currZlConfig; +} + +bool Service::setServerConfig(std::string key, std::string val) +{ + Poco::URI uri; + addSecurityParameter(uri); + uri.setPath("/index/api/setServerConfig"); + uri.addQueryParameter(key, val); + auto json = sendRequest(uri.toString()); + try + { + int code = json["code"]; + if (0 == code) + { + return true; + } + } + catch (Poco::Exception& e) + { + logger().error(e.displayText()); + } + return false; +} + +Poco::DynamicStruct Service::sendRequest(std::string uri) +{ + Poco::Net::HTTPClientSession session("127.0.0.1", getHttpPort()); + Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, uri); + Poco::Net::HTTPResponse response; + session.sendRequest(request); + std::istream& st = session.receiveResponse(response); + std::string rawData; + Poco::StreamCopier::copyToString(st, rawData); + Poco::DynamicStruct json = *Poco::JSON::Parser().parse(rawData).extract(); + int code = json["code"]; + if (0 != code) + { + logger().debug("zl sendRequest,uri: %s,return: %s", uri, rawData); + } + return json; +} + +Poco::JSON::Object::Ptr Service::sendRequestReturnJson(std::string uri) +{ + Poco::Net::HTTPClientSession session("127.0.0.1", getHttpPort()); + Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, uri); + Poco::Net::HTTPResponse response; + session.sendRequest(request); + std::istream& st = session.receiveResponse(response); + std::string rawData; + Poco::StreamCopier::copyToString(st, rawData); + auto json = Poco::JSON::Parser().parse(rawData).extract(); + if (0 != json->getValue("code")) + { + logger().debug("zl sendRequest,uri: %s,return: %s", uri, rawData); + } + return json; +} + +void Service::addSecurityParameter(Poco::URI& uri) +{ + uri.addQueryParameter("secret", "035c73f7-bb6b-4889-a715-d9eb2d1925dd"); +} + +std::string Service::getDefaultHost() +{ + return "__defaultVhost__"; +} + +Poco::AutoPtr Service::loadIniFileConfig() +{ + try + { + return new Poco::Util::IniFileConfiguration(iniConfigFilePath); + } + catch (const Poco::Exception& ex) + { + logger().error("Service::loadIniFileConfig, Error loading INI file:%s", ex.displayText()); + return nullptr; + } +} + +int Service::getHttpPort() +{ + if (zlHttpPort <= 0) + { + auto iniConfig = loadIniFileConfig(); + zlHttpPort = iniConfig->getInt("http.port"); + } + return zlHttpPort; +} +} +} diff --git a/video/zlmediakit/Service.h b/video/zlmediakit/Service.h new file mode 100644 index 0000000..8007619 --- /dev/null +++ b/video/zlmediakit/Service.h @@ -0,0 +1,121 @@ +#ifndef C85BB91A_0670_4C40_AB2B_A96B8DDBC346 +#define C85BB91A_0670_4C40_AB2B_A96B8DDBC346 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include +#include "Poco/Dynamic/Struct.h" +#include "Poco/Tuple.h" +#include "Poco/JSON/Object.h" +#include "Poco/JSON/Parser.h" +#include "Poco/JSON/Query.h" +#include "Poco/JSON/JSONException.h" +#include +#include + +namespace Video +{ +namespace ZLMediaKit +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class Service +{ +public: + struct StremPusherProxyInfo + { + int code = 0; + std::string msg; + + int status = 1; + + uint64_t liveSeconds; + + uint64_t republishCount; + }; + + struct StreamProxyInfo + { + int code = 0; + std::string msg; + + int status = 1; + + uint64_t liveSecs; + + uint64_t rePullCount; + }; + +public: + static std::string getDefaultHost(); + + static Poco::Dynamic::Array getMediaList(std::string schema = "", std::string app = "", std::string stream = ""); + + static bool isMediaOnline(std::string schema, std::string app, std::string stream); + + static Poco::Tuple addStreamPusherProxy(std::string schema, std::string app, std::string stream, + std::string url, std::string rtp_type = "1"); + + static int delStreamPusherProxy(std::string key); + + static int getStremPusherProxyInfo(std::string key, StremPusherProxyInfo& info); + + static int startSendRtp(std::string app, std::string stream, std::string ssrc, std::string url, std::string port, + std::string udp = "1"); + + static int startSendRtpPassive(std::string app, std::string stream, std::string ssrc, std::string udp = "1"); + + static int stopSendRtp(std::string app, std::string stream, std::string ssrc); + + static bool isRecording(std::string app, std::string stream, int type); + + static bool startRecord(std::string app, std::string stream, int type, std::string customized_path, int max_second); + + static bool stopRecord(std::string app, std::string stream, int type); + + static std::string getMp4RecordFile(std::string app, std::string stream, std::string period, + std::string customized_path); + + static Poco::Tuple addStreamProxy(std::string app, std::string stream, std::string url, + int auto_close = 1, int rtp_type = 0); + + static bool delStreamProxy(std::string key); + + static int getProxyInfo(std::string key, StreamProxyInfo& info); + + static Poco::Tuple close_streams(std::string app, std::string stream, int force = 1); + + static Poco::JSON::Object::Ptr getServerConfig(); + + template static T getServerConfigOne(std::string paramKey) + { + auto allConfig = getServerConfig(); + T t = allConfig->getValue(paramKey); + return t; + } + + static bool setServerConfig(std::string key, std::string val); + + static Poco::Dynamic::Array listStreamProxy(); + + static Poco::Dynamic::Array listStreamPusherProxy(); + +private: + static Poco::DynamicStruct sendRequest(std::string url); + + static Poco::JSON::Object::Ptr sendRequestReturnJson(std::string uri); + + static void addSecurityParameter(Poco::URI& uri); + + static Poco::AutoPtr loadIniFileConfig(); + + static int getHttpPort(); +}; +} +} +#endif diff --git a/video/zlmediakit/StreamPusher.cpp b/video/zlmediakit/StreamPusher.cpp new file mode 100644 index 0000000..ec9ff32 --- /dev/null +++ b/video/zlmediakit/StreamPusher.cpp @@ -0,0 +1,122 @@ +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "StreamPusher.h" +#include "Service.h" +#include "Logger.h" +#include + +namespace Video +{ +namespace ZLMediaKit +{ +/* + ******************************************************************************* + * CLASS DEFINE + ******************************************************************************* + */ +StreamPusher::StreamPusher(const std::string& name, const std::string& streamApp, const std::string streamID) + : _name(name + "/ZLStreamPusher"), _streamApp(streamApp), _streamID(streamID), _task(name) +{ + setConnected(false); + + setURL(""); + setUsingTCPTransport(false); + setRetryInterval(2000); +} + +StreamPusher::~StreamPusher() +{ +} + +void StreamPusher::start() +{ + if (streamApp().empty() || streamID().empty() || getURL().empty()) + throw Poco::InvalidArgumentException(_name + ": stream-app, stream-id or URL empty"); + + _task.run( + [this](std::atomic& isCancelled) -> void + { + setStarted(true); + const std::string scheme = Poco::URI(getURL()).getScheme(); + while (!isCancelled) + { + setConnected(false); + + while (!isCancelled) + { + try + { + auto result = Service::addStreamPusherProxy(scheme, streamApp(), streamID(), getURL()); + int code = result.get<0>(); + if (code == 0) + { + setConnected(true); + + _key = result.get<1>(); + ConnectionChanged.notify(this); + break; + } + } + catch (Poco::Exception& e) + { + logger().error("%s: Create %s stream pusher FAIL: %s", name(), scheme, e.displayText()); + } + catch (std::exception& e) + { + logger().error("%s: Create %s stream pusher FAIL: %s", name(), scheme, (std::string)e.what()); + } + + Poco::Thread::sleep(getRetryInterval()); + } + + while (!isCancelled) + { + Poco::Thread::sleep(2000); + Service::StremPusherProxyInfo info; + info.status = 1; + Service::getStremPusherProxyInfo(_key, info); + bool connected = (info.status == 0); + if (connected != getConnected()) + { + setConnected(connected); + ConnectionChanged.notify(this); + } + if (info.code != 0 && info.msg == "can not find pusher") + { + break; + } + } + } + + Service::delStreamPusherProxy(_key); + if (getConnected()) + { + setConnected(false); + ConnectionChanged.notify(this); + } + + setStarted(false); + }); +} + +void StreamPusher::cancel() +{ + _task.cancel(); +} + +void StreamPusher::onConfigChanged(ParameterNames& names) +{ + if (!getStarted()) + return; + + if (names.exists(_URL) || names.exists(_UsingTCPTransport)) + { + cancel(); + start(); + } +} +} +} diff --git a/video/zlmediakit/StreamPusher.h b/video/zlmediakit/StreamPusher.h new file mode 100644 index 0000000..8005192 --- /dev/null +++ b/video/zlmediakit/StreamPusher.h @@ -0,0 +1,54 @@ +#ifndef CAC2CCAF_4F83_412E_992A_82EA76F15756 +#define CAC2CCAF_4F83_412E_992A_82EA76F15756 +/* + ******************************************************************************* + * INCLUDE + ******************************************************************************* + */ +#include "util/Configurable.h" +#include "util/ClassAssistant.h" +#include "util/Task.h" +#include + +namespace Video +{ +namespace ZLMediaKit +{ +/* + ******************************************************************************* + * CLASS DECLARE + ******************************************************************************* + */ +class StreamPusher: public Util::Configurable +{ +public: + using Ptr = Poco::SharedPtr; + + StreamPusher(const std::string& name, const std::string& streamApp, const std::string streamID); + ~StreamPusher(); + + void start(); + void cancel(); + + READONLY_PROPERTY_DECLARE(name, std::string) + READONLY_PROPERTY_DECLARE(streamApp, std::string) + READONLY_PROPERTY_DECLARE(streamID, std::string) + PROPERTY_DECLARE(Started, bool, false, private, public) + PROPERTY_DECLARE(Connected, bool, false, private, public) + + CONFIGURABLE_DECLARE_PARAMETER(URL, std::string) + CONFIGURABLE_DECLARE_PARAMETER(UsingTCPTransport, bool) + CONFIGURABLE_DECLARE_PARAMETER(RetryInterval, uint32_t) + + Poco::BasicEvent ConnectionChanged; + +protected: + void onConfigChanged(ParameterNames& names) override; + +private: + Util::Task _task; + std::string _key; +}; +} +} +#endif