diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..378eac25d --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +build diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..579061a0a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,40 @@ +#sources +*.c text +*.cc text +*.cxx text +*.cpp text +*.c++ text +*.hpp text +*.h text +*.h++ text +*.hh text + +# Compiled Object files +*.slo binary +*.lo binary +*.o binary +*.obj binary + + +# Precompiled Headers +*.gch binary +*.pch binary + + +# Compiled Dynamic libraries +*.so binary +*.dylib binary +*.dll binary + + +# Compiled Static libraries +*.lai binary +*.la binary +*.a binary +*.lib binary + + +# Executables +*.exe binary +*.out binary +*.app binary \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..a46e89e52 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,88 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Analysis" + +on: + push: + branches: [ "main" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + schedule: + - cron: '40 9 * * 0' + +env: + # Path to the CMake build directory. + build: '${{ github.workspace }}/build' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup VCPKG + uses: friendlyanon/setup-vcpkg@v1 + with: { committish: 63aa65e65b9d2c08772ea15d25fb8fdb0d32e557 } + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + #- name: Autobuild + #uses: github/codeql-action/autobuild@v2 + + - name: Configure CMake + run: cmake -B ${{ env.build }} --preset linux-x64-release + + # Build is not required for MSVC Code Analysis and will be used for Codecov + - name: Build CMake + run: cmake --build ${{ env.build }} --preset linux-x64-release + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/msvc.yml b/.github/workflows/msvc.yml new file mode 100644 index 000000000..5dc37805f --- /dev/null +++ b/.github/workflows/msvc.yml @@ -0,0 +1,97 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# +# Find more information at: +# https://github.com/microsoft/msvc-code-analysis-action + +name: Microsoft C++ Code Analysis + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + schedule: + - cron: "41 16 * * 1" + +env: + # Path to the CMake build directory. + build: "${{ github.workspace }}/build" + +permissions: + contents: read + +jobs: + analyze: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + name: Analyze + runs-on: windows-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup VCPKG + uses: friendlyanon/setup-vcpkg@v1 + with: { committish: 63aa65e65b9d2c08772ea15d25fb8fdb0d32e557 } + + - name: Get SW + uses: egorpugin/sw-action@master + + - name: SW setup and add to PATH + run: | + ./sw setup + echo "D:\a\WolfEngine\WolfEngine" >> $env:GITHUB_PATH + + - name: Setup OpenCppCoverage and add to PATh + id: setup_opencppcoverage + run: | + choco install OpenCppCoverage -y + echo "C:\Program Files\OpenCppCoverage" >> $env:GITHUB_PATH + + - name: Configure CMake + run: cmake -DCMAKE_BUILD_TYPE=Debug -B ${{ env.build }} + + + # Build is not required for MSVC Code Analysis and will be used for Codecov + - name: Build CMake + run: cmake --build ${{ env.build }} + + - name: Run MSVC Code Analysis + uses: microsoft/msvc-code-analysis-action@04825f6d9e00f87422d6bf04e1a38b1f3ed60d99 + # Provide a unique ID to access the sarif output path + id: run-analysis + with: + cmakeBuildDirectory: ${{ env.build }} + # Ruleset file that will determine what checks will be run + ruleset: NativeRecommendedRules.ruleset + ignoredTargetPaths: ${{ env.build }}/_deps/boost_chrono-src;${{ env.build }}/_deps/boost_context-src;${{ env.build }}/_deps/boost_coroutine-src;${{ env.build }}/_deps/boost_date_time-src;${{ env.build }}/_deps/boost_exception-src;${{ env.build }}/_deps/fmt-src;${{ env.build }}/_deps/boost_container-src;${{ env.build }}/_deps/opencv-src;${{ env.build }}/_deps/rapidjson-src;${{ env.build }}/_deps/tesseract-src + + - name: Generate Codecov Report + id: generate_test_report + shell: cmd + run: OpenCppCoverage.exe --continue_after_cpp_exception --export_type cobertura:WolfCov.xml --sources %CD% --excluded_sources %CD%\build\_deps -- %CD%\build\wolf\Debug\wolf_tests.exe + - name: Upload Report to Codecov + uses: codecov/codecov-action@v2 + with: + files: ./WolfCov.xml + fail_ci_if_error: true + functionalities: fix + + # Upload SARIF file to GitHub Code Scanning Alerts + #- name: Upload SARIF to GitHub + # uses: github/codeql-action/upload-sarif@v2 + # with: + # sarif_file: ${{ steps.run-analysis.outputs.sarif }} + + # Upload SARIF file as an Artifact to download and view + - name: Upload SARIF as an Artifact + uses: actions/upload-artifact@v3 + with: + name: sarif-file + path: ${{ steps.run-analysis.outputs.sarif }} diff --git a/.gitignore b/.gitignore index b8bd0267b..40c135a73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,31 @@ +*.log +*.tlog +*.wLog +*.idb +*.pdb +*.ipch +*.ilk +*.recipe +*.res +*.enc +*.vscode +*.advixeproj +*.advixeexp +*.dflgadvixe +*.infoadvixe +*.DS_Store +*.db +*.db-shm +*.db-wal +*.opendb +*.DS_Store +*.pem + +/bin/* +/build/* +/coverage/* +/install/* + # Compiled Object files *.slo *.lo @@ -8,21 +36,12 @@ *.gch *.pch -# Compiled Dynamic libraries -*.so -*.dylib -*.dll # Fortran module files *.mod -# Compiled Static libraries -*.lai -*.la -*.a -*.lib # Executables *.exe *.out -*.app +*.app \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..e6f019818 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,85 @@ +{ + "C_Cpp.default.configurationProvider": "vector-of-bool.cmake-tools", + "editor.formatOnSave": true, + "files.associations": { + "__locale": "cpp", + "regex": "cpp", + "*.ipp": "cpp", + "pointers": "cpp", + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "strstream": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "coroutine": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "source_location": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "semaphore": "cpp", + "shared_mutex": "cpp", + "span": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cfenv": "cpp", + "cinttypes": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "variant": "cpp" + } +} \ No newline at end of file diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md new file mode 100644 index 000000000..98a392b70 --- /dev/null +++ b/CHANGE_LOG.md @@ -0,0 +1,46 @@ +# ToDos +- Dynamic lod creator for lod sample +- Forward+ +- DirectX 12 +- Realtime Raytracing +- DEBUG, RELEASE, MinSizeRelease(does not have assimp and just use wscene files) + +# 2.1 +A major release, rewrite most of wolf.system with pure C + - fixed some bugs + - improved memory pool + - executing python from c has been added + +# 2.0.0.0 +A major release, rewrite most of wolf.system with pure C + - Build for Win64 + - Build for OSX + - Build for iOS + - Build for Android-armv7 + - Build for Linux64 + + +# 1.68.0.9 (2018-10-03) +A minor release with many compatibility-breaking changes. + +New features: +- CMAKE added for building wolf for linux platform + +# 1.65.0.0 (2018-06-04) +A minor release with many compatibility-breaking changes. + +New features: +- system::w_logger optimized and integrated with spdlog +- framework::w_media_core optimized for streaming +- gpu occlusion culling has been added + + +# 1.63.1.0 (2018-04-19) + +A minor release with many compatibility-breaking changes. + +New features: +- Integrated with Vulkan SDK version 1.1.73.0 +- Integrated with VulkanMemoryAllocator for better gpu memory managment +- The new coordinate system is Left handed Y-Up +- The function "contains" have been added to wolf::system::w_bounding_sphere diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..0714bbec8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.22) + +# set the name of the projects +project(wolf) +add_subdirectory(wolf) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 000000000..b0460e2a8 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,258 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 20 + }, + "configurePresets": [ + { + "name": "base", + "hidden": true, + "description": "base project for other configurations.", + "binaryDir": "${sourceDir}/build/${presetName}", + "installDir": "${sourceDir}/install/${presetName}", + "generator": "Ninja", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "ARCH": "win64" + } + }, + { + "name": "wasm32-unknown-emscripten-debug", + "displayName": "wasm32-unknown-emscripten", + "description": "Configure debug mode based on Emscripten", + "inherits": "base", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "$env{EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "wasm32-unknown-emscripten-release", + "displayName": "Win x64 Release", + "description": "Configure release mode based on Emscripten", + "inherits": "wasm32-unknown-emscripten-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "win-x64-debug", + "displayName": "Win x64 Debug", + "description": "Sets windows platform and debug build type for x64 arch", + "inherits": "base", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "win-x64-release", + "displayName": "Win x64 Release", + "description": "Sets windows platform and release build type for x64 arch", + "inherits": "win-x64-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "base-android", + "hidden": true, + "inherits": "base", + "environment": { + "ANDROID_NDK_HOME": "$env{NDK}", + "ANDROID_NDK": "$env{NDK}", + "ANDROID_NATIVE_API_LEVEL": "24", + "ANDROID_PLATFORM": "android-24" + }, + "cacheVariables": { + "ANDROID": true, + "CMAKE_TOOLCHAIN_FILE": "$env{ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake", + "ANDROID_STL": "c++_shared", + "ANDROID_ABI": "arm64-v8a", + "CPU": "armv8", + "CMAKE_ANDROID_ARCH_ABI": "arm64-v8a", + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", + "CMAKE_SYSTEM_NAME": "Android" + } + }, + { + "name": "android-arm64-debug", + "inherits": "base-android", + "displayName": "Android ARM64 v8a Debug", + "description": "Sets android platform and debug build type for arm64-v8a arch", + "architecture": { + "value": "arm64-v8a", + "strategy": "external" + }, + "environment": { + "ANDROID_ABI": "arm64-v8a", + "CPU": "armv8", + "ARCH": "aarch64", + "CMAKE_ANDROID_ARCH_ABI": "arm64-v8a", + "CMAKE_BUILD_TYPE": "Debug" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "android-arm64-release", + "displayName": "Android ARM64 v8a Release", + "description": "Sets android platform and release build type for arm64-v8a arch", + "inherits": "android-arm64-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + }, + "environment": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "android-arm-debug", + "inherits": "base-android", + "displayName": "Android ARM eabi v7a Debug", + "description": "Sets android platform and debug build type for armeabi-v7a arch", + "architecture": { + "value": "armeabi-v7a", + "strategy": "external" + }, + "environment": { + "ANDROID_ABI": "armeabi-v7a", + "CPU": "arm", + "ARCH": "arm", + "CMAKE_ANDROID_ARCH_ABI": "armeabi-v7a", + "CMAKE_BUILD_TYPE": "Debug" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "ANDROID_ABI": "armeabi-v7a", + "CMAKE_ANDROID_ARCH_ABI": "armeabi-v7a", + "ARCH": "arm", + "CPU": "arm" + } + }, + { + "name": "android-arm-release", + "displayName": "Android ARM eabi v7a Release", + "description": "Sets android platform and release build type for armeabi-v7a arch", + "inherits": "android-arm-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + }, + "environment": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "linux-x64-debug", + "displayName": "GCC x64 linux gnu debug", + "description": "Using compilers: C = /usr/bin/gcc, CXX = /usr/bin/g++", + "binaryDir": "${sourceDir}/build/${presetName}", + "cacheVariables": { + "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install/${presetName}", + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "linux-x64-release", + "displayName": "GCC x64 linux gnu release", + "inherits": "linux-x64-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + } + ], + "buildPresets": [ + { + "name": "wasm32-unknown-emscripten-debug", + "displayName": "wasm32-unknown-emscripten-debug", + "description": "Build WebAssembly with Emscripten", + "configurePreset": "wasm32-unknown-emscripten-debug" + }, + { + "name": "wasm32-unknown-emscripten-release", + "displayName": "wasm32-unknown-emscripten-release", + "description": "Build WebAssembly with Emscripten", + "configurePreset": "wasm32-unknown-emscripten-release" + }, + { + "name": "win-x64-release", + "displayName": "Win x64 Release", + "description": "Sets windows platform and debug build type for x64 arch in release mode", + "configurePreset": "win-x64-release" + }, + { + "name": "win-x64-debug", + "displayName": "Win x64 Debug", + "description": "Sets windows platform and debug build type for x64 arch in debug mode", + "configurePreset": "win-x64-debug" + }, + { + "name": "android-arm64-debug", + "displayName": "Android ARM64 v8a Debug", + "description": "Build Android", + "configurePreset": "android-arm64-debug" + }, + { + "name": "android-arm64-release", + "displayName": "Android ARM64 v8a Release", + "description": "Build Android", + "configurePreset": "android-arm64-release" + }, + { + "name": "android-arm-debug", + "displayName": "Android ARM eabi v7a Debug", + "description": "Build Android", + "configurePreset": "android-arm-debug" + }, + { + "name": "android-arm-release", + "displayName": "Android ARM eabi v7a Release", + "description": "Build Android", + "configurePreset": "android-arm-release" + }, + { + "name": "linux-x64-debug", + "displayName": "Sets linux platform and debug build type for x64 arch in debug mode", + "description": "linux x64 Debug", + "configurePreset": "linux-x64-debug" + }, + { + "name": "linux-x64-release", + "displayName": "Sets linux platform and release build type for x64 arch in release mode", + "description": "linux x64 Release", + "configurePreset": "linux-x64-release" + }, + { + "name": "wasm", + "description": "", + "displayName": "", + "configurePreset": "wasm32-unknown-emscripten-debug" + } + ], + "testPresets": [ + { + "name": "base-tests", + "hidden": true, + "output": { + "outputOnFailure": true + } + }, + { + "name": "windows-tests", + "inherits": "base-tests", + "configurePreset": "win-x64-debug" + } + ] +} \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..7fbcdc34e --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +pooya{dot}eimandar{at}gmail{dot}com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/LICENSE b/LICENSE index 4d333c1c7..261eeb9e9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,22 +1,201 @@ -The MIT License (MIT) + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Copyright (c) 2014 Pooya Eimandar + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -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: + 1. Definitions. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -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. + "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/Logo.png b/Logo.png new file mode 100644 index 000000000..261a58993 Binary files /dev/null and b/Logo.png differ diff --git a/README.md b/README.md index 843fd1364..076dabe2b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,116 @@ -Wolf-Engine -=========== - -

-The Wolf Game Engine is a cross-platform game engine created by Pooya Eimandar for use in game titles. -

-This is the next generation of Persian Game Engine which is mainly focused on mobile platforms. The engine will be use on Microsoft WinRT platforms, Android and it will be adapted for a range of video game genres. You can use it under this License. -

-

-More information about product will be provided after first Milestone. -

+# Wolf Engine [![Apache licensed](https://img.shields.io/badge/license-Apache-blue)](https://github.com/WolfEngine/Wolf.Engine/blob/main/LICENSE.md) [![codecov](https://codecov.io/github/WolfEngine/WolfEngine/branch/main/graph/badge.svg?token=AhoU9QV7eS)](https://codecov.io/github/WolfEngine/WolfEngine) [![CodeQL](https://github.com/WolfEngine/WolfEngine/actions/workflows/codeql.yml/badge.svg?branch=main)](https://github.com/WolfEngine/WolfEngine/actions/workflows/codeql.yml) [![Microsoft C++ Code Analysis](https://github.com/WolfEngine/WolfEngine/actions/workflows/msvc.yml/badge.svg?branch=main)](https://github.com/WolfEngine/WolfEngine/actions/workflows/msvc.yml) + +WolfEngine +

Welcome to the Wolf Engine source code.

+

The Wolf Engine is the next +generation of Persian Game Engine which is a +cross-platform open source game engine created by Pooya Eimandar. +The Wolf is a comprehensive set of C++ open source libraries for realtime rendering, realtime streaming and game developing, which is support Lua and WASM as an embedded scripting languages.

+ +# Build +- Prerequisites + - For windows, make sure install the latest Windows 11/10 SDK + - [git](https://git-scm.com/downloads) + - [CMake](https://cmake.org/download/) + - [vcpkg](https://vcpkg.io/) + - [Meson](https://github.com/mesonbuild/meson/releases) + - [Ninja](https://ninja-build.org/). Alternatively, setup [Python3](https://www.python.org/downloads/) and use "pip3 install ninja" + - [QT6](https://www.qt.io/download) for demos and examples + - [NDK](https://developer.android.com/ndk/downloads) for android. + +then make sure get the main branch +`git clone https://github.com/WolfEngine/WolfEngine.git --branch main --depth 1` + +## CMakePresets + +To list configure presets: `cmake . --list-presets` +To list build presets: `cmake --build --list-presets` +To install wolf: `cmake --install --prefix ` + +For example for building wolf for android: +``` +cmake . --preset android-arm64-release +cmake --build --preset android-arm64-release +``` + +For example for building wolf for windows: +``` +cmake . --preset win-x64-release +cmake --build --preset win-x64-release +cmake --install C:/WolfEngine/build/win-x64-release --prefix C:/wolf +``` + +## Recent Sample +

Dynamic LOD Generation using Simplygon

+Dynamic LOD Generation gif + +## Supported platforms + +| Not Supported | Planned | In Progress | Done | +|:-----------:|:-----------:|:-----------:|:-----------:| +| :x: | :memo: | :construction: | :white_check_mark: | + +### Supported platforms and APIs for render module + +| API | Windows | Linux | macOS | iOS | Android | Wasm | +|:-----------:|:-----------:|:--------------------------:|:--------------:|:-------------:|:--------------:|:-------------:| +| GPU | Vulkan/OpenGL ES :construction: | Vulkan/OpenGL ES :memo: | MoltenVK :memo: | MoltenVK :memo: | Vulkan/OpenGL ES :memo: | WebGL/WebGPU :memo: | + +### Supported platforms and APIs for media module + +| API | Windows | Linux | macOS | iOS | Android | Wasm | +|:-----------:|:-----------:|:--------------------------:|:--------------:|:-------------:|:--------------:|:-------------:| +| [Bitmap](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/media/test/ffmpeg.hpp) | :white_check_mark: | :white_check_mark: | :memo: | :memo: | :memo: | :x: | +| [FFmpeg](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/stream/test/ffmpeg_stream.hpp) | :white_check_mark: | :white_check_mark: | :memo: | :memo: | :memo: | :x: | +| [JPEG](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/media/test/ffmpeg.hpp) | :white_check_mark: | :white_check_mark: | :memo: | :memo: | :memo: | :x: | +| [OpenAL](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/media/test/openal.hpp) | :white_check_mark: | :white_check_mark: | :memo: | :memo: | :memo: | :x: | +| [PNG](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/media/test/ffmpeg.hpp) | :white_check_mark: | :white_check_mark: | :memo: | :memo: | :memo: | :x: | +| WebP | :memo: | :memo: | :memo: | :memo: | :memo: | :x: | + +### Supported platforms and APIs for stream module + +| API | Windows | Linux | macOS | iOS | Android | Wasm | +|:-----------:|:-----------:|:--------------------------:|:--------------:|:-------------:|:--------------:|:-------------:| +| gRPC | :memo: | :x: | :x: | :x: | :x: | :x: | +| [Janus](https://github.com/WolfEngine/WolfEngine/tree/main/wolf_demo/wasm) | :construction: | :x: | :x: | :x: | :x: | :white_check_mark: | +| QUIC | :memo: | :memo: | :memo: | :memo: | :memo: | :x: | +| [RIST](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/stream/test/rist.hpp) | :white_check_mark: | :memo: | :memo: | :memo: | :white_check_mark: | :x: | +| RTMP | :memo: | :x: | :x: | :x: | :x: | :x: | +| [RTSP](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/stream/test/ffmpeg_stream.hpp) | :white_check_mark: | :memo: | :memo: | :memo: | :memo: | :x: | +| [SRT](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/stream/test/ffmpeg_stream.hpp) | :white_check_mark: | :memo: | :memo: | :memo: | :memo: | :x: | +| webRTC | :memo: | :memo: | :memo: | :memo: | :memo: | :memo: | +| [WebSocket](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/system/test/ws.hpp) | :white_check_mark: | :white_check_mark: | :memo: | :memo: | :memo: | :memo: | + +### Supported platforms and APIs for system module + +| API | Windows | Linux | macOS | iOS | Android | Wasm | +|:-----------:|:-----------:|:--------------------------:|:--------------:|:-------------:|:--------------:|:-------------:| +| [Coroutine](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/system/test/coroutine.hpp) | :white_check_mark: | :white_check_mark: | :memo: | :x: | :x: | :x: | +| [GameTime](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/system/test/gametime.hpp) | :white_check_mark: | :white_check_mark: | :memo: | :memo: | :memo: | :white_check_mark: | +| [Gamepad](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/system/test/gamepad.hpp) | :white_check_mark: | :white_check_mark: | :memo: | :memo: | :memo: | :white_check_mark: | +| [Virtual Gamepad](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/system/test/gamepad.hpp) | :white_check_mark: | :x: | :x: | :x: | :x: | :x: | +| [Log](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/system/test/log.hpp) | :white_check_mark: | :white_check_mark: | :construction: | :construction: | :construction: | :construction: | +| LuaJit | :memo: | :memo: | :memo: | :memo: | :memo: | :x: | +| [LZ4](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/system/test/compress.hpp) | :white_check_mark: | :white_check_mark: | :memo: | :memo: | :memo: | :x: | +| [LZMA](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/system/test/compress.hpp) | :white_check_mark: | :white_check_mark: | :memo: | :x: | :x: | :x: | +| OpenTelemetry | :memo: | :memo: | :memo: | :x: | :x: | :x: | +| RAFT | :memo: | :memo: | :memo: | :memo: | :memo: | :memo: | +| Screen Capture | :memo: | :construction: | :construction: | :x: | :x: | :x: | +| [Signal Slot](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/system/test/signal_slot.hpp) | :white_check_mark: | :white_check_mark: | :construction: | :x: | :x: | :x: | +| [Stacktrace](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/tests.cpp) | :white_check_mark: | :white_check_mark: | :construction: | :construction: | :construction: | :x: | +| Sycl | :memo: | :memo: | :memo: | :x: | :x: | :x: | +| [TCP](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/system/test/tcp.hpp) | :white_check_mark: | :white_check_mark: | :memo: | :memo: | :memo: | :x: | +| [Trace](https://github.com/WolfEngine/WolfEngine/blob/main/wolf/system/test/trace.hpp) | :white_check_mark: | :white_check_mark: | :memo: | :memo: | :memo: | :x: | +| UDP | :construction: | :memo: | :memo: | :memo: | :memo: | :x: | +| Wasm3 | :memo: | :memo: | :memo: | :memo: | :memo: | :memo: | + +## Projects using Wolf +* [Wolf.Playout](https://www.youtube.com/watch?v=EZSdEjBvuGY), a playout automation software +* [Falcon](https://youtu.be/ygpz35ddZ_4), a real time 3D monitoring system +* [PlayPod](https://playpod.ir), the first cloud gaming platform launched in Middle East +* [RivalArium](https://rivalarium.com), play and rival other users via our leagues and duels from any device, any location and let your skills generate income + +## [Youtube](https://www.youtube.com/c/WolfEngine) +## [Twitter](https://www.twitter.com/Wolf_Engine) + +Wolf Engine © 2014-2023 [Pooya Eimandar](https://www.linkedin.com/in/pooyaeimandar/) diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000..ab7368fbc --- /dev/null +++ b/TODO.md @@ -0,0 +1,7 @@ +- font rendering +- support android +- support windows +- support linux +- support osx +- support ios + diff --git a/content/audio/sine.wav b/content/audio/sine.wav new file mode 100644 index 000000000..fb3acf663 Binary files /dev/null and b/content/audio/sine.wav differ diff --git a/content/texture/rgb.png b/content/texture/rgb.png new file mode 100644 index 000000000..ea177c855 Binary files /dev/null and b/content/texture/rgb.png differ diff --git a/coverage.bat b/coverage.bat new file mode 100644 index 000000000..ffff76a84 --- /dev/null +++ b/coverage.bat @@ -0,0 +1 @@ +OpenCppCoverage.exe --continue_after_cpp_exception --export_type=html:%CD%\coverage\ --sources %CD% --excluded_sources %CD%\build\_deps -- %CD%\build\wolf\Debug\wolf_tests.exe \ No newline at end of file diff --git a/coverage/index.html b/coverage/index.html new file mode 100644 index 000000000..0a899a8da --- /dev/null +++ b/coverage/index.html @@ -0,0 +1,105 @@ + + + + + wolf_tests.exe + + + + + + + + + +

+ wolf_tests.exe +

+

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CoverageTotal linesItems
+ Cover 71% + 925 + + wolf_tests.exe + +
+ Cover 71% + 925 + G:\SourceCodes\WolfEngine\WolfEngine\build\wolf\Debug\wolf_tests.exe + + +
+
+ + + + + + +
+ Generated by + + OpenCppCoverage (Version: 0.9.9.0) + +
+ + \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 000000000..bf3b221ba --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,39 @@ +ARG UBUNTU_VERSION="22.04" +FROM ubuntu:${UBUNTU_VERSION} + +ENV VCPKG_ROOT=/opt/vcpkg +ARG VCPKG_GITHUB_REPOSITORY=https://github.com/microsoft/vcpkg/archive/master.tar.gz +ARG VCPKG_COMPRESSED_FILE_NAME=vcpkg.tar.gz + +RUN set -eux \ + && export DEBIAN_FRONTEND="noninteractive" \ + && apt-get update \ + && apt-get install --yes --no-install-recommends \ + build-essential \ + ca-certificates \ + cmake \ + cpp \ + curl \ + git \ + make \ls + pkg-config \ + tar \ + unzip \ + wget \ + zip + +WORKDIR ${VCPKG_ROOT} + +RUN wget -qO ${VCPKG_COMPRESSED_FILE_NAME} ${VCPKG_GITHUB_REPOSITORY} \ + && tar xf ${VCPKG_COMPRESSED_FILE_NAME} --strip-components=1 \ + && rm ${VCPKG_COMPRESSED_FILE_NAME} \ + && /opt/vcpkg/bootstrap-vcpkg.sh \ + && ln -s /opt/vcpkg/vcpkg /usr/local/bin/vcpkg + +WORKDIR /workspace + +COPY . . + +RUN cmake -B build --preset linux-x64-release + +RUN rm -rf ./* diff --git a/docs/GCC.txt b/docs/GCC.txt new file mode 100644 index 000000000..c297154cb --- /dev/null +++ b/docs/GCC.txt @@ -0,0 +1,15 @@ +cd /home/build +GCC_VERSION=10.2.0 +wget https://ftp.gnu.org/gnu/gcc/gcc-${GCC_VERSION}/gcc-${GCC_VERSION}.tar.gz +tar xzvf gcc-${GCC_VERSION}.tar.gz +mkdir obj.gcc-${GCC_VERSION} +cd gcc-${GCC_VERSION} +./contrib/download_prerequisites +cd ../obj.gcc-${GCC_VERSION} +#RedHat base +sudo yum groupinstall "Development Tools" +#Debian +sudo apt-get install build-essential +../gcc-${GCC_VERSION}/configure --disable-multilib --enable-languages=c,c++ +make -j $(nproc) +make install diff --git a/docs/ProblemSolutions.txt b/docs/ProblemSolutions.txt new file mode 100644 index 000000000..6935c45c9 --- /dev/null +++ b/docs/ProblemSolutions.txt @@ -0,0 +1,83 @@ +------------------------------------------------------- ------------------------------------------------------- +-------------------- Problem ----------------------- -------------------- Solution ----------------------- +------------------------------------------------------- ------------------------------------------------------- +initializePlugin function could not be found in Add following command to the Liker->command Line +Maya plug-in "/export:initializePlugin /export:uninitializePlugin" + + +------------------------------------------------------- ------------------------------------------------------- +No target architecture error at compile time use #include instead of #include + +------------------------------------------------------- ------------------------------------------------------- + +Copy relative content to the execution directory $(OutDir)/%(RelativeDir)/%(filename).cso for more info + see following link + http://msdn.microsoft.com/en-us/library/ms164313.aspx + +------------------------------------------------------- ------------------------------------------------------- + +Debug does not active, breakpoint disable Properties->Linker->debugging->GenerateDebugInfo->yes + Properties->Linker->debugging->DebuggingAssembly->yes + Properties->C/C++->Code Generation->Multi-threaded Debug DLL (/MDd) + +------------------------------------------------------- ------------------------------------------------------- + +Could not find entry point First right click on + Solution->Properties->Configuration Manager + set the x64 + Then check dllMain or main function + +------------------------------------------------------- ------------------------------------------------------- + +error MSB6006: "icl.exe" exited with code 4 switch from intel c++ to microsoft visual c++ to check other errors, + alsoCheck precompiled headers + +------------------------------------------------------- ------------------------------------------------------- +error LNK2019: unresolved external symbol __imp___CrtDbgReportW Remove _DEBUG from preprocessor or somwhere in your code +referenced in function, when you add std::map or std::vector + +------------------------------------------------------- ------------------------------------------------------- +Can link constructor or destructor ot method of a class make sure add API(see W_Object.h) before declaration of method in class +from DLL + + +------------------------------------------------------- ------------------------------------------------------- +eglGetPlatformDisplayEXT does not generate display device make sure copy libGLESv2.dll, libEGL.dll & d3dcompiler_47.dll in bin folder +in Angle project + +------------------------------------------------------- ------------------------------------------------------- +Error MSB6006 "fxc.exe" exited with code 1. check all *.hlsl codes and shader type in Properties->HLSL Compiler->General in your project + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/git-commands.txt b/docs/git-commands.txt new file mode 100644 index 000000000..ca3fdde51 --- /dev/null +++ b/docs/git-commands.txt @@ -0,0 +1,14 @@ +//subtrees + +git config commit.gpgsign false +git fetch git@github.com:g-truc/glm.git master +git subtree add --prefix=engine/dependencies/glm-master/ --squash git@github.com:g-truc/glm.git master +git subtree pull --prefix=engine/dependencies/glm-master/ --squash git@github.com:g-truc/glm.git master + +git fetch git@github.com:dwd/rapidxml.git master +git subtree add --prefix=engine/dependencies/rapidxml-master/ --squash git@github.com:dwd/rapidxml.git master +git subtree pull --prefix=engine/dependencies/rapidxml-master/ --squash git@github.com:dwd/rapidxml.git master + +git fetch git@github.com:miloyip/rapidjson.git master +git subtree add --prefix=engine/dependencies/rapidjson-master/ --squash git@github.com:miloyip/rapidjson.git master +git subtree pull --prefix=engine/dependencies/rapidjson-master/ --squash git@github.com:miloyip/rapidjson.git master diff --git a/docs/glslValidator.txt b/docs/glslValidator.txt new file mode 100644 index 000000000..8daeef456 --- /dev/null +++ b/docs/glslValidator.txt @@ -0,0 +1 @@ +./glslangValidator.exe -V -t -o ~path/to/out.spv ~/path/to/in.vert \ No newline at end of file diff --git a/docs/glslValidator_MoltenVK.txt b/docs/glslValidator_MoltenVK.txt new file mode 100644 index 000000000..696bbadf5 --- /dev/null +++ b/docs/glslValidator_MoltenVK.txt @@ -0,0 +1,6 @@ +cd /Users/pooyaeimandar/Documents/github/WolfSource/Wolf.Engine/engine/dependencies/vulkan/Mac/Molten/MoltenShaderConverter/Tools/ + +./MoltenShaderConverter -gi /Users/pooyaeimandar/Documents/github/WolfSource/Wolf.Engine/samples/02_basics/02_shader/src/content/shaders/shader.frag -t f -so /Users/pooyaeimandar/Documents/github/WolfSource/Wolf.Engine/samples/02_basics/02_shader/src/content/shaders/shader.frag.spv + + +./MoltenShaderConverter -gi /Users/pooyaeimandar/Documents/github/WolfSource/Wolf.Engine/samples/02_basics/02_shader/src/content/shaders/shader.vert -t v -so /Users/pooyaeimandar/Documents/github/WolfSource/Wolf.Engine/samples/02_basics/02_shader/src/content/shaders/shader.vert.spv \ No newline at end of file diff --git a/manifest.manifest b/manifest.manifest new file mode 100644 index 000000000..1c1323f75 --- /dev/null +++ b/manifest.manifest @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/wolf/.clang-format b/wolf/.clang-format new file mode 100644 index 000000000..c8838e98f --- /dev/null +++ b/wolf/.clang-format @@ -0,0 +1,33 @@ +# http://clang.llvm.org/docs/ClangFormatStyleOptions.html +BasedOnStyle: Google +# This defaults to 'Auto'. Explicitly set it for a while, so that +# 'vector >' in existing files gets formatted to +# 'vector>'. ('Auto' means that clang-format will only use +# 'int>>' if the file already contains at least one such instance.) +Standard: Cpp11 +SortIncludes: true +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +BreakStringLiterals: false +DerivePointerAlignment: true +PointerAlignment: Left +ColumnLimit: 100 +ForEachMacros: ['list_for_every_entry','list_for_every_entry_safe'] +IncludeBlocks: Regroup +IncludeCategories: + # This specific header must come last in kernel source files. Its + # matching rule must come first so the lower-priority rules don't match. + - Regex: '^' + Priority: 1000 + # C Header: , , etc + - Regex: '^(<((zircon/|lib/|fuchsia/|arpa/|net/|netinet/|sys/|fidl/)[a-zA-Z0-9_/\.-]+\.h|[a-zA-Z0-9_-]+\.h)>)' + Priority: 1 + # Cpp Header: and + - Regex: '^(<(experimental/)*[a-zA-Z0-9_-]+>)' + Priority: 2 + # Libraries: + - Regex: '^(<[a-zA-Z0-9_/-]+\.h>)' + Priority: 3 + # Local headers: "foo/bar.h" + - Regex: '^("[.a-zA-Z0-9_/-]+\.h")' + Priority: 4 \ No newline at end of file diff --git a/wolf/.clang-tidy b/wolf/.clang-tidy new file mode 100644 index 000000000..2b2f09529 --- /dev/null +++ b/wolf/.clang-tidy @@ -0,0 +1,68 @@ +--- +Checks: > + -*, + bugprone-*, + clang-diagnostic-*, + -clang-diagnostic-unused-command-line-argument, + google-*, + -google-runtime-references, + misc-*, + -misc-noexcept*, + -misc-unused-parameters, + -misc-const-correctness, + modernize-*, + -modernize-avoid-c-arrays, + -modernize-deprecated-headers, + -modernize-raw-string-literal, + -modernize-return-braced-init-list, + -modernize-use-auto, + -modernize-use-equals-delete, + -modernize-use-nodiscard, + -modernize-use-trailing-return-type, + performance-*, + readability-*, + -readability-function-cognitive-complexity, + -readability-identifier-length, + -readability-implicit-bool-conversion, + -readability-isolate-declaration, + -readability-magic-numbers, + -readability-qualified-auto, + -readability-uppercase-literal-suffix, + -readability-identifier-length, + -readability-named-parameter, +WarningsAsErrors: false +AnalyzeTemporaryDtors: false +FormatStyle: file +CheckOptions: + - key: bugprone-signed-char-misuse.CharTypdefsToIgnore + value: 'int8_t' + - key: bugprone-assert-side-effect.AssertMacros + value: 'FXL_DCHECK' + - key: google-readability-braces-around-statements.ShortStatementLines + value: '2' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: '1' + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-loop-convert.NamingStyle + value: CamelCase + - key: modernize-pass-by-value.IncludeStyle + value: llvm + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + - key: modernize-use-default-member-init.UseAssignment + value: '1' + - key: modernize-use-emplace.IgnoreImplicitConstructors + value: '1' + - key: modernize-use-nullptr.NullMacros + value: 'NULL' + - key: readability-braces-around-statements.ShortStatementLines + value: '2' \ No newline at end of file diff --git a/wolf/.gitignore b/wolf/.gitignore new file mode 100644 index 000000000..08b0e5705 --- /dev/null +++ b/wolf/.gitignore @@ -0,0 +1,5 @@ +*.DS_Store +*.suo +/build/* +/shells/ffmpeg/build/* +/rust/target/* \ No newline at end of file diff --git a/wolf/CMakeLists.txt b/wolf/CMakeLists.txt new file mode 100644 index 000000000..9532b814a --- /dev/null +++ b/wolf/CMakeLists.txt @@ -0,0 +1,386 @@ +#cmake . -B build -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake -DANDROID_ABI=armeabi-v7a -DANDROID_NDK=$ANDROID_NDK_HOME -DANDROID_PLATFORM=android-21 -DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a -DCMAKE_ANDROID_NDK=$ANDROID_NDK_HOME -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=21 -DCMAKE_BUILD_TYPE=Debug -GNinja +#cd build +#ninja + +cmake_minimum_required(VERSION 3.22...3.23) + +# set the name of the projects +project(wolf + DESCRIPTION "Wolf Engine" + LANGUAGES CXX +) + +set(TEST_PROJECT_NAME "${PROJECT_NAME}_tests") +message("CXX Compiler ID is ${CMAKE_CXX_COMPILER_ID}") + +# set the options and enviroment variables +#set(WEBRTC_SRC $ENV{WEBRTC_ROOT} CACHE STRING "path to the root folder of webrtc folder") + +#define includes, libs and srcs +set(INCLUDES) +set(LIBS PARENT_SCOPE) +set(SRCS) +set(TESTS_SRCS) + +# check the OS +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + if (WIN32) + set(WIN64 TRUE) + endif() +endif() + +if(UNIX AND (NOT APPLE) AND (NOT EMSCRIPTEN)) + set(LINUX TRUE) +endif() + +if (MSVC AND NOT WIN64) + message( FATAL_ERROR "Only Window 64 bit is supported" ) +endif() + +# set target os +if (WIN64) + set(TARGET_OS "win") + set(LIB_EXT "lib") + set(SHARED_EXT "dll") +elseif(APPLE) + set(TARGET_OS "mac") + set(LIB_EXT "a") + set(SHARED_EXT "dylib") +elseif(LINUX) + set(TARGET_OS "linux") + set(LIB_EXT "a") + set(SHARED_EXT "so") +elseif (NOT EMSCRIPTEN) + message( FATAL_ERROR "Unsuported OS, please open an issue at https://github.com/WolfEngine/WolfEngine" ) +endif() + +# required packages +find_package(Git REQUIRED) +if (NOT EMSCRIPTEN) + find_package (Threads) +endif() + +# use folders +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +# FetchContent for cloning repositories, avaiable since CMAKE 3.11 +include(FetchContent) +set(FETCHCONTENT_QUIET OFF) + +# set type of library +if (LINUX OR ANDROID_ABI) + set(LIBRARY_TYPE "SHARED") +else() + set(LIBRARY_TYPE "STATIC") +endif() + +set(LIBRARY_TYPE "${LIBRARY_TYPE}" CACHE STRING "Library type") + +# Build options +set(LIB_INSTALL_DIR lib CACHE STRING "Install location of libraries") +set(BIN_INSTALL_DIR bin CACHE STRING "Install location of executables") +set(INCLUDE_INSTALL_DIR include CACHE STRING "Install location of executables") + +# CMAKE GUI Options +set(EMSDK "$ENV{EMSDK}" CACHE STRING "Emscripten SDK path") +set(BOOST_VERSION "1.82.0" CACHE STRING "Boost version tag") + +# render module +option(WOLF_RENDER "Enable cross platform render engine based on Vulkan / OpenGL ES" OFF) + +# media modules +option(WOLF_MEDIA_FFMPEG "Enable ffmpeg encoding and decoding" OFF) +option(WOLF_MEDIA_OPENAL "Enable OpenAL soft" OFF) +option(WOLF_MEDIA_SCREEN_CAPTURE "Enable screen capture" OFF) +option(WOLF_MEDIA_STB "Enable stb headers" OFF) +option(WOLF_MEDIA_GSTREAMER "Enable gstreamer wrapper" OFF) + +# stream modules +option(WOLF_STREAM_GRPC "Enable gRPC connection" ON) +option(WOLF_STREAM_QUIC "Enable QUIC" OFF) +option(WOLF_STREAM_RIST "Enable RIST streaming protocol" OFF) +option(WOLF_STREAM_WEBRTC "Enable webRTC" OFF) + +# system modules +option(WOLF_SYSTEM_GAMEPAD_CLIENT "Enable gamepad input handling" OFF) +option(WOLF_SYSTEM_GAMEPAD_VIRTUAL "Enable virtual gamepad simulator" OFF) +option(WOLF_SYSTEM_HTTP_WS "Enable http1.1 and websocket client/server based on boost beast or Emscripten" OFF) +option(WOLF_SYSTEM_LOG "Enable log" OFF) +option(WOLF_SYSTEM_LZ4 "Enable lz4 for compression" OFF) +option(WOLF_SYSTEM_LZMA "Enable lzma for compression" OFF) +option(WOLF_SYSTEM_LUA "Enable lua scripting language" OFF) +option(WOLF_SYSTEM_MIMALLOC "Enable Microsoft's mimalloc memory allocator" OFF) +option(WOLF_SYSTEM_POSTGRESQL "Enable postgresql database client" OFF) +option(WOLF_SYSTEM_PYTHON "Enable embedded Python3 scripting" OFF) +option(WOLF_SYSTEM_SIG_SLOT "Enable signal/slot based on boost signals2" OFF) +option(WOLF_SYSTEM_SOCKET "Enable TCP/UDP protocol over socket" OFF) +option(WOLF_SYSTEM_OPENSSL "Enable openSSL" OFF) +option(WOLF_SYSTEM_STACKTRACE "Enable boost stacktrace" OFF) +option(WOLF_SYSTEM_ZLIB "Enable Zlib compression library" OFF) + +# machine learing modules +option(WOLF_ML_NUDITY_DETECTION "Enable machine learning nudity detection" OFF) +option(WOLF_ML_OCR "Enable machine learning referee ocr" OFF) + +#option(WOLF_ENABLE_LTO "Enable cross language linking time optimization" OFF) +option(WOLF_TEST "Enable tests" ON) +if (NOT MSVC) + option(WOLF_ENABLE_ASAN "Enable ASAN" OFF) +endif() + +if(ENABLE_LTO) + include(CheckIPOSupported) + check_ipo_supported(RESULT supported OUTPUT error) + if(supported) + message(STATUS "IPO / LTO enabled") + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + add_link_options(-fuse-ld=lld) + else() + message(STATUS "IPO / LTO not supported: <${error}>") + endif() +endif() + +# set C/CXX standards +set(CMAKE_C_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) +set(THREADS_PREFER_PTHREAD_FLAG TRUE) + +#set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +if ("${CMAKE_BUILD_TYPE}" STREQUAL "") + set(CMAKE_BUILD_TYPE "Debug") +endif() + +# set C++ flags based on compiler +if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + # using Clang or AppleClang + set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++2b -fexceptions -fcoroutines-ts") + set(CMAKE_CXX_FLAGS_DEBUG "-g") + set(CMAKE_CXX_FLAGS_RELEASE "-O3") +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # using GCC + set(CMAKE_CXX_STANDARD 23) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++2b -fexceptions -fcoroutines") + set(CMAKE_CXX_FLAGS_DEBUG "-g") + set(CMAKE_CXX_FLAGS_RELEASE "-O3") +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + # using Microsoft Visual C++ + # set C++23 as the primary standard + set(CMAKE_CXX_STANDARD 23) + set(CMAKE_CXX_FLAGS "/EHsc /W3 /bigobj") +endif() + +set(GETOPT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/system) + +# include cmakes +include(cmake/vcpkg.cmake) +include(cmake/system.cmake) +include(cmake/stream.cmake) +include(cmake/media.cmake) +include(cmake/ml.cmake) + +if (EMSCRIPTEN) + message(WARNING "WOLF_TEST will be disabled for Emscripten") + set(WOLF_TEST OFF) +else() + # currently threads was not supported with WASM + list(APPEND LIBS Threads::Threads) +endif() + +# disable build testing +set(BUILD_TESTING OFF CACHE BOOL "BUILD_TESTING") + +if (WOLF_ENABLE_ASAN) + set(ENABLE_ASAN TRUE) +endif() + +#add_compile_options(-fsanitize=address) +#add_link_options(-fsanitize=address) + +# enabling clang-tidy +# can be enabled with .CLANG-TIDY from Visual Studio Code +# https://devblogs.microsoft.com/cppblog/visual-studio-code-c-december-2021-update-clang-tidy/ +# can be enabled with .CLANG-TIDY from Visual Studio +# https://devblogs.microsoft.com/cppblog/code-analysis-with-clang-tidy-in-visual-studio/ +#set(CMAKE_C_CLANG_TIDY +# clang-tidy; +# -format-style=file;) +#set(CMAKE_CXX_CLANG_TIDY +# clang-tidy; +# -format-style=file;) + +# add definitions + +add_definitions( + -DBOOST_ASIO_NO_DEPRECATED + -DBOOST_ASIO_HAS_CO_AWAIT + -DBOOST_ASIO_HAS_STD_COROUTINE +) +if (MSVC) + add_definitions( + -EHsc + -DNOMINMAX + -DWIN32_LEAN_AND_MEAN + -DWIN64 + -DWIN32 + ) +elseif (EMSCRIPTEN) + add_definitions( + -DEMSCRIPTEN + ) +elseif(APPLE) + add_definitions(-DNEED_XLOCALE_H=1) +endif() + +if(CMAKE_BUILD_TYPE MATCHES Debug) + add_definitions(-DDEBUG -D_DEBUG) +else() + add_definitions(-DNDEBUG) +endif() + +# setup Wolf definitions +get_cmake_property(_vars VARIABLES) +foreach (_var ${_vars}) + string(FIND ${_var} "WOLF_" out) + if(("${out}" EQUAL 0) AND ("(${${_var}}" MATCHES ON)) + add_definitions("-D${_var}") + endif() +endforeach() + +# include sources +file(GLOB_RECURSE WOLF_SRCS + "${CMAKE_CURRENT_SOURCE_DIR}/wolf.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/wolf.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/DISABLE_ANALYSIS_BEGIN" + "${CMAKE_CURRENT_SOURCE_DIR}/DISABLE_ANALYSIS_END" +) + +file(GLOB_RECURSE WOLF_PROTOS + "${CMAKE_CURRENT_SOURCE_DIR}/protos/*" +) + +file(GLOB_RECURSE WOLF_CMAKES + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/*" +) + +# includes +include_directories( + ${INCLUDES} + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +# add source codes +add_library(${PROJECT_NAME} ${LIBRARY_TYPE} + ${SRCS} + ${WOLF_SRCS} + ${WOLF_CMAKES} + ${WOLF_PROTOS} +) + +if (WOLF_STREAM_RIST) + add_dependencies(${PROJECT_NAME} ${RIST_TARGET}) +endif() + +if (MSVC OR WIN32) + if (LIBRARY_TYPE STREQUAL "STATIC") + set_property(TARGET ${PROJECT_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + else() + set_property(TARGET ${PROJECT_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") + endif() +endif() + +# link libraries +target_link_libraries(${PROJECT_NAME} PUBLIC ${LIBS}) + +# create source group +source_group("wolf" FILES ${WOLF_SRCS}) +source_group("cmake" FILES ${WOLF_CMAKES}) +source_group("protos" FILES ${WOLF_PROTOS}) +source_group("stream/grpc" FILES ${WOLF_STREAM_GRPC_SRC}) +source_group("stream/janus" FILES ${WOLF_STREAM_JANUS_SRC}) +source_group("stream/test" FILES ${WOLF_STREAM_TEST_SRC}) +source_group("stream/quic" FILES ${WOLF_STREAM_QUIC_SRC}) +source_group("stream/rist" FILES ${WOLF_STREAM_RIST_SRC}) +source_group("stream/webrtc/capturer" FILES ${WOLF_STREAM_WEBRTC_CAPTURER_SRC}) +source_group("stream/webrtc/data" FILES ${WOLF_STREAM_WEBRTC_DATA_SRC}) +source_group("stream/webrtc/interceptor" FILES ${WOLF_STREAM_WEBRTC_INTERCEPTOR_SRC}) +source_group("stream/webrtc/media" FILES ${WOLF_STREAM_WEBRTC_MEDIA_SRC}) +source_group("stream/webrtc/peer" FILES ${WOLF_STREAM_WEBRTC_PEER_SRC}) +source_group("stream/test" FILES ${WOLF_STREAM_QUIC_SRC}) +source_group("stream" FILES ${WOLF_STREAM_SRC}) +source_group("system/gamepad" FILES ${WOLF_SYSTEM_GAMEPAD_CLIENT_SRC} ${WOLF_SYSTEM_GAMEPAD_VIRTUAL_SRCS}) +source_group("system/log" FILES ${WOLF_SYSTEM_LOG_SRC}) +source_group("system/compression" FILES ${WOLF_SYSTEM_LZ4_SRCS} ${WOLF_SYSTEM_LZMA_SRCS}) +source_group("system/script" FILES ${WOLF_SYSTEM_LUA_SRC}) +source_group("system/script" FILES ${WOLF_SYSTEM_PYTHON_SRC}) +source_group("system/socket" FILES ${WOLF_SYSTEM_SOCKET_SRC} ${WOLF_SYSTEM_HTTP_WS_SRC}) +source_group("system/test" FILES ${WOLF_SYSTEM_TEST_SRC}) +source_group("system" FILES ${WOLF_SYSTEM_SRC}) +source_group("media/test" FILES ${WOLF_MEDIA_TEST_SRC}) +source_group("media/ffmpeg" FILES ${WOLF_MEDIA_FFMPEG_SRC}) +source_group("media" FILES ${WOLF_MEDIA_OPENAL_SRC} ${WOLF_MEDIA_STB_SRC}) +source_group("ml/referee_ocr" FILES ${WOLF_ML_OCR_SRC}) +source_group("ml/nudity_detection" FILES ${WOLF_ML_NUD_DET_SRC}) + +# add compile options +if (NOT WIN32) + target_compile_options(${PROJECT_NAME} PRIVATE -std=c++2b -fPIC) +endif() + +if (WOLF_ENABLE_ASAN) + target_compile_options(${PROJECT_NAME} PRIVATE -fsanitize=address) + target_link_options(${PROJECT_NAME} PRIVATE -fsanitize=address) +endif() + +# build tests +if (WOLF_TEST) + add_executable(${TEST_PROJECT_NAME} + tests.cpp + ${TESTS_SRCS} + ) + + if (MSVC OR WIN32) + if (LIBRARY_TYPE STREQUAL "STATIC") + set_property(TARGET ${TEST_PROJECT_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + else() + set_property(TARGET ${TEST_PROJECT_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") + endif() + endif() + + if(WOLF_ML_OCR AND LINUX) + target_link_libraries(${TEST_PROJECT_NAME} PRIVATE ${PROJECT_NAME} ${leptonica_BINARY_DIR}/install/lib/libleptonica.so) + else() + target_link_libraries(${TEST_PROJECT_NAME} PRIVATE ${PROJECT_NAME}) + endif() + + if (NOT WIN32) + target_compile_options(${TEST_PROJECT_NAME} PRIVATE -std=c++2b) + endif() + + include(CTest) + add_test(NAME ${TEST_PROJECT_NAME} COMMAND ${TEST_PROJECT_NAME}) +endif() + +if(WOLF_ML_OCR AND WIN64) + add_custom_command(TARGET ${TEST_PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE} ${CMAKE_BINARY_DIR}/${PROJECT_NAME}/${CMAKE_BUILD_TYPE} + ) +endif() + +# install target +install(TARGETS ${PROJECT_NAME} + LIBRARY DESTINATION ${LIB_INSTALL_DIR} + ARCHIVE DESTINATION ${LIB_INSTALL_DIR}) + +foreach(ITEM ${INCLUDES}) + install(DIRECTORY ${ITEM}/ DESTINATION ${INCLUDE_INSTALL_DIR}) +endforeach() + +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/wolf.hpp DESTINATION ${INCLUDE_INSTALL_DIR}) + +if (WOLF_TEST) + install(TARGETS ${TEST_PROJECT_NAME} DESTINATION ${BIN_INSTALL_DIR}) +endif() diff --git a/wolf/DISABLE_ANALYSIS_BEGIN b/wolf/DISABLE_ANALYSIS_BEGIN new file mode 100644 index 000000000..a17af7e5d --- /dev/null +++ b/wolf/DISABLE_ANALYSIS_BEGIN @@ -0,0 +1,5 @@ +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning (disable:ALL_CODE_ANALYSIS_WARNINGS) +#endif +// NOLINTBEGIN diff --git a/wolf/DISABLE_ANALYSIS_END b/wolf/DISABLE_ANALYSIS_END new file mode 100644 index 000000000..c528772b3 --- /dev/null +++ b/wolf/DISABLE_ANALYSIS_END @@ -0,0 +1,4 @@ +#ifdef _MSC_VER +#pragma warning(pop) +#endif +// NOLINTEND \ No newline at end of file diff --git a/wolf/cmake/media.cmake b/wolf/cmake/media.cmake new file mode 100644 index 000000000..ce5dd0c1d --- /dev/null +++ b/wolf/cmake/media.cmake @@ -0,0 +1,111 @@ + # link to ffmpeg +if (WOLF_MEDIA_FFMPEG) + file(GLOB_RECURSE WOLF_MEDIA_FFMPEG_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/media/ffmpeg/*" + ) + list(APPEND SRCS ${WOLF_MEDIA_FFMPEG_SRC}) + list(APPEND INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/third_party/ffmpeg/include) + + list(APPEND FFMPEG_LIBS + avcodec + avdevice + avfilter + avformat + avutil + swresample + swscale + ) + + foreach (lib_name ${FFMPEG_LIBS}) + list(APPEND LIBS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/ffmpeg/lib/${TARGET_OS}/${lib_name}.${LIB_EXT}) + endforeach() +endif() + +# link openAL +if (WOLF_MEDIA_OPENAL) + message("fetching https://github.com/kcat/openal-soft.git") + + FetchContent_Declare( + openal + GIT_REPOSITORY https://github.com/kcat/openal-soft.git + GIT_TAG master + ) + + set(ALSOFT_EXAMPLES OFF CACHE BOOL "ALSOFT_EXAMPLES") + set(ALSOFT_INSTALL_EXAMPLES OFF CACHE BOOL "ALSOFT_INSTALL_EXAMPLES") + set(LIBTYPE "STATIC" CACHE STRING "STATIC") + + set(FETCHCONTENT_QUIET OFF) + FetchContent_MakeAvailable(openal) + + file(GLOB_RECURSE WOLF_MEDIA_OPENAL_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/media/w_openal.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/media/w_openal.cpp" + ) + + list(APPEND SRCS ${WOLF_MEDIA_OPENAL_SRC}) + list(APPEND INCLUDES ${openal_SOURCE_DIR}/include) + list(APPEND LIBS OpenAL::OpenAL) + + set_target_properties( + build_version + common + ex-common + OpenAL + openal-info + PROPERTIES FOLDER "openAL") + +endif() + +if (WOLF_MEDIA_STB) + message("fetching https://github.com/nothings/stb.git") + + FetchContent_Declare( + stb + GIT_REPOSITORY https://github.com/nothings/stb.git + GIT_TAG master + ) + + FetchContent_Populate(stb) + + file(GLOB_RECURSE WOLF_MEDIA_STB_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/media/w_image.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/media/w_image.hpp" + ) + + list(APPEND SRCS ${WOLF_MEDIA_STB_SRC}) + list(APPEND INCLUDES ${stb_SOURCE_DIR}) + +endif() + +if (WOLF_MEDIA_GSTREAMER) + file(GLOB_RECURSE WOLF_MEDIA_GSTREAMER_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/media/gst/*" + "${CMAKE_CURRENT_SOURCE_DIR}/media/gst/audio/*" + "${CMAKE_CURRENT_SOURCE_DIR}/media/gst/core/*" + "${CMAKE_CURRENT_SOURCE_DIR}/media/gst/elements/*" + "${CMAKE_CURRENT_SOURCE_DIR}/media/gst/video/*" + ) + + find_package(PkgConfig REQUIRED) + + pkg_check_modules(gstreamer REQUIRED IMPORTED_TARGET + gstreamer-1.0 + gstreamer-video-1.0 + gstreamer-audio-1.0) + + add_library(gstreamer-lib INTERFACE) + + target_compile_options(gstreamer-lib INTERFACE ${gstreamer_CFLAGS}) + target_include_directories(gstreamer-lib INTERFACE ${gstreamer_INCLUDE_DIRS}) + target_link_directories(gstreamer-lib BEFORE INTERFACE ${gstreamer_LIBRARY_DIRS}) + target_link_libraries(gstreamer-lib INTERFACE ${gstreamer_LIBRARIES}) + + list(APPEND SRCS ${WOLF_MEDIA_GSTREAMER_SRC}) + list(APPEND LIBS gstreamer-lib) +endif() + +file(GLOB_RECURSE WOLF_MEDIA_TEST_SRC +"${CMAKE_CURRENT_SOURCE_DIR}/media/test/*" +) +list(APPEND SRCS ${WOLF_MEDIA_TEST_SRC}) diff --git a/wolf/cmake/ml.cmake b/wolf/cmake/ml.cmake new file mode 100644 index 000000000..8ef6d3e97 --- /dev/null +++ b/wolf/cmake/ml.cmake @@ -0,0 +1,196 @@ +if(WOLF_ML_OCR) + if(LINUX) + # fetch leptonica + message("fetching https://github.com/DanBloomberg/leptonica.git") + FetchContent_Declare( + leptonica + GIT_REPOSITORY https://github.com/DanBloomberg/leptonica.git + GIT_TAG 1.80.0 + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + ) + FetchContent_Populate(leptonica) + + add_custom_command(OUTPUT lept_config.out COMMAND cmake -B ${leptonica_BINARY_DIR} -DBUILD_SHARED_LIBS=1 -DCMAKE_INSTALL_PREFIX:PATH=${leptonica_BINARY_DIR}/install ${leptonica_SOURCE_DIR}) + add_custom_target(lept_config ALL DEPENDS lept_config.out) + add_custom_command(OUTPUT lept_build.out COMMAND cmake --build ${leptonica_BINARY_DIR} --target install) + add_custom_target(lept_build ALL DEPENDS lept_build.out) + endif() + + # fetch tesseract + message("fetching https://github.com/tesseract-ocr/tesseract.git") + FetchContent_Declare( + tesseract + GIT_REPOSITORY https://github.com/tesseract-ocr/tesseract.git + GIT_TAG main + + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + ) + + if(WIN64) + set(FETCHCONTENT_QUIET OFF) + + set(BUILD_TESTS OFF CACHE BOOL "BUILD_TESTS") + set(BUILD_TRAINING_TOOLS OFF CACHE BOOL "BUILD_TRAINING_TOOLS") + set(DISABLE_ARCHIVE ON CACHE BOOL "DISABLE_ARCHIVE") + set(DISABLE_CURL ON CACHE BOOL "DISABLE_CURL") + set(FAST_FLOAT ON CACHE BOOL "FAST_FLOAT") + set(GRAPHICS_DISABLED ON CACHE BOOL "GRAPHICS_DISABLED") + set(INSTALL_CONFIGS OFF CACHE BOOL "INSTALL_CONFIGS") + set(SW_BUILD ON CACHE BOOL "SW_BUILD") + + FetchContent_MakeAvailable(tesseract) + list(APPEND INCLUDES + ${tesseract_SOURCE_DIR}/include + ${tesseract_BINARY_DIR}/include + ) + + if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + set(DEBUG_LIB_EXTENTION "d") + else() + set(DEBUG_LIB_EXTENTION "") + endif() + + list(APPEND LIBS + ${tesseract_BINARY_DIR}/${CMAKE_BUILD_TYPE}/tesseract53${DEBUG_LIB_EXTENTION}.lib + ) + elseif(LINUX) + FetchContent_Populate(tesseract) + + list(APPEND INCLUDES + ${tesseract_SOURCE_DIR}/include + ${tesseract_BINARY_DIR}/install/include + ) + + link_directories(${tesseract_BINARY_DIR}/install/lib) + list(APPEND LIBS + tesseract + ) + + add_custom_command(OUTPUT tess_config.out COMMAND cmake -B ${tesseract_BINARY_DIR} -DBUILD_SHARED_LIBS=1 -DBUILD_TESTS=OFF -DBUILD_TRAINING_TOOLS=OFF -DDISABLE_ARCHIVE=ON -DDISABLE_CURL=ON -DFAST_FLOAT=ON -DGRAPHICS_DISABLED=ON -DINSTALL_CONFIGS=OFF -DLeptonica_DIR=${leptonica_BINARY_DIR} -DCMAKE_INSTALL_PREFIX:PATH=${tesseract_BINARY_DIR}/install ${tesseract_SOURCE_DIR}) + add_custom_target(tess_config ALL DEPENDS tess_config.out) + add_custom_command(OUTPUT tess_build.out COMMAND cmake --build ${tesseract_BINARY_DIR} --target install) + add_custom_target(tess_build ALL DEPENDS tess_build.out) + endif() + + # fetch opencv + message("fetching https://github.com/opencv/opencv.git") + FetchContent_Declare( + opencv + GIT_REPOSITORY https://github.com/opencv/opencv.git + GIT_TAG 4.5.4 + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + ) + + if(WIN64) + FetchContent_GetProperties(opencv) + + set(BUILD_LIST core,highgui,videoio CACHE STRING "BUILD_LIST") + set(WITH_IPP OFF CACHE BOOL "WITH_IPP") + set(BUILD_EXAMPLES OFF CACHE BOOL "BUILD_EXAMPLES") + set(OPENCV_GENERATE_PKGCONFIG ON CACHE BOOL "OPENCV_GENERATE_PKGCONFIG") + + FetchContent_MakeAvailable(opencv) + + list(APPEND INCLUDES + ${CMAKE_BINARY_DIR} + ${opencv_SOURCE_DIR}/include + ${opencv_SOURCE_DIR}/modules/core/include + ${opencv_SOURCE_DIR}/modules/highgui/include + ${opencv_SOURCE_DIR}/modules/imgcodecs/include + ${opencv_SOURCE_DIR}/modules/imgproc/include + ${opencv_SOURCE_DIR}/modules/videoio/include + ) + list(APPEND LIBS + ${opencv_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}/opencv_core454${DEBUG_LIB_EXTENTION}.lib + ${opencv_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}/opencv_highgui454${DEBUG_LIB_EXTENTION}.lib + ${opencv_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}/opencv_imgcodecs454${DEBUG_LIB_EXTENTION}.lib + ${opencv_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}/opencv_imgproc454${DEBUG_LIB_EXTENTION}.lib + ${opencv_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}/opencv_videoio454${DEBUG_LIB_EXTENTION}.lib + ) + elseif(LINUX) + FetchContent_Populate(opencv) + + list(APPEND INCLUDES + ${opencv_BINARY_DIR}/install/include/opencv4 + ) + list(APPEND LIBS + ${opencv_BINARY_DIR}/install/lib/libopencv_core.so + ${opencv_BINARY_DIR}/install/lib/libopencv_highgui.so + ${opencv_BINARY_DIR}/install/lib/libopencv_imgcodecs.so + ${opencv_BINARY_DIR}/install/lib/libopencv_imgproc.so + ${opencv_BINARY_DIR}/install/lib/libopencv_videoio.so + ) + + add_custom_command(OUTPUT opencv_config.out COMMAND cmake -B ${opencv_BINARY_DIR} -DBUILD_LIST=core,highgui,videoio -DBUILD_opencv_python3=OFF -DWITH_IPP=OFF -DBUILD_EXAMPLES=OFF -DOPENCV_GENERATE_PKGCONFIG=ON -DCMAKE_INSTALL_PREFIX:PATH=${opencv_BINARY_DIR}/install ${opencv_SOURCE_DIR}) + add_custom_target(opencv_config ALL DEPENDS opencv_config.out) + add_custom_command(OUTPUT opencv_build.out COMMAND cmake --build ${opencv_BINARY_DIR} --target install) + add_custom_target(opencv_build ALL DEPENDS opencv_build.out) + endif() + + # fetch rapidjson + message("fetching https://github.com/Tencent/rapidjson.git") + FetchContent_Declare( + rapidjson + GIT_REPOSITORY https://github.com/Tencent/rapidjson.git + + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + ) + + FetchContent_Populate(rapidjson) + + list(APPEND INCLUDES + ${rapidjson_SOURCE_DIR}/include + ) + + file(GLOB_RECURSE WOLF_ML_OCR_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/ml/referee_ocr/*" + ) + + list(APPEND SRCS + ${WOLF_ML_OCR_SRC} + ) +endif() + +if(WOLF_ML_NUDITY_DETECTION) + # Set the C++ standard for the rest of the project + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + + # Find Torch package + find_package(Torch REQUIRED) + + # Set the C++ standard for the rest of the project + set(CMAKE_CXX_STANDARD 23) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + + find_package(OpenCV REQUIRED) + + list(APPEND INCLUDES + ${OpenCV_INCLUDE_DIRS} + ) + + list(APPEND LIBS + ${TORCH_LIBRARIES} + ${OpenCV_LIBS} + ) + + file(GLOB_RECURSE WOLF_ML_SHARED_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/ml/w_common.*" + ) + + list(APPEND SRCS + ${WOLF_ML_SHARED_SRC} + ) + + file(GLOB_RECURSE WOLF_ML_NUD_DET_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/ml/nudity_detection/*" + ) + + list(APPEND SRCS + ${WOLF_ML_NUD_DET_SRC} + ) +endif() \ No newline at end of file diff --git a/wolf/cmake/stream.cmake b/wolf/cmake/stream.cmake new file mode 100644 index 000000000..894abce79 --- /dev/null +++ b/wolf/cmake/stream.cmake @@ -0,0 +1,254 @@ +# fetch gRPC +if (WOLF_STREAM_GRPC) + if (EMSCRIPTEN) + message(FATAL_ERROR "the wasm32 target is not supported for WOLF_STREAM_GRPC") + endif() + + vcpkg_install(asio-grpc asio-grpc TRUE) + list(APPEND LIBS asio-grpc::asio-grpc) + + file(GLOB_RECURSE WOLF_STREAM_GRPC_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/stream/grpc/*" + ) + + list(APPEND SRCS + ${WOLF_STREAM_GRPC_SRC} + ) + + if(WOLF_TEST) + add_library(generate-protos OBJECT) + + target_link_libraries(generate-protos PUBLIC protobuf::libprotobuf gRPC::grpc++_unsecure) + + set(PROTO_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/protos") + set(PROTO_IMPORT_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/protos") + + asio_grpc_protobuf_generate( + GENERATE_GRPC GENERATE_MOCK_CODE + TARGET generate-protos + USAGE_REQUIREMENT PUBLIC + IMPORT_DIRS ${PROTO_IMPORT_DIRS} + OUT_DIR "${PROTO_BINARY_DIR}" + PROTOS "${CMAKE_CURRENT_SOURCE_DIR}/protos/raft.proto") + + list(APPEND INCLUDES "${PROTO_BINARY_DIR}") + list(APPEND TESTS_SRCS + "${PROTO_BINARY_DIR}/raft.grpc.pb.cc" + "${PROTO_BINARY_DIR}/raft.pb.cc" + ) + endif() + +endif() + +if (WOLF_STREAM_JANUS) + + file(GLOB_RECURSE WOLF_STREAM_JANUS_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/stream/janus/*" + ) + + list(APPEND SRCS + ${WOLF_STREAM_JANUS_SRC} + ) + +endif() + +# fetch msquic +if (WOLF_STREAM_QUIC) + if (EMSCRIPTEN) + message(FATAL_ERROR "WOLF_STREAM_QUIC is not supported for wasm32 target") + endif() + + if (NOT WIN32) + message(FATAL_ERROR "WOLF_STREAM_QUIC feature is not avilable on non-windows yet.") + endif() + + file(GLOB_RECURSE WOLF_STREAM_QUIC_SRCS + "${CMAKE_CURRENT_SOURCE_DIR}/stream/quic/*" + ) + + if (WIN32 OR WIN64) + FetchContent_Declare( + msquic + URL https://github.com/microsoft/msquic/releases/download/v2.2.0/msquic_windows_x64_Release_schannel.zip + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + ) + FetchContent_Populate(msquic) + else() + message(FATAL_ERROR "WOLF_STREAM_QUIC feature is not supported on target platform.") + endif() + + add_library(msquic-lib INTERFACE) + add_library(msquic::msquic ALIAS msquic-lib) + target_include_directories(msquic-lib INTERFACE ${msquic_SOURCE_DIR}/include) + target_link_directories(msquic-lib INTERFACE BEFORE ${msquic_SOURCE_DIR}/bin) + target_link_directories(msquic-lib INTERFACE BEFORE ${msquic_SOURCE_DIR}/lib) + target_link_libraries(msquic-lib INTERFACE msquic) + + list(APPEND SRCS ${WOLF_STREAM_QUIC_SRCS}) + list(APPEND LIBS msquic::msquic) +endif() + +if (WOLF_STREAM_RIST) + if (EMSCRIPTEN) + message(FATAL_ERROR "the wasm32 target is not supported for WOLF_STREAM_RIST") + endif() + + set(RIST_TARGET "rist") + message("fetching https://code.videolan.org/rist/librist.git") + FetchContent_Declare( + ${RIST_TARGET} + GIT_REPOSITORY https://code.videolan.org/rist/librist.git + GIT_TAG master + ) + + set(FETCHCONTENT_QUIET OFF) + FetchContent_MakeAvailable(${RIST_TARGET}) + + if (ANDROID) + add_custom_command(OUTPUT rist_command.out COMMAND + /bin/bash "${CMAKE_CURRENT_SOURCE_DIR}/third_party/shells/librist/librist-android.sh" --build_dir=${rist_BINARY_DIR} + WORKING_DIRECTORY ${rist_SOURCE_DIR}) + add_custom_target(rist ALL DEPENDS rist_command.out) + + list(APPEND LIBS + ${rist_BINARY_DIR}/librist.a) + else () + STRING(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) + + add_custom_command(OUTPUT rist_command.out COMMAND cmd /c "meson setup ${rist_BINARY_DIR} --backend vs2022 --default-library static --buildtype ${CMAKE_BUILD_TYPE_LOWER} & meson compile -C ${rist_BINARY_DIR}" WORKING_DIRECTORY ${rist_SOURCE_DIR}) + add_custom_target(rist ALL DEPENDS rist_command.out) + + list(APPEND LIBS + ws2_32 + ${rist_BINARY_DIR}/librist.a) + + endif() + + list(APPEND INCLUDES + ${rist_BINARY_DIR} + ${rist_BINARY_DIR}/include + ${rist_BINARY_DIR}/include/librist + ${rist_SOURCE_DIR}/contrib + ${rist_SOURCE_DIR}/contrib/mbedtls/include + ${rist_SOURCE_DIR}/include + ${rist_SOURCE_DIR}/include/librist + ${rist_SOURCE_DIR}/src + ) + + file(GLOB_RECURSE WOLF_STREAM_RIST_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/stream/rist/*" + ) + list(APPEND SRCS ${WOLF_STREAM_RIST_SRC}) +endif() + +if (WOLF_STREAM_WEBRTC) + if (EMSCRIPTEN) + message(FATAL_ERROR "the wasm32 target is not supported for WOLF_STREAM_WEBRTC") + endif() + + # we need http & json for webrtc + + if (NOT WOLF_SYSTEM_JSON) + message( FATAL_ERROR "'WOLF_STREAM_WEBRTC' needs 'WOLF_SYSTEM_JSON' = ON" ) + endif() + + if (NOT WOLF_STREAM_HTTP) + message( FATAL_ERROR "'WOLF_STREAM_WEBRTC' needs 'WOLF_STREAM_HTTP' = ON" ) + endif() + + list(APPEND INCLUDES + ${WEBRTC_SRC} + ${WEBRTC_SRC}/third_party/abseil-cpp + ${WEBRTC_SRC}/third_party/libyuv/include + ) + if (WIN32) + # enable/disable debug option + if(CMAKE_BUILD_TYPE MATCHES Debug) + add_definitions( + -D_HAS_ITERATOR_DEBUGGING=1 + -D_ITERATOR_DEBUG_LEVEL=2 + ) + else() + add_definitions( + -D_HAS_ITERATOR_DEBUGGING=0 + -D_ITERATOR_DEBUG_LEVEL=0 + ) + endif() + + add_definitions( + -DWEBRTC_WIN + -D__PRETTY_FUNCTION__=__FUNCTION__ + #-DUSE_X11 + #-D_WINSOCKAPI_ + -DHAVE_SOUND) + + list(APPEND LIBS + d3d11 + dmoguids + dwmapi + dxgi + iphlpapi + msdmo + secur32 + strmiids + winmm + wmcodecdspuuid + ) + elseif (APPLE) + + add_definitions( + -DHAVE_SOUND + -DWEBRTC_MAC + -DWEBRTC_POSIX + -fno-rtti) + + find_library(APPLICATION_SERVICES ApplicationServices) + find_library(AUDIO_TOOLBOX AudioToolBox) + find_library(CORE_AUDIO CoreAudio) + find_library(CORE_FOUNDATION CoreFoundation) + find_library(CORE_SERVICES CoreServices) + find_library(FOUNDATION Foundation) + + list(APPEND LIBS + ${APPLICATION_SERVICES} + ${AUDIO_TOOLBOX} + ${CORE_AUDIO} + ${CORE_FOUNDATION} + ${CORE_SERVICES} + ${FOUNDATION} + ) + endif() + add_definitions(-DHAVE_JPEG) + link_directories(${WEBRTC_SRC}/out/${TARGET_OS}/${CMAKE_BUILD_TYPE}/obj/) + list(APPEND LIBS webrtc) + + file(GLOB_RECURSE WOLF_STREAM_WEBRTC_CAPTURER_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/stream/webrtc/capturer/*" + ) + file(GLOB_RECURSE WOLF_STREAM_WEBRTC_DATA_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/stream/webrtc/data/*" + ) + file(GLOB_RECURSE WOLF_STREAM_WEBRTC_INTERCEPTOR_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/stream/webrtc/interceptor/*" + ) + file(GLOB_RECURSE WOLF_STREAM_WEBRTC_MEDIA_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/stream/webrtc/media/*" + ) + file(GLOB_RECURSE WOLF_STREAM_WEBRTC_PEER_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/stream/webrtc/peer/*" + ) + + list(APPEND SRCS + ${WOLF_STREAM_WEBRTC_CAPTURER_SRC} + ${WOLF_STREAM_WEBRTC_DATA_SRC} + ${WOLF_STREAM_WEBRTC_INTERCEPTOR_SRC} + ${WOLF_STREAM_WEBRTC_MEDIA_SRC} + ${WOLF_STREAM_WEBRTC_PEER_SRC} + ) +endif() + +file(GLOB_RECURSE WOLF_STREAM_TEST_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/stream/test/*" +) +list(APPEND SRCS ${WOLF_STREAM_TEST_SRC}) + diff --git a/wolf/cmake/system.cmake b/wolf/cmake/system.cmake new file mode 100644 index 000000000..521525cbb --- /dev/null +++ b/wolf/cmake/system.cmake @@ -0,0 +1,306 @@ +if (WOLF_SYSTEM_STACKTRACE) + if (EMSCRIPTEN) + message(FATAL_ERROR "the wasm32 target is not supported for WOLF_SYSTEM_STACKTRACE") + elseif(NOT MSVC) + message(FATAL_ERROR "WOLF_SYSTEM_STACKTRACE is only supported on Visual C++") + endif() +endif() + + +# fetch mimalloc +if (WOLF_SYSTEM_MIMALLOC) + if (EMSCRIPTEN) + message(FATAL_ERROR "the wasm32 target is not supported for WOLF_SYSTEM_MIMALLOC") + endif() + vcpkg_install(mimalloc mimalloc TRUE) + list(APPEND LIBS mimalloc) +endif() + +# fetch boost components via vcpkg +if (EMSCRIPTEN) + execute_process(COMMAND vcpkg install + boost-leaf + boost-signals2 --triplet=${VCPKG_TARGET_TRIPLET}) +elseif(WOLF_SYSTEM_PYTHON) + execute_process(COMMAND vcpkg install + boost-asio + boost-beast + boost-leaf + boost-python + boost-signals2 + boost-test --triplet=${VCPKG_TARGET_TRIPLET}) +else() + execute_process(COMMAND vcpkg install + boost-asio + boost-beast + boost-leaf + boost-signals2 + boost-test --triplet=${VCPKG_TARGET_TRIPLET}) +endif() + +set(Boost_INCLUDE_DIR $ENV{VCPKG_ROOT}/installed/${VCPKG_TARGET_TRIPLET}/include CACHE STRING "boost include directory" FORCE) +list(APPEND INCLUDES ${Boost_INCLUDE_DIR}) +find_package(Boost ${Boost_VERSION} REQUIRED) + +# install gsl +vcpkg_install(Microsoft.GSL ms-gsl TRUE) +list(APPEND LIBS Microsoft.GSL::GSL) + +if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT EMSCRIPTEN) + vcpkg_install(fmt fmt FALSE) + list(APPEND LIBS fmt::fmt-header-only) +endif() + +if (WOLF_SYSTEM_GAMEPAD_CLIENT) + file(GLOB_RECURSE WOLF_SYSTEM_GAMEPAD_CLIENT_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/system/gamepad/w_gamepad_client_emc.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/gamepad/w_gamepad_client_keymap.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/gamepad/w_gamepad_client_sdl.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/gamepad/w_gamepad_client_types.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/gamepad/w_gamepad_client.hpp" + ) + list(APPEND SRCS ${WOLF_SYSTEM_GAMEPAD_CLIENT_SRC}) + + if (NOT EMSCRIPTEN) + message("fetching https://github.com/libsdl-org/SDL") + FetchContent_Declare( + SDL3-static + GIT_REPOSITORY https://github.com/libsdl-org/SDL + GIT_TAG main + ) + + set(SDL_AUDIO OFF CACHE BOOL "SDL_AUDIO") + set(SDL_DIRECTX OFF CACHE BOOL "SDL_DIRECTX") + set(SDL_DISKAUDIO OFF CACHE BOOL "SDL_DISKAUDIO") + set(SDL_DUMMYAUDIO OFF CACHE BOOL "SDL_DUMMYAUDIO") + set(SDL_DUMMYVIDEO OFF CACHE BOOL "SDL_DUMMYVIDEO") + set(SDL_FILE OFF CACHE BOOL "SDL_FILE") + set(SDL_FILESYSTEM OFF CACHE BOOL "SDL_FILESYSTEM") + set(SDL_METAL OFF CACHE BOOL "SDL_METAL") + set(SDL_OFFSCREEN OFF CACHE BOOL "SDL_OFFSCREEN") + set(SDL_OPENGL OFF CACHE BOOL "SDL_OPENGL") + set(SDL_OPENGLES OFF CACHE BOOL "SDL_OPENGLES") + set(SDL_RENDER OFF CACHE BOOL "SDL_RENDER") + set(SDL_RENDER_D3D OFF CACHE BOOL "SDL_RENDER_D3D") + set(SDL_RENDER_METAL OFF CACHE BOOL "SDL_RENDER_METAL") + set(SDL_SHARED OFF CACHE BOOL "SDL_SHARED") + set(SDL_TEST OFF CACHE BOOL "SDL_TEST") + set(SDL_TESTS OFF CACHE BOOL "SDL_TESTS") + set(SDL_VIDEO OFF CACHE BOOL "SDL_VIDEO") + set(SDL_VULKAN OFF CACHE BOOL "SDL_VULKAN") + set(SDL_WASAPI OFF CACHE BOOL "SDL_WASAPI") + + set(SDL_HIGHDAPI_JOYSTICK ON CACHE BOOL "SDL_HIGHDAPI_JOYSTICK") + set(SDL_JOYSTICK ON CACHE BOOL "SDL_JOYSTICK") + set(SDL_STATIC ON CACHE BOOL "SDL_STATIC") + set(SDL_XINPUT ON CACHE BOOL "SDL_XINPUT") + + set(FETCHCONTENT_QUIET OFF) + FetchContent_MakeAvailable(SDL3-static) + + list(APPEND INCLUDES + ${SDL3-static_SOURCE_DIR}/include + ) + list(APPEND LIBS SDL3-static) + + set_target_properties( + SDL3-static + uninstall + PROPERTIES FOLDER "SDL") + endif() +endif() + +if (WOLF_SYSTEM_GAMEPAD_VIRTUAL) + if (NOT WIN32) + message(SEND_ERROR "WOLF_SYSTEM_GAMEPAD_VIRTUAL can only build for Windows") + else() + message("fetching https://github.com/ViGEm/ViGEmClient.git") + FetchContent_Declare( + ViGEmClient + GIT_REPOSITORY https://github.com/ViGEm/ViGEmClient.git + GIT_TAG master + ) + FetchContent_MakeAvailable(ViGEmClient) + + file(GLOB_RECURSE WOLF_SYSTEM_GAMEPAD_VIRTUAL_SRCS + "${CMAKE_CURRENT_SOURCE_DIR}/system/gamepad/w_gamepad_virtual_pool.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/gamepad/w_gamepad_virtual_pool.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/gamepad/w_gamepad_virtual.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/gamepad/w_gamepad_virtual.hpp" + ) + list(APPEND SRCS ${WOLF_SYSTEM_GAMEPAD_VIRTUAL_SRCS}) + list(APPEND INCLUDES ${ViGEmClient_SOURCE_DIR}/include) + list(APPEND LIBS + ViGEmClient + Xinput.lib + SetupAPI.lib) + endif() +endif() + +if (WOLF_SYSTEM_LOG) + if (EMSCRIPTEN) + message(FATAL_ERROR "the wasm32 target is not supported for WOLF_SYSTEM_LOG") + endif() + + vcpkg_install(spdlog spdlog TRUE) + list(APPEND LIBS spdlog::spdlog spdlog::spdlog_header_only) + + file(GLOB_RECURSE WOLF_SYSTEM_LOG_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/system/log/*" + ) + list(APPEND SRCS ${WOLF_SYSTEM_LOG_SRC}) +endif() + +if (WOLF_SYSTEM_LZ4) + if (EMSCRIPTEN) + message(FATAL_ERROR "the wasm32 target is not supported for WOLF_SYSTEM_LZ4") + endif() + vcpkg_install(lz4 lz4 TRUE) + list(APPEND LIBS lz4::lz4) + + file(GLOB_RECURSE WOLF_SYSTEM_LZ4_SRCS + "${CMAKE_CURRENT_SOURCE_DIR}/system/compression/w_lz4.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/compression/w_lz4.hpp" + ) + list(APPEND SRCS ${WOLF_SYSTEM_LZ4_SRCS}) +endif() + +if (WOLF_SYSTEM_LZMA) + if (EMSCRIPTEN) + message(FATAL_ERROR "the wasm32 target is not supported for WOLF_SYSTEM_LZMA") + endif() + message("fetching https://github.com/WolfEngine/lzma.git") + FetchContent_Declare( + lzma + GIT_REPOSITORY https://github.com/WolfEngine/lzma.git + GIT_TAG main + ) + set(FETCHCONTENT_QUIET OFF) + FetchContent_MakeAvailable(lzma) + + file(GLOB_RECURSE WOLF_SYSTEM_LZMA_SRCS + "${CMAKE_CURRENT_SOURCE_DIR}/system/compression/w_lzma.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/compression/w_lzma.hpp" + ) + list(APPEND SRCS ${WOLF_SYSTEM_LZMA_SRCS}) + list(APPEND INCLUDES ${lzma_SOURCE_DIR}/src) + list(APPEND LIBS lzma) +endif() + +# include openSSL +if (WOLF_SYSTEM_OPENSSL AND NOT EMSCRIPTEN) + vcpkg_install(OpenSSL openssl FALSE) + list(APPEND LIBS OpenSSL::SSL OpenSSL::Crypto) +endif() + +# include socket/websocket sources +if (WOLF_SYSTEM_SOCKET AND NOT EMSCRIPTEN) + file(GLOB_RECURSE WOLF_SYSTEM_SOCKET_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/system/socket/w_socket_options.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/socket/w_tcp_client.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/socket/w_tcp_client.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/socket/w_tcp_server.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/socket/w_tcp_server.hpp" + ) + list(APPEND SRCS ${WOLF_SYSTEM_SOCKET_SRC}) +endif() + +if (WOLF_SYSTEM_HTTP_WS) + if (EMSCRIPTEN) + file(GLOB_RECURSE WOLF_SYSTEM_HTTP_WS_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/system/socket/w_ws_client_emc.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/socket/w_ws_client_emc.hpp" + ) + else() + file(GLOB_RECURSE WOLF_SYSTEM_HTTP_WS_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/system/socket/w_ws_client.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/socket/w_ws_client.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/socket/w_ws_server.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/socket/w_ws_server.hpp" + ) + endif() + list(APPEND SRCS ${WOLF_SYSTEM_HTTP_WS_SRC}) +endif() + +if (WOLF_SYSTEM_ZLIB) + vcpkg_install(ZLIB zlib FALSE) + list(APPEND LIBS ZLIB::ZLIB) +endif() + +if (WOLF_SYSTEM_POSTGRESQL) + vcpkg_install(libpq libpq TRUE) + list(APPEND LIBS libpq::libpq) + + file(GLOB_RECURSE WOLF_SYSTEM_POSTGRESQL_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/system/db/w_postgresql.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/db/w_postgresql.hpp" + ) + list(APPEND LIBS PostgreSQL::PostgreSQL) +endif() + +if (WOLF_SYSTEM_LUA) + vcpkg_install(Lua lua FALSE) + vcpkg_install(sol2 sol2 TRUE) + + list(APPEND LIBS lua sol2) + + file(GLOB_RECURSE WOLF_SYSTEM_LUA_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/system/script/w_lua.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/script/w_lua.hpp" + ) + + list(APPEND SRCS + ${WOLF_SYSTEM_LUA_SRC} + ) +endif() + +if (WOLF_SYSTEM_PYTHON) + find_package(Python3 REQUIRED COMPONENTS Development) + find_package(Boost REQUIRED COMPONENTS python${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}) + + file(GLOB_RECURSE WOLF_SYSTEM_PYTHON_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/system/script/w_python.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/script/w_python.hpp" + ) + list(APPEND SRCS + ${WOLF_SYSTEM_PYTHON_SRC} + ) + list(APPEND INCLUDES ${Python3_INCLUDE_DIRS}) + list(APPEND LIBS Python3::Python Boost::python${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}) + + get_filename_component(PYTHON_HOME ${Python3_EXECUTABLE} DIRECTORY) + add_definitions( + -DBOOST_PYTHON_STATIC_LIB + -DPYTHON_HOME="${PYTHON_HOME}" + ) +endif() + +if (EMSCRIPTEN) + file (GLOB_RECURSE WOLF_SYSTEM_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/system/w_gametime.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/w_gametime.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/w_trace.hpp" + ) +else() + file (GLOB_RECURSE WOLF_SYSTEM_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/system/getopt.h" + "${CMAKE_CURRENT_SOURCE_DIR}/system/w_gametime.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/w_gametime.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/w_leak_detector.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/w_leak_detector.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/w_process.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/w_process.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/w_time.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/w_time.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/system/w_trace.hpp" + ) +endif() + +file(GLOB_RECURSE WOLF_SYSTEM_TEST_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/system/test/*" +) + +list(APPEND SRCS + ${WOLF_SYSTEM_SRC} + ${WOLF_SYSTEM_TEST_SRC} +) diff --git a/wolf/cmake/vcpkg.cmake b/wolf/cmake/vcpkg.cmake new file mode 100644 index 000000000..4e0e3b4a1 --- /dev/null +++ b/wolf/cmake/vcpkg.cmake @@ -0,0 +1,62 @@ +if(NOT DEFINED ENV{VCPKG_ROOT}) + message(FATAL_ERROR "VCPKG_ROOT environment variable is not set.") +endif() + +if(QT_IS_ANDROID_MULTI_ABI_EXTERNAL_PROJECT) + if(CMAKE_TOOLCHAIN_FILE MATCHES "android_x86_64") + set(vcpkg_triplet "x64-android") + elseif(CMAKE_TOOLCHAIN_FILE MATCHES "android_x86") + set(vcpkg_triplet "x86-android") + elseif(CMAKE_TOOLCHAIN_FILE MATCHES "android_arm64_v8a") + set(vcpkg_triplet "arm64-android") + elseif(CMAKE_TOOLCHAIN_FILE MATCHES "android_armv7") + set(vcpkg_triplet "arm-neon-android") + endif() +elseif(ANDROID_ABI) + if(ANDROID_ABI STREQUAL "x86_64") + set(vcpkg_triplet "x64-android") + elseif(ANDROID_ABI STREQUAL "x86") + set(vcpkg_triplet "x86-android") + elseif(ANDROID_ABI STREQUAL "arm64-v8a") + set(vcpkg_triplet "arm64-android") + elseif(ANDROID_ABI STREQUAL "armeabi-v7a") + set(vcpkg_triplet "arm-neon-android") + endif() +elseif(EMSCRIPTEN) + set(vcpkg_triplet "wasm32-emscripten") +else() # desktop + if(WIN32) + set(vcpkg_triplet "x64-windows") + elseif(APPLE) + set(vcpkg_triplet "x64-osx") + elseif(UNIX) + set(vcpkg_triplet "x64-linux") + endif() +endif() + +set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake") +if (LIBRARY_TYPE STREQUAL "STATIC" AND NOT EMSCRIPTEN) + set(VCPKG_TARGET_TRIPLET ${vcpkg_triplet}-static CACHE STRING "vcpkg target triplet" FORCE) +else() + set(VCPKG_TARGET_TRIPLET ${vcpkg_triplet} CACHE STRING "vcpkg target triplet" FORCE) +endif() + +include("$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake") + +function(vcpkg_install PACKAGE PACKAGE_NAME WITH_CONFIG) + message(STATUS "finding ${PACKAGE}") + if (WITH_CONFIG) + find_package(${PACKAGE} CONFIG) + else() + find_package(${PACKAGE}) + endif() + if(NOT ${PACKAGE}_FOUND) + message(STATUS "installing ${PACKAGE} via vcpkg") + execute_process(COMMAND vcpkg install ${PACKAGE_NAME} --triplet=${VCPKG_TARGET_TRIPLET}) + if (WITH_CONFIG) + find_package(${PACKAGE} CONFIG REQUIRED) + else() + find_package(${PACKAGE} REQUIRED) + endif() + endif() +endfunction() diff --git a/wolf/media/ffmpeg/w_av_config.cpp b/wolf/media/ffmpeg/w_av_config.cpp new file mode 100644 index 000000000..9f972cebe --- /dev/null +++ b/wolf/media/ffmpeg/w_av_config.cpp @@ -0,0 +1,15 @@ +#include "w_av_config.hpp" + +using w_av_config = wolf::media::ffmpeg::w_av_config; + +w_av_config::w_av_config(_In_ AVPixelFormat p_format, _In_ int p_width, _In_ int p_height, + _In_ int p_alignment) noexcept + : format(p_format), width(p_width), height(p_height), alignment(p_alignment) {} + +w_av_config::w_av_config(_In_ int p_nb_channels, _In_ AVSampleFormat p_sample_fmts, + _In_ int p_sample_rate) noexcept + : sample_rate(p_sample_rate), sample_fmts(p_sample_fmts), nb_channels(p_nb_channels) {} + +int w_av_config::get_required_video_buffer_size() const noexcept { + return av_image_get_buffer_size(this->format, this->width, this->height, this->alignment); +} diff --git a/wolf/media/ffmpeg/w_av_config.hpp b/wolf/media/ffmpeg/w_av_config.hpp new file mode 100644 index 000000000..177acff65 --- /dev/null +++ b/wolf/media/ffmpeg/w_av_config.hpp @@ -0,0 +1,68 @@ +/* + Project: Wolf Engine. Copyright 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#ifdef WOLF_MEDIA_FFMPEG + +#pragma once + +#include + +extern "C" { +#include +#include +} + +namespace wolf::media::ffmpeg { +class w_av_config { + public: +#pragma region Constructors /Destructor + // default constructor + W_API w_av_config() noexcept = default; + + // constructor for av video format + W_API w_av_config(_In_ AVPixelFormat p_format, _In_ int p_width, _In_ int p_height, + _In_ int p_alignment = 1) noexcept; + + // constructor for av audio format + W_API w_av_config(_In_ int p_nb_channels, _In_ AVSampleFormat p_sample_fmts, + _In_ int p_sample_rate) noexcept; + + // destructor + W_API virtual ~w_av_config() noexcept = default; + + // move constructor. + W_API w_av_config(w_av_config &&p_other) noexcept = default; + // move assignment operator. + W_API w_av_config &operator=(w_av_config &&p_other) noexcept = default; + + // copy constructor + w_av_config(const w_av_config &p_other) noexcept = default; + // copy assignment operator + w_av_config &operator=(const w_av_config &p_other) noexcept = default; +#pragma endregion + + /** + * @returns the required buffer size for video frame + */ + W_API int get_required_video_buffer_size() const noexcept; + + // the format of av frame + AVPixelFormat format = AVPixelFormat::AV_PIX_FMT_NONE; + // the width of av frame + int width = 0; + // the height of av frame + int height = 0; + // data alignment + int alignment = 0; + // the sample rate of the audio + int sample_rate = 0; + // the sample format of the audio + AVSampleFormat sample_fmts = AVSampleFormat::AV_SAMPLE_FMT_NONE; + // number of channels + int nb_channels = 0; +}; +} // namespace wolf::media::ffmpeg + +#endif // WOLF_MEDIA_FFMPEG \ No newline at end of file diff --git a/wolf/media/ffmpeg/w_av_format.cpp b/wolf/media/ffmpeg/w_av_format.cpp new file mode 100644 index 000000000..17e37e89d --- /dev/null +++ b/wolf/media/ffmpeg/w_av_format.cpp @@ -0,0 +1,117 @@ +#ifdef WOLF_MEDIA_FFMPEG + +#include "w_av_format.hpp" +#include "w_av_frame.hpp" + +using w_av_format = wolf::media::ffmpeg::w_av_format; + +w_av_format::w_av_format() noexcept + : _stream_buffer(nullptr), _fmt_ctx(nullptr), _io_ctx(nullptr) {} + +void w_av_format::_release() noexcept { + if (this->_stream_buffer != nullptr) { + auto _ptr = this->_stream_buffer.get(); + free(_ptr); + this->_stream_buffer = nullptr; + } + if (this->_fmt_ctx != nullptr) { + auto _ptr = this->_fmt_ctx.get(); + avformat_close_input(&_ptr); + this->_stream_buffer = nullptr; + } + if (this->_io_ctx != nullptr) { + auto _ptr = this->_io_ctx.get(); + av_free(_ptr); + this->_stream_buffer = nullptr; + } +} + +static int s_read_packet(void *p_opaque, _Inout_ uint8_t *p_buf, _In_ int p_buf_size) { + auto _av_fmt = gsl::narrow_cast(p_opaque); + if (_av_fmt) { + if (_av_fmt->on_read_callback) { + return _av_fmt->on_read_callback(p_buf, p_buf_size); + } + } + return -1; // failed +} + +boost::leaf::result w_av_format::init(_In_ int p_stream_buf_size) noexcept { + _release(); + + // alloc a buffer for the stream + auto _ptr = gsl::narrow_cast(malloc(p_stream_buf_size)); + if (_ptr == nullptr) { + // out of memory + return W_FAILURE(std::errc::not_enough_memory, "could not allocate memory for stream buffer"); + } + this->_stream_buffer.reset(_ptr); + + // get a AVContext stream + auto _io_ctx_ptr = + avio_alloc_context(this->_stream_buffer.get(), // buffer + p_stream_buf_size, // buffer size + 0, // buffer is only readable - set to 1 for read/write + this, // use your specified data + s_read_packet, // function - reading Packets (see example) + nullptr, // function - write Packets + nullptr // function - seek to position in stream (see example) + ); + if (_io_ctx_ptr == nullptr) { + // out of memory + return W_FAILURE(std::errc::not_enough_memory, "could not allocate io context"); + } + this->_io_ctx.reset(_io_ctx_ptr); + + // allocate a AVContext + auto _fmt_ctx_ptr = avformat_alloc_context(); + if (_fmt_ctx_ptr == nullptr) { + // out of memory + return W_FAILURE(std::errc::not_enough_memory, "could not allocate avformat context"); + } + this->_fmt_ctx.reset(_fmt_ctx_ptr); + + // Set up the format context based on custom IO + this->_fmt_ctx->pb = _io_ctx_ptr; + this->_fmt_ctx->flags |= AVFMT_FLAG_CUSTOM_IO; + + // open "file" (open our custom IO) + // empty string is where filename would go. doesn't matter since we aren't + // reading a file NULL params are format and demuxer settings, respectively + if (avformat_open_input(&_fmt_ctx_ptr, "", nullptr, nullptr) < 0) { + // error on opening input + return W_FAILURE(std::errc::not_enough_memory, "could not allocate io context"); + } + + // find the stream info + if (avformat_find_stream_info(_fmt_ctx_ptr, nullptr) < 0) { + // Error on finding stream info + return W_FAILURE(std::errc::operation_canceled, "could not get stream info"); + } + + // find best stream + const auto _ret = + av_find_best_stream(_fmt_ctx_ptr, AVMediaType::AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0); + if (_ret < 0) { + // Error on finding stream info + return W_FAILURE(std::errc::operation_canceled, "could not find best stream"); + } + + return 0; +} + +uint8_t *w_av_format::get_io_ctx_buffer() const { + if (this->_io_ctx) { + return this->_io_ctx->buffer; + } + return nullptr; +} + +int w_av_format::get_io_ctx_size() const { + if (this->_io_ctx) { + return this->_io_ctx->buffer_size; + } + return -1; +} + +#endif // WOLF_MEDIA_FFMPEG \ No newline at end of file diff --git a/wolf/media/ffmpeg/w_av_format.hpp b/wolf/media/ffmpeg/w_av_format.hpp new file mode 100644 index 000000000..b41a0a27e --- /dev/null +++ b/wolf/media/ffmpeg/w_av_format.hpp @@ -0,0 +1,50 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#ifdef WOLF_MEDIA_FFMPEG + +#pragma once + +#include + +extern "C" { +#include +} + +namespace wolf::media::ffmpeg { +class w_av_format { + public: +#pragma region Constructors /Destructor + W_API w_av_format() noexcept; + W_API ~w_av_format() noexcept { _release(); }; + // move constructor. + W_API w_av_format(w_av_format &&p_other) noexcept = default; + // move assignment operator. + W_API w_av_format &operator=(w_av_format &&p_other) noexcept = default; +#pragma endregion + + boost::leaf::result init(_In_ int p_stream_buf_size = 32'767) noexcept; + + uint8_t *get_io_ctx_buffer() const; + int get_io_ctx_size() const; + + std::function on_read_callback; + + private: + // copy constructor. + w_av_format(const w_av_format &) = delete; + // copy assignment operator. + w_av_format &operator=(const w_av_format &) = delete; + + // release + void _release() noexcept; + + std::unique_ptr _stream_buffer = nullptr; + std::unique_ptr _fmt_ctx = nullptr; + std::unique_ptr _io_ctx = nullptr; +}; +} // namespace wolf::media::ffmpeg + +#endif // WOLF_MEDIA_FFMPEG diff --git a/wolf/media/ffmpeg/w_av_frame.cpp b/wolf/media/ffmpeg/w_av_frame.cpp new file mode 100644 index 000000000..03d681d87 --- /dev/null +++ b/wolf/media/ffmpeg/w_av_frame.cpp @@ -0,0 +1,300 @@ +#ifdef WOLF_MEDIA_FFMPEG + +#include "w_av_frame.hpp" +#include "w_ffmpeg_ctx.hpp" + +extern "C" { +#include +#include +#include +} + +#ifdef WOLF_MEDIA_STB +#include +#include +#endif //WOLF_MEDIA_STB + +using w_av_frame = wolf::media::ffmpeg::w_av_frame; +using w_av_config = wolf::media::ffmpeg::w_av_config; + +w_av_frame::w_av_frame(_In_ w_av_config &&p_config) noexcept : _config(std::move(p_config)) {} + +void w_av_frame::_release() noexcept { + if (this->_av_frame != nullptr) { + av_frame_free(&this->_av_frame); + if (this->_config.nb_channels > 0) { + av_channel_layout_uninit(&this->_av_frame->ch_layout); + } + } +} + +void w_av_frame::_move(w_av_frame &&p_other) noexcept { + if (this == &p_other) { + return; + } + this->_av_frame = std::exchange(p_other._av_frame, nullptr); + this->_config = std::move(p_other._config); + this->_data = std::move(p_other._data); +} + +boost::leaf::result w_av_frame::init() noexcept { + _release(); + + // allocate memory for AVFrame + this->_av_frame = av_frame_alloc(); + if (this->_av_frame == nullptr) { + return W_FAILURE(std::errc::not_enough_memory, "could not allocate memory for AVFrame"); + } + + // set audio + this->_av_frame->sample_rate = this->_config.sample_rate; + if (this->_config.nb_channels > 0) { + av_channel_layout_default(&this->_av_frame->ch_layout, this->_config.nb_channels); + } + + // set video + this->_av_frame->format = gsl::narrow_cast(this->_config.format); + this->_av_frame->width = this->_config.width; + this->_av_frame->height = this->_config.height; + + return 0; +} + +boost::leaf::result w_av_frame::set_video_frame( + _Inout_ std::vector &&p_data) noexcept { + const auto _width = this->_config.width; + const auto _height = this->_config.height; + const auto _alignment = this->_config.alignment; + + // check for width and height + if (_width <= 0 || _height <= 0) { + return W_FAILURE(std::errc::invalid_argument, "width or height of w_av_frame is zero"); + } + + // move the owenership of data to buffer + this->_data = std::forward &&>(p_data); + + const auto _buffer_size = this->_config.get_required_video_buffer_size(); + if (this->_data.empty()) { + this->_data.resize(_buffer_size, 0); + } + + // if size does not fit + if (this->_data.size() != _buffer_size) { + return W_FAILURE(std::errc::invalid_argument, + wolf::format("w_av_frame video buffer size is expected {} but is {}", + _buffer_size, this->_data.size())); + } + + const auto _ret = av_image_fill_arrays(this->_av_frame->data, this->_av_frame->linesize, + gsl::narrow_cast(this->_data.data()), + this->_config.format, this->_av_frame->width, + this->_av_frame->height, _alignment); + if (_ret < 0) { + return W_FAILURE(std::errc::operation_canceled, "av_image_fill_arrays failed"); + } + return _ret; +} + +void w_av_frame::set_pts(_In_ int64_t p_pts) noexcept { this->_av_frame->pts = p_pts; } + +std::tuple w_av_frame::get() const noexcept { + if (this->_av_frame) { + auto _buffer_size = + av_image_get_buffer_size(gsl::narrow_cast(this->_av_frame->format), + this->_av_frame->width, this->_av_frame->height, 4); + + return std::make_tuple(this->_av_frame->data, _buffer_size); + } + return std::make_tuple(nullptr, 0); +} + +w_av_config w_av_frame::get_config() const noexcept { return this->_config; } + +boost::leaf::result w_av_frame::convert_audio(_In_ w_av_config &&p_dst_config) { + auto _ret = 0; + + SwrContext *swr = nullptr; + auto _dst_frame = w_av_frame(std::move(p_dst_config)); + _dst_frame.init(); + + DEFER { + if (_ret != S_OK) { + if (swr) { + if (swr_is_initialized(swr)) { + swr_close(swr); + } + swr_free(&swr); + } + } + }); + + swr_alloc_set_opts2(&swr, &this->_channel_layout, p_dst_config.sample_fmts, + p_dst_config.sample_rate, &this->_av_frame->ch_layout, + gsl::narrow_cast(this->_av_frame->format), + this->_av_frame->sample_rate, 0, nullptr); + + if (swr == nullptr) { + _ret = -1; + return W_FAILURE(std::errc::operation_canceled, "could not create audio SwrContext"); + } + + _ret = swr_init(swr); + + if (_ret < 0) { + return W_FAILURE(std::errc::operation_canceled, "could not initialize audio SwrContext"); + } + + // get number of samples + auto _sample_rate = gsl::narrow_cast(this->_av_frame->sample_rate); + + auto _delay = swr_get_delay(swr, _sample_rate); + + const auto _rescale_rnd = + av_rescale_rnd(_delay + _sample_rate, gsl::narrow_cast(p_dst_config.sample_rate), + _sample_rate, AV_ROUND_UP); + + _dst_frame._av_frame->nb_samples = gsl::narrow_cast(_rescale_rnd); + + auto size = av_samples_alloc(gsl::narrow_cast(&_dst_frame._av_frame->data[0]), + &_dst_frame._av_frame->linesize[0], + this->_channel_layout.nb_channels, _dst_frame._config.nb_channels, + gsl::narrow_cast(p_dst_config.sample_fmts), 1); + + if (size < 0) { + _ret = -1; + return W_FAILURE(std::errc::operation_canceled, + "could not allocate memory for buffer of audio"); + } + + /* convert to destination format */ + size = swr_convert(swr, gsl::narrow_cast(&_dst_frame._av_frame->data[0]), + _dst_frame._av_frame->nb_samples, + (const uint8_t **)(&this->_av_frame->data[0]), this->_av_frame->nb_samples); + + if (size < 0) { + _ret = -1; + return W_FAILURE(std::errc::operation_canceled, "error while audio converting\n"); + } + + const auto _buffer_size = av_samples_get_buffer_size(&_dst_frame._av_frame->linesize[0], + _dst_frame._av_frame->ch_layout.nb_channels, + size, p_dst_config.sample_fmts, 1); + + if (_buffer_size < 0) { + _ret = -1; + return W_FAILURE(std::errc::operation_canceled, "could not get sample buffer size\n"); + } + + return _dst_frame; +} + +boost::leaf::result w_av_frame::convert_video(_In_ w_av_config &&p_dst_config) { + // create a buffer and dst frame + auto _video_buffer = std::vector(); + auto _dst_frame = w_av_frame(std::move(p_dst_config)); + BOOST_LEAF_CHECK(_dst_frame.init()); + BOOST_LEAF_CHECK(_dst_frame.set_video_frame(std::move(_video_buffer))); + + auto *_context = sws_getContext( + this->_config.width, this->_config.height, this->_config.format, _dst_frame._config.width, + _dst_frame._config.height, _dst_frame._config.format, SWS_BICUBIC, nullptr, nullptr, nullptr); + if (_context == nullptr) { + return W_FAILURE(std::errc::not_enough_memory, "could not create sws context"); + } + + auto _dst_frame_nn = gsl::not_null(_dst_frame._av_frame); + const auto _height = + sws_scale(_context, gsl::narrow_cast(this->_av_frame->data), + gsl::narrow_cast(this->_av_frame->linesize), 0, this->_config.height, + gsl::narrow_cast(_dst_frame_nn->data), + gsl::narrow_cast(_dst_frame_nn->linesize)); + + // free context + sws_freeContext(_context); + + if (_height < 0) { + return W_FAILURE( + std::errc::invalid_argument, + "w_av_frame sws_scale failed because: \"" + w_ffmpeg_ctx::get_av_error_str(_height) + "\""); + } + + return _dst_frame; +} + +boost::leaf::result w_av_frame::load_video_frame_from_img_file( + _In_ const std::filesystem::path &p_path, _In_ AVPixelFormat p_pixel_fmt) { +#ifdef WOLF_MEDIA_STB + + // width, height, comp + int _width = 0; + int _height = 0; + int _comp = 0; + + const auto _path = p_path.string(); + if (!std::filesystem::exists(p_path)) { + return W_FAILURE(std::errc::invalid_argument, " path not exist for av_frame" + _path); + } + + auto *_raw_img_data = stbi_load(_path.c_str(), &_width, &_height, &_comp, 0); + + if (_raw_img_data == nullptr) { + return W_FAILURE(std::errc::invalid_argument, "could not load image file " + _path); + } + + auto _len = gsl::narrow_cast(_width * _height * _comp); + const auto _raw_img_data_span = gsl::span(_raw_img_data, _raw_img_data + _len); + auto _raw_img_data_vec = + std::vector(_raw_img_data_span.begin(), _raw_img_data_span.end()); + + free(_raw_img_data); + + auto _src_config = w_av_config(p_pixel_fmt, _width, _height); + // create an av_frame from image raw data + auto _src_frame = w_av_frame(std::move(_src_config)); + BOOST_LEAF_CHECK(_src_frame.init()); + BOOST_LEAF_CHECK(_src_frame.set_video_frame(std::move(_raw_img_data_vec))); + + return _src_frame; +#else + return W_FAILURE(std::errc::not_supported, "WOLF_MEDIA_STB not defined"); +#endif +} + +boost::leaf::result w_av_frame::save_video_frame_to_img_file( + _In_ const std::filesystem::path &p_path, int p_quality) noexcept { + try { +#ifdef WOLF_MEDIA_STB + + if (this->_av_frame == nullptr || this->_av_frame->width == 0 || this->_av_frame->height == 0) { + return W_FAILURE(std::errc::invalid_argument, "bad parameters for avframe"); + } + + const auto _path = p_path.string(); + auto _ext = p_path.extension().string(); + std::transform(_ext.cbegin(), _ext.cend(), _ext.begin(), ::tolower); + + const auto _comp = this->_av_frame->linesize[0] / this->_av_frame->width; + if (_ext == ".bmp") { + return stbi_write_bmp(_path.c_str(), this->_config.width, this->_config.height, _comp, + this->_av_frame->data[0]); + } + if (_ext == ".png") { + return stbi_write_png(_path.c_str(), this->_config.width, this->_config.height, _comp, + this->_av_frame->data[0], this->_av_frame->linesize[0]); + } + if (_ext == ".jpg" || _ext == ".jpeg") { + return stbi_write_jpg(_path.c_str(), this->_config.width, this->_config.height, _comp, + this->_av_frame->data[0], p_quality); + } + return W_FAILURE(std::errc::invalid_argument, "image format not supported for " + _path); + } catch (const std::exception &p_exc) { + return W_FAILURE(std::errc::invalid_argument, + "caught an esxception for " + std::string(p_exc.what())); + } +#else + return W_FAILURE(std::errc::not_supported, "WOLF_MEDIA_STB not defined"); +#endif +} + +#endif // WOLF_MEDIA_FFMPEG \ No newline at end of file diff --git a/wolf/media/ffmpeg/w_av_frame.hpp b/wolf/media/ffmpeg/w_av_frame.hpp new file mode 100644 index 000000000..71155ae3e --- /dev/null +++ b/wolf/media/ffmpeg/w_av_frame.hpp @@ -0,0 +1,136 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#ifdef WOLF_MEDIA_FFMPEG + +#pragma once + +#include +#include "w_av_config.hpp" + +extern "C" { +#include +} + +namespace wolf::media::ffmpeg { + +class w_decoder; +class w_encoder; + +class w_av_frame { + friend w_decoder; + friend w_encoder; + + public: + /** + * constructor the av_frame with specific config + * @param p_config, the av audio config + */ + W_API explicit w_av_frame(_In_ w_av_config &&p_config) noexcept; + + // destructor + W_API virtual ~w_av_frame() noexcept { _release(); } + + // move constructor. + W_API w_av_frame(w_av_frame &&p_other) noexcept { _move(std::forward(p_other)); } + // move assignment operator. + W_API w_av_frame &operator=(w_av_frame &&p_other) noexcept { + _move(std::forward(p_other)); + return *this; + } + + /** + * initialize the avframe + * @returns zero on success + */ + W_API + boost::leaf::result init() noexcept; + + /** + * set the AVFrame data + * @param p_data, the initial data of ffmpeg AVFrame + * @param p_alignment, the alignment + * @returns zero on success + */ + W_API boost::leaf::result set_video_frame(_Inout_ std::vector &&p_data) noexcept; + + /** + * set the AVFrame's pts + * @param p_pts, the pts data + * @returns void + */ + W_API void set_pts(_In_ int64_t p_pts) noexcept; + + /** + * get data and linesize as a tuple + * @returns tuple + */ + W_API + std::tuple get() const noexcept; + + /** + * convert the ffmpeg video AVFrame + * @returns the converted instance of AVFrame + */ + W_API + boost::leaf::result convert_video(_In_ w_av_config &&p_dst_config); + + /** + * convert the ffmpeg audio AVFrame + * @returns the converted instance of AVFrame + */ + W_API + boost::leaf::result convert_audio(_In_ w_av_config &&p_dst_config); + + /** + * @returns config + */ + W_API w_av_config get_config() const noexcept; + +#ifdef WOLF_MEDIA_STB + + /** + * create w_av_frame from image file path + * @returns the AVFrame + */ + W_API + static boost::leaf::result load_video_frame_from_img_file( + _In_ const std::filesystem::path &p_path, _In_ AVPixelFormat p_pixel_fmt); + + /** + * save to to the image file + * @param p_quality, quality will be used only for jpeg and is between 1 and + * 100 + * @returns zero on success + */ + W_API + boost::leaf::result save_video_frame_to_img_file(_In_ const std::filesystem::path &p_path, + int p_quality = 100) noexcept; + +#endif + + private: + // copy constructor. + w_av_frame(const w_av_frame &) = delete; + // copy assignment operator. + w_av_frame &operator=(const w_av_frame &) = delete; + + // release + void _release() noexcept; + // move + void _move(w_av_frame &&p_other) noexcept; + + // the channel layout of the audio + AVChannelLayout _channel_layout = {}; + // the AVFrame config + w_av_config _config = {}; + // the ffmpeg AVFrame + gsl::owner _av_frame = nullptr; + // the ffmpeg AVFrame data + std::vector _data = {}; +}; +} // namespace wolf::media::ffmpeg + +#endif // WOLF_MEDIA_FFMPEG diff --git a/wolf/media/ffmpeg/w_av_packet.cpp b/wolf/media/ffmpeg/w_av_packet.cpp new file mode 100644 index 000000000..462e9d25c --- /dev/null +++ b/wolf/media/ffmpeg/w_av_packet.cpp @@ -0,0 +1,64 @@ +#ifdef WOLF_MEDIA_FFMPEG + +#include "w_av_packet.hpp" + +using w_av_packet = wolf::media::ffmpeg::w_av_packet; + +w_av_packet::w_av_packet(_In_ AVPacket *p_av_packet) noexcept + : _packet(p_av_packet) {} + +boost::leaf::result w_av_packet::init() noexcept { + _release(); + return init(nullptr, 0); +} + +boost::leaf::result w_av_packet::init(_Inout_ std::vector &&p_data) noexcept { + _release(); + this->_own_data = std::forward &&>(p_data); + return init(this->_own_data.data(), this->_own_data.size()); +} + +boost::leaf::result w_av_packet::init(_In_ uint8_t *p_data, _In_ size_t p_data_len) noexcept { + + this->_packet = av_packet_alloc(); + if (this->_packet == nullptr) { + return W_FAILURE(std::errc::not_enough_memory, "could not allocate memory for av packet"); + } + if (p_data && p_data_len) { + this->_packet->data = p_data; + this->_packet->size = gsl::narrow_cast(p_data_len); + } + + return 0; +} + +void w_av_packet::unref() noexcept { av_packet_unref(this->_packet); } + +uint8_t *w_av_packet::get_data() const noexcept { + return this->_packet->data; +} + +int w_av_packet::get_size() const noexcept { + return this->_packet->size; +} + +int w_av_packet::get_stream_index() const noexcept { + return this->_packet->stream_index; +} + +void w_av_packet::_release() noexcept { + if (this->_packet != nullptr) { + av_packet_free(&this->_packet); + this->_packet = nullptr; + } +} + +void w_av_packet::_move(_Inout_ w_av_packet &&p_other) noexcept { + if (this == &p_other) { + return; + } + this->_packet = std::exchange(p_other._packet, nullptr); + this->_own_data = std::move(p_other._own_data); +} + +#endif // WOLF_MEDIA_FFMPEG diff --git a/wolf/media/ffmpeg/w_av_packet.hpp b/wolf/media/ffmpeg/w_av_packet.hpp new file mode 100644 index 000000000..12287bd8e --- /dev/null +++ b/wolf/media/ffmpeg/w_av_packet.hpp @@ -0,0 +1,96 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#ifdef WOLF_MEDIA_FFMPEG + +#pragma once + +#include + +extern "C" { +#include +} + +namespace wolf::media::ffmpeg { + +class w_decoder; +class w_encoder; +class w_ffmpeg; + +class w_av_packet { + friend w_decoder; + friend w_encoder; + friend w_ffmpeg; + + public: + // default construct an av_packet + W_API w_av_packet() noexcept = default; + + /** + * construct an av_packet + */ + W_API explicit w_av_packet(_In_ AVPacket *p_av_packet) noexcept; + + // move constructor. + W_API w_av_packet(w_av_packet &&p_other) noexcept { + _move(std::forward(p_other)); + } + // move assignment operator. + W_API w_av_packet &operator=(w_av_packet &&p_other) noexcept { + _move(std::forward(p_other)); + return *this; + } + + // destructor + W_API virtual ~w_av_packet() noexcept { _release(); } + + /** + * initialize the av_packet + * @returns zero on success + */ + W_API boost::leaf::result init() noexcept; + + /** + * initialize the av_packet from data + * @returns zero on success + */ + W_API boost::leaf::result init(_In_ uint8_t *p_data, _In_ size_t p_data_len) noexcept; + + /** + * initialize the av_packet + * @returns void + */ + W_API boost::leaf::result init(_Inout_ std::vector &&p_data) noexcept; + + /** + * unref av_packet + */ + W_API void unref() noexcept; + + // get packet data + W_API uint8_t *get_data() const noexcept; + + // get packet size + W_API int get_size() const noexcept; + + // get stream index + W_API int get_stream_index() const noexcept; + + private: + // copy constructor. + w_av_packet(const w_av_packet &) = delete; + // copy assignment operator. + w_av_packet &operator=(const w_av_packet &) = delete; + // release the resources + void _release() noexcept; + // move the resources + void _move(_Inout_ w_av_packet && p_other) noexcept; + + gsl::owner _packet = {}; + std::vector _own_data; +}; +} // namespace wolf::media::ffmpeg + +#endif \ No newline at end of file diff --git a/wolf/media/ffmpeg/w_decoder.cpp b/wolf/media/ffmpeg/w_decoder.cpp new file mode 100644 index 000000000..4e0a9d7c6 --- /dev/null +++ b/wolf/media/ffmpeg/w_decoder.cpp @@ -0,0 +1,70 @@ +#ifdef WOLF_MEDIA_FFMPEG + +#include "w_decoder.hpp" + +using w_decoder = wolf::media::ffmpeg::w_decoder; + +boost::leaf::result w_decoder::decode_frame_from_packet(_In_ AVPacket *p_packet, + _Inout_ w_av_frame &p_frame) { + // start decoding + auto _ret = avcodec_send_packet(this->ctx.codec_ctx, p_packet); + if (_ret < 0) { + return W_FAILURE(std::errc::operation_canceled, + "could not parse packet for decoding because:\"" + + w_ffmpeg_ctx::get_av_error_str(_ret) + "\""); + } + + for (;;) { + _ret = avcodec_receive_frame(this->ctx.codec_ctx, p_frame._av_frame); + if (_ret == 0 || _ret == AVERROR(EAGAIN) || _ret == AVERROR_EOF) { + break; + } + if (_ret < 0) { + return W_FAILURE(std::errc::operation_canceled, + "error happened during the encoding because:\"" + + w_ffmpeg_ctx::get_av_error_str(_ret) + "\""); + } + } + return 0; +} + +boost::leaf::result w_decoder::decode(_In_ const w_av_packet &p_packet, + _Inout_ w_av_frame &p_frame, + _In_ bool p_flush) noexcept { + auto _dst_packet = w_av_packet(); + _dst_packet.init(); + + for (;;) { + const auto _bytes = + av_parser_parse2(this->ctx.parser, this->ctx.codec_ctx, &_dst_packet._packet->data, + &_dst_packet._packet->size, p_packet._packet->data, p_packet._packet->size, + AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); + + if (_bytes == 0) { + break; + } + + if (_dst_packet._packet->size == 0) { + // try decode the inputed packet + BOOST_LEAF_CHECK(decode_frame_from_packet(p_packet._packet, p_frame)); + } else { + if (_bytes < 0) { + return W_FAILURE(std::errc::operation_canceled, "could not parse packet for decoding"); + } + p_packet._packet->data += _bytes; + p_packet._packet->size -= _bytes; + if (_dst_packet._packet->size > 0) { + BOOST_LEAF_CHECK(decode_frame_from_packet(_dst_packet._packet, p_frame)); + } + } + } + + if (p_flush) { + // flush the decoder + BOOST_LEAF_CHECK(decode_frame_from_packet(nullptr, p_frame)); + } + + return 0; +} + +#endif // WOLF_MEDIA_FFMPEG \ No newline at end of file diff --git a/wolf/media/ffmpeg/w_decoder.hpp b/wolf/media/ffmpeg/w_decoder.hpp new file mode 100644 index 000000000..08bc925d6 --- /dev/null +++ b/wolf/media/ffmpeg/w_decoder.hpp @@ -0,0 +1,46 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#ifdef WOLF_MEDIA_FFMPEG + +#pragma once + +#include "w_av_frame.hpp" +#include "w_av_packet.hpp" +#include "w_ffmpeg_ctx.hpp" +#include + +namespace wolf::media::ffmpeg { + +class w_decoder { +public: + w_ffmpeg_ctx ctx = {}; + + // constructor + W_API w_decoder() = default; + // destructor + W_API virtual ~w_decoder() noexcept = default; + + // move constructor. + W_API w_decoder(w_decoder &&p_other) noexcept = default; + // move assignment operator. + W_API w_decoder &operator=(w_decoder &&p_other) noexcept = default; + + W_API boost::leaf::result decode(_In_ const w_av_packet &p_packet, + _Inout_ w_av_frame &p_frame, + _In_ bool p_flush = false) noexcept; + +private: + // copy constructor + w_decoder(const w_decoder &) = delete; + // copy operator + w_decoder &operator=(const w_decoder &) = delete; + + boost::leaf::result decode_frame_from_packet(_In_ AVPacket *p_packet, + _Inout_ w_av_frame &p_frame); +}; +} // namespace wolf::media::ffmpeg + +#endif // WOLF_MEDIA_FFMPEG \ No newline at end of file diff --git a/wolf/media/ffmpeg/w_encoder.cpp b/wolf/media/ffmpeg/w_encoder.cpp new file mode 100644 index 000000000..f141a489d --- /dev/null +++ b/wolf/media/ffmpeg/w_encoder.cpp @@ -0,0 +1,62 @@ +#ifdef WOLF_MEDIA_FFMPEG + +#include "w_encoder.hpp" + +using w_encoder = wolf::media::ffmpeg::w_encoder; +using w_av_packet = wolf::media::ffmpeg::w_av_packet; + +boost::leaf::result w_encoder::_encode_frame_to_packet( + _In_ const AVFrame *p_frame, _Inout_ std::vector &p_packet_data) const noexcept { + auto _packet = w_av_packet(); + _packet.init(); + + for (;;) { + auto _ret = avcodec_send_frame(this->ctx.codec_ctx, p_frame); + if (_ret < 0) { + return W_FAILURE(std::errc::operation_canceled, + "failed to send the avframe for encoding because:\"" + + w_ffmpeg_ctx::get_av_error_str(_ret) + "\""); + } + + for (;;) { + _ret = avcodec_receive_packet(this->ctx.codec_ctx, _packet._packet); + if (_ret == 0 || _ret == AVERROR_EOF) { + if (_packet._packet->size) { + std::copy(_packet._packet->data, _packet._packet->data + _packet._packet->size, + std::back_inserter(p_packet_data)); + } + return 0; + } + + _packet.unref(); + + if (_ret == AVERROR(EAGAIN)) { + break; + } + return W_FAILURE(std::errc::operation_canceled, + "error happened during the encoding because:\"" + + w_ffmpeg_ctx::get_av_error_str(_ret) + "\""); + } + } + return 0; +} + +boost::leaf::result w_encoder::encode(_In_ const w_av_frame &p_frame, + _Inout_ w_av_packet &p_packet, + _In_ bool p_flush) noexcept { + std::vector _packet_data; + + // encode frame to packet + BOOST_LEAF_CHECK(_encode_frame_to_packet(p_frame._av_frame, _packet_data)); + if (p_flush) { + // flush + BOOST_LEAF_CHECK(_encode_frame_to_packet(nullptr, _packet_data)); + } + + // init packet + p_packet.init(std::move(_packet_data)); + + return {}; +} + +#endif // WOLF_MEDIA_FFMPEG \ No newline at end of file diff --git a/wolf/media/ffmpeg/w_encoder.hpp b/wolf/media/ffmpeg/w_encoder.hpp new file mode 100644 index 000000000..8b738daa2 --- /dev/null +++ b/wolf/media/ffmpeg/w_encoder.hpp @@ -0,0 +1,47 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#ifdef WOLF_MEDIA_FFMPEG + +#pragma once + +#include "w_av_frame.hpp" +#include "w_av_packet.hpp" +#include "w_ffmpeg_ctx.hpp" +#include + +namespace wolf::media::ffmpeg { + +class w_encoder { +public: + // constructor + W_API w_encoder() = default; + // destructor + W_API virtual ~w_encoder() noexcept = default; + + // move constructor. + W_API w_encoder(w_encoder &&p_other) noexcept = default; + // move assignment operator. + W_API w_encoder &operator=(w_encoder &&p_other) noexcept = default; + + W_API boost::leaf::result encode(_In_ const w_av_frame &p_frame, + _Inout_ w_av_packet &p_packet, + _In_ bool p_flush = true) noexcept; + + w_ffmpeg_ctx ctx = {}; + +private: + // copy constructor + w_encoder(const w_encoder &) = delete; + // copy operator + w_encoder &operator=(const w_encoder &) = delete; + + boost::leaf::result + _encode_frame_to_packet(_In_ const AVFrame *p_frame, + _Inout_ std::vector &p_packet_data) const noexcept; +}; +} // namespace wolf::media::ffmpeg + +#endif // WOLF_MEDIA_FFMPEG \ No newline at end of file diff --git a/wolf/media/ffmpeg/w_ffmpeg.cpp b/wolf/media/ffmpeg/w_ffmpeg.cpp new file mode 100644 index 000000000..5773a87ea --- /dev/null +++ b/wolf/media/ffmpeg/w_ffmpeg.cpp @@ -0,0 +1,345 @@ +#ifdef WOLF_MEDIA_FFMPEG + +#include "w_ffmpeg.hpp" + +extern "C" { +#include +} + +using w_av_codec_opt = wolf::media::ffmpeg::w_av_codec_opt; +using w_av_config = wolf::media::ffmpeg::w_av_config; +using w_av_set_opt = wolf::media::ffmpeg::w_av_set_opt; +using w_decoder = wolf::media::ffmpeg::w_decoder; +using w_encoder = wolf::media::ffmpeg::w_encoder; +using w_ffmpeg = wolf::media::ffmpeg::w_ffmpeg; +using w_ffmpeg_ctx = wolf::media::ffmpeg::w_ffmpeg_ctx; + +static boost::leaf::result s_set_dict( + _In_ const std::vector &p_opts) noexcept { + AVDictionary *_dict = nullptr; + if (p_opts.empty()) { + return _dict; + } + + auto _ret = av_dict_set(&_dict, nullptr, nullptr, 0); + if (_ret < 0) { + return W_FAILURE(std::errc::operation_canceled, + "could not allocate memory for AVDictionary because: " + + w_ffmpeg_ctx::get_av_error_str(_ret)); + } + + try { + for (const auto &_opt : p_opts) { + if (_opt.name.empty()) { + continue; + } + + auto _name_str = _opt.name.c_str(); + if (std::holds_alternative(_opt.value)) { + // set an integer value + const auto _value = std::get(_opt.value); + const auto _ret = av_dict_set_int(&_dict, _name_str, _value, 0); + if (_ret < 0) { + return W_FAILURE(std::errc::invalid_argument, "could not set int value for " + _opt.name + + ":" + std::to_string(_value) + + " because " + + w_ffmpeg_ctx::get_av_error_str(_ret)); + } + } else { + // set string value + const auto _value_str = &std::get(_opt.value); + if (_value_str && !_value_str->empty()) { + const auto _ret = av_dict_set(&_dict, _opt.name.c_str(), _value_str->c_str(), 0); + if (_ret < 0) { + return W_FAILURE(std::errc::invalid_argument, + "could not set string value for " + _opt.name + ":" + *_value_str + + " because " + w_ffmpeg_ctx::get_av_error_str(_ret)); + } + } + } + } + } catch (const std::exception &p_exc) { + return W_FAILURE(std::errc::operation_canceled, + "s_set_dict failed because: " + std::string(p_exc.what())); + } + return _dict; +} + +static boost::leaf::result s_create(_Inout_ w_ffmpeg_ctx &p_ctx, + _In_ const w_av_config &p_config, + _In_ const w_av_codec_opt &p_codec_opts, + _In_ const std::vector &p_opts) noexcept { + p_ctx.codec_ctx = avcodec_alloc_context3(p_ctx.codec); + if (p_ctx.codec_ctx == nullptr) { + return W_FAILURE(std::errc::not_enough_memory, + "could not allocate memory for avcodec context3"); + } + + bool _has_error = false; + DEFER { + if (_has_error && p_ctx.codec_ctx) { + auto _ptr = p_ctx.codec_ctx; + avcodec_free_context(&_ptr); + p_ctx.codec_ctx = nullptr; + } + }); + + p_ctx.codec_ctx->width = p_config.width; + p_ctx.codec_ctx->height = p_config.height; + p_ctx.codec_ctx->bit_rate = p_codec_opts.bitrate; + p_ctx.codec_ctx->time_base = AVRational{1, p_codec_opts.fps}; + p_ctx.codec_ctx->framerate = AVRational{p_codec_opts.fps, 1}; + p_ctx.codec_ctx->pix_fmt = p_config.format; + + // set gop + if (p_codec_opts.gop >= 0) { + p_ctx.codec_ctx->gop_size = p_codec_opts.gop; + } + // set refs + if (p_codec_opts.refs >= 0) { + p_ctx.codec_ctx->refs = p_codec_opts.refs; + } + // set frames + if (p_codec_opts.max_b_frames >= 0) { + p_ctx.codec_ctx->max_b_frames = p_codec_opts.max_b_frames; + } + // set thread numbers + if (p_codec_opts.thread_count >= 0) { + p_ctx.codec_ctx->thread_count = p_codec_opts.thread_count; + } + // set level + if (p_codec_opts.level >= 0) { + p_ctx.codec_ctx->level = p_codec_opts.level; + } + // set flags + if (p_ctx.codec_ctx->flags & AVFMT_GLOBALHEADER) { + p_ctx.codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } + + try { + for (const auto &_opt : p_opts) { + if (_opt.name.empty()) { + continue; + } + + auto _name_str = _opt.name.c_str(); + if (std::holds_alternative(_opt.value)) { + // set an integer value + const auto _value = std::get(_opt.value); + const auto _ret = av_opt_set_int(p_ctx.codec_ctx->priv_data, _name_str, _value, 0); + if (_ret < 0) { + return W_FAILURE(std::errc::invalid_argument, "could not set int value for " + _opt.name + + ":" + std::to_string(_value) + + " because " + + w_ffmpeg_ctx::get_av_error_str(_ret)); + } + } else if (std::holds_alternative(_opt.value)) { + // set double value + const auto _value = std::get(_opt.value); + const auto _ret = av_opt_set_double(p_ctx.codec_ctx->priv_data, _name_str, _value, 0); + if (_ret < 0) { + return W_FAILURE(std::errc::invalid_argument, "could not set double value for " + + _opt.name + ":" + + std::to_string(_value) + " because " + + w_ffmpeg_ctx::get_av_error_str(_ret)); + } + } else { + // set string value + const auto _value_str = &std::get(_opt.value); + if (_value_str && !_value_str->empty()) { + const auto _ret = + av_opt_set(p_ctx.codec_ctx->priv_data, _opt.name.c_str(), _value_str->c_str(), 0); + if (_ret < 0) { + return W_FAILURE(std::errc::invalid_argument, + "could not set string value for " + _opt.name + ":" + *_value_str + + " because " + w_ffmpeg_ctx::get_av_error_str(_ret)); + } + } + } + } + } catch (const std::exception &p_exc) { + return W_FAILURE(std::errc::operation_canceled, + "could not set av option because: " + std::string(p_exc.what())); + } + + // open avcodec + const auto _ret = avcodec_open2(p_ctx.codec_ctx, p_ctx.codec_ctx->codec, nullptr); + if (_ret < 0) { + return W_FAILURE(std::errc::operation_canceled, + "could not open avcodec because " + w_ffmpeg_ctx::get_av_error_str(_ret)); + } + return 0; +} + +boost::leaf::result w_ffmpeg::create_encoder( + _In_ const w_av_config &p_config, _In_ AVCodecID p_id, _In_ const w_av_codec_opt &p_codec_opts, + _In_ const std::vector &p_opts) noexcept { + w_encoder _encoder = {}; + + _encoder.ctx.codec = avcodec_find_encoder(p_id); + if (_encoder.ctx.codec == nullptr) { + return W_FAILURE(std::errc::invalid_argument, + "could not find encoder codec id: " + std::to_string(p_id)); + } + + BOOST_LEAF_CHECK(s_create(_encoder.ctx, p_config, p_codec_opts, p_opts)); + + return _encoder; +} + +boost::leaf::result w_ffmpeg::create_encoder( + _In_ const w_av_config &p_config, _In_ const std::string &p_id, + _In_ const w_av_codec_opt &p_codec_opts, + _In_ const std::vector &p_opts) noexcept { + w_encoder _encoder = {}; + + _encoder.ctx.codec = avcodec_find_encoder_by_name(p_id.c_str()); + if (_encoder.ctx.codec == nullptr) { + return W_FAILURE(std::errc::invalid_argument, "could not find encoder codec id: " + p_id); + }; + + BOOST_LEAF_CHECK(s_create(_encoder.ctx, p_config, p_codec_opts, p_opts)); + + return _encoder; +} + +boost::leaf::result w_ffmpeg::create_decoder( + _In_ const w_av_config &p_config, _In_ const AVCodecID p_id, + _In_ const w_av_codec_opt &p_codec_opts, + _In_ const std::vector &p_opts) noexcept { + w_decoder _decoder = {}; + + _decoder.ctx.codec = avcodec_find_decoder(p_id); + if (_decoder.ctx.codec == nullptr) { + return W_FAILURE(std::errc::invalid_argument, + "could not find decoder codec id: " + std::to_string(p_id)); + } + + _decoder.ctx.parser = av_parser_init(_decoder.ctx.codec->id); + if (_decoder.ctx.parser == nullptr) { + return W_FAILURE(std::errc::invalid_argument, + "could not initialize parser for codec id: " + std::to_string(p_id)); + } + + BOOST_LEAF_CHECK(s_create(_decoder.ctx, p_config, p_codec_opts, p_opts)); + + return _decoder; +} + +boost::leaf::result w_ffmpeg::create_decoder( + _In_ const w_av_config &p_config, _In_ const std::string &p_id, + _In_ const w_av_codec_opt &p_codec_opts, + _In_ const std::vector &p_opts) noexcept { + w_decoder _decoder = {}; + + _decoder.ctx.codec = avcodec_find_decoder_by_name(p_id.c_str()); + if (_decoder.ctx.codec == nullptr) { + return W_FAILURE(std::errc::invalid_argument, "could not find decoder codec id: " + p_id); + } + + _decoder.ctx.parser = av_parser_init(_decoder.ctx.codec->id); + if (_decoder.ctx.parser == nullptr) { + return W_FAILURE(std::errc::invalid_argument, + "could not initialize parser for codec id: " + p_id); + } + + BOOST_LEAF_CHECK(s_create(_decoder.ctx, p_config, p_codec_opts, p_opts)); + + return _decoder; +} + +boost::leaf::result w_ffmpeg::open_stream( + _In_ const std::string &p_url, _In_ const std::vector &p_opts, + _In_ const + std::function &p_on_frame) noexcept { + try { + // url is invalid + if (p_url.empty()) { + return W_FAILURE(std::errc::invalid_argument, + "could not allocate memory for av format context"); + } + + // allocate memory for avformat context + auto _fmt_ctx = avformat_alloc_context(); + if (_fmt_ctx == nullptr) { + return W_FAILURE(std::errc::not_enough_memory, + "could not allocate memory for av format context from the url: " + p_url); + } + + DEFER { + if (_fmt_ctx != nullptr) { + // free av format context + avformat_free_context(_fmt_ctx); + _fmt_ctx = nullptr; + } + }); + + // allocate memory for packet + auto _packet = w_av_packet(); + BOOST_LEAF_CHECK(_packet.init()); + + // set options to av format context + BOOST_LEAF_AUTO(_dict, s_set_dict(p_opts)); + + // open input url + int _ret = avformat_open_input(&_fmt_ctx, p_url.c_str(), nullptr, &_dict); + if (_ret < 0) { + return W_FAILURE(std::errc::operation_canceled, + "could not open input url: " + p_url + + " because: " + w_ffmpeg_ctx::get_av_error_str(_ret)); + } + + // find the stream info + _ret = avformat_find_stream_info(_fmt_ctx, nullptr); + if (_ret < 0) { + return W_FAILURE(std::errc::operation_canceled, + "could not find stream info from the url: " + p_url); + } + + if (_fmt_ctx->nb_streams == 0) { + return W_FAILURE(std::errc::operation_canceled, "missing stream for the url: " + p_url); + } + + // search for audio & video stream + const auto _video_stream_index = + av_find_best_stream(_fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0); + const auto _audio_stream_index = + av_find_best_stream(_fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0); + + if (_audio_stream_index < 0 && _video_stream_index < 0) { + return W_FAILURE(std::errc::operation_canceled, + "could not find any video or audio stream from the url: " + p_url); + } + + AVStream *_audio_stream = nullptr; + AVStream *_video_stream = nullptr; + + if (_audio_stream_index >= 0) { + _audio_stream = _fmt_ctx->streams[_audio_stream_index]; + } + if (_video_stream_index >= 0) { + _video_stream = _fmt_ctx->streams[_video_stream_index]; + } + + for (;;) { + // unref packet + _packet.unref(); + // read packet + _ret = av_read_frame(_fmt_ctx, _packet._packet); + if (_ret < 0) { + break; + } + + if (p_on_frame && !p_on_frame(_packet, _audio_stream, _video_stream)) { + break; + } + } + return 0; + } catch (const std::exception &p_exc) { + return W_FAILURE(std::errc::operation_canceled, + "caught an exception: " + std::string(p_exc.what())); + } +} + +#endif // WOLF_MEDIA_FFMPEG \ No newline at end of file diff --git a/wolf/media/ffmpeg/w_ffmpeg.hpp b/wolf/media/ffmpeg/w_ffmpeg.hpp new file mode 100644 index 000000000..f418536f1 --- /dev/null +++ b/wolf/media/ffmpeg/w_ffmpeg.hpp @@ -0,0 +1,105 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#ifdef WOLF_MEDIA_FFMPEG + +#pragma once + +#include "w_av_packet.hpp" +#include "w_ffmpeg_ctx.hpp" +#include "w_encoder.hpp" +#include "w_decoder.hpp" +#include +#include + +namespace wolf::media::ffmpeg { + +struct w_av_codec_opt { + int64_t bitrate; + int fps; + int gop; + int level; + int max_b_frames; + int refs; + int thread_count; +}; + +struct w_av_set_opt { + // name of option + std::string name; + // value type + std::variant value; +}; + +class w_ffmpeg { + public: + /* + * create ffmpeg encoder + * @param p_config, the video config + * @param p_id, the avcodec id + * @param p_codec_opts, the codec settings + * @param p_opts, the codec options + * @returns encoder object on success + */ + W_API static boost::leaf::result create_encoder( + _In_ const w_av_config &p_config, _In_ AVCodecID p_id, + _In_ const w_av_codec_opt &p_codec_opts, + _In_ const std::vector &p_opts = {}) noexcept; + + /* + * create ffmpeg encoder + * @param p_config, the video config + * @param p_id, the avcodec id in string (e.g. "libsvtav1", "libvpx") + * @param p_codec_opts, the codec settings + * @param p_opts, the codec options + * @returns encoder object on success + */ + W_API static boost::leaf::result create_encoder( + _In_ const w_av_config &p_config, _In_ const std::string &p_id, + _In_ const w_av_codec_opt &p_codec_opts, + _In_ const std::vector &p_opts = {}) noexcept; + + /* + * create ffmpeg decoder + * @param p_config, the avconfig + * @param p_id, the avcodec id in string (e.g. "libsvtav1", "libvpx") + * @param p_codec_opts, the codec options + * @param p_opts, the codec options + * @returns encoder object on success + */ + W_API static boost::leaf::result create_decoder( + _In_ const w_av_config &p_config, _In_ AVCodecID p_id, + _In_ const w_av_codec_opt &p_codec_opts, + _In_ const std::vector &p_opts = {}) noexcept; + + /* + * create ffmpeg decoder + * @param p_config, the avconfig + * @param p_id, the avcodec id in string (e.g. "libsvtav1", "libvpx") + * @param p_codec_opts, the codec settings + * @param p_opts, the codec options + * @returns encoder object on success + */ + W_API static boost::leaf::result create_decoder( + _In_ const w_av_config &p_config, _In_ const std::string &p_id, + _In_ const w_av_codec_opt &p_codec_opts, + _In_ const std::vector &p_opts = {}) noexcept; + + /* + * open and receive stream from file or url + * @param p_url, the url + * @param p_opts, the codec options + * @param p_on_frame, on frame data recieved callback + * @returns encoder object on success + */ + W_API static boost::leaf::result open_stream( + _In_ const std::string &p_url, _In_ const std::vector &p_opts, + _In_ const + std::function &p_on_frame) noexcept; +}; +} // namespace wolf::media::ffmpeg + +#endif // WOLF_MEDIA_FFMPEG \ No newline at end of file diff --git a/wolf/media/ffmpeg/w_ffmpeg_ctx.cpp b/wolf/media/ffmpeg/w_ffmpeg_ctx.cpp new file mode 100644 index 000000000..ba168bb09 --- /dev/null +++ b/wolf/media/ffmpeg/w_ffmpeg_ctx.cpp @@ -0,0 +1,40 @@ +#ifdef WOLF_MEDIA_FFMPEG + +#include "w_ffmpeg_ctx.hpp" + +using w_ffmpeg_ctx = wolf::media::ffmpeg::w_ffmpeg_ctx; + +void w_ffmpeg_ctx::_release() noexcept { + if (this->parser != nullptr) { + av_parser_close(this->parser); + this->parser = nullptr; + } + if (this->codec_ctx != nullptr) { + if (avcodec_is_open(this->codec_ctx) > 0) { + avcodec_close(this->codec_ctx); + } + avcodec_free_context(&this->codec_ctx); + this->codec_ctx = nullptr; + } +} + +void w_ffmpeg_ctx::_move(w_ffmpeg_ctx &&p_other) noexcept { + if (this == &p_other) { + return; + } + this->codec_ctx = std::exchange(p_other.codec_ctx, nullptr); + this->codec = std::exchange(p_other.codec, nullptr); + this->parser = std::exchange(p_other.parser, nullptr); +} + +std::string w_ffmpeg_ctx::get_av_error_str(_In_ int p_error_code) noexcept { + std::array _error[] = {'\0'}; + try { + std::ignore = av_make_error_string(_error->data(), W_MAX_PATH, p_error_code); + return std::string(_error->data()); + } catch (...) { + return std::string(); + } +} + +#endif // WOLF_MEDIA_FFMPEG \ No newline at end of file diff --git a/wolf/media/ffmpeg/w_ffmpeg_ctx.hpp b/wolf/media/ffmpeg/w_ffmpeg_ctx.hpp new file mode 100644 index 000000000..b6dd9d9fe --- /dev/null +++ b/wolf/media/ffmpeg/w_ffmpeg_ctx.hpp @@ -0,0 +1,53 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#ifdef WOLF_MEDIA_FFMPEG + +#pragma once + +#include + +extern "C" { +#include +} + +namespace wolf::media::ffmpeg { + +class w_ffmpeg_ctx { + public: + // constructor + W_API w_ffmpeg_ctx() = default; + // destructor + W_API virtual ~w_ffmpeg_ctx() noexcept { _release(); } + + // move constructor. + W_API w_ffmpeg_ctx(w_ffmpeg_ctx &&p_other) noexcept { + _move(std::forward(p_other)); + } + // move assignment operator. + W_API w_ffmpeg_ctx &operator=(w_ffmpeg_ctx &&p_other) noexcept { + _move(std::forward(p_other)); + return *this; + } + + W_API static std::string get_av_error_str(_In_ int p_error_code) noexcept; + + gsl::owner codec_ctx = {}; + gsl::owner codec = {}; + gsl::owner parser = {}; + + private: + // copy constructor + w_ffmpeg_ctx(const w_ffmpeg_ctx &) = delete; + // copy operator + w_ffmpeg_ctx &operator=(const w_ffmpeg_ctx &) = delete; + // release all resources + void _release() noexcept; + // move all resources + void _move(w_ffmpeg_ctx &&p_other) noexcept; +}; +} // namespace wolf::media::ffmpeg + +#endif // WOLF_MEDIA_FFMPEG \ No newline at end of file diff --git a/wolf/media/gst/audio/w_audio_format.cpp b/wolf/media/gst/audio/w_audio_format.cpp new file mode 100644 index 000000000..86458e89b --- /dev/null +++ b/wolf/media/gst/audio/w_audio_format.cpp @@ -0,0 +1 @@ +#include "media/gst/audio/w_audio_format.hpp" diff --git a/wolf/media/gst/audio/w_audio_format.hpp b/wolf/media/gst/audio/w_audio_format.hpp new file mode 100644 index 000000000..43bdfbb63 --- /dev/null +++ b/wolf/media/gst/audio/w_audio_format.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +namespace wolf::media::gst { + +/** + * enum same as GstAudioFormat. + */ +enum class w_audio_format +{ + Unknonw = GST_AUDIO_FORMAT_UNKNOWN, + Encoded = GST_AUDIO_FORMAT_ENCODED, + + S8 = GST_AUDIO_FORMAT_S8, + S16 = GST_AUDIO_FORMAT_S16, + S16LE = GST_AUDIO_FORMAT_S16LE, + S16BE = GST_AUDIO_FORMAT_S16BE, + S32 = GST_AUDIO_FORMAT_S32, + S32LE = GST_AUDIO_FORMAT_S32LE, + S32BE = GST_AUDIO_FORMAT_S32BE, + + U8 = GST_AUDIO_FORMAT_U8, + U16 = GST_AUDIO_FORMAT_U16, + U16LE = GST_AUDIO_FORMAT_U16LE, + U16BE = GST_AUDIO_FORMAT_U16BE, + U32 = GST_AUDIO_FORMAT_U32, + U32LE = GST_AUDIO_FORMAT_U32LE, + U32BE = GST_AUDIO_FORMAT_U32BE, + + F32 = GST_AUDIO_FORMAT_F32, + F32LE = GST_AUDIO_FORMAT_F32LE, + F32BE = GST_AUDIO_FORMAT_F32BE, + F64 = GST_AUDIO_FORMAT_F32, + F64LE = GST_AUDIO_FORMAT_F64LE, + F64BE = GST_AUDIO_FORMAT_F64BE +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/audio/w_audio_info.cpp b/wolf/media/gst/audio/w_audio_info.cpp new file mode 100644 index 000000000..5a5f5f09c --- /dev/null +++ b/wolf/media/gst/audio/w_audio_info.cpp @@ -0,0 +1,25 @@ +#include "media/gst/audio/w_audio_info.hpp" + +namespace wolf::media::gst { + +auto w_audio_info::make(w_audio_format p_format, size_t p_channels, size_t p_samples) + -> boost::leaf::result +{ + auto audioinfo_raw = gst_audio_info_new(); + if (!audioinfo_raw) { + return W_FAILURE(std::errc::operation_canceled, "couldn't create audio info."); + } + + auto format_raw = static_cast(p_format); + gst_audio_info_set_format( + audioinfo_raw, + format_raw, + gsl::narrow_cast(p_samples), + gsl::narrow_cast(p_channels), + nullptr + ); + + return w_audio_info(internal::w_raw_tag{}, audioinfo_raw); +} + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/audio/w_audio_info.hpp b/wolf/media/gst/audio/w_audio_info.hpp new file mode 100644 index 000000000..ec2994223 --- /dev/null +++ b/wolf/media/gst/audio/w_audio_info.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include "wolf.hpp" + +#include "media/gst/internal/w_common.hpp" +#include "media/gst/internal/w_wrapper.hpp" +#include "media/gst/core/w_caps.hpp" +#include "media/gst/audio/w_audio_format.hpp" + +#include + +#include +#include + +namespace wolf::media::gst { + +/** + * wrapper of GstAudioInfo. + */ +class w_audio_info : public w_wrapper +{ + friend class internal::w_raw_access; + +public: + /** + * @brief make a w_audio_info instance with given format and other info. + * @return a constructed audio info on success. + */ + [[nodiscard]] static auto make(w_audio_format p_format, + std::size_t p_channels = 2, + std::size_t p_samples = 44100) + -> boost::leaf::result; + + /** + * @brief make a w_caps with info values. + */ + [[nodiscard]] w_caps to_caps() + { + auto caps_raw = gst_audio_info_to_caps(raw()); + return internal::w_raw_access::from_raw(caps_raw); + } + + /** + * @brief set sample rate. + * @param p_rate sample rate. + */ + void set_rate(std::size_t p_rate) + { + raw()->rate = gsl::narrow_cast(p_rate); + } + + /** + * @brief set channels. + */ + void set_channels(std::size_t p_channels) + { + raw()->channels = gsl::narrow_cast(p_channels); + } + +private: + w_audio_info(internal::w_raw_tag, GstAudioInfo* p_rawptr) noexcept + : w_wrapper(p_rawptr) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_buffer.cpp b/wolf/media/gst/core/w_buffer.cpp new file mode 100644 index 000000000..75089a79b --- /dev/null +++ b/wolf/media/gst/core/w_buffer.cpp @@ -0,0 +1,18 @@ +#include "media/gst/core/w_buffer.hpp" + +#include "media/gst/internal/w_common.hpp" + +namespace wolf::media::gst { + +auto w_buffer::make(size_t p_size) -> boost::leaf::result +{ + auto buffer_raw = gst_buffer_new_and_alloc(p_size); + if (!buffer_raw) { + return W_FAILURE(std::errc::operation_canceled, + "couldn't create and allocate Buffer."); + } + + return w_buffer(internal::w_raw_tag{}, buffer_raw); +} + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_buffer.hpp b/wolf/media/gst/core/w_buffer.hpp new file mode 100644 index 000000000..4a264b032 --- /dev/null +++ b/wolf/media/gst/core/w_buffer.hpp @@ -0,0 +1,192 @@ +#pragma once + +#include "wolf.hpp" + +#include "media/gst/internal/w_wrapper.hpp" + +#include + +#include +#include + +namespace wolf::media::gst { + +/** + * @brief read/write map flags. + */ +enum class w_buffer_map_flags +{ + Read = GST_MAP_READ, + Write = GST_MAP_WRITE, + ReadWrite = GST_MAP_READWRITE +}; + +class w_buffer; + +/** + * helper class to map underlying data of w_buffer + * to an accessible memory region and unmap on destruction. + */ +class w_buffer_mapped_data +{ + friend class w_buffer; + +public: + w_buffer_mapped_data(const w_buffer_mapped_data&) = delete; + w_buffer_mapped_data(w_buffer_mapped_data&& p_other) noexcept + : _buffer(std::exchange(p_other._buffer, nullptr)) + , _map(std::exchange(p_other._map, GstMapInfo{})) + {} + + w_buffer_mapped_data& operator=(const w_buffer_mapped_data&) = delete; + w_buffer_mapped_data& operator=(w_buffer_mapped_data&& p_other) noexcept + { + std::swap(_buffer, p_other._buffer); + std::swap(_map, p_other._map); + return *this; + } + + ~w_buffer_mapped_data() noexcept + { + if (_buffer) { + gst_buffer_unmap(_buffer, &_map); + } + } + + [[nodiscard]] std::size_t size() const noexcept { return _map.size; } + + [[nodiscard]] guint8* begin() noexcept { return _map.data; } + [[nodiscard]] const guint8* begin() const noexcept { return _map.data; } + + [[nodiscard]] guint8* end() noexcept { return _map.data + _map.size; } + [[nodiscard]] const guint8* end() const noexcept { return _map.data + _map.size; } + + [[nodiscard]] guint8* data() noexcept { return _map.data; } + [[nodiscard]] const guint8* data() const noexcept { return _map.data; } + + [[nodiscard]] guint8& operator[](std::size_t p_index) { return _map.data[p_index]; } + [[nodiscard]] const guint8& operator[](std::size_t p_index) const { return _map.data[p_index]; } + +private: + w_buffer_mapped_data() noexcept {} + + /** + * @brief helper method to map raw buffer to a normal accessible data region. + * @param p_buffer raw gstreamer buffer. + * @param p_flags read/write flags. defaults to read-write. + */ + [[nodiscard]] static auto make(internal::w_raw_tag, + GstBuffer* p_buffer, + w_buffer_map_flags p_flags = w_buffer_map_flags::ReadWrite) + -> boost::leaf::result + { + auto ret = w_buffer_mapped_data(); + + auto flags_raw = static_cast(p_flags); + if (!p_buffer || !gst_buffer_map(p_buffer, &ret._map, flags_raw)) { + return W_FAILURE(std::errc::operation_canceled, "couldn't map the buffer data."); + } + + ret._buffer = p_buffer; + + return ret; + } + + GstBuffer* _buffer = nullptr; + GstMapInfo _map{}; +}; + +/** + * wrapper of GstBuffer. + */ +class w_buffer : public w_wrapper +{ + friend class internal::w_raw_access; + +public: + /** + * @brief make a buffer with given size. + * @return buffer on success. + */ + [[nodiscard]] static auto make(std::size_t p_size) -> boost::leaf::result; + + /** + * @brief get timestamp in nanoseconds. + */ + [[nodiscard]] auto get_timestamp() const noexcept + { + return GST_BUFFER_TIMESTAMP(raw()); + } + + /** + * @brief get duration of this buffer's playback in nanoseconds. + */ + [[nodiscard]] auto get_duration() const noexcept + { + return raw()->duration; + } + + /** + * @brief set timestamp in nanoseconds. + */ + void set_timestamp(std::size_t p_nanoseconds) noexcept + { + GST_BUFFER_TIMESTAMP(raw()) = static_cast(p_nanoseconds); + } + + /** + * @brief set duration of this buffer's playback in nanoseconds. + */ + void set_duration(std::size_t p_nanoseconds) noexcept + { + raw()->duration = static_cast(p_nanoseconds); + } + + /** + * @brief map to write-only data region. + */ + [[nodiscard]] auto map_data_write() + { + return w_buffer_mapped_data::make( + internal::w_raw_tag{}, + raw(), + w_buffer_map_flags::Write + ); + } + + /** + * @brief map to read-only data region. + */ + [[nodiscard]] auto map_data_read() const + { + // NOTE unfortunately raw methods need pointer to non-const data, thus const_cast. + return w_buffer_mapped_data::make( + internal::w_raw_tag{}, + const_cast(raw()), + w_buffer_map_flags::Read + ); + } + + /** + * @brief map to readable-writable data region. + */ + [[nodiscard]] auto map_data() + { + return w_buffer_mapped_data::make(internal::w_raw_tag{}, raw()); + } + + /** + * @brief map to readable data region. + */ + [[nodiscard]] auto map_data() const + { + return map_data_read(); + } + +private: + explicit w_buffer(internal::w_raw_tag, GstBuffer* p_buffer) noexcept + : w_wrapper(p_buffer) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_bus.cpp b/wolf/media/gst/core/w_bus.cpp new file mode 100644 index 000000000..6dfb9a1b3 --- /dev/null +++ b/wolf/media/gst/core/w_bus.cpp @@ -0,0 +1 @@ +#include "media/gst/core/w_bus.hpp" diff --git a/wolf/media/gst/core/w_bus.hpp b/wolf/media/gst/core/w_bus.hpp new file mode 100644 index 000000000..00227882c --- /dev/null +++ b/wolf/media/gst/core/w_bus.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include "media/gst/core/w_signal_handler.hpp" +#include "media/gst/core/w_message.hpp" +#include "media/gst/internal/w_wrapper.hpp" + +#include + +namespace wolf::media::gst { + +/** + * @brief wrapper of GstBus. a bus to send messages to and notify listeners. + */ +class w_bus : public w_wrapper +{ + friend class internal::w_raw_access; + +public: + w_bus() = delete; + + w_bus(const w_bus&) = delete; + w_bus(w_bus&&) noexcept = default; + + w_bus& operator=(const w_bus&) = delete; + w_bus& operator=(w_bus&&) noexcept = default; + + ~w_bus() noexcept + { + for (auto i = 0u; i < _watch_count; ++i) { + gst_bus_remove_signal_watch(raw()); + } + } + + /** + * @brief hook a listener handler on message signal. + * @param p_handler message listener handler. it will be passed `w_nonowning`. + */ + template + void hook_message(F&& p_handler) + { + ensure_watch(); + constexpr auto invoker = +[](GstBus* /* p_self */, GstMessage* p_message, gpointer p_callee) { + auto& func = (*static_cast(p_callee)); + func(internal::w_raw_access::from_raw>(p_message)); + }; + _sighandlers.message.hook("message", invoker, std::forward(p_handler)); + } + + /** + * @brief unhook the message listener handler on message signal. + */ + void unhook_message() + { + if (_sighandlers.message.unhook()) { + gst_bus_remove_signal_watch(raw()); + --_watch_count; + } + } + +private: + explicit w_bus(internal::w_raw_tag, GstBus* p_bus_raw) noexcept + : w_wrapper(p_bus_raw) + , _sighandlers(G_OBJECT(p_bus_raw)) + {} + + /** + * @brief make sure there is at least one signal watch. + */ + void ensure_watch() noexcept + { + if (_watch_count == 0) { + watch(); + } + } + + /** + * @brief add a signal watch so a listener can be added. + */ + void watch() noexcept + { + gst_bus_add_signal_watch(raw()); + ++_watch_count; + } + + // signals + struct signal_handler_set { + w_signal_handler> message; + + signal_handler_set(auto* p_rawptr) : message(p_rawptr) {} + } _sighandlers; + + std::size_t _watch_count = 0; +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_caps.cpp b/wolf/media/gst/core/w_caps.cpp new file mode 100644 index 000000000..2fcfeef1a --- /dev/null +++ b/wolf/media/gst/core/w_caps.cpp @@ -0,0 +1 @@ +#include "media/gst/core/w_caps.hpp" diff --git a/wolf/media/gst/core/w_caps.hpp b/wolf/media/gst/core/w_caps.hpp new file mode 100644 index 000000000..b560ea9a0 --- /dev/null +++ b/wolf/media/gst/core/w_caps.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include "wolf.hpp" + +#include "media/gst/internal/w_common.hpp" +#include "media/gst/internal/w_wrapper.hpp" +#include "media/gst/core/w_structure.hpp" + +#include + +#include + +namespace wolf::media::gst { + +/** + * @brief wrapper of GstCaps, gstreamer capabilities concept. + */ +class w_caps : public w_wrapper +{ + friend class internal::w_raw_access; + +public: + /** + * @brief make an empty caps. + * @return an instance of w_caps on success. + */ + [[nodiscard]] static auto make() -> boost::leaf::result + { + auto caps_raw = gst_caps_new_empty(); + if (!caps_raw) { + return W_FAILURE(std::errc::operation_canceled, + "couldn't make caps."); + } + + return w_caps(internal::w_raw_tag{}, caps_raw); + } + + /** + * @brief make a caps that acceps any kind of media. + * @return an instance of w_caps on success. + */ + [[nodiscard]] static auto make_any() -> boost::leaf::result + { + auto caps_raw = gst_caps_new_any(); + if (!caps_raw) { + return W_FAILURE(std::errc::operation_canceled, + "couldn't make caps as any."); + } + + return w_caps(internal::w_raw_tag{}, caps_raw); + } + + /** + * @brief make an empty caps with media name. + * @return an instance of w_caps on success. + */ + [[nodiscard]] static auto make_simple(const char* p_media_name) + -> boost::leaf::result + { + auto caps_raw = gst_caps_new_empty_simple(p_media_name); + if (!caps_raw) { + return W_FAILURE( + std::errc::operation_canceled, + "couldn't make caps with given media name." + ); + } + + return w_caps(internal::w_raw_tag{}, caps_raw); + } + + /** + * @brief add a capability structure. + * @param p_structure a capability structure. + */ + void add(w_structure&& p_structure) + { + gst_caps_append_structure(raw(), internal::w_raw_access::disown_raw(p_structure)); + } + +private: + w_caps(internal::w_raw_tag, GstCaps* p_caps_raw) noexcept + : w_wrapper(p_caps_raw) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_clock.cpp b/wolf/media/gst/core/w_clock.cpp new file mode 100644 index 000000000..b11285840 --- /dev/null +++ b/wolf/media/gst/core/w_clock.cpp @@ -0,0 +1 @@ +#include "media/gst/core/w_clock.hpp" diff --git a/wolf/media/gst/core/w_clock.hpp b/wolf/media/gst/core/w_clock.hpp new file mode 100644 index 000000000..6b384c146 --- /dev/null +++ b/wolf/media/gst/core/w_clock.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "media/gst/internal/w_wrapper.hpp" + +#include + +namespace wolf::media::gst { + +/** + * @brief wrapper of GstClock. + * + * @note this class is not user constructible. it's nonowning by default. + */ +class w_clock : public w_wrapper +{ + friend class internal::w_raw_access; + +public: + w_clock() = delete; + + /** + * @brief get time in nanoseconds. + * @return time in nanoseconds. + */ + std::size_t get_time() noexcept + { + return gst_clock_get_time(raw()); + } + +private: + explicit w_clock(internal::w_raw_tag, GstClock* p_clock_raw) noexcept + : w_wrapper(p_clock_raw) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_element.cpp b/wolf/media/gst/core/w_element.cpp new file mode 100644 index 000000000..3ae806735 --- /dev/null +++ b/wolf/media/gst/core/w_element.cpp @@ -0,0 +1,29 @@ +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +auto w_element::make(const char *p_factory_name) -> boost::leaf::result +{ + auto element_factory_raw = gst_element_factory_find(p_factory_name); + if (!element_factory_raw) { + auto err_msg = wolf::format("couldn't find factory name: {}", p_factory_name); + return W_FAILURE(std::errc::operation_canceled, err_msg); + } + + auto element_raw = gst_element_factory_create(element_factory_raw, nullptr); + if (!element_raw) { + auto err_msg = wolf::format( + "couldn't create the element with given factory name: {}", + p_factory_name + ); + return W_FAILURE(std::errc::operation_canceled, err_msg); + } + + if (G_IS_INITIALLY_UNOWNED(element_raw)) { + gst_object_ref_sink(element_raw); + } + + return w_element(internal::w_raw_tag{}, element_raw); +} + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_element.hpp b/wolf/media/gst/core/w_element.hpp new file mode 100644 index 000000000..ce80116ac --- /dev/null +++ b/wolf/media/gst/core/w_element.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include "wolf.hpp" + +#include "media/gst/internal/w_common.hpp" +#include "media/gst/internal/w_wrapper.hpp" +#include "media/gst/core/w_pad.hpp" +#include "media/gst/core/w_clock.hpp" + +#include + +#include + +namespace wolf::media::gst { + +/** + * @brief wrapper of GstElement, gstreamer elements process stream data. + * + * elements combine to make dataflow pipeline graphs (DAG) + * to process one or more media stream(s). + */ +class w_element : public w_wrapper +{ + friend class internal::w_raw_access; + +public: + /** + * @brief make an element by given factory name. + * @param p_factory_name element's factory name. + * @return an instance of element of given name on success. + */ + [[nodiscard]] static auto make(const char* p_factory_name) + -> boost::leaf::result; + + w_element(const w_element&) = delete; + w_element(w_element&&) noexcept = default; + + w_element& operator=(const w_element&) = delete; + w_element& operator=(w_element&&) noexcept = default; + + virtual ~w_element() = default; + + /** + * @brief get a request=pad with given name. + */ + [[nodiscard]] auto request_pad(const char* p_pad_name) + -> boost::leaf::result + { + auto raw_pad = gst_element_request_pad_simple(raw(), p_pad_name); + if (!raw_pad) { + auto err_msg = wolf::format("request for pad `{}` failed.", p_pad_name); + return W_FAILURE(std::errc::operation_canceled, err_msg); + } + return internal::w_raw_access::from_raw(raw_pad); + } + + /** + * @brief get an always=pad with given name. + */ + [[nodiscard]] auto always_pad(const char* p_pad_name) + -> boost::leaf::result + { + auto raw_pad = gst_element_get_static_pad(raw(), p_pad_name); + if (!raw_pad) { + auto err_msg = wolf::format("couldn't find always pad: `{}`", p_pad_name); + return W_FAILURE(std::errc::operation_canceled, err_msg); + } + return internal::w_raw_access::from_raw(raw_pad); + } + + /** + * @brief get element's clock. + */ + w_nonowning clock() noexcept + { + return internal::w_raw_access::from_raw>(raw()->clock); + } + +private: + explicit w_element(internal::w_raw_tag, GstElement* p_element) noexcept + : w_wrapper(p_element) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_element_factory.cpp b/wolf/media/gst/core/w_element_factory.cpp new file mode 100644 index 000000000..60cf086a7 --- /dev/null +++ b/wolf/media/gst/core/w_element_factory.cpp @@ -0,0 +1 @@ +#include "media/gst/core/w_element_factory.hpp" diff --git a/wolf/media/gst/core/w_element_factory.hpp b/wolf/media/gst/core/w_element_factory.hpp new file mode 100644 index 000000000..2da45f481 --- /dev/null +++ b/wolf/media/gst/core/w_element_factory.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "media/gst/internal/w_common.hpp" +#include "media/gst/core/w_element.hpp" + +#include + +#include +#include +#include + +namespace wolf::media::gst { + +/** + * @brief gstreamer's element factory to create elements. + */ +class w_element_factory +{ +public: + w_element_factory() = delete; + + /** + * @brief make an element from given gstreamer launch command. + * @param p_launch_str a gstreamer lauch command. + */ + [[nodiscard]] static auto make_from_launch_str(const char* p_launch_str) + -> boost::leaf::result + { + auto element_raw = gst_parse_launch(p_launch_str, nullptr); + if (!element_raw) { + return W_FAILURE(std::errc::operation_canceled, + "couldn't create element from launch string."); + } + + return internal::w_raw_access::from_raw(element_raw); + } + + /** + * @brief make a an element of given factory name, with given element name. + * @param p_factory_name name of element module. + * @param p_element_name custom name for element to reference later. (nullable) + * @return an element on success. + */ + [[nodiscard]] static auto make_simple(const char* p_factory_name, const char* p_element_name) + -> boost::leaf::result + { + auto element_raw = gst_element_factory_make(p_factory_name, p_element_name); + if (!element_raw) { + auto err_msg = wolf::format("couldn't create element with factory name: {}", p_factory_name); + return W_FAILURE(std::errc::operation_canceled, err_msg); + } + return internal::w_raw_access::from_raw(element_raw); + } +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_format.cpp b/wolf/media/gst/core/w_format.cpp new file mode 100644 index 000000000..75a7e6775 --- /dev/null +++ b/wolf/media/gst/core/w_format.cpp @@ -0,0 +1 @@ +#include "media/gst/core/w_format.hpp" diff --git a/wolf/media/gst/core/w_format.hpp b/wolf/media/gst/core/w_format.hpp new file mode 100644 index 000000000..35b88edd8 --- /dev/null +++ b/wolf/media/gst/core/w_format.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace wolf::media::gst { + +/** wrapper of GstFormat */ +enum class w_format +{ + Undefined = GST_FORMAT_UNDEFINED, + Default = GST_FORMAT_DEFAULT, + Bytes = GST_FORMAT_BYTES, + Time = GST_FORMAT_TIME, + Buffers = GST_FORMAT_BUFFERS, + Percent = GST_FORMAT_PERCENT +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_mainloop.cpp b/wolf/media/gst/core/w_mainloop.cpp new file mode 100644 index 000000000..8082386b3 --- /dev/null +++ b/wolf/media/gst/core/w_mainloop.cpp @@ -0,0 +1 @@ +#include "media/gst/core/w_mainloop.hpp" diff --git a/wolf/media/gst/core/w_mainloop.hpp b/wolf/media/gst/core/w_mainloop.hpp new file mode 100644 index 000000000..f07bf5a20 --- /dev/null +++ b/wolf/media/gst/core/w_mainloop.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include "wolf.hpp" + +#include "media/gst/internal/w_wrapper.hpp" + +#include + +#include +#include +#include +#include + +namespace wolf::media::gst { + +/** + * @brief wrapper of GMainLoop, GLib's mainloop/eventloop facility. + */ +class w_mainloop : public w_wrapper +{ +public: + /** + * @brief create a simple instance of mainloop. + * @return a w_mainloop instance on success. + */ + [[nodiscard]] static auto make() -> boost::leaf::result + { + auto main_loop_raw = g_main_loop_new(nullptr, false); + if (!main_loop_raw) { + return W_FAILURE(std::errc::operation_canceled, + "mainloop construction error."); + } + + return w_mainloop(internal::w_raw_tag{}, main_loop_raw); + } + + /** + * @brief add a callable to be called on event loop idle times. + * @param p_func callable to be called on idle times. + * @return a sourceid to remove it later. + * @note `p_func` must have a consistent lifetime and address + * until either being removed by `idle_remove` or the instance + * of this class be destructed. + */ + template + std::size_t idle_add(F& p_func) + { + constexpr auto invoker = +[](F* p_funcptr) { (*p_funcptr)(); }; + return g_idle_add(GSourceFunc(invoker), std::addressof(p_func)); + } + + bool idle_remove(std::size_t p_sourceid) + { + if (!p_sourceid) { + return false; + } + + return g_source_remove(gsl::narrow_cast(p_sourceid)); + } + + /** + * @brief run the main/event loop. + */ + void run() + { + g_main_loop_run(raw()); + } + + /** + * @brief stop the main/event loop. + */ + void stop() + { + g_main_loop_quit(raw()); + } + +private: + w_mainloop(internal::w_raw_tag, GMainLoop* p_main_loop_raw) noexcept + : w_wrapper(p_main_loop_raw) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_message.cpp b/wolf/media/gst/core/w_message.cpp new file mode 100644 index 000000000..8d892575d --- /dev/null +++ b/wolf/media/gst/core/w_message.cpp @@ -0,0 +1 @@ +#include "media/gst/core/w_message.hpp" diff --git a/wolf/media/gst/core/w_message.hpp b/wolf/media/gst/core/w_message.hpp new file mode 100644 index 000000000..5ddd29c76 --- /dev/null +++ b/wolf/media/gst/core/w_message.hpp @@ -0,0 +1,143 @@ +#pragma once + +#include "media/gst/internal/w_common.hpp" +#include "media/gst/internal/w_wrapper.hpp" + +#include + +#include + +namespace wolf::media::gst { + +class w_message; + +/** type of GstMessage */ +enum class w_message_type : std::size_t +{ + Unknown = 0, + EOS, + Error, + Warning, + Info +}; + +//- message structs + +/** the unknown variant of GstMessage */ +struct w_message_unknown {}; + +/** the end-of-stream variant of GstMessage */ +struct w_message_eos {}; + +/** the error message variant of GstMessage */ +struct w_message_error +{ + friend class w_message; + +public: + w_message_error() = delete; + + w_message_error(const w_message_error&) = delete; + + ~w_message_error() noexcept + { + if (_error) { + g_clear_error(&_error); + } + + if (_debug_info) { + g_free(_debug_info); + } + } + + /** + * @brief print the error message to standard output. + */ + void print() const + { + g_printerr( + "Error received from element %s: %s\n", + GST_OBJECT_NAME(_msg->src), + _error->message + ); + g_printerr( + "Debugging information: %s\n", + _debug_info ? _debug_info : "none" + ); + } + +private: + /** + * @brief parse the message to extract error info. + */ + explicit w_message_error(internal::w_raw_tag, GstMessage* p_msg_raw) + : _msg(p_msg_raw) + { + gst_message_parse_error(p_msg_raw, &_error, &_debug_info); + } + + GstMessage* _msg = nullptr; //< non-owning. + GError* _error = nullptr; + gchar* _debug_info = nullptr; +}; + +/** + * @brief wrapper of GstMessage. gstreamer's ultimate message struct. + * + * there are so many different kinds and variants of message, + * and all are represented by w_message. + * + * for ease of use, there are helper message representative classes, + * which on visit the appropriate one will be created and passed to + * given visitor. + */ +class w_message : public w_wrapper +{ + friend class internal::w_raw_access; + +public: + // not supported yet. + w_message() = delete; + +// w_message_type type() const noexcept +// { +// return convert_message_type(GST_MESSAGE_TYPE(msg_)); +// } + + /** + * @brief visit the message based on its type as the helper representative type. + * @param p_visitor visitor to visit message variant. + */ + template + auto visit(VisitorF&& p_visitor) + { + return raw_visit(std::forward(p_visitor), raw()); + } + +private: + explicit w_message(internal::w_raw_tag, GstMessage* p_msg_raw) + : w_wrapper(p_msg_raw) + {} + + /** + * @brief create and visit appropriate repr variant by given raw message and visitor. + * @param p_visitor visitor to visit message variant. + * @param p_msg_raw raw message pointer. + */ + template + static auto raw_visit(VisitorF&& p_visitor, GstMessage* p_msg_raw) + { + switch (GST_MESSAGE_TYPE(p_msg_raw)) { + case GST_MESSAGE_EOS: + return std::forward(w_message_eos{}); + case GST_MESSAGE_ERROR: + return std::forward(p_visitor)( + w_message_error(internal::w_raw_tag{}, p_msg_raw) + ); + default: // GST_MESSAGE_UNKNOWN + return std::forward(p_visitor)(w_message_unknown{}); + } + } +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_nonowning.cpp b/wolf/media/gst/core/w_nonowning.cpp new file mode 100644 index 000000000..0f4014720 --- /dev/null +++ b/wolf/media/gst/core/w_nonowning.cpp @@ -0,0 +1 @@ +#include "media/gst/core/w_nonowning.hpp" diff --git a/wolf/media/gst/core/w_nonowning.hpp b/wolf/media/gst/core/w_nonowning.hpp new file mode 100644 index 000000000..d2d14fe09 --- /dev/null +++ b/wolf/media/gst/core/w_nonowning.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include "media/gst/internal/w_common.hpp" + +#include + +namespace wolf::media::gst { + +/** + * @brief nonowning view of `T` pointing to same resource + * given to make nonowning view of. similar to std::string_view/std::span. + * + * it's for sharing things like element.clock() returning Clock instance, + * which the instance does not own the clock's lifetime, but + * points to same resource to keep track of. + * + * this class is wrapper acting as a pointer to an instance of `T` + * with shared resource beneath. + * + * @tparam T underlying type of nonowning view. + * + * @note make sure the given object lives longer than this nonowning view of it, + * or any access will be undefined behavior. + * similar to std::string_view or std::span. + */ +template +class w_nonowning +{ + friend class internal::w_raw_access; + + using value_type = std::remove_cvref_t; + +public: + /** + * @brief implicit constructor from a value of wrapping type. + * @param p_value value to make nonowing view of. + */ + w_nonowning(value_type& p_value) + : w_nonowning(internal::w_raw_access::raw(p_value)) + {} + + w_nonowning(const w_nonowning&) = default; + w_nonowning(w_nonowning&&) noexcept = default; + + w_nonowning& operator=(const w_nonowning&) = default; + w_nonowning& operator=(w_nonowning&&) noexcept = default; + + ~w_nonowning() noexcept + { + // disown raw resource before resource + // be released/free'ed by T's destructor. + internal::w_raw_access::disown_raw(_value); + } + + value_type* operator->() noexcept { return std::addressof(_value); } + const value_type* operator->() const noexcept { return std::addressof(_value); } + + value_type& operator*() noexcept { return _value; } + const value_type& operator*() const noexcept { return _value; } + +private: + template + w_nonowning(internal::w_raw_tag, RawT* p_rawptr) noexcept + : _value(internal::w_raw_access::from_raw(p_rawptr)) + {} + + value_type _value; +}; + + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_pad.cpp b/wolf/media/gst/core/w_pad.cpp new file mode 100644 index 000000000..fe5335021 --- /dev/null +++ b/wolf/media/gst/core/w_pad.cpp @@ -0,0 +1 @@ +#include "media/gst/core/w_pad.hpp" diff --git a/wolf/media/gst/core/w_pad.hpp b/wolf/media/gst/core/w_pad.hpp new file mode 100644 index 000000000..bd7f8958b --- /dev/null +++ b/wolf/media/gst/core/w_pad.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "media/gst/internal/w_common.hpp" +#include "media/gst/internal/w_wrapper.hpp" + +#include + +#include + +namespace wolf::media::gst { + +/** gstreamer pad availablity */ +enum class w_availability +{ + Always, + Request, + Sometimes +}; + +/** + * @brief wrapper of GstPad. + * + * each gstreamer element has source and/or sink pads, + * which they connect by to each other with. + */ +class w_pad : public w_wrapper +{ + friend class internal::w_raw_access; + +public: + // so far nothing... + +private: + explicit w_pad(internal::w_raw_tag, GstPad* p_pad) noexcept + : w_wrapper(p_pad) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_pipeline.cpp b/wolf/media/gst/core/w_pipeline.cpp new file mode 100644 index 000000000..681ec3850 --- /dev/null +++ b/wolf/media/gst/core/w_pipeline.cpp @@ -0,0 +1,51 @@ +#include "media/gst/core/w_pipeline.hpp" + +namespace wolf::media::gst { + +auto w_pipeline::make(const char *p_name) + -> boost::leaf::result +{ + auto pipeline_raw = gst_pipeline_new(p_name); + if (!pipeline_raw) { + return W_FAILURE(std::errc::operation_canceled, + wolf::format("couldn't create pipeline: {}", p_name)); + } + return w_pipeline(internal::w_raw_tag{}, pipeline_raw); +} + +auto w_pipeline::get_bus() -> boost::leaf::result +{ + auto bus_raw = gst_element_get_bus(raw()); + if (!bus_raw) { + return W_FAILURE(std::errc::operation_canceled, + "couldn't get pipeline's bus."); + } + return internal::w_raw_access::from_raw(bus_raw); +} + +bool w_pipeline::bin(w_flow_path &p_flow) +{ + for (auto& element : p_flow) { + if (!bin(*element)) { + return false; + } + } + + return true; +} + +bool w_pipeline::link(w_flow_path &p_flow) +{ + auto* last = p_flow.first().get(); + for (int i = 1; i < p_flow.size(); ++i) { + if (!link(*last, *p_flow[i])) { + return false; + } + + last = p_flow[i].get(); + } + + return true; +} + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_pipeline.hpp b/wolf/media/gst/core/w_pipeline.hpp new file mode 100644 index 000000000..18c7a6699 --- /dev/null +++ b/wolf/media/gst/core/w_pipeline.hpp @@ -0,0 +1,144 @@ +#pragma once + +#include "wolf.hpp" + +#include "media/gst/w_flow.hpp" +#include "media/gst/core/w_element.hpp" +#include "media/gst/core/w_bus.hpp" +#include "media/gst/internal/w_wrapper.hpp" + +#include + +#include +#include + +namespace wolf::media::gst { + +/** + * @brief wrapper of GstElement, providing gstreamer pipeline concept. + */ +class w_pipeline : public w_wrapper +{ +public: + /** + * @brief make an instance of pipeline with given name. + * @param p_name pipeline's name. + * @return an instance of pipeline on success. + */ + [[nodiscard]] static auto make(const char* p_name) + -> boost::leaf::result; + + /** + * @brief get pipeline's bus. + */ + [[nodiscard]] auto get_bus() -> boost::leaf::result; + + /** + * @brief add given elements to pipeline's bin. + * @param p_args pack of elements. + * @return boolean indicating success or failure. + */ + template + requires (sizeof...(Args) > 1) + bool bin(Args&& ...p_args) + { + return (true && ... && bin(std::forward(p_args))); + } + + /** + * @brief add elements from given flow path to pipeline's bin. + * @param p_flow elements flow path. + * @return boolean indicating success or failure. + */ + bool bin(w_flow_path& p_flow); + + /** + * @brief add given single element to pipeline's bin. + * @param p_element single element. + * @return boolean indicating success or failure. + */ + bool bin(w_element& p_element) + { + auto element_raw = internal::w_raw_access::raw(p_element); + p_element.parented(); + return gst_bin_add(GST_BIN(raw()), element_raw); + } + + /** + * @brief link a pack of given linkables (element or pad) together after each other. + * @param p_a source linkable to link with `p_b`. + * @param p_b sink linkable to link with `p_a`. + * @param p_rest pack of rest of linkables to be linked after `p_b` as source. + * @return boolean indicating success or failure. + */ + template + bool link(T&& p_a, U&& p_b, Rest&& ...p_rest) + { + auto raw_a = internal::w_raw_access::raw(p_a); + auto raw_b = internal::w_raw_access::raw(p_b); + + if (!gst_element_link(raw_a, raw_b)) { + return false; + } + + if constexpr (sizeof...(Rest) > 0) { + return link(std::forward(p_b), std::forward(p_rest)...); + } + + return true; + } + + /** + * @brief link two elements. + * @param p_src source element. + * @param p_sink sink element. + * @return boolean indicating success or failure. + */ + bool link(w_element& p_src, w_element& p_sink) + { + return gst_element_link( + internal::w_raw_access::raw(p_src), + internal::w_raw_access::raw(p_sink) + ); + } + + /** + * @brief link a flow path's elements after each other. + * @param p_flow flow path set of elements. + * @return boolean indicating success or failure. + */ + bool link(w_flow_path& p_flow); + + /** + * @brief set the pipeline to play state. + * @return boolean indicating success or failure. + */ + bool play() + { + return static_cast(gst_element_set_state(raw(), GST_STATE_PLAYING)); + } + + /** + * @brief set the pipeline to pause state. + * @return boolean indicating success or failure. + */ + bool pause() + { + return static_cast(gst_element_set_state(raw(), GST_STATE_PAUSED)); + } + + /** + * @brief set the pipeline to stop state. + * @return boolean indicating success or failure. + */ + bool stop() { + return static_cast(gst_element_set_state(raw(), GST_STATE_NULL)); + } + +private: + w_pipeline(internal::w_raw_tag, GstElement* pipeline_raw) noexcept + : w_wrapper(pipeline_raw) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_refptr.cpp b/wolf/media/gst/core/w_refptr.cpp new file mode 100644 index 000000000..57c996b5e --- /dev/null +++ b/wolf/media/gst/core/w_refptr.cpp @@ -0,0 +1 @@ +#include "media/gst/core/w_refptr.hpp" diff --git a/wolf/media/gst/core/w_refptr.hpp b/wolf/media/gst/core/w_refptr.hpp new file mode 100644 index 000000000..f183ae3f1 --- /dev/null +++ b/wolf/media/gst/core/w_refptr.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include + +#include +#include + +namespace wolf::media::gst { + +/** + * shared ref-counting smart pointer of given `T`. + * + * @tparam T type to wrap shared reference to. + */ +template +using w_refptr = std::shared_ptr; + +/** + * @brief convert given value to a ref-counting smart pointer. + * @param p_value value to make shared. + * @return sharable ref-counting refptr. + */ +template +inline w_refptr to_refptr(T&& p_value) +{ + using type = std::remove_cvref_t; + return std::make_shared(std::forward(p_value)); +} + +/** + * @brief convert given value to a ref-counting smart pointer. + * @param p_value a refptr to increase its ref-count. + * @return another copy of given refptr pointing to same value. + */ +template +inline w_refptr to_refptr(w_refptr p_value) +{ + return p_value; +} + +//template +//inline w_refptr to_refptr(boost::leaf::result p_value) +//{ +// if (!p_value) { +// return w_refptr(nullptr); +// } +// return to_refptr(std::move(*p_value)); +//} + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_signal_handler.cpp b/wolf/media/gst/core/w_signal_handler.cpp new file mode 100644 index 000000000..ce41c0bc5 --- /dev/null +++ b/wolf/media/gst/core/w_signal_handler.cpp @@ -0,0 +1 @@ +#include "media/gst/core/w_signal_handler.hpp" diff --git a/wolf/media/gst/core/w_signal_handler.hpp b/wolf/media/gst/core/w_signal_handler.hpp new file mode 100644 index 000000000..e87d2aa52 --- /dev/null +++ b/wolf/media/gst/core/w_signal_handler.hpp @@ -0,0 +1,123 @@ +#pragma once + +#include "media/gst/internal/w_common.hpp" + +#include + +#include +#include +#include + +namespace wolf::media::gst { + +/** + * @brief internal signal handler to use in signallable wrappers. + * + * it holds the handler and takes ownership of its lifetime, + * and unhook at destruction. + * + * @tparam Args pack of args that will be passed to hooked signal handler. + */ +template +class w_signal_handler +{ +public: + using rawptr_type = GObject*; + using handler_type = std::function; + using handlerid_type = gulong; + + constexpr static auto simple_invoker = + +[](GObject* /* p_instance */, Args ...p_args, void* p_callee) { + (*static_cast(p_callee))(std::move(p_args)...); + }; + + /** + * @brief make signal handler of given gobject instance. + * @param p_rawptr gobject instance to hook/unhook signal on. + */ + explicit w_signal_handler(rawptr_type p_rawptr) + : _rawptr(p_rawptr) + { + // fatal error, this shouldn't be caught but to terminate. + if (!p_rawptr) { + throw std::invalid_argument("given raw pointer to handle signals is null."); + } + } + + w_signal_handler(const w_signal_handler&) = delete; + w_signal_handler(w_signal_handler&&) noexcept = default; + + w_signal_handler& operator=(const w_signal_handler&) = delete; + w_signal_handler& operator=(w_signal_handler&&) noexcept = default; + + ~w_signal_handler() noexcept { unhook(); } + + /** + * @brief hook given handler on given signal name with given invoker. + * @param p_name name of signal to hook. + * @param p_invoker invoker is raw signal handler + * which will invoke given handler by converted args. + * @param p_handler user handler to handle named signal. + * @return boolean indicating successful hook or not. + * @note invoker does raw convertions too. + */ + template F> + bool hook(const char* p_name, auto* p_invoker, F&& p_handler) + { + unhook(); + + auto handlerid = g_signal_connect( + _rawptr, + p_name, + (GCallback)p_invoker, + (void*)std::addressof(_handler) + ); + if (!handlerid) { + return false; + } + + _handler = std::forward(p_handler); + _handlerid = handlerid; + + return true; + } + + /** + * @brief hook given handler on given signal name with given invoker. + * @param p_name name of signal to hook. + * @param p_handler user handler to handle named signal. + * @return boolean indicating successful hook or not. + */ + template F> + bool hook(const char* p_name, F&& p_handler) + { + // simple invoker without type conversion. + return hook(p_name, simple_invoker, std::forward(p_handler)); + } + + /** + * @brief unhook and release the previously hooked handler. + * @return boolean indicating success or failure. + * returns false if nothing was hooked already. + */ + bool unhook() noexcept + { + if (!_handlerid) { + return false; + } + + g_signal_handler_disconnect(_rawptr, _handlerid); + _handlerid = 0; + + _handler = {}; + + return true; + } + +private: + rawptr_type _rawptr = nullptr; //< non-onwing. + handler_type _handler{}; + handlerid_type _handlerid{}; +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_structure.cpp b/wolf/media/gst/core/w_structure.cpp new file mode 100644 index 000000000..4ded46d2a --- /dev/null +++ b/wolf/media/gst/core/w_structure.cpp @@ -0,0 +1,28 @@ +#include "media/gst/core/w_structure.hpp" + +namespace wolf::media::gst { + +auto w_structure::make(const char *p_name) + -> boost::leaf::result +{ + auto structure_raw = gst_structure_new_empty(p_name); + if (!structure_raw) { + return W_FAILURE(std::errc::operation_canceled, + "couldn't create structure."); + } + + return w_structure(internal::w_raw_tag{}, structure_raw); +} + +auto w_structure::make_from_str(const char *p_str) -> boost::leaf::result +{ + auto structure_raw = gst_structure_from_string(p_str, nullptr); + if (!structure_raw) { + return W_FAILURE(std::errc::operation_canceled, + "couldn't create structure from given string repr."); + } + + return w_structure(internal::w_raw_tag{}, structure_raw); +} + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/core/w_structure.hpp b/wolf/media/gst/core/w_structure.hpp new file mode 100644 index 000000000..12122142d --- /dev/null +++ b/wolf/media/gst/core/w_structure.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include "wolf.hpp" + +#include "media/gst/internal/w_wrapper.hpp" + +#include // use specific headers or forward-declare + +#include +#include + +namespace wolf::media::gst { + +struct w_fraction +{ + std::size_t numerator; + std::size_t denomerator = 1; +}; + +/** + * @brief wrapper of GstStructure, commonly used to represent a single gstreamer caps. + */ +class w_structure : public w_wrapper +{ + friend class internal::w_raw_access; + +public: + /** + * @brief make an instance of structure with given name. + * @param p_name name of cap strucutre. + * @return instance of structure on success. + */ + [[nodiscard]] static auto make(const char* p_name) + -> boost::leaf::result; + + /** + * @brief make structure cap from given string representation. + * @param p_str gstreamer string repr of a structure. + * @return instance of structure on success. + */ + [[nodiscard]] static auto make_from_str(const char* p_str) + -> boost::leaf::result; + + /** + * @brief set name/value pairs of feilds. + */ + template + void set_field_pairs(const char* p_fieldname, T&& p_fieldvalue, Rest&& ...p_rest) + { + set_field(p_fieldname, std::forward(p_fieldvalue)); + + if constexpr (sizeof...(Rest) > 0) { + set_field_pairs(std::forward(p_rest)...); + } + } + + /** + * @brief set a fraction field. + */ + void set_field(const char* p_fieldname, w_fraction p_fraction) + { + gst_structure_set( + raw(), + p_fieldname, + GST_TYPE_FRACTION, + p_fraction.numerator, + p_fraction.denomerator, + nullptr + ); + } + + /** + * @brief set uint64 field. + */ + void set_field(const char* p_fieldname, std::size_t p_value) + { + gst_structure_set(raw(), p_fieldname, G_TYPE_UINT64, p_value, nullptr); + } + +private: + w_structure(internal::w_raw_tag, GstStructure* p_structure_raw) + : w_wrapper(p_structure_raw) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_aacparse.cpp b/wolf/media/gst/elements/w_element_aacparse.cpp new file mode 100644 index 000000000..0a839090f --- /dev/null +++ b/wolf/media/gst/elements/w_element_aacparse.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_aacparse.hpp" diff --git a/wolf/media/gst/elements/w_element_aacparse.hpp b/wolf/media/gst/elements/w_element_aacparse.hpp new file mode 100644 index 000000000..2503941a8 --- /dev/null +++ b/wolf/media/gst/elements/w_element_aacparse.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of aacparse gstreamer element. + */ +class w_element_aacparse : public w_element +{ + constexpr static const char* factory_name = "aacparse"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_aacparse(std::move(base_element)); + } + +private: + explicit w_element_aacparse(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_appsrc.cpp b/wolf/media/gst/elements/w_element_appsrc.cpp new file mode 100644 index 000000000..d38efb7c5 --- /dev/null +++ b/wolf/media/gst/elements/w_element_appsrc.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_appsrc.hpp" diff --git a/wolf/media/gst/elements/w_element_appsrc.hpp b/wolf/media/gst/elements/w_element_appsrc.hpp new file mode 100644 index 000000000..99e57ccad --- /dev/null +++ b/wolf/media/gst/elements/w_element_appsrc.hpp @@ -0,0 +1,134 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" +#include "media/gst/core/w_signal_handler.hpp" +#include "media/gst/core/w_caps.hpp" +#include "media/gst/core/w_format.hpp" + +#include + +namespace wolf::media::gst { + +/** + * @brief wrappper of appsrc gstreamer element. + */ +class w_element_appsrc : public w_element +{ + constexpr static const char* factory_name = "appsrc"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_appsrc(std::move(base_element)); + } + + //- properties + + void set_block(bool p_block) { + g_object_set(raw(), "block", p_block, nullptr); + } + + void set_caps(w_caps& p_caps) + { + g_object_set(raw(), "caps", internal::w_raw_access::raw(p_caps), nullptr); + } + + void set_format(w_format p_format) + { + g_object_set(raw(), "format", static_cast(p_format), nullptr); + } + + void set_live(bool p_is_live) + { + g_object_set(raw(), "is-live", p_is_live, nullptr); + } + + void set_max_buffer(std::size_t p_count) + { + g_object_set(raw(), "max-buffers", p_count, nullptr); + } + + void set_max_latency(std::size_t p_nanoseconds) + { + g_object_set(raw(), "max-latency", p_nanoseconds, nullptr); + } + + void set_max_time(std::size_t p_nanoseconds) + { + g_object_set(raw(), "max-time", p_nanoseconds, nullptr); + } + + //- signals + + template + void hook_enough_data(F&& p_callback) + { + _sighandlers.enough_data.hook("enough-data", std::forward(p_callback)); + } + + void unhook_enough_data() + { + _sighandlers.seek_data.unhook(); + } + + template + void hook_need_data(F&& p_callback) + { + _sighandlers.need_data.hook("need-data", std::forward(p_callback)); + } + + void unhook_need_data() + { + _sighandlers.seek_data.unhook(); + } + + template + void hook_seek_data(F&& p_callback) + { + _sighandlers.seek_data.hook("seek-data", std::forward(p_callback)); + } + + void unhook_seek_data() + { + _sighandlers.seek_data.unhook(); + } + + //- action signals + + bool emit_eos() + { + GstFlowReturn ret; + g_signal_emit_by_name(raw(), "end-of-stream", &ret); + return static_cast(ret); + } + + template + bool emit_push_buffer(BufferT&& p_buffer) + { + GstFlowReturn ret; + g_signal_emit_by_name(raw(), "push-buffer", internal::w_raw_access::raw(p_buffer), &ret); + return static_cast(ret); + } + +private: + explicit w_element_appsrc(w_element&& p_base) + : w_element(std::move(p_base)) + , _sighandlers(G_OBJECT(raw())) + {} + + // signals + struct signal_handler_set { + w_signal_handler<> enough_data; + w_signal_handler need_data; /// params: length + w_signal_handler seek_data; /// params: offset + + signal_handler_set(auto* p_rawptr) + : enough_data(p_rawptr) + , need_data(p_rawptr) + , seek_data(p_rawptr) + {} + } _sighandlers; +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_audioconvert.cpp b/wolf/media/gst/elements/w_element_audioconvert.cpp new file mode 100644 index 000000000..ae7bb9fed --- /dev/null +++ b/wolf/media/gst/elements/w_element_audioconvert.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_audioconvert.hpp" diff --git a/wolf/media/gst/elements/w_element_audioconvert.hpp b/wolf/media/gst/elements/w_element_audioconvert.hpp new file mode 100644 index 000000000..99d1644ef --- /dev/null +++ b/wolf/media/gst/elements/w_element_audioconvert.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of audioconvert gstreamer element. + */ +class w_element_audioconvert : public w_element +{ + constexpr static const char* factory_name = "audioconvert"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_audioconvert(std::move(base_element)); + } + +private: + explicit w_element_audioconvert(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_audioresample.cpp b/wolf/media/gst/elements/w_element_audioresample.cpp new file mode 100644 index 000000000..1559ddb91 --- /dev/null +++ b/wolf/media/gst/elements/w_element_audioresample.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_audioresample.hpp" diff --git a/wolf/media/gst/elements/w_element_audioresample.hpp b/wolf/media/gst/elements/w_element_audioresample.hpp new file mode 100644 index 000000000..942f98716 --- /dev/null +++ b/wolf/media/gst/elements/w_element_audioresample.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of audioresample gstreamer element. + */ +class w_element_audioresample : public w_element +{ + constexpr static const char* factory_name = "audioresample"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_audioresample(std::move(base_element)); + } + +private: + explicit w_element_audioresample(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_autovideosink.cpp b/wolf/media/gst/elements/w_element_autovideosink.cpp new file mode 100644 index 000000000..c5e6580c3 --- /dev/null +++ b/wolf/media/gst/elements/w_element_autovideosink.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_autovideosink.hpp" diff --git a/wolf/media/gst/elements/w_element_autovideosink.hpp b/wolf/media/gst/elements/w_element_autovideosink.hpp new file mode 100644 index 000000000..1faeec568 --- /dev/null +++ b/wolf/media/gst/elements/w_element_autovideosink.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +#include + +namespace wolf::media::gst { + +/** + * @brief wrappper of audiovideosink gstreamer element. + */ +class w_element_autovideosink : public w_element +{ + constexpr static const char* factory_name = "autovideosink"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_autovideosink(std::move(base_element)); + } + +private: + explicit w_element_autovideosink(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_avencflv.cpp b/wolf/media/gst/elements/w_element_avencflv.cpp new file mode 100644 index 000000000..857d19a79 --- /dev/null +++ b/wolf/media/gst/elements/w_element_avencflv.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_avencflv.hpp" diff --git a/wolf/media/gst/elements/w_element_avencflv.hpp b/wolf/media/gst/elements/w_element_avencflv.hpp new file mode 100644 index 000000000..c448a3d44 --- /dev/null +++ b/wolf/media/gst/elements/w_element_avencflv.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of avencflv gstreamer element. + */ +class w_element_avencflv : public w_element +{ + constexpr static const char* factory_name = "avenc_flv"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_avencflv(std::move(base_element)); + } + +private: + explicit w_element_avencflv(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_capsfilter.cpp b/wolf/media/gst/elements/w_element_capsfilter.cpp new file mode 100644 index 000000000..44f235671 --- /dev/null +++ b/wolf/media/gst/elements/w_element_capsfilter.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_capsfilter.hpp" diff --git a/wolf/media/gst/elements/w_element_capsfilter.hpp b/wolf/media/gst/elements/w_element_capsfilter.hpp new file mode 100644 index 000000000..0d16614e7 --- /dev/null +++ b/wolf/media/gst/elements/w_element_capsfilter.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" +#include "media/gst/core/w_caps.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of capsfilter gstreamer element. + */ +class w_element_capsfilter : public w_element +{ + constexpr static const char* factory_name = "capsfilter"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_capsfilter(std::move(base_element)); + } + + void set_caps(w_caps& p_caps) + { + g_object_set(raw(), "caps", internal::w_raw_access::raw(p_caps), nullptr); + } + +private: + explicit w_element_capsfilter(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_filesink.cpp b/wolf/media/gst/elements/w_element_filesink.cpp new file mode 100644 index 000000000..521631c94 --- /dev/null +++ b/wolf/media/gst/elements/w_element_filesink.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_filesink.hpp" diff --git a/wolf/media/gst/elements/w_element_filesink.hpp b/wolf/media/gst/elements/w_element_filesink.hpp new file mode 100644 index 000000000..ed3470911 --- /dev/null +++ b/wolf/media/gst/elements/w_element_filesink.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of filesink gstreamer element. + */ +class w_element_filesink : public w_element +{ + constexpr static const char* factory_name = "filesink"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_filesink(std::move(base_element)); + } + + void set_location(const char* p_location) + { + g_object_set(raw(), "location", p_location, nullptr); + } + +private: + explicit w_element_filesink(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_flvmux.cpp b/wolf/media/gst/elements/w_element_flvmux.cpp new file mode 100644 index 000000000..d7a9f500f --- /dev/null +++ b/wolf/media/gst/elements/w_element_flvmux.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_flvmux.hpp" diff --git a/wolf/media/gst/elements/w_element_flvmux.hpp b/wolf/media/gst/elements/w_element_flvmux.hpp new file mode 100644 index 000000000..cad265987 --- /dev/null +++ b/wolf/media/gst/elements/w_element_flvmux.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of flvmux gstreamer element. + */ +class w_element_flvmux : public w_element +{ + constexpr static const char* factory_name = "flvmux"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_flvmux(std::move(base_element)); + } + + void set_streamable(bool p_streamable) + { + g_object_set(raw(), "streamable", p_streamable, nullptr); + } + +private: + explicit w_element_flvmux(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_h264parse.cpp b/wolf/media/gst/elements/w_element_h264parse.cpp new file mode 100644 index 000000000..291821c92 --- /dev/null +++ b/wolf/media/gst/elements/w_element_h264parse.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_h264parse.hpp" diff --git a/wolf/media/gst/elements/w_element_h264parse.hpp b/wolf/media/gst/elements/w_element_h264parse.hpp new file mode 100644 index 000000000..3fd17d957 --- /dev/null +++ b/wolf/media/gst/elements/w_element_h264parse.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of h264parse gstreamer element. + */ +class w_element_h264parse : public w_element +{ + constexpr static const char* factory_name = "h264parse"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_h264parse(std::move(base_element)); + } + +private: + explicit w_element_h264parse(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_mp4mux.cpp b/wolf/media/gst/elements/w_element_mp4mux.cpp new file mode 100644 index 000000000..18b9cc9b1 --- /dev/null +++ b/wolf/media/gst/elements/w_element_mp4mux.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_mp4mux.hpp" diff --git a/wolf/media/gst/elements/w_element_mp4mux.hpp b/wolf/media/gst/elements/w_element_mp4mux.hpp new file mode 100644 index 000000000..48712e21b --- /dev/null +++ b/wolf/media/gst/elements/w_element_mp4mux.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of mp4mux gstreamer element. + */ +class w_element_mp4mux : public w_element +{ + constexpr static const char* factory_name = "mp4mux"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_mp4mux(std::move(base_element)); + } + +private: + explicit w_element_mp4mux(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_openh264enc.cpp b/wolf/media/gst/elements/w_element_openh264enc.cpp new file mode 100644 index 000000000..df227b382 --- /dev/null +++ b/wolf/media/gst/elements/w_element_openh264enc.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_openh264enc.hpp" diff --git a/wolf/media/gst/elements/w_element_openh264enc.hpp b/wolf/media/gst/elements/w_element_openh264enc.hpp new file mode 100644 index 000000000..854495979 --- /dev/null +++ b/wolf/media/gst/elements/w_element_openh264enc.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of openh264enc gstreamer element. + */ +class w_element_openh264enc : public w_element +{ + constexpr static const char* factory_name = "openh264enc"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_openh264enc(std::move(base_element)); + } + +private: + explicit w_element_openh264enc(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_queue.cpp b/wolf/media/gst/elements/w_element_queue.cpp new file mode 100644 index 000000000..2ba4e2d03 --- /dev/null +++ b/wolf/media/gst/elements/w_element_queue.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_queue.hpp" diff --git a/wolf/media/gst/elements/w_element_queue.hpp b/wolf/media/gst/elements/w_element_queue.hpp new file mode 100644 index 000000000..8b48a7271 --- /dev/null +++ b/wolf/media/gst/elements/w_element_queue.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of queue gstreamer element. + */ +class w_element_queue : public w_element +{ + constexpr static const char* factory_name = "queue"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_queue(std::move(base_element)); + } + +private: + explicit w_element_queue(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_rtmpsink.cpp b/wolf/media/gst/elements/w_element_rtmpsink.cpp new file mode 100644 index 000000000..7499353dd --- /dev/null +++ b/wolf/media/gst/elements/w_element_rtmpsink.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_rtmpsink.hpp" diff --git a/wolf/media/gst/elements/w_element_rtmpsink.hpp b/wolf/media/gst/elements/w_element_rtmpsink.hpp new file mode 100644 index 000000000..044f7fb88 --- /dev/null +++ b/wolf/media/gst/elements/w_element_rtmpsink.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of rtmpsink gstreamer element. + */ +class w_element_rtmpsink : public w_element +{ + constexpr static const char* factory_name = "rtmpsink"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_rtmpsink(std::move(base_element)); + } + + void set_location(const char* p_location) + { + g_object_set(raw(), "location", p_location, nullptr); + } + +private: + explicit w_element_rtmpsink(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_rtph264pay.cpp b/wolf/media/gst/elements/w_element_rtph264pay.cpp new file mode 100644 index 000000000..d2adc96ff --- /dev/null +++ b/wolf/media/gst/elements/w_element_rtph264pay.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_rtph264pay.hpp" diff --git a/wolf/media/gst/elements/w_element_rtph264pay.hpp b/wolf/media/gst/elements/w_element_rtph264pay.hpp new file mode 100644 index 000000000..5292414f8 --- /dev/null +++ b/wolf/media/gst/elements/w_element_rtph264pay.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of rtph264pay gstreamer element. + */ +class w_element_rtph264pay : public w_element +{ + constexpr static const char* factory_name = "rtph264pay"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_rtph264pay(std::move(base_element)); + } + +private: + explicit w_element_rtph264pay(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_udpsink.cpp b/wolf/media/gst/elements/w_element_udpsink.cpp new file mode 100644 index 000000000..c174e7272 --- /dev/null +++ b/wolf/media/gst/elements/w_element_udpsink.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_udpsink.hpp" diff --git a/wolf/media/gst/elements/w_element_udpsink.hpp b/wolf/media/gst/elements/w_element_udpsink.hpp new file mode 100644 index 000000000..f0b3f3b3e --- /dev/null +++ b/wolf/media/gst/elements/w_element_udpsink.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of udpsink gstreamer element. + */ +class w_element_udpsink : public w_element +{ + constexpr static const char* factory_name = "udpsink"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_udpsink(std::move(base_element)); + } + + void set_host(const char* p_host) + { + g_object_set(raw(), "host", p_host, nullptr); + } + + void set_port(unsigned short p_port) + { + g_object_set(raw(), "port", static_cast(p_port), nullptr); + } + +private: + explicit w_element_udpsink(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_videoconvert.cpp b/wolf/media/gst/elements/w_element_videoconvert.cpp new file mode 100644 index 000000000..4f475fb77 --- /dev/null +++ b/wolf/media/gst/elements/w_element_videoconvert.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_videoconvert.hpp" diff --git a/wolf/media/gst/elements/w_element_videoconvert.hpp b/wolf/media/gst/elements/w_element_videoconvert.hpp new file mode 100644 index 000000000..92fbb97cf --- /dev/null +++ b/wolf/media/gst/elements/w_element_videoconvert.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of videoconvert gstreamer element. + */ +class w_element_videoconvert : public w_element +{ + constexpr static const char* factory_name = "videoconvert"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_videoconvert(std::move(base_element)); + } + +private: + explicit w_element_videoconvert(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_videoscale.cpp b/wolf/media/gst/elements/w_element_videoscale.cpp new file mode 100644 index 000000000..84ec189ba --- /dev/null +++ b/wolf/media/gst/elements/w_element_videoscale.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_videoscale.hpp" diff --git a/wolf/media/gst/elements/w_element_videoscale.hpp b/wolf/media/gst/elements/w_element_videoscale.hpp new file mode 100644 index 000000000..e51cfb4ae --- /dev/null +++ b/wolf/media/gst/elements/w_element_videoscale.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of videoscale gstreamer element. + */ +class w_element_videoscale : public w_element +{ + constexpr static const char* factory_name = "videoscale"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_videoscale(std::move(base_element)); + } + +private: + explicit w_element_videoscale(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_videotestsrc.cpp b/wolf/media/gst/elements/w_element_videotestsrc.cpp new file mode 100644 index 000000000..daebccc71 --- /dev/null +++ b/wolf/media/gst/elements/w_element_videotestsrc.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_videotestsrc.hpp" diff --git a/wolf/media/gst/elements/w_element_videotestsrc.hpp b/wolf/media/gst/elements/w_element_videotestsrc.hpp new file mode 100644 index 000000000..79172b9b5 --- /dev/null +++ b/wolf/media/gst/elements/w_element_videotestsrc.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of videotestsrc gstreamer element. + */ +class w_element_videotestsrc : public w_element +{ + constexpr static const char* factory_name = "videotestsrc"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_videotestsrc(std::move(base_element)); + } + +private: + explicit w_element_videotestsrc(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst + diff --git a/wolf/media/gst/elements/w_element_voaacenc.cpp b/wolf/media/gst/elements/w_element_voaacenc.cpp new file mode 100644 index 000000000..a90065fd0 --- /dev/null +++ b/wolf/media/gst/elements/w_element_voaacenc.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_voaacenc.hpp" diff --git a/wolf/media/gst/elements/w_element_voaacenc.hpp b/wolf/media/gst/elements/w_element_voaacenc.hpp new file mode 100644 index 000000000..312b23b04 --- /dev/null +++ b/wolf/media/gst/elements/w_element_voaacenc.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of voaacenc gstreamer element. + */ +class w_element_voaacenc : public w_element +{ + constexpr static const char* factory_name = "voaacenc"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_voaacenc(std::move(base_element)); + } + + void set_bitrate(std::size_t p_bitrate) + { + g_object_set(raw(), "bitrate", p_bitrate, nullptr); + } + +private: + explicit w_element_voaacenc(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_wasapisrc.cpp b/wolf/media/gst/elements/w_element_wasapisrc.cpp new file mode 100644 index 000000000..b6f877b05 --- /dev/null +++ b/wolf/media/gst/elements/w_element_wasapisrc.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_wasapisrc.hpp" diff --git a/wolf/media/gst/elements/w_element_wasapisrc.hpp b/wolf/media/gst/elements/w_element_wasapisrc.hpp new file mode 100644 index 000000000..8ebac477e --- /dev/null +++ b/wolf/media/gst/elements/w_element_wasapisrc.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of wasapisrc gstreamer element. + */ +class w_element_wasapisrc : public w_element +{ + constexpr static const char* factory_name = "wasapisrc"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_wasapisrc(std::move(base_element)); + } + + void set_device(const char* p_device) + { + g_object_set(raw(), "device", p_device, nullptr); + } + + void set_low_latency(bool p_enable) + { + g_object_set(raw(), "low-latency", p_enable, nullptr); + } + +private: + explicit w_element_wasapisrc(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/elements/w_element_x264enc.cpp b/wolf/media/gst/elements/w_element_x264enc.cpp new file mode 100644 index 000000000..e8154c6d7 --- /dev/null +++ b/wolf/media/gst/elements/w_element_x264enc.cpp @@ -0,0 +1 @@ +#include "media/gst/elements/w_element_x264enc.hpp" diff --git a/wolf/media/gst/elements/w_element_x264enc.hpp b/wolf/media/gst/elements/w_element_x264enc.hpp new file mode 100644 index 000000000..53180c83f --- /dev/null +++ b/wolf/media/gst/elements/w_element_x264enc.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include "media/gst/core/w_element.hpp" + +namespace wolf::media::gst { + +/** + * @brief wrappper of x264enc gstreamer element. + */ +class w_element_x264enc : public w_element +{ + constexpr static const char* factory_name = "x264enc"; + +public: + [[nodiscard]] static auto make() -> boost::leaf::result + { + BOOST_LEAF_AUTO(base_element, w_element::make(factory_name)); + return w_element_x264enc(std::move(base_element)); + } + + void set_bitrate(std::size_t p_bitrate) + { + g_object_set(raw(), "bitrate", p_bitrate, nullptr); + } + + void set_pass(std::size_t p_pass) + { + g_object_set(raw(), "pass", p_pass, nullptr); + } + + void set_quantizer(std::size_t p_quantizer) + { + g_object_set(raw(), "quantizer", p_quantizer, nullptr); + } + + void set_qp_min(std::size_t p_qp_min) + { + g_object_set(raw(), "qp-min", p_qp_min, nullptr); + } + + void set_qp_max(std::size_t p_qp_max) + { + g_object_set(raw(), "qp-max", p_qp_max, nullptr); + } + + void set_speed_preset(std::size_t p_speed_preset) + { + g_object_set(raw(), "speed-preset", p_speed_preset, nullptr); + } + + void set_key_int_max(std::size_t p_key_int_max) + { + g_object_set(raw(), "key-int-max", p_key_int_max, nullptr); + } + + void set_b_adapt(bool p_b_adapt) + { + g_object_set(raw(), "b-adapt", p_b_adapt, nullptr); + } + + void set_bframes(std::size_t p_bframes) + { + g_object_set(raw(), "bframes", p_bframes, nullptr); + } + +private: + explicit w_element_x264enc(w_element&& p_base) noexcept + : w_element(std::move(p_base)) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/internal/w_common.cpp b/wolf/media/gst/internal/w_common.cpp new file mode 100644 index 000000000..223055d86 --- /dev/null +++ b/wolf/media/gst/internal/w_common.cpp @@ -0,0 +1 @@ +#include "media/gst/internal/w_common.hpp" diff --git a/wolf/media/gst/internal/w_common.hpp b/wolf/media/gst/internal/w_common.hpp new file mode 100644 index 000000000..387bc809a --- /dev/null +++ b/wolf/media/gst/internal/w_common.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include + +namespace wolf::media::gst::internal { + +/** tag to indicate having a raw pointer in the context. */ +struct w_raw_tag {}; + +/** + * @brief helper utility to be common friend of wrappers, + * poviding access to certain api for any wrapper to another. + * + * this comes handy when w_audio_info wants to create a w_caps, + * or w_pipeline wants to add w_element to its bin. + * + * instead of having the aformentioned types know/befriend each other, + * they'll be friend to this class and provide private access to this, + * so any of them can access each other with this class as a common friend. + */ +class w_raw_access +{ +public: + /** + * @brief create an instance of `T` with given raw pointer. + * @param p_raw_ptr raw pointer that `T` wraps or accepts. + * @param p_args custom extended args. + */ + template + static auto from_raw(RawT* p_raw_ptr, Args&& ...p_args) + { + return T(w_raw_tag{}, p_raw_ptr, std::forward(p_args)...); + } + + /** + * @brief get raw pointer wrapped in the given wrapper object. + * @param p_obj wrapper object. + */ + template + static auto raw(T& p_obj) noexcept + { + return p_obj.raw(); + } + + /** + * @brief get the underlying raw pointer from given object, + * and make the object no longer point to that raw resource without releasing it. + * @param p_obj the wrapper object to discard its underlying raw pointer. + */ + template + static auto disown_raw(T&& p_obj) noexcept + { + return p_obj.disown_raw(); + } +}; + +} // namespace wolf::media::gst::internal diff --git a/wolf/media/gst/internal/w_utils.cpp b/wolf/media/gst/internal/w_utils.cpp new file mode 100644 index 000000000..94154a874 --- /dev/null +++ b/wolf/media/gst/internal/w_utils.cpp @@ -0,0 +1 @@ +#include "media/gst/internal/w_utils.hpp" diff --git a/wolf/media/gst/internal/w_utils.hpp b/wolf/media/gst/internal/w_utils.hpp new file mode 100644 index 000000000..62f90e71e --- /dev/null +++ b/wolf/media/gst/internal/w_utils.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include + +namespace wolf::media::gst::internal { + +/** + * @brief The famous overloaded pattern to overload several callables, e.g. lambdas. + * + * reference: https://www.cppstories.com/2019/02/2lines3featuresoverload.html/ + */ +template +struct w_overloaded : Fs... { using Fs::operator()...; }; + +template +w_overloaded(Fs...) -> w_overloaded; + +/** + * @brief a helper tool used with `overloaded` and `std::visit` to + * behave similar to `default` clause in switch/case statements. + * + * it is implicitly convertible from anything, but discards them, + * thus in an overload set, it will be the least match, thus if any other + * callable wouldn't match, this one will be used. + * + * example: + * ```cpp + * std::visit( + * w_overloaded{ + * [](int num) { ... case int ... }, + * [](char* str) { ... case c-string ... }, + * [](w_blackhole) { ... default ... } + * }, + * my_varaint + * ); + * ``` + */ +struct w_blackhole +{ + template + constexpr w_blackhole(Ts&& ...) noexcept {} +}; + +/** + * @brief make an instantiable callable type out of + * a compile-time function pointer. + * + * @tparam FV a function symbole/pointer. + */ +template +class w_functor { +public: + template + auto operator()(Args&& ...p_args) const + { + return FV(std::forward(p_args)...); + } +}; + +} // namespace wolf::media::gst::internal diff --git a/wolf/media/gst/internal/w_wrapper.cpp b/wolf/media/gst/internal/w_wrapper.cpp new file mode 100644 index 000000000..d66611602 --- /dev/null +++ b/wolf/media/gst/internal/w_wrapper.cpp @@ -0,0 +1 @@ +#include "media/gst/internal/w_wrapper.hpp" diff --git a/wolf/media/gst/internal/w_wrapper.hpp b/wolf/media/gst/internal/w_wrapper.hpp new file mode 100644 index 000000000..f5b3149f6 --- /dev/null +++ b/wolf/media/gst/internal/w_wrapper.hpp @@ -0,0 +1,113 @@ +#pragma once + +#include "media/gst/internal/w_common.hpp" +#include "media/gst/internal/w_utils.hpp" +#include "media/gst/core/w_nonowning.hpp" + +#include +#include + +namespace wolf::media::gst { + +/** + * the w_wrapper is helper CRTP class wrapping given raw C type pointer, + * managing its lifetime and ownership/parentship. + * + * @tparam DerivedT class type inheriting this w_wrapper. + * @tparam RawT raw type to wrap. + * @tparam BaseT base type of `RawT`, for GObject types. (not supported yet, use `void`) + * @tparam FreeFV function (pointer) to free the `RawT` pointed resource. + * @tparam CopyFV optional function (pointer) to copy the `RawT` pointed resource. + * (default: nullptr) + */ +template +class w_wrapper +{ + static_assert( + std::is_same_v, + "currently wrapper base isn't supported. it must be void." + ); + + friend class internal::w_raw_access; + + using underlying_type = std::remove_cvref_t; + +public: + w_wrapper(const w_wrapper& p_other) + requires (CopyFV != nullptr) + : _ptr(CopyFV(p_other._ptr.get())) + , _parented(false) + {} + + w_wrapper(w_wrapper&&) noexcept = default; + + w_wrapper& operator=(const w_wrapper& p_other) + requires (CopyFV != nullptr) + { + reset(CopyFV(p_other._ptr.get())); + _parented = false; + return *this; + } + + w_wrapper& operator=(w_wrapper&&) noexcept = default; + + ~w_wrapper() noexcept + { + // avoid being passed to FreeFV if it's parented, + // as its parent should/will take care of its lifetime. + if (_parented) { + _ptr.release(); + } + } + + /** + * create a nonowning/shallow copy of the instance wrapping + * underlying raw pointer. + * + * only for lvalues, no accidental disposal + * of rvalues leading dangling references. + * + * @return a non-owning shallow copy of the instance. + */ + auto nonowning_view() & -> w_nonowning + { + return internal::w_raw_access::from_raw>(_ptr.get()); + } + + /** + * set as parented to keep the raw pointer pointing to resource, + * but not to try to release it at destruction as that'd be up + * to the parent. + * + * only applicable to lvalues since calling it + * on rvalues has a high chance of memory/resource leak, + * and it's advised to call this method after + * adoption by parent object. + */ + void parented() & + { + _parented = true; + } + +protected: + // only derived classes should be able to initialize this class + // with thier raw pointer provided resource. + explicit w_wrapper(underlying_type* ptr) : _ptr(ptr) {} + + [[nodiscard]] underlying_type* raw() noexcept { return _ptr.get(); } + [[nodiscard]] const underlying_type* raw() const noexcept { return _ptr.get(); } + + void replace_raw(underlying_type* ptr = nullptr) { _ptr.reset(ptr); } + + auto disown_raw() noexcept { return _ptr.release(); } + +private: + bool _parented = false; + std::unique_ptr> _ptr; +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/video/w_video_format.cpp b/wolf/media/gst/video/w_video_format.cpp new file mode 100644 index 000000000..f2d24061e --- /dev/null +++ b/wolf/media/gst/video/w_video_format.cpp @@ -0,0 +1 @@ +#include "media/gst/video/w_video_format.hpp" diff --git a/wolf/media/gst/video/w_video_format.hpp b/wolf/media/gst/video/w_video_format.hpp new file mode 100644 index 000000000..1c8ede1ed --- /dev/null +++ b/wolf/media/gst/video/w_video_format.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace wolf::media::gst { + +/** + * enum same as GstVideoFormat. + */ +enum class w_video_format +{ + RGB = GST_VIDEO_FORMAT_RGB, + RGBx = GST_VIDEO_FORMAT_RGBx, + BGR = GST_VIDEO_FORMAT_BGR, + BGRx = GST_VIDEO_FORMAT_BGRx + // ... +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/video/w_video_info.cpp b/wolf/media/gst/video/w_video_info.cpp new file mode 100644 index 000000000..1a652eff6 --- /dev/null +++ b/wolf/media/gst/video/w_video_info.cpp @@ -0,0 +1,26 @@ +#include "media/gst/video/w_video_info.hpp" + +namespace wolf::media::gst { + +auto w_video_info::make(w_video_format p_format, size_t p_width, size_t p_height) + -> boost::leaf::result +{ + auto video_info_raw = gst_video_info_new(); + if (!video_info_raw) { + return W_FAILURE(std::errc::operation_canceled, "couldn't create video info."); + } + + bool res = gst_video_info_set_format( + video_info_raw, + static_cast(p_format), + gsl::narrow_cast(p_width), + gsl::narrow_cast(p_height) + ); + if (!res) { + return W_FAILURE(std::errc::operation_canceled, "couldn't set video info."); + } + + return w_video_info(internal::w_raw_tag{}, video_info_raw); +} + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/video/w_video_info.hpp b/wolf/media/gst/video/w_video_info.hpp new file mode 100644 index 000000000..79cc51eeb --- /dev/null +++ b/wolf/media/gst/video/w_video_info.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include "wolf.hpp" + +#include "media/gst/internal/w_common.hpp" +#include "media/gst/internal/w_wrapper.hpp" +#include "media/gst/video/w_video_format.hpp" +#include "media/gst/core/w_caps.hpp" + +#include + +#include +#include + +namespace wolf::media::gst { + +/** + * wrapper of GstAudioInfo. + */ +class w_video_info : public w_wrapper +{ + friend class internal::w_raw_access; + +public: + /** + * @brief make an w_video_info instance with given format and other info. + * @return a constructed audio info on success. + */ + [[nodiscard]] static auto make(w_video_format p_format, + std::size_t p_width, + std::size_t p_height) + -> boost::leaf::result; + + /** + * @brief make an equivalent w_caps instance from this audio info. + */ + w_caps to_caps() + { + auto ptr = gst_video_info_to_caps(raw()); + return internal::w_raw_access::from_raw(ptr); + } + + /** + * @brief set playback fps. + * @param p_fps frame per second. + */ + void set_fps(std::size_t p_fps) + { + raw()->fps_n = 1; + raw()->fps_d = gsl::narrow_cast(p_fps); + } + + /** + * @brief set playback fps in relative fraction. + */ + void set_fps(std::size_t p_numerator, std::size_t p_denomirator) + { + raw()->fps_n = gsl::narrow_cast(p_numerator); + raw()->fps_d = gsl::narrow_cast(p_denomirator); + } + +private: + w_video_info(internal::w_raw_tag, GstVideoInfo* video_info_raw) noexcept + : w_wrapper(video_info_raw) + {} +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/w_application.cpp b/wolf/media/gst/w_application.cpp new file mode 100644 index 000000000..def012399 --- /dev/null +++ b/wolf/media/gst/w_application.cpp @@ -0,0 +1 @@ +#include "media/gst/w_application.hpp" diff --git a/wolf/media/gst/w_application.hpp b/wolf/media/gst/w_application.hpp new file mode 100644 index 000000000..c66614d21 --- /dev/null +++ b/wolf/media/gst/w_application.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +namespace wolf::media::gst { + +/** + * @brief a gstreamer application. + */ +class w_application +{ +public: + w_application() = delete; + + static bool init(int* argc, char*** argv) + { + gst_init(argc, argv); + return true; + } +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/gst/w_flow.cpp b/wolf/media/gst/w_flow.cpp new file mode 100644 index 000000000..2a42a3020 --- /dev/null +++ b/wolf/media/gst/w_flow.cpp @@ -0,0 +1 @@ +#include "media/gst/w_flow.hpp" diff --git a/wolf/media/gst/w_flow.hpp b/wolf/media/gst/w_flow.hpp new file mode 100644 index 000000000..8d1a8e0ac --- /dev/null +++ b/wolf/media/gst/w_flow.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include "media/gst/core/w_refptr.hpp" +#include "media/gst/core/w_element.hpp" + +#include +#include +#include + +namespace wolf::media::gst { + +/** + * @brief a container of w_refptr representing a path in a pipeline flow. + * + * this container will always contain 2 elements at least. + * + * @note this container does not link + * or check for correct src/sink composition. + * it's only a storage. + */ +class w_flow_path +{ +public: + /** + * @brief make a flow path. + * @param elements list of elements to make a flow path from. + */ + template + requires (sizeof...(Ts) >= 2) + static auto make(Ts&& ...elements) { + auto path = w_flow_path(); + + path._vec.reserve(sizeof...(Ts)); + (..., path._vec.emplace_back(to_refptr(std::forward(elements)))); + + return path; + } + + constexpr auto size() const noexcept -> std::size_t { return _vec.size(); } + + auto& first() { return _vec[0]; } + const auto& first() const { return _vec[0]; } + + auto& last() { return _vec[size() - 1]; } + const auto& last() const { return _vec[size() - 1]; } + + auto begin() noexcept { return std::begin(_vec); } + auto begin() const noexcept { return std::begin(_vec); } + + auto end() noexcept { return std::end(_vec); } + auto end() const noexcept { return std::end(_vec); } + + auto& operator[](std::size_t index) { return _vec[index]; } + const auto& operator[](std::size_t index) const { return _vec[index]; } + +private: + w_flow_path() {} + + std::vector> _vec; +}; + +} // namespace wolf::media::gst diff --git a/wolf/media/test/avframe.hpp b/wolf/media/test/avframe.hpp new file mode 100644 index 000000000..9e9bff3df --- /dev/null +++ b/wolf/media/test/avframe.hpp @@ -0,0 +1,51 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#if defined(WOLF_TEST) && defined(WOLF_MEDIA_FFMPEG) && defined(WOLF_MEDIA_STB) + +#include +#include + +#include +#include + +BOOST_AUTO_TEST_CASE(avframe_test) { + const wolf::system::w_leak_detector _detector = {}; + + std::cout << "entering test case 'avframe_test'" << std::endl; + + boost::leaf::try_handle_all( + [&]() -> boost::leaf::result { + using w_av_frame = wolf::media::ffmpeg::w_av_frame; + using w_av_config = wolf::media::ffmpeg::w_av_config; + + // create av frame from img + const std::filesystem::path path = + std::filesystem::current_path().append("../../content/texture/rgb.png"); + + BOOST_LEAF_AUTO(_src_frame, w_av_frame::load_video_frame_from_img_file( + path, AVPixelFormat::AV_PIX_FMT_RGBA)); + + const auto _config = _src_frame.get_config(); + + auto _dst_config = + w_av_config(AVPixelFormat::AV_PIX_FMT_BGRA, _config.width, _config.height); + BOOST_LEAF_AUTO(_dst_frame, _src_frame.convert_video(std::move(_dst_config))); + + const std::filesystem::path _new_path = std::filesystem::current_path().append("/rgb_converted.png"); + BOOST_LEAF_CHECK(_dst_frame.save_video_frame_to_img_file(_new_path)); + + return {}; + }, + [](const w_trace &p_trace) { + const auto _msg = wolf::format("avframe_test got an error: {}", p_trace.to_string()); + BOOST_ERROR(_msg); + }, + [] { BOOST_ERROR("avframe_test got an error!"); }); + + std::cout << "leaving test case 'avframe_test'" << std::endl; +} + +#endif \ No newline at end of file diff --git a/wolf/media/test/ffmpeg.hpp b/wolf/media/test/ffmpeg.hpp new file mode 100644 index 000000000..f486ca053 --- /dev/null +++ b/wolf/media/test/ffmpeg.hpp @@ -0,0 +1,235 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#if defined(WOLF_TEST) && defined(WOLF_MEDIA_FFMPEG) && defined(WOLF_MEDIA_STB) + +#include +#include + +#include +#include + +using w_av_frame = wolf::media::ffmpeg::w_av_frame; +using w_av_codec_opt = wolf::media::ffmpeg::w_av_codec_opt; +using w_av_config = wolf::media::ffmpeg::w_av_config; +using w_av_set_opt = wolf::media::ffmpeg::w_av_set_opt; +using w_av_packet = wolf::media::ffmpeg::w_av_packet; +using w_ffmpeg = wolf::media::ffmpeg::w_ffmpeg; + +static boost::leaf::result> s_encode( + _In_ const std::string &p_name, _In_ std::variant &p_codec_id, + _In_ const w_av_codec_opt &p_codec_options, + _In_ const std::vector &p_av_set_options, _In_ const bool p_flush = true) { + // namespaces + using w_encoder = wolf::media::ffmpeg::w_encoder; + + // create av frame from img + const auto _img_file_name = std::string("../../content/texture/rgb.png"); + const std::filesystem::path _img_path = std::filesystem::current_path().append(_img_file_name); + + BOOST_LEAF_AUTO(_src_frame, w_av_frame::load_video_frame_from_img_file( + _img_path, AVPixelFormat::AV_PIX_FMT_RGBA)); + + // source & destination configs + const auto _src_config = _src_frame.get_config(); + auto _dst_config = + w_av_config(AVPixelFormat::AV_PIX_FMT_YUV420P, _src_config.width, _src_config.height); + + // encode frame to packet + boost::leaf::result _encoder_res; + if (p_codec_id.index() == 0) { + auto _codec_id = std::get<0>(p_codec_id); + _encoder_res = + w_ffmpeg::create_encoder(_dst_config, _codec_id, p_codec_options, p_av_set_options); + } else { + const auto _codec_id = std::get<1>(p_codec_id); + _encoder_res = + w_ffmpeg::create_encoder(_dst_config, _codec_id, p_codec_options, p_av_set_options); + } + // check encoder + BOOST_LEAF_AUTO(_encoder, std::move(_encoder_res)); + + // convert source frame to yuv frame + auto _dst_config_clone = w_av_config(_dst_config); + BOOST_LEAF_AUTO(_yuv_frame, _src_frame.convert_video(std::move(_dst_config_clone))); + + auto _packet = w_av_packet(); + BOOST_LEAF_CHECK(_encoder.encode(_yuv_frame, _packet, p_flush)); + + const std::filesystem::path _encoded_path = + std::filesystem::current_path().append(wolf::format("/{}_yuv_encoded.png", p_name)); + BOOST_LEAF_CHECK(_yuv_frame.save_video_frame_to_img_file(_encoded_path)); + + return std::make_tuple(std::move(_packet), _src_config, _dst_config); +} + +static boost::leaf::result s_decode( + _In_ std::tuple &p_encoded_tuple, + _In_ const std::string &p_name, _In_ std::variant &p_codec_id, + _In_ const w_av_codec_opt &p_codec_options, + _In_ const std::vector &p_av_set_options, _In_ const bool p_flush = false) { + using w_decoder = wolf::media::ffmpeg::w_decoder; + + // create destination avframe for decoder + auto _video_buffer = std::vector(); + auto _decoded_frame = w_av_frame(std::forward(std::get<2>(p_encoded_tuple))); + BOOST_LEAF_CHECK(_decoded_frame.init()); + BOOST_LEAF_CHECK(_decoded_frame.set_video_frame(std::move(_video_buffer))); + + // create a decoder + boost::leaf::result _decoder_res; + if (p_codec_id.index() == 0) { + auto _codec_id = std::get<0>(p_codec_id); + _decoder_res = w_ffmpeg::create_decoder(_decoded_frame.get_config(), _codec_id, p_codec_options, + p_av_set_options); + } else { + const auto _codec_id = std::get<1>(p_codec_id); + _decoder_res = w_ffmpeg::create_decoder(_decoded_frame.get_config(), _codec_id, p_codec_options, + p_av_set_options); + } + + // check decoder + BOOST_LEAF_AUTO(_decoder, std::move(_decoder_res)); + + // decode the packet + BOOST_LEAF_CHECK(_decoder.decode(std::get<0>(p_encoded_tuple), _decoded_frame, p_flush)); + + BOOST_LEAF_AUTO(_final_frame, _decoded_frame.convert_video( + std::forward(std::get<1>(p_encoded_tuple)))); + + const std::filesystem::path _path = + std::filesystem::current_path().append(wolf::format("/{}_rgb_decoded.png", p_name)); + BOOST_LEAF_CHECK(_decoded_frame.save_video_frame_to_img_file(_path)); + + return {}; +} + +BOOST_AUTO_TEST_CASE(av1_encode_decode_test) { + const wolf::system::w_leak_detector _detector = {}; + + constexpr auto _name = "av1"; + std::cout << "entering test case '_encode_decode_test'" << std::endl; + + boost::leaf::try_handle_all( + [&]() -> boost::leaf::result { + const auto _codec_opt = w_av_codec_opt{ + 4'000'000, /*bitrate*/ + 60, /*fps*/ + 600, /*gop*/ + -1, /*level*/ + 2, /*max_b_frames*/ + 3, /*refs*/ + -1, /*thread_count*/ + }; + + // for more info read https://trac.ffmpeg.org/wiki/Encode/AV1 + const auto _opts = std::vector{ + w_av_set_opt{"preset", 12}, + w_av_set_opt{"crf", 50}, + }; + + std::variant _encode_codec_id("libsvtav1"); + BOOST_LEAF_AUTO(_encoded_tuple, s_encode(_name, _encode_codec_id, _codec_opt, _opts)); + + std::variant _decode_codec_id("libdav1d"); + BOOST_LEAF_CHECK(s_decode(_encoded_tuple, _name, _decode_codec_id, _codec_opt, {})); + + return {}; + }, + [](const w_trace &p_trace) { + const auto _msg = wolf::format("got error: {}", p_trace.to_string()); + BOOST_ERROR(_msg); + }, + [] { + const auto _msg = wolf::format("got an error"); + BOOST_ERROR(_msg); + }); + + std::cout << "leaving test case 'av1_encode_decode_test'" << std::endl; +} + +BOOST_AUTO_TEST_CASE(vp9_encode_decode_test) { + const wolf::system::w_leak_detector _detector = {}; + + constexpr auto _name = "vp9"; + std::cout << "entering test case 'vp9_encode_decode_test'" << std::endl; + + boost::leaf::try_handle_all( + [&]() -> boost::leaf::result { + const auto _codec_opt = w_av_codec_opt{ + 4'000'000, /*bitrate*/ + 60, /*fps*/ + 600, /*gop*/ + -1, /*level*/ + 2, /*max_b_frames*/ + 3, /*refs*/ + -1, /*thread_count*/ + }; + + // for more info read https://trac.ffmpeg.org/wiki/Encode/VP9 + const auto _opts = std::vector(); + + std::variant _codec_id(AVCodecID::AV_CODEC_ID_VP9); + BOOST_LEAF_AUTO(_encoded_tuple, s_encode(_name, _codec_id, _codec_opt, _opts)); + + BOOST_LEAF_CHECK(s_decode(_encoded_tuple, _name, _codec_id, _codec_opt, {})); + + return {}; + }, + [](const w_trace &p_trace) { + const auto _msg = wolf::format("got error: {}", p_trace.to_string()); + BOOST_ERROR(_msg); + }, + [] { + const auto _msg = wolf::format("got an error"); + BOOST_ERROR(_msg); + }); + + std::cout << "leaving test case 'vp9_encode_decode_test'" << std::endl; +} + +BOOST_AUTO_TEST_CASE(x264_encode_decode_test) { + const wolf::system::w_leak_detector _detector = {}; + + constexpr auto _name = "x264"; + std::cout << "entering test case 'x264_encode_decode_test'" << std::endl; + + boost::leaf::try_handle_all( + [&]() -> boost::leaf::result { + const auto _codec_opt = w_av_codec_opt{ + 4'000'000, /*bitrate*/ + 60, /*fps*/ + 600, /*gop*/ + -1, /*level*/ + 2, /*max_b_frames*/ + 3, /*refs*/ + -1, /*thread_count*/ + }; + + // for more info read https://trac.ffmpeg.org/wiki/Encode/H.264 + const auto _opts = std::vector{ + w_av_set_opt{"profile", "main"}, w_av_set_opt{"preset", "veryfast"}, + w_av_set_opt{"tune", "zerolatency"}, w_av_set_opt{"crf", 22}}; + + std::variant _codec_id(AVCodecID::AV_CODEC_ID_H264); + BOOST_LEAF_AUTO(_encoded_tuple, s_encode(_name, _codec_id, _codec_opt, _opts)); + + BOOST_LEAF_CHECK(s_decode(_encoded_tuple, _name, _codec_id, _codec_opt, {}, true)); + + return {}; + }, + [](const w_trace &p_trace) { + const auto _msg = wolf::format("got error: {}", p_trace.to_string()); + BOOST_ERROR(_msg); + }, + [] { + const auto _msg = wolf::format("got an error"); + BOOST_ERROR(_msg); + }); + + std::cout << "leaving test case 'x264_encode_decode_test'" << std::endl; +} + +#endif \ No newline at end of file diff --git a/wolf/media/test/gstreamer.hpp b/wolf/media/test/gstreamer.hpp new file mode 100644 index 000000000..041b2a800 --- /dev/null +++ b/wolf/media/test/gstreamer.hpp @@ -0,0 +1,134 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#if defined(WOLF_TEST) && defined(WOLF_MEDIA_GSTREAMER) + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +BOOST_AUTO_TEST_CASE(gstreamer_wrapper) { + namespace gst = wolf::media::gst; + + boost::leaf::try_handle_all( + []() -> boost::leaf::result { + gst::w_application::init(nullptr, nullptr); + + std::size_t width = 640; + std::size_t height = 480; + + BOOST_LEAF_AUTO(video_info, + gst::w_video_info::make(gst::w_video_format::RGBx, width, height)); + auto video_caps = video_info.to_caps(); + + BOOST_LEAF_AUTO(pipeline, gst::w_pipeline::make("main")); + BOOST_LEAF_AUTO(mainloop, gst::w_mainloop::make()); + + BOOST_LEAF_AUTO(appsrc, gst::w_element_appsrc::make()); + auto appsrc_ref = gst::to_refptr(std::move(appsrc)); + appsrc_ref->set_caps(video_caps); + appsrc_ref->set_format(gst::w_format::Time); + + auto push_data = [&appsrc_ref, width, height, frames=0]() mutable { + auto buffer = gst::w_buffer::make(width * height * 4); + if (!buffer) { + return; + } + + //buffer->set_timestamp(appsrc_ref->clock()->get_time()); + buffer->set_duration(double(frames) / 120.0 * 1e9); + + { + // make sure buffer_map doesn't outlive buffer. + auto data_map = buffer->map_data_write(); + auto data = data_map->data(); + for (std::size_t y = 0; y < height; ++y) { + for (std::size_t x = 0; x < width; ++x) { + // a simple SDF rendering. a square in middle, + // and a circle of same size passing through diagonally. + const auto ratio = double(width) / height; + const auto t = double(frames % 120) / 120 - 0.5; + const auto u = (double(x) / width - 0.5) * ratio; + const auto v = double(y) / height - 0.5; + + const auto square = std::max(std::abs(u), std::abs(v)) - 0.2; + const auto circle = std::sqrt(std::pow(u - t, 2) + std::pow(v - t, 2)) - 0.2; + + data[y * width * 4 + x * 4 + 0] = (square < 0) * 255; + data[y * width * 4 + x * 4 + 1] = (circle < 0) * 255; + data[y * width * 4 + x * 4 + 2] = 0; + data[y * width * 4 + x * 4 + 3] = 0; + } + } + } + + appsrc_ref->emit_push_buffer(std::move(*buffer)); + + frames = (frames + 1) % 120; + }; + + std::size_t sourceid = 0; + appsrc_ref->hook_need_data([&](auto /* length */) { + if (sourceid) return; + sourceid = mainloop.idle_add(push_data); + }); + appsrc_ref->hook_enough_data([&] { + mainloop.idle_remove(sourceid); + sourceid = 0; + }); + + BOOST_LEAF_AUTO(test_video_src, + gst::w_element_factory::make_simple("videotestsrc", "src")); + BOOST_LEAF_AUTO(video_convert, + gst::w_element_factory::make_simple("videoconvert", "video_convert")); + BOOST_LEAF_AUTO(auto_video_sink, + gst::w_element_factory::make_simple("autovideosink", "sink")); + + auto flow = gst::w_flow_path::make( + appsrc_ref, + std::move(video_convert), + std::move(auto_video_sink) + ); + + BOOST_REQUIRE(pipeline.bin(flow)); + BOOST_REQUIRE(pipeline.link(flow)); + + BOOST_REQUIRE(pipeline.play()); + + auto timer = std::jthread([&mainloop, &pipeline]() { + for (int i = 1; i <= 5; ++i) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + pipeline.stop(); + mainloop.stop(); + }); + + mainloop.run(); + + return {}; + }, + [](const w_trace &p_trace) { + BOOST_ERROR(wolf::format("got error: {}", p_trace.to_string())); + }, + [] { + BOOST_ERROR(wolf::format("got an error")); + } + ); +} + +#endif // defined(WOLF_TEST) && defined(WOLF_MEDIA_GSTREAMER) diff --git a/wolf/media/test/image.hpp b/wolf/media/test/image.hpp new file mode 100644 index 000000000..6155b9b84 --- /dev/null +++ b/wolf/media/test/image.hpp @@ -0,0 +1,50 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#if defined(WOLF_TEST) && defined(WOLF_MEDIA_STB) + +#include +#include + +#include + +BOOST_AUTO_TEST_CASE(image_load_save_test) { + const wolf::system::w_leak_detector _detector = {}; + + std::cout << "entering test case 'image_load_save_test'" << std::endl; + + boost::leaf::try_handle_all( + [&]() -> boost::leaf::result { + using w_image = wolf::media::w_image; + using w_image_data = wolf::media::w_image_data; + + const auto &_rgb_file = std::filesystem::current_path().append("../../content/texture/rgb.png"); + auto _src_image_res = w_image::load(_rgb_file, 0); + // POOYA: don't use BOOST_LEAF_AUTO because CodeCov might break + if (!_src_image_res.has_error()) { + BOOST_LEAF_AUTO(_src_image, std::move(_src_image_res)); + const auto &_bmp_path = std::filesystem::current_path().append("/rgb_bmp.png"); + BOOST_LEAF_CHECK(w_image::save_bmp(_bmp_path, _src_image)); + + constexpr auto _quality = 90; + const auto &_jpg_path = std::filesystem::current_path().append("/rgb_jpg.png"); + BOOST_LEAF_CHECK(w_image::save_jpg(_jpg_path, _src_image, _quality)); + } + + return {}; + }, + [](const w_trace &p_trace) { + const auto _msg = wolf::format("got error: {}", p_trace.to_string()); + BOOST_ERROR(_msg); + }, + [] { + const auto _msg = wolf::format("got an error"); + BOOST_ERROR(_msg); + }); + + std::cout << "leaving test case 'image_load_save_test'" << std::endl; +} + +#endif \ No newline at end of file diff --git a/wolf/media/test/openal.hpp b/wolf/media/test/openal.hpp new file mode 100644 index 000000000..99d08ddd3 --- /dev/null +++ b/wolf/media/test/openal.hpp @@ -0,0 +1,45 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#if defined(WOLF_TEST) && defined(WOLF_MEDIA_OPENAL) + +#pragma once + +#include +#include + +#include + +BOOST_AUTO_TEST_CASE(openal_play_wav) { + using wolf::media::w_openal; + using wolf::media::w_openal_config; + + const wolf::system::w_leak_detector _detector = {}; + + // TODO (pooya: openAL has memoy leak https://github.com/kcat/openal-soft/issues/782) + auto a = w_openal(); + a.init(w_openal_config{}); + + auto _current_dir = std::filesystem::current_path().append("../../content/audio/sine.wav"); + std::cout << _current_dir.string(); + + BOOST_REQUIRE(std::filesystem::exists(_current_dir) == true); + + // let file_name = "sine.wav"; + // let wolf_dir = current_dir.join("wolf"); + // let file_path = if wolf_dir.exists() { wolf_dir.join(file_name) } + // else {current_dir.join(file_name)}; + + // make sure constructor works. later update with wav header. + // auto openal = w_openal{ + // w_openal_config{ + // .format = AL_FORMAT_STEREO16, + // .sample_rate = 48000, + // .channels = 1 + // } + //}; +} + +#endif diff --git a/wolf/media/w_image.cpp b/wolf/media/w_image.cpp new file mode 100644 index 000000000..e3a4003a1 --- /dev/null +++ b/wolf/media/w_image.cpp @@ -0,0 +1,126 @@ +#ifdef WOLF_MEDIA_STB + +#include "w_image.hpp" + +#define STB_IMAGE_IMPLEMENTATION +#include + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include + +#pragma region w_image_data + +using w_image_data = wolf::media::w_image_data; + +w_image_data::w_image_data(w_image_data &&p_other) noexcept { + _move(std::move(p_other)); +} + +// move assignment operator. +w_image_data &w_image_data::operator=(w_image_data &&p_other) noexcept { + _move(std::move(p_other)); + return *this; +} + +w_image_data::~w_image_data() noexcept { + this->width = 0; + this->height = 0; + this->comp = 0; + if (this->raw_data != nullptr) { + stbi_image_free(this->raw_data); + this->raw_data = nullptr; + } +} + +void w_image_data::_move(w_image_data &&p_other) noexcept { + this->width = p_other.width; + this->height = p_other.height; + this->comp = p_other.comp; + this->raw_data = std::exchange(p_other.raw_data, nullptr); +} + +#pragma endregion + +using w_image = wolf::media::w_image; + +boost::leaf::result +w_image::load(_In_ const std::filesystem::path &p_path, + int p_requested_component) { + if (p_path.empty()) { + return W_FAILURE(std::errc::invalid_argument, "could not load image"); + } + + const auto _path_str = p_path.string(); + + w_image_data _data = {}; + _data.raw_data = stbi_load(_path_str.c_str(), &_data.width, &_data.height, + &_data.comp, p_requested_component); + if (_data.raw_data == nullptr) { + return W_FAILURE(std::errc::operation_canceled, + "could not load image from file: " + _path_str); + } + + return _data; +} + +boost::leaf::result +w_image::save_bmp(_In_ const std::filesystem::path &p_path, + _In_ const w_image_data &p_image_data) { + if (p_path.empty() || !std::filesystem::exists(p_path.parent_path())) { + return W_FAILURE(std::errc::invalid_argument, + "could not save bmp file to the following path: " + + p_path.string()); + } + return stbi_write_bmp(p_path.string().c_str(), p_image_data.width, + p_image_data.height, p_image_data.comp, + p_image_data.raw_data); +} + +boost::leaf::result +w_image::save_hdr(_In_ const std::filesystem::path &p_path, + _In_ const gsl::span p_data, _In_ uint32_t p_width, + _In_ uint32_t p_height, _In_ uint32_t p_comp) { + + if (p_path.empty() || !std::filesystem::exists(p_path.parent_path())) { + return W_FAILURE(std::errc::invalid_argument, + "could not save hdr file to the the following path: " + + p_path.string()); + } + return stbi_write_hdr(p_path.string().c_str(), + gsl::narrow_cast(p_width), + gsl::narrow_cast(p_height), + gsl::narrow_cast(p_comp), + p_data.data()); +} + +boost::leaf::result +w_image::save_jpg(_In_ const std::filesystem::path &p_path, + _In_ const w_image_data &p_image_data, + _In_ uint32_t p_quality) { + if (p_path.empty() || !std::filesystem::exists(p_path.parent_path())) { + return W_FAILURE(std::errc::invalid_argument, + "could not save jpg file to the following path: " + + p_path.string()); + } + return stbi_write_jpg(p_path.string().c_str(), p_image_data.width, + p_image_data.height, p_image_data.comp, + p_image_data.raw_data, gsl::narrow_cast(p_quality)); +} + +boost::leaf::result +w_image::save_png(_In_ const std::filesystem::path &p_path, + _In_ const w_image_data &p_image_data, + _In_ uint32_t p_stride) { + if (p_path.empty() || !std::filesystem::exists(p_path.parent_path())) { + return W_FAILURE(std::errc::invalid_argument, + "could not save png file to the following path: " + + p_path.string()); + } + return stbi_write_png(p_path.string().c_str(), p_image_data.width, + p_image_data.height, p_image_data.comp, + p_image_data.raw_data, gsl::narrow_cast(p_stride)); +} + +#endif + + diff --git a/wolf/media/w_image.hpp b/wolf/media/w_image.hpp new file mode 100644 index 000000000..a53bc5c64 --- /dev/null +++ b/wolf/media/w_image.hpp @@ -0,0 +1,104 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#ifdef WOLF_MEDIA_STB + +#pragma once + +#include "w_image_data.hpp" +#include + +namespace wolf::media { + +class w_image_data { +public: + // constructor + W_API w_image_data() noexcept = default; + + // move constructor + W_API w_image_data(w_image_data &&p_other) noexcept; + + // move assignment operator. + W_API w_image_data &operator=(w_image_data &&p_other) noexcept; + + // destructor + W_API ~w_image_data() noexcept; + + uint8_t *raw_data = nullptr; + int width = -1; + int height = -1; + int comp = -1; + +private: + // copy constructor. + w_image_data(const w_image_data &) = delete; + // copy assignment operator. + w_image_data &operator=(const w_image_data &) = delete; + + // move implementation + void _move(w_image_data &&p_other) noexcept; +}; + +class w_image { +public: + /** + * load raw data from image path + * p_path, the image path + * p_requested_component, the requested component (default value is zero) + * returns raw bytes on success as result format + */ + W_API static boost::leaf::result + load(_In_ const std::filesystem::path &p_path, int p_requested_component = 0); + + /** + * save raw data in the format of bmp + * p_path, the destination path + * p_image_data, the image data + * returns zero on success as result format + */ + W_API static boost::leaf::result + save_bmp(_In_ const std::filesystem::path &p_path, + _In_ const w_image_data &p_image_data); + + /** + * save raw data in the format of hdr + * p_path, the destination path + * p_data, the raw data of RGBA + * p_width, the width of image + * p_height, the height of image + * p_comp, the component of image + * returns zero on success as result format + */ + W_API static boost::leaf::result + save_hdr(_In_ const std::filesystem::path &p_path, + _In_ gsl::span p_data, _In_ uint32_t p_width, + _In_ uint32_t p_height, _In_ uint32_t p_comp); + + /** + * save raw data in the format of jpg + * p_path, the destination path + * p_image_data, the image data + * p_quality, the quality of jpeg (an integer value between 1 and 100. + the higher quality looks better but results in a bigger image) + * returns zero on success as result format + */ + W_API static boost::leaf::result + save_jpg(_In_ const std::filesystem::path &p_path, + _In_ const w_image_data &p_image_data, _In_ uint32_t p_quality); + + /** + * save raw data in the format of png + * p_path, the destination path + * p_image_data, the image data + * p_stride, the stride of data + * returns zero on success as result format + */ + W_API static boost::leaf::result + save_png(_In_ const std::filesystem::path &p_path, + _In_ const w_image_data &p_image_data, _In_ uint32_t p_stride); +}; +} // namespace wolf::media + +#endif diff --git a/wolf/media/w_image_data.cpp b/wolf/media/w_image_data.cpp new file mode 100644 index 000000000..a2387ea29 --- /dev/null +++ b/wolf/media/w_image_data.cpp @@ -0,0 +1,41 @@ +#ifdef WOLF_MEDIA_STB + +#include "w_image.hpp" + +#include +#define STB_IMAGE_IMPLEMENTATION +#include +#include + +using w_image_data = wolf::media::w_image_data; + +w_image_data::w_image_data(w_image_data &&p_other) noexcept { + _move(std::move(p_other)); +} + +// move assignment operator. +w_image_data &w_image_data::operator=(w_image_data &&p_other) noexcept { + _move(std::move(p_other)); + return *this; +} + +w_image_data::~w_image_data() noexcept { + this->width = 0; + this->height = 0; + this->comp = 0; + if (this->raw_data != nullptr) { + stbi_image_free(this->raw_data); + this->raw_data = nullptr; + } +} + +void w_image_data::_move(w_image_data &&p_other) noexcept { + this->width = p_other.width; + this->height = p_other.height; + this->comp = p_other.comp; + this->raw_data = std::exchange(p_other.raw_data, nullptr); +} + +#endif + + diff --git a/wolf/media/w_image_data.hpp b/wolf/media/w_image_data.hpp new file mode 100644 index 000000000..caeec7fe8 --- /dev/null +++ b/wolf/media/w_image_data.hpp @@ -0,0 +1,14 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#ifdef WOLF_MEDIA_STB + +#pragma once + +#include + +namespace wolf::media {} // namespace wolf::media + +#endif diff --git a/wolf/media/w_openal.cpp b/wolf/media/w_openal.cpp new file mode 100644 index 000000000..2dc75d4e5 --- /dev/null +++ b/wolf/media/w_openal.cpp @@ -0,0 +1,268 @@ +#ifdef WOLF_MEDIA_OPENAL + +#include "w_openal.hpp" + +#include + +using w_openal = wolf::media::w_openal; +using w_openal_config = wolf::media::w_openal_config; + +ALsizei AL_APIENTRY w_openal::s_openal_callback(_In_ void *p_user_ptr, + _In_ void *p_data, + _In_ ALsizei p_size) { + const auto _openal_nn = gsl::narrow_cast(p_user_ptr); + size_t _bytes = 0; + + auto _read_offset = _openal_nn->_read_pos; + while (_bytes < p_size) { + /* If the write offset == read offset, there's nothing left in the + * ring-buffer. Break from the loop and give what has been written. + */ + const auto _write_offset = _openal_nn->_write_pos; + if (_write_offset == _read_offset) { + break; + } + + // If the write offset is behind the read offset, the readable + // portion wrapped around. Just read up to the end of the buffer in + // that case, otherwise read up to the write offset. Also limit the + // amount to copy given how much is remaining to write. + auto _todo = ((_write_offset < _read_offset) ? _openal_nn->_data_size + : _write_offset) - + _read_offset; + _todo = _todo > (p_size - _bytes) ? (p_size - _bytes) : _todo; + + // copy from the ring buffer to the provided output buffer. + // wrap the resulting read offset if it reached the end of the ring-buffer. + + memcpy(p_data, &_openal_nn->_data[_read_offset], _todo); + p_data = gsl::narrow_cast(p_data) + _todo; + _bytes += gsl::narrow_cast(_todo); + + _read_offset += _todo; + if (_read_offset == _openal_nn->_data_size) { + _read_offset = 0; + } + } + // finally, store the updated read offset, and return how many bytes have been + // written + _openal_nn->_read_pos = _read_offset; + + return gsl::narrow_cast(_bytes); +} + +w_openal::w_openal(w_openal &&p_other) noexcept { + + if (this == &p_other) + return; + + _release(); + + this->_config = std::move(p_other._config); + this->_device = std::exchange(p_other._device, nullptr); + this->_ctx = std::exchange(p_other._ctx, nullptr); + this->_data = std::exchange(p_other._data, nullptr); + this->_data_size = std::exchange(p_other._data_size, 0); + this->_read_pos = std::exchange(p_other._read_pos, 0); + this->_write_pos = std::exchange(p_other._write_pos, 0); + this->_buffer = std::exchange(p_other._buffer, 0); + this->_source = std::exchange(p_other._source, 0); + this->_start_offset = std::exchange(p_other._start_offset, 0); + this->_decoder_offset = std::exchange(p_other._decoder_offset, 0); + this->_total_read_bytes = std::exchange(p_other._total_read_bytes, 0); + this->_size_of_chunk = std::exchange(p_other._size_of_chunk, 0); + this->_callback_ptr = std::exchange(p_other._callback_ptr, nullptr); +} + +w_openal::~w_openal() noexcept { _release(); } + + void w_openal::reset() { + this->_read_pos = 0; + this->_write_pos = 0; + this->_decoder_offset = 0; + this->_total_read_bytes = 0; +} + +std::tuple w_openal::get_all_devices() { + std::string _input_devices; + std::string _output_devices; + + if (alcIsExtensionPresent(nullptr, "ALC_enumeration_EXT") == AL_TRUE) { + if (alcIsExtensionPresent(nullptr, "ALC_enumerate_all_EXT") == AL_TRUE) { + _output_devices = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); + } else { + _output_devices = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); + } + _input_devices = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); + } + + return std::make_tuple(_output_devices, _input_devices); +} + +std::string w_openal::get_last_error() { + const auto _error = alGetError(); + switch (_error) { + default: + return "unknown error code"; + case AL_NO_ERROR: + return ""; + case AL_INVALID_NAME: + return "AL_INVALID_NAME"; + case AL_INVALID_ENUM: + return "AL_INVALID_ENUM"; + case AL_INVALID_VALUE: + return "AL_INVALID_VALUE"; + case AL_INVALID_OPERATION: + return "AL_INVALID_OPERATION"; + case AL_OUT_OF_MEMORY: + return "AL_OUT_OF_MEMORY"; + } +} + +w_openal_config w_openal::get_config() const { return this->_config; } + +boost::leaf::result +w_openal::init(_In_ const w_openal_config &p_config) noexcept { + + auto _ret = 0; + this->_config = p_config; + + DEFER{ + if (_ret != 0) { + _release(); + } + }); + + // open and initialize a audio device + this->_device = alcOpenDevice(nullptr); + if (this->_device == nullptr) { + _ret = -1; + return W_FAILURE(std::errc::operation_canceled, + "could not open a openal device"); + } + + //this->_ctx = alcCreateContext(this->_device, nullptr); + //if (this->_ctx == nullptr || alcMakeContextCurrent(this->_ctx) == ALC_FALSE) { + // _ret = -1; + // return W_FAILURE(std::errc::operation_canceled, "could not get openal context"); + //} + + //// get the sound format, and figure out the OpenAL format + //const auto _format = this->_config.format; + //const auto _sample_rate = this->_config.sample_rate; + //const auto _number_of_channels = this->_config.channels; + + //this->_data = nullptr; + + //switch (_format) { + //default: + //case AL_FORMAT_MONO16: + //case AL_FORMAT_STEREO16: { + // // Signed 16-bit buffer format + // this->_size_of_chunk = sizeof(int16_t); + // break; + //} + //case AL_FORMAT_MONO8: + //case AL_FORMAT_STEREO8: { + // // Unsigned 8-bit buffer format + // this->_size_of_chunk = sizeof(uint8_t); + // break; + //} + //} + + //// generate the buffer + //alGenBuffers(1, &this->_buffer); + //auto _error = get_last_error(); + //if (!_error.empty()) { + // _ret = -1; + // return W_FAILURE(std::errc::operation_canceled, + // "could not generate buffer for openAL because " + _error); + //} + + //alGenSources(1, &this->_source); + //_error = get_last_error(); + //if (!_error.empty()) { + // _ret = -1; + // return W_FAILURE(std::errc::operation_canceled, + // "could not generate sources for openAL"); + //} + + //if (alIsExtensionPresent("AL_SOFTX_callback_buffer") != 0) { + // _ret = -1; + // return W_FAILURE(std::errc::operation_canceled, + // "could not get AL_SOFT_callback_buffer"); + //} + + //this->_callback_ptr = gsl::narrow_cast( + // alGetProcAddress("alBufferCallbackSOFT")); + //if (this->_callback_ptr == nullptr) { + // _ret = -1; + // return W_FAILURE(std::errc::operation_canceled, + // "could not get LPALBUFFERCALLBACKSOFT"); + //} + + //alcGetIntegerv(this->_device, ALC_REFRESH, 1, &this->_config.refresh_rate); + + //// set a 1s ring buffer size + //this->_data_size = + // gsl::narrow_cast(_sample_rate * _number_of_channels) * + // this->_size_of_chunk * sizeof(float); + + //if (this->_data != nullptr) { + // free(this->_data); + //} + //this->_data = gsl::narrow_cast(calloc(1, this->_data_size)); + //if (this->_data == nullptr) { + // _ret = -1; + // return W_FAILURE(std::errc::not_enough_memory, + // std::format("could not allocate {} byets for openal buffer", + // this->_data_size)); + //} + //this->_read_pos = 0; + //this->_write_pos = 0; + //this->_decoder_offset = 0; + //this->_callback_ptr(this->_buffer, _format, _sample_rate, s_openal_callback, + // this /* send openal object on user's data*/ + //); + + //alSourcei(this->_source, AL_BUFFER, (ALint)(this->_buffer)); + //_error = get_last_error(); + //if (!_error.empty()) { + // _ret = -1; + // return W_FAILURE(std::errc::not_enough_memory, + // "could not set openal source because " + _error); + //} + + return _ret; +} + +void w_openal::_release() noexcept { + if (this->_source != 0) { + alSourceRewind(this->_source); + alSourcei(this->_source, AL_BUFFER, 0); + alDeleteSources(1, &this->_source); + } + + if (this->_data != nullptr) { + free(this->_data); + this->_data = nullptr; + } + + if (this->_buffer != 0) { + alDeleteBuffers(1, &this->_buffer); + } + + if (this->_ctx != nullptr) { + alcDestroyContext(this->_ctx); + } + if (this->_device != nullptr) { + alcCloseDevice(this->_device); + this->_device = nullptr; + } + + this->_callback_ptr = nullptr; +} + +#endif + + diff --git a/wolf/media/w_openal.hpp b/wolf/media/w_openal.hpp new file mode 100644 index 000000000..96c28de9b --- /dev/null +++ b/wolf/media/w_openal.hpp @@ -0,0 +1,244 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#ifdef WOLF_MEDIA_OPENAL + +#pragma once + +#include + +#include +#include +#include +#include + +namespace wolf::media { + +struct w_openal_config { + // the audio format + ALenum format = AL_FORMAT_STEREO16; + // sample rate of audio + ALsizei sample_rate = 44100; + // refresh rate of audio + int refresh_rate = 25; + // number of channels + int channels = 2; +}; + +class w_openal { +public: + // default constructor + W_API w_openal() noexcept = default; + + // move constructor. + W_API w_openal(w_openal &&p_other) noexcept; + // move assignment operator. + W_API w_openal &operator=(w_openal &&p_other) = default; + + // destructor + W_API virtual ~w_openal() noexcept; + + /** + * initialize the openal + * @param p_config, the openal audio + * returns zero on success as result format + */ + W_API + boost::leaf::result + init(_In_ const w_openal_config &p_config) noexcept; + + /** + * update the openal + * @param p_audio_frame_buffer, the audio frame buffer + * @param p_audio_frame_buffer_len, the length of audio frame buffer + * @returns zero on success + */ + template + requires std::is_integral_v || std::is_integral_v + W_API boost::leaf::result + update(_In_ const T *p_audio_frame_buffer, + _In_ const size_t p_audio_frame_buffer_len) { + // get state and pos + ALenum _state = 0; + ALint _pos = 0; + alGetSourcei(this->_source, AL_SAMPLE_OFFSET, &_pos); + alGetSourcei(this->_source, AL_SOURCE_STATE, &_state); + + const auto _frame_size = this->_config.channels * this->_size_of_chunk; + auto _write_offset = this->_write_pos; + + while (this->_total_read_bytes < p_audio_frame_buffer_len) { + size_t _read_bytes = 0; + const size_t _read_offset = this->_read_pos; + if (_read_offset > _write_offset) { + /* + * Note that the ring buffer's writable space is one byte less + * than the available area because the write offset ending up + * at the read offset would be interpreted as being empty + * instead of full. + */ + auto _writable = _read_offset - _write_offset - 1; + if (_writable < _frame_size) { + break; + } + + _writable = (_writable > p_audio_frame_buffer_len) + ? p_audio_frame_buffer_len + : _writable; + std::memcpy(&this->_data[_write_offset], p_audio_frame_buffer, + _frame_size * + gsl::narrow_cast(_writable / _frame_size)); + + const auto _num_frames = _writable / _frame_size; + if (_num_frames < 1) { + break; + } + + _read_bytes = _num_frames * _frame_size; + _write_offset += _read_bytes; + + this->_total_read_bytes += _read_bytes; + } else { + /* + * If the read offset is at or behind the write offset, the + * writeable area (might) wrap around. Make sure the sample + * data can fit, and calculate how much can go in front before + * wrapping. + */ + auto _writable = !_read_offset ? this->_data_size - _write_offset - 1 + : (this->_data_size - _write_offset); + if (_writable < _frame_size) { + break; + } + + _writable = (_writable > p_audio_frame_buffer_len) + ? p_audio_frame_buffer_len + : _writable; + + std::memcpy(&this->_data[_write_offset], p_audio_frame_buffer, + _frame_size * + gsl::narrow_cast(_writable / _frame_size)); + + const auto _num_frames = _writable / _frame_size; + if (_num_frames < 1) { + break; + } + + _read_bytes = _num_frames * _frame_size; + _write_offset += _read_bytes; + if (_write_offset == this->_data_size) { + _write_offset = 0; + } + this->_total_read_bytes += _read_bytes; + } + this->_write_pos = _write_offset; + this->_decoder_offset += _read_bytes; + } + + if (_state != AL_PLAYING && _state != AL_PAUSED) { + + /* + if the source is not playing or paused, it either underrun + (AL_STOPPED) or is just getting started (AL_INITIAL). + if the ring buffer is empty, it's done, + otherwise play the source with what's available. + */ + const auto _readable = ((_write_offset >= this->_read_pos) + ? _write_offset + : (this->_data_size + _write_offset)) - + this->_read_pos; + if (_readable == 0) { + return W_FAILURE(std::errc::operation_canceled, + "no openal data avaiable for reading"); + } + + /* + * store the playback offset that the source will start reading from, + * so it can be tracked during playback. + */ + this->_start_offset = this->_decoder_offset - _readable; + alSourcePlay(this->_source); + + auto _error = get_last_error(); + if (!_error.empty()) { + return W_FAILURE(std::errc::operation_canceled, + "error while updating openal because: " + _error); + } + } + + this->_total_read_bytes = 0; + + return 0; + } + + /** + * reset the openal + * @returns void + */ + W_API + void reset(); + + /** + * get openal config + * @returns the openal config + */ + W_API + w_openal_config get_config() const; + + /** + * returns all output/input devices + * @returns output_devices, input_devices + */ + W_API + static std::tuple get_all_devices(); + + /** + * get the last error + * @returns last error of openal + */ + W_API + static std::string get_last_error(); + + private: + // disable copy constructor + w_openal(const w_openal &) = delete; + // disable copy operator + w_openal &operator=(const w_openal &) = delete; + + static ALsizei AL_APIENTRY s_openal_callback(_In_ void *p_user_ptr, + _In_ void *p_data, + _In_ ALsizei p_size); + void _release() noexcept; + + w_openal_config _config = {}; + + ALCdevice *_device = nullptr; + ALCcontext *_ctx = nullptr; + + // a lockless ring-buffer (supports single-provider, single-consumer + // operation) + ALbyte *_data = nullptr; + size_t _data_size = 0; + size_t _read_pos = 0; + size_t _write_pos = 0; + + // The buffer to get the callback, and source to play with + ALuint _buffer = 0; + ALuint _source = 0; + size_t _start_offset = 0; + + // Handle for the audio file to decode + size_t _decoder_offset = 0; + + size_t _total_read_bytes = 0; + size_t _size_of_chunk = 0; + LPALBUFFERCALLBACKSOFT _callback_ptr = nullptr; +}; +} // namespace wolf::media + +#endif + + + diff --git a/wolf/ml/nudity_detection/w_nudity_detection.cpp b/wolf/ml/nudity_detection/w_nudity_detection.cpp new file mode 100644 index 000000000..a78d67a4e --- /dev/null +++ b/wolf/ml/nudity_detection/w_nudity_detection.cpp @@ -0,0 +1,80 @@ +#include "w_nudity_detection.hpp" + +#include + +#include +#include + +#include "../w_common.hpp" +#include + +using w_nud_det = wolf::ml::nudet::w_nud_det; + +w_nud_det::w_nud_det(_In_ std::string& nudity_detection_model_path) { + _model = torch::jit::load(nudity_detection_model_path); + _model.to(torch::kCUDA); + _model.eval(); + + network_warm_up(get_env_int("TEMP_IMAGE_HEIGHT"), get_env_int("TEMP_IMAGE_WIDTH")); +} + +w_nud_det::~w_nud_det() = default; + +auto w_nud_det::nudity_detection(_In_ uint8_t* pImageData, _In_ const int pImageWidth, + _In_ const int pImageHeight, _In_ const int pImageChannels) + -> std::vector { + std::vector result; + + // // Put image in tensor + // std::vector + // image_data; + // auto e = std::end(image_data); + // const int total = pImageWidth * pImageHeight * pImageChannels; + // image_data.insert(e, pImageData, pImageData + total); + + // auto input = cppflow::tensor(image_data, {pImageHeight, pImageWidth, pImageChannels}); + // input = cppflow::cast(input, TF_UINT8, TF_FLOAT, false); + // input = input / 255.f; + // input = cppflow::expand_dims(input, 0, static_cast(3)); + // auto output = _model(input); + + // result = output.get_data(); + + torch::Tensor tensor_image = + torch::from_blob(pImageData, {1, pImageHeight, pImageWidth, pImageChannels}, torch::kByte); + + tensor_image = tensor_image.permute({0, 3, 1, 2}); + tensor_image = tensor_image.toType(torch::kFloat); + tensor_image = tensor_image.div(255); + + // Define normalization constants + const float kNormalizationMean[] = {0.485f, 0.456f, 0.406f}; + const float kNormalizationStd[] = {0.229f, 0.224f, 0.225f}; + + // Create the normalization transform + auto normalization_transform = torch::data::transforms::Normalize( + {kNormalizationMean[0], kNormalizationMean[1], kNormalizationMean[2]}, + {kNormalizationStd[0], kNormalizationStd[1], kNormalizationStd[2]}); + + // Apply normalization transform to the image tensor + tensor_image = normalization_transform(tensor_image); + + tensor_image = tensor_image.to(torch::kCUDA); + + torch::Tensor output; + + output = _model.forward({tensor_image}).toTensor(); + + for (int i = 0; i < output.sizes()[1]; i++) { + result.push_back(output[0][i].item()); + } + + return result; +} + +auto w_nud_det::network_warm_up(_In_ int pHeight, _In_ int pWidth) -> void { + for (int i = 0; i < 2; i++) { + cv::Mat const temp_image = cv::Mat(cv::Size(pWidth, pHeight), CV_8UC3, cv::Scalar(0, 0, 0)); + auto result = nudity_detection(temp_image.data, pWidth, pHeight, temp_image.channels()); + } +} diff --git a/wolf/ml/nudity_detection/w_nudity_detection.hpp b/wolf/ml/nudity_detection/w_nudity_detection.hpp new file mode 100644 index 000000000..b8b3708ff --- /dev/null +++ b/wolf/ml/nudity_detection/w_nudity_detection.hpp @@ -0,0 +1,55 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#pragma once + +#include + +#include +#include + +// #include "cppflow/cppflow.h" +// #include "dlib_export.h" +// #include "salieri.h" + +#include "wolf.hpp" + +namespace wolf::ml::nudet { + +class w_nud_det { + public: + /*! + The constructor of the class. + */ + explicit w_nud_det(_In_ std::string& nudity_detection_model_path); + + /*! + The deconstructor of the class. + */ + ~w_nud_det(); + + /*! + The function gets a string as input and return the boolean representation of the input. + + \param pVariable the string input. + \return a vector of float numbers each between 0 to 1 that shows the nudity factors. + */ + W_API auto nudity_detection(_In_ uint8_t* pImageData, _In_ int pImageWidth, _In_ int pImageHeight, + _In_ int pImageChannels) -> std::vector; + + /*! + The function uses to warm-up the network in the w_nud_det class initialization. + + \param pHeight the temp image height. + \param pWidth the temp image width. + \return (void) + */ + W_API auto network_warm_up(_In_ int pHeight, _In_ int pWidth) -> void; + + private: + // :cppflow:model _model; + torch::jit::script::Module _model; +}; +} // namespace wolf::ml::nudet diff --git a/wolf/ml/referee_ocr/salieri.h b/wolf/ml/referee_ocr/salieri.h new file mode 100755 index 000000000..ab445f0ce --- /dev/null +++ b/wolf/ml/referee_ocr/salieri.h @@ -0,0 +1,1563 @@ +/** + * Salieri + * v1 + * + * Salieri is a header which contains definitions for the Microsoft + * source-code annotation language (SAL). It is *not* an + * implementation of SAL; that's for compilers and static analyzers. + * For the most part we just define macros to nothing. + * + * The goal is to allow projects to use SAL without creating a hard + * dependency on it (i.e., you can still use compilers other than + * MSVC). Simply include `salieri.h`, which you can/should distribute + * with your code, instead of ``. + * + * Multiple copies of Salieri can be included safely, even different + * versions. Including a newer version will simiply replace older + * definitions with newer ones, and including older versions will have + * no effect. + * + * I don't think anything in here is copyrightable, but just in case: + * + * To the extent possible under law, the author(s) have dedicated + * all copyright and related and neighboring rights to this software + * to the public domain worldwide. This software is distributed + * without any warranty. + * + * For details, see . + */ + +#if !defined(SALIERI_VERSION) || (SALIERI_VERSION < 1) + +/* TODO: figure out when first appeared. */ +#if defined(_MSC_VER) +#include +#elif defined(__has_include) +#if __has_include() +#include +#endif +#endif + +/* The main concern for the implementation is that we don't want to + * replace annotations from , but we *do* want to replace + * annotations from older versions of Salieri. To keep track of + * everything, when we (Salieri) define a SAL annotation, we also + * define SALIERI_DEFINED_${annotation} (with the leading and trailing + * '-' stripped). Then, before redefining an annotation macro we + * check to make sure SALIERI_DEFINED_${annontation} is defined. + * + * This means you can safely use Salieri in a public header in your + * project. + */ + +/* Function Parameters & Return Value + * + * https://msdn.microsoft.com/en-us/library/hh916382.aspx + *****/ + +/* Pointer Parameters */ + +#if defined(_In_) && defined(SALIERI_DEFINED_In) +#undef _In_ +#endif +#if !defined(_In_) +#define _In_ +#define SALIERI_DEFINED_In +#endif + +#if defined(_Out_) && defined(SALIERI_DEFINED_Out) +#undef _Out_ +#endif +#if !defined(_Out_) +#define _Out_ +#define SALIERI_DEFINED_Out +#endif + +#if defined(_Inout_) && defined(SALIERI_DEFINED_Inout) +#undef _Inout_ +#endif +#if !defined(_Inout_) +#define _Inout_ +#define SALIERI_DEFINED_Inout +#endif + +#if defined(_In_z_) && defined(SALIERI_DEFINED_In_z) +#undef _In_z_ +#endif +#if !defined(_In_z_) +#define _In_z_ +#define SALIERI_DEFINED_In_z +#endif + +#if defined(_Inout_z_) && defined(SALIERI_DEFINED_Inout_z) +#undef _Inout_z_ +#endif +#if !defined(_Inout_z_) +#define _Inout_z_ +#define SALIERI_DEFINED_Inout_z +#endif + +#if defined(_In_reads_) && defined(SALIERI_DEFINED_In_reads) +#undef _In_reads_ +#endif +#if !defined(_In_reads_) +#define _In_reads_(s) +#define SALIERI_DEFINED_In_reads +#endif + +#if defined(_In_reads_bytes_) && defined(SALIERI_DEFINED_In_reads_bytes) +#undef _In_reads_bytes_ +#endif +#if !defined(_In_reads_bytes_) +#define _In_reads_bytes_(s) +#define SALIERI_DEFINED_In_reads_bytes +#endif + +#if defined(_In_reads_z_) && defined(SALIERI_DEFINED_In_reads_z) +#undef _In_reads_z_ +#endif +#if !defined(_In_reads_z_) +#define _In_reads_z_(s) +#define SALIERI_DEFINED_In_reads_z +#endif + +#if defined(_In_reads_or_z_) && defined(SALIERI_DEFINED_In_reads_or_z) +#undef _In_reads_or_z_ +#endif +#if !defined(_In_reads_or_z_) +#define _In_reads_or_z_(s) +#define SALIERI_DEFINED_In_reads_or_z +#endif + +#if defined(_Out_writes_) && defined(SALIERI_DEFINED_Out_writes) +#undef _Out_writes_ +#endif +#if !defined(_Out_writes_) +#define _Out_writes_(s) +#define SALIERI_DEFINED_Out_writes +#endif + +#if defined(_Out_writes_bytes_) && defined(SALIERI_DEFINED_Out_writes_bytes) +#undef _Out_writes_bytes_ +#endif +#if !defined(_Out_writes_bytes_) +#define _Out_writes_bytes_(s) +#define SALIERI_DEFINED_Out_writes_bytes +#endif + +#if defined(_Out_writes_z_) && defined(SALIERI_DEFINED_Out_writes_z) +#undef _Out_writes_z_ +#endif +#if !defined(_Out_writes_z_) +#define _Out_writes_z_(s) +#define SALIERI_DEFINED_Out_writes_z +#endif + +#if defined(_Inout_updates_) && defined(SALIERI_DEFINED_Inout_updates) +#undef _Inout_updates_ +#endif +#if !defined(_Inout_updates_) +#define _Inout_updates_(s) +#define SALIERI_DEFINED_Inout_updates +#endif + +#if defined(_Inout_updates_bytes_) && defined(SALIERI_DEFINED_Inout_updates_bytes) +#undef _Inout_updates_bytes_ +#endif +#if !defined(_Inout_updates_bytes_) +#define _Inout_updates_bytes_(s) +#define SALIERI_DEFINED_Inout_updates_bytes +#endif + +#if defined(_Inout_updates_z_) && defined(SALIERI_DEFINED_Inout_updates_z) +#undef _Inout_updates_z_ +#endif +#if !defined(_Inout_updates_z_) +#define _Inout_updates_z_(s) +#define SALIERI_DEFINED_Inout_updates_z +#endif + +#if defined(_Out_writes_to_) && defined(SALIERI_DEFINED_Out_writes_to) +#undef _Out_writes_to_ +#endif +#if !defined(_Out_writes_to_) +#define _Out_writes_to_(s, c) +#define SALIERI_DEFINED_Out_writes_to +#endif + +#if defined(_Out_writes_bytes_to_) && defined(SALIERI_DEFINED_Out_writes_bytes_to) +#undef _Out_writes_bytes_to_ +#endif +#if !defined(_Out_writes_bytes_to_) +#define _Out_writes_bytes_to_(s, c) +#define SALIERI_DEFINED_Out_writes_bytes_to +#endif + +#if defined(_Out_writes_all_) && defined(SALIERI_DEFINED_Out_writes_all) +#undef _Out_writes_all_ +#endif +#if !defined(_Out_writes_all_) +#define _Out_writes_all_(s) +#define SALIERI_DEFINED_Out_writes_all +#endif + +#if defined(_Out_writes_bytes_all_) && defined(SALIERI_DEFINED_Out_writes_bytes_all) +#undef _Out_writes_bytes_all_ +#endif +#if !defined(_Out_writes_bytes_all_) +#define _Out_writes_bytes_all_(s) +#define SALIERI_DEFINED_Out_writes_bytes_all +#endif + +#if defined(_In_updates_to_) && defined(SALIERI_DEFINED_In_updates_to) +#undef _In_updates_to_ +#endif +#if !defined(_In_updates_to_) +#define _In_updates_to_(s, c) +#define SALIERI_DEFINED_In_updates_to +#endif + +#if defined(_In_updates_bytes_to_) && defined(SALIERI_DEFINED_In_updates_bytes_to) +#undef _In_updates_bytes_to_ +#endif +#if !defined(_In_updates_bytes_to_) +#define _In_updates_bytes_to_(s, c) +#define SALIERI_DEFINED_In_updates_bytes_to +#endif + +#if defined(_Inout_updates_z_) && defined(SALIERI_DEFINED_Inout_updates_z) +#undef _Inout_updates_z_ +#endif +#if !defined(_Inout_updates_z_) +#define _Inout_updates_z_(s) +#define SALIERI_DEFINED_Inout_updates_z +#endif + +#if defined(_Out_writes_to_) && defined(SALIERI_DEFINED_Out_writes_to) +#undef _Out_writes_to_ +#endif +#if !defined(_Out_writes_to_) +#define _Out_writes_to_(s, c) +#define SALIERI_DEFINED_Out_writes_to +#endif + +#if defined(_Out_writes_bytes_to_) && defined(SALIERI_DEFINED_Out_writes_bytes_to) +#undef _Out_writes_bytes_to_ +#endif +#if !defined(_Out_writes_bytes_to_) +#define _Out_writes_bytes_to_(s, c) +#define SALIERI_DEFINED_Out_writes_bytes_to +#endif + +#if defined(_Out_writes_all_) && defined(SALIERI_DEFINED_Out_writes_all) +#undef _Out_writes_all_ +#endif +#if !defined(_Out_writes_all_) +#define _Out_writes_all_(s) +#define SALIERI_DEFINED_Out_writes_all +#endif + +#if defined(_Out_writes_bytes_all_) && defined(SALIERI_DEFINED_Out_writes_bytes_all) +#undef _Out_writes_bytes_all_ +#endif +#if !defined(_Out_writes_bytes_all_) +#define _Out_writes_bytes_all_(s) +#define SALIERI_DEFINED_Out_writes_bytes_all +#endif + +#if defined(_Inout_updates_to_) && defined(SALIERI_DEFINED_Inout_updates_to) +#undef _Inout_updates_to_ +#endif +#if !defined(_Inout_updates_to_) +#define _Inout_updates_to_(s, c) +#define SALIERI_DEFINED_Inout_updates_to +#endif + +#if defined(_Inout_updates_bytes_to_) && defined(SALIERI_DEFINED_Inout_updates_bytes_to) +#undef _Inout_updates_bytes_to_ +#endif +#if !defined(_Inout_updates_bytes_to_) +#define _Inout_updates_bytes_to_(s, c) +#define SALIERI_DEFINED_Inout_updates_bytes_to +#endif + +#if defined(_Inout_updates_all_) && defined(SALIERI_DEFINED_Inout_updates_all) +#undef _Inout_updates_all_ +#endif +#if !defined(_Inout_updates_all_) +#define _Inout_updates_all_(s) +#define SALIERI_DEFINED_Inout_updates_all +#endif + +#if defined(_Inout_updates_bytes_all_) && defined(SALIERI_DEFINED_Inout_updates_bytes_all) +#undef _Inout_updates_bytes_all_ +#endif +#if !defined(_Inout_updates_bytes_all_) +#define _Inout_updates_bytes_all_(s) +#define SALIERI_DEFINED_Inout_updates_bytes_all +#endif + +#if defined(_In_reads_to_ptr_) && defined(SALIERI_DEFINED_In_reads_to_ptr) +#undef _In_reads_to_ptr_ +#endif +#if !defined(_In_reads_to_ptr_) +#define _In_reads_to_ptr_(p) +#define SALIERI_DEFINED_In_reads_to_ptr +#endif + +#if defined(_In_reads_to_ptr_z_) && defined(SALIERI_DEFINED_In_reads_to_ptr_z) +#undef _In_reads_to_ptr_z_ +#endif +#if !defined(_In_reads_to_ptr_z_) +#define _In_reads_to_ptr_z_(p) +#define SALIERI_DEFINED_In_reads_to_ptr_z +#endif + +#if defined(_Out_writes_to_ptr_) && defined(SALIERI_DEFINED_Out_writes_to_ptr) +#undef _Out_writes_to_ptr_ +#endif +#if !defined(_Out_writes_to_ptr_) +#define _Out_writes_to_ptr_(p) +#define SALIERI_DEFINED_Out_writes_to_ptr +#endif + +#if defined(_Out_writes_to_ptr_z_) && defined(SALIERI_DEFINED_Out_writes_to_ptr_z) +#undef _Out_writes_to_ptr_z_ +#endif +#if !defined(_Out_writes_to_ptr_z_) +#define _Out_writes_to_ptr_z_(p) +#define SALIERI_DEFINED_Out_writes_to_ptr_z +#endif + +/* Optional Pointer Parameters */ + +#if defined(_In_opt_) && defined(SALIERI_DEFINED_In_opt) +#undef _In_opt_ +#endif +#if !defined(_In_opt_) +#define _In_opt_ +#define SALIERI_DEFINED_In_opt +#endif + +#if defined(_Out_opt_) && defined(SALIERI_DEFINED_Out_opt) +#undef _Out_opt_ +#endif +#if !defined(_Out_opt_) +#define _Out_opt_ +#define SALIERI_DEFINED_Out_opt +#endif + +#if defined(_Inout_opt_) && defined(SALIERI_DEFINED_Inout_opt) +#undef _Inout_opt_ +#endif +#if !defined(_Inout_opt_) +#define _Inout_opt_ +#define SALIERI_DEFINED_Inout_opt +#endif + +#if defined(_In_opt_z_) && defined(SALIERI_DEFINED_In_opt_z) +#undef _In_opt_z_ +#endif +#if !defined(_In_opt_z_) +#define _In_opt_z_ +#define SALIERI_DEFINED_In_opt_z +#endif + +#if defined(_Inout_opt_z_) && defined(SALIERI_DEFINED_Inout_opt_z) +#undef _Inout_opt_z_ +#endif +#if !defined(_Inout_opt_z_) +#define _Inout_opt_z_ +#define SALIERI_DEFINED_Inout_opt_z +#endif + +#if defined(_In_reads_opt_) && defined(SALIERI_DEFINED_In_reads_opt) +#undef _In_reads_opt_ +#endif +#if !defined(_In_reads_opt_) +#define _In_reads_opt_(s) +#define SALIERI_DEFINED_In_reads_opt +#endif + +#if defined(_In_reads_bytes_opt_) && defined(SALIERI_DEFINED_In_reads_bytes_opt) +#undef _In_reads_bytes_opt_ +#endif +#if !defined(_In_reads_bytes_opt_) +#define _In_reads_bytes_opt_(s) +#define SALIERI_DEFINED_In_reads_bytes_opt +#endif + +#if defined(_In_reads_opt_z_) && defined(SALIERI_DEFINED_In_reads_opt_z) +#undef _In_reads_opt_z_ +#endif +#if !defined(_In_reads_opt_z_) +#define _In_reads_opt_z_(s) +#define SALIERI_DEFINED_In_reads_opt_z +#endif + +#if defined(_Out_writes_opt_) && defined(SALIERI_DEFINED_Out_writes_opt) +#undef _Out_writes_opt_ +#endif +#if !defined(_Out_writes_opt_) +#define _Out_writes_opt_(s) +#define SALIERI_DEFINED_Out_writes_opt +#endif + +#if defined(_Out_writes_bytes_) && defined(SALIERI_DEFINED_Out_writes_bytes) +#undef _Out_writes_bytes_ +#endif +#if !defined(_Out_writes_bytes_) +#define _Out_writes_bytes_(s) +#define SALIERI_DEFINED_Out_writes_bytes +#endif + +#if defined(_Out_writes_opt_z_) && defined(SALIERI_DEFINED_Out_writes_opt_z) +#undef _Out_writes_opt_z_ +#endif +#if !defined(_Out_writes_opt_z_) +#define _Out_writes_opt_z_(s) +#define SALIERI_DEFINED_Out_writes_opt_z +#endif + +#if defined(_Inout_updates_opt_) && defined(SALIERI_DEFINED_Inout_updates_opt) +#undef _Inout_updates_opt_ +#endif +#if !defined(_Inout_updates_opt_) +#define _Inout_updates_opt_(s) +#define SALIERI_DEFINED_Inout_updates_opt +#endif + +#if defined(_Inout_updates_bytes_opt_) && defined(SALIERI_DEFINED_Inout_updates_bytes_opt) +#undef _Inout_updates_bytes_opt_ +#endif +#if !defined(_Inout_updates_bytes_opt_) +#define _Inout_updates_bytes_opt_(s) +#define SALIERI_DEFINED_Inout_updates_bytes_opt +#endif + +#if defined(_Inout_updates_opt_z_) && defined(SALIERI_DEFINED_Inout_updates_opt_z) +#undef _Inout_updates_opt_z_ +#endif +#if !defined(_Inout_updates_opt_z_) +#define _Inout_updates_opt_z_(s) +#define SALIERI_DEFINED_Inout_updates_opt_z +#endif + +#if defined(_Out_writes_to_opt_) && defined(SALIERI_DEFINED_Out_writes_to_opt) +#undef _Out_writes_to_opt_ +#endif +#if !defined(_Out_writes_to_opt_) +#define _Out_writes_to_opt_(s, c) +#define SALIERI_DEFINED_Out_writes_to_opt +#endif + +#if defined(_Out_writes_bytes_to_opt_) && defined(SALIERI_DEFINED_Out_writes_bytes_to_opt) +#undef _Out_writes_bytes_to_opt_ +#endif +#if !defined(_Out_writes_bytes_to_opt_) +#define _Out_writes_bytes_to_opt_(s, c) +#define SALIERI_DEFINED_Out_writes_bytes_to_opt +#endif + +#if defined(_Out_writes_all_opt_) && defined(SALIERI_DEFINED_Out_writes_all_opt) +#undef _Out_writes_all_opt_ +#endif +#if !defined(_Out_writes_all_opt_) +#define _Out_writes_all_opt_(s) +#define SALIERI_DEFINED_Out_writes_all_opt +#endif + +#if defined(_Out_writes_bytes_all_opt_) && defined(SALIERI_DEFINED_Out_writes_bytes_all_opt) +#undef _Out_writes_bytes_all_opt_ +#endif +#if !defined(_Out_writes_bytes_all_opt_) +#define _Out_writes_bytes_all_opt_(s) +#define SALIERI_DEFINED_Out_writes_bytes_all_opt +#endif + +#if defined(_In_updates_to_opt_) && defined(SALIERI_DEFINED_In_updates_to_opt) +#undef _In_updates_to_opt_ +#endif +#if !defined(_In_updates_to_opt_) +#define _In_updates_to_opt_(s, c) +#define SALIERI_DEFINED_In_updates_to_opt +#endif + +#if defined(_In_updates_bytes_to_opt_) && defined(SALIERI_DEFINED_In_updates_bytes_to_opt) +#undef _In_updates_bytes_to_opt_ +#endif +#if !defined(_In_updates_bytes_to_opt_) +#define _In_updates_bytes_to_opt_(s, c) +#define SALIERI_DEFINED_In_updates_bytes_to_opt +#endif + +#if defined(_Inout_updates_all_opt_) && defined(SALIERI_DEFINED_Inout_updates_all_opt) +#undef _Inout_updates_all_opt_ +#endif +#if !defined(_Inout_updates_all_opt_) +#define _Inout_updates_all_opt_(s) +#define SALIERI_DEFINED_Inout_updates_all_opt +#endif + +#if defined(_Inout_updates_bytes_all_opt_) && defined(SALIERI_DEFINED_Inout_updates_bytes_all_opt) +#undef _Inout_updates_bytes_all_opt_ +#endif +#if !defined(_Inout_updates_bytes_all_opt_) +#define _Inout_updates_bytes_all_opt_(s) +#define SALIERI_DEFINED_Inout_updates_bytes_all_opt +#endif + +#if defined(_In_reads_to_ptr_opt_) && defined(SALIERI_DEFINED_In_reads_to_ptr_opt) +#undef _In_reads_to_ptr_opt_ +#endif +#if !defined(_In_reads_to_ptr_opt_) +#define _In_reads_to_ptr_opt_(p) +#define SALIERI_DEFINED_In_reads_to_ptr_opt +#endif + +#if defined(_In_reads_to_ptr_opt_z_) && defined(SALIERI_DEFINED_In_reads_to_ptr_opt_z) +#undef _In_reads_to_ptr_opt_z_ +#endif +#if !defined(_In_reads_to_ptr_opt_z_) +#define _In_reads_to_ptr_opt_z_(p) +#define SALIERI_DEFINED_In_reads_to_ptr_opt_z +#endif + +#if defined(_Out_writes_to_ptr_opt_) && defined(SALIERI_DEFINED_Out_writes_to_ptr_opt) +#undef _Out_writes_to_ptr_opt_ +#endif +#if !defined(_Out_writes_to_ptr_opt_) +#define _Out_writes_to_ptr_opt_(p) +#define SALIERI_DEFINED_Out_writes_to_ptr_opt +#endif + +#if defined(_Out_writes_to_ptr_opt_z_) && defined(SALIERI_DEFINED_Out_writes_to_ptr_opt_z) +#undef _Out_writes_to_ptr_opt_z_ +#endif +#if !defined(_Out_writes_to_ptr_opt_z_) +#define _Out_writes_to_ptr_opt_z_(p) +#define SALIERI_DEFINED_Out_writes_to_ptr_opt_z +#endif + +/* Output Pointer Parameters */ + +#if defined(_Outptr_) && defined(SALIERI_DEFINED_Outptr) +#undef _Outptr_ +#endif +#if !defined(_Outptr_) +#define _Outptr_ +#define SALIERI_DEFINED_Outptr +#endif + +#if defined(_Outptr_opt_) && defined(SALIERI_DEFINED_Outptr_opt) +#undef _Outptr_opt_ +#endif +#if !defined(_Outptr_opt_) +#define _Outptr_opt_ +#define SALIERI_DEFINED_Outptr_opt +#endif + +#if defined(_Outptr_result_maybenull_) && defined(SALIERI_DEFINED_Outptr_result_maybenull) +#undef _Outptr_result_maybenull_ +#endif +#if !defined(_Outptr_result_maybenull_) +#define _Outptr_result_maybenull_ +#define SALIERI_DEFINED_Outptr_result_maybenull +#endif + +#if defined(_Outptr_opt_result_maybenull_) && defined(SALIERI_DEFINED_Outptr_opt_result_maybenull) +#undef _Outptr_opt_result_maybenull_ +#endif +#if !defined(_Outptr_opt_result_maybenull_) +#define _Outptr_opt_result_maybenull_ +#define SALIERI_DEFINED_Outptr_opt_result_maybenull +#endif + +#if defined(_Outptr_result_z_) && defined(SALIERI_DEFINED_Outptr_result_z) +#undef _Outptr_result_z_ +#endif +#if !defined(_Outptr_result_z_) +#define _Outptr_result_z_ +#define SALIERI_DEFINED_Outptr_result_z +#endif + +#if defined(_Outptr_opt_result_z_) && defined(SALIERI_DEFINED_Outptr_opt_result_z) +#undef _Outptr_opt_result_z_ +#endif +#if !defined(_Outptr_opt_result_z_) +#define _Outptr_opt_result_z_ +#define SALIERI_DEFINED_Outptr_opt_result_z +#endif + +#if defined(_Outptr_result_maybenull_z_) && defined(SALIERI_DEFINED_Outptr_result_maybenull_z) +#undef _Outptr_result_maybenull_z_ +#endif +#if !defined(_Outptr_result_maybenull_z_) +#define _Outptr_result_maybenull_z_ +#define SALIERI_DEFINED_Outptr_result_maybenull_z +#endif + +#if defined(_Outptr_opt_result_maybenull_z_) && defined(SALIERI_DEFINED_Outptr_opt_result_maybenull_z) +#undef _Outptr_opt_result_maybenull_z_ +#endif +#if !defined(_Outptr_opt_result_maybenull_z_) +#define _Outptr_opt_result_maybenull_z_ +#define SALIERI_DEFINED_Outptr_opt_result_maybenull_z +#endif + +#if defined(_COM_Outptr_) && defined(SALIERI_DEFINED_COM_Outptr) +#undef _COM_Outptr_ +#endif +#if !defined(_COM_Outptr_) +#define _COM_Outptr_ +#define SALIERI_DEFINED_COM_Outptr +#endif + +#if defined(_COM_Outptr_opt_) && defined(SALIERI_DEFINED_COM_Outptr_opt) +#undef _COM_Outptr_opt_ +#endif +#if !defined(_COM_Outptr_opt_) +#define _COM_Outptr_opt_ +#define SALIERI_DEFINED_COM_Outptr_opt +#endif + +#if defined(_COM_Outptr_result_maybenull_) && defined(SALIERI_DEFINED_COM_Outptr_result_maybenull) +#undef _COM_Outptr_result_maybenull_ +#endif +#if !defined(_COM_Outptr_result_maybenull_) +#define _COM_Outptr_result_maybenull_ +#define SALIERI_DEFINED_COM_Outptr_result_maybenull +#endif + +#if defined(_Outptr_opt_result_maybenull_) && defined(SALIERI_DEFINED_Outptr_opt_result_maybenull) +#undef _Outptr_opt_result_maybenull_ +#endif +#if !defined(_Outptr_opt_result_maybenull_) +#define _Outptr_opt_result_maybenull_ +#define SALIERI_DEFINED_Outptr_opt_result_maybenull +#endif + +#if defined(_Outptr_result_buffer_) && defined(SALIERI_DEFINED_Outptr_result_buffer) +#undef _Outptr_result_buffer_ +#endif +#if !defined(_Outptr_result_buffer_) +#define _Outptr_result_buffer_(s) +#define SALIERI_DEFINED_Outptr_result_buffer +#endif + +#if defined(_Outptr_result_bytebuffer_) && defined(SALIERI_DEFINED_Outptr_result_bytebuffer) +#undef _Outptr_result_bytebuffer_ +#endif +#if !defined(_Outptr_result_bytebuffer_) +#define _Outptr_result_bytebuffer_(s) +#define SALIERI_DEFINED_Outptr_result_bytebuffer +#endif + +#if defined(_Outptr_opt_result_buffer_) && defined(SALIERI_DEFINED_Outptr_opt_result_buffer) +#undef _Outptr_opt_result_buffer_ +#endif +#if !defined(_Outptr_opt_result_buffer_) +#define _Outptr_opt_result_buffer_(s) +#define SALIERI_DEFINED_Outptr_opt_result_buffer +#endif + +#if defined(_Outptr_opt_result_bytebuffer_) && defined(SALIERI_DEFINED_Outptr_opt_result_bytebuffer) +#undef _Outptr_opt_result_bytebuffer_ +#endif +#if !defined(_Outptr_opt_result_bytebuffer_) +#define _Outptr_opt_result_bytebuffer_(s) +#define SALIERI_DEFINED_Outptr_opt_result_bytebuffer +#endif + +#if defined(_Outptr_result_buffer_to_) && defined(SALIERI_DEFINED_Outptr_result_buffer_to) +#undef _Outptr_result_buffer_to_ +#endif +#if !defined(_Outptr_result_buffer_to_) +#define _Outptr_result_buffer_to_(s, c) +#define SALIERI_DEFINED_Outptr_result_buffer_to +#endif + +#if defined(_Outptr_result_bytebuffer_to_) && defined(SALIERI_DEFINED_Outptr_result_bytebuffer_to) +#undef _Outptr_result_bytebuffer_to_ +#endif +#if !defined(_Outptr_result_bytebuffer_to_) +#define _Outptr_result_bytebuffer_to_(s, c) +#define SALIERI_DEFINED_Outptr_result_bytebuffer_to +#endif + +#if defined(_Outptr_opt_result_buffer_to_) && defined(SALIERI_DEFINED_Outptr_opt_result_buffer_to) +#undef _Outptr_opt_result_buffer_to_ +#endif +#if !defined(_Outptr_opt_result_buffer_to_) +#define _Outptr_opt_result_buffer_to_(s, c) +#define SALIERI_DEFINED_Outptr_opt_result_buffer_to +#endif + +#if defined(_Outptr_opt_result_bytebuffer_to_) && defined(SALIERI_DEFINED_Outptr_opt_result_bytebuffer_to) +#undef _Outptr_opt_result_bytebuffer_to_ +#endif +#if !defined(_Outptr_opt_result_bytebuffer_to_) +#define _Outptr_opt_result_bytebuffer_to_(s, c) +#define SALIERI_DEFINED_Outptr_opt_result_bytebuffer_to +#endif + +#if defined(_Result_nullonfailure_) && defined(SALIERI_DEFINED_Result_nullonfailure) +#undef _Result_nullonfailure_ +#endif +#if !defined(_Result_nullonfailure_) +#define _Result_nullonfailure_ +#define SALIERI_DEFINED_Result_nullonfailure +#endif + +#if defined(_Result_zeroonfailure_) && defined(SALIERI_DEFINED_Result_zeroonfailure) +#undef _Result_zeroonfailure_ +#endif +#if !defined(_Result_zeroonfailure_) +#define _Result_zeroonfailure_ +#define SALIERI_DEFINED_Result_zeroonfailure +#endif + +#if defined(_Outptr_result_nullonfailure_) && defined(SALIERI_DEFINED_Outptr_result_nullonfailure) +#undef _Outptr_result_nullonfailure_ +#endif +#if !defined(_Outptr_result_nullonfailure_) +#define _Outptr_result_nullonfailure_ +#define SALIERI_DEFINED_Outptr_result_nullonfailure +#endif + +#if defined(_Outptr_opt_result_nullonfailure_) && defined(SALIERI_DEFINED_Outptr_opt_result_nullonfailure) +#undef _Outptr_opt_result_nullonfailure_ +#endif +#if !defined(_Outptr_opt_result_nullonfailure_) +#define _Outptr_opt_result_nullonfailure_ +#define SALIERI_DEFINED_Outptr_opt_result_nullonfailure +#endif + +#if defined(_Outref_result_nullonfailure_) && defined(SALIERI_DEFINED_Outref_result_nullonfailure) +#undef _Outref_result_nullonfailure_ +#endif +#if !defined(_Outref_result_nullonfailure_) +#define _Outref_result_nullonfailure_ +#define SALIERI_DEFINED_Outref_result_nullonfailure +#endif + +/* Output Reference Parameters */ + +#if defined(_Outref_) && defined(SALIERI_DEFINED_Outref) +#undef _Outref_ +#endif +#if !defined(_Outref_) +#define _Outref_ +#define SALIERI_DEFINED_Outref +#endif + +#if defined(_Outref_result_maybenull_) && defined(SALIERI_DEFINED_Outref_result_maybenull) +#undef _Outref_result_maybenull_ +#endif +#if !defined(_Outref_result_maybenull_) +#define _Outref_result_maybenull_ +#define SALIERI_DEFINED_Outref_result_maybenull +#endif + +#if defined(_Outref_result_buffer_) && defined(SALIERI_DEFINED_Outref_result_buffer) +#undef _Outref_result_buffer_ +#endif +#if !defined(_Outref_result_buffer_) +#define _Outref_result_buffer_(s) +#define SALIERI_DEFINED_Outref_result_buffer +#endif + +#if defined(_Outref_result_bytebuffer_) && defined(SALIERI_DEFINED_Outref_result_bytebuffer) +#undef _Outref_result_bytebuffer_ +#endif +#if !defined(_Outref_result_bytebuffer_) +#define _Outref_result_bytebuffer_(s) +#define SALIERI_DEFINED_Outref_result_bytebuffer +#endif + +#if defined(_Outref_result_buffer_to_) && defined(SALIERI_DEFINED_Outref_result_buffer_to) +#undef _Outref_result_buffer_to_ +#endif +#if !defined(_Outref_result_buffer_to_) +#define _Outref_result_buffer_to_(s, c) +#define SALIERI_DEFINED_Outref_result_buffer_to +#endif + +#if defined(_Outref_result_bytebuffer_to_) && defined(SALIERI_DEFINED_Outref_result_bytebuffer_to) +#undef _Outref_result_bytebuffer_to_ +#endif +#if !defined(_Outref_result_bytebuffer_to_) +#define _Outref_result_bytebuffer_to_(s, c) +#define SALIERI_DEFINED_Outref_result_bytebuffer_to +#endif + +#if defined(_Outref_result_buffer_all_) && defined(SALIERI_DEFINED_Outref_result_buffer_all) +#undef _Outref_result_buffer_all_ +#endif +#if !defined(_Outref_result_buffer_all_) +#define _Outref_result_buffer_all_(s) +#define SALIERI_DEFINED_Outref_result_buffer_all +#endif + +#if defined(_Outref_result_bytebuffer_all_) && defined(SALIERI_DEFINED_Outref_result_bytebuffer_all) +#undef _Outref_result_bytebuffer_all_ +#endif +#if !defined(_Outref_result_bytebuffer_all_) +#define _Outref_result_bytebuffer_all_(s) +#define SALIERI_DEFINED_Outref_result_bytebuffer_all +#endif + +#if defined(_Outref_result_buffer_maybenull_) && defined(SALIERI_DEFINED_Outref_result_buffer_maybenull) +#undef _Outref_result_buffer_maybenull_ +#endif +#if !defined(_Outref_result_buffer_maybenull_) +#define _Outref_result_buffer_maybenull_(s) +#define SALIERI_DEFINED_Outref_result_buffer_maybenull +#endif + +#if defined(_Outref_result_bytebuffer_maybenull_) && defined(SALIERI_DEFINED_Outref_result_bytebuffer_maybenull) +#undef _Outref_result_bytebuffer_maybenull_ +#endif +#if !defined(_Outref_result_bytebuffer_maybenull_) +#define _Outref_result_bytebuffer_maybenull_(s) +#define SALIERI_DEFINED_Outref_result_bytebuffer_maybenull +#endif + +#if defined(_Outref_result_buffer_to_maybenull_) && defined(SALIERI_DEFINED_Outref_result_buffer_to_maybenull) +#undef _Outref_result_buffer_to_maybenull_ +#endif +#if !defined(_Outref_result_buffer_to_maybenull_) +#define _Outref_result_buffer_to_maybenull_(s, c) +#define SALIERI_DEFINED_Outref_result_buffer_to_maybenull +#endif + +#if defined(_Outref_result_bytebuffer_to_maybenull_) && defined(SALIERI_DEFINED_Outref_result_bytebuffer_to_maybenull) +#undef _Outref_result_bytebuffer_to_maybenull_ +#endif +#if !defined(_Outref_result_bytebuffer_to_maybenull_) +#define _Outref_result_bytebuffer_to_maybenull_(s, c) +#define SALIERI_DEFINED_Outref_result_bytebuffer_to_maybenull +#endif + +#if defined(_Outref_result_buffer_all_maybenull_) && defined(SALIERI_DEFINED_Outref_result_buffer_all_maybenull) +#undef _Outref_result_buffer_all_maybenull_ +#endif +#if !defined(_Outref_result_buffer_all_maybenull_) +#define _Outref_result_buffer_all_maybenull_(s) +#define SALIERI_DEFINED_Outref_result_buffer_all_maybenull +#endif + +#if defined(_Outref_result_bytebuffer_all_maybenull_) && defined(SALIERI_DEFINED_Outref_result_bytebuffer_all_maybenull) +#undef _Outref_result_bytebuffer_all_maybenull_ +#endif +#if !defined(_Outref_result_bytebuffer_all_maybenull_) +#define _Outref_result_bytebuffer_all_maybenull_(s) +#define SALIERI_DEFINED_Outref_result_bytebuffer_all_maybenull +#endif + +/* Return Values */ + +#if defined(_Ret_z_) && defined(SALIERI_DEFINED_Ret_z) +#undef _Ret_z_ +#endif +#if !defined(_Ret_z_) +#define _Ret_z_ +#define SALIERI_DEFINED_Ret_z +#endif + +#if defined(_Ret_writes_) && defined(SALIERI_DEFINED_Ret_writes) +#undef _Ret_writes_ +#endif +#if !defined(_Ret_writes_) +#define _Ret_writes_(s) +#define SALIERI_DEFINED_Ret_writes +#endif + +#if defined(_Ret_writes_bytes_) && defined(SALIERI_DEFINED_Ret_writes_bytes) +#undef _Ret_writes_bytes_ +#endif +#if !defined(_Ret_writes_bytes_) +#define _Ret_writes_bytes_(s) +#define SALIERI_DEFINED_Ret_writes_bytes +#endif + +#if defined(_Ret_writes_z_) && defined(SALIERI_DEFINED_Ret_writes_z) +#undef _Ret_writes_z_ +#endif +#if !defined(_Ret_writes_z_) +#define _Ret_writes_z_(s) +#define SALIERI_DEFINED_Ret_writes_z +#endif + +#if defined(_Ret_writes_to_) && defined(SALIERI_DEFINED_Ret_writes_to) +#undef _Ret_writes_to_ +#endif +#if !defined(_Ret_writes_to_) +#define _Ret_writes_to_(s, c) +#define SALIERI_DEFINED_Ret_writes_to +#endif + +#if defined(_Ret_writes_maybenull_) && defined(SALIERI_DEFINED_Ret_writes_maybenull) +#undef _Ret_writes_maybenull_ +#endif +#if !defined(_Ret_writes_maybenull_) +#define _Ret_writes_maybenull_(s) +#define SALIERI_DEFINED_Ret_writes_maybenull +#endif + +#if defined(_Ret_writes_to_maybenull_) && defined(SALIERI_DEFINED_Ret_writes_to_maybenull) +#undef _Ret_writes_to_maybenull_ +#endif +#if !defined(_Ret_writes_to_maybenull_) +#define _Ret_writes_to_maybenull_(s) +#define SALIERI_DEFINED_Ret_writes_to_maybenull +#endif + +#if defined(_Ret_writes_maybenull_z_) && defined(SALIERI_DEFINED_Ret_writes_maybenull_z) +#undef _Ret_writes_maybenull_z_ +#endif +#if !defined(_Ret_writes_maybenull_z_) +#define _Ret_writes_maybenull_z_(s) +#define SALIERI_DEFINED_Ret_writes_maybenull_z +#endif + +#if defined(_Ret_maybenull_) && defined(SALIERI_DEFINED_Ret_maybenull) +#undef _Ret_maybenull_ +#endif +#if !defined(_Ret_maybenull_) +#define _Ret_maybenull_ +#define SALIERI_DEFINED_Ret_maybenull +#endif + +#if defined(_Ret_maybenull_z_) && defined(SALIERI_DEFINED_Ret_maybenull_z) +#undef _Ret_maybenull_z_ +#endif +#if !defined(_Ret_maybenull_z_) +#define _Ret_maybenull_z_ +#define SALIERI_DEFINED_Ret_maybenull_z +#endif + +#if defined(_Ret_null_) && defined(SALIERI_DEFINED_Ret_null) +#undef _Ret_null_ +#endif +#if !defined(_Ret_null_) +#define _Ret_null_ +#define SALIERI_DEFINED_Ret_null +#endif + +#if defined(_Ret_notnull_) && defined(SALIERI_DEFINED_Ret_notnull) +#undef _Ret_notnull_ +#endif +#if !defined(_Ret_notnull_) +#define _Ret_notnull_ +#define SALIERI_DEFINED_Ret_notnull +#endif + +#if defined(_Ret_writes_bytes_to_) && defined(SALIERI_DEFINED_Ret_writes_bytes_to) +#undef _Ret_writes_bytes_to_ +#endif +#if !defined(_Ret_writes_bytes_to_) +#define _Ret_writes_bytes_to_ +#define SALIERI_DEFINED_Ret_writes_bytes_to +#endif + +#if defined(_Ret_writes_bytes_to_) && defined(SALIERI_DEFINED_Ret_writes_bytes_to) +#undef _Ret_writes_bytes_to_ +#endif +#if !defined(_Ret_writes_bytes_to_) +#define _Ret_writes_bytes_to_ +#define SALIERI_DEFINED_Ret_writes_bytes_to +#endif + +#if defined(_Ret_writes_bytes_maybenull_) && defined(SALIERI_DEFINED_Ret_writes_bytes_maybenull) +#undef _Ret_writes_bytes_maybenull_ +#endif +#if !defined(_Ret_writes_bytes_maybenull_) +#define _Ret_writes_bytes_maybenull_ +#define SALIERI_DEFINED_Ret_writes_bytes_maybenull +#endif + +#if defined(_Ret_writes_bytes_to_maybenull_) && defined(SALIERI_DEFINED_Ret_writes_bytes_to_maybenull) +#undef _Ret_writes_bytes_to_maybenull_ +#endif +#if !defined(_Ret_writes_bytes_to_maybenull_) +#define _Ret_writes_bytes_to_maybenull_ +#define SALIERI_DEFINED_Ret_writes_bytes_to_maybenull +#endif + +/* Other Common Annotations */ + +#if defined(_In_range_) && defined(SALIERI_DEFINED_In_range) +#undef _In_range_ +#endif +#if !defined(_In_range_) +#define _In_range_(low, hi) +#define SALIERI_DEFINED_In_range +#endif + +#if defined(_Out_range_) && defined(SALIERI_DEFINED_Out_range) +#undef _Out_range_ +#endif +#if !defined(_Out_range_) +#define _Out_range_(low, hi) +#define SALIERI_DEFINED_Out_range +#endif + +#if defined(_Ret_range_) && defined(SALIERI_DEFINED_Ret_range) +#undef _Ret_range_ +#endif +#if !defined(_Ret_range_) +#define _Ret_range_(low, hi) +#define SALIERI_DEFINED_Ret_range +#endif + +#if defined(_Deref_in_range_) && defined(SALIERI_DEFINED_Deref_in_range) +#undef _Deref_in_range_ +#endif +#if !defined(_Deref_in_range_) +#define _Deref_in_range_(low, hi) +#define SALIERI_DEFINED_Deref_in_range +#endif + +#if defined(_Deref_out_range_) && defined(SALIERI_DEFINED_Deref_out_range) +#undef _Deref_out_range_ +#endif +#if !defined(_Deref_out_range_) +#define _Deref_out_range_(low, hi) +#define SALIERI_DEFINED_Deref_out_range +#endif + +#if defined(_Deref_inout_range_) && defined(SALIERI_DEFINED_Deref_inout_range) +#undef _Deref_inout_range_ +#endif +#if !defined(_Deref_inout_range_) +#define _Deref_inout_range_(low, hi) +#define SALIERI_DEFINED_Deref_inout_range +#endif + +#if defined(_Field_range_) && defined(SALIERI_DEFINED_Field_range) +#undef _Field_range_ +#endif +#if !defined(_Field_range_) +#define _Field_range_(low, hi) +#define SALIERI_DEFINED_Field_range +#endif + +#if defined(_Pre_equal_to_) && defined(SALIERI_DEFINED_Pre_equal_to) +#undef _Pre_equal_to_ +#endif +#if !defined(_Pre_equal_to_) +#define _Pre_equal_to_(expr) +#define SALIERI_DEFINED_Pre_equal_to +#endif + +#if defined(_Post_equal_to_) && defined(SALIERI_DEFINED_Post_equal_to) +#undef _Post_equal_to_ +#endif +#if !defined(_Post_equal_to_) +#define _Post_equal_to_(expr) +#define SALIERI_DEFINED_Post_equal_to +#endif + +#if defined(_Struct_size_bytes_) && defined(SALIERI_DEFINED_Struct_size_bytes) +#undef _Struct_size_bytes_ +#endif +#if !defined(_Struct_size_bytes_) +#define _Struct_size_bytes_(size) +#define SALIERI_DEFINED_Struct_size_bytes +#endif + +/* Annotating Function Behavior + * + * https://msdn.microsoft.com/en-us/library/jj159529.aspx + *****/ + +#if defined(_Called_from_function_class_) && defined(SALIERI_DEFINED_Called_from_function_class) +#undef _Called_from_function_class_ +#endif +#if !defined(_Called_from_function_class_) +#define _Called_from_function_class_(name) +#define SALIERI_DEFINED_Called_from_function_class +#endif + +#if defined(_Check_return_) && defined(SALIERI_DEFINED_Check_return) +#undef _Check_return_ +#endif +#if !defined(_Check_return_) +#define _Check_return_ +#define SALIERI_DEFINED_Check_return +#endif + +#if defined(_Function_class_) && defined(SALIERI_DEFINED_Function_class) +#undef _Function_class_ +#endif +#if !defined(_Function_class_) +#define _Function_class_(name) +#define SALIERI_DEFINED_Function_class +#endif + +#if defined(_Raises_SEH_exception_) && defined(SALIERI_DEFINED_Raises_SEH_exception) +#undef _Raises_SEH_exception_ +#endif +#if !defined(_Raises_SEH_exception_) +#define _Raises_SEH_exception_ +#define SALIERI_DEFINED_Raises_SEH_exception +#endif + +#if defined(_Must_inspect_result_) && defined(SALIERI_DEFINED_Must_inspect_result) +#undef _Must_inspect_result_ +#endif +#if !defined(_Must_inspect_result_) +#define _Must_inspect_result_ +#define SALIERI_DEFINED_Must_inspect_result +#endif + +#if defined(_Use_decl_annotations_) && defined(SALIERI_DEFINED_Use_decl_annotations) +#undef _Use_decl_annotations_ +#endif +#if !defined(_Use_decl_annotations_) +#define _Use_decl_annotations_ +#define SALIERI_DEFINED_Use_decl_annotations +#endif + +#if defined(_Always_) && defined(SALIERI_DEFINED_Always) +#undef _Always_ +#endif +#if !defined(_Always_) +#define _Always_(anno_list) +#define SALIERI_DEFINED_Always +#endif + +#if defined(_On_failure_) && defined(SALIERI_DEFINED_On_failure) +#undef _On_failure_ +#endif +#if !defined(_On_failure_) +#define _On_failure_(anno_list) +#define SALIERI_DEFINED_On_failure +#endif + +#if defined(_Return_type_success_) && defined(SALIERI_DEFINED_Return_type_success) +#undef _Return_type_success_ +#endif +#if !defined(_Return_type_success_) +#define _Return_type_success_(expr) +#define SALIERI_DEFINED_Return_type_success +#endif + +#if defined(_Success_) && defined(SALIERI_DEFINED_Success) +#undef _Success_ +#endif +#if !defined(_Success_) +#define _Success_(expr) +#define SALIERI_DEFINED_Success +#endif + +/* Annotating Structs and Classes + * + * https://msdn.microsoft.com/en-us/library/jj159528.aspx + *****/ + +#if defined(_Field_range_) && defined(SALIERI_DEFINED_Field_range) +#undef _Field_range_ +#endif +#if !defined(_Field_range_) +#define _Field_range_(low, high) +#define SALIERI_DEFINED_Field_range +#endif + +#if defined(_Field_size_) && defined(SALIERI_DEFINED_Field_size) +#undef _Field_size_ +#endif +#if !defined(_Field_size_) +#define _Field_size_(size) +#define SALIERI_DEFINED_Field_size +#endif + +#if defined(_Field_size_part_) && defined(SALIERI_DEFINED_Field_size_part) +#undef _Field_size_part_ +#endif +#if !defined(_Field_size_part_) +#define _Field_size_part_(size) +#define SALIERI_DEFINED_Field_size_part +#endif + +#if defined(_Field_size_opt_) && defined(SALIERI_DEFINED_Field_size_opt) +#undef _Field_size_opt_ +#endif +#if !defined(_Field_size_opt_) +#define _Field_size_opt_(size) +#define SALIERI_DEFINED_Field_size_opt +#endif + +#if defined(_Field_size_bytes_) && defined(SALIERI_DEFINED_Field_size_bytes) +#undef _Field_size_bytes_ +#endif +#if !defined(_Field_size_bytes_) +#define _Field_size_bytes_(size) +#define SALIERI_DEFINED_Field_size_bytes +#endif + +#if defined(_Field_size_bytes_opt_) && defined(SALIERI_DEFINED_Field_size_bytes_opt) +#undef _Field_size_bytes_opt_ +#endif +#if !defined(_Field_size_bytes_opt_) +#define _Field_size_bytes_opt_(size) +#define SALIERI_DEFINED_Field_size_bytes_opt +#endif + +#if defined(_Field_size_part_) && defined(SALIERI_DEFINED_Field_size_part) +#undef _Field_size_part_ +#endif +#if !defined(_Field_size_part_) +#define _Field_size_part_(size, count) +#define SALIERI_DEFINED_Field_size_part +#endif + +#if defined(_Field_size_part_opt_) && defined(SALIERI_DEFINED_Field_size_part_opt) +#undef _Field_size_part_opt_ +#endif +#if !defined(_Field_size_part_opt_) +#define _Field_size_part_opt_(size, count) +#define SALIERI_DEFINED_Field_size_part_opt +#endif + +#if defined(_Field_size_bytes_part_) && defined(SALIERI_DEFINED_Field_size_bytes_part) +#undef _Field_size_bytes_part_ +#endif +#if !defined(_Field_size_bytes_part_) +#define _Field_size_bytes_part_(size, count) +#define SALIERI_DEFINED_Field_size_bytes_part +#endif + +#if defined(_Field_size_bytes_part_opt_) && defined(SALIERI_DEFINED_Field_size_bytes_part_opt) +#undef _Field_size_bytes_part_opt_ +#endif +#if !defined(_Field_size_bytes_part_opt_) +#define _Field_size_bytes_part_opt_(size, count) +#define SALIERI_DEFINED_Field_size_bytes_part_opt +#endif + +#if defined(_Field_size_full_) && defined(SALIERI_DEFINED_Field_size_full) +#undef _Field_size_full_ +#endif +#if !defined(_Field_size_full_) +#define _Field_size_full_(size) +#define SALIERI_DEFINED_Field_size_full +#endif + +#if defined(_Field_size_full_opt_) && defined(SALIERI_DEFINED_Field_size_full_opt) +#undef _Field_size_full_opt_ +#endif +#if !defined(_Field_size_full_opt_) +#define _Field_size_full_opt_(size) +#define SALIERI_DEFINED_Field_size_full_opt +#endif + +#if defined(_Field_size_bytes_full_) && defined(SALIERI_DEFINED_Field_size_bytes_full) +#undef _Field_size_bytes_full_ +#endif +#if !defined(_Field_size_bytes_full_) +#define _Field_size_bytes_full_(size) +#define SALIERI_DEFINED_Field_size_bytes_full +#endif + +#if defined(_Field_size_bytes_full_opt_) && defined(SALIERI_DEFINED_Field_size_bytes_full_opt) +#undef _Field_size_bytes_full_opt_ +#endif +#if !defined(_Field_size_bytes_full_opt_) +#define _Field_size_bytes_full_opt_(size) +#define SALIERI_DEFINED_Field_size_bytes_full_opt +#endif + +#if defined(_Struct_size_bytes_) && defined(SALIERI_DEFINED_Struct_size_bytes) +#undef _Struct_size_bytes_ +#endif +#if !defined(_Struct_size_bytes_) +#define _Struct_size_bytes_(size) +#define SALIERI_DEFINED_Struct_size_bytes +#endif + +/* Annotating Locking Behavior + * + * https://msdn.microsoft.com/en-us/library/hh916381.aspx + *****/ + +#if defined(_Acquires_exclusive_lock_) && defined(SALIERI_DEFINED_Acquires_exclusive_lock) +#undef _Acquires_exclusive_lock_ +#endif +#if !defined(_Acquires_exclusive_lock_) +#define _Acquires_exclusive_lock_(expr) +#define SALIERI_DEFINED_Acquires_exclusive_lock +#endif + +#if defined(_Acquires_lock_) && defined(SALIERI_DEFINED_Acquires_lock) +#undef _Acquires_lock_ +#endif +#if !defined(_Acquires_lock_) +#define _Acquires_lock_(expr) +#define SALIERI_DEFINED_Acquires_lock +#endif + +#if defined(_Acquires_nonreentrant_lock_) && defined(SALIERI_DEFINED_Acquires_nonreentrant_lock) +#undef _Acquires_nonreentrant_lock_ +#endif +#if !defined(_Acquires_nonreentrant_lock_) +#define _Acquires_nonreentrant_lock_(expr) +#define SALIERI_DEFINED_Acquires_nonreentrant_lock +#endif + +#if defined(_Acquires_shared_lock_) && defined(SALIERI_DEFINED_Acquires_shared_lock) +#undef _Acquires_shared_lock_ +#endif +#if !defined(_Acquires_shared_lock_) +#define _Acquires_shared_lock_(expr) +#define SALIERI_DEFINED_Acquires_shared_lock +#endif + +#if defined(_Create_lock_level_) && defined(SALIERI_DEFINED_Create_lock_level) +#undef _Create_lock_level_ +#endif +#if !defined(_Create_lock_level_) +#define _Create_lock_level_(name) +#define SALIERI_DEFINED_Create_lock_level +#endif + +#if defined(_Has_lock_kind_) && defined(SALIERI_DEFINED_Has_lock_kind) +#undef _Has_lock_kind_ +#endif +#if !defined(_Has_lock_kind_) +#define _Has_lock_kind_(kind) +#define SALIERI_DEFINED_Has_lock_kind +#endif + +#if defined(_Has_lock_level_) && defined(SALIERI_DEFINED_Has_lock_level) +#undef _Has_lock_level_ +#endif +#if !defined(_Has_lock_level_) +#define _Has_lock_level_(name) +#define SALIERI_DEFINED_Has_lock_level +#endif + +#if defined(_Lock_level_order_) && defined(SALIERI_DEFINED_Lock_level_order) +#undef _Lock_level_order_ +#endif +#if !defined(_Lock_level_order_) +#define _Lock_level_order_(name1, name2) +#define SALIERI_DEFINED_Lock_level_order +#endif + +#if defined(_Post_same_lock_) && defined(SALIERI_DEFINED_Post_same_lock) +#undef _Post_same_lock_ +#endif +#if !defined(_Post_same_lock_) +#define _Post_same_lock_(expr1, expr2) +#define SALIERI_DEFINED_Post_same_lock +#endif + +#if defined(_Releases_exclusive_lock_) && defined(SALIERI_DEFINED_Releases_exclusive_lock) +#undef _Releases_exclusive_lock_ +#endif +#if !defined(_Releases_exclusive_lock_) +#define _Releases_exclusive_lock_(expr) +#define SALIERI_DEFINED_Releases_exclusive_lock +#endif + +#if defined(_Releases_lock_) && defined(SALIERI_DEFINED_Releases_lock) +#undef _Releases_lock_ +#endif +#if !defined(_Releases_lock_) +#define _Releases_lock_(expr) +#define SALIERI_DEFINED_Releases_lock +#endif + +#if defined(_Releases_nonreentrant_lock_) && defined(SALIERI_DEFINED_Releases_nonreentrant_lock) +#undef _Releases_nonreentrant_lock_ +#endif +#if !defined(_Releases_nonreentrant_lock_) +#define _Releases_nonreentrant_lock_(expr) +#define SALIERI_DEFINED_Releases_nonreentrant_lock +#endif + +#if defined(_Releases_shared_lock_) && defined(SALIERI_DEFINED_Releases_shared_lock) +#undef _Releases_shared_lock_ +#endif +#if !defined(_Releases_shared_lock_) +#define _Releases_shared_lock_(expr) +#define SALIERI_DEFINED_Releases_shared_lock +#endif + +#if defined(_Requires_lock_held_) && defined(SALIERI_DEFINED_Requires_lock_held) +#undef _Requires_lock_held_ +#endif +#if !defined(_Requires_lock_held_) +#define _Requires_lock_held_(expr) +#define SALIERI_DEFINED_Requires_lock_held +#endif + +#if defined(_Requires_lock_not_held_) && defined(SALIERI_DEFINED_Requires_lock_not_held) +#undef _Requires_lock_not_held_ +#endif +#if !defined(_Requires_lock_not_held_) +#define _Requires_lock_not_held_(expr) +#define SALIERI_DEFINED_Requires_lock_not_held +#endif + +#if defined(_Requires_no_locks_held_) && defined(SALIERI_DEFINED_Requires_no_locks_held) +#undef _Requires_no_locks_held_ +#endif +#if !defined(_Requires_no_locks_held_) +#define _Requires_no_locks_held_ +#define SALIERI_DEFINED_Requires_no_locks_held +#endif + +#if defined(_Requires_shared_lock_held_) && defined(SALIERI_DEFINED_Requires_shared_lock_held) +#undef _Requires_shared_lock_held_ +#endif +#if !defined(_Requires_shared_lock_held_) +#define _Requires_shared_lock_held_(expr) +#define SALIERI_DEFINED_Requires_shared_lock_held +#endif + +#if defined(_Requires_exclusive_lock_held_) && defined(SALIERI_DEFINED_Requires_exclusive_lock_held) +#undef _Requires_exclusive_lock_held_ +#endif +#if !defined(_Requires_exclusive_lock_held_) +#define _Requires_exclusive_lock_held_(expr) +#define SALIERI_DEFINED_Requires_exclusive_lock_held +#endif + +/* Shared Data Access Annotations */ + +#if defined(_Guarded_by_) && defined(SALIERI_DEFINED_Guarded_by) +#undef _Guarded_by_ +#endif +#if !defined(_Guarded_by_) +#define _Guarded_by_(expr) +#define SALIERI_DEFINED_Guarded_by +#endif + +#if defined(_Interlocked_) && defined(SALIERI_DEFINED_Interlocked) +#undef _Interlocked_ +#endif +#if !defined(_Interlocked_) +#define _Interlocked_ +#define SALIERI_DEFINED_Interlocked +#endif + +#if defined(_Interlocked_operand_) && defined(SALIERI_DEFINED_Interlocked_operand) +#undef _Interlocked_operand_ +#endif +#if !defined(_Interlocked_operand_) +#define _Interlocked_operand_ +#define SALIERI_DEFINED_Interlocked_operand +#endif + +#if defined(_Write_guarded_by_) && defined(SALIERI_DEFINED_Write_guarded_by) +#undef _Write_guarded_by_ +#endif +#if !defined(_Write_guarded_by_) +#define _Write_guarded_by_(expr) +#define SALIERI_DEFINED_Write_guarded_by +#endif + +/* Specifying When and Where an Annotation Applies + * + * https://msdn.microsoft.com/en-us/library/jj159526.aspx + *****/ + +#if defined(_At_) && defined(SALIERI_DEFINED_At) +#undef _At_ +#endif +#if !defined(_At_) +#define _At_(expr, anno_list) +#define SALIERI_DEFINED_At +#endif + +#if defined(_At_buffer_) && defined(SALIERI_DEFINED_At_buffer) +#undef _At_buffer_ +#endif +#if !defined(_At_buffer_) +#define _At_buffer_(expr, iter, elem_count, anno_list) +#define SALIERI_DEFINED_At_buffer +#endif + +#if defined(_Group_) && defined(SALIERI_DEFINED_Group) +#undef _Group_ +#endif +#if !defined(_Group_) +#define _Group_(anno_list) +#define SALIERI_DEFINED_Group +#endif + +#if defined(_When_) && defined(SALIERI_DEFINED_When) +#undef _When_ +#endif +#if !defined(_When_) +#define _When_(expr, anno_list) +#define SALIERI_DEFINED_When +#endif + +/* Intrinsic Functions + * + * https://msdn.microsoft.com/en-us/library/jj159527.aspx + *****/ + +/* General Purpose */ + +#if defined(_Curr_) && defined(SALIERI_DEFINED_Curr) +#undef _Curr_ +#endif +#if !defined(_Curr_) +#define _Curr_ +#define SALIERI_DEFINED_Curr +#endif + +#if defined(_Inexpressible_) && defined(SALIERI_DEFINED_Inexpressible) +#undef _Inexpressible_ +#endif +#if !defined(_Inexpressible_) +#define _Inexpressible_(expr) +#define SALIERI_DEFINED_Inexpressible +#endif + +#if defined(_Nullterm_length_) && defined(SALIERI_DEFINED_Nullterm_length) +#undef _Nullterm_length_ +#endif +#if !defined(_Nullterm_length_) +#define _Nullterm_length_(param) +#define SALIERI_DEFINED_Nullterm_length +#endif + +#if defined(_Old_) && defined(SALIERI_DEFINED_Old) +#undef _Old_ +#endif +#if !defined(_Old_) +#define _Old_(expr) +#define SALIERI_DEFINED_Old +#endif + +#if defined(_Param_) && defined(SALIERI_DEFINED_Param) +#undef _Param_ +#endif +#if !defined(_Param_) +#define _Param_(n) +#define SALIERI_DEFINED_Param +#endif + +/* String Specific */ + +#if defined(_String_length_) && defined(SALIERI_DEFINED_String_length) +#undef _String_length_ +#endif +#if !defined(_String_length_) +#define _String_length_(param) +#define SALIERI_DEFINED_String_length +#endif + +#if defined(SALIERI_VERSION) +#undef SALIERI_VERSION +#endif + +#define SALIERI_VERSION 1 + +#endif /* !defined(SALIERI_VERSION) || (SALIERI_VERSION < X) */ diff --git a/wolf/ml/referee_ocr/w_image_processor.cpp b/wolf/ml/referee_ocr/w_image_processor.cpp new file mode 100644 index 000000000..8ec55dc17 --- /dev/null +++ b/wolf/ml/referee_ocr/w_image_processor.cpp @@ -0,0 +1,171 @@ +#include "w_image_processor.hpp" + +#include "salieri.h" + +// using config_for_ocr_struct = wolf::ml::ocr::config_for_ocr_struct; +// using gaussian_blur = wolf::ml::ocr::gaussian_blur; +// using make_contour_white_background = +// wolf::ml::ocr::make_contour_white_background; +// using negative_image = wolf::ml::ocr::negative_image; +// using prepare_image_for_contour_detection = +// wolf::ml::ocr::prepare_image_for_contour_detection; +// using resize_image = wolf::ml::ocr::resize_image; +// using threshold_image = wolf::ml::ocr::threshold_image; +// using config_for_ocr_struct = wolf::ml::ocr::config_for_ocr_struct; +// using namespace wolf::ml::ocr; + +namespace wolf::ml::ocr { + +std::vector> +find_all_countors(_In_ cv::Mat &filtered_image) { + std::vector> contours; + std::vector hierarchy; + + cv::findContours(filtered_image, contours, hierarchy, cv::RETR_TREE, + cv::CHAIN_APPROX_SIMPLE, cv::Point(0, 0)); + + return contours; +} + +void gaussian_blur(_Inout_ cv::Mat &frame_box, + _In_ config_for_ocr_struct &ocr_config) { + int kernel_size = ocr_config.gaussian_blur_win_size; + cv::GaussianBlur(frame_box, frame_box, cv::Size(kernel_size, kernel_size), 0, + 0); +} + +void make_contour_white_background(_Inout_ cv::Mat &contour_image, + _In_ config_for_ocr_struct &ocr_config) { + int height = contour_image.rows; + int width = contour_image.cols; + int n_channels = contour_image.channels(); + + if (ocr_config.make_white_background) { + if (n_channels == 1) { + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + if (contour_image.at(i, j) > + ocr_config.white_background_threshold) { + contour_image.at(i, j) = 180; + } + } + } + } else if (n_channels == 3) { + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + cv::Vec3b &color_pixel = contour_image.at(i, j); + int count = 0, combine = 0; + for (int i = 0; i < 3; i++) { + if (color_pixel[i] > ocr_config.white_background_threshold) { + count++; + combine += color_pixel[i]; + } + } + if (count > 2 || + combine > ocr_config.white_background_threshold * 2 + 100) { + color_pixel[0] = + 255; // (color_pixel[0]*2 > 255) ? 255:color_pixel[0]; + color_pixel[1] = + 255; // (color_pixel[1]*2 > 255) ? 255:color_pixel[1]; + color_pixel[2] = + 255; // (color_pixel[2]*2 > 255) ? 255:color_pixel[2]; + } + } + } + } + } +} + +void negative_image(_Inout_ cv::Mat &contour_image) { + int height = contour_image.rows; + int width = contour_image.cols; + int n_channels = contour_image.channels(); + + if (n_channels == 1) { + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + contour_image.at(i, j) = 255 - contour_image.at(i, j); + } + } + } else if (n_channels == 3) { + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + cv::Vec3b &color_pixel = contour_image.at(i, j); + color_pixel[0] = 255 - color_pixel[0]; + color_pixel[1] = 255 - color_pixel[1]; + color_pixel[2] = 255 - color_pixel[2]; + } + } + } +} + +cv::Mat +prepare_image_for_contour_detection(_In_ cv::Mat &image, + _In_ config_for_ocr_struct &ocr_config) { + cv::Mat filtered_image = image.clone(); + if (ocr_config.do_resize) { + int dist_height = ocr_config.resized_height; + resize_image(filtered_image, dist_height); + } + + if (ocr_config.do_blur) { + gaussian_blur(filtered_image, ocr_config); + } + + if (ocr_config.do_threshold) { + threshold_image(filtered_image, ocr_config); + } + return filtered_image; +} + +void resize_image(_Inout_ cv::Mat &frame_box, _In_ int dest_height, + _In_ int dest_width) { + /*! +#include +#include + +#include "salieri.h" + +/*! \brief This class is responsible for image processing tasks. + + This class contains functions, structures, and variables that use for + image processing purposes. In the project, in other classes, in the case of + processing image, one must create an object of the ImageProcessing class. +*/ + +namespace wolf::ml::ocr { + +//! Restriction struct. +/*! + Characters must satisfy the struct restrictions. + */ +struct restrictions_struct { + int min_area; + int max_area; + int min_height; + int max_height; + int min_width; + int max_width; +}; + +//! OCR configuration struct. +/*! + The necessary configurations for processing optical characters. + */ +struct config_for_ocr_struct { + /*!> +find_all_countors(_In_ cv::Mat &filtered_image); + +/*! + blur image by specified configuration + \param frame_box The image needs to be processed. + \param ocr_config The necessary configurations for processing + optical characters. + */ +void gaussian_blur(_Inout_ cv::Mat &frame_box, + _In_ config_for_ocr_struct &ocr_config); + +/*! + takes an image and makes the background white. this function uses a + threshold to find background points \param contour_image The image needs + to be processed. \param ocr_config The necessary configurations for + processing optical characters. + */ +void make_contour_white_background(_Inout_ cv::Mat &contour_image, + _In_ config_for_ocr_struct &ocr_config); + +/*! + The negative_image function changed the pixels' value. The new value + is obtained by 255 - the previous value. + + \param contour_image contour_image is a cropped part of the + original image that contains one of the contours. + */ +void negative_image(_Inout_ cv::Mat &contour_image); + +/*! + apply some filters to better find contours. + + \param image The image needs to be processed. + \param ocr_config The necessary configurations for processing + optical characters. + */ +cv::Mat +prepare_image_for_contour_detection(_In_ cv::Mat &image, + _In_ config_for_ocr_struct &ocr_config); + +/*! + resize image to specified size. + \param frame_box The image needs to be processed. + \param dest_height The height of destination image. + \param dest_width The width of destination image. + */ +void resize_image(_Inout_ cv::Mat &frame_box, _In_ int dest_height = -1, + _In_ int dest_width = -1); + +/*! + The initial_filtering function prepares the input image for processing. + \param frame_box The image needs to be processed. + \param ocr_config The necessary configurations for processing + optical characters. + */ +void threshold_image(_Inout_ cv::Mat &frame_box, + _In_ config_for_ocr_struct &ocr_config); + +} // namespace wolf::ml::ocr \ No newline at end of file diff --git a/wolf/ml/referee_ocr/w_ocr_engine.cpp b/wolf/ml/referee_ocr/w_ocr_engine.cpp new file mode 100644 index 000000000..d13839db4 --- /dev/null +++ b/wolf/ml/referee_ocr/w_ocr_engine.cpp @@ -0,0 +1,960 @@ +#include "w_ocr_engine.hpp" + +#include +#include +#include + +#include +#include +#include + +#include "w_utilities.hpp" + +namespace fs = std::filesystem; + +#ifdef __TELEMETRY +#include "opentelemetry/sdk/version/version.h" +#include "opentelemetry/trace/provider.h" + +namespace trace = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; + +namespace { +nostd::shared_ptr get_tracer() { + auto provider = trace::Provider::GetTracerProvider(); + return provider->GetTracer("pes_21", OPENTELEMETRY_SDK_VERSION); +} +} // namespace +#endif + +using w_ocr_engine = wolf::ml::ocr::w_ocr_engine; +using config_for_ocr_struct = wolf::ml::ocr::config_for_ocr_struct; + +w_ocr_engine::w_ocr_engine() { + std::string key = "TESSERACT_LOG"; + std::string tesseract_log = get_env_string(key.c_str()); + + digit_api->Init(nullptr, "eng", tesseract::OEM_LSTM_ONLY); + digit_api->SetPageSegMode(tesseract::PSM_SINGLE_CHAR); + digit_api->SetVariable("tessedit_char_whitelist", "0123456789"); + digit_api->SetVariable("user_defined_dpi", "70"); + digit_api->SetVariable("debug_file", tesseract_log.c_str()); + + word_api->Init(nullptr, "eng", tesseract::OEM_LSTM_ONLY); + word_api->SetPageSegMode(tesseract::PSM_SINGLE_CHAR); + word_api->SetVariable( + "tessedit_char_whitelist", + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); // ,"ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + //// , + word_api->SetVariable("user_defined_dpi", "70"); + word_api->SetVariable("debug_file", tesseract_log.c_str()); +} + +w_ocr_engine::~w_ocr_engine() { + digit_api->End(); + word_api->End(); +} + +bool w_ocr_engine::check_if_overlapped(_In_ cv::Rect box_1, _In_ cv::Rect box_2, + _In_ config_for_ocr_struct &ocr_config) { + bool if_overlapped = false; + int area_1, area_2, overlapped_area; + + int dx = std::min(box_1.x + box_1.width, box_2.x + box_2.width) - + std::max(box_1.x, box_2.x); + int dy = std::min(box_1.y + box_1.height, box_2.y + box_2.height) - + std::max(box_1.y, box_2.y); + + if (dx > 0 && dy > 0) { + area_1 = box_1.width * box_1.height; + area_2 = box_2.width * box_2.height; + + overlapped_area = dx * dy; + + if (double(overlapped_area) / double(area_1) > + ocr_config.overlapped_threshold || + double(overlapped_area) / double(area_2) > + ocr_config.overlapped_threshold) { + if_overlapped = true; + } + } + + return if_overlapped; +} + +// this function is related to cluster_char_structs function +bool compare_char_by_x_position( + const w_ocr_engine::characters_struct &first_charactor, + const w_ocr_engine::characters_struct &second_charactor) { + return first_charactor.center.x < second_charactor.center.x; +} + +std::vector +w_ocr_engine::contours_to_char_structs( + _In_ std::vector> contours) { + std::vector modified_contours; + size_t number_of_contours = contours.size(); + + for (size_t i = 0; i < number_of_contours; i++) { + std::vector contour_poly; + characters_struct temp_modified_contour; + temp_modified_contour.contour = contours[i]; + + double epsilon = 3; + bool closed = true; + cv::approxPolyDP(cv::Mat(contours[i]), contour_poly, epsilon, closed); + + temp_modified_contour.bound_rect = cv::boundingRect(cv::Mat(contour_poly)); + + contour_poly.clear(); + temp_modified_contour.center.x = temp_modified_contour.bound_rect.x + + temp_modified_contour.bound_rect.width / 2; + + temp_modified_contour.center.y = + temp_modified_contour.bound_rect.y + + temp_modified_contour.bound_rect.height / 2; + + temp_modified_contour.height = temp_modified_contour.bound_rect.height; + + modified_contours.push_back(temp_modified_contour); + } + return modified_contours; +} + +void w_ocr_engine::enhance_contour_image_for_model( + _Inout_ cv::Mat &contour_image, _In_ config_for_ocr_struct &ocr_config) { + if (!(ocr_config.make_white_background || ocr_config.do_resize_contour)) { + return; + } + + if (ocr_config.make_white_background) { + make_contour_white_background(contour_image, ocr_config); + } + + if (ocr_config.do_resize_contour) { + // float resize_fraction = + // float(ocr_config.desired_contour_height)/float(height); + int dist_height = ocr_config.desired_contour_height; + int dist_width = 24; // int(resize_fraction*width); + + cv::resize(contour_image, contour_image, cv::Size(dist_width, dist_height), + 0.0, 0.0, cv::InterpolationFlags::INTER_AREA); + } + + return; +} + +double w_ocr_engine::euclidean_distance(characters_struct &first_character, + characters_struct &second_character) { + double dist_x = std::pow( + float(first_character.center.x - second_character.center.x), 2.0); + double dist_y = std::pow( + float(first_character.center.y - second_character.center.y), 2.0); + double dist = std::pow(dist_x + dist_y, 0.5); + + return dist; +} + +auto w_ocr_engine::euclidean_distance(int x1, int x2, int y1, int y2) + -> double { + double dist_x = std::pow(float(x1 - x2), 2.0); + double dist_y = std::pow(float(y1 - y2), 2.0); + double dist = std::pow(dist_x + dist_y, 0.5); + + return dist; +} + +auto w_ocr_engine::spaces_between_two_chars(characters_struct left_char, + characters_struct right_char, + float height_to_dist_ratio) + -> std::string { + std::string temp_spaces = ""; + + int left_char_right_corner = + left_char.bound_rect.x + left_char.bound_rect.width; + int right_char_left_corner = right_char.bound_rect.x; + + if (right_char_left_corner - left_char_right_corner > 0) { + if (float(right_char_left_corner - left_char_right_corner) > + float(left_char.bound_rect.height) * height_to_dist_ratio) { + temp_spaces = " "; + } else { + temp_spaces = " "; + } + } + + return temp_spaces; +} + +std::vector +w_ocr_engine::char_clusters_to_text( + std::vector> clustered_characters) { + std::vector words; + + for (size_t i = 0; i < clustered_characters.size(); i++) { + std::sort(clustered_characters[i].begin(), clustered_characters[i].end()); + character_and_center temp; + temp.center = clustered_characters[i][0].center; + + std::string spaces = ""; + float height_to_dist_ratio = + get_env_float("SOCCER_GLOBAL_HEIGHT_TO_DIST_RATIO"); + + for (size_t j = 0; j < clustered_characters[i].size(); j++) { + std::string temp_string = + split_string(clustered_characters[i][j].text, '\n')[0]; + if (j < clustered_characters[i].size() - 1) { + spaces = spaces_between_two_chars(clustered_characters[i][j], + clustered_characters[i][j + 1], + height_to_dist_ratio); + } + temp_string += spaces; + temp.text.append(temp_string); + } + + std::transform(temp.text.begin(), temp.text.end(), temp.text.begin(), + ::toupper); + + words.push_back(temp); + } + + clustered_characters.clear(); + + std::sort(words.begin(), words.end()); + return words; +} + +std::vector +w_ocr_engine::filter_chars_by_contour_size( + _Inout_ std::vector &character, + _In_ config_for_ocr_struct &ocr_config) { + std::vector filtered_characters; + for (int i = 0; i < character.size(); i++) { + double area = cv::contourArea(character[i].contour); + if (area < ocr_config.restrictions.min_area || + area > ocr_config.restrictions.max_area) { + continue; + } + if (character[i].bound_rect.height < ocr_config.restrictions.min_height || + character[i].bound_rect.height > ocr_config.restrictions.max_height || + character[i].bound_rect.width < ocr_config.restrictions.min_width || + character[i].bound_rect.width > ocr_config.restrictions.max_width) { + continue; + } + filtered_characters.push_back(character[i]); + } + return filtered_characters; +} + +std::vector +w_ocr_engine::image_to_char_structs(_In_ cv::Mat &image_box, + _In_ config_for_ocr_struct &ocr_config) { + cv::Mat filtered_image = + prepare_image_for_contour_detection(image_box, ocr_config); + + std::vector> contours = + find_all_countors(filtered_image); + std::vector characters = + contours_to_char_structs(contours); + + std::vector filtered_characters = + filter_chars_by_contour_size(characters, ocr_config); + + merge_overlapped_contours(filtered_characters, ocr_config); + + for (size_t i = 0; i < filtered_characters.size(); i++) { + margin_bounding_rect(filtered_characters[i].bound_rect, ocr_config.margin, + filtered_image); + } + + // TODO add this log "This code has not been optimized for color image, yet" + // << std::endl; + return filtered_characters; +} + +auto w_ocr_engine::char_vec_to_string( + _In_ std::vector char_vector, + _In_ cv::Mat &frame, _In_ config_for_ocr_struct &ocr_config) + -> std::vector { + std::vector labeled_characters = + label_chars_in_char_structs(char_vector, frame, ocr_config); + std::vector> + clustered_characters = + cluster_char_structs(labeled_characters, ocr_config); + std::vector string = + char_clusters_to_text(clustered_characters); + + return string; +} + +std::vector +w_ocr_engine::image_to_string(_In_ cv::Mat &image, + _In_ config_for_ocr_struct &ocr_config) { + std::vector characters = + image_to_char_structs(image, ocr_config); + std::vector labeled_characters = + label_chars_in_char_structs(characters, image, ocr_config); + std::vector> clustered_characters = + cluster_char_structs(labeled_characters, ocr_config); + std::vector string = + char_clusters_to_text(clustered_characters); + + return string; +} + +std::vector +w_ocr_engine::label_chars_in_char_structs( + _In_ std::vector &characters, + _In_ cv::Mat &image_box, _In_ config_for_ocr_struct &ocr_config) { + std::vector labeled_chars; + tesseract::TessBaseAPI *tess_api; + if (ocr_config.is_digit) { + tess_api = digit_api; + } else { + tess_api = word_api; + } + + for (size_t i = 0; i < characters.size(); i++) { + cv::Mat temp_contour_image; + cv::Mat contour_image; + // Sometimes it is better to use the original image for w_ocr_engine + if (ocr_config.binary) { + cv::Mat filtered_image = + prepare_image_for_contour_detection(image_box, ocr_config); + filtered_image(characters[i].bound_rect).copyTo(contour_image); + } else { + // original_image(modified_bounding_rects[i].bound_rect).copyTo(contour_image); + contour_image = mask_contour(image_box, characters[i]); + } + + if (ocr_config.is_white) { + negative_image(contour_image); + } + + if (ocr_config.verbose) { + temp_contour_image = contour_image.clone(); + } + + enhance_contour_image_for_model(contour_image, ocr_config); + tess_api->SetImage(contour_image.data, contour_image.cols, + contour_image.rows, 3, int(contour_image.step)); + + std::string text_data = tess_api->GetUTF8Text(); + + if (std::strcmp(text_data.c_str(), "") != 0) { + characters_struct temp_character; + temp_character = characters[i]; + temp_character.text = split_string(text_data, '\n')[0]; + + if (!temp_character.text.empty()) { + temp_character.text = temp_character.text.substr(0, 1); + } + + labeled_chars.push_back(temp_character); + } + contour_image.release(); + temp_contour_image.release(); + } + return labeled_chars; +} + +void w_ocr_engine::margin_bounding_rect(_Inout_ cv::Rect &bounding_rect, + _In_ int margin, + _In_ cv::Mat &filtered_image) { + int height = filtered_image.rows; + int width = filtered_image.cols; + int temp; + int temp_margin_width = int(std::ceil(float(margin) / 2)); + int temp_margin_height = int(std::ceil(float(margin) / 2)); + + if (bounding_rect.width < 1 && margin > 0) { + temp_margin_width = 2; + } + + if (bounding_rect.width < bounding_rect.height / 5) { + // temp = (bounding_rect.x - 1 * bounding_rect.width); + // bounding_rect.x = temp > 0 ? temp : 0; + // temp = (bounding_rect.x + 4 * bounding_rect.width); + // bounding_rect.width = temp < width ? 4 * bounding_rect.width : (width - + // bounding_rect.x - 1); + temp = (bounding_rect.x - 1 * bounding_rect.height / 6); + bounding_rect.x = temp > 0 ? temp : 0; + temp = (bounding_rect.x + bounding_rect.height); + bounding_rect.width = + temp < width ? bounding_rect.height / 2 : (width - bounding_rect.x - 1); + } else { + temp = (bounding_rect.x - temp_margin_width); + bounding_rect.x = temp > 0 ? temp : 0; + temp = (bounding_rect.x + bounding_rect.width + 3 * temp_margin_width); + bounding_rect.width = temp < width + ? bounding_rect.width + 3 * temp_margin_width + : (width - bounding_rect.x - 1); + } + + temp = (bounding_rect.y - temp_margin_height); + bounding_rect.y = temp > 0 ? temp : 0; + temp = (bounding_rect.y + bounding_rect.height + 2 * temp_margin_height); + bounding_rect.height = temp < height + ? bounding_rect.height + 2 * temp_margin_height + : (height - bounding_rect.y - 1); +} + +cv::Mat w_ocr_engine::mask_contour(_In_ cv::Mat &image, + _In_ characters_struct &contour_info) { + cv::Mat temp_plane_image = + cv::Mat(cv::Size(image.cols, image.rows), CV_8UC1, cv::Scalar(0)); + cv::Mat mask_image; + cv::Mat contour_image; + + std::vector> temp_contours; + temp_contours.push_back(contour_info.contour); + std::vector> hull(temp_contours.size()); + for (unsigned int i = 0, n = temp_contours.size(); i < n; ++i) { + cv::convexHull(cv::Mat(temp_contours[i]), hull[i], false); + } + cv::drawContours(temp_plane_image, temp_contours, 0, cv::Scalar(255), 3); + cv::fillPoly(temp_plane_image, temp_contours, cv::Scalar(255)); + + temp_plane_image(contour_info.bound_rect).copyTo(mask_image); + image(contour_info.bound_rect).copyTo(contour_image, mask_image); + + temp_plane_image.release(); + mask_image.release(); + return contour_image; +} + +void w_ocr_engine::merge_overlapped_contours( + _Inout_ std::vector &character, + _In_ config_for_ocr_struct &ocr_config) { + cv::Rect ref_box; + int index; + std::vector overlapped_boxes_index; + // int width, height; + + bool flag; + if (character.size() > 0) { + flag = true; + } else { + flag = false; + } + + index = 0; + while (flag) { + ref_box = character[index].bound_rect; + + overlapped_boxes_index.clear(); + for (int i = 0; i < character.size(); i++) { + if (i == index) { + continue; + } + if (check_if_overlapped(ref_box, character[i].bound_rect, ocr_config)) { + overlapped_boxes_index.push_back(i); + } + } + + for (int j = overlapped_boxes_index.size() - 1; j >= 0; j--) { + character[index].bound_rect.x = + std::min(character[index].bound_rect.x, + character[overlapped_boxes_index[j]].bound_rect.x); + character[index].bound_rect.y = + std::min(character[index].bound_rect.y, + character[overlapped_boxes_index[j]].bound_rect.y); + character[index].bound_rect.width = + std::max(character[index].bound_rect.x + + character[index].bound_rect.width, + character[overlapped_boxes_index[j]].bound_rect.x + + character[overlapped_boxes_index[j]].bound_rect.width) - + character[index].bound_rect.x; + character[index].bound_rect.height = + std::max(character[index].bound_rect.y + + character[index].bound_rect.height, + character[overlapped_boxes_index[j]].bound_rect.y + + character[overlapped_boxes_index[j]].bound_rect.height) - + character[index].bound_rect.y; + character.erase(character.begin() + int(overlapped_boxes_index[j])); + } + + overlapped_boxes_index.clear(); + if (index >= character.size() - 1) { + flag = false; + } + + index++; + } +} + +std::vector> +w_ocr_engine::cluster_char_structs( + std::vector characters, + config_for_ocr_struct &ocr_config) { + std::vector> clustered_characters; + + if (characters.size() == 0) { + return clustered_characters; + } + + std::vector temp_char_cluster; + std::vector temp_index_list; + bool is_clustering; + + temp_char_cluster.push_back(characters.back()); + characters.pop_back(); + + if (characters.size() > 0) { + is_clustering = true; + + while (is_clustering) { + for (size_t index = 0; index < characters.size(); index++) { + for (size_t i = 0; i < temp_char_cluster.size(); i++) { + if (~temp_char_cluster[i].processed) { + // double temp_dist = euclidean_distance(temp_char_cluster[i], + // characters[index]); + + double temp_dist_1 = + euclidean_distance(temp_char_cluster[i].bound_rect.x, + characters[index].bound_rect.x + + characters[index].bound_rect.width, + temp_char_cluster[i].bound_rect.y + + temp_char_cluster[i].bound_rect.height, + characters[index].bound_rect.y + + characters[index].bound_rect.height); + double temp_dist_2 = + euclidean_distance(temp_char_cluster[i].bound_rect.x + + temp_char_cluster[i].bound_rect.width, + characters[index].bound_rect.x, + temp_char_cluster[i].bound_rect.y + + temp_char_cluster[i].bound_rect.height, + characters[index].bound_rect.y + + characters[index].bound_rect.height); + + int temp_y_dist = std::abs(temp_char_cluster[i].bound_rect.y - + characters[index].bound_rect.y); + + if ((temp_dist_1 < 0.8 * double(temp_char_cluster[i].height) || + temp_dist_2 < 0.8 * double(temp_char_cluster[i].height)) && + temp_y_dist < temp_char_cluster[i].height) { + temp_index_list.push_back(index); + break; + } + } + } + } + + for (size_t i = 0; i < temp_char_cluster.size(); i++) { + temp_char_cluster[i].processed = true; + } + + if (temp_index_list.size() > 0) { + std::reverse(temp_index_list.begin(), temp_index_list.end()); + + for (size_t i = 0; i < temp_index_list.size(); i++) { + temp_char_cluster.push_back(characters[temp_index_list[i]]); + characters.erase(characters.begin() + int(temp_index_list[i])); + } + + if (characters.size() == 0) { + clustered_characters.push_back(temp_char_cluster); + temp_char_cluster.clear(); + } + } else { + clustered_characters.push_back(temp_char_cluster); + temp_char_cluster.clear(); + + temp_char_cluster.push_back(characters.back()); + characters.pop_back(); + if (characters.size() == 0) { + clustered_characters.push_back(temp_char_cluster); + temp_char_cluster.clear(); + } + } + + if (characters.size() == 0) { + is_clustering = false; + } + + temp_index_list.clear(); + } + } else { + clustered_characters.push_back(temp_char_cluster); + temp_char_cluster.clear(); + // return; + } + return clustered_characters; +} + +cv::Mat w_ocr_engine::show_in_better_way(cv::Mat &input_image, + int out_put_image_height, + float resize_factor) { + int height = out_put_image_height; + int width = height * 4 / 3; + cv::Mat temp_image; + if (resize_factor * input_image.rows > height) { + resize_factor = float(height / input_image.rows); + } + if (resize_factor * input_image.cols > width) { + resize_factor = float(width / input_image.cols); + } + if (input_image.channels() == 3) { + temp_image = cv::Mat(cv::Size(width, height), CV_8UC3, cv::Scalar(0, 0, 0)); + } else { + temp_image = cv::Mat(cv::Size(width, height), CV_8UC1, cv::Scalar(0)); + } + + cv::Mat temp_contour = input_image.clone(); + cv::resize(temp_contour, temp_contour, + cv::Size(input_image.cols * resize_factor, + input_image.rows * resize_factor)); + temp_contour.copyTo( + temp_image(cv::Rect(0, 0, input_image.cols * resize_factor, + input_image.rows * resize_factor))); + + temp_contour.release(); + return temp_image; +} + +std::vector w_ocr_engine::split_string(std::string input_string, + char reference) { + std::stringstream test(input_string); + std::string segment; + std::vector seglist; + + while (std::getline(test, segment, reference)) { + seglist.push_back(segment); + } + + return seglist; +} + +auto w_ocr_engine::same_height( + _In_ std::vector pClusteredChars) -> bool { + bool result = true; + int average_height = 0; + + for (int i = 0; i < pClusteredChars.size(); i++) { + average_height += pClusteredChars[i].height; + } + average_height /= pClusteredChars.size(); + int min_height = average_height - average_height / 5; + int max_height = average_height + average_height / 5; + + for (int i = 0; i < pClusteredChars.size(); i++) { + if (pClusteredChars[i].height > max_height || + pClusteredChars[i].height < min_height) { + result = false; + } + } + + return result; +} + +auto w_ocr_engine::same_level( + _In_ std::vector pClusteredChars) -> bool { + bool result = true; + int average_height = 0; + int average_level = 0; + + for (int i = 0; i < pClusteredChars.size(); i++) { + average_height += pClusteredChars[i].height; + average_level += + pClusteredChars[i].bound_rect.y + pClusteredChars[i].bound_rect.height; + } + average_height /= pClusteredChars.size(); + average_level /= pClusteredChars.size(); + int min_level = average_level - average_height / 10; + int max_level = average_level + average_height / 10; + + for (int i = 0; i < pClusteredChars.size(); i++) { + if (pClusteredChars[i].bound_rect.y + pClusteredChars[i].bound_rect.height > + max_level || + pClusteredChars[i].bound_rect.y + pClusteredChars[i].bound_rect.height < + min_level) { + result = false; + } + } + + return result; +} + +auto w_ocr_engine::show_contours( + _Inout_ cv::Mat &pImage, + _In_ std::vector pClusteredChars, + _In_ std::string pWindowName, _In_ bool pShow) -> void { + cv::Mat mask_image; + + for (int i = 0; i < pClusteredChars.size(); i++) { + std::vector> temp_contours; + temp_contours.push_back(pClusteredChars[i].contour); + std::vector> hull(temp_contours.size()); + for (unsigned int i = 0, n = temp_contours.size(); i < n; ++i) { + cv::convexHull(cv::Mat(temp_contours[i]), hull[i], false); + } + cv::drawContours(pImage, temp_contours, 0, cv::Scalar(255), 3); + cv::fillPoly(pImage, temp_contours, cv::Scalar(255)); + } + + if (pShow) { + cv::imshow(pWindowName, pImage); + cv::waitKey(); + } + + return; +} + +auto w_ocr_engine::fill_cluster_features( + _Inout_ std::vector &pClusteredChar, + _In_ int pImageWidth, _In_ int pIndex) -> cluster_features { + cluster_features features; + + features.index_in_parent_vector = pIndex; + + features.min_x = pClusteredChar[0].bound_rect.x; + features.max_x = + pClusteredChar[0].bound_rect.x + pClusteredChar[0].bound_rect.width; + features.min_y = pClusteredChar[0].bound_rect.y; + features.average_y = pClusteredChar[0].bound_rect.y; + features.average_height = pClusteredChar[0].bound_rect.height; + + for (int i = 1; i < pClusteredChar.size(); i++) { + features.min_x = (features.min_x > pClusteredChar[i].bound_rect.x) + ? pClusteredChar[i].bound_rect.x + : features.min_x; + features.max_x = (features.max_x > pClusteredChar[i].bound_rect.x + + pClusteredChar[i].bound_rect.width) + ? features.max_x + : pClusteredChar[i].bound_rect.x + + pClusteredChar[i].bound_rect.width; + features.min_y = (features.min_y > pClusteredChar[i].bound_rect.y) + ? pClusteredChar[i].bound_rect.y + : features.min_y; + features.average_y += pClusteredChar[0].bound_rect.y; + features.average_height += pClusteredChar[0].bound_rect.height; + } + + features.average_y /= pClusteredChar.size(); + features.average_height /= pClusteredChar.size(); + + int mid = pImageWidth / 2; + int temp1 = mid - features.min_x; + int temp2 = mid - features.max_x; + + if (temp1 >= 0) { + if (temp2 > 0) { + features.position = cluster_position::left; + features.symmetric_x1 = temp2; + features.symmetric_x2 = temp1; + } else { + features.position = cluster_position::middle; + features.symmetric_x1 = 0; + features.symmetric_x2 = 0; + } + } else { + if (temp2 >= 0) { + features.position = cluster_position::middle; + features.symmetric_x1 = 0; + features.symmetric_x2 = 0; + } else { + features.position = cluster_position::right; + features.symmetric_x1 = abs(temp1); + features.symmetric_x2 = abs(temp2); + } + } + + return features; +} + +auto w_ocr_engine::check_twin_clusters(_In_ cluster_features &pFirstInput, + _In_ cluster_features &pSecondInput, + _In_ float pThreshold) -> bool { + bool result = false; + + float Y_diff_ration = + float(std::abs(pFirstInput.average_y - pSecondInput.average_y)) / + float((pFirstInput.average_height + pSecondInput.average_height) / 2); + float a, b, overlapped_ratio; + + float height_diff_ratio = + (pFirstInput.average_height > pSecondInput.average_height) + ? float(pFirstInput.average_height - pSecondInput.average_height) / + float(pFirstInput.average_height) + : float(pSecondInput.average_height - pFirstInput.average_height) / + float(pSecondInput.average_height); + + if (Y_diff_ration < 0.05 && height_diff_ratio < 0.05) { + a = (pFirstInput.symmetric_x1 < pSecondInput.symmetric_x1) + ? float(pSecondInput.symmetric_x1) + : float(pFirstInput.symmetric_x1); + b = (pFirstInput.symmetric_x2 < pSecondInput.symmetric_x2) + ? float(pFirstInput.symmetric_x2) + : float(pSecondInput.symmetric_x2); + + overlapped_ratio = + (b - a) / float(pFirstInput.symmetric_x2 - pFirstInput.symmetric_x1); + float temp = + (b - a) / float(pSecondInput.symmetric_x2 - pSecondInput.symmetric_x1); + + overlapped_ratio = (overlapped_ratio > temp) ? overlapped_ratio : temp; + + if (overlapped_ratio > pThreshold) { + result = true; + } + } + + return result; +} + +auto w_ocr_engine::keep_twins( + _Inout_ std::vector> &pClusteredChar, + _In_ int pImageWidth, _In_ int pImageHeight, _In_ bool pWord) -> void { + std::vector cluster_features_vector; + int n_cluster = pClusteredChar.size(); + + for (int i = 0; i < n_cluster; i++) { + cluster_features_vector.push_back( + fill_cluster_features(pClusteredChar[i], pImageWidth, i)); + } + + for (int i = 0; i < n_cluster - 1; i++) { + if (cluster_features_vector[i].matched) { + continue; + } + for (int j = i + 1; j < n_cluster; j++) { + if (cluster_features_vector[j].matched) { + continue; + } + if (check_twin_clusters(cluster_features_vector[i], + cluster_features_vector[j], 0.6)) { + cluster_features_vector[i].matched = true; + cluster_features_vector[j].matched = true; + + cluster_features_vector[i].twin_index_in_parent_vector = + cluster_features_vector[j].index_in_parent_vector; + cluster_features_vector[j].twin_index_in_parent_vector = + cluster_features_vector[i].index_in_parent_vector; + } + } + } + + if (pWord) { + for (int i = 1; i < n_cluster; i++) { + if (!cluster_features_vector[i].matched) { + continue; + } + + if (cluster_features_vector[i].average_y > pImageHeight * 4 / 5) { + cluster_features_vector[i].matched = false; + cluster_features_vector[cluster_features_vector[i] + .twin_index_in_parent_vector] + .matched = false; + } + } + + int more = -1; + + for (int i = 1; i < n_cluster; i++) { + if (!cluster_features_vector[i].matched) { + continue; + } + if (more == -1) { + more = pClusteredChar[i].size(); + } + + if (more < pClusteredChar[i].size()) { + more = pClusteredChar[i].size(); + } + } + + for (int i = 1; i < n_cluster; i++) { + if (!cluster_features_vector[i].matched) { + continue; + } + + if (more > pClusteredChar[i].size() && + more > pClusteredChar[cluster_features_vector[i] + .twin_index_in_parent_vector] + .size()) { + cluster_features_vector[i].matched = false; + cluster_features_vector[cluster_features_vector[i] + .twin_index_in_parent_vector] + .matched = false; + } + } + } else { + int largest = -1; + + for (int i = 1; i < n_cluster; i++) { + if (!cluster_features_vector[i].matched) { + continue; + } + if (largest == -1) { + largest = cluster_features_vector[i].average_height; + } + + if (largest < cluster_features_vector[i].average_height) { + largest = cluster_features_vector[i].average_height; + } + } + + for (int i = 1; i < n_cluster; i++) { + if (!cluster_features_vector[i].matched) { + continue; + } + if (largest == -1) { + largest = cluster_features_vector[i].average_height; + } + + if (largest > cluster_features_vector[i].average_height && + largest > cluster_features_vector[cluster_features_vector[i] + .twin_index_in_parent_vector] + .average_height) { + cluster_features_vector[i].matched = false; + cluster_features_vector[cluster_features_vector[i] + .twin_index_in_parent_vector] + .matched = false; + } else { + largest = cluster_features_vector[i].average_height; + } + } + } + + for (int i = 0; i < n_cluster; i++) { + int index = n_cluster - (i + 1); + if (!cluster_features_vector[index].matched) { + pClusteredChar.erase(pClusteredChar.begin() + index); + } + } + + return; +} + +auto w_ocr_engine::keep_time( + _Inout_ std::vector> &pClusteredChar) + -> void { + int n_cluster = pClusteredChar.size(); + if (n_cluster == 0) { + return; + } + int height = pClusteredChar[n_cluster - 1][0].bound_rect.y; + + for (int i = 1; i < n_cluster; i++) { + int index = n_cluster - (i + 1); + if (height < pClusteredChar[index][0].bound_rect.y) { + pClusteredChar.erase(pClusteredChar.begin() + index); + } else { + height = pClusteredChar[index][0].bound_rect.y; + pClusteredChar.pop_back(); + } + } +} + +auto w_ocr_engine::add_text_to_original_image( + _Inout_ cv::Mat &pImage, + _In_ std::vector &pClusteredChar) -> void { + for (int i = 0; i < pClusteredChar.size(); i++) { + cv::putText(pImage, pClusteredChar[i].text, + cv::Point(pClusteredChar[i].bound_rect.x, + pClusteredChar[i].bound_rect.y + 100), + cv::FONT_HERSHEY_DUPLEX, 1, cv::Scalar(0, 255, 0), false); + } + + return; +} diff --git a/wolf/ml/referee_ocr/w_ocr_engine.hpp b/wolf/ml/referee_ocr/w_ocr_engine.hpp new file mode 100644 index 000000000..6e20db4ae --- /dev/null +++ b/wolf/ml/referee_ocr/w_ocr_engine.hpp @@ -0,0 +1,448 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "w_image_processor.hpp" + +namespace wolf::ml::ocr { + +//! optical character reader class. +/*! \brief This class is responsible for manipulating the OCR tasks. + + This class contains functions, structures, and variables that use + for optical character reading purposes. In the project, in other classes, + in the case of reading an optical character, one must create an object of + the OCR class. +*/ +class w_ocr_engine { +public: + //! The enum shows the position of the cluster in the whole image. + enum cluster_position { left, right, middle }; + //! The struct contains the features related to a cluster. + struct cluster_features { + /*! contour; + /*! char_vector, + _In_ cv::Mat &frame, _In_ config_for_ocr_struct &ocr_config) + -> std::vector; + + /*! + The image_to_string function gets an image and returns the text of the + box. + + \param frame_box The image needs to be processed. + \param ocr_config The necessary configurations for processing + optical characters. \return returns the text of frame_box + */ + std::vector + image_to_string(_In_ cv::Mat &frame_box, + _In_ config_for_ocr_struct &ocr_config); + + /*! + The check_if_overlapped checks the input rect of boxes to decide if + two rects overlapped. + + \param box_1 The rect information of the first box. + \param box_2 The rect information of the second box. + \param ocr_config The necessary configurations for processing + optical characters. \return True, if two boxes overlapped. + */ + bool check_if_overlapped(_In_ cv::Rect box_1, _In_ cv::Rect box_2, + _In_ config_for_ocr_struct &ocr_config); + + /*! + Get vector of contours and create a vector of char structs. this + structs does not have text. + + \param contours a vector contains contours. + \return a vector contains character structs + */ + std::vector + contours_to_char_structs(_In_ std::vector> contours); + + /*! + The enchance_contour_image function modifies the background of the + contour, makes the background white. The function also resizes the contour + image to reach a better w_ocr_engine result. + + \param contour_image The modified contour image. The output image + of the function. \param ocr_config The necessary configurations for + processing optical characters. + */ + void enhance_contour_image_for_model(_Inout_ cv::Mat &contour_image, + _In_ config_for_ocr_struct &ocr_config); + + /*! + The euclidean_distance function calculates the euclidean distance of + two input characters. + + \param first_character The first character. + \param second_character The second character. + \return The calculated distance of two input characters. + */ + double euclidean_distance(characters_struct &first_character, + characters_struct &second_character); + + auto euclidean_distance(int x1, int x2, int y1, int y2) -> double; + + /*! + The spaces_between_two_chars function returns a string containing the + spaces that should be placed between characters. + + \param left_char The left hand side character. + \param right_char The right hand side character. + \param height_to_dist_ratio The ratio of the character height and + distance. \return The spaces that should be placed between characters. + */ + auto spaces_between_two_chars(_In_ characters_struct left_char, + _In_ characters_struct right_char, + _In_ float height_to_dist_ratio) -> std::string; + + /*! + The char_clusters_to_text puts the clustered characters together to + create the word. This function uses one of the class variables as input and + stores the result in another variable of the class. + */ + std::vector char_clusters_to_text( + _In_ std::vector> clustered_characters); + + /*! + The filter_chars_by_contour_size function eliminates abnormal contours + from the char struct vector and returns new vector. + + \param contours A vector of all contours. + \param ocr_config The necessary configurations for processing + optical characters. \return a vector of char structs. + */ + std::vector + filter_chars_by_contour_size(_In_ std::vector &character, + _In_ config_for_ocr_struct &ocr_config); + + /*! + the image_to_char_structs takes an image and returns a vector of char + struct. + + \param frame_box image contains characters. + \param ocr_config The necessary configurations for processing + optical characters. \return a vector of char structs. + */ + std::vector + image_to_char_structs(_In_ cv::Mat &frame_box, + _In_ config_for_ocr_struct &ocr_config); + + /*! + Takes vector of char structs and recognize text in each struct. + + \param characters vector of char structs + \param frame_box image contains characters. + \param ocr_config The necessary configurations for processing + optical characters. \return a vector of char structs. + */ + std::vector + label_chars_in_char_structs(_In_ std::vector &characters, + _In_ cv::Mat &frame_box, + _In_ config_for_ocr_struct &ocr_config); + + /*! + The margin_bounding_rect function margins the contours. It is + necessary for obtaining better results. + + \param bounding_rect A vector of the bounding rect of the + contours. \param margin The margin value. \param filtered_image image + contains characters. + + */ + void margin_bounding_rect(_Inout_ cv::Rect &bounding_rect, _In_ int margin, + _In_ cv::Mat &filtered_image); + + /*! + The mask_contour function applies the mask of the contour area to the + input image. The resulting image should contain the contour. + + \param image The input image. A window of the original image. The + window contains all characters of one property. \param contour_info + Information of the desired contour. \return The modified contour image. + The output image of the function. + */ + cv::Mat mask_contour(_In_ cv::Mat &image, + _In_ characters_struct &contour_info); + + /*! + The merge_overlapped_contours function merges the overlapped contours. + + \param bound_rect A vector of the bounding rect of the contours. + \param ocr_config The necessary configurations for processing + optical characters. + */ + void + merge_overlapped_contours(_Inout_ std::vector &bound_rect, + _In_ config_for_ocr_struct &ocr_config); + + /*! + The cluster_char_structs function puts related characters togethter. + + \param ocr_config The necessary configurations for processing + optical characters. + */ + std::vector> + cluster_char_structs(std::vector characters, + config_for_ocr_struct &ocr_config); + + /*! + The function resizes the input image and maps it in the output image. + It helps the programmers to check their algorithm's influence on the input + image in a better way. + + \param input_image The input image. + \param out_put_image_height The height of output image. + \param resize_factor The resize factor. + \return returns resized image + */ + cv::Mat show_in_better_way(cv::Mat &input_image, + int out_put_image_height = 400, + float resize_factor = 5); + + /*! + The split_string function uses for splitting input strings based on + the reference character. + + \param input_string The input string. + \param reference The strings that are located between two + references would be placed in a string. \return A vector of split + string. + */ + std::vector split_string(std::string input_string, + char reference); + + /*! + The same_height function returns true if all characters in the cluster + share the same height. + + \param pClusteredChars The cluster contains many characters. + \return The result would be true if all characters in the cluster + share the same height. + */ + auto same_height(_In_ std::vector pClusteredChars) -> bool; + + /*! + The same_level function returns true if all characters in the cluster + share the same y-position. + + \param pClusteredChars The cluster contains many characters. + \return The result would be true if all characters in the cluster + share the same y-position. + */ + auto same_level(_In_ std::vector pClusteredChars) -> bool; + + /*! + The same_level function returns true if all characters in the cluster + share the same y-position. + + \param image A frame of the game. + \param pClusteredChars The cluster contains many characters. + \param pWindowName The cv::imshow image name. + \param pShow + \return The output would be a cv image containing the clusters. + */ + static auto show_contours(_Inout_ cv::Mat &pImage, + _In_ std::vector pClusteredChars, + _In_ std::string pWindowName, + _In_ bool pShow = true) -> void; + + /*! + The fill_cluster_features function extracts all feature of the input + cluster. + + \param pClusteredChars The cluster contains many characters. + \param pImageWidth The width of input image in pixel. + \param pIndex The index of the array in the vector. + \return An structure of the cluster features. + */ + auto + fill_cluster_features(_Inout_ std::vector &pClusteredChar, + _In_ int pImageWidth, _In_ int pIndex) + -> cluster_features; + + /*! + The check_twin_clusters function checks the input cluster features and + decides whether two input clusters are twins. + + \param pFirstInput The first cluster features. + \param pSecondInput The second cluster features. + \param pThreshold The threshold for decision-making. + \return The result would be true if the input clusters are twins. + */ + auto check_twin_clusters(_In_ cluster_features &pFirstInput, + _In_ cluster_features &pSecondInput, + _In_ float pThreshold) -> bool; + + /*! + The keep_twins function eliminates the single clusters. + + \param pClusteredChars The cluster contains many characters. + \param pImageWidth The width of input image in pixel. + \param pWord The value would be "true" if it is a word + cluster. \return + */ + auto keep_twins( + _Inout_ std::vector> &pClusteredChar, + _In_ int pImageWidth, _In_ int pImageHeight, _In_ bool pWord) -> void; + + /*! + The keep_time function keeps the time cluster. + + \param pClusteredChars The cluster contains many characters. + \return + */ + auto + keep_time(_Inout_ std::vector> &pClusteredChar) + -> void; + + /*! + The add_text_to_original_image adds texts to the input image. + + \param pImage The input image. + \param pClusteredChars The cluster contains many characters. + \return + */ + auto add_text_to_original_image( + _Inout_ cv::Mat &pImage, + _In_ std::vector &pClusteredChar) -> void; + +private: + /*! get_tracer() { + auto provider = trace::Provider::GetTracerProvider(); + return provider->GetTracer("pes_21", OPENTELEMETRY_SDK_VERSION); +} + +} // namespace +#endif + +using w_read_video_frames = wolf::ml::ocr::w_read_video_frames; + +w_read_video_frames::w_read_video_frames(std::string video_path) { + path = video_path; + + cap = cv::VideoCapture(path.c_str(), cv::CAP_ANY); + if (!cap.isOpened()) { + std::cout << "Error opening video stream or file" << std::endl; + } + frame_count = cap.get(cv::CAP_PROP_FRAME_COUNT); +} + +cv::Mat w_read_video_frames::read_video_frame_by_frame() { + cv::Mat frame; + if (!cap.read(frame)) { + release(); + return frame; + } + frame_number += 1; + return frame; +} + +void w_read_video_frames::write_image_to_video(std::string out_video_path) { +#ifdef __TELEMETRY + auto scoped_span = + trace::Scope(get_tracer()->StartSpan("write_image_to_video")); +#endif + cv::Mat frame; + + frame = read_video_frame_by_frame(); + if (frame.empty()) { + std::cout << "Error opening video stream" << std::endl; + return; + } + int frame_width = frame.cols; + int frame_height = frame.rows; + + // Define the codec and create VideoWriter object.The output is stored in + // 'outcpp.avi' file. + cv::VideoWriter video(out_video_path, + cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), 60.0, + cv::Size(frame_width, frame_height)); + + while (true) { + frame = read_video_frame_by_frame(); + if (frame.empty()) + break; + + // Write the frame into the file 'outcpp.avi' + video.write(frame); + cv::waitKey(10); + } + + video.release(); + frame.release(); + + return; +} + +void w_read_video_frames::release() { cap.release(); } + +cv::Mat w_read_video_frames::read_specific_frame(int input_frame_number) { + frame_number = input_frame_number; + + cv::Mat frame; + if (frame_number > int(frame_count)) { + release(); + return frame; + } + cap.set(1, frame_number); + + if (!cap.read(frame)) { + return frame; + } + + return frame; +} + +int w_read_video_frames::get_current_frame_number() { return frame_number; } + +double w_read_video_frames::get_frame_amount() { return frame_count; } + +void w_read_video_frames::video_player() { + w_read_video_frames player(path); + + int skip = 100; + int frame_number = 52000 - skip; + + while (true) { + frame_number += skip; + + cv::Mat frame = player.read_specific_frame(frame_number); + cv::imshow("video player", frame); + std::cout << frame_number << std::endl; + int key = cv::waitKey(); + // std::cout << key << std::endl; + + if (key == 1113937 || key == 1113940) { + frame_number -= 2 * skip; + } + } +} diff --git a/wolf/ml/referee_ocr/w_read_video_frames.hpp b/wolf/ml/referee_ocr/w_read_video_frames.hpp new file mode 100644 index 000000000..6c571acf1 --- /dev/null +++ b/wolf/ml/referee_ocr/w_read_video_frames.hpp @@ -0,0 +1,82 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#pragma once + +#include +#include + +#include "wolf.hpp" + +namespace wolf::ml::ocr { + +//! read frames of the video class. +/*! \brief This class is responsible for reading the frame of the input + video. + + This class contains functions, structures, and variables that use + for reading the frames of the input video. +*/ +class w_read_video_frames { +public: + /*! +#include + +using w_referee = wolf::ml::ocr::w_referee; +using w_ocr_engine = wolf::ml::ocr::w_ocr_engine; + +w_referee::w_referee() {} + +void w_referee::match_result_struct::release() { + match_result_struct::result_image.release(); +} + +w_ocr_engine::character_and_center w_referee::concatenate_name_result( + std::vector &result) { + w_ocr_engine::character_and_center temp_concatenated_result = result[0]; + + for (size_t i = 1; i < result.size(); i++) { + temp_concatenated_result.text = + temp_concatenated_result.text + " " + result[i].text; + } + + return temp_concatenated_result; +} + +auto w_referee::if_the_string_is_in_the_vector( + w_ocr_engine::character_and_center the_character, + std::vector &the_vector) -> bool { + bool is_in_the_vector = false; + + for (size_t i = 0; i < the_vector.size(); i++) { + // std::cout << the_string.c_str() << " : " << the_vector[i].str.c_str() << + // " " << std::strcmp(the_string.c_str(), the_vector[i].str.c_str()) << + // std::endl; + if (std::strcmp(the_character.text.c_str(), the_vector[i].str.c_str()) == + 0) { + is_in_the_vector = true; + the_vector[i].already_voted++; + break; + } + } + + if (!is_in_the_vector) { + vote_over_string_vector temp; + temp.str = the_character.text; + temp.center = the_character.center; + the_vector.push_back(temp); + } + + return is_in_the_vector; +} + +auto w_referee::voting_over_results_and_names( + frame_result_struct &voted_results, + std::vector &all_results) -> void { + std::vector home_result{}; + std::vector away_result{}; + std::vector home_name{}; + std::vector away_name{}; + std::vector home_penalty_result{}; + std::vector away_penalty_result{}; + + home_result.clear(); + away_result.clear(); + home_name.clear(); + away_name.clear(); + + for (int j = 0; j < all_results.size(); j++) { + if_the_string_is_in_the_vector(all_results[size_t(j)].home_result, + home_result); + if_the_string_is_in_the_vector(all_results[size_t(j)].away_result, + away_result); + if_the_string_is_in_the_vector(all_results[size_t(j)].home_name, home_name); + if_the_string_is_in_the_vector(all_results[size_t(j)].away_name, away_name); + if (all_results[size_t(j)].home_penalty_result.text.compare("") != 0) { + if_the_string_is_in_the_vector(all_results[size_t(j)].home_penalty_result, + home_penalty_result); + } + if (all_results[size_t(j)].away_penalty_result.text.compare("") != 0) { + if_the_string_is_in_the_vector(all_results[size_t(j)].away_penalty_result, + away_penalty_result); + } + } + + std::sort(home_result.begin(), home_result.end()); + std::sort(away_result.begin(), away_result.end()); + std::sort(home_name.begin(), home_name.end()); + std::sort(away_name.begin(), away_name.end()); + if (home_penalty_result.size() != 0 && away_penalty_result.size() != 0) { + std::sort(home_penalty_result.begin(), home_penalty_result.end()); + std::sort(away_penalty_result.begin(), away_penalty_result.end()); + voted_results.home_penalty_result.text = + home_penalty_result[home_penalty_result.size() - 1].str; + voted_results.home_penalty_result.center = + home_penalty_result[home_penalty_result.size() - 1].center; + voted_results.away_penalty_result.text = + away_penalty_result[away_penalty_result.size() - 1].str; + voted_results.away_penalty_result.center = + away_penalty_result[away_penalty_result.size() - 1].center; + } + voted_results.home_result.text = home_result[home_result.size() - 1].str; + voted_results.home_result.center = home_result[home_result.size() - 1].center; + voted_results.away_result.text = away_result[away_result.size() - 1].str; + voted_results.away_result.center = away_result[away_result.size() - 1].center; + voted_results.home_name.text = home_name[home_name.size() - 1].str; + voted_results.home_name.center = home_name[home_name.size() - 1].center; + voted_results.away_name.text = away_name[away_name.size() - 1].str; + voted_results.away_name.center = away_name[away_name.size() - 1].center; + + voted_results.frame_number = all_results[0].frame_number; + voted_results.stat = all_results[0].stat; + + return; +} diff --git a/wolf/ml/referee_ocr/w_referee.hpp b/wolf/ml/referee_ocr/w_referee.hpp new file mode 100644 index 000000000..f84f0c933 --- /dev/null +++ b/wolf/ml/referee_ocr/w_referee.hpp @@ -0,0 +1,198 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#pragma once + +#include +#include + +#include "w_ocr_engine.hpp" +#include "w_read_video_frames.hpp" +#include "wolf.hpp" + +namespace wolf::ml::ocr { + +//! w_referee class. +/*! \brief This class is a bridge between the w_ocr_engine class and the referee + task. + + This class contains functions, structures, and variables that use the + w_ocr_engine class for doing referee purposes. +*/ +class w_referee { +public: + //! The result_type enum shows the state of the result. + /*! + The result of the game would be stored in the database based on this + enum and each stat has own properties. + */ + enum result_type { + first_half, + second_half, + extra_first_half, + extra_second_half, + penalty, + final_result + }; + + //! A structure for the frame result. + /*! + The structure will be filled after the end of each frame processing and + contain the frame necessary results. + */ + struct frame_result_struct { + /*! all_frames_results; + /*! &result); + + /*! + The function updates the vector of voting. + + First, it checks if the input string is in the vector. If true, then + the voting value would be incremented. Otherwise, a new variable would be + added to the vector. + + \param the_character the input string and the related information + related to the string. \param the_vector vector of string with theirs + repetition number. \return is the string in the vector? + */ + W_API auto if_the_string_is_in_the_vector( + w_ocr_engine::character_and_center the_character, + std::vector &the_vector) -> bool; + + /*! + The voting function. + + \param voted_results The voted results. + \param all_results All results. + */ + W_API void + voting_over_results_and_names(frame_result_struct &voted_results, + std::vector &all_results); +}; +} // namespace wolf::ml::ocr \ No newline at end of file diff --git a/wolf/ml/referee_ocr/w_soccer.cpp b/wolf/ml/referee_ocr/w_soccer.cpp new file mode 100644 index 000000000..46534b84f --- /dev/null +++ b/wolf/ml/referee_ocr/w_soccer.cpp @@ -0,0 +1,606 @@ +#include "w_soccer.hpp" + +#include +#include +#include +#include +#include +#include + +#include "w_referee.hpp" +#include "w_utilities.hpp" + +using w_soccer = wolf::ml::ocr::w_soccer; +using w_ocr_engine = wolf::ml::ocr::w_ocr_engine; +using config_for_ocr_struct = wolf::ml::ocr::config_for_ocr_struct; +using w_referee = wolf::ml::ocr::w_referee; + +w_soccer::w_soccer() { + // LOG_P(w_log_type::W_LOG_INFO, "creating w_soccer object ..."); + + char *type_1 = new char[30]; + snprintf(type_1, 30, "SOCCER_SCREEN_IDENTITY"); + screen_identity = set_config(type_1); + + char *type_2 = new char[30]; + snprintf(type_2, 30, "SOCCER_RESULT_HOME"); + result_home = set_config(type_2); + char *type_3 = new char[30]; + snprintf(type_3, 30, "SOCCER_RESULT_AWAY"); + result_away = set_config(type_3); + + char *type_4 = new char[30]; + snprintf(type_4, 30, "SOCCER_NAME_HOME"); + name_home = set_config(type_4); + char *type_5 = new char[30]; + snprintf(type_5, 30, "SOCCER_NAME_AWAY"); + name_away = set_config(type_5); + + char *type_6 = new char[30]; + snprintf(type_6, 30, "SOCCER_PLATFORM_FREE"); + platform_free = set_config_for_ocr(type_6); + + char *type_7 = new char[30]; + snprintf(type_7, 30, "SOCCER_PENALTY"); + penalty = set_config_for_ocr(type_7); + + delete[] type_1; + delete[] type_2; + delete[] type_3; + delete[] type_4; + delete[] type_5; + delete[] type_6; + delete[] type_7; + + fill_stat_map(); +} + +w_soccer::~w_soccer() {} + +auto w_soccer::set_config(_In_ char *pType) -> w_ocr_engine::config_struct { + std::string type(pType); + + w_ocr_engine::config_struct config; + + config.name = get_env_string((type + "_WINDOW_NAME").c_str()); + config.window = get_env_cv_rect((type + "_WINDOW").c_str()); + config.is_time = get_env_boolean((type + "_IS_TIME").c_str()); + config.config_for_ocr.do_resize_contour = + get_env_boolean((type + "_DO_RESIZE_CONTOUR").c_str()); + config.config_for_ocr.gaussian_blur_win_size = + get_env_int((type + "_GAUSSIAN_BLUR_WIN_SIZE").c_str()); + config.config_for_ocr.if_store_image_boxes = + get_env_boolean((type + "_IF_STORE_IMAGE_BOXES").c_str()); + config.config_for_ocr.is_white = + get_env_boolean((type + "_IS_WHITE").c_str()); + config.config_for_ocr.is_digit = + get_env_boolean((type + "_IS_DIGIT").c_str()); + config.config_for_ocr.make_white_background = + get_env_boolean((type + "_MAKE_WHITE_BACKGROUND").c_str()); + config.config_for_ocr.margin = get_env_int((type + "_MARGIN").c_str()); + config.config_for_ocr.verbose = get_env_boolean((type + "_VERBOSE").c_str()); + config.config_for_ocr.threshold_value = + get_env_int((type + "_THRESHOLD").c_str()); + config.config_for_ocr.white_background_threshold = + get_env_int((type + "_WHITE_BACKGROUND_THRESHOLD").c_str()); + config.config_for_ocr.restrictions.max_area = + get_env_int((type + "_RESTRICTIONS_MAX_AREA").c_str()); + config.config_for_ocr.restrictions.min_area = + get_env_int((type + "_RESTRICTIONS_MIN_AREA").c_str()); + config.config_for_ocr.restrictions.max_width = + get_env_int((type + "_RESTRICTIONS_MAX_WIDTH").c_str()); + config.config_for_ocr.restrictions.min_width = + get_env_int((type + "_RESTRICTIONS_MIN_WIDTH").c_str()); + config.config_for_ocr.restrictions.max_height = + get_env_int((type + "_RESTRICTIONS_MAX_HEIGHT").c_str()); + config.config_for_ocr.restrictions.min_height = + get_env_int((type + "_RESTRICTIONS_MIN_HEIGHT").c_str()); + + return config; +} + +auto w_soccer::set_config_for_ocr(_In_ char *pType) -> config_for_ocr_struct { + std::string type(pType); + config_for_ocr_struct config; + + config.restrictions.max_area = + get_env_int((type + "_RESTRICTIONS_MAX_AREA").c_str()); + config.restrictions.min_area = + get_env_int((type + "_RESTRICTIONS_MIN_AREA").c_str()); + config.restrictions.max_width = + get_env_int((type + "_RESTRICTIONS_MAX_WIDTH").c_str()); + config.restrictions.min_width = + get_env_int((type + "_RESTRICTIONS_MIN_WIDTH").c_str()); + config.restrictions.max_height = + get_env_int((type + "_RESTRICTIONS_MAX_HEIGHT").c_str()); + config.restrictions.min_height = + get_env_int((type + "_RESTRICTIONS_MIN_HEIGHT").c_str()); + config.fraction = get_env_float((type + "_FRACTION").c_str()); + + return config; +} + +auto w_soccer::fill_stat_map() -> void { + stat_first_half = get_env_string("SOCCER_STAT_FIRST_HALF_STRING"); + stat_second_half = get_env_string("SOCCER_STAT_SECOND_HALF_STRING"); + stat_extra_first_half = get_env_string("SOCCER_STAT_EXTRA_FIRST_HALF_STRING"); + stat_extra_second_half = + get_env_string("SOCCER_STAT_EXTRA_SECOND_HALF_STRING"); + stat_penalty = get_env_string("SOCCER_STAT_PENALTY_STRING"); + + bool is_platform_free = get_env_boolean("SOCCER_GLOBAL_PLATFORM_FREE"); + + stat_map.insert(std::pair( + get_first_character_of_string(stat_first_half, is_platform_free), + "first_half")); + stat_map.insert(std::pair( + get_first_character_of_string(stat_second_half, is_platform_free), + "second_half")); + stat_map.insert(std::pair( + get_first_character_of_string(stat_extra_first_half, is_platform_free), + "extra_first_half")); + stat_map.insert(std::pair( + get_first_character_of_string(stat_extra_second_half, is_platform_free), + "extra_second_half")); + stat_map.insert(std::pair( + get_first_character_of_string(stat_penalty, is_platform_free), + "penalty")); +} + +auto w_soccer::extract_result_from_frame_boxes( + _In_ cv::Mat &frame, _Inout_ frame_result_struct &frame_data) -> void { + std::vector temp_words, temp_team_names; + + cv::Mat frame_box = frame(screen_identity.window); + temp_words = + ocr_object.image_to_string(frame_box, screen_identity.config_for_ocr); + + if (temp_words.size() == 1) { + if (temp_words[0].text.c_str()[0] == stat_first_half.c_str()[0] || + temp_words[0].text.c_str()[0] == stat_second_half.c_str()[0]) { + frame_data.stat = stat_map[temp_words[0].text]; + temp_words.clear(); + + frame_box = frame(result_home.window); + temp_words = + ocr_object.image_to_string(frame_box, result_home.config_for_ocr); + if (temp_words.size() != 0) { + frame_data.home_result = temp_words[0]; + + frame_box = frame(result_away.window); + temp_words = + ocr_object.image_to_string(frame_box, result_away.config_for_ocr); + if (temp_words.size() != 0) { + frame_data.away_result = temp_words[0]; + + temp_team_names.clear(); + + frame_box = frame(name_home.window); + temp_team_names = + ocr_object.image_to_string(frame_box, name_home.config_for_ocr); + if (temp_team_names.size() == 0) { + frame_box.release(); + return; + } + frame_data.home_name = concatenate_name_result(temp_team_names); + + frame_box = frame(name_away.window); + temp_team_names = + ocr_object.image_to_string(frame_box, name_away.config_for_ocr); + if (temp_team_names.size() == 0) { + frame_box.release(); + return; + } + frame_data.away_name = concatenate_name_result(temp_team_names); + } + } + } + } + frame_box.release(); +} + +auto w_soccer::extract_result_based_on_clusters_symmetricity( + _In_ cv::Mat &frame, _Inout_ frame_result_struct &frame_data) -> void { + cv::Mat cloned_image = frame.clone(); + int image_width = frame.cols; + + if (get_env_boolean("CONFIG_STORE_LATEST_FRAME")) { + std::string image_data_file_name = + get_env_string("SOCCER_GLOBAL_LOG_FILE") + "_" + + std::to_string(frame_number % 10); + std::ofstream ofs(image_data_file_name.c_str(), std::ofstream::out); + ofs.write((char *)frame.data, + frame.total() * frame.channels() * sizeof(uint8_t)); + ofs.close(); + } + + std::vector> digits_candidates; + std::vector> words_candidates; + std::vector> time_candidates; + + extract_all_image_char_clusters(cloned_image, digits_candidates, + words_candidates, time_candidates); + + if (digits_candidates.size() == 2 && words_candidates.size() == 2 && + time_candidates.size() == 1) { + for (int i = 0; i < words_candidates.size(); i++) { + std::vector text_of_cluster = + ocr_object.char_vec_to_string(words_candidates[i], frame, + name_home.config_for_ocr); + + if (text_of_cluster.size() > 0) { + if (text_of_cluster[0].center.x < image_width / 2) { + frame_data.home_name = text_of_cluster[0]; + } else { + frame_data.away_name = text_of_cluster[0]; + } + } + } + for (int i = 0; i < digits_candidates.size(); i++) { + std::vector text_of_cluster = + ocr_object.char_vec_to_string(digits_candidates[i], frame, + result_home.config_for_ocr); + if (text_of_cluster.size() > 0) { + if (text_of_cluster[0].center.x < image_width / 2) { + frame_data.home_result = text_of_cluster[0]; + } else { + frame_data.away_result = text_of_cluster[0]; + } + } + } + for (int i = 0; i < time_candidates.size(); i++) { + std::vector text_of_cluster = + ocr_object.char_vec_to_string(time_candidates[i], frame, + screen_identity.config_for_ocr); + if (text_of_cluster.size() > 0) { + frame_data.stat = get_nearest_string(text_of_cluster[0].text, stat_map); + if (frame_data.stat.compare("") == 0) { + w_ocr_engine::config_struct temp_screen_identity = screen_identity; + temp_screen_identity.config_for_ocr.is_digit = false; + text_of_cluster = ocr_object.char_vec_to_string( + time_candidates[i], frame, temp_screen_identity.config_for_ocr); + std::vector temp_result = + split_string(text_of_cluster[0].text, ' '); + if (temp_result.size() > 1) { + frame_data.stat = get_nearest_string(temp_result[1], stat_map); + } + } + if (frame_data.stat.compare("") != 0) { + extract_penalty_result_symmetricity(frame, digits_candidates, + words_candidates, time_candidates, + frame_data); + // frame_data.stat = stat_map[frame_data.stat]; + } + } + } + } + cloned_image.release(); +} + +auto w_soccer::extract_penalty_result_symmetricity( + _In_ cv::Mat &frame, + _In_ std::vector> + digits_candidates, + _In_ std::vector> + words_candidates, + _In_ std::vector> + time_candidates, + _Inout_ frame_result_struct &frame_data) -> void { + if (frame_data.stat.compare(get_env_string("SOCCER_STAT_CHECK_PENALTY")) != + 0 || + digits_candidates.size() != 2 || time_candidates.size() != 1) { + return; + } + cv::Rect result_box_bound_rect; + + if (stat_second_half.compare(get_env_string("SOCCER_STAT_CHECK_PENALTY")) == + 0) { + w_ocr_engine::characters_struct left, right; + left = (digits_candidates[0][0].bound_rect.x > + digits_candidates[1][0].bound_rect.x) + ? digits_candidates[1][digits_candidates[1].size() - 1] + : digits_candidates[0][digits_candidates[0].size() - 1]; + right = (digits_candidates[0][0].bound_rect.x > + digits_candidates[1][0].bound_rect.x) + ? digits_candidates[0][0] + : digits_candidates[1][0]; + + result_box_bound_rect.x = left.bound_rect.x + left.bound_rect.width; + result_box_bound_rect.width = right.bound_rect.x - result_box_bound_rect.x; + + if (result_box_bound_rect.width < 0) { + std::cout << "something went wrong!!!" << std::endl; + return; + } + + if (result_box_bound_rect.width > frame.cols / 3) { + return; + } + + result_box_bound_rect.y = + left.bound_rect.y + left.bound_rect.height / 2 + 4; + result_box_bound_rect.height = left.bound_rect.height / 2; + } else if (stat_penalty.compare( + get_env_string("SOCCER_STAT_CHECK_PENALTY")) == 0) { + result_box_bound_rect.x = time_candidates[0][0].bound_rect.x - 50; + result_box_bound_rect.width = 50 + 10; + // (time_candidates[0][time_candidates[0].size() - 1].bound_rect.x + + // time_candidates[0][time_candidates[0].size() - 1].bound_rect.width - + // result_box_bound_rect.x) / + // 2; + result_box_bound_rect.y = time_candidates[0][0].bound_rect.y; + result_box_bound_rect.height = time_candidates[0][0].bound_rect.height; + } + + cv::Mat result_box; + frame(result_box_bound_rect).copyTo(result_box); + + // cv::imshow("image", result_box); + // cv::waitKey(); + + std::vector temp_words; + + temp_words = + ocr_object.image_to_string(result_box, result_away.config_for_ocr); + + if (temp_words.size() == 2 && stat_second_half.compare(get_env_string( + "SOCCER_STAT_CHECK_PENALTY")) == 0) { + frame_data.home_penalty_result = + (temp_words[0].center.x < temp_words[1].center.x) ? temp_words[0] + : temp_words[1]; + frame_data.away_penalty_result = + (temp_words[0].center.x < temp_words[1].center.x) ? temp_words[1] + : temp_words[0]; + } else if (temp_words.size() == 1 && stat_penalty.compare(get_env_string( + "SOCCER_STAT_CHECK_PENALTY")) == 0) { + std::vector temp_result = + split_string(temp_words[0].text, ' '); + frame_data.home_penalty_result.text = temp_result[0]; + frame_data.away_penalty_result.text = temp_result[1]; + } + + return; +} + +auto w_soccer::single_image_result_extraction(_In_ uint8_t *pRawImage, + _In_ int height, _In_ int width, + _In_ ocr_callback *callback) + -> int { + int desired_height = get_env_int("SOCCER_GLOBAL_FRAME_HEIGHT"); + int desired_width = get_env_int("SOCCER_GLOBAL_FRAME_WIDTH"); + if (!pRawImage || height != desired_height || width != desired_width) { + return 1; + } + cv::Mat original_image = cv::Mat(height, width, CV_8UC3, pRawImage); + + frame_result_struct temp_frame_data; + + if (get_env_boolean("SOCCER_GLOBAL_PLATFORM_FREE")) { + extract_result_based_on_clusters_symmetricity(original_image, + temp_frame_data); + } else { + extract_result_from_frame_boxes(original_image, temp_frame_data); + } + + if (temp_frame_data.stat.compare("") != 0 && + temp_frame_data.home_result.text.compare("") != 0 && + temp_frame_data.away_result.text.compare("") != 0 && + temp_frame_data.home_name.text.compare("") != 0 && + temp_frame_data.away_name.text.compare("") != 0) { + temp_frame_data.frame_number = frame_number; + cv::Mat temp_image = original_image.clone(); + update_match_data(temp_frame_data, temp_image); + temp_image.release(); + } + + // update the frame number + frame_number += 1; + for (int i = 0; i < matches_data.size(); i++) { + if (i == 0 && frame_number < matches_data[i].frame_number + 10) { + continue; + } + matches_data[i].ready = true; + } + + extract_game_results(); + + if (callback) { + for (int i = 0; i < matches_data.size(); i++) { + if (matches_data[i].extracted && !matches_data[i].applied) { + std::string str_result = matches_data[i].stat + "," + + matches_data[i].home_name.text + "," + + matches_data[i].home_result.text + "," + + matches_data[i].away_name.text + "," + + matches_data[i].away_result.text; + if (matches_data[i].home_penalty_result.text.compare("") != 0 && + matches_data[i].away_penalty_result.text.compare("") != 0) { + str_result = str_result + "," + + matches_data[i].home_penalty_result.text + "," + + matches_data[i].away_penalty_result.text; + } + char *char_result = new char[256]; + strcpy(char_result, str_result.c_str()); + + callback(char_result, 256, matches_data[i].result_image.data, + matches_data[i].result_image.cols, + matches_data[i].result_image.rows); + the_last_result_message = str_result; + + matches_data[i].applied = true; + } + } + } + + return 0; +} + +auto w_soccer::extract_all_image_char_clusters( + cv::Mat &image, + std::vector> + &digits_candidates, + std::vector> &words_candidates, + std::vector> &time_candidates) + -> void { + cv::cvtColor(image, image, cv::COLOR_BGR2GRAY); + int global_threshold = get_env_int("SOCCER_GLOBAL_THRESHOLD"); + cv::threshold(image, image, global_threshold, 255, + cv::THRESH_BINARY); // cv::THRESH_OTSU); + + std::vector> contours; + std::vector hierarchy; + + cv::findContours(image, contours, hierarchy, cv::RETR_TREE, + cv::CHAIN_APPROX_SIMPLE, cv::Point(0, 0)); + std::vector modified_bounding_rects = + ocr_object.contours_to_char_structs(contours); + + modified_bounding_rects = ocr_object.filter_chars_by_contour_size( + modified_bounding_rects, platform_free); + contours.clear(); + hierarchy.clear(); + + ocr_object.merge_overlapped_contours(modified_bounding_rects, platform_free); + + std::vector> + clustered_characters = ocr_object.cluster_char_structs( + modified_bounding_rects, platform_free); + + int ref_index = clustered_characters.size(); + for (int i = 0; i < ref_index; i++) { + int index = ref_index - (i + 1); + if (clustered_characters[index].size() < 3 && + ocr_object.same_level(clustered_characters[index])) { + digits_candidates.push_back(clustered_characters[index]); + } + if (clustered_characters[index].size() >= 3 && + ocr_object.same_level(clustered_characters[index])) { + words_candidates.push_back(clustered_characters[index]); + } + if (clustered_characters[index][0].bound_rect.x < image.cols / 2 && + clustered_characters[index][clustered_characters[index].size() - 1] + .bound_rect.x > image.cols / 2 && + clustered_characters[index].size() < 16 && + clustered_characters[index].size() > 1) { + time_candidates.push_back(clustered_characters[index]); + } + } + // cv::Mat temp_image = cv::Mat(image.rows, image.cols, CV_8UC1, + // cv::Scalar(0)); for (int i = 0; i < clustered_characters.size(); i++) + // { + // ocr_object.show_contours(temp_image, clustered_characters[i], "Hello", + // true); + // } + + clustered_characters.clear(); + + ocr_object.keep_twins(words_candidates, image.cols, image.rows, true); + ocr_object.keep_twins(digits_candidates, image.cols, image.rows, false); + ocr_object.keep_time(time_candidates); + + for (int i = 0; i < digits_candidates.size(); i++) { + for (size_t j = 0; j < digits_candidates[i].size(); j++) { + ocr_object.margin_bounding_rect(digits_candidates[i][j].bound_rect, + platform_free.margin, image); + } + } + + for (int i = 0; i < words_candidates.size(); i++) { + for (size_t j = 0; j < words_candidates[i].size(); j++) { + ocr_object.margin_bounding_rect(words_candidates[i][j].bound_rect, + platform_free.margin, image); + } + } + + for (int i = 0; i < time_candidates.size(); i++) { + for (size_t j = 0; j < time_candidates[i].size(); j++) { + ocr_object.margin_bounding_rect(time_candidates[i][j].bound_rect, + platform_free.margin, image); + } + } + + return; +} + +auto w_soccer::replace_team_names_with_most_similar_string( + _Inout_ std::vector &result) -> void { + for (int i = 0; i < int(result.size()); i++) { + // LOG_P(w_log_type::W_LOG_INFO, "recognized home name : %s", + // result[i].home_name.text); LOG_P(w_log_type::W_LOG_INFO, "recognized away + // name : %s", result[i].away_name.text); + std::string path = get_env_string("SIMILAR_STRINGS_FILE_PATH"); + result[i].home_name.text = + get_nearest_string(result[i].home_name.text, path); + result[i].away_name.text = + get_nearest_string(result[i].away_name.text, path); + // LOG_P(w_log_type::W_LOG_INFO, "recognized home name after replace with + // most similar: %s", result[i].home_name.text); + // LOG_P(w_log_type::W_LOG_INFO, "recognized away name after replace with + // most similar: %s", result[i].away_name.text); + } +} + +auto w_soccer::initial_match_result_struct( + w_referee::frame_result_struct frame_data, cv::Mat &image) + -> w_referee::match_result_struct { + w_referee::match_result_struct temp_match_data; + temp_match_data.all_frames_results.push_back(frame_data); + temp_match_data.stat = frame_data.stat; + temp_match_data.result_image = image; + temp_match_data.frame_number = frame_data.frame_number; + + return temp_match_data; +} + +auto w_soccer::update_match_data(_In_ w_referee::frame_result_struct frame_data, + _In_ cv::Mat &image) -> void { + if (matches_data.size() == 0 || + frame_data.frame_number > + matches_data[matches_data.size() - 1].frame_number + 10) { + matches_data.push_back(initial_match_result_struct(frame_data, image)); + } else { + matches_data[matches_data.size() - 1].frame_number = + frame_data.frame_number; + matches_data[matches_data.size() - 1].all_frames_results.push_back( + frame_data); + } +} + +auto w_soccer::extract_game_results() -> void { + w_referee ocr_object; + + for (int i = 0; i < matches_data.size(); i++) { + int min_frames = get_env_int("SOCCER_GLOBAL_MIN_FRAMES"); + if (!matches_data[i].ready || matches_data[i].extracted || + matches_data[i].all_frames_results.size() < min_frames) { + continue; + } + w_referee::frame_result_struct temp_frame_result; + ocr_object.voting_over_results_and_names( + temp_frame_result, matches_data[i].all_frames_results); + + if (get_env_boolean("SIMILARITY_USE_FOR_TEAM_NAMES")) { + std::string path = get_env_string("SIMILAR_STRINGS_FILE_PATH"); + temp_frame_result.away_name.text = + get_nearest_string(temp_frame_result.away_name.text, path); + temp_frame_result.home_name.text = + get_nearest_string(temp_frame_result.home_name.text, path); + } + + matches_data[i].extracted = true; + matches_data[i].away_name = temp_frame_result.away_name; + matches_data[i].away_result = temp_frame_result.away_result; + matches_data[i].home_name = temp_frame_result.home_name; + matches_data[i].home_result = temp_frame_result.home_result; + matches_data[i].home_penalty_result = temp_frame_result.home_penalty_result; + matches_data[i].away_penalty_result = temp_frame_result.away_penalty_result; + } +} + +auto w_soccer::get_matches_data() + -> std::vector { + return matches_data; +} + +auto w_soccer::get_stat_map() -> std::map { + return stat_map; +} diff --git a/wolf/ml/referee_ocr/w_soccer.hpp b/wolf/ml/referee_ocr/w_soccer.hpp new file mode 100644 index 000000000..f730f236d --- /dev/null +++ b/wolf/ml/referee_ocr/w_soccer.hpp @@ -0,0 +1,225 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#pragma once + +#include +#include +#include + +#include "salieri.h" +#include "w_image_processor.hpp" +#include "w_ocr_engine.hpp" +#include "w_referee.hpp" +#include "wolf.hpp" + +typedef void ocr_callback(char *result_buffer, int result_buffer_size, + uint8_t *image_buffer, int image_width, + int image_height); + +namespace wolf::ml::ocr { + +//! w_soccer class. +/*! \brief It is extract the soccer game result. + + This class contains functions, structures, and variables that use the + w_soccer class for doing referee purposes. +*/ +class w_soccer : public w_referee { +public: + /*! + The constructor of the class. + In the constructor the configs are set. + + \return + */ + W_API w_soccer(); + + /*! + The deconstructor of the class. + + The function is empty. + */ + W_API ~w_soccer(); + + /*! + The function return the config of desired image window. + + \param pType The type of box + \return + */ + W_API static auto set_config(_In_ char *pType) -> w_ocr_engine::config_struct; + + /*! + The function set the stat map global variable by dotenvs information. + */ + auto fill_stat_map() -> void; + + W_API auto set_config_for_ocr(_In_ char *pType) -> config_for_ocr_struct; + + /*! + The function returns frame results, the results are extracted from + pre-defined frame boxes. + + \param frame The input frame image. + \param frame_data The frame result would be stored in the frame_date. + \return Void + */ + W_API auto extract_result_from_frame_boxes( + _In_ cv::Mat &frame, _Inout_ frame_result_struct &frame_data) -> void; + + /*! + The function returns frame results, the results are extracted based on + the character clusters' symmetricity. + + \param frame The input frame image. + \param frame_data The frame result would be stored in the frame_date. + \return Void + */ + W_API auto extract_result_based_on_clusters_symmetricity( + _In_ cv::Mat &frame, _Inout_ frame_result_struct &frame_data) -> void; + + /*! + The function checks for penalty results and if the scene contains the + penalty results the results would be extracted. + + \param frame The input frame image. + \param digits_candidates The cluster of digits characters. + \param words_candidates The cluster of words characters. + \param time_candidates The cluster of time stat related characters. + \param frame_data The frame result would be stored in the frame_date. + \return Void + */ + W_API auto extract_penalty_result_symmetricity( + _In_ cv::Mat &frame, + _In_ std::vector> + digits_candidates, + _In_ std::vector> + words_candidates, + _In_ std::vector> + time_candidates, + _Inout_ frame_result_struct &frame_data) -> void; + + /*! + The function returns the game results, if the image contains the game final + result. + + \param pRawImage The sequence of the input image pixels array in BGR + format. \param height The image height. \param width The image width. \param + pStr The image result. \return Void + */ + W_API auto single_image_result_extraction(_In_ uint8_t *pRawImage, + _In_ int height, _In_ int width, + _In_ ocr_callback *callback) -> int; + + /*! + The extract_all_image_char_clusters function returns the character cluster + related to the frame result. + + \param pImage The input image. + \param pDdigitsCandidates The character clusters of the result texts. + \param pWordsCandidates The character clusters of the team name texts. + \param pTimeCandidates The character cluster of the stat texts. + \return Void + */ + W_API auto extract_all_image_char_clusters( + cv::Mat &pImage, + std::vector> + &pDigitsCandidates, + std::vector> + &pWordsCandidates, + std::vector> + &pTimeCandidates) -> void; + + /*! + replace team names stored in match_result_struct using string similarity + algorithms. + + \param result input struct + \return + */ + W_API static auto replace_team_names_with_most_similar_string( + _Inout_ std::vector &result) -> void; + + /*! + The initial_match_result_struct function fills a match_result_struct with the + initial values. + + \param frame_data input struct + \param image input struct + \return An initialed variable of match data structure. + */ + W_API auto + initial_match_result_struct(w_referee::frame_result_struct frame_data, + cv::Mat &image) -> w_referee::match_result_struct; + + /*! + The update_match_data function store frames data in the match_date variable. + + \param frame_data input struct + \param image input struct + \return + */ + W_API auto update_match_data(_In_ w_referee::frame_result_struct frame_data, + _In_ cv::Mat &image) -> void; + + /*! + The extract_game_results function extracts the game results from the + match_data and stores the results in the match_data. + + \return + */ + W_API auto extract_game_results() -> void; + + /*! + The get_matches_data function returns the private match_data variable. + + \return The private match_data variable. + */ + W_API auto get_matches_data() -> std::vector; + + /*! + The get_stat_map function returns the private stat_map variable. + + \return The private stat_map variable. + */ + W_API auto get_stat_map() -> std::map; + +private: + /*! matches_data; + + /*! stat_map; + + std::string the_last_result_message = ""; + + /*! +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// #include "spdlog/sinks/basic_file_sink.h" +// #include "spdlog/sinks/stdout_color_sinks.h" +// #include "spdlog/spdlog.h" +namespace fs = std::filesystem; +#ifdef __TELEMETRY +#include "opentelemetry/sdk/version/version.h" +#include "opentelemetry/trace/provider.h" + +namespace trace = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; + +namespace { +nostd::shared_ptr get_tracer() { + auto provider = trace::Provider::GetTracerProvider(); + return provider->GetTracer("pes_21", OPENTELEMETRY_SDK_VERSION); +} +} // namespace +#endif + +namespace wolf::ml::ocr { + +auto get_nearest_string(_In_ std::string pInput, std::string pFilePath) + -> std::string { + // LOG_P(w_log_type::W_LOG_INFO, "path to similar strings file: %s", + // pFilePath); + std::ifstream similar_strings(pFilePath); + float threshold = get_env_float("SIMILARITY_THRESHOLD"); + // LOG_P(w_log_type::W_LOG_INFO, "similarity threshold: %f", threshold); + std::string candidate_string; + float best_similarity = 0; + std::string most_similar; + + if (pInput.length() == 0) { + return pInput; + } + + while (std::getline(similar_strings, candidate_string)) { + float similarity = + normalized_levenshtein_similarity(pInput, candidate_string); + if (similarity > best_similarity) { + most_similar = candidate_string; + best_similarity = similarity; + } + } + + if (best_similarity > threshold) { + return most_similar; + } else { + return pInput; + } +} + +auto get_nearest_string(_In_ std::string pInput, + _In_ std::map pMap) + -> std::string + +{ + float threshold = get_env_float("SIMILARITY_THRESHOLD_STAT"); + // LOG_P(w_log_type::W_LOG_INFO, "similarity threshold: %f", threshold); + std::string candidate_string; + float best_similarity = 0; + std::string most_similar; + + if (pInput.length() == 0) { + return pInput; + } + + for (auto it = pMap.begin(); it != pMap.end(); it++) { + candidate_string = it->first; + float similarity = + normalized_levenshtein_similarity(pInput, candidate_string); + if (similarity > best_similarity) { + most_similar = candidate_string; + best_similarity = similarity; + } + } + + if (best_similarity > threshold) { + return most_similar; + } else { + return ""; + } +} + +std::string get_value_from_json_file_by_key(std::string pJsonFilePath, + std::string pKey) { +#ifdef __TELEMETRY + auto span = get_tracer()->StartSpan("get_value_from_json_file_by_key"); +#endif + using namespace rapidjson; + + std::ifstream ifs{pJsonFilePath}; + if (!ifs.is_open()) { + fs::path cwd = fs::current_path(); + // spdlog::error("current path {}", cwd.string()); + // spdlog::error("Could not open {} file for reading!", pJsonFilePath); + std::exit(ENOENT); + } + + IStreamWrapper isw{ifs}; + + Document doc{}; + doc.ParseStream(isw); + std::string out = doc[pKey.c_str()].GetString(); + + return out; +} + +std::vector +line_of_numbers_in_string_to_vector_of_integers(std::string pVariable) { +#ifdef __TELEMETRY + auto span = get_tracer()->StartSpan( + "line_of_numbers_in_string_to_vector_of_integers"); +#endif + std::vector result; + + std::vector temp = split_string(pVariable, ','); + for (int i = 0; i < temp.size(); i++) { + result.push_back(std::stoi(temp[i])); + } + + return result; +} + +auto normalized_levenshtein_similarity(_In_ const std::string &s1, + _In_ const std::string &s2) -> float { + const size_t m = s1.size(); + const size_t n = s2.size(); + int distance; + if (m == 0 && n == 0) + return 0; + else if (m == 0) + distance = n; + else if (n == 0) + distance = m; + else { + std::vector costs(n + 1); + std::iota(costs.begin(), costs.end(), 0); + size_t i = 0; + for (auto c1 : s1) { + costs[0] = i + 1; + size_t corner = i; + size_t j = 0; + for (auto c2 : s2) { + size_t upper = costs[j + 1]; + costs[j + 1] = (c1 == c2) + ? corner + : 1 + std::min(std::min(upper, corner), costs[j]); + corner = upper; + ++j; + } + ++i; + } + distance = costs[n]; + } + float max = std::max(s1.length(), s2.length()); + float normalized_distance = distance / max; + return 1 - normalized_distance; +} + +auto read_text_file_line_by_line(_In_ std::string pFilePath) + -> std::vector { +#ifdef __TELEMETRY + auto span = + trace::Scope(get_tracer()->StartSpan("read_text_file_line_by_line")); +#endif + std::vector lines; + std::string line; + + std::ifstream input_file(pFilePath); + if (!input_file.is_open()) { + std::cerr << "Could not open the file - '" << pFilePath << "'" << std::endl; + return lines; + } + + while (std::getline(input_file, line)) { + lines.push_back(line); + } + + input_file.close(); + return lines; +} + +bool replace_string(std::string &str, const std::string &from, + const std::string &to) { + size_t start_pos = str.find(from); + if (start_pos == std::string::npos) + return false; + str.replace(start_pos, from.length(), to); + return true; +} + +std::vector split_string(std::string input_string, + char reference) { +#ifdef __TELEMETRY + auto scoped_span = trace::Scope(get_tracer()->StartSpan("split_string")); + // auto scoped_span = get_tracer()->StartSpan("split_string"); +#endif + std::stringstream test(input_string); + std::string segment; + std::vector seglist; + + while (std::getline(test, segment, reference)) { + seglist.push_back(segment); + } + + return seglist; +} + +bool string_2_boolean(std::string pVariable) { +#ifdef __TELEMETRY + auto span = get_tracer()->StartSpan("string_2_boolean"); +#endif + bool result; + std::transform(pVariable.begin(), pVariable.end(), pVariable.begin(), + ::tolower); + + if (pVariable.compare("true") == 0 || pVariable.compare("false") == 0) { + std::istringstream is(pVariable); + is >> std::boolalpha >> result; + } else { + throw std::runtime_error( + "Invalid input, the input must be 'true' or 'false' not " + pVariable); + } + + return result; +} + +auto store_image_in_folder( + _In_ std::vector &pVideoResult, + _In_ std::string pOutputImageFolderPath, _In_ std::string pVideoPath) + -> void { +#ifdef __TELEMETRY + auto span = get_tracer()->StartSpan("store_image_in_folder"); +#endif + fs::path temp_video_path = pVideoPath; + std::string temp_name = temp_video_path.filename().string(); + std::string video_name = split_string(temp_name, '.')[0]; + + for (size_t i = 0; i < pVideoResult.size(); i++) { + fs::path out_path = pOutputImageFolderPath + "/" + video_name + "_" + + std::to_string(i) + ".png"; + cv::imwrite(out_path.string().c_str(), pVideoResult[i].result_image); + cv::waitKey(300); + pVideoResult[i].release(); + } + + return; +} + +void write_in_file_append(std::string file_path, std::string content) { +#ifdef __TELEMETRY + auto scoped_span = + trace::Scope(get_tracer()->StartSpan("write_in_file_append")); + // auto scoped_span = get_tracer()->StartSpan("write_in_file_append"); +#endif + + std::ofstream file; + + file.open(file_path, std::ios_base::app); // append instead of overwrite + file << content << std::endl; + + file.close(); + return; +} + +void write_in_file(std::string file_path, std::string content) { +#ifdef __TELEMETRY + auto scoped_span = + trace::Scope(get_tracer()->StartSpan("write_in_file_append")); + // auto scoped_span = get_tracer()->StartSpan("write_in_file_append"); +#endif + + std::ofstream file; + + file.open(file_path); // overwrite + file << content << std::endl; + + file.close(); + return; +} + +auto write_results_in_file( + _In_ std::vector &pVideoResult, + _In_ std::string pOutputTextPath) -> void { +#ifdef __TELEMETRY + auto span = trace::Scope(get_tracer()->StartSpan("write_results_in_file")); +#endif + for (size_t i = 0; i < pVideoResult.size(); i++) { + if (pVideoResult[i].home_penalty_result.text.compare("") != 0 && + pVideoResult[i].away_penalty_result.text.compare("") != 0) { + write_in_file_append(pOutputTextPath, + pVideoResult[i].stat + "," + + pVideoResult[i].home_name.text + "," + + pVideoResult[i].home_result.text + "," + + pVideoResult[i].away_name.text + "," + + pVideoResult[i].away_result.text + "," + + pVideoResult[i].home_penalty_result.text + "," + + pVideoResult[i].away_penalty_result.text + "," + + std::to_string(pVideoResult[i].frame_number)); + } else { + write_in_file_append(pOutputTextPath, + pVideoResult[i].stat + "," + + pVideoResult[i].home_name.text + "," + + pVideoResult[i].home_result.text + "," + + pVideoResult[i].away_name.text + "," + + pVideoResult[i].away_result.text + "," + + std::to_string(pVideoResult[i].frame_number)); + } + } + + return; +} + +auto is_line_contains_variable(const std::string pStr) -> bool { +#ifdef __TELEMETRY + auto span = + trace::Scope(get_tracer()->StartSpan("is_line_contains_variable")); +#endif + bool decision = false; + if (pStr.size() > 0) { + if (pStr.at(0) != '#' && pStr.size() > 2) { + decision = true; + } + } + return decision; +} + +auto set_env(_In_ const char *pDotEnvFilePath) -> void { +#ifdef __TELEMETRY + auto span = trace::Scope(get_tracer()->StartSpan("set_env")); +#endif + std::string env_file_path(pDotEnvFilePath); + auto lines = read_text_file_line_by_line(env_file_path); + + std::vector> env_vector; + for (int i = 0; i < lines.size(); i++) { + if (is_line_contains_variable(lines[i])) { + env_vector.push_back(split_string(lines[i], '=')); + } + } + + for (int i = 0; i < env_vector.size(); i++) { +#ifdef _WIN32 + _putenv_s(env_vector[i][0].c_str(), env_vector[i][1].c_str()); +#else + setenv(env_vector[i][0].c_str(), env_vector[i][1].c_str(), 1); +#endif + } +} + +auto get_env_int(_In_ const char *pKey) -> int { + int value = -1; + if (const char *env_p = getenv(pKey)) { + std::string temp(env_p); + value = std::stoi(temp); + } else { + // TODO add log + } + + return value; +} + +auto get_env_float(_In_ const char *pKey) -> float { + float value = -1; + if (const char *env_p = getenv(pKey)) { + std::string temp(env_p); + value = std::stof(temp); + } else { + // TODO add log + } + + return value; +} + +auto get_env_boolean(_In_ const char *pKey) -> bool { + bool value = false; + if (const char *env_p = getenv(pKey)) { + std::string temp(env_p); + value = string_2_boolean(temp); + } else { + // TODO add log + } + + return value; +} + +auto get_env_string(_In_ const char *pKey) -> std::string { + std::string value; + if (const char *env_p = getenv(pKey)) { + value = std::string(env_p); + } else { + // TODO add log + } + + return value; +} + +auto get_env_cv_rect(_In_ const char *pKey) -> cv::Rect { + cv::Rect value = cv::Rect(0, 0, 0, 0); + if (const char *env_p = getenv(pKey)) { + std::string temp(env_p); + std::vector int_vect = + line_of_numbers_in_string_to_vector_of_integers(temp); + value = cv::Rect(int_vect[0], int_vect[1], int_vect[2], int_vect[3]); + } else { + // TODO add log + } + + return value; +} + +auto get_relative_path_to_root() -> std::string { + fs::path cwd = fs::current_path(); + fs::path dot_env_file_path; + if (cwd.parent_path().filename().compare("build") == 0) { + dot_env_file_path = "../../"; + } else if (cwd.filename().compare("build") == 0 || + cwd.filename().compare("ocr") == 0) { + dot_env_file_path = "../"; + } else if (cwd.parent_path().parent_path().filename().compare("build") == 0) { + dot_env_file_path = "../../../"; + } else { + dot_env_file_path = ""; + } + + std::string temp = dot_env_file_path.string(); + + return temp; +} + +auto get_first_character_of_string(_In_ std::string pStr, _In_ bool pEscape) + -> std::string { + if (pEscape || pStr.length() == 0) { + return pStr; + } + + char first_char = pStr[0]; + std::string result(1, first_char); + + return result; +} +} // namespace wolf::ml::ocr diff --git a/wolf/ml/referee_ocr/w_utilities.hpp b/wolf/ml/referee_ocr/w_utilities.hpp new file mode 100644 index 000000000..a0dd8e597 --- /dev/null +++ b/wolf/ml/referee_ocr/w_utilities.hpp @@ -0,0 +1,226 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "w_referee.hpp" +#include "wolf.hpp" + +namespace wolf::ml::ocr { + +/*! + The get_nearest_string returns the nearest string to input among strings + stored to the file specified by pFilePath environment variable. when the most + similar string is found, its similarity is compared to SIMILARITY_THRESHOLD + environment variable and if greater than it, the similar string is returned. + otherwise, the input string is returned. + + \param pInput The input string. + \param pFilePath The file path contains target strings. + \return The most similar string to input string. +*/ +W_API auto get_nearest_string(_In_ std::string pInput, + _In_ std::string pFilePath) -> std::string; + +/*! + return the nearest string to input among strings stored in the input + pMap. when the most similar string is found, its similarity is compared to + SIMILARITY_THRESHOLD environment variable and if greater than it, the similar + string is returned. otherwise, the input string is returned. + + \param pInput The input string. + \param pMap A map contains the target strings. + \return most similar string to input string. +*/ +W_API auto get_nearest_string(_In_ std::string pInput, + _In_ std::map pMap) + -> std::string; + +/*! + The function gets the specific value by it's key and return the value in + string format. + + \param pJsonFilePath the path of json file. + \param pKey the corresponding key that related to the desired value. + \return desired value. +*/ +W_API std::string get_value_from_json_file_by_key(std::string pJsonFilePath, + std::string pKey); + +/*! + The function gets a string as input, the string contains numbers of int + numbers separated by spaces and split them and return them in a vector of + integers. + + \param pVariable string of integers number. + \return a vector of integers. +*/ +W_API std::vector +line_of_numbers_in_string_to_vector_of_integers(std::string pVariable); + +/*! + compute the normalized similarity between input strings using the + Levenshtein metric. output is a number between 0 and 1. 1 shows high + similarity and 0 shows low similarity. + + \param s1 first string. + \param s2 second string. + \return normalized similarity metric. +*/ +W_API auto normalized_levenshtein_similarity(_In_ const std::string &s1, + _In_ const std::string &s2) + -> float; + +/*! + replace the specified phrase with another specified phrase in string. + + \param str input string. + \param from first phrase. + \param to second phrase. + \return boolean parameter show success or failure of function +*/ +W_API bool replace_string(std::string &str, const std::string &from, + const std::string &to); + +/*! + The function gets a string as input and return the boolean + representation of the input. + + \param pVariable the string input. + \return the boolean representain of the input string. +*/ +W_API bool string_2_boolean(std::string pVariable); + +/*! + The function gets a string as input, the string contains numbers of int + numbers separated by spaces and split them and return them in a vector of + integers. + + \param pVideoResult The result of the video. + \param pOutputImageFolderPath The path for the output image folder. + \param pVideoPath The path of the game video. + \return +*/ +W_API auto store_image_in_folder( + _In_ std::vector &pVideoResult, + _In_ std::string pOutputImageFolderPath, _In_ std::string pVideoPath) + -> void; + +W_API void write_in_file_append(std::string file_path, std::string content); + +W_API void write_in_file(std::string file_path, std::string content); +W_API std::vector split_string(std::string input_string, + char reference); + +/*! + The function stores the video output text result in the pOutputTextPath + file path. + + \param pVideoResult The result of the video. + \param pOutputTextPath The path for the output text file. + \return +*/ +W_API auto write_results_in_file( + _In_ std::vector &pVideoResult, + _In_ std::string pOutputTextPath) -> void; + +/*! + The function reads all lines of the input file and returns them in a + string vector. + + \param pFilePath The path of the input file. + \return a vector of strings. +*/ +W_API auto read_text_file_line_by_line(_In_ std::string pFilePath) + -> std::vector; + +/*! + The .env file may have empty or commented lines. The + is_line_contains_variable functions use to detect these lines. + + \param pStr The input string. + \return False, if the input string contains # or is empty. +*/ +W_API auto is_line_contains_variable(const std::string pStr) -> bool; + +/*! + The function reads environment variables from the .env file and set them + in the environment by using the putenv function. + + \param pDotEnvFilePath The path of the .env file. + \return +*/ +W_API auto set_env(_In_ const char *pDotEnvFilePath) -> void; + +/*! + The function return the value of an environment variable based on the + input key. + + \param pKey The path of the .env file. + \return the value of the variable in int. +*/ +W_API auto get_env_int(_In_ const char *pKey) -> int; + +/*! + The function return the value of an environment variable based on the + input key. + + \param pKey The path of the .env file. + \return the value of the variable in float. +*/ +W_API auto get_env_float(_In_ const char *pKey) -> float; + +/*! + The function return the value of an environment variable based on the + input key. + + \param pKey The path of the .env file. + \return the value of the variable in boolean. +*/ +W_API auto get_env_boolean(_In_ const char *pKey) -> bool; + +/*! + The function return the value of an environment variable based on the + input key. + + \param pKey The path of the .env file. + \return the value of the variable in string. +*/ +W_API auto get_env_string(_In_ const char *pKey) -> std::string; + +/*! + The function return the value of an environment variable based on the + input key. + + \param pKey The path of the .env file. + \return The value of the variable in cv::Rect. +*/ +W_API auto get_env_cv_rect(_In_ const char *pKey) -> cv::Rect; + +/*! + The function returns the related root path compared to the current path. + + \return The related root path compared to the current path. +*/ +W_API auto get_relative_path_to_root() -> std::string; + +/*! + The get_first_character_of_string function returns the first character + of the string. + + \param pStr The input string. + \param pScape if true then return input string without any change. + \return The first character of input string. +*/ +W_API auto get_first_character_of_string(_In_ std::string pStr, + _In_ bool pEscape) -> std::string; +} // namespace wolf::ml::ocr diff --git a/wolf/ml/test/common_test_asset/image_processor/find_all_contours_solid_black.png b/wolf/ml/test/common_test_asset/image_processor/find_all_contours_solid_black.png new file mode 100644 index 000000000..5f77ef3cc Binary files /dev/null and b/wolf/ml/test/common_test_asset/image_processor/find_all_contours_solid_black.png differ diff --git a/wolf/ml/test/common_test_asset/image_processor/gaussian_filter_7.png b/wolf/ml/test/common_test_asset/image_processor/gaussian_filter_7.png new file mode 100644 index 000000000..d9bd867c6 Binary files /dev/null and b/wolf/ml/test/common_test_asset/image_processor/gaussian_filter_7.png differ diff --git a/wolf/ml/test/common_test_asset/image_processor/gaussian_filter_default.png b/wolf/ml/test/common_test_asset/image_processor/gaussian_filter_default.png new file mode 100644 index 000000000..5bc2a690a Binary files /dev/null and b/wolf/ml/test/common_test_asset/image_processor/gaussian_filter_default.png differ diff --git a/wolf/ml/test/common_test_asset/image_processor/make_white_background_50.png b/wolf/ml/test/common_test_asset/image_processor/make_white_background_50.png new file mode 100644 index 000000000..46ab38573 Binary files /dev/null and b/wolf/ml/test/common_test_asset/image_processor/make_white_background_50.png differ diff --git a/wolf/ml/test/common_test_asset/image_processor/make_white_background_default.png b/wolf/ml/test/common_test_asset/image_processor/make_white_background_default.png new file mode 100644 index 000000000..a2816e612 Binary files /dev/null and b/wolf/ml/test/common_test_asset/image_processor/make_white_background_default.png differ diff --git a/wolf/ml/test/common_test_asset/image_processor/negative_image_default.png b/wolf/ml/test/common_test_asset/image_processor/negative_image_default.png new file mode 100644 index 000000000..5aa8237dc Binary files /dev/null and b/wolf/ml/test/common_test_asset/image_processor/negative_image_default.png differ diff --git a/wolf/ml/test/common_test_asset/image_processor/prepare_image_for_contour_detection_default.png b/wolf/ml/test/common_test_asset/image_processor/prepare_image_for_contour_detection_default.png new file mode 100644 index 000000000..0d122c9b9 Binary files /dev/null and b/wolf/ml/test/common_test_asset/image_processor/prepare_image_for_contour_detection_default.png differ diff --git a/wolf/ml/test/common_test_asset/image_processor/test_image.png b/wolf/ml/test/common_test_asset/image_processor/test_image.png new file mode 100644 index 000000000..52a862d4c Binary files /dev/null and b/wolf/ml/test/common_test_asset/image_processor/test_image.png differ diff --git a/wolf/ml/test/common_test_asset/image_processor/threshold_filter_180.png b/wolf/ml/test/common_test_asset/image_processor/threshold_filter_180.png new file mode 100644 index 000000000..a89aad411 Binary files /dev/null and b/wolf/ml/test/common_test_asset/image_processor/threshold_filter_180.png differ diff --git a/wolf/ml/test/common_test_asset/image_processor/threshold_filter_default.png b/wolf/ml/test/common_test_asset/image_processor/threshold_filter_default.png new file mode 100644 index 000000000..af9c2823d Binary files /dev/null and b/wolf/ml/test/common_test_asset/image_processor/threshold_filter_default.png differ diff --git a/wolf/ml/test/common_test_asset/nudity_detection/.check_stream_to_avoid_nsfw_context b/wolf/ml/test/common_test_asset/nudity_detection/.check_stream_to_avoid_nsfw_context new file mode 100644 index 000000000..0c96dc53f --- /dev/null +++ b/wolf/ml/test/common_test_asset/nudity_detection/.check_stream_to_avoid_nsfw_context @@ -0,0 +1,6 @@ +NUDITY_DETECTION_MODEL_PATH=C:/src/models/pytorch/ResNet50_nsfw_model.pt +TRAINED_MODEL_IMAGE_HEIGHT=224 +TRAINED_MODEL_IMAGE_WIDTH=224 +TEMP_IMAGE_HEIGHT=224 +TEMP_IMAGE_WIDTH=224 +STREAM_SERVER_URL=srt://20.216.45.101:554?mode=caller&transtype=live&timeout=5000000 \ No newline at end of file diff --git a/wolf/ml/test/common_test_asset/ocr_engine/contours_to_char_structs_Solid_black.png b/wolf/ml/test/common_test_asset/ocr_engine/contours_to_char_structs_Solid_black.png new file mode 100644 index 000000000..5f77ef3cc Binary files /dev/null and b/wolf/ml/test/common_test_asset/ocr_engine/contours_to_char_structs_Solid_black.png differ diff --git a/wolf/ml/test/common_test_asset/ocr_engine/contours_to_char_structs_default.png b/wolf/ml/test/common_test_asset/ocr_engine/contours_to_char_structs_default.png new file mode 100644 index 000000000..0d122c9b9 Binary files /dev/null and b/wolf/ml/test/common_test_asset/ocr_engine/contours_to_char_structs_default.png differ diff --git a/wolf/ml/test/common_test_asset/ocr_engine/enhance_contour_image_for_model_make_white.png b/wolf/ml/test/common_test_asset/ocr_engine/enhance_contour_image_for_model_make_white.png new file mode 100644 index 000000000..a2816e612 Binary files /dev/null and b/wolf/ml/test/common_test_asset/ocr_engine/enhance_contour_image_for_model_make_white.png differ diff --git a/wolf/ml/test/common_test_asset/ocr_engine/enhance_contour_image_for_model_resize.png b/wolf/ml/test/common_test_asset/ocr_engine/enhance_contour_image_for_model_resize.png new file mode 100644 index 000000000..3d5533079 Binary files /dev/null and b/wolf/ml/test/common_test_asset/ocr_engine/enhance_contour_image_for_model_resize.png differ diff --git a/wolf/ml/test/common_test_asset/ocr_engine/filter_chars_by_contour_size copy 2.png b/wolf/ml/test/common_test_asset/ocr_engine/filter_chars_by_contour_size copy 2.png new file mode 100644 index 000000000..80ee8a982 Binary files /dev/null and b/wolf/ml/test/common_test_asset/ocr_engine/filter_chars_by_contour_size copy 2.png differ diff --git a/wolf/ml/test/common_test_asset/ocr_engine/filter_chars_by_contour_size copy.png b/wolf/ml/test/common_test_asset/ocr_engine/filter_chars_by_contour_size copy.png new file mode 100644 index 000000000..80ee8a982 Binary files /dev/null and b/wolf/ml/test/common_test_asset/ocr_engine/filter_chars_by_contour_size copy.png differ diff --git a/wolf/ml/test/common_test_asset/ocr_engine/filter_chars_by_contour_size.png b/wolf/ml/test/common_test_asset/ocr_engine/filter_chars_by_contour_size.png new file mode 100644 index 000000000..80ee8a982 Binary files /dev/null and b/wolf/ml/test/common_test_asset/ocr_engine/filter_chars_by_contour_size.png differ diff --git a/wolf/ml/test/common_test_asset/ocr_engine/mask_contour.png b/wolf/ml/test/common_test_asset/ocr_engine/mask_contour.png new file mode 100644 index 000000000..2f0b23a19 Binary files /dev/null and b/wolf/ml/test/common_test_asset/ocr_engine/mask_contour.png differ diff --git a/wolf/ml/test/common_test_asset/ocr_engine/test_image.png b/wolf/ml/test/common_test_asset/ocr_engine/test_image.png new file mode 100644 index 000000000..52a862d4c Binary files /dev/null and b/wolf/ml/test/common_test_asset/ocr_engine/test_image.png differ diff --git a/wolf/ml/test/common_test_asset/soccer/.fill_stat_map b/wolf/ml/test/common_test_asset/soccer/.fill_stat_map new file mode 100644 index 000000000..0f0c5aaef --- /dev/null +++ b/wolf/ml/test/common_test_asset/soccer/.fill_stat_map @@ -0,0 +1,109 @@ + +CONFIG_SIMILARITY_ALGORITHM=NormalizedLevenshtein +CONFIG_USE_SIMILARITY=True +SIMILAR_STRINGS_FILE_PATH=asset/team_names.txt +SIMILARITY_THRESHOLD=0.5 +SOCCER_GLOBAL_HEIGHT_TO_DIST_RATIO=0.4 +SOCCER_GLOBAL_PLATFORM_FREE=TRUE +SOCCER_NAME_AWAY_DO_RESIZE_CONTOUR=FALSE +SOCCER_NAME_AWAY_GAUSSIAN_BLUR_WIN_SIZE=5 +SOCCER_NAME_AWAY_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_NAME_AWAY_IS_DIGIT=FALSE +SOCCER_NAME_AWAY_IS_TIME=FALSE +SOCCER_NAME_AWAY_IS_WHITE=TRUE +SOCCER_NAME_AWAY_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_NAME_AWAY_MARGIN=5 +SOCCER_NAME_AWAY_RESTRICTIONS_MAX_AREA=1000 +SOCCER_NAME_AWAY_RESTRICTIONS_MAX_HEIGHT=100 +SOCCER_NAME_AWAY_RESTRICTIONS_MAX_WIDTH=100 +SOCCER_NAME_AWAY_RESTRICTIONS_MIN_AREA=1 +SOCCER_NAME_AWAY_RESTRICTIONS_MIN_HEIGHT=20 +SOCCER_NAME_AWAY_RESTRICTIONS_MIN_WIDTH=1 +SOCCER_NAME_AWAY_THRESHOLD=150 +SOCCER_NAME_AWAY_VERBOSE=FALSE +SOCCER_NAME_AWAY_WHITE_BACKGROUND_THRESHOLD=30 +SOCCER_NAME_AWAY_WINDOW_NAME=name_away +SOCCER_NAME_AWAY_WINDOW=740,103,337,42 +SOCCER_NAME_HOME_DO_RESIZE_CONTOUR=TRUE +SOCCER_NAME_HOME_GAUSSIAN_BLUR_WIN_SIZE=3 +SOCCER_NAME_HOME_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_NAME_HOME_IS_DIGIT=FALSE +SOCCER_NAME_HOME_IS_TIME=FALSE +SOCCER_NAME_HOME_IS_WHITE=TRUE +SOCCER_NAME_HOME_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_NAME_HOME_MARGIN=5 +SOCCER_NAME_HOME_RESTRICTIONS_MAX_AREA=500 +SOCCER_NAME_HOME_RESTRICTIONS_MAX_HEIGHT=40 +SOCCER_NAME_HOME_RESTRICTIONS_MAX_WIDTH=30 +SOCCER_NAME_HOME_RESTRICTIONS_MIN_AREA=1 +SOCCER_NAME_HOME_RESTRICTIONS_MIN_HEIGHT=20 +SOCCER_NAME_HOME_RESTRICTIONS_MIN_WIDTH=1 +SOCCER_NAME_HOME_THRESHOLD=150 +SOCCER_NAME_HOME_VERBOSE=False +SOCCER_NAME_HOME_WHITE_BACKGROUND_THRESHOLD=40 +SOCCER_NAME_HOME_WINDOW_NAME=name_home +SOCCER_NAME_HOME_WINDOW=200,103,337,42 +SOCCER_RESULT_AWAY_DO_RESIZE_CONTOUR=FALSE +SOCCER_RESULT_AWAY_GAUSSIAN_BLUR_WIN_SIZE=3 +SOCCER_RESULT_AWAY_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_RESULT_AWAY_IS_DIGIT=TRUE +SOCCER_RESULT_AWAY_IS_TIME=TRUE +SOCCER_RESULT_AWAY_IS_WHITE=TRUE +SOCCER_RESULT_AWAY_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_RESULT_AWAY_MARGIN=6 +SOCCER_RESULT_AWAY_RESTRICTIONS_MAX_AREA=10000 +SOCCER_RESULT_AWAY_RESTRICTIONS_MAX_HEIGHT=45 +SOCCER_RESULT_AWAY_RESTRICTIONS_MAX_WIDTH=32 +SOCCER_RESULT_AWAY_RESTRICTIONS_MIN_AREA=20 +SOCCER_RESULT_AWAY_RESTRICTIONS_MIN_HEIGHT=20 +SOCCER_RESULT_AWAY_RESTRICTIONS_MIN_WIDTH=4 +SOCCER_RESULT_AWAY_THRESHOLD=110 +SOCCER_RESULT_AWAY_VERBOSE=FALSE +SOCCER_RESULT_AWAY_WHITE_BACKGROUND_THRESHOLD=150 +SOCCER_RESULT_AWAY_WINDOW_NAME=result_away +SOCCER_RESULT_AWAY_WINDOW=685,99,32,47 +SOCCER_RESULT_HOME_DO_RESIZE_CONTOUR=FALSE +SOCCER_RESULT_HOME_GAUSSIAN_BLUR_WIN_SIZE=3 +SOCCER_RESULT_HOME_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_RESULT_HOME_IS_DIGIT=TRUE +SOCCER_RESULT_HOME_IS_TIME=TRUE +SOCCER_RESULT_HOME_IS_WHITE=TRUE +SOCCER_RESULT_HOME_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_RESULT_HOME_MARGIN=6 +SOCCER_RESULT_HOME_RESTRICTIONS_MAX_AREA=1500 +SOCCER_RESULT_HOME_RESTRICTIONS_MAX_HEIGHT=45 +SOCCER_RESULT_HOME_RESTRICTIONS_MAX_WIDTH=32 +SOCCER_RESULT_HOME_RESTRICTIONS_MIN_AREA=20 +SOCCER_RESULT_HOME_RESTRICTIONS_MIN_HEIGHT=20 +SOCCER_RESULT_HOME_RESTRICTIONS_MIN_WIDTH=4 +SOCCER_RESULT_HOME_THRESHOLD=110 +SOCCER_RESULT_HOME_VERBOSE=FALSE +SOCCER_RESULT_HOME_WHITE_BACKGROUND_THRESHOLD=150 +SOCCER_RESULT_HOME_WINDOW_NAME=game_result_home +SOCCER_RESULT_HOME_WINDOW=564,99,32,47 +SOCCER_SCREEN_IDENTITY_DO_RESIZE_CONTOUR=TRUE +SOCCER_SCREEN_IDENTITY_GAUSSIAN_BLUR_WIN_SIZE=3 +SOCCER_SCREEN_IDENTITY_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_SCREEN_IDENTITY_IS_DIGIT=TRUE +SOCCER_SCREEN_IDENTITY_IS_TIME=FALSE +SOCCER_SCREEN_IDENTITY_IS_WHITE=TRUE +SOCCER_SCREEN_IDENTITY_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_SCREEN_IDENTITY_MARGIN=5 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MAX_AREA=105 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MAX_HEIGHT=16 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MAX_WIDTH=14 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MIN_AREA=2 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MIN_HEIGHT=8 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MIN_WIDTH=2 +SOCCER_SCREEN_IDENTITY_THRESHOLD=150 +SOCCER_SCREEN_IDENTITY_VERBOSE=FALSE +SOCCER_SCREEN_IDENTITY_WHITE_BACKGROUND_THRESHOLD=30 +SOCCER_SCREEN_IDENTITY_WINDOW_NAME=screen_identity +SOCCER_SCREEN_IDENTITY_WINDOW=614,80,17,26 +SOCCER_STAT_EXTRA_FIRST_HALF_STRING=105 00 +SOCCER_STAT_EXTRA_SECOND_HALF_STRING=120 00 +SOCCER_STAT_FIRST_HALF_STRING=45 00 +SOCCER_STAT_PENALTY_STRING=penalty +SOCCER_STAT_SECOND_HALF_STRING=90 00 +TELEMETRY_USE=OFF +TESSERACT_LOG=tesseract.log diff --git a/wolf/ml/test/common_test_asset/soccer/.initial_match_result_struct b/wolf/ml/test/common_test_asset/soccer/.initial_match_result_struct new file mode 100644 index 000000000..ade28d6d9 --- /dev/null +++ b/wolf/ml/test/common_test_asset/soccer/.initial_match_result_struct @@ -0,0 +1,100 @@ +CONFIG_SIMILARITY_ALGORITHM=NormalizedLevenshtein +CONFIG_USE_SIMILARITY=True +SIMILARITY_THRESHOLD=0.5 +SOCCER_NAME_AWAY_DO_RESIZE_CONTOUR=FALSE +SOCCER_NAME_AWAY_GAUSSIAN_BLUR_WIN_SIZE=5 +SOCCER_NAME_AWAY_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_NAME_AWAY_IS_DIGIT=FALSE +SOCCER_NAME_AWAY_IS_TIME=FALSE +SOCCER_NAME_AWAY_IS_WHITE=TRUE +SOCCER_NAME_AWAY_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_NAME_AWAY_MARGIN=5 +SOCCER_NAME_AWAY_RESTRICTIONS_MAX_AREA=1000 +SOCCER_NAME_AWAY_RESTRICTIONS_MAX_HEIGHT=100 +SOCCER_NAME_AWAY_RESTRICTIONS_MAX_WIDTH=100 +SOCCER_NAME_AWAY_RESTRICTIONS_MIN_AREA=1 +SOCCER_NAME_AWAY_RESTRICTIONS_MIN_HEIGHT=20 +SOCCER_NAME_AWAY_RESTRICTIONS_MIN_WIDTH=1 +SOCCER_NAME_AWAY_THRESHOLD=150 +SOCCER_NAME_AWAY_VERBOSE=FALSE +SOCCER_NAME_AWAY_WHITE_BACKGROUND_THRESHOLD=30 +SOCCER_NAME_AWAY_WINDOW_NAME=name_away +SOCCER_NAME_AWAY_WINDOW=740,103,337,42 +SOCCER_NAME_HOME_DO_RESIZE_CONTOUR=TRUE +SOCCER_NAME_HOME_GAUSSIAN_BLUR_WIN_SIZE=3 +SOCCER_NAME_HOME_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_NAME_HOME_IS_DIGIT=FALSE +SOCCER_NAME_HOME_IS_TIME=FALSE +SOCCER_NAME_HOME_IS_WHITE=TRUE +SOCCER_NAME_HOME_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_NAME_HOME_MARGIN=5 +SOCCER_NAME_HOME_RESTRICTIONS_MAX_AREA=500 +SOCCER_NAME_HOME_RESTRICTIONS_MAX_HEIGHT=40 +SOCCER_NAME_HOME_RESTRICTIONS_MAX_WIDTH=30 +SOCCER_NAME_HOME_RESTRICTIONS_MIN_AREA=1 +SOCCER_NAME_HOME_RESTRICTIONS_MIN_HEIGHT=20 +SOCCER_NAME_HOME_RESTRICTIONS_MIN_WIDTH=1 +SOCCER_NAME_HOME_THRESHOLD=150 +SOCCER_NAME_HOME_VERBOSE=False +SOCCER_NAME_HOME_WHITE_BACKGROUND_THRESHOLD=40 +SOCCER_NAME_HOME_WINDOW_NAME=name_home +SOCCER_NAME_HOME_WINDOW=200,103,337,42 +SOCCER_RESULT_AWAY_DO_RESIZE_CONTOUR=FALSE +SOCCER_RESULT_AWAY_GAUSSIAN_BLUR_WIN_SIZE=3 +SOCCER_RESULT_AWAY_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_RESULT_AWAY_IS_DIGIT=TRUE +SOCCER_RESULT_AWAY_IS_TIME=TRUE +SOCCER_RESULT_AWAY_IS_WHITE=TRUE +SOCCER_RESULT_AWAY_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_RESULT_AWAY_MARGIN=6 +SOCCER_RESULT_AWAY_RESTRICTIONS_MAX_AREA=10000 +SOCCER_RESULT_AWAY_RESTRICTIONS_MAX_HEIGHT=45 +SOCCER_RESULT_AWAY_RESTRICTIONS_MAX_WIDTH=32 +SOCCER_RESULT_AWAY_RESTRICTIONS_MIN_AREA=20 +SOCCER_RESULT_AWAY_RESTRICTIONS_MIN_HEIGHT=20 +SOCCER_RESULT_AWAY_RESTRICTIONS_MIN_WIDTH=4 +SOCCER_RESULT_AWAY_THRESHOLD=110 +SOCCER_RESULT_AWAY_VERBOSE=FALSE +SOCCER_RESULT_AWAY_WHITE_BACKGROUND_THRESHOLD=150 +SOCCER_RESULT_AWAY_WINDOW_NAME=result_away +SOCCER_RESULT_AWAY_WINDOW=685,99,32,47 +SOCCER_RESULT_HOME_DO_RESIZE_CONTOUR=FALSE +SOCCER_RESULT_HOME_GAUSSIAN_BLUR_WIN_SIZE=3 +SOCCER_RESULT_HOME_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_RESULT_HOME_IS_DIGIT=TRUE +SOCCER_RESULT_HOME_IS_TIME=TRUE +SOCCER_RESULT_HOME_IS_WHITE=TRUE +SOCCER_RESULT_HOME_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_RESULT_HOME_MARGIN=6 +SOCCER_RESULT_HOME_RESTRICTIONS_MAX_AREA=1500 +SOCCER_RESULT_HOME_RESTRICTIONS_MAX_HEIGHT=45 +SOCCER_RESULT_HOME_RESTRICTIONS_MAX_WIDTH=32 +SOCCER_RESULT_HOME_RESTRICTIONS_MIN_AREA=20 +SOCCER_RESULT_HOME_RESTRICTIONS_MIN_HEIGHT=20 +SOCCER_RESULT_HOME_RESTRICTIONS_MIN_WIDTH=4 +SOCCER_RESULT_HOME_THRESHOLD=110 +SOCCER_RESULT_HOME_VERBOSE=FALSE +SOCCER_RESULT_HOME_WHITE_BACKGROUND_THRESHOLD=150 +SOCCER_RESULT_HOME_WINDOW_NAME=game_result_home +SOCCER_RESULT_HOME_WINDOW=564,99,32,47 +SOCCER_SCREEN_IDENTITY_DO_RESIZE_CONTOUR=TRUE +SOCCER_SCREEN_IDENTITY_GAUSSIAN_BLUR_WIN_SIZE=3 +SOCCER_SCREEN_IDENTITY_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_SCREEN_IDENTITY_IS_DIGIT=TRUE +SOCCER_SCREEN_IDENTITY_IS_TIME=FALSE +SOCCER_SCREEN_IDENTITY_IS_WHITE=TRUE +SOCCER_SCREEN_IDENTITY_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_SCREEN_IDENTITY_MARGIN=5 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MAX_AREA=105 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MAX_HEIGHT=16 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MAX_WIDTH=14 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MIN_AREA=2 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MIN_HEIGHT=8 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MIN_WIDTH=2 +SOCCER_SCREEN_IDENTITY_THRESHOLD=150 +SOCCER_SCREEN_IDENTITY_VERBOSE=FALSE +SOCCER_SCREEN_IDENTITY_WHITE_BACKGROUND_THRESHOLD=30 +SOCCER_SCREEN_IDENTITY_WINDOW_NAME=screen_identity +SOCCER_SCREEN_IDENTITY_WINDOW=614,80,17,26 +TELEMETRY_USE=OFF +TESSERACT_LOG=tesseract.log diff --git a/wolf/ml/test/common_test_asset/soccer/.replace_team_names_with_most_similar_string b/wolf/ml/test/common_test_asset/soccer/.replace_team_names_with_most_similar_string new file mode 100644 index 000000000..4b12582a2 --- /dev/null +++ b/wolf/ml/test/common_test_asset/soccer/.replace_team_names_with_most_similar_string @@ -0,0 +1,2 @@ +SIMILAR_STRINGS_FILE_PATH=../wolf/ml/test/common_test_asset/soccer/replace_team_names_with_most_similar_string.txt +SIMILARITY_THRESHOLD=0.5 diff --git a/wolf/ml/test/common_test_asset/soccer/.replace_team_names_with_most_similar_string_0_9 b/wolf/ml/test/common_test_asset/soccer/.replace_team_names_with_most_similar_string_0_9 new file mode 100644 index 000000000..308eb5f44 --- /dev/null +++ b/wolf/ml/test/common_test_asset/soccer/.replace_team_names_with_most_similar_string_0_9 @@ -0,0 +1,2 @@ +SIMILAR_STRINGS_FILE_PATH=../wolf/ml/test/common_test_asset/soccer/replace_team_names_with_most_similar_string.txt +SIMILARITY_THRESHOLD=0.9 diff --git a/wolf/ml/test/common_test_asset/soccer/.set_config b/wolf/ml/test/common_test_asset/soccer/.set_config new file mode 100644 index 000000000..d4abea73d --- /dev/null +++ b/wolf/ml/test/common_test_asset/soccer/.set_config @@ -0,0 +1,20 @@ +SOCCER_SCREEN_IDENTITY=COMMENT +SOCCER_SCREEN_IDENTITY_WINDOW_NAME=window_name_test +SOCCER_SCREEN_IDENTITY_IS_TIME=FALSE +SOCCER_SCREEN_IDENTITY_WINDOW=614,80,17,26 +SOCCER_SCREEN_IDENTITY_DO_RESIZE_CONTOUR=TRUE +SOCCER_SCREEN_IDENTITY_GAUSSIAN_BLUR_WIN_SIZE=3 +SOCCER_SCREEN_IDENTITY_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_SCREEN_IDENTITY_IS_WHITE=TRUE +SOCCER_SCREEN_IDENTITY_IS_DIGIT=TRUE +SOCCER_SCREEN_IDENTITY_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_SCREEN_IDENTITY_MARGIN=5 +SOCCER_SCREEN_IDENTITY_THRESHOLD=150 +SOCCER_SCREEN_IDENTITY_VERBOSE=FALSE +SOCCER_SCREEN_IDENTITY_WHITE_BACKGROUND_THRESHOLD=30 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MAX_AREA=105 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MIN_AREA=2 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MAX_WIDTH=14 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MIN_WIDTH=2 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MAX_HEIGHT=16 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MIN_HEIGHT=8 diff --git a/wolf/ml/test/common_test_asset/soccer/.single_image_result_extraction b/wolf/ml/test/common_test_asset/soccer/.single_image_result_extraction new file mode 100644 index 000000000..0e659dc13 --- /dev/null +++ b/wolf/ml/test/common_test_asset/soccer/.single_image_result_extraction @@ -0,0 +1,129 @@ + +CONFIG_SIMILARITY_ALGORITHM=NormalizedLevenshtein +CONFIG_USE_SIMILARITY=True +SIMILAR_STRINGS_FILE_PATH=asset/team_names.txt +SIMILARITY_THRESHOLD=0.5 +SIMILARITY_THRESHOLD_STAT=0.7 +SIMILARITY_USE_FOR_TEAM_NAMES=FALSE +SOCCER_GLOBAL_FRAME_HEIGHT=720 +SOCCER_GLOBAL_FRAME_WIDTH=1280 +SOCCER_GLOBAL_HEIGHT_TO_DIST_RATIO=0.4 +SOCCER_GLOBAL_MIN_FRAMES=4 +SOCCER_GLOBAL_PLATFORM_FREE=TRUE +SOCCER_GLOBAL_THRESHOLD=190 +SOCCER_NAME_AWAY_DO_RESIZE_CONTOUR=FALSE +SOCCER_NAME_AWAY_GAUSSIAN_BLUR_WIN_SIZE=5 +SOCCER_NAME_AWAY_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_NAME_AWAY_IS_DIGIT=FALSE +SOCCER_NAME_AWAY_IS_TIME=FALSE +SOCCER_NAME_AWAY_IS_WHITE=TRUE +SOCCER_NAME_AWAY_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_NAME_AWAY_MARGIN=5 +SOCCER_NAME_AWAY_RESTRICTIONS_MAX_AREA=1000 +SOCCER_NAME_AWAY_RESTRICTIONS_MAX_HEIGHT=100 +SOCCER_NAME_AWAY_RESTRICTIONS_MAX_WIDTH=100 +SOCCER_NAME_AWAY_RESTRICTIONS_MIN_AREA=1 +SOCCER_NAME_AWAY_RESTRICTIONS_MIN_HEIGHT=20 +SOCCER_NAME_AWAY_RESTRICTIONS_MIN_WIDTH=1 +SOCCER_NAME_AWAY_THRESHOLD=150 +SOCCER_NAME_AWAY_VERBOSE=FALSE +SOCCER_NAME_AWAY_WHITE_BACKGROUND_THRESHOLD=30 +SOCCER_NAME_AWAY_WINDOW_NAME=name_away +SOCCER_NAME_AWAY_WINDOW=740,103,337,42 +SOCCER_NAME_HOME_DO_RESIZE_CONTOUR=TRUE +SOCCER_NAME_HOME_GAUSSIAN_BLUR_WIN_SIZE=3 +SOCCER_NAME_HOME_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_NAME_HOME_IS_DIGIT=FALSE +SOCCER_NAME_HOME_IS_TIME=FALSE +SOCCER_NAME_HOME_IS_WHITE=TRUE +SOCCER_NAME_HOME_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_NAME_HOME_MARGIN=5 +SOCCER_NAME_HOME_RESTRICTIONS_MAX_AREA=500 +SOCCER_NAME_HOME_RESTRICTIONS_MAX_HEIGHT=40 +SOCCER_NAME_HOME_RESTRICTIONS_MAX_WIDTH=30 +SOCCER_NAME_HOME_RESTRICTIONS_MIN_AREA=1 +SOCCER_NAME_HOME_RESTRICTIONS_MIN_HEIGHT=20 +SOCCER_NAME_HOME_RESTRICTIONS_MIN_WIDTH=1 +SOCCER_NAME_HOME_THRESHOLD=150 +SOCCER_NAME_HOME_VERBOSE=False +SOCCER_NAME_HOME_WHITE_BACKGROUND_THRESHOLD=40 +SOCCER_NAME_HOME_WINDOW_NAME=name_home +SOCCER_NAME_HOME_WINDOW=200,103,337,42 +SOCCER_PENALTY_RESTRICTIONS_MAX_AREA=1400 +SOCCER_PENALTY_RESTRICTIONS_MAX_HEIGHT=50 +SOCCER_PENALTY_RESTRICTIONS_MAX_WIDTH=30 +SOCCER_PENALTY_RESTRICTIONS_MIN_AREA=10 +SOCCER_PENALTY_RESTRICTIONS_MIN_HEIGHT=10 +SOCCER_PENALTY_RESTRICTIONS_MIN_WIDTH=2 +SOCCER_PLATFORM_FREE_FRACTION=0.6 +SOCCER_PLATFORM_FREE_RESTRICTIONS_MAX_AREA=1000 +SOCCER_PLATFORM_FREE_RESTRICTIONS_MAX_HEIGHT=45 +SOCCER_PLATFORM_FREE_RESTRICTIONS_MAX_WIDTH=27 +SOCCER_PLATFORM_FREE_RESTRICTIONS_MIN_AREA=10 +SOCCER_PLATFORM_FREE_RESTRICTIONS_MIN_HEIGHT=14 +SOCCER_PLATFORM_FREE_RESTRICTIONS_MIN_WIDTH=2 +SOCCER_RESULT_AWAY_DO_RESIZE_CONTOUR=FALSE +SOCCER_RESULT_AWAY_GAUSSIAN_BLUR_WIN_SIZE=3 +SOCCER_RESULT_AWAY_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_RESULT_AWAY_IS_DIGIT=TRUE +SOCCER_RESULT_AWAY_IS_TIME=TRUE +SOCCER_RESULT_AWAY_IS_WHITE=TRUE +SOCCER_RESULT_AWAY_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_RESULT_AWAY_MARGIN=6 +SOCCER_RESULT_AWAY_RESTRICTIONS_MAX_AREA=10000 +SOCCER_RESULT_AWAY_RESTRICTIONS_MAX_HEIGHT=45 +SOCCER_RESULT_AWAY_RESTRICTIONS_MAX_WIDTH=32 +SOCCER_RESULT_AWAY_RESTRICTIONS_MIN_AREA=20 +SOCCER_RESULT_AWAY_RESTRICTIONS_MIN_HEIGHT=20 +SOCCER_RESULT_AWAY_RESTRICTIONS_MIN_WIDTH=4 +SOCCER_RESULT_AWAY_THRESHOLD=110 +SOCCER_RESULT_AWAY_VERBOSE=FALSE +SOCCER_RESULT_AWAY_WHITE_BACKGROUND_THRESHOLD=150 +SOCCER_RESULT_AWAY_WINDOW_NAME=result_away +SOCCER_RESULT_AWAY_WINDOW=685,99,32,47 +SOCCER_RESULT_HOME_DO_RESIZE_CONTOUR=FALSE +SOCCER_RESULT_HOME_GAUSSIAN_BLUR_WIN_SIZE=3 +SOCCER_RESULT_HOME_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_RESULT_HOME_IS_DIGIT=TRUE +SOCCER_RESULT_HOME_IS_TIME=TRUE +SOCCER_RESULT_HOME_IS_WHITE=TRUE +SOCCER_RESULT_HOME_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_RESULT_HOME_MARGIN=6 +SOCCER_RESULT_HOME_RESTRICTIONS_MAX_AREA=1500 +SOCCER_RESULT_HOME_RESTRICTIONS_MAX_HEIGHT=45 +SOCCER_RESULT_HOME_RESTRICTIONS_MAX_WIDTH=32 +SOCCER_RESULT_HOME_RESTRICTIONS_MIN_AREA=20 +SOCCER_RESULT_HOME_RESTRICTIONS_MIN_HEIGHT=20 +SOCCER_RESULT_HOME_RESTRICTIONS_MIN_WIDTH=4 +SOCCER_RESULT_HOME_THRESHOLD=110 +SOCCER_RESULT_HOME_VERBOSE=FALSE +SOCCER_RESULT_HOME_WHITE_BACKGROUND_THRESHOLD=150 +SOCCER_RESULT_HOME_WINDOW_NAME=game_result_home +SOCCER_RESULT_HOME_WINDOW=564,99,32,47 +SOCCER_SCREEN_IDENTITY_DO_RESIZE_CONTOUR=TRUE +SOCCER_SCREEN_IDENTITY_GAUSSIAN_BLUR_WIN_SIZE=3 +SOCCER_SCREEN_IDENTITY_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_SCREEN_IDENTITY_IS_DIGIT=TRUE +SOCCER_SCREEN_IDENTITY_IS_TIME=FALSE +SOCCER_SCREEN_IDENTITY_IS_WHITE=TRUE +SOCCER_SCREEN_IDENTITY_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_SCREEN_IDENTITY_MARGIN=5 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MAX_AREA=105 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MAX_HEIGHT=16 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MAX_WIDTH=14 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MIN_AREA=2 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MIN_HEIGHT=8 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MIN_WIDTH=2 +SOCCER_SCREEN_IDENTITY_THRESHOLD=150 +SOCCER_SCREEN_IDENTITY_VERBOSE=FALSE +SOCCER_SCREEN_IDENTITY_WHITE_BACKGROUND_THRESHOLD=30 +SOCCER_SCREEN_IDENTITY_WINDOW_NAME=screen_identity +SOCCER_SCREEN_IDENTITY_WINDOW=614,80,17,26 +SOCCER_STAT_CHECK_PENALTY=PEN +SOCCER_STAT_EXTRA_FIRST_HALF_STRING=105 00 +SOCCER_STAT_EXTRA_SECOND_HALF_STRING=120 00 +SOCCER_STAT_FIRST_HALF_STRING=45 00 +SOCCER_STAT_PENALTY_STRING=PEN +SOCCER_STAT_SECOND_HALF_STRING=90 00 +TELEMETRY_USE=OFF +TESSERACT_LOG=tesseract.log diff --git a/wolf/ml/test/common_test_asset/soccer/.update_match_data b/wolf/ml/test/common_test_asset/soccer/.update_match_data new file mode 100644 index 000000000..ade28d6d9 --- /dev/null +++ b/wolf/ml/test/common_test_asset/soccer/.update_match_data @@ -0,0 +1,100 @@ +CONFIG_SIMILARITY_ALGORITHM=NormalizedLevenshtein +CONFIG_USE_SIMILARITY=True +SIMILARITY_THRESHOLD=0.5 +SOCCER_NAME_AWAY_DO_RESIZE_CONTOUR=FALSE +SOCCER_NAME_AWAY_GAUSSIAN_BLUR_WIN_SIZE=5 +SOCCER_NAME_AWAY_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_NAME_AWAY_IS_DIGIT=FALSE +SOCCER_NAME_AWAY_IS_TIME=FALSE +SOCCER_NAME_AWAY_IS_WHITE=TRUE +SOCCER_NAME_AWAY_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_NAME_AWAY_MARGIN=5 +SOCCER_NAME_AWAY_RESTRICTIONS_MAX_AREA=1000 +SOCCER_NAME_AWAY_RESTRICTIONS_MAX_HEIGHT=100 +SOCCER_NAME_AWAY_RESTRICTIONS_MAX_WIDTH=100 +SOCCER_NAME_AWAY_RESTRICTIONS_MIN_AREA=1 +SOCCER_NAME_AWAY_RESTRICTIONS_MIN_HEIGHT=20 +SOCCER_NAME_AWAY_RESTRICTIONS_MIN_WIDTH=1 +SOCCER_NAME_AWAY_THRESHOLD=150 +SOCCER_NAME_AWAY_VERBOSE=FALSE +SOCCER_NAME_AWAY_WHITE_BACKGROUND_THRESHOLD=30 +SOCCER_NAME_AWAY_WINDOW_NAME=name_away +SOCCER_NAME_AWAY_WINDOW=740,103,337,42 +SOCCER_NAME_HOME_DO_RESIZE_CONTOUR=TRUE +SOCCER_NAME_HOME_GAUSSIAN_BLUR_WIN_SIZE=3 +SOCCER_NAME_HOME_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_NAME_HOME_IS_DIGIT=FALSE +SOCCER_NAME_HOME_IS_TIME=FALSE +SOCCER_NAME_HOME_IS_WHITE=TRUE +SOCCER_NAME_HOME_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_NAME_HOME_MARGIN=5 +SOCCER_NAME_HOME_RESTRICTIONS_MAX_AREA=500 +SOCCER_NAME_HOME_RESTRICTIONS_MAX_HEIGHT=40 +SOCCER_NAME_HOME_RESTRICTIONS_MAX_WIDTH=30 +SOCCER_NAME_HOME_RESTRICTIONS_MIN_AREA=1 +SOCCER_NAME_HOME_RESTRICTIONS_MIN_HEIGHT=20 +SOCCER_NAME_HOME_RESTRICTIONS_MIN_WIDTH=1 +SOCCER_NAME_HOME_THRESHOLD=150 +SOCCER_NAME_HOME_VERBOSE=False +SOCCER_NAME_HOME_WHITE_BACKGROUND_THRESHOLD=40 +SOCCER_NAME_HOME_WINDOW_NAME=name_home +SOCCER_NAME_HOME_WINDOW=200,103,337,42 +SOCCER_RESULT_AWAY_DO_RESIZE_CONTOUR=FALSE +SOCCER_RESULT_AWAY_GAUSSIAN_BLUR_WIN_SIZE=3 +SOCCER_RESULT_AWAY_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_RESULT_AWAY_IS_DIGIT=TRUE +SOCCER_RESULT_AWAY_IS_TIME=TRUE +SOCCER_RESULT_AWAY_IS_WHITE=TRUE +SOCCER_RESULT_AWAY_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_RESULT_AWAY_MARGIN=6 +SOCCER_RESULT_AWAY_RESTRICTIONS_MAX_AREA=10000 +SOCCER_RESULT_AWAY_RESTRICTIONS_MAX_HEIGHT=45 +SOCCER_RESULT_AWAY_RESTRICTIONS_MAX_WIDTH=32 +SOCCER_RESULT_AWAY_RESTRICTIONS_MIN_AREA=20 +SOCCER_RESULT_AWAY_RESTRICTIONS_MIN_HEIGHT=20 +SOCCER_RESULT_AWAY_RESTRICTIONS_MIN_WIDTH=4 +SOCCER_RESULT_AWAY_THRESHOLD=110 +SOCCER_RESULT_AWAY_VERBOSE=FALSE +SOCCER_RESULT_AWAY_WHITE_BACKGROUND_THRESHOLD=150 +SOCCER_RESULT_AWAY_WINDOW_NAME=result_away +SOCCER_RESULT_AWAY_WINDOW=685,99,32,47 +SOCCER_RESULT_HOME_DO_RESIZE_CONTOUR=FALSE +SOCCER_RESULT_HOME_GAUSSIAN_BLUR_WIN_SIZE=3 +SOCCER_RESULT_HOME_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_RESULT_HOME_IS_DIGIT=TRUE +SOCCER_RESULT_HOME_IS_TIME=TRUE +SOCCER_RESULT_HOME_IS_WHITE=TRUE +SOCCER_RESULT_HOME_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_RESULT_HOME_MARGIN=6 +SOCCER_RESULT_HOME_RESTRICTIONS_MAX_AREA=1500 +SOCCER_RESULT_HOME_RESTRICTIONS_MAX_HEIGHT=45 +SOCCER_RESULT_HOME_RESTRICTIONS_MAX_WIDTH=32 +SOCCER_RESULT_HOME_RESTRICTIONS_MIN_AREA=20 +SOCCER_RESULT_HOME_RESTRICTIONS_MIN_HEIGHT=20 +SOCCER_RESULT_HOME_RESTRICTIONS_MIN_WIDTH=4 +SOCCER_RESULT_HOME_THRESHOLD=110 +SOCCER_RESULT_HOME_VERBOSE=FALSE +SOCCER_RESULT_HOME_WHITE_BACKGROUND_THRESHOLD=150 +SOCCER_RESULT_HOME_WINDOW_NAME=game_result_home +SOCCER_RESULT_HOME_WINDOW=564,99,32,47 +SOCCER_SCREEN_IDENTITY_DO_RESIZE_CONTOUR=TRUE +SOCCER_SCREEN_IDENTITY_GAUSSIAN_BLUR_WIN_SIZE=3 +SOCCER_SCREEN_IDENTITY_IF_STORE_IMAGE_BOXES=FALSE +SOCCER_SCREEN_IDENTITY_IS_DIGIT=TRUE +SOCCER_SCREEN_IDENTITY_IS_TIME=FALSE +SOCCER_SCREEN_IDENTITY_IS_WHITE=TRUE +SOCCER_SCREEN_IDENTITY_MAKE_WHITE_BACKGROUND=FALSE +SOCCER_SCREEN_IDENTITY_MARGIN=5 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MAX_AREA=105 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MAX_HEIGHT=16 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MAX_WIDTH=14 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MIN_AREA=2 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MIN_HEIGHT=8 +SOCCER_SCREEN_IDENTITY_RESTRICTIONS_MIN_WIDTH=2 +SOCCER_SCREEN_IDENTITY_THRESHOLD=150 +SOCCER_SCREEN_IDENTITY_VERBOSE=FALSE +SOCCER_SCREEN_IDENTITY_WHITE_BACKGROUND_THRESHOLD=30 +SOCCER_SCREEN_IDENTITY_WINDOW_NAME=screen_identity +SOCCER_SCREEN_IDENTITY_WINDOW=614,80,17,26 +TELEMETRY_USE=OFF +TESSERACT_LOG=tesseract.log diff --git a/wolf/ml/test/common_test_asset/soccer/replace_team_names_with_most_similar_string.txt b/wolf/ml/test/common_test_asset/soccer/replace_team_names_with_most_similar_string.txt new file mode 100644 index 000000000..69e3c0da8 --- /dev/null +++ b/wolf/ml/test/common_test_asset/soccer/replace_team_names_with_most_similar_string.txt @@ -0,0 +1 @@ +REAL MADRID diff --git a/wolf/ml/test/common_test_asset/soccer/single_image_result_extraction.mp4 b/wolf/ml/test/common_test_asset/soccer/single_image_result_extraction.mp4 new file mode 100644 index 000000000..ed4805668 Binary files /dev/null and b/wolf/ml/test/common_test_asset/soccer/single_image_result_extraction.mp4 differ diff --git a/wolf/ml/test/common_test_asset/soccer/single_image_result_extraction.png b/wolf/ml/test/common_test_asset/soccer/single_image_result_extraction.png new file mode 100644 index 000000000..4b3d14eb0 Binary files /dev/null and b/wolf/ml/test/common_test_asset/soccer/single_image_result_extraction.png differ diff --git a/wolf/ml/test/common_test_asset/utilities/.get_env_boolean b/wolf/ml/test/common_test_asset/utilities/.get_env_boolean new file mode 100644 index 000000000..963c3bfc2 --- /dev/null +++ b/wolf/ml/test/common_test_asset/utilities/.get_env_boolean @@ -0,0 +1,3 @@ + +# Variables for testing get_env_boolean functions +BOOLEAN_VALUE=True diff --git a/wolf/ml/test/common_test_asset/utilities/.get_env_cv_rect b/wolf/ml/test/common_test_asset/utilities/.get_env_cv_rect new file mode 100644 index 000000000..bfe6ec357 --- /dev/null +++ b/wolf/ml/test/common_test_asset/utilities/.get_env_cv_rect @@ -0,0 +1,3 @@ + +# Variables for testing get_env_boolean functions +CV_RECT_VALUE=313,110,72,14 diff --git a/wolf/ml/test/common_test_asset/utilities/.get_env_float b/wolf/ml/test/common_test_asset/utilities/.get_env_float new file mode 100644 index 000000000..c398cc426 --- /dev/null +++ b/wolf/ml/test/common_test_asset/utilities/.get_env_float @@ -0,0 +1,3 @@ + +# Variables for testing get_env_float functions +FLOAT_VALUE=4.5 diff --git a/wolf/ml/test/common_test_asset/utilities/.get_env_int b/wolf/ml/test/common_test_asset/utilities/.get_env_int new file mode 100644 index 000000000..d742a068f --- /dev/null +++ b/wolf/ml/test/common_test_asset/utilities/.get_env_int @@ -0,0 +1,3 @@ + +# Variables for testing get_env_int functions +INT_VALUE=7 diff --git a/wolf/ml/test/common_test_asset/utilities/.get_env_string b/wolf/ml/test/common_test_asset/utilities/.get_env_string new file mode 100644 index 000000000..08ab60d17 --- /dev/null +++ b/wolf/ml/test/common_test_asset/utilities/.get_env_string @@ -0,0 +1,3 @@ + +# Variables for testing get_env_boolean functions +STRING_VALUE=this is a test! diff --git a/wolf/ml/test/common_test_asset/utilities/.get_nearest_string_0_5 b/wolf/ml/test/common_test_asset/utilities/.get_nearest_string_0_5 new file mode 100644 index 000000000..ce124cb31 --- /dev/null +++ b/wolf/ml/test/common_test_asset/utilities/.get_nearest_string_0_5 @@ -0,0 +1,2 @@ +SIMILAR_STRINGS_FILE_PATH=../wolf/ml/test/common_test_asset/utilities/get_nearest_string.txt +SIMILARITY_THRESHOLD=0.5 diff --git a/wolf/ml/test/common_test_asset/utilities/.get_nearest_string_0_9 b/wolf/ml/test/common_test_asset/utilities/.get_nearest_string_0_9 new file mode 100644 index 000000000..ac1030d5d --- /dev/null +++ b/wolf/ml/test/common_test_asset/utilities/.get_nearest_string_0_9 @@ -0,0 +1,2 @@ +SIMILAR_STRINGS_FILE_PATH=../wolf/ml/test/common_test_asset/utilities/get_nearest_string.txt +SIMILARITY_THRESHOLD=0.9 diff --git a/wolf/ml/test/common_test_asset/utilities/.set_env b/wolf/ml/test/common_test_asset/utilities/.set_env new file mode 100644 index 000000000..679c0f4cc --- /dev/null +++ b/wolf/ml/test/common_test_asset/utilities/.set_env @@ -0,0 +1,6 @@ +# This is a comment line +V1=v1 +V2=v2 +# Another comment line +V3=v3 +V4=v4 diff --git a/wolf/ml/test/common_test_asset/utilities/get_nearest_string.txt b/wolf/ml/test/common_test_asset/utilities/get_nearest_string.txt new file mode 100644 index 000000000..c343a0fc6 --- /dev/null +++ b/wolf/ml/test/common_test_asset/utilities/get_nearest_string.txt @@ -0,0 +1,2 @@ +test1 +hello diff --git a/wolf/ml/test/common_test_asset/utilities/get_value_from_json_file_by_key.json b/wolf/ml/test/common_test_asset/utilities/get_value_from_json_file_by_key.json new file mode 100644 index 000000000..0afae5cdb --- /dev/null +++ b/wolf/ml/test/common_test_asset/utilities/get_value_from_json_file_by_key.json @@ -0,0 +1,4 @@ +{ + "cpp-test-json": "ready", + "cpp-env-json": "OFF" +} diff --git a/wolf/ml/test/w_image_processor_test.hpp b/wolf/ml/test/w_image_processor_test.hpp new file mode 100644 index 000000000..d003f77a0 --- /dev/null +++ b/wolf/ml/test/w_image_processor_test.hpp @@ -0,0 +1,170 @@ +#ifdef WOLF_TEST + +#pragma once + +#ifdef WOLF_ML_OCR + +#define BOOST_TEST_MODULE ml_image_processor + +#include + +#include +#include +#include + +namespace fs = std::filesystem; +fs::path image_processor_path = "../wolf/ml/test/common_test_asset/image_processor"; + +using namespace wolf::ml::ocr; + +BOOST_AUTO_TEST_CASE(prepare_image_for_contour_detection_with_default_arguments) { + fs::path source_image_path = image_processor_path / "test_image.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + fs::path desired_image_path = + image_processor_path / "prepare_image_for_contour_detection_default.png"; + cv::Mat desired_image_temp = cv::imread(desired_image_path.string()); + cv::Mat desired_image; + cv::cvtColor(desired_image_temp, desired_image, cv::COLOR_BGR2GRAY); + config_for_ocr_struct config_for_ocr_struct; + cv::Mat processed_image = + prepare_image_for_contour_detection(source_image, config_for_ocr_struct); + cv::Mat diff = processed_image != desired_image; + BOOST_TEST(cv::countNonZero(diff) == 0); +} + +BOOST_AUTO_TEST_CASE(negative_image_with_default_arguments) { + fs::path source_image_path = image_processor_path / "test_image.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + fs::path desired_image_path = + image_processor_path / "negative_image_default.png"; + cv::Mat desired_image = cv::imread(desired_image_path.string()); + negative_image(source_image); + BOOST_TEST((sum(source_image != desired_image) == cv::Scalar(0, 0, 0, 0))); +} + +BOOST_AUTO_TEST_CASE(make_white_background_with_default_arguments) { + fs::path source_image_path = image_processor_path / "test_image.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + fs::path desired_image_temp = + image_processor_path / "make_white_background_default.png"; + cv::Mat desired_image = cv::imread(desired_image_temp.string()); + config_for_ocr_struct config_for_ocr_struct; + config_for_ocr_struct.make_white_background = true; + make_contour_white_background(source_image, config_for_ocr_struct); + BOOST_TEST((sum(source_image != desired_image) == cv::Scalar(0, 0, 0, 0))); +} + +BOOST_AUTO_TEST_CASE(make_white_background_with_threshold_50) { + fs::path source_image_path = image_processor_path / "test_image.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + fs::path desired_image_temp = + image_processor_path / "make_white_background_50.png"; + cv::Mat desired_image = cv::imread(desired_image_temp.string()); + config_for_ocr_struct config_for_ocr_struct; + config_for_ocr_struct.make_white_background = true; + config_for_ocr_struct.white_background_threshold = 50; + make_contour_white_background(source_image, config_for_ocr_struct); + BOOST_TEST((sum(source_image != desired_image) == cv::Scalar(0, 0, 0, 0))); +} + +BOOST_AUTO_TEST_CASE(find_all_contours_on_image_with_contours) { + fs::path source_image_path = image_processor_path / "test_image.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + config_for_ocr_struct config_for_ocr_struct; + cv::Mat filtered_image = + prepare_image_for_contour_detection(source_image, config_for_ocr_struct); + std::vector> contours = + find_all_countors(filtered_image); + BOOST_TEST(contours.size() == 475); +} + +BOOST_AUTO_TEST_CASE(find_all_contours_on_image_without_contours) { + fs::path source_image_path = + image_processor_path / "find_all_contours_solid_black.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + config_for_ocr_struct config_for_ocr_struct; + cv::Mat filtered_image = + prepare_image_for_contour_detection(source_image, config_for_ocr_struct); + std::vector> contours = + find_all_countors(filtered_image); + BOOST_TEST(contours.size() == 0); +} + +BOOST_AUTO_TEST_CASE(resize_function_with_only_height_argument) { + fs::path source_image_path = image_processor_path / "test_image.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + resize_image(source_image, 40); + BOOST_TEST(source_image.rows == 40); +} + +BOOST_AUTO_TEST_CASE(resize_function_with_only_width_argument) { + fs::path source_image_path = image_processor_path / "test_image.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + resize_image(source_image, -1, 60); + BOOST_TEST(source_image.cols == 60); +} + +BOOST_AUTO_TEST_CASE(resize_function_without_argument) { + fs::path source_image_path = image_processor_path / "test_image.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + int cols = source_image.cols; + int rows = source_image.rows; + resize_image(source_image); + BOOST_TEST(source_image.cols == cols); + BOOST_TEST(source_image.rows == rows); +} + +BOOST_AUTO_TEST_CASE(gaussian_filter_with_default_argument) { + fs::path source_image_path = image_processor_path / "test_image.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + fs::path desired_image_path = + image_processor_path / "gaussian_filter_default.png"; + cv::Mat desired_image = cv::imread(desired_image_path.string()); + config_for_ocr_struct config_for_ocr_struct; + gaussian_blur(source_image, config_for_ocr_struct); + BOOST_TEST((sum(source_image != desired_image) == cv::Scalar(0, 0, 0, 0))); +} + +BOOST_AUTO_TEST_CASE(gaussian_filter_with_win_size_7) { + fs::path source_image_path = image_processor_path / "test_image.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + fs::path desired_image_path = image_processor_path / "gaussian_filter_7.png"; + cv::Mat desired_image = cv::imread(desired_image_path.string()); + config_for_ocr_struct config_for_ocr_struct; + config_for_ocr_struct.gaussian_blur_win_size = 7; + gaussian_blur(source_image, config_for_ocr_struct); + BOOST_TEST((sum(source_image != desired_image) == cv::Scalar(0, 0, 0, 0))); +} + +BOOST_AUTO_TEST_CASE(threshold_filter_with_default_argument) { + fs::path source_image_path = image_processor_path / "test_image.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + fs::path desired_image_path = + image_processor_path / "threshold_filter_default.png"; + cv::Mat desired_image_temp = cv::imread(desired_image_path.string()); + cv::Mat desired_image; + cv::cvtColor(desired_image_temp, desired_image, cv::COLOR_BGR2GRAY); + config_for_ocr_struct config_for_ocr_struct; + threshold_image(source_image, config_for_ocr_struct); + cv::Mat diff = source_image != desired_image; + BOOST_TEST(cv::countNonZero(diff) == 0); +} + +BOOST_AUTO_TEST_CASE(threshold_filter_with_threshold_value_180) { + fs::path source_image_path = image_processor_path / "test_image.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + fs::path desired_image_path = + image_processor_path / "threshold_filter_180.png"; + cv::Mat desired_image_temp = cv::imread(desired_image_path.string()); + cv::Mat desired_image; + cv::cvtColor(desired_image_temp, desired_image, cv::COLOR_BGR2GRAY); + config_for_ocr_struct config_for_ocr_struct; + config_for_ocr_struct.threshold_value = 180; + threshold_image(source_image, config_for_ocr_struct); + cv::Mat diff = desired_image != desired_image; + BOOST_TEST(cv::countNonZero(diff) == 0); +} + +#endif // WOLF_ML_OCR + +#endif // WOLF_TEST diff --git a/wolf/ml/test/w_nudity_detection_test.hpp b/wolf/ml/test/w_nudity_detection_test.hpp new file mode 100644 index 000000000..b02aa11e7 --- /dev/null +++ b/wolf/ml/test/w_nudity_detection_test.hpp @@ -0,0 +1,205 @@ +#ifdef WOLF_TEST + +#pragma once + +#ifdef WOLF_ML_NUDITY_DETECTION + +// #define BOOST_TEST_MODULE ml_nudity_detection + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace fs = std::filesystem; +fs::path nudity_detection_path = "../../../wolf/ml/test/common_test_asset/nudity_detection"; + +using namespace wolf::ml; +using w_nud_det = wolf::ml::nudet::w_nud_det; + +BOOST_AUTO_TEST_CASE(check_stream_to_avoid_nsfw_context) { + const wolf::system::w_leak_detector _detector = {}; + + // set envirenment variables + fs::path env_file_path = nudity_detection_path / ".check_stream_to_avoid_nsfw_context"; + set_env(env_file_path.string().c_str()); + + std::cout << "nudity_detection_text is running" << std::endl; + + using w_av_set_opt = wolf::media::ffmpeg::w_av_set_opt; + const auto _opt = std::vector(); + + constexpr auto _url = "srt://20.216.45.101:554?mode=caller&transtype=live&timeout=5000000"; + + // initial the nudity detection param + std::string nudity_detection_model_path = + get_env_string("NUDITY_DETECTION_MODEL_PATH"); + std::cout << "+++++++++++++++++++++++++++++++" << nudity_detection_model_path << std::endl; + w_nud_det *nud_detector = new w_nud_det(nudity_detection_model_path); + cv::Mat frame_image; + cv::Mat rgb_image; + int tranied_model_height = get_env_int("TRAINED_MODEL_IMAGE_HEIGHT"); + int trained_model_width = get_env_int("TRAINED_MODEL_IMAGE_WIDTH"); + + boost::leaf::try_handle_all( + [&]() -> boost::leaf::result { + using namespace std::chrono_literals; + using w_av_packet = wolf::media::ffmpeg::w_av_packet; + using w_ffmpeg = wolf::media::ffmpeg::w_ffmpeg; + using w_av_config = wolf::media::ffmpeg::w_av_config; + using w_av_codec_opt = wolf::media::ffmpeg::w_av_codec_opt; + using w_av_frame = wolf::media::ffmpeg::w_av_frame; + using w_openal = wolf::media::w_openal; + + // frame index + int _index = 0; + // 5 seconds for io socket timeout + const auto t0 = std::chrono::high_resolution_clock::now(); + bool _video_initialized = false; + wolf::media::ffmpeg::w_decoder _video_decoder = {}; + wolf::media::w_openal_config _openal_config = {}; + wolf::media::w_openal _openal = {}; + std::unique_ptr _av_video_frame; + auto _width = 0; + auto _height = 0; + + auto _on_frame_cb = + [&](const w_av_packet &p_packet, const AVStream *p_audio_stream, + const AVStream *p_video_stream) -> bool { + const auto _stream_index = p_packet.get_stream_index(); + if (p_video_stream) { + std::cout << "got video frame" << std::endl; + + if (_video_initialized == false) { + auto _codec_param = p_video_stream->codecpar; + _width = _codec_param->width; + _height = _codec_param->height; + + const auto _codec_id = _codec_param->codec_id; + const auto _fmt = gsl::narrow_cast(_codec_param->format); + + const auto _config = w_av_config(_fmt, _width, _height); + + const auto _codec_opt = w_av_codec_opt{ + _codec_param->bit_rate, /*bitrate*/ + 30, /*fps*/ + 600, /*gop*/ + _codec_param->level, /*level*/ + 2, /*max_b_frames*/ + 3, /*refs*/ + -1, /*thread_count*/ + }; + + auto _decoder_res = w_ffmpeg::create_decoder(_config, _codec_id, _codec_opt); + if (_decoder_res.has_error()) { + return false; // close it + } + + _video_decoder = std::move(_decoder_res.value()); + + // create destination avframe for decoder + _av_video_frame = + std::make_unique((w_av_config &&) std::move(_config)); + + auto _decoded_frame_res = _av_video_frame->init(); + if (_decoded_frame_res.has_error()) { + std::cout << "could not initialize decoder" << std::endl; + return false; // close it + } + + _video_initialized = true; + } + + auto _res = _video_decoder.decode(p_packet, *_av_video_frame); + if (_res.has_error()) { + std::cout << "could not decode" << std::endl; + } else { + auto _dst_config = w_av_config(AVPixelFormat::AV_PIX_FMT_BGRA, _width, _height); + + auto _rgba_frame = + _av_video_frame->convert_video((w_av_config&&)std::move(_dst_config)); + + + if (_rgba_frame.has_error()) { + std::cout << "could not convert av video frame to rgb frame" << std::endl; + } else { + const auto _path = std::string("C:/src/WolfEngine/build") + + "/" + std::to_string(_index++) + "_rist_decoded.png"; + std::cout << "path:" << _path << std::endl; + _rgba_frame.value().save_video_frame_to_img_file(_path); + std::cout << "rgb frame created" << std::endl; + + + + auto image_dat_tuple = _rgba_frame.value().get(); + auto image_data = std::get<0>(image_dat_tuple); + frame_image = cv::Mat(_height, _width, CV_8UC4, image_data[0]); + cv::cvtColor(frame_image, rgb_image, cv::COLOR_RGBA2RGB); + cv::Mat temp_rgb_image = rgb_image.clone(); + + // cv::resize(rgb_image, rgb_image, cv::Size(trained_model_width, tranied_model_height), 0, 0, cv::INTER_NEAREST); + // std::vector value; + // const auto T0 = std::chrono::high_resolution_clock::now(); + // value = nud_detector->nudity_detection(rgb_image.data, rgb_image.cols, + // rgb_image.rows, rgb_image.channels()); + + // const auto T1 = std::chrono::high_resolution_clock::now(); + + // const auto D = std::chrono::duration_cast(T1 - T0); + + // for(int i=0; i<5; i++) { + // cv::putText(temp_rgb_image, std::to_string(value[i]), cv::Point(100,(i+1)*50), cv::FONT_HERSHEY_DUPLEX, 1, cv::Scalar(0,255,0), 2, false); + // } + // cv::putText(temp_rgb_image, std::to_string(D.count()), cv::Point(300,50), cv::FONT_HERSHEY_DUPLEX, 1, cv::Scalar(255,0,0), 2, false); + + cv::imshow("It is a test!", temp_rgb_image); + cv::waitKey(1); + + // std::cout << "+ +" << value[0] + // << " " << value[1] + // << " " << value[2] + // << " " << value[3] + // << " " << value[4] + // << " " << value[5] + // << " " << value[6] + // << " " << value[7] + // << " " << value[8] + // << " " << value[9] + // << std::endl; + } + } + } + const auto t1 = std::chrono::high_resolution_clock::now(); + if (std::chrono::duration_cast(t1 - t0) > 100s) { + return false; + } + return true; + }; + + BOOST_CHECK(w_ffmpeg::open_stream(_url, _opt, _on_frame_cb)); + + return {}; + }, + [](const w_trace &p_trace) { + std::cout << "got error :" << p_trace << std::endl; + BOOST_REQUIRE(false); + }, + [] { + std::cout << "got an error!" << std::endl; + BOOST_ERROR(false); + }); + + std::cout << "nudity_detection_text is done!" << std::endl; +} + +#endif // WOLF_ML_NUDITY_DETECTION + +#endif // WOLF_TEST diff --git a/wolf/ml/test/w_ocr_engine_test.hpp b/wolf/ml/test/w_ocr_engine_test.hpp new file mode 100644 index 000000000..b063f7675 --- /dev/null +++ b/wolf/ml/test/w_ocr_engine_test.hpp @@ -0,0 +1,273 @@ +#ifdef WOLF_TEST + +#pragma once + +#ifdef WOLF_ML_OCR + +#define BOOST_TEST_MODULE ml_ocr_engine + +#include + +#include +#include +#include + +#include + +using namespace wolf::ml::ocr; + +namespace fs = std::filesystem; +w_ocr_engine ocr_object; +fs::path ocr_engine_asset_path = "../wolf/ml/test/common_test_asset/ocr_engine"; + +BOOST_AUTO_TEST_CASE(check_if_overlapped_with_default_arguments_on_overlapped_rects) { + cv::Rect rect1(0, 0, 10, 10); + cv::Rect rect2(0, 0, 10, 10); + config_for_ocr_struct config_for_ocr_struct; + BOOST_TEST(ocr_object.check_if_overlapped(rect1, rect2, config_for_ocr_struct) == + true); +} + +BOOST_AUTO_TEST_CASE(check_if_overlapped_with_default_arguments_on_non_overlapped_rects) { + cv::Rect rect1(0, 0, 10, 10); + cv::Rect rect2(10, 10, 10, 10); + config_for_ocr_struct config_for_ocr_struct; + BOOST_TEST(ocr_object.check_if_overlapped(rect1, rect2, config_for_ocr_struct) == + false); +} + +BOOST_AUTO_TEST_CASE(check_if_overlapped_with_threshold_0_01) { + cv::Rect rect1(0, 0, 10, 10); + cv::Rect rect2(9, 9, 10, 10); + config_for_ocr_struct config_for_ocr_struct; + config_for_ocr_struct.overlapped_threshold = 0.01; + BOOST_TEST(ocr_object.check_if_overlapped(rect1, rect2, config_for_ocr_struct) == + false); +} + +BOOST_AUTO_TEST_CASE(check_if_overlapped_with_threshold_0_009) { + cv::Rect rect1(0, 0, 10, 10); + cv::Rect rect2(9, 9, 10, 10); + config_for_ocr_struct config_for_ocr_struct; + config_for_ocr_struct.overlapped_threshold = 0.009; + BOOST_TEST(ocr_object.check_if_overlapped(rect1, rect2, config_for_ocr_struct) == + true); +} + +BOOST_AUTO_TEST_CASE(contours_to_char_structs_with_default_arguments) { + fs::path desired_image_path = + ocr_engine_asset_path / "contours_to_char_structs_default.png"; + cv::Mat desired_image_temp = cv::imread(desired_image_path.string()); + cv::Mat desired_image; + cv::cvtColor(desired_image_temp, desired_image, cv::COLOR_BGR2GRAY); + std::vector> contours = + find_all_countors(desired_image); + std::vector characters = + ocr_object.contours_to_char_structs(contours); + BOOST_TEST(characters.size() == 475); +} + +BOOST_AUTO_TEST_CASE(contours_to_char_structs_with_default_arguments_on_empty_contour_vector) { + fs::path desired_image_path = + ocr_engine_asset_path / "contours_to_char_structs_Solid_black.png"; + cv::Mat desired_image_temp = cv::imread(desired_image_path.string()); + cv::Mat desired_image; + cv::cvtColor(desired_image_temp, desired_image, cv::COLOR_BGR2GRAY); + std::vector> contours = + find_all_countors(desired_image); + std::vector characters = + ocr_object.contours_to_char_structs(contours); + BOOST_TEST(characters.size() == 0); +} + +BOOST_AUTO_TEST_CASE(enhance_contour_image_for_modelwithdefaultarguments) { + fs::path source_image_path = ocr_engine_asset_path / "test_image.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + cv::Mat processed_image = source_image.clone(); + config_for_ocr_struct config_for_ocr_struct; + ocr_object.enhance_contour_image_for_model(processed_image, + config_for_ocr_struct); + BOOST_TEST((sum(source_image != processed_image) == cv::Scalar(0, 0, 0, 0))); +} + +BOOST_AUTO_TEST_CASE(enhance_contour_image_for_model_with_resize) { + fs::path source_image_path = ocr_engine_asset_path / "test_image.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + fs::path desired_image_path = + ocr_engine_asset_path / "enhance_contour_image_for_model_resize.png"; + cv::Mat desired_image = cv::imread(desired_image_path.string()); + config_for_ocr_struct config_for_ocr_struct; + config_for_ocr_struct.do_resize_contour = true; + ocr_object.enhance_contour_image_for_model(source_image, + config_for_ocr_struct); + BOOST_TEST((sum(source_image != desired_image) == cv::Scalar(0, 0, 0, 0))); +} + +BOOST_AUTO_TEST_CASE(enhance_contour_image_for_modelwithmake_white) { + fs::path source_image_path = ocr_engine_asset_path / "test_image.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + fs::path desired_image_path = + ocr_engine_asset_path / "enhance_contour_image_for_model_make_white.png"; + cv::Mat desired_image = cv::imread(desired_image_path.string()); + config_for_ocr_struct config_for_ocr_struct; + config_for_ocr_struct.make_white_background = true; + ocr_object.enhance_contour_image_for_model(source_image, + config_for_ocr_struct); + BOOST_TEST((sum(source_image != desired_image) == cv::Scalar(0, 0, 0, 0))); +} + +BOOST_AUTO_TEST_CASE(euclidean_distance_with_two_non_overlapped_chars) { + w_ocr_engine::characters_struct char1; + w_ocr_engine::characters_struct char2; + char1.center.x = 1; + char1.center.y = 1; + char2.center.x = 1; + char2.center.y = 2; + double distance = ocr_object.euclidean_distance(char1, char2); + BOOST_TEST(distance == 1); +} + +BOOST_AUTO_TEST_CASE(euclidean_distance_with_two_overlapped_chars) { + w_ocr_engine::characters_struct char1; + w_ocr_engine::characters_struct char2; + char1.center.x = 1; + char1.center.y = 1; + char2.center.x = 1; + char2.center.y = 1; + double distance = ocr_object.euclidean_distance(char1, char2); + BOOST_TEST(distance == 0); +} + +BOOST_AUTO_TEST_CASE(euclidean_distance_with_two_empty_chars) { + w_ocr_engine::characters_struct char1; + w_ocr_engine::characters_struct char2; + double distance = ocr_object.euclidean_distance(char1, char2); + BOOST_TEST(distance == 0); +} + +BOOST_AUTO_TEST_CASE(filter_chars_by_contour_size_without_restriction) { + fs::path source_image_path = + ocr_engine_asset_path / "filter_chars_by_contour_size.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + config_for_ocr_struct config_for_ocr_struct; + config_for_ocr_struct.restrictions.max_area = 99999999; + config_for_ocr_struct.restrictions.min_area = 1; + config_for_ocr_struct.restrictions.max_height = 99999999; + config_for_ocr_struct.restrictions.min_height = 1; + config_for_ocr_struct.restrictions.max_width = 99999999; + config_for_ocr_struct.restrictions.min_width = 1; + cv::Mat prepared = + prepare_image_for_contour_detection(source_image, config_for_ocr_struct); + std::vector> contours = find_all_countors(prepared); + std::vector characters = + ocr_object.contours_to_char_structs(contours); + std::vector filtered_characters = + ocr_object.filter_chars_by_contour_size(characters, + config_for_ocr_struct); + BOOST_TEST(filtered_characters.size() == 3); +} + +BOOST_AUTO_TEST_CASE(filter_chars_by_contour_size_by_area_restriction) { + fs::path source_image_path = + ocr_engine_asset_path / "filter_chars_by_contour_size.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + config_for_ocr_struct config_for_ocr_struct; + config_for_ocr_struct.restrictions.max_area = 500000; + config_for_ocr_struct.restrictions.min_area = 1; + config_for_ocr_struct.restrictions.max_height = 99999999; + config_for_ocr_struct.restrictions.min_height = 1; + config_for_ocr_struct.restrictions.max_width = 99999999; + config_for_ocr_struct.restrictions.min_width = 1; + cv::Mat prepared = + prepare_image_for_contour_detection(source_image, config_for_ocr_struct); + std::vector> contours = find_all_countors(prepared); + std::vector characters = + ocr_object.contours_to_char_structs(contours); + std::vector filtered_characters = + ocr_object.filter_chars_by_contour_size(characters, + config_for_ocr_struct); + BOOST_TEST(filtered_characters.size() == 2); +} + +BOOST_AUTO_TEST_CASE(filter_chars_by_contour_size_by_width_restriction) { + fs::path source_image_path = + ocr_engine_asset_path / "filter_chars_by_contour_size.png"; + cv::Mat source_image = cv::imread(source_image_path.string()); + config_for_ocr_struct config_for_ocr_struct; + config_for_ocr_struct.restrictions.max_area = 500000; + config_for_ocr_struct.restrictions.min_area = 1; + config_for_ocr_struct.restrictions.max_height = 99999999; + config_for_ocr_struct.restrictions.min_height = 1; + config_for_ocr_struct.restrictions.max_width = 300; + config_for_ocr_struct.restrictions.min_width = 1; + cv::Mat prepared = + prepare_image_for_contour_detection(source_image, config_for_ocr_struct); + std::vector> contours = find_all_countors(prepared); + std::vector characters = + ocr_object.contours_to_char_structs(contours); + std::vector filtered_characters = + ocr_object.filter_chars_by_contour_size(characters, + config_for_ocr_struct); + BOOST_TEST(filtered_characters.size() == 1); +} + +BOOST_AUTO_TEST_CASE(merge_overlapped_contours_on_two_overlapped_contours) { + config_for_ocr_struct config_for_ocr_struct; + w_ocr_engine::characters_struct char1; + char1.bound_rect.x = 0; + char1.bound_rect.y = 0; + char1.bound_rect.width = 10; + char1.bound_rect.height = 10; + w_ocr_engine::characters_struct char2; + char2.bound_rect.x = 0; + char2.bound_rect.y = 0; + char2.bound_rect.width = 10; + char2.bound_rect.height = 10; + std::vector characters; + characters.push_back(char1); + characters.push_back(char2); + ocr_object.merge_overlapped_contours(characters, config_for_ocr_struct); + BOOST_TEST(characters.size() == 1); +} + +BOOST_AUTO_TEST_CASE(merge_overlapped_contours_on_two_non_overlapped_contours) { + config_for_ocr_struct config_for_ocr_struct; + w_ocr_engine::characters_struct char1; + char1.bound_rect.x = 0; + char1.bound_rect.y = 0; + char1.bound_rect.width = 10; + char1.bound_rect.height = 10; + w_ocr_engine::characters_struct char2; + char2.bound_rect.x = 0; + char2.bound_rect.y = 10; + char2.bound_rect.width = 10; + char2.bound_rect.height = 10; + std::vector characters; + characters.push_back(char1); + characters.push_back(char2); + ocr_object.merge_overlapped_contours(characters, config_for_ocr_struct); + BOOST_TEST(characters.size() == 2); +} + +BOOST_AUTO_TEST_CASE(mask_contour_works) { + fs::path contours_image_path = + ocr_engine_asset_path / "filter_chars_by_contour_size.png"; + fs::path source_image_path = ocr_engine_asset_path / "test_image.png"; + fs::path desired_image_path = ocr_engine_asset_path / "mask_contour.png"; + cv::Mat contours_image = cv::imread(contours_image_path.string()); + cv::Mat source_image = cv::imread(source_image_path.string()); + cv::Mat desired_image = cv::imread(desired_image_path.string()); + config_for_ocr_struct config_for_ocr_struct; + config_for_ocr_struct.restrictions.max_area = 500000; + cv::Mat prepared = prepare_image_for_contour_detection(contours_image, + config_for_ocr_struct); + std::vector> contours = find_all_countors(prepared); + std::vector characters = + ocr_object.contours_to_char_structs(contours); + cv::Mat processed = ocr_object.mask_contour(source_image, characters[1]); + BOOST_TEST((sum(processed != desired_image) == cv::Scalar(0, 0, 0, 0))); +} + +#endif // WOLF_ML_OCR + +#endif // WOLF_TEST diff --git a/wolf/ml/test/w_referee_test.hpp b/wolf/ml/test/w_referee_test.hpp new file mode 100644 index 000000000..5dab44648 --- /dev/null +++ b/wolf/ml/test/w_referee_test.hpp @@ -0,0 +1,108 @@ +#ifdef WOLF_TEST + +#pragma once + +#ifdef WOLF_ML_OCR + +#define BOOST_TEST_MODULE ml_referee + +#include + +#include +#include +#include + +#include + +namespace fs = std::filesystem; +fs::path w_referee_path = "../wolf/ml/test/common_test_asset/w_referee"; + +using namespace wolf::ml::ocr; + +BOOST_AUTO_TEST_CASE(concatenate_name_result_function) { + w_referee ocr_referee_object; + + std::vector cluster_temp; + + w_ocr_engine::character_and_center char_center_temp; + char_center_temp.text = "T"; + char_center_temp.center = cv::Point(5, 20); + cluster_temp.push_back(char_center_temp); + char_center_temp.text = "e"; + char_center_temp.center = cv::Point(10, 20); + cluster_temp.push_back(char_center_temp); + char_center_temp.text = "s"; + char_center_temp.center = cv::Point(15, 20); + cluster_temp.push_back(char_center_temp); + char_center_temp.text = "t"; + char_center_temp.center = cv::Point(20, 20); + cluster_temp.push_back(char_center_temp); + + w_ocr_engine::character_and_center temp_result = + ocr_referee_object.concatenate_name_result(cluster_temp); + + BOOST_TEST(temp_result.text.compare("T e s t") == 0); +} + +BOOST_AUTO_TEST_CASE(if_the_string_is_in_the_vector_function) { + w_referee ocr_referee_object; + + std::vector vote_vector_temp; + w_referee::vote_over_string_vector vote_var_temp; + vote_var_temp.already_voted = false; + vote_var_temp.center = cv::Point(70, 77); + vote_var_temp.str = "Test"; + vote_vector_temp.push_back(vote_var_temp); + + w_ocr_engine::character_and_center char_temp; + char_temp.center = cv::Point(70, 77); + char_temp.text = "Test"; + + BOOST_TEST(ocr_referee_object.if_the_string_is_in_the_vector(char_temp, + vote_vector_temp)); +} + +BOOST_AUTO_TEST_CASE(voting_over_results_and_names_function) { + w_referee ocr_referee_object; + + std::vector all_results_temp; + w_referee::frame_result_struct frame_date_temp; + + frame_date_temp.frame_number = 110; + frame_date_temp.away_name.text = "ENGLAND"; + frame_date_temp.away_result.text = "1"; + frame_date_temp.home_name.text = "IRAN"; + frame_date_temp.home_result.text = "2"; + frame_date_temp.stat = w_referee::final_result; + all_results_temp.push_back(frame_date_temp); + all_results_temp.push_back(frame_date_temp); + all_results_temp.push_back(frame_date_temp); + all_results_temp.push_back(frame_date_temp); + all_results_temp.push_back(frame_date_temp); + all_results_temp.push_back(frame_date_temp); + all_results_temp.push_back(frame_date_temp); + all_results_temp.push_back(frame_date_temp); + all_results_temp.push_back(frame_date_temp); + all_results_temp.push_back(frame_date_temp); + frame_date_temp.away_result.text = "3"; + all_results_temp.push_back(frame_date_temp); + all_results_temp.push_back(frame_date_temp); + all_results_temp.push_back(frame_date_temp); + all_results_temp.push_back(frame_date_temp); + all_results_temp.push_back(frame_date_temp); + all_results_temp.push_back(frame_date_temp); + all_results_temp.push_back(frame_date_temp); + all_results_temp.push_back(frame_date_temp); + all_results_temp.push_back(frame_date_temp); + + w_referee::frame_result_struct frame_date_result; + + ocr_referee_object.voting_over_results_and_names(frame_date_result, + all_results_temp); + + BOOST_TEST(frame_date_result.away_result.text.compare("1") == 0); +} + +#endif // WOLF_ML_OCR + +#endif // WOLF_TEST diff --git a/wolf/ml/test/w_soccer_test.hpp b/wolf/ml/test/w_soccer_test.hpp new file mode 100644 index 000000000..a4a467670 --- /dev/null +++ b/wolf/ml/test/w_soccer_test.hpp @@ -0,0 +1,246 @@ +#ifdef WOLF_TEST + +#pragma once + +#ifdef WOLF_ML_OCR + +#define BOOST_TEST_MODULE ml_soccer + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace fs = std::filesystem; +fs::path soccer_asset_path = "../wolf/ml/test/common_test_asset/soccer"; + +using namespace wolf::ml::ocr; + +BOOST_AUTO_TEST_CASE(set_config_function) { + fs::path env_file_path = soccer_asset_path / ".set_config"; + + set_env(env_file_path.string().c_str()); + + char *type = new char[50]; + snprintf(type, 50, "SOCCER_SCREEN_IDENTITY"); + w_ocr_engine::config_struct screen_identity = w_soccer::set_config(type); + + delete[] type; + + BOOST_TEST(!screen_identity.config_for_ocr.verbose); + BOOST_TEST(screen_identity.config_for_ocr.restrictions.max_area == 105); + BOOST_TEST(screen_identity.name.compare("window_name_test") == 0); +} + +std::vector video_result; +typedef void ocr_callback(char *, int, uint8_t *, int, int); + +void ocr_result(char *pResultBuffer, int pResultBufferSize, + uint8_t *pImageBuffer, int pImageWidth, int pImageHeight) { + w_referee::match_result_struct match_result_temp; + std::string temp(pResultBuffer); + std::vector result_vec = split_string(temp, ','); + + match_result_temp.stat = result_vec[0]; + match_result_temp.home_name.text = result_vec[1]; + match_result_temp.home_result.text = result_vec[2]; + match_result_temp.away_name.text = result_vec[3]; + match_result_temp.away_result.text = result_vec[4]; + + cv::Mat image = cv::Mat(pImageHeight, pImageWidth, CV_8UC3, pImageBuffer); + + match_result_temp.result_image = image; + video_result.push_back(match_result_temp); +} + +BOOST_AUTO_TEST_CASE(single_image_result_extraction_function) { + fs::path json_file_path = + soccer_asset_path / ".single_image_result_extraction"; + fs::path video_path = + soccer_asset_path / "single_image_result_extraction.mp4"; + + set_env(json_file_path.string().c_str()); + + ocr_callback *_callback = &ocr_result; + + w_soccer *referee_obj = new w_soccer(); + + // This is an example of using the Project + w_read_video_frames player(video_path.string()); + int number_of_frames = int(player.get_frame_amount()); + + cv::Mat frame; + for (int i = 0; i < number_of_frames; i++) { + frame = player.read_video_frame_by_frame(); + if (frame.empty()) { + break; + } + + char *res_str = new char[256]; + referee_obj->single_image_result_extraction(frame.data, frame.rows, + frame.cols, _callback); + } + frame.release(); + + std::cout << "Results: " << video_result[0].stat << " " + << video_result[0].home_name.text << " " + << video_result[0].away_name.text << " " + << video_result[0].home_result.text << " " + << video_result[0].away_result.text << std::endl; + + BOOST_TEST(video_result[0].stat.compare("90 00") == 0); + BOOST_TEST(video_result[0].home_name.text.compare("ARSENAL") == 0); + BOOST_TEST(video_result[0].away_name.text.compare("FC BAYERN") == 0); + BOOST_TEST(video_result[0].home_result.text.compare("0") == 0); + BOOST_TEST(video_result[0].away_result.text.compare("8") == 0); +} + +BOOST_AUTO_TEST_CASE(fill_stat_map_function) { + fs::path json_file_path = soccer_asset_path / ".fill_stat_map"; + + set_env(json_file_path.string().c_str()); + + w_soccer referee_obj; + + std::map stat_map = referee_obj.get_stat_map(); + + BOOST_TEST(stat_map["45 00"].compare("first_half") == 0); + BOOST_TEST(stat_map["90 00"].compare("second_half") == 0); + // BOOST_TEST(referee_obj->stat_extra_first_half.compare("105 00") == 0); + // BOOST_TEST(referee_obj->stat_extra_second_half.compare("120 00") == 0); + // BOOST_TEST(referee_obj->stat_penalty.compare("penalty") == 0); +} + +BOOST_AUTO_TEST_CASE(replace_team_names_with_most_similar_string_finds_similar_team_to_REALMDRD) { + std::vector results; + w_referee::match_result_struct result; + + fs::path similar_strings_path = + soccer_asset_path / ".replace_team_names_with_most_similar_string"; + + set_env(similar_strings_path.string().c_str()); + + result.away_name.text = "REALMDRD"; + results.push_back(result); + + w_soccer::replace_team_names_with_most_similar_string(results); + + BOOST_TEST(results[0].away_name.text.compare("REAL MADRID") == 0); +} + +BOOST_AUTO_TEST_CASE(initial_match_result_struct_function) { + fs::path json_file_path = soccer_asset_path / ".initial_match_result_struct"; + + set_env(json_file_path.string().c_str()); + + w_referee::frame_result_struct frame_data; + frame_data.away_name.text = "IRAN"; + frame_data.away_result.text = "3"; + frame_data.home_name.text = "ENGLAND"; + frame_data.home_result.text = "3"; + frame_data.frame_number = 114; + frame_data.stat = "first_half"; + cv::Mat image = cv::Mat::zeros(50, 50, CV_8UC3); + + w_soccer soccer_object; + + w_referee::match_result_struct match_struct_temp; + match_struct_temp = + soccer_object.initial_match_result_struct(frame_data, image); + + image.release(); + BOOST_TEST(match_struct_temp.all_frames_results[0].away_name.text.compare( + "IRAN") == 0); + BOOST_TEST(match_struct_temp.stat.compare("first_half") == 0); + BOOST_TEST(match_struct_temp.all_frames_results[0].away_result.text.compare( + "3") == 0); +} + +BOOST_AUTO_TEST_CASE(update_match_data_function) { + fs::path json_file_path = soccer_asset_path / ".update_match_data"; + + set_env(json_file_path.string().c_str()); + + w_referee::frame_result_struct frame_data; + frame_data.away_name.text = "IRAN"; + frame_data.away_result.text = "3"; + frame_data.home_name.text = "ENGLAND"; + frame_data.home_result.text = "3"; + frame_data.frame_number = 114; + frame_data.stat = "first_half"; + cv::Mat image = cv::Mat::zeros(50, 50, CV_8UC3); + + w_soccer soccer_object; + + std::vector match_data_temp; + match_data_temp = soccer_object.get_matches_data(); + BOOST_TEST(match_data_temp.size() == 0); + + soccer_object.update_match_data(frame_data, image); + frame_data.frame_number = 116; + soccer_object.update_match_data(frame_data, image); + frame_data.frame_number = 117; + soccer_object.update_match_data(frame_data, image); + + match_data_temp = soccer_object.get_matches_data(); + BOOST_TEST(match_data_temp.size() == 1); + + frame_data.frame_number = 210; + soccer_object.update_match_data(frame_data, image); + + match_data_temp = soccer_object.get_matches_data(); + BOOST_TEST(match_data_temp.size() == 2); + BOOST_TEST(match_data_temp[1].all_frames_results[0].frame_number == 210); +} + +BOOST_AUTO_TEST_CASE(get_matches_data_function) { + fs::path json_file_path = soccer_asset_path / ".update_match_data"; + + set_env(json_file_path.string().c_str()); + + w_referee::frame_result_struct frame_data; + frame_data.away_name.text = "IRAN"; + frame_data.away_result.text = "3"; + frame_data.home_name.text = "ENGLAND"; + frame_data.home_result.text = "3"; + frame_data.frame_number = 114; + frame_data.stat = "first_half"; + cv::Mat image = cv::Mat::zeros(50, 50, CV_8UC3); + + w_soccer soccer_object; + + std::vector match_data_temp; + soccer_object.update_match_data(frame_data, image); + match_data_temp = soccer_object.get_matches_data(); + BOOST_TEST(match_data_temp.size() == 1); + BOOST_TEST(match_data_temp[0].all_frames_results[0].frame_number == 114); +} + +BOOST_AUTO_TEST_CASE(replace_team_names_with_most_similar_string_finds_similar_team_to_empty_string) { + std::vector results; + w_referee::match_result_struct result; + + fs::path similar_strings_path = + soccer_asset_path / ".replace_team_names_with_most_similar_string"; + + set_env(similar_strings_path.string().c_str()); + + result.away_name.text = ""; + results.push_back(result); + + w_soccer::replace_team_names_with_most_similar_string(results); + + BOOST_TEST(results[0].away_name.text.compare("") == 0); +} + + +#endif // WOLF_ML_OCR + +#endif // WOLF_TEST \ No newline at end of file diff --git a/wolf/ml/test/w_utilities_test.hpp b/wolf/ml/test/w_utilities_test.hpp new file mode 100644 index 000000000..ad05e5ae1 --- /dev/null +++ b/wolf/ml/test/w_utilities_test.hpp @@ -0,0 +1,317 @@ +#ifdef WOLF_TEST + +#pragma once + +#ifdef WOLF_ML_OCR + +#define BOOST_TEST_MODULE ml_utilities + +#include + +#include +#include +#include + +namespace fs = std::filesystem; +fs::path utilities_asset_path = "../wolf/ml/test/common_test_asset/utilities"; + +using namespace wolf::ml::ocr; + +BOOST_AUTO_TEST_CASE(get_value_from_json_file_by_key_gives_value_by_key) { + fs::path json_file_path = + utilities_asset_path / "get_value_from_json_file_by_key.json"; + std::string key = "cpp-test-json"; + std::string value = + get_value_from_json_file_by_key(json_file_path.string(), key); + BOOST_TEST(value.compare("ready") == 0); +} + +BOOST_AUTO_TEST_CASE(string_2_boolean_on_capitalized_characters) { + std::vector list_string; + list_string.push_back("true"); + list_string.push_back("FalSe"); + bool temp; + temp = string_2_boolean(list_string[0]); + BOOST_TEST(temp); + temp = string_2_boolean(list_string[1]); + BOOST_TEST(!temp); +} + +BOOST_AUTO_TEST_CASE(line_of_numbers_in_string_to_vector_of_integers_function) { + std::string temp = "100,200,300,400"; + std::vector integer_list = + line_of_numbers_in_string_to_vector_of_integers(temp); + BOOST_TEST(integer_list[0] == 100); + BOOST_TEST(integer_list[1] == 200); + BOOST_TEST(integer_list[2] == 300); + BOOST_TEST(integer_list[3] == 400); +} + +BOOST_AUTO_TEST_CASE(store_image_in_folder_function) { + std::vector temp_video_result; + w_referee::match_result_struct temp; + w_referee::frame_result_struct frame_result; + + frame_result.home_name.text = "Home"; + frame_result.home_result.text = "2"; + frame_result.away_name.text = "Away"; + frame_result.away_result.text = "1"; + frame_result.frame_number = 3; + frame_result.stat = "first_half"; + int height = 50; + int width = 50; + temp.result_image = cv::Mat::zeros(height, width, CV_8UC3); + temp.all_frames_results.push_back(frame_result); + temp_video_result.push_back(temp); + + std::string output_folder_path = "."; + fs::path temp_video_path = "../test/123.mp4"; + store_image_in_folder(temp_video_result, output_folder_path, + temp_video_path.string()); + + fs::path file_path = "./123_0.png"; + + bool is_file_exist = false; + if (std::filesystem::remove(file_path.string())) { + is_file_exist = true; + } + BOOST_TEST(is_file_exist); +} + +BOOST_AUTO_TEST_CASE(write_results_in_file_function) { + std::vector temp_video_result; + w_referee::match_result_struct temp; + w_referee::frame_result_struct frame_result; + + frame_result.home_name.text = "Home"; + frame_result.home_result.text = "2"; + frame_result.away_name.text = "Away"; + frame_result.away_result.text = "1"; + frame_result.frame_number = 3; + frame_result.stat = "first_half"; + int height = 50; + int width = 50; + temp.result_image = cv::Mat::zeros(height, width, CV_8UC3); + temp.all_frames_results.push_back(frame_result); + temp_video_result.push_back(temp); + + fs::path temp_output_text_file = "./test_write_results_in_file.txt"; + + write_results_in_file(temp_video_result, temp_output_text_file.string()); + + std::vector lines = + read_text_file_line_by_line(temp_output_text_file.string()); + + if (!std::filesystem::remove(temp_output_text_file.string())) { + BOOST_TEST(false); + } + + BOOST_TEST(lines[0].compare(temp_video_result[0].stat + "," + + temp_video_result[0].home_name.text + "," + + temp_video_result[0].home_result.text + "," + + temp_video_result[0].away_name.text + "," + + temp_video_result[0].away_result.text + "," + + std::to_string(temp_video_result[0].frame_number)) == + 0); +} + +BOOST_AUTO_TEST_CASE(read_text_file_line_by_line_function) { + fs::path file_path = "./test_read_text_file_line_by_line.txt"; + std::string content = "This is a test!"; + + write_in_file_append(file_path.string(), content); + std::vector lines = + read_text_file_line_by_line(file_path.string()); + + if (!std::filesystem::remove(file_path.string())) { + BOOST_TEST(false); + } + + BOOST_TEST(lines[0].compare(content) == 0); +} + +BOOST_AUTO_TEST_CASE(normalized_levenshtein_similarity_on_one_empty_argument) { + float similarity = normalized_levenshtein_similarity("", "text"); + BOOST_TEST(similarity == 0); +} + +BOOST_AUTO_TEST_CASE(normalized_levenshtein_similarity_on_two_empty_argument) { + float similarity = normalized_levenshtein_similarity("", ""); + BOOST_TEST(similarity == 0); +} + +BOOST_AUTO_TEST_CASE(normalized_levenshtein_similarity_on_two_equal_string) { + float similarity = normalized_levenshtein_similarity("test", "test"); + BOOST_TEST(similarity == 1); +} + +BOOST_AUTO_TEST_CASE(normalized_levenshtein_similarity_with_similarity_0_5_on_strings) { + float similarity = normalized_levenshtein_similarity("test", "mast"); + BOOST_TEST(similarity == 0.5); +} + +BOOST_AUTO_TEST_CASE(normalized_levenshtein_similarity_on_same_numbers) { + float similarity = normalized_levenshtein_similarity("1234", "1234"); + BOOST_TEST(similarity == 1); +} + +BOOST_AUTO_TEST_CASE(normalized_levenshtein_similarity_on_strings_with_different_length) { + float similarity = normalized_levenshtein_similarity("test", "tst"); + BOOST_TEST(similarity == 0.75); +} + +BOOST_AUTO_TEST_CASE(get_nearest_string_with_threshold_0_5_returns_similar_string) { + fs::path similar_strings_path = + utilities_asset_path / ".get_nearest_string_0_5"; + + set_env(similar_strings_path.string().c_str()); + + std::string path = get_env_string("SIMILAR_STRINGS_FILE_PATH"); + std::string output = get_nearest_string("tst1", path); + BOOST_TEST(output.compare("test1") == 0); +} + +BOOST_AUTO_TEST_CASE(get_nearest_string_with_threshold_0_9_returns_input) { + fs::path similar_strings_path = + utilities_asset_path / ".get_nearest_string_0_9"; + + set_env(similar_strings_path.string().c_str()); + + std::string path = get_env_string("SIMILAR_STRINGS_FILE_PATH"); + std::string output = get_nearest_string("tst1", path); + BOOST_TEST(output.compare("tst1") == 0); +} + +BOOST_AUTO_TEST_CASE(get_nearest_string_on_empty_string_returns_empty_string) { + fs::path similar_strings_path = + utilities_asset_path / ".get_nearest_string_0_5"; + + set_env(similar_strings_path.string().c_str()); + + std::string path = get_env_string("SIMILAR_STRINGS_FILE_PATH"); + std::string output = get_nearest_string("", path); + BOOST_TEST(output.compare("") == 0); +} + +BOOST_AUTO_TEST_CASE(replace_string_first_phrase_exists_in_text) { + std::string text = "hello hamed"; + replace_string(text, "hamed", "bagher"); + BOOST_TEST(text.compare("hello bagher") == 0); +} + +BOOST_AUTO_TEST_CASE(replace_string_returns_text_if_first_phrase_not_exists_in_text) { + std::string text = "hello hamed"; + replace_string(text, "shahoo", "bagher"); + BOOST_TEST(text.compare("hello hamed") == 0); +} + +BOOST_AUTO_TEST_CASE(replace_string_returns_empty_text_if_input_text_was_empty) { + std::string text = ""; + replace_string(text, "shahoo", "bagher"); + BOOST_TEST(text.compare("") == 0); +} + +BOOST_AUTO_TEST_CASE(is_line_contains_variable_function_find_empty_line) { + const std::string content = ""; + + BOOST_TEST(!is_line_contains_variable(content)); +} + +BOOST_AUTO_TEST_CASE(is_line_contains_variable_function_find_line_started_by_sharp) { + const std::string content = "# This is a commented line!"; + + BOOST_TEST(!is_line_contains_variable(content)); +} + +BOOST_AUTO_TEST_CASE(set_env_function) { + fs::path dot_env_file_path = utilities_asset_path / ".set_env"; + + set_env(dot_env_file_path.string().c_str()); + + if (const char *env_p1 = getenv("V1")) { + std::string temp(env_p1); + BOOST_TEST(temp.compare("v1") == 0); + } + if (const char *env_p2 = getenv("V2")) { + std::string temp(env_p2); + BOOST_TEST(temp.compare("v2") == 0); + } + if (const char *env_p3 = getenv("V3")) { + std::string temp(env_p3); + BOOST_TEST(temp.compare("v3") == 0); + } + if (const char *env_p4 = getenv("V4")) { + std::string temp(env_p4); + BOOST_TEST(temp.compare("v4") == 0); + } +} + +BOOST_AUTO_TEST_CASE(get_env_int_function) { + fs::path dot_env_file_path = utilities_asset_path / ".get_env_int"; + + set_env(dot_env_file_path.string().c_str()); + + std::string key = "INT_VALUE"; + int value = get_env_int(key.c_str()); + + BOOST_TEST(value == 7); +} + +BOOST_AUTO_TEST_CASE(get_env_float_function) { + fs::path dot_env_file_path = utilities_asset_path / ".get_env_float"; + + set_env(dot_env_file_path.string().c_str()); + + std::string key = "FLOAT_VALUE"; + float value = get_env_float(key.c_str()); + + BOOST_TEST(value == 4.5); +} + +BOOST_AUTO_TEST_CASE(get_env_boolean_function) { + fs::path dot_env_file_path = utilities_asset_path / ".get_env_boolean"; + + set_env(dot_env_file_path.string().c_str()); + + std::string key = "BOOLEAN_VALUE"; + bool value = get_env_boolean(key.c_str()); + + BOOST_TEST(value); +} + +BOOST_AUTO_TEST_CASE(get_env_string_function) { + fs::path dot_env_file_path = utilities_asset_path / ".get_env_string"; + + set_env(dot_env_file_path.string().c_str()); + + std::string key = "STRING_VALUE"; + std::string value = get_env_string(key.c_str()); + + BOOST_TEST(value.compare("this is a test! == 0")); +} + +BOOST_AUTO_TEST_CASE(get_env_cv_rect_function) { + fs::path dot_env_file_path = utilities_asset_path / ".get_env_cv_rect"; + + set_env(dot_env_file_path.string().c_str()); + + std::string key = "CV_RECT_VALUE"; + cv::Rect value = get_env_cv_rect(key.c_str()); + + BOOST_TEST(value.x == 313); + BOOST_TEST(value.y == 110); + BOOST_TEST(value.width == 72); + BOOST_TEST(value.height == 14); +} + +BOOST_AUTO_TEST_CASE(get_relative_path_to_root_function) { + std::string temp = get_relative_path_to_root(); + + bool result = (temp.compare("../") == 0 || temp.compare("../../../") == 0); + + BOOST_TEST(result); +} + +#endif // WOLF_ML_OCR + +#endif // WOLF_TEST diff --git a/wolf/ml/w_common.cpp b/wolf/ml/w_common.cpp new file mode 100644 index 000000000..e9a7d5252 --- /dev/null +++ b/wolf/ml/w_common.cpp @@ -0,0 +1,152 @@ +#include "w_common.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace wolf::ml { + +auto write_in_file_append(_In_ std::string pFilePath, _In_ std::string pContent) -> void { + std::ofstream file; + + file.open(pFilePath, std::ios_base::app); // append instead of overwrite + file << pContent << std::endl; + + file.close(); +} + +auto read_text_file_line_by_line(_In_ std::string pFilePath) -> std::vector { + std::vector lines; + std::string line; + + std::ifstream input_file(pFilePath); + if (!input_file.is_open()) { + std::cerr << "Could not open the file - '" << pFilePath << "'" << std::endl; + return lines; + } + + while (std::getline(input_file, line)) { + lines.push_back(line); + } + + input_file.close(); + return lines; +} + +auto split_string(_In_ std::string input_string, _In_ char reference) -> std::vector { + std::stringstream test(input_string); + std::string segment; + std::vector seglist; + + while (std::getline(test, segment, reference)) { + seglist.push_back(segment); + } + + return seglist; +} + +auto string_2_boolean(_In_ std::string pVariable) -> bool { + bool result; + std::transform(pVariable.begin(), pVariable.end(), pVariable.begin(), ::tolower); + + if (pVariable.compare("true") == 0 || pVariable.compare("false") == 0) { + std::istringstream is(pVariable); + is >> std::boolalpha >> result; + } else { + throw std::runtime_error("Invalid input, the input must be 'true' or 'false' not " + pVariable); + } + + return result; +} + +auto is_line_contains_variable(const std::string pStr) -> bool { + bool decision = false; + if (pStr.size() > 0) { + if (pStr.at(0) != '#' && pStr.size() > 2) { + decision = true; + } + } + return decision; +} + +auto set_env(_In_ const char* pDotEnvFilePath) -> void { +#ifdef __TELEMETRY + auto span = trace::Scope(get_tracer()->StartSpan("set_env")); +#endif + std::string env_file_path(pDotEnvFilePath); + auto lines = read_text_file_line_by_line(env_file_path); + + std::vector> env_vector; + for (int i = 0; i < lines.size(); i++) { + if (is_line_contains_variable(lines[i])) { + env_vector.push_back(split_string(lines[i], '=')); + } + } + + for (int i = 0; i < env_vector.size(); i++) { +#ifdef _WIN32 + _putenv_s(env_vector[i][0].c_str(), env_vector[i][1].c_str()); +#else + setenv(env_vector[i][0].c_str(), env_vector[i][1].c_str(), 1); +#endif + } +} + +auto get_env_int(_In_ const char* pKey) -> int { + int value = -1; + if (const char* env_p = getenv(pKey)) { + std::string temp(env_p); + value = std::stoi(temp); + } else { + // TODO add log + } + + return value; +} + +auto get_env_float(_In_ const char* pKey) -> float { + float value = -1; + if (const char* env_p = getenv(pKey)) { + std::string temp(env_p); + value = std::stof(temp); + } else { + // TODO add log + } + + return value; +} + +auto get_env_boolean(_In_ const char* pKey) -> bool { + bool value = false; + if (const char* env_p = getenv(pKey)) { + std::string temp(env_p); + value = string_2_boolean(temp); + } else { + // TODO add log + } + + return value; +} + +auto get_env_string(_In_ const char* pKey) -> std::string { + std::string value; + if (const char* env_p = getenv(pKey)) { + value = std::string(env_p); + } else { + // TODO add log + } + + return value; +} + +} // namespace wolf::ml \ No newline at end of file diff --git a/wolf/ml/w_common.hpp b/wolf/ml/w_common.hpp new file mode 100644 index 000000000..f2e0504ab --- /dev/null +++ b/wolf/ml/w_common.hpp @@ -0,0 +1,99 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#pragma once + +#include +#include +#include +#include +#include + +// #include "salieri.h" + +#include "wolf.hpp" +#include + +namespace wolf::ml { + +/*! + The function appends the input as input content to the end of the text file. + + \param pFilePath the file path. + \param pContent the input text content. + \return (void) +*/ +W_API auto write_in_file_append(_In_ std::string pFilePath, _In_ std::string pContent) -> void; + +/*! + The function gets a string as input and return the boolean representation of the input. + + \param pVariable the string input. + \return the boolean representain of the input string. +*/ +W_API auto string_2_boolean(_In_ std::string pVariable) -> bool; + +W_API auto split_string(_In_ std::string input_string, _In_ char reference) + -> std::vector; + +/*! + The function reads all lines of the input file and returns them in a string vector. + + \param pFilePath The path of the input file. + \return a vector of strings. +*/ +W_API auto read_text_file_line_by_line(_In_ std::string pFilePath) -> std::vector; + +/*! + The .env file may have empty or commented lines. The is_line_contains_variable functions + use to detect these lines. + + \param pStr The input string. + \return False, if the input string contains # or is empty. +*/ +W_API auto is_line_contains_variable(const std::string pStr) -> bool; + +/*! + The function reads environment variables from the .env file and set them in the environment + by using the putenv function. + + \param pDotEnvFilePath The path of the .env file. + \return +*/ +W_API auto set_env(_In_ const char* pDotEnvFilePath) -> void; + +/*! + The function return the value of an environment variable based on the input key. + + \param pKey The path of the .env file. + \return the value of the variable in int. +*/ +W_API auto get_env_int(_In_ const char* pKey) -> int; + +/*! + The function return the value of an environment variable based on the input key. + + \param pKey The path of the .env file. + \return the value of the variable in float. +*/ +W_API auto get_env_float(_In_ const char* pKey) -> float; + +/*! + The function return the value of an environment variable based on the input key. + + \param pKey The path of the .env file. + \return the value of the variable in boolean. +*/ +W_API auto get_env_boolean(_In_ const char* pKey) -> bool; + +/*! + The function return the value of an environment variable based on the input key. + + \param pKey The path of the .env file. + \return the value of the variable in string. +*/ +W_API auto get_env_string(_In_ const char* pKey) -> std::string; + +} // namespace wolf::ml diff --git a/wolf/protos/raft.proto b/wolf/protos/raft.proto new file mode 100644 index 000000000..c96d3dd30 --- /dev/null +++ b/wolf/protos/raft.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; +package wolf.raft; + +service Raft { + rpc BootstrapClientStreaming(stream RaftBootstrapReq) returns (RaftBootstrapRes) {} + rpc Bootstrap (RaftBootstrapReq) returns (RaftBootstrapRes) {} + rpc BootstrapServerStreaming(RaftBootstrapReq) returns (stream RaftBootstrapRes) {} + rpc BootstrapBidirectionalStreaming(stream RaftBootstrapReq) returns (stream RaftBootstrapRes) {} + +} + +/* + Represents all error codes of services +*/ +enum ErrorCode { + RAFT_UNDEFINED_ERROR = 0; + RAFT_BOOTSTRAP_FAILED = 1; +} + +/* + The structure of the error response which is shared + between all rpc(s) of all services. +*/ +message RaftErrorRes { + string msg_id = 1; + ErrorCode code = 2; + string msg = 3; +} + +/* + messages for Bootstrap +*/ +message RaftBootstrapReq { + string msg_id = 1; + uint64 number_of_nodes = 3; +} + +message RaftBootstrapOkRes { + string msg_id = 1; +} + +message RaftBootstrapRes { + oneof response { + RaftBootstrapOkRes ok_res = 1; + RaftErrorRes error_res = 2; + } +} diff --git a/wolf/stream/grpc/w_grpc_client.cpp b/wolf/stream/grpc/w_grpc_client.cpp new file mode 100644 index 000000000..712c0b510 --- /dev/null +++ b/wolf/stream/grpc/w_grpc_client.cpp @@ -0,0 +1 @@ +//#include "w_grpc_client.hpp" \ No newline at end of file diff --git a/wolf/stream/grpc/w_grpc_client.hpp b/wolf/stream/grpc/w_grpc_client.hpp new file mode 100644 index 000000000..ef3da3c49 --- /dev/null +++ b/wolf/stream/grpc/w_grpc_client.hpp @@ -0,0 +1,138 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#ifdef WOLF_STREAM_GRPC + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +namespace wolf::stream::rpc { +class w_grpc_client { + public: + W_API w_grpc_client() noexcept = default; + W_API ~w_grpc_client() noexcept = default; + + W_API boost::leaf::result init(_In_ const std::string_view p_server_url, + _In_ const short p_port) noexcept { + if (p_server_url.empty()) { + return W_FAILURE(std::errc::invalid_argument, "missing grpc server url endpoint"); + } + + grpc::ChannelArguments _channel_args{}; + _channel_args.SetInt(GRPC_ARG_MAX_RECEIVE_MESSAGE_LENGTH, + INT_MAX); // Set the maximum message size to the maximum value allowed by + // a 32-bit signed integer + _channel_args.SetInt(GRPC_ARG_MAX_SEND_MESSAGE_LENGTH, + INT_MAX); // Set the maximum message size to the maximum value allowed by + // a 32-bit signed integer + + const auto _host = wolf::format("{}:{}", p_server_url, p_port); + this->_channel = + grpc::CreateCustomChannel(_host, grpc::InsecureChannelCredentials(), _channel_args); + + if (this->_channel == nullptr) { + return W_FAILURE(std::errc::operation_canceled, + "could not create channel to connect to grpc server"); + } + + return 0; + } + + // template + // W_API boost::asio::awaitable send_bidirectional(_In_ Request& request, + // _Inout_ Response& response) { + // Stub stub{_channel}; + // grpc::ClientContext client_context; + // + // std::unique_ptr> reader_writer; + // bool request_ok = co_await agrpc::request(PrepareAsync, stub, client_context, + // reader_writer); if (!request_ok) { + // // Channel is either permanently broken or transiently broken but with the fail-fast + // option. co_return; + // } + // + // // Reads and writes can be performed simultaneously. + // using namespace boost::asio::experimental::awaitable_operators; + // auto [read_ok, write_ok] = + // co_await (agrpc::read(reader_writer, response) && agrpc::write(reader_writer, request)); + // + // // Do not forget to signal that we are done writing before finishing. + // co_await agrpc::writes_done(reader_writer); + // + // grpc::Status status; + // co_await agrpc::finish(reader_writer, status); + // } + // + // template + // W_API boost::asio::awaitable send_stream(_In_ Request& request, + // _Inout_ Response& response) noexcept { + // Stub stub{_channel}; + // grpc::ClientContext client_context; + // + // std::unique_ptr> writer; + // bool request_ok = co_await agrpc::request(PrepareAsync, stub, client_context, writer, + // response); + // + // // Optionally read initial metadata first. + // bool read_ok = co_await agrpc::read_initial_metadata(writer); + // + // // Send a message. + // bool write_ok = co_await agrpc::write(writer, request); + // + // // Signal that we are done writing. + // bool writes_done_ok = co_await agrpc::writes_done(writer); + // + // // Wait for the server to recieve all our messages. + // grpc::Status status; + // co_await agrpc::finish(writer, status); + // + // co_return; + // } + + template + W_API boost::asio::awaitable send_unary(_In_ Request& p_request, + _Inout_ Response& p_response) noexcept { + Stub stub{_channel}; + grpc::ClientContext client_context; + + using RPC = agrpc::RPC; + auto _res = co_await RPC::request(_context, stub, client_context, p_request, p_response, + boost::asio::use_awaitable); + co_return; + } + + template + W_API void exec(_In_ std::chrono::steady_clock::duration&& p_timeout, _In_ Function&& p_function, + _In_ Args&&... p_args) noexcept { + boost::asio::co_spawn( + this->_context, + [&]() -> boost::asio::template awaitable { + co_await (std::forward(p_function)(std::forward(p_args)...) || + wolf::system::w_time::timeout(std::chrono::steady_clock::now() + p_timeout)); + }, + boost::asio::detached); + + this->_context.run(); + } + + private: + agrpc::GrpcContext _context; + std::shared_ptr _channel; +}; +} // namespace wolf::stream::rpc + +#endif diff --git a/wolf/stream/grpc/w_grpc_server.cpp b/wolf/stream/grpc/w_grpc_server.cpp new file mode 100644 index 000000000..5ff2ff3e5 --- /dev/null +++ b/wolf/stream/grpc/w_grpc_server.cpp @@ -0,0 +1,2 @@ +//#include "w_grpc_server.hpp" + diff --git a/wolf/stream/grpc/w_grpc_server.hpp b/wolf/stream/grpc/w_grpc_server.hpp new file mode 100644 index 000000000..d641342e2 --- /dev/null +++ b/wolf/stream/grpc/w_grpc_server.hpp @@ -0,0 +1,40 @@ +/* + Project: Wolf Engine. Copyright © 2014-2023 Pooya Eimandar + https://github.com/WolfEngine/WolfEngine +*/ + +#ifdef WOLF_STREAM_GRPC + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace wolf::stream::rpc { +class w_grpc_server { + public: + /* + * @param p_io_context, the boost io context + * @param p_url,the endpoint of the server + * @param p_port, the port of the server + * @param p_socket_options, the socket options + * @param p_on_data_callback, on data callback for session + * @param p_on_error_callback, on error callback for session + * @returns void + */ + //W_API static boost::leaf::result run( + // _In_ boost::asio::io_context &p_io_context, _In_ const std::string_view &p_url, + // _In_ const short p_port, _In_ std::chrono::steady_clock::duration &&p_timeout) noexcept; +}; +} // namespace wolf::stream::rpc + +#endif diff --git a/wolf/stream/http/w_http_server.cpp b/wolf/stream/http/w_http_server.cpp new file mode 100644 index 000000000..e2be22ff2 --- /dev/null +++ b/wolf/stream/http/w_http_server.cpp @@ -0,0 +1,24 @@ +#include "w_http_server.hpp" +#include + +using w_http_server = wolf::stream::http::w_http_server; + +int log_message(const struct mg_connection *p_conn, const char *p_message) +{ + (void)p_conn; + std::fprintf(stderr, "%s\n", p_message); + return 0; +} + +static CivetCallbacks s_callbacks; +const CivetCallbacks *get_civet_callbacks() +{ + std::memset(&s_callbacks, 0, sizeof(s_callbacks)); + s_callbacks.log_message = &log_message; + return &s_callbacks; +} + +w_http_server::w_http_server(const std::vector &p_options, + const void *p_user_data) + : _server(std::make_unique(p_options, get_civet_callbacks(), + p_user_data)) {} \ No newline at end of file diff --git a/wolf/stream/http/w_http_server.hpp b/wolf/stream/http/w_http_server.hpp new file mode 100644 index 000000000..d52ab061d --- /dev/null +++ b/wolf/stream/http/w_http_server.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace wolf::stream::http +{ + using w_http_function = + std::function, Json::Value>( + const mg_request_info *req_info, const Json::Value &)>; + + class w_http_server + { + public: + w_http_server(const std::vector &p_options, + const void *p_user_data); + virtual ~w_http_server(){}; + + // prevent copy constructor + w_http_server(const w_http_server &) = delete; + + // prevent copying + w_http_server &operator=(const w_http_server &) = delete; + + // void add_handlers(std::map &p_funcs); + // void remove_handler(std::string p_handler_name); + + private: + std::unique_ptr _server; + }; +} // namespace wolf::stream::http diff --git a/wolf/stream/janus/w_janus_api_emc.cpp b/wolf/stream/janus/w_janus_api_emc.cpp new file mode 100644 index 000000000..15dc9494e --- /dev/null +++ b/wolf/stream/janus/w_janus_api_emc.cpp @@ -0,0 +1,555 @@ +#if defined(WOLF_STREAM_JANUS) && defined(EMSCRIPTEN) + +#include "w_janus_api_emc.hpp" +#include +#include + +using w_janus_api_emc = wolf::stream::janus::w_janus_api_emc; + +static std::function s_on_init_callback = nullptr; +static std::function s_on_screen_shared_callback = nullptr; +static std::function s_on_join_room_callback = nullptr; + +extern "C" { +EMSCRIPTEN_KEEPALIVE +void on_init_callback(int p_init_state) { + if (s_on_init_callback) { + s_on_init_callback(p_init_state); + } +} + +EMSCRIPTEN_KEEPALIVE +void on_screen_shared(_In_ double p_room_id) { + if (s_on_screen_shared_callback) { + s_on_screen_shared_callback(p_room_id); + } +} + +EMSCRIPTEN_KEEPALIVE +void on_join_room_callback() { + if (s_on_join_room_callback) { + s_on_join_room_callback(); + } +} +} + +// clang-format off + +boost::leaf::result +w_janus_api_emc::init(_In_ bool p_debug_mode, + _In_ std::function p_on_init_callback) noexcept { + + s_on_init_callback = std::move(p_on_init_callback); + + return EM_ASM_INT({ + var _debug = ""; + + if ($0 === 1) { + _debug = "all"; + } + // init Janus + Janus.init({ + debug: _debug , callback: function () { + // Make sure the browser supports WebRTC + if (!Janus.isWebrtcSupported()) { + console.error("No WebRTC support"); + return 1; + } + } + }); + + // global variables + capture = null; + id = null; + local_tracks = {}; + local_videos = 0; + remote_tracks = {}; + remote_videos = 0; + role = null; + room = null; + screen = null; + source = null; + spinner = null; + + // generate random opaque id + var _char_set = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + opaque_id = ""; + for (var i = 0; i < 11; i++) { + var _random_pos = Math.floor(Math.random() * _char_set.length); + opaque_id += _char_set.substring(_random_pos, _random_pos + 1); + } + + + // create a session + janus = new Janus({ + server: server, + iceServers: ice_servers, + success: function () { + // attach to videoroom plugin + janus.attach( + { + plugin: "janus.plugin.videoroom", + opaqueId: opaque_id, + success: function (p_plugin_handle) { + screen = p_plugin_handle; + Janus.log("plugin attached! (" + screen.getPlugin() + ", id=" + screen.getId() + ")"); + _on_init_callback(0); + }, + error: function (p_error) { + Janus.error("error attaching plugin: ", p_error); + _on_init_callback(1); + }, + consentDialog: function (p_on) { + Janus.debug("consent dialog should be " + (p_on ? "on" : "off") + " now"); + }, + iceState: function (p_state) { + Janus.log("ICE state changed to " + p_state); + }, + mediaState: function (p_medium, p_on, p_mid) { + Janus.log("Janus " + (p_on ? "started" : "stopped") + " receiving our " + p_medium + " (mid=" + p_mid + ")"); + }, + webrtcState: function (p_on) { + Janus.log("Janus WebRTC peer connection is " + (p_on ? "up" : "down") + " now"); + $("#streamscreen").parent().parent().unblock(); + if (p_on) { + Janus.log("your screen sharing session just started: pass the \"" + room + "\" session identifier to those who want to attend."); + _on_screen_shared(room); + } else { + Janus.log("your screen sharing session just stopped"); + janus.destroy(); + } + }, + slowLink: function (p_uplink, p_lost, p_mid) { + Janus.log("Janus reports problems " + (p_uplink ? "sending" : "receiving") + " packets on mid " + p_mid + " (" + p_lost + " lost packets)"); + }, + onmessage: function (p_msg, p_jsep) { + Janus.debug("got a message (publisher): ", p_msg); + var _event = p_msg["videoroom"]; + Janus.debug("event: " + _event); + if (_event) { + if (_event === "joined") { + id = p_msg["id"]; + Janus.log("successfully joined room " + p_msg["room"] + " with ID " + id); + if (role === "publisher") { + // this is our session, publish our stream + Janus.debug("negotiating WebRTC stream for our screen (capture " + capture + ")"); + // safari expects a user gesture to share the screen + if (Janus.webRTCAdapter.browserDetails.browser === "safari") { + window.alert("Safari requires a user gesture before the screen can be shared: close this dialog to do that"); + screen.createOffer( + { + // we want to capture the screen and audio, but sendonly + tracks: [ + { type: 'audio', capture: true, recv: false }, + { type: 'screen', capture: true, recv: false } + ], + success: function (p_jsep) { + Janus.debug("Got publisher SDP!", p_jsep); + var _publish = { request: "configure", audio: true, video: true }; + screen.send({ message: _publish, jsep: p_jsep }); + }, + error: function (p_error) { + Janus.error("WebRTC error:", p_error.message); + } + }); + } else { + // other browsers should be fine, we try to call getDisplayMedia directly + screen.createOffer( + { + // we want sendonly audio and screensharing + tracks: [ + { type: 'audio', capture: true, recv: false }, + { type: 'screen', capture: true, recv: false } + ], + success: function (p_jsep) { + Janus.debug("got publisher SDP!", p_jsep); + var _publish = { request: "configure", audio: true, video: true }; + screen.send({ message: _publish, jsep: p_jsep }); + }, + error: function (p_error) { + Janus.error("WebRTC error:", p_error); + } + }); + } + } else { + // we're just watching a session, any feed to attach to? + if (p_msg["publishers"]) { + var _list = p_msg["publishers"]; + Janus.debug("got a list of available publishers/feeds:", _list); + for (var f in _list) { + if (_list[f]["dummy"]) continue; + var _id = _list[f]["id"]; + var _display = _list[f]["display"]; + Janus.debug(" >> [" + _id + "] " + _display); + new_remote_feed(_id, _display) + } + } + } + } else if (_event === "event") { + // any feed to attach to? + if (role === "listener" && p_msg["publishers"]) { + var _list = p_msg["publishers"]; + Janus.debug("got a list of available publishers/feeds:", _list); + for (var f in _list) { + if (_list[f]["dummy"]) continue; + var _id = _list[f]["id"]; + var _display = _list[f]["display"]; + Janus.debug(" >> [" + _id + "] " + _display); + new_remote_feed(_id, _display) + } + } else if (p_msg["leaving"]) { + // one of the publishers has gone away? + var _leaving = p_msg["leaving"]; + Janus.log("publisher left: " + _leaving); + if (role === "listener" && p_msg["leaving"] === source) { + Janus.error("the screen sharing session is over, the publisher left"); + } + } else if (p_msg["error"]) { + Janus.error(p_msg["error"]); + } + } + } + if (p_jsep) { + Janus.debug("handling SDP as well ", p_jsep); + screen.handleRemoteJsep({ jsep: p_jsep }); + } + }, + onlocaltrack: function (p_track, p_on) { + Janus.debug("local track " + (p_on ? "added" : "removed") + ":", p_track); + // we use the track ID as name of the element, but it may contain invalid characters + var _track_id = p_track.id.replace(/[{}]/g, ""); + if (!p_on) { + // track removed, get rid of the stream and the rendering + var _stream = local_tracks[_track_id]; + if (_stream) { + try { + var _tracks = _stream.getTracks(); + for (var _t in _tracks) { + var _mst = _tracks[_t]; + if (_mst) { + _mst.stop(); + } + } + } catch (p_e) { } + } + if (p_track.kind === "video") { + $('#screenvideo' + _track_id).remove(); + local_videos--; + if (local_videos === 0) { + // No video, at least for now: show a placeholder + if ($('#streamscreen .no-video-container').length === 0) { + Janus.log("No webcam available"); + } + } + } + delete local_tracks[_track_id]; + return; + } + // if we're here, a new track was added + var _stream = local_tracks[_track_id]; + if (_stream) { + // we've been here already + return; + } + if (p_track.kind === "audio") { + // we ignore local audio tracks, they'd generate echo anyway + if (local_videos === 0) { + // No video, at least for now: show a placeholder + if ($('#streamscreen .no-video-container').length === 0) { + Janus.log("No webcam available"); + } + } + } else { + // new video track: create a stream out of it + local_videos++; + $('#streamscreen .no-video-container').remove(); + _stream = new MediaStream([p_track]); + local_tracks[_track_id] = _stream; + Janus.log("Created local stream:", _stream); + $('#streamscreen').append('