├── .clang-format ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ └── bug-report.md ├── renovate.json └── workflows │ └── build.yml ├── .gitignore ├── .vscode ├── c_cpp_properties.json ├── extensions.json └── settings.json ├── CHANGES.md ├── CMakeLists.txt ├── LICENSE ├── NOTICE.md ├── README.md ├── Sora ├── Editor │ └── SoraUnitySdkPostProcessor.cs └── Sora.cs ├── VERSION ├── buildbase.py ├── canary.py ├── proto ├── sora_conf.proto └── sora_conf_internal.proto ├── ruff.toml ├── run.py └── src ├── android_helper ├── android_context.cpp ├── android_context.h └── jni_onload.cc ├── converter.cpp ├── converter.h ├── device_list.cpp ├── device_list.h ├── id_pointer.cpp ├── id_pointer.h ├── mac_helper ├── ios_audio_init.h └── ios_audio_init.mm ├── sora.cpp ├── sora.h ├── sora_version.h.template ├── unity.cpp ├── unity.h ├── unity ├── IUnityGraphics.h ├── IUnityGraphicsD3D11.h ├── IUnityGraphicsMetal.h ├── IUnityGraphicsVulkan.h ├── IUnityInterface.h └── IUnityRenderingExtensions.h ├── unity_audio_device.h ├── unity_camera_capturer.cpp ├── unity_camera_capturer.h ├── unity_camera_capturer_d3d11.cpp ├── unity_camera_capturer_metal.mm ├── unity_camera_capturer_opengl.cpp ├── unity_camera_capturer_vulkan.cpp ├── unity_context.cpp ├── unity_context.h ├── unity_renderer.cpp └── unity_renderer.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Chromium 4 | DerivePointerAlignment: false 5 | PointerAlignment: Left 6 | ReflowComments: false 7 | --- 8 | Language: ObjC 9 | BasedOnStyle: Chromium 10 | DerivePointerAlignment: false 11 | PointerAlignment: Left 12 | ReflowComments: false 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | doc/* linguist-documentation 2 | src/unity/* linguist-vendored 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: 改善に役立つレポートを作成する 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Discord ID を教えて下さい** 11 | 12 | Discord の ID を教えて下さい。 13 | 14 | **バグを説明してください** 15 | 16 | バグが何であるかの明確かつ簡潔な説明をお願いします。 17 | 18 | **再現する** 19 | 20 | 問題の再現手順を教えてください。 21 | 22 | **期待する動作** 23 | 24 | 何が起こることが期待されていたのか、明確で簡潔な説明をお願いします。 25 | 26 | **実際の動作** 27 | 28 | 何が起きたのかについての明確で簡潔な説明をお願いします。 29 | 30 | **カメラ/マイク** 31 | 32 | 利用しているカメラやマイクの型番を教えてください。 33 | 34 | **Sora Unity SDK のバージョン** 35 | 36 | 利用している Sora Unity SDK のバージョンを教えてください。自前バイナリの方はビルドしたコミットハッシュを教えてください。 37 | 38 | **Unity のバージョン** 39 | 40 | 利用している Unity のバージョンを教えてください。 41 | 42 | **利用している OS のバージョン** 43 | 44 | 利用している OS のバージョンを教えてください。 Android をご利用の方は端末の機種名も教えてください。 45 | 46 | **NVIDIA ドライバーのバージョン** 47 | 48 | NVIDIA VIDEO CODEC SDK をご利用の場合は、 NVIDIA ドライバーのバージョンを教えてください。 49 | 50 | **webrtc_logs_0 の出力ログ** 51 | 52 | txt ファイルにて添付してください。 53 | 54 | **追加の情報** 55 | 56 | この問題に関する他の情報をここに追加してください。 57 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base", 5 | ":timezone(Asia/Tokyo)", 6 | ":combinePatchMinorReleases", 7 | ":prHourlyLimitNone", 8 | ":prConcurrentLimit10", 9 | "group:recommended", 10 | "group:allNonMajor", 11 | "schedule:weekly" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'doc/**' 7 | - '**.md' 8 | schedule: 9 | - cron: "0 0 * * *" 10 | 11 | jobs: 12 | build-windows: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | name: 17 | - windows_x86_64 18 | runs-on: windows-2022 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Cache 22 | id: cache 23 | uses: actions/cache@v4 24 | with: 25 | path: _install 26 | key: ${{ matrix.name }}-v3-${{ hashFiles('VERSIONS') }} 27 | restore-keys: | 28 | ${{ matrix.name }}-v3- 29 | - run: python3 run.py ${{ matrix.name }} 30 | - name: Copy ${{ matrix.name }} files 31 | run: | 32 | mkdir ${{ matrix.name }} 33 | Copy-Item _build\windows_x86_64\release\sora_unity_sdk\Release\SoraUnitySdk.dll ${{ matrix.name }} 34 | # ソースコード(生成した分も含む)も一緒に入れておく 35 | Copy-Item -Recurse Sora ${{ matrix.name }}\Sora 36 | - name: Upload ${{ matrix.name }} Artifact 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: ${{ matrix.name }} 40 | path: ${{ matrix.name }} 41 | build-macos: 42 | strategy: 43 | fail-fast: false 44 | matrix: 45 | name: 46 | - macos_arm64 47 | - ios 48 | runs-on: macos-14 49 | steps: 50 | - uses: actions/checkout@v4 51 | - name: Cache 52 | id: cache 53 | uses: actions/cache@v4 54 | with: 55 | path: _install 56 | key: ${{ matrix.name }}-v7-${{ hashFiles('VERSIONS') }} 57 | restore-keys: | 58 | ${{ matrix.name }}-v7- 59 | - run: python3 run.py ${{ matrix.name }} 60 | # macOS 用 61 | - name: Copy macOS arm64 files 62 | if: matrix.name == 'macos_arm64' 63 | run: | 64 | mkdir macos_arm64/ 65 | cp -r _build/macos_arm64/release/sora_unity_sdk/SoraUnitySdk.bundle/ macos_arm64/SoraUnitySdk.bundle/ 66 | - name: Upload macOS arm64 Artifact 67 | if: matrix.name == 'macos_arm64' 68 | uses: actions/upload-artifact@v4 69 | with: 70 | name: macos_arm64 71 | path: macos_arm64/ 72 | # iOS 用 73 | - name: Copy iOS files 74 | if: matrix.name == 'ios' 75 | run: | 76 | mkdir ios/ 77 | cp _install/ios/release/webrtc/lib/libwebrtc.a ios/ 78 | cp _install/ios/release/boost/lib/libboost_json.a ios/ 79 | cp _install/ios/release/boost/lib/libboost_filesystem.a ios/ 80 | cp _install/ios/release/sora/lib/libsora.a ios/ 81 | cp _build/ios/release/sora_unity_sdk/libSoraUnitySdk.a ios/ 82 | - name: Upload iOS Artifact 83 | if: matrix.name == 'ios' 84 | uses: actions/upload-artifact@v4 85 | with: 86 | name: ios 87 | path: ios/ 88 | build-ubuntu: 89 | strategy: 90 | fail-fast: false 91 | matrix: 92 | include: 93 | - name: android 94 | runs-on: ubuntu-22.04 95 | - name: ubuntu-22.04_x86_64 96 | runs-on: ubuntu-22.04 97 | - name: ubuntu-24.04_x86_64 98 | runs-on: ubuntu-24.04 99 | runs-on: ${{ matrix.runs-on }} 100 | steps: 101 | - uses: actions/checkout@v4 102 | - name: Cache 103 | id: cache 104 | uses: actions/cache@v4 105 | with: 106 | path: _install 107 | key: ${{ matrix.name }}-v1-${{ hashFiles('VERSIONS') }} 108 | restore-keys: | 109 | ${{ matrix.name }}-v1- 110 | - name: Prepare Ubuntu x86_64 111 | if: matrix.name == 'ubuntu-22.04_x86_64' || matrix.name == 'ubuntu-24.04_x86_64' 112 | run: | 113 | sudo apt install libgl-dev 114 | - run: python3 run.py ${{ matrix.name }} 115 | # Android 用 116 | - name: Copy Android files 117 | if: matrix.name == 'android' 118 | run: | 119 | mkdir android/ 120 | cp _install/android/release/webrtc/jar/webrtc.jar android/ 121 | cp _install/android/release/sora/lib/Sora.aar android/ 122 | cp _build/android/release/sora_unity_sdk/libSoraUnitySdk.so android/ 123 | - name: Upload Android Artifact 124 | if: matrix.name == 'android' 125 | uses: actions/upload-artifact@v4 126 | with: 127 | name: android 128 | path: android/ 129 | # Ubuntu 用 130 | - name: Copy Ubuntu 22.04 x86_64 files 131 | if: matrix.name == 'ubuntu-22.04_x86_64' 132 | run: | 133 | mkdir ubuntu-22.04_x86_64/ 134 | cp _build/ubuntu-22.04_x86_64/release/sora_unity_sdk/libSoraUnitySdk.so ubuntu-22.04_x86_64/ 135 | - name: Upload Ubuntu x86_64 Artifact 136 | if: matrix.name == 'ubuntu-22.04_x86_64' 137 | uses: actions/upload-artifact@v4 138 | with: 139 | name: ubuntu-22.04_x86_64 140 | path: ubuntu-22.04_x86_64/ 141 | - name: Copy Ubuntu 24.04 x86_64 files 142 | if: matrix.name == 'ubuntu-24.04_x86_64' 143 | run: | 144 | mkdir ubuntu-24.04_x86_64/ 145 | cp _build/ubuntu-24.04_x86_64/release/sora_unity_sdk/libSoraUnitySdk.so ubuntu-24.04_x86_64/ 146 | - name: Upload Ubuntu 24.04 x86_64 Artifact 147 | if: matrix.name == 'ubuntu-24.04_x86_64' 148 | uses: actions/upload-artifact@v4 149 | with: 150 | name: ubuntu-24.04_x86_64 151 | path: ubuntu-24.04_x86_64/ 152 | package: 153 | runs-on: ubuntu-24.04 154 | needs: 155 | - build-windows 156 | - build-macos 157 | - build-ubuntu 158 | steps: 159 | - uses: actions/checkout@v4 160 | - name: Download artifacts 161 | uses: actions/download-artifact@v4 162 | - name: Packaging 163 | run: | 164 | set -ex 165 | mkdir -p SoraUnitySdk/Plugins/SoraUnitySdk/windows/x86_64 166 | mkdir -p SoraUnitySdk/Plugins/SoraUnitySdk/macos/arm64 167 | mkdir -p SoraUnitySdk/Plugins/SoraUnitySdk/ios 168 | mkdir -p SoraUnitySdk/Plugins/SoraUnitySdk/android/arm64-v8a 169 | mkdir -p SoraUnitySdk/Plugins/SoraUnitySdk/ubuntu-22.04/x86_64 170 | mkdir -p SoraUnitySdk/Plugins/SoraUnitySdk/ubuntu-24.04/x86_64 171 | mkdir -p SoraUnitySdk/StreamingAssets/SoraUnitySdk 172 | cp windows_x86_64/SoraUnitySdk.dll SoraUnitySdk/Plugins/SoraUnitySdk/windows/x86_64/ 173 | cp -r windows_x86_64/Sora/ SoraUnitySdk/SoraUnitySdk/ 174 | cp -r macos_arm64/SoraUnitySdk.bundle SoraUnitySdk/Plugins/SoraUnitySdk/macos/arm64/SoraUnitySdk.bundle 175 | cp android/libSoraUnitySdk.so SoraUnitySdk/Plugins/SoraUnitySdk/android/arm64-v8a/ 176 | cp android/webrtc.jar SoraUnitySdk/Plugins/SoraUnitySdk/android/ 177 | cp android/Sora.aar SoraUnitySdk/Plugins/SoraUnitySdk/android/ 178 | cp ios/libSoraUnitySdk.a SoraUnitySdk/Plugins/SoraUnitySdk/ios/ 179 | cp ios/libwebrtc.a SoraUnitySdk/Plugins/SoraUnitySdk/ios/ 180 | cp ios/libboost_json.a SoraUnitySdk/Plugins/SoraUnitySdk/ios/ 181 | cp ios/libsora.a SoraUnitySdk/Plugins/SoraUnitySdk/ios/ 182 | cp ios/libboost_filesystem.a SoraUnitySdk/Plugins/SoraUnitySdk/ios/ 183 | cp ubuntu-22.04_x86_64/libSoraUnitySdk.so SoraUnitySdk/Plugins/SoraUnitySdk/ubuntu-22.04/x86_64/ 184 | cp ubuntu-24.04_x86_64/libSoraUnitySdk.so SoraUnitySdk/Plugins/SoraUnitySdk/ubuntu-24.04/x86_64/ 185 | cp LICENSE SoraUnitySdk/SoraUnitySdk/ 186 | cp NOTICE.md SoraUnitySdk/SoraUnitySdk/ 187 | 188 | - name: Upload SoraUnitySdk 189 | uses: actions/upload-artifact@v4 190 | with: 191 | name: SoraUnitySdk 192 | path: SoraUnitySdk 193 | release: 194 | name: Upload Release Asset 195 | if: contains(github.ref, 'tags/202') 196 | needs: [package] 197 | runs-on: ubuntu-24.04 198 | steps: 199 | - name: Checkout code 200 | uses: actions/checkout@v4 201 | - name: Download SoraUnitySdk 202 | uses: actions/download-artifact@v4 203 | with: 204 | name: SoraUnitySdk 205 | path: SoraUnitySdk 206 | - name: Archive to zip SoraUnitySdk 207 | run: | 208 | zip -r SoraUnitySdk SoraUnitySdk 209 | - name: Create Release 210 | id: create_release 211 | uses: actions/create-release@v1 212 | env: 213 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 214 | with: 215 | tag_name: ${{ github.ref }} 216 | release_name: Release ${{ github.ref }} 217 | draft: false 218 | prerelease: ${{ contains(github.ref, 'canary') }} 219 | - name: Upload Release Asset 220 | id: upload-release-asset 221 | uses: actions/upload-release-asset@v1 222 | env: 223 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 224 | with: 225 | upload_url: ${{ steps.create_release.outputs.upload_url }} 226 | asset_path: ./SoraUnitySdk.zip 227 | asset_name: SoraUnitySdk.zip 228 | asset_content_type: application/zip 229 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /_source 2 | /_build 3 | /_install 4 | *.swp 5 | /Sora/Generated 6 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Windows Release", 5 | "includePath": [ 6 | "${workspaceFolder}/src", 7 | "${workspaceFolder}/_install/windows_x86_64/release/sora/include", 8 | "${workspaceFolder}/_install/windows_x86_64/release/boost/include", 9 | "${workspaceFolder}/_install/windows_x86_64/release/webrtc/include", 10 | "${workspaceFolder}/_install/windows_x86_64/release/webrtc/include/third_party/zlib", 11 | "${workspaceFolder}/_install/windows_x86_64/release/webrtc/include/third_party/abseil-cpp", 12 | "${workspaceFolder}/_install/windows_x86_64/release/webrtc/include/third_party/boringssl/src/include", 13 | "${workspaceFolder}/_install/windows_x86_64/release/webrtc/include/third_party/libyuv/include", 14 | "${workspaceFolder}/_build/windows_x86_64/release/sora_unity_sdk", 15 | "${workspaceFolder}/_build/windows_x86_64/release/sora_unity_sdk/proto/cpp" 16 | ], 17 | "defines": [ 18 | "_DEBUG", 19 | "UNICODE", 20 | "_UNICODE", 21 | "_CONSOLE", 22 | "_WIN32_WINNT=0x0A00", 23 | "WEBRTC_WIN", 24 | "NOMINMAX", 25 | "WIN32_LEAN_AND_MEAN", 26 | "SORA_UNITY_SDK_WINDOWS" 27 | ], 28 | "cStandard": "c11", 29 | "cppStandard": "c++17", 30 | "intelliSenseMode": "msvc-x64" 31 | }, 32 | { 33 | "name": "macOS arm64 Release", 34 | "includePath": [ 35 | "${workspaceFolder}/src", 36 | "${workspaceFolder}/_install/macos_arm64/release/sora/include", 37 | "${workspaceFolder}/_install/macos_arm64/release/boost/include", 38 | "${workspaceFolder}/_install/macos_arm64/release/webrtc/include", 39 | "${workspaceFolder}/_install/macos_arm64/release/webrtc/include/third_party/zlib", 40 | "${workspaceFolder}/_install/macos_arm64/release/webrtc/include/third_party/abseil-cpp", 41 | "${workspaceFolder}/_install/macos_arm64/release/webrtc/include/third_party/boringssl/src/include", 42 | "${workspaceFolder}/_install/macos_arm64/release/webrtc/include/third_party/libyuv/include", 43 | "${workspaceFolder}/_install/macos_arm64/release/webrtc/include/sdk/objc", 44 | "${workspaceFolder}/_build/macos_arm64/release/sora_unity_sdk", 45 | "${workspaceFolder}/_build/macos_arm64/release/sora_unity_sdk/proto/cpp" 46 | ], 47 | "defines": [ 48 | "WEBRTC_POSIX", 49 | "WEBRTC_MAC", 50 | "SORA_UNITY_SDK_MACOS" 51 | ], 52 | "cStandard": "c11", 53 | "cppStandard": "c++17", 54 | "intelliSenseMode": "msvc-x64" 55 | }, 56 | { 57 | "name": "Adnroid Release", 58 | "includePath": [ 59 | "${workspaceFolder}/src", 60 | "${workspaceFolder}/_install/android/release/sora/include", 61 | "${workspaceFolder}/_install/android/release/boost/include", 62 | "${workspaceFolder}/_install/android/release/webrtc/include", 63 | "${workspaceFolder}/_install/android/release/webrtc/include/third_party/zlib", 64 | "${workspaceFolder}/_install/android/release/webrtc/include/third_party/abseil-cpp", 65 | "${workspaceFolder}/_install/android/release/webrtc/include/third_party/boringssl/src/include", 66 | "${workspaceFolder}/_install/android/release/webrtc/include/third_party/libyuv/include", 67 | "${workspaceFolder}/_install/android/release/webrtc/include/sdk/android", 68 | "${workspaceFolder}/_install/android/release/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include", 69 | "${workspaceFolder}/_build/android/release/sora_unity_sdk", 70 | "${workspaceFolder}/_build/android/release/sora_unity_sdk/proto/cpp" 71 | ], 72 | "defines": [ 73 | "WEBRTC_POSIX", 74 | "WEBRTC_LINUX", 75 | "WEBRTC_ANDROID", 76 | "SORA_UNITY_SDK_ANDROID" 77 | ], 78 | "cStandard": "c11", 79 | "cppStandard": "c++17", 80 | "intelliSenseMode": "clang-x64" 81 | }, 82 | { 83 | "name": "ubuntu-22.04_x86_64 Release", 84 | "includePath": [ 85 | "${workspaceFolder}/src", 86 | "${workspaceFolder}/_install/ubuntu-22.04_x86_64/release/sora/include", 87 | "${workspaceFolder}/_install/ubuntu-22.04_x86_64/release/boost/include", 88 | "${workspaceFolder}/_install/ubuntu-22.04_x86_64/release/webrtc/include", 89 | "${workspaceFolder}/_install/ubuntu-22.04_x86_64/release/webrtc/include/third_party/zlib", 90 | "${workspaceFolder}/_install/ubuntu-22.04_x86_64/release/webrtc/include/third_party/abseil-cpp", 91 | "${workspaceFolder}/_install/ubuntu-22.04_x86_64/release/webrtc/include/third_party/boringssl/src/include", 92 | "${workspaceFolder}/_install/ubuntu-22.04_x86_64/release/webrtc/include/third_party/libyuv/include", 93 | "${workspaceFolder}/_build/ubuntu-22.04_x86_64/release/sora_unity_sdk", 94 | "${workspaceFolder}/_build/ubuntu-22.04_x86_64/release/sora_unity_sdk/proto/cpp" 95 | ], 96 | "defines": [ 97 | "WEBRTC_POSIX", 98 | "WEBRTC_LINUX", 99 | "SORA_UNITY_SDK_UBUNTU" 100 | ], 101 | "cStandard": "c11", 102 | "cppStandard": "c++17", 103 | "intelliSenseMode": "clang-x64" 104 | } 105 | ], 106 | "version": 4 107 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["charliermarsh.ruff", "ms-python.mypy-type-checker"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.analysis.autoImportCompletions": true, 3 | "python.analysis.typeCheckingMode": "basic", 4 | "[python]": { 5 | "editor.formatOnSave": true, 6 | "editor.formatOnSaveMode": "file", 7 | "editor.defaultFormatter": "charliermarsh.ruff", 8 | "editor.autoIndent": "full", 9 | "editor.codeActionsOnSave": { 10 | "source.fixAll.ruff": "always", 11 | "source.organizeImports.ruff": "explicit" 12 | } 13 | }, 14 | "[cpp]": { 15 | "editor.formatOnSave": true, 16 | }, 17 | "files.associations": { 18 | "*.cs": "csharp", 19 | "memory": "cpp", 20 | "utility": "cpp", 21 | "xmemory": "cpp", 22 | "thread": "cpp", 23 | "algorithm": "cpp", 24 | "any": "cpp", 25 | "array": "cpp", 26 | "atomic": "cpp", 27 | "bitset": "cpp", 28 | "cctype": "cpp", 29 | "cfenv": "cpp", 30 | "charconv": "cpp", 31 | "chrono": "cpp", 32 | "cinttypes": "cpp", 33 | "clocale": "cpp", 34 | "cmath": "cpp", 35 | "codecvt": "cpp", 36 | "compare": "cpp", 37 | "complex": "cpp", 38 | "concepts": "cpp", 39 | "condition_variable": "cpp", 40 | "csetjmp": "cpp", 41 | "csignal": "cpp", 42 | "cstdarg": "cpp", 43 | "cstddef": "cpp", 44 | "cstdint": "cpp", 45 | "cstdio": "cpp", 46 | "cstdlib": "cpp", 47 | "cstring": "cpp", 48 | "ctime": "cpp", 49 | "cwchar": "cpp", 50 | "cwctype": "cpp", 51 | "deque": "cpp", 52 | "exception": "cpp", 53 | "execution": "cpp", 54 | "resumable": "cpp", 55 | "filesystem": "cpp", 56 | "forward_list": "cpp", 57 | "fstream": "cpp", 58 | "functional": "cpp", 59 | "future": "cpp", 60 | "initializer_list": "cpp", 61 | "iomanip": "cpp", 62 | "ios": "cpp", 63 | "iosfwd": "cpp", 64 | "iostream": "cpp", 65 | "istream": "cpp", 66 | "iterator": "cpp", 67 | "limits": "cpp", 68 | "list": "cpp", 69 | "locale": "cpp", 70 | "map": "cpp", 71 | "memory_resource": "cpp", 72 | "mutex": "cpp", 73 | "new": "cpp", 74 | "numeric": "cpp", 75 | "optional": "cpp", 76 | "ostream": "cpp", 77 | "queue": "cpp", 78 | "random": "cpp", 79 | "ratio": "cpp", 80 | "regex": "cpp", 81 | "scoped_allocator": "cpp", 82 | "set": "cpp", 83 | "shared_mutex": "cpp", 84 | "sstream": "cpp", 85 | "stack": "cpp", 86 | "stdexcept": "cpp", 87 | "streambuf": "cpp", 88 | "string": "cpp", 89 | "strstream": "cpp", 90 | "system_error": "cpp", 91 | "tuple": "cpp", 92 | "type_traits": "cpp", 93 | "typeindex": "cpp", 94 | "typeinfo": "cpp", 95 | "unordered_map": "cpp", 96 | "unordered_set": "cpp", 97 | "valarray": "cpp", 98 | "variant": "cpp", 99 | "vector": "cpp", 100 | "xfacet": "cpp", 101 | "xhash": "cpp", 102 | "xiosbase": "cpp", 103 | "xlocale": "cpp", 104 | "xlocbuf": "cpp", 105 | "xlocinfo": "cpp", 106 | "xlocmes": "cpp", 107 | "xlocmon": "cpp", 108 | "xlocnum": "cpp", 109 | "xloctime": "cpp", 110 | "xstddef": "cpp", 111 | "xstring": "cpp", 112 | "xtr1common": "cpp", 113 | "xtree": "cpp", 114 | "xutility": "cpp", 115 | "*.ipp": "cpp", 116 | "hash_map": "cpp", 117 | "hash_set": "cpp" 118 | } 119 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | # Only interpret if() arguments as variables or keywords when unquoted. 4 | cmake_policy(SET CMP0054 NEW) 5 | # MSVC runtime library flags are selected by an abstraction. 6 | cmake_policy(SET CMP0091 NEW) 7 | 8 | project(SoraUnitySdk C CXX) 9 | 10 | list(APPEND CMAKE_PREFIX_PATH ${SORA_DIR}) 11 | list(APPEND CMAKE_MODULE_PATH ${SORA_DIR}/share/cmake) 12 | 13 | set(Boost_USE_STATIC_LIBS ON) 14 | if (WIN32) 15 | set(Boost_USE_STATIC_RUNTIME ON) 16 | endif() 17 | 18 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER) 19 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) 20 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER) 21 | 22 | find_package(Boost REQUIRED COMPONENTS json filesystem) 23 | find_package(WebRTC REQUIRED) 24 | find_package(Sora REQUIRED) 25 | find_package(Threads REQUIRED) 26 | 27 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 28 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 29 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 30 | 31 | if (SORA_UNITY_SDK_PACKAGE STREQUAL "windows_x86_64") 32 | add_library(SoraUnitySdk SHARED) 33 | 34 | # configure_file で使う 35 | set(SORA_UNITY_SDK_PLATFORM Windows) 36 | elseif (SORA_UNITY_SDK_PACKAGE STREQUAL "macos_x86_64" OR SORA_UNITY_SDK_PACKAGE STREQUAL "macos_arm64") 37 | add_library(SoraUnitySdk MODULE) 38 | set_target_properties(SoraUnitySdk PROPERTIES BUNDLE TRUE) 39 | set_target_properties(SoraUnitySdk PROPERTIES CXX_VISIBILITY_PRESET hidden) 40 | target_link_options(SoraUnitySdk PRIVATE -ObjC) 41 | 42 | # configure_file で使う 43 | set(SORA_UNITY_SDK_PLATFORM macOS) 44 | elseif (SORA_UNITY_SDK_PACKAGE STREQUAL "ios") 45 | add_library(SoraUnitySdk) 46 | set_target_properties(SoraUnitySdk PROPERTIES CXX_VISIBILITY_PRESET hidden) 47 | target_link_options(SoraUnitySdk PRIVATE -ObjC) 48 | 49 | set(SORA_UNITY_SDK_PLATFORM iOS) 50 | elseif (SORA_UNITY_SDK_PACKAGE STREQUAL "android") 51 | add_library(SoraUnitySdk SHARED) 52 | 53 | set(SORA_UNITY_SDK_PLATFORM Android) 54 | elseif (SORA_UNITY_SDK_PACKAGE STREQUAL "ubuntu-22.04_x86_64") 55 | add_library(SoraUnitySdk SHARED) 56 | 57 | set(SORA_UNITY_SDK_PLATFORM Ubuntu) 58 | elseif (SORA_UNITY_SDK_PACKAGE STREQUAL "ubuntu-24.04_x86_64") 59 | add_library(SoraUnitySdk SHARED) 60 | 61 | set(SORA_UNITY_SDK_PLATFORM Ubuntu) 62 | endif() 63 | 64 | set_target_properties(SoraUnitySdk PROPERTIES CXX_STANDARD 20 C_STANDARD 99) 65 | 66 | target_sources(SoraUnitySdk 67 | PRIVATE 68 | src/converter.cpp 69 | src/device_list.cpp 70 | src/id_pointer.cpp 71 | src/sora.cpp 72 | src/unity_camera_capturer.cpp 73 | src/unity_context.cpp 74 | src/unity_renderer.cpp 75 | src/unity.cpp 76 | ) 77 | 78 | file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/proto") 79 | file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/proto/cpp") 80 | file(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/Sora/Generated") 81 | if (WIN32) 82 | set(EXE_EXT ".exe") 83 | endif() 84 | add_custom_command( 85 | OUTPUT 86 | "${CMAKE_CURRENT_BINARY_DIR}/proto/cpp/sora_conf.json.h" 87 | "${CMAKE_CURRENT_BINARY_DIR}/proto/cpp/sora_conf_internal.json.h" 88 | "${CMAKE_CURRENT_SOURCE_DIR}/Sora/Generated/SoraConf.cs" 89 | "${CMAKE_CURRENT_SOURCE_DIR}/Sora/Generated/SoraConfInternal.cs" 90 | "${CMAKE_CURRENT_SOURCE_DIR}/Sora/Generated/Jsonif.cs" 91 | COMMAND 92 | "${PROTOBUF_DIR}/bin/protoc${EXE_EXT}" 93 | ARGS 94 | --jsonif-cpp_out "${CMAKE_CURRENT_BINARY_DIR}/proto/cpp" 95 | --jsonif-unity_out "${CMAKE_CURRENT_SOURCE_DIR}/Sora/Generated" 96 | --plugin=protoc-gen-jsonif-cpp="${PROTOC_GEN_JSONIF_DIR}/bin/protoc-gen-jsonif-cpp${EXE_EXT}" 97 | --plugin=protoc-gen-jsonif-unity="${PROTOC_GEN_JSONIF_DIR}/bin/protoc-gen-jsonif-unity${EXE_EXT}" 98 | -I "${CMAKE_CURRENT_SOURCE_DIR}/proto/" 99 | "${CMAKE_CURRENT_SOURCE_DIR}/proto/sora_conf.proto" 100 | "${CMAKE_CURRENT_SOURCE_DIR}/proto/sora_conf_internal.proto" 101 | DEPENDS 102 | "${CMAKE_CURRENT_SOURCE_DIR}/proto/sora_conf.proto" 103 | "${CMAKE_CURRENT_SOURCE_DIR}/proto/sora_conf_internal.proto" 104 | ) 105 | add_custom_target(sora_conf.json.h 106 | DEPENDS 107 | "${CMAKE_CURRENT_BINARY_DIR}/proto/cpp/sora_conf.json.h" 108 | "${CMAKE_CURRENT_BINARY_DIR}/proto/cpp/sora_conf_internal.json.h" 109 | ) 110 | add_dependencies(SoraUnitySdk sora_conf.json.h) 111 | target_include_directories(SoraUnitySdk PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/proto/cpp") 112 | 113 | string(SUBSTRING ${SORA_UNITY_SDK_COMMIT} 0 8 SORA_UNITY_SDK_COMMIT_SHORT) 114 | string(SUBSTRING ${WEBRTC_COMMIT} 0 8 WEBRTC_COMMIT_SHORT) 115 | 116 | configure_file(src/sora_version.h.template ${CMAKE_CURRENT_BINARY_DIR}/sora_version.h) 117 | 118 | target_include_directories(SoraUnitySdk PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 119 | 120 | target_link_libraries(SoraUnitySdk PRIVATE Sora::sora) 121 | 122 | if (SORA_UNITY_SDK_PACKAGE STREQUAL "windows_x86_64") 123 | # 文字コードを utf-8 として扱うのと、シンボルテーブル数を増やす 124 | target_compile_options(SoraUnitySdk 125 | PRIVATE 126 | "$<$:/utf-8;/bigobj>" 127 | ) 128 | set_target_properties(SoraUnitySdk 129 | PROPERTIES 130 | # CRTライブラリを静的リンクさせる 131 | MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" 132 | ) 133 | 134 | target_sources(SoraUnitySdk PRIVATE src/unity_camera_capturer_d3d11.cpp) 135 | 136 | #target_link_libraries(SoraUnitySdk 137 | # PRIVATE 138 | # dbghelp.lib 139 | # delayimp.lib 140 | # dnsapi.lib 141 | # msimg32.lib 142 | # oleaut32.lib 143 | # psapi.lib 144 | # shell32.lib 145 | # shlwapi.lib 146 | # usp10.lib 147 | # version.lib 148 | # wininet.lib 149 | # winmm.lib 150 | # ws2_32.lib 151 | # amstrmid.lib 152 | # Strmiids.lib 153 | # crypt32.lib 154 | # dmoguids.lib 155 | # iphlpapi.lib 156 | # msdmo.lib 157 | # Secur32.lib 158 | # wmcodecdspuuid.lib 159 | # dxgi.lib 160 | # D3D11.lib 161 | #) 162 | 163 | target_compile_definitions(SoraUnitySdk 164 | PRIVATE 165 | SORA_UNITY_SDK_WINDOWS 166 | _WIN32_WINNT=0x0A00 167 | NOMINMAX 168 | WIN32_LEAN_AND_MEAN 169 | ) 170 | elseif (SORA_UNITY_SDK_PACKAGE STREQUAL "macos_x86_64" OR SORA_UNITY_SDK_PACKAGE STREQUAL "macos_arm64") 171 | enable_language(OBJCXX) 172 | 173 | target_sources(SoraUnitySdk PRIVATE src/unity_camera_capturer_metal.mm) 174 | 175 | #target_link_libraries(SoraUnitySdk 176 | # PRIVATE 177 | # "-framework Foundation" 178 | # "-framework AVFoundation" 179 | # "-framework CoreServices" 180 | # "-framework CoreFoundation" 181 | # "-framework AudioUnit" 182 | # "-framework AudioToolbox" 183 | # "-framework CoreAudio" 184 | # "-framework CoreGraphics" 185 | # "-framework CoreMedia" 186 | # "-framework CoreVideo" 187 | # "-framework QuartzCore" 188 | # "-framework VideoToolbox" 189 | # "-framework AppKit" 190 | # "-framework Metal" 191 | # "-framework MetalKit" 192 | # "-framework OpenGL" 193 | #) 194 | 195 | target_compile_definitions(SoraUnitySdk 196 | PRIVATE 197 | SORA_UNITY_SDK_MACOS 198 | ) 199 | 200 | elseif (SORA_UNITY_SDK_PACKAGE STREQUAL "ios") 201 | enable_language(OBJCXX) 202 | 203 | target_sources(SoraUnitySdk 204 | PRIVATE 205 | src/unity_camera_capturer_metal.mm 206 | src/mac_helper/ios_audio_init.mm 207 | ) 208 | 209 | target_compile_definitions(SoraUnitySdk 210 | PRIVATE 211 | SORA_UNITY_SDK_IOS 212 | ) 213 | install(TARGETS SoraUnitySdk DESTINATION lib) 214 | 215 | elseif (SORA_UNITY_SDK_PACKAGE STREQUAL "android") 216 | 217 | # ref: https://github.com/shiguredo/sora-cpp-sdk/blob/bfb7ed87f4d4c08212115dce00f4be6629ea65d4/test/android/app/src/main/cpp/CMakeLists.txt#L48-L56 218 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -B ${CLANG_DIR}/bin") 219 | 220 | find_library(ANDROID_LIB_LOG log) 221 | find_library(ANDROID_LIB_ANDROID android) 222 | find_library(ANDROID_LIB_OPENSLES OpenSLES) 223 | find_library(ANDROID_LIB_VULKAN vulkan) 224 | find_library(ANDROID_LIB_EGL EGL) 225 | find_library(ANDROID_LIB_GLESV2 GLESv2) 226 | 227 | target_sources(SoraUnitySdk 228 | PRIVATE 229 | src/android_helper/jni_onload.cc 230 | src/android_helper/android_context.cpp 231 | src/unity_camera_capturer_vulkan.cpp 232 | src/unity_camera_capturer_opengl.cpp 233 | ) 234 | 235 | target_compile_definitions(SoraUnitySdk 236 | PRIVATE 237 | SORA_UNITY_SDK_ANDROID 238 | ) 239 | target_compile_options(SoraUnitySdk PRIVATE "-nostdinc++") 240 | target_include_directories(SoraUnitySdk PRIVATE ${LIBCXX_INCLUDE_DIR}) 241 | 242 | target_link_libraries(SoraUnitySdk 243 | PRIVATE 244 | ${ANDROID_LIB_LOG} 245 | ${ANDROID_LIB_ANDROID} 246 | ${ANDROID_LIB_OPENSLES} 247 | ${ANDROID_LIB_VULKAN} 248 | ${ANDROID_LIB_EGL} 249 | ${ANDROID_LIB_GLESV2} 250 | ) 251 | file(READ ${SORA_DIR}/share/webrtc.ldflags _WEBRTC_ANDROID_LDFLAGS) 252 | string(REGEX REPLACE "\n" ";" _WEBRTC_ANDROID_LDFLAGS "${_WEBRTC_ANDROID_LDFLAGS}") 253 | target_link_options(SoraUnitySdk 254 | PRIVATE 255 | ${_WEBRTC_ANDROID_LDFLAGS} 256 | ) 257 | 258 | elseif (SORA_UNITY_SDK_PACKAGE STREQUAL "ubuntu-22.04_x86_64" OR SORA_UNITY_SDK_PACKAGE STREQUAL "ubuntu-24.04_x86_64") 259 | 260 | target_sources(SoraUnitySdk 261 | PRIVATE 262 | src/unity_camera_capturer_opengl.cpp 263 | ) 264 | 265 | target_compile_definitions(SoraUnitySdk PRIVATE SORA_UNITY_SDK_UBUNTU) 266 | target_compile_options(SoraUnitySdk PRIVATE "-nostdinc++") 267 | target_include_directories(SoraUnitySdk PRIVATE ${LIBCXX_INCLUDE_DIR}) 268 | 269 | endif() 270 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /NOTICE.md: -------------------------------------------------------------------------------- 1 | # WebRTC 2 | 3 | ``` 4 | Copyright (c) 2011, The WebRTC project authors. All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are 8 | met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | 18 | * Neither the name of Google nor the names of its contributors may 19 | be used to endorse or promote products derived from this software 20 | without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | ``` 34 | 35 | # Boost C++ Libraries 36 | 37 | https://www.boost.org/LICENSE_1_0.txt 38 | 39 | ``` 40 | Boost Software License - Version 1.0 - August 17th, 2003 41 | 42 | Permission is hereby granted, free of charge, to any person or organization 43 | obtaining a copy of the software and accompanying documentation covered by 44 | this license (the "Software") to use, reproduce, display, distribute, 45 | execute, and transmit the Software, and to prepare derivative works of the 46 | Software, and to permit third-parties to whom the Software is furnished to 47 | do so, all subject to the following: 48 | 49 | The copyright notices in the Software and this entire statement, including 50 | the above license grant, this restriction and the following disclaimer, 51 | must be included in all copies of the Software, in whole or in part, and 52 | all derivative works of the Software, unless such copies or derivative 53 | works are solely in the form of machine-executable object code generated by 54 | a source language processor. 55 | 56 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 57 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 58 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 59 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 60 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 61 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 62 | DEALINGS IN THE SOFTWARE. 63 | ``` 64 | 65 | # Sora C++ SDK 66 | 67 | https://github.com/shiguredo/sora-cpp-sdk 68 | 69 | ``` 70 | Apache License 71 | Version 2.0, January 2004 72 | http://www.apache.org/licenses/ 73 | 74 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 75 | 76 | 1. Definitions. 77 | 78 | "License" shall mean the terms and conditions for use, reproduction, 79 | and distribution as defined by Sections 1 through 9 of this document. 80 | 81 | "Licensor" shall mean the copyright owner or entity authorized by 82 | the copyright owner that is granting the License. 83 | 84 | "Legal Entity" shall mean the union of the acting entity and all 85 | other entities that control, are controlled by, or are under common 86 | control with that entity. For the purposes of this definition, 87 | "control" means (i) the power, direct or indirect, to cause the 88 | direction or management of such entity, whether by contract or 89 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 90 | outstanding shares, or (iii) beneficial ownership of such entity. 91 | 92 | "You" (or "Your") shall mean an individual or Legal Entity 93 | exercising permissions granted by this License. 94 | 95 | "Source" form shall mean the preferred form for making modifications, 96 | including but not limited to software source code, documentation 97 | source, and configuration files. 98 | 99 | "Object" form shall mean any form resulting from mechanical 100 | transformation or translation of a Source form, including but 101 | not limited to compiled object code, generated documentation, and conversions to other media types. 102 | 103 | "Work" shall mean the work of authorship, whether in Source or 104 | Object form, made available under the License, as indicated by a 105 | copyright notice that is included in or attached to the work 106 | (an example is provided in the Appendix below). 107 | 108 | "Derivative Works" shall mean any work, whether in Source or Object 109 | form, that is based on (or derived from) the Work and for which the 110 | editorial revisions, annotations, elaborations, or other modifications 111 | represent, as a whole, an original work of authorship. For the purposes 112 | of this License, Derivative Works shall not include works that remain 113 | separable from, or merely link (or bind by name) to the interfaces of, 114 | the Work and Derivative Works thereof. 115 | 116 | "Contribution" shall mean any work of authorship, including 117 | the original version of the Work and any modifications or additions 118 | to that Work or Derivative Works thereof, that is intentionally 119 | submitted to Licensor for inclusion in the Work by the copyright owner 120 | or by an individual or Legal Entity authorized to submit on behalf of 121 | the copyright owner. For the purposes of this definition, "submitted" 122 | means any form of electronic, verbal, or written communication sent 123 | to the Licensor or its representatives, including but not limited to 124 | communication on electronic mailing lists, source code control systems, 125 | and issue tracking systems that are managed by, or on behalf of, the 126 | Licensor for the purpose of discussing and improving the Work, but 127 | excluding communication that is conspicuously marked or otherwise 128 | designated in writing by the copyright owner as "Not a Contribution." 129 | 130 | "Contributor" shall mean Licensor and any individual or Legal Entity 131 | on behalf of whom a Contribution has been received by Licensor and 132 | subsequently incorporated within the Work. 133 | 134 | 2. Grant of Copyright License. Subject to the terms and conditions of 135 | this License, each Contributor hereby grants to You a perpetual, 136 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 137 | copyright license to reproduce, prepare Derivative Works of, 138 | publicly display, publicly perform, sublicense, and distribute the 139 | Work and such Derivative Works in Source or Object form. 140 | 141 | 3. Grant of Patent License. Subject to the terms and conditions of 142 | this License, each Contributor hereby grants to You a perpetual, 143 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 144 | (except as stated in this section) patent license to make, have made, 145 | use, offer to sell, sell, import, and otherwise transfer the Work, 146 | where such license applies only to those patent claims licensable 147 | by such Contributor that are necessarily infringed by their 148 | Contribution(s) alone or by combination of their Contribution(s) 149 | with the Work to which such Contribution(s) was submitted. If You 150 | institute patent litigation against any entity (including a 151 | cross-claim or counterclaim in a lawsuit) alleging that the Work 152 | or a Contribution incorporated within the Work constitutes direct 153 | or contributory patent infringement, then any patent licenses 154 | granted to You under this License for that Work shall terminate 155 | as of the date such litigation is filed. 156 | 157 | 4. Redistribution. You may reproduce and distribute copies of the 158 | Work or Derivative Works thereof in any medium, with or without 159 | modifications, and in Source or Object form, provided that You 160 | meet the following conditions: 161 | 162 | (a) You must give any other recipients of the Work or 163 | Derivative Works a copy of this License; and 164 | 165 | (b) You must cause any modified files to carry prominent notices 166 | stating that You changed the files; and 167 | 168 | (c) You must retain, in the Source form of any Derivative Works 169 | that You distribute, all copyright, patent, trademark, and 170 | attribution notices from the Source form of the Work, 171 | excluding those notices that do not pertain to any part of 172 | the Derivative Works; and 173 | 174 | (d) If the Work includes a "NOTICE" text file as part of its 175 | distribution, then any Derivative Works that You distribute must 176 | include a readable copy of the attribution notices contained 177 | within such NOTICE file, excluding those notices that do not 178 | pertain to any part of the Derivative Works, in at least one 179 | of the following places: within a NOTICE text file distributed 180 | as part of the Derivative Works; within the Source form or 181 | documentation, if provided along with the Derivative Works; or, 182 | within a display generated by the Derivative Works, if and 183 | wherever such third-party notices normally appear. The contents 184 | of the NOTICE file are for informational purposes only and 185 | do not modify the License. You may add Your own attribution 186 | notices within Derivative Works that You distribute, alongside 187 | or as an addendum to the NOTICE text from the Work, provided 188 | that such additional attribution notices cannot be construed 189 | as modifying the License. 190 | 191 | You may add Your own copyright statement to Your modifications and 192 | may provide additional or different license terms and conditions 193 | for use, reproduction, or distribution of Your modifications, or 194 | for any such Derivative Works as a whole, provided Your use, 195 | reproduction, and distribution of the Work otherwise complies with 196 | the conditions stated in this License. 197 | 198 | 5. Submission of Contributions. Unless You explicitly state otherwise, 199 | any Contribution intentionally submitted for inclusion in the Work 200 | by You to the Licensor shall be under the terms and conditions of 201 | this License, without any additional terms or conditions. 202 | Notwithstanding the above, nothing herein shall supersede or modify 203 | the terms of any separate license agreement you may have executed 204 | with Licensor regarding such Contributions. 205 | 206 | 6. Trademarks. This License does not grant permission to use the trade 207 | names, trademarks, service marks, or product names of the Licensor, 208 | except as required for reasonable and customary use in describing the 209 | origin of the Work and reproducing the content of the NOTICE file. 210 | 211 | 7. Disclaimer of Warranty. Unless required by applicable law or 212 | agreed to in writing, Licensor provides the Work (and each 213 | Contributor provides its Contributions) on an "AS IS" BASIS, 214 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 215 | implied, including, without limitation, any warranties or conditions 216 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 217 | PARTICULAR PURPOSE. You are solely responsible for determining the 218 | appropriateness of using or redistributing the Work and assume any 219 | risks associated with Your exercise of permissions under this License. 220 | 221 | 8. Limitation of Liability. In no event and under no legal theory, 222 | whether in tort (including negligence), contract, or otherwise, 223 | unless required by applicable law (such as deliberate and grossly 224 | negligent acts) or agreed to in writing, shall any Contributor be 225 | liable to You for damages, including any direct, indirect, special, 226 | incidental, or consequential damages of any character arising as a 227 | result of this License or out of the use or inability to use the 228 | Work (including but not limited to damages for loss of goodwill, 229 | work stoppage, computer failure or malfunction, or any and all 230 | other commercial damages or losses), even if such Contributor 231 | has been advised of the possibility of such damages. 232 | 233 | 9. Accepting Warranty or Additional Liability. While redistributing 234 | the Work or Derivative Works thereof, You may choose to offer, 235 | and charge a fee for, acceptance of support, warranty, indemnity, 236 | or other liability obligations and/or rights consistent with this 237 | License. However, in accepting such obligations, You may act only 238 | on Your own behalf and on Your sole responsibility, not on behalf 239 | of any other Contributor, and only if You agree to indemnify, 240 | defend, and hold each Contributor harmless for any liability 241 | incurred by, or claims asserted against, such Contributor by reason 242 | of your accepting any such warranty or additional liability. 243 | 244 | END OF TERMS AND CONDITIONS 245 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sora Unity SDK 2 | 3 | [![libwebrtc](https://img.shields.io/badge/libwebrtc-132.6834-blue.svg)](https://chromium.googlesource.com/external/webrtc/+/branch-heads/6834) 4 | [![GitHub tag](https://img.shields.io/github/tag/shiguredo/sora-unity-sdk.svg)](https://github.com/shiguredo/sora-unity-sdk) 5 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 6 | [![Actions Status](https://github.com/shiguredo/sora-unity-sdk/workflows/build/badge.svg)](https://github.com/shiguredo/sora-unity-sdk/actions) 7 | 8 | Sora Unity SDK は [WebRTC SFU Sora](https://sora.shiguredo.jp/) の Unity クライアントアプリケーションを開発するためのライブラリです。 9 | 10 | ## About Shiguredo's open source software 11 | 12 | We will not respond to PRs or issues that have not been discussed on Discord. Also, Discord is only available in Japanese. 13 | 14 | Please read before use. 15 | 16 | ## 時雨堂のオープンソースソフトウェアについて 17 | 18 | 利用前に をお読みください。 19 | 20 | ## ドキュメント 21 | 22 | [Sora Unity SDK ドキュメント](https://sora-unity-sdk.shiguredo.jp/) 23 | 24 | ## サンプル 25 | 26 | [shiguredo/sora\-unity\-sdk\-samples: WebRTC SFU Sora Unity SDK サンプル集](https://github.com/shiguredo/sora-unity-sdk-samples) 27 | 28 | ### サンプル動作例 29 | 30 | - [「こんな感じに Unity のカメラ映像を WebRTC で配信できるようになりました https://t\.co/R98ZmZTFOK」 / Twitter](https://twitter.com/melponn/status/1193406538494275592) 31 | - [「ちゃんとリアルタイムで配信してます(モーション作るのが面倒だったのでシーンエディタから動かしてる)。Unity 側が配信で、ブラウザ(右上)で受信してる。 https://t\.co/TIL7NYroZm」 / Twitter](https://twitter.com/melponn/status/1193411591183552512) 32 | - [「Momo (on Jetson Nano) -> Sora-Labo -> Sora-Unity と Sora-Js 同時受信。ここまでがお手軽すぎてやばい。」 / Twitter](https://twitter.com/izmhrats/status/1203299775354851328?s=20) 33 | 34 | ## 対応 Unity バージョン 35 | 36 | - Unity 6000.0 (LTS) 37 | - Unity 2022.3 (LTS) 38 | 39 | ## システム条件 40 | 41 | - WebRTC SFU Sora 2024.2.0 以降 42 | 43 | ## 対応プラットフォーム 44 | 45 | - Windows 10 22H2 x86_64 以降 46 | - macOS 13.4.1 M1 以降 47 | - Android 7 以降 48 | - iOS 13 以降 49 | - Ubuntu 22.04 x86_64 50 | - Ubuntu 24.04 x86_64 51 | 52 | ## 対応機能 53 | 54 | - AV1 への対応 55 | - Windows への対応 56 | - macOS への対応 57 | - Apple M1 対応 58 | - Android への対応 59 | - Android OpenGL ES への対応 60 | - iOS 対応 61 | - Ubuntu 22.04 への対応 62 | - SRTP/SRTCP の AES-GCM 対応 63 | - Unity のカメラ映像を取得し Sora で送信 64 | - カメラから映像を取得し Sora に送信 65 | - カメラから映像を取得し Unity アプリに出力 66 | - マイクから音声を取得し Sora に送信 67 | - マイクから音声を取得し Unity アプリに出力 68 | - Unity アプリで Sora からの音声を受信 69 | - Unity アプリで Sora からの映像を受信 70 | - Unity アプリで Sora からの音声を再生 71 | - ソフトウェアエンコード/デコード VP8 / VP9 への対応 72 | - Opus への対応 73 | - デバイス指定機能 74 | - マイクの代わりに Unity からのオーディオ出力 75 | - Unity カメラからの映像取得に対応 76 | - Unity 側で受信したオーディオの再生に対応 77 | - Sora から受信した音声を Unity アプリに出力 78 | - Sora から受信した映像を Unity アプリに出力 79 | - Sora マルチストリーム機能への対応 80 | - Sora シグナリング通知への対応 81 | - Sora メタデータへの対応 82 | - Sora シグナリング開始時の音声コーデック/ビットレート指定に対応 83 | - Sora シグナリング開始時の映像コーデック/ビットレート指定に対応 84 | - Sora シグナリング通知への対応 85 | - Sora プッシュ通知への対応 86 | - Sora サイマルキャストへの対応 87 | - Sora スポットライトへの対応 88 | - Sora データチャネルへの対応 89 | - Sora データチャネルメッセージングへの対応 90 | - 実験的機能 91 | - Apple VideoToolbox 92 | - H.265 ハードウェアエンコードへの対応 93 | - H.265 ハードウェアデコードへの対応 94 | - H.264 ハードウェアエンコードへの対応 95 | - H.264 ハードウェアデコードへの対応 96 | - NVIDIA VIDEO CODEC SDK 97 | - H.265 のハードウェアエンコードへの対応 98 | - H.265 のハードウェアデコードへの対応 99 | - H.264 のハードウェアエンコードへの対応 100 | - H.264 のハードウェアデコードへの対応 101 | - VP8 のハードウェアデコードへの対応 102 | - VP9 のハードウェアデコードへの対応 103 | - Intel VPL 104 | - H.265 ハードウェアエンコードへの対応 105 | - H.265 ハードウェアデコードへの対応 106 | - H.264 のハードウェアエンコードへの対応 107 | - H.264 のハードウェアデコードへの対応 108 | - AV1 のハードウェアエンコードへの対応 109 | - AV1 のハードウェアデコードへの対応 110 | - ミュート機能 111 | - iOS, Android 向け音声出力先変更機能 112 | - マイクやカメラ等のメディアデバイスをつかまないようにする機能 113 | - 接続確立後のカメラ切り替え機能 114 | 115 | ## 有償での優先実装 116 | 117 | - Windows 版 NVIDIA VIDEO CODEC SDK による H.264 エンコーダ対応 118 | - [スロースネットワークス株式会社](http://www.sloth-networks.co.jp) 様 119 | - WebRTC's Statistics 対応 120 | - 企業名非公開 121 | - Windows 版 NVIDIA VIDEO CODEC SDK による H.264 デコーダ対応 122 | - [スロースネットワークス株式会社](http://www.sloth-networks.co.jp) 様 123 | - Android 版対応 124 | - [株式会社KDDIテクノロジー](https://kddi-tech.com/) 様 125 | - Android OpenGL ES 対応 126 | - 企業名非公開 127 | - Microsoft HoloLens 2 対応 128 | - [株式会社NTTコノキュー](https://www.nttqonoq.com/) 様 129 | - ミュート機能 130 | - [株式会社NTTコノキュー](https://www.nttqonoq.com/) 様 131 | - iOS 向け音声出力先変更機能 132 | - [KDDI株式会社](https://www.kddi.com/) 様 133 | - 接続確立後のカメラ切り替え機能 134 | - [KDDI株式会社](https://www.kddi.com/) 様 135 | - マイクやカメラ等のメディアデバイスをつかまないようにする機能 136 | - [KDDI株式会社](https://www.kddi.com/) 様 137 | - Android 向け音声出力先変更機能 138 | - [KDDI株式会社](https://www.kddi.com/) 様 139 | - Android 向け音声デバイス切り替え対応 140 | - [KDDI株式会社](https://www.kddi.com/) 様 141 | 142 | ## 有償での優先実装が可能な機能一覧 143 | 144 | **詳細は Discord またはメールにてお問い合わせください** 145 | 146 | - オープンソースでの公開が前提 147 | - 可能であれば企業名の公開 148 | - 公開が難しい場合は `企業名非公開` と書かせていただきます 149 | 150 | ### 機能 151 | 152 | **こちらに掲載していない機能でも対応できる場合がありますのでまずはお問い合わせください** 153 | 154 | - サイマルキャスト rid 指定対応 155 | - iOS, Android 以外の音声出力先変更機能 156 | - 音声トラックの個別取得機能 157 | - 音声トラック ID 取得機能 (統計情報との紐づけによる利用を想定) 158 | - 映像トラックの個別取得機能 159 | - 映像トラック ID 取得機能 (統計情報との紐づけによる利用を想定) 160 | 161 | ## ライセンス 162 | 163 | Apache License 2.0 164 | 165 | ``` 166 | Copyright 2019-2025, Wandbox LLC (Original Author) 167 | Copyright 2019-2025, Shiguredo Inc. 168 | 169 | Licensed under the Apache License, Version 2.0 (the "License"); 170 | you may not use this file except in compliance with the License. 171 | You may obtain a copy of the License at 172 | 173 | http://www.apache.org/licenses/LICENSE-2.0 174 | 175 | Unless required by applicable law or agreed to in writing, software 176 | distributed under the License is distributed on an "AS IS" BASIS, 177 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 178 | See the License for the specific language governing permissions and 179 | limitations under the License. 180 | ``` 181 | 182 | ## Sora Unity SDK for MS HoloLens 2 183 | 184 | 継続的なメンテナンスを終了し、サポートも終了しました。 185 | -------------------------------------------------------------------------------- /Sora/Editor/SoraUnitySdkPostProcessor.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_IOS 2 | 3 | using UnityEngine; 4 | using UnityEditor; 5 | using UnityEditor.iOS.Xcode; 6 | using UnityEditor.Callbacks; 7 | 8 | public class SoraUnitySdkPostProcessor 9 | { 10 | [PostProcessBuildAttribute(500)] 11 | public static void OnPostprocessBuild(BuildTarget buildTarget, string pathToBuiltProject) 12 | { 13 | if (buildTarget != BuildTarget.iOS) 14 | { 15 | return; 16 | } 17 | 18 | var projPath = pathToBuiltProject + "/Unity-iPhone.xcodeproj/project.pbxproj"; 19 | PBXProject proj = new PBXProject(); 20 | proj.ReadFromFile(projPath); 21 | #if UNITY_2019_3_OR_NEWER 22 | string guid = proj.GetUnityFrameworkTargetGuid(); 23 | #else 24 | string guid = proj.TargetGuidByName("Unity-iPhone"); 25 | #endif 26 | 27 | proj.AddBuildProperty(guid, "OTHER_LDFLAGS", "-ObjC"); 28 | proj.SetBuildProperty(guid, "ENABLE_BITCODE", "NO"); 29 | proj.AddFrameworkToProject(guid, "VideoToolbox.framework", false); 30 | proj.AddFrameworkToProject(guid, "GLKit.framework", false); 31 | proj.AddFrameworkToProject(guid, "Network.framework", false); 32 | // libwebrtc.a には新しい libvpx が、libiPhone-lib.a には古い libvpx が入っていて、 33 | // デフォルトのリンク順序だと古い libvpx が使われてしまう。 34 | // それを回避するために libiPhone-lib.a を削除して新しく追加し直すことで 35 | // リンク順序を変えてやる。 36 | string fileGuid = proj.FindFileGuidByProjectPath("Libraries/libiPhone-lib.a"); 37 | proj.RemoveFileFromBuild(guid, fileGuid); 38 | proj.AddFileToBuild(guid, fileGuid); 39 | 40 | proj.WriteToFile(projPath); 41 | } 42 | } 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | SORA_UNITY_SDK_VERSION=2025.2.0-canary.8 2 | SORA_CPP_SDK_VERSION=2025.3.1 3 | WEBRTC_BUILD_VERSION=m136.7103.0.0 4 | BOOST_VERSION=1.88.0 5 | CMAKE_VERSION=4.0.1 6 | PROTOBUF_VERSION=25.6 7 | PROTOC_GEN_JSONIF_VERSION=0.13.0 8 | ANDROID_NDK_VERSION=r26b 9 | ANDROID_NATIVE_API_LEVEL=29 10 | -------------------------------------------------------------------------------- /canary.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import re 3 | import subprocess 4 | 5 | VERSION_FILE = "VERSION" 6 | 7 | 8 | def update_sdk_version(version_content): 9 | updated_content = [] 10 | sdk_version_updated = False 11 | new_version = None 12 | 13 | for line in version_content: 14 | line = line.strip() # 前後の余分なスペースや改行を削除 15 | if line.startswith("SORA_UNITY_SDK_VERSION="): 16 | version_match = re.match( 17 | r"SORA_UNITY_SDK_VERSION=(\d{4}\.\d+\.\d+)(-canary\.(\d+))?", line 18 | ) 19 | if version_match: 20 | major_minor_patch = version_match.group(1) 21 | canary_suffix = version_match.group(2) 22 | if canary_suffix is None: 23 | new_version = f"{major_minor_patch}-canary.0" 24 | else: 25 | canary_number = int(version_match.group(3)) 26 | new_version = f"{major_minor_patch}-canary.{canary_number + 1}" 27 | 28 | updated_content.append(f"SORA_UNITY_SDK_VERSION={new_version}") 29 | sdk_version_updated = True 30 | else: 31 | updated_content.append(line) 32 | else: 33 | updated_content.append(line) 34 | 35 | if not sdk_version_updated: 36 | raise ValueError("SORA_UNITY_SDK_VERSION not found in VERSION file.") 37 | 38 | return updated_content, new_version 39 | 40 | 41 | def write_version_file(filename, updated_content, dry_run): 42 | if dry_run: 43 | print(f"Dry run: The following changes would be written to {filename}:") 44 | for line in updated_content: 45 | print(line.strip()) 46 | else: 47 | with open(filename, "w") as file: 48 | file.write("\n".join(updated_content) + "\n") 49 | print(f"{filename} updated.") 50 | 51 | 52 | def git_operations(new_version, dry_run): 53 | commit_message = f"[canary] Update VERSION to {new_version}" 54 | 55 | if dry_run: 56 | print(f"Dry run: Would execute git commit -am '{commit_message}'") 57 | print(f"Dry run: Would execute git tag {new_version}") 58 | print("Dry run: Would execute git push") 59 | print(f"Dry run: Would execute git push origin {new_version}") 60 | else: 61 | print(f"Executing: git commit -am '{commit_message}'") 62 | subprocess.run(["git", "commit", "-am", commit_message], check=True) 63 | 64 | print(f"Executing: git tag {new_version}") 65 | subprocess.run(["git", "tag", new_version], check=True) 66 | 67 | print("Executing: git push") 68 | subprocess.run(["git", "push"], check=True) 69 | 70 | print(f"Executing: git push origin {new_version}") 71 | subprocess.run(["git", "push", "origin", new_version], check=True) 72 | 73 | 74 | def main(): 75 | parser = argparse.ArgumentParser( 76 | description="Update VERSION file and push changes with git." 77 | ) 78 | parser.add_argument( 79 | "--dry-run", action="store_true", help="Perform a dry run without making any changes." 80 | ) 81 | args = parser.parse_args() 82 | 83 | # Read and update the VERSION file 84 | with open(VERSION_FILE, "r") as file: 85 | version_content = file.readlines() 86 | updated_version_content, new_version = update_sdk_version(version_content) 87 | write_version_file(VERSION_FILE, updated_version_content, args.dry_run) 88 | 89 | # Perform git operations 90 | git_operations(new_version, args.dry_run) 91 | 92 | 93 | if __name__ == "__main__": 94 | main() -------------------------------------------------------------------------------- /proto/sora_conf.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package sora_conf; 4 | 5 | enum ErrorCode { 6 | NOT_SET = 0; 7 | // ユーザが Close を呼び、それが正常に終了した 8 | CLOSE_SUCCEEDED = 1; 9 | // ユーザが Close を呼んだが、正常に終了しなかった 10 | CLOSE_FAILED = 2; 11 | // 内部的なエラー 12 | INTERNAL_ERROR = 3; 13 | // ユーザが渡したパラメータが何か間違っている 14 | INVALID_PARAMETER = 4; 15 | // WebSocket のハンドシェイクに失敗した 16 | WEBSOCKET_HANDSHAKE_FAILED = 5; 17 | // サーバからの切断が正常に行われた 18 | WEBSOCKET_ONCLOSE = 6; 19 | // 何らかの通信の問題が発生してサーバから切断された 20 | WEBSOCKET_ONERROR = 7; 21 | // PeerConnectionState が failed 状態になった 22 | PEER_CONNECTION_STATE_FAILED = 8; 23 | // ICE candidate の交換のどこかで失敗した 24 | ICE_FAILED = 9; 25 | } 26 | 27 | message VideoFrameBuffer { 28 | enum Type { 29 | kNative = 0; 30 | kI420 = 1; 31 | kI420A = 2; 32 | kI422 = 3; 33 | kI444 = 4; 34 | kI010 = 5; 35 | kI210 = 6; 36 | kI410 = 7; 37 | kNV12 = 8; 38 | } 39 | int64 baseptr = 1; 40 | Type type = 2; 41 | int32 width = 3; 42 | int32 height = 4; 43 | 44 | int32 i420_stride_y = 110; 45 | int32 i420_stride_u = 111; 46 | int32 i420_stride_v = 112; 47 | int64 i420_data_y = 113; 48 | int64 i420_data_u = 114; 49 | int64 i420_data_v = 115; 50 | 51 | int32 nv12_stride_y = 170; 52 | int32 nv12_stride_uv = 171; 53 | int64 nv12_data_y = 172; 54 | int64 nv12_data_uv = 173; 55 | } 56 | 57 | message VideoFrame { 58 | int64 baseptr = 1; 59 | int32 id = 2; 60 | int64 timestamp_us = 3; 61 | uint32 timestamp = 4; 62 | int64 ntp_time_ms = 5; 63 | int32 rotation = 6; 64 | VideoFrameBuffer video_frame_buffer = 7; 65 | } -------------------------------------------------------------------------------- /proto/sora_conf_internal.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package sora_conf.internal; 4 | 5 | message DataChannel { 6 | string label = 1; 7 | string direction = 2; 8 | optional bool ordered = 4; 9 | optional int32 max_packet_life_time = 6; 10 | optional int32 max_retransmits = 8; 11 | optional string protocol = 10; 12 | optional bool compress = 12; 13 | message Header { 14 | repeated string content = 1; 15 | } 16 | optional Header header = 14; 17 | } 18 | 19 | message ForwardingFilter { 20 | optional string action = 2; 21 | optional string name = 4; 22 | optional int32 priority = 6; 23 | message Rule { 24 | string field = 1; 25 | string op = 2; 26 | repeated string values = 3; 27 | } 28 | message Rules { 29 | repeated Rule rules = 1; 30 | } 31 | repeated Rules rules = 3; 32 | optional string version = 5; 33 | optional string metadata = 7; 34 | } 35 | 36 | message ForwardingFilters { 37 | repeated ForwardingFilter filters = 1; 38 | } 39 | 40 | message VideoCodecCapability { 41 | message Codec { 42 | string type = 1; 43 | bool encoder = 2; 44 | bool decoder = 3; 45 | string parameters = 4; 46 | } 47 | message Engine { 48 | string name = 1; 49 | repeated Codec codecs = 2; 50 | string parameters = 3; 51 | } 52 | repeated Engine engines = 1; 53 | } 54 | 55 | message VideoCodecCapabilityConfig { 56 | optional string openh264_path = 1; 57 | } 58 | 59 | message VideoCodecPreference { 60 | message Codec { 61 | string type = 1; 62 | optional string encoder = 2; 63 | optional string decoder = 3; 64 | string parameters = 4; 65 | } 66 | repeated Codec codecs = 1; 67 | } 68 | 69 | message CameraConfig { 70 | int32 capturer_type = 17; 71 | int64 unity_camera_texture = 18; 72 | string video_capturer_device = 19; 73 | int32 video_width = 22; 74 | int32 video_height = 23; 75 | int32 video_fps = 24; 76 | } 77 | 78 | message ConnectConfig { 79 | string unity_version = 1; 80 | repeated string signaling_url = 2; 81 | string channel_id = 3; 82 | string client_id = 4; 83 | string metadata = 5; 84 | string role = 6; 85 | optional bool multistream = 8; 86 | optional bool spotlight = 10; 87 | int32 spotlight_number = 11; 88 | string spotlight_focus_rid = 12; 89 | string spotlight_unfocus_rid = 13; 90 | optional bool simulcast = 15; 91 | string simulcast_rid = 16; 92 | bool no_video_device = 160; 93 | bool no_audio_device = 161; 94 | CameraConfig camera_config = 17; 95 | bool video = 20; 96 | bool audio = 21; 97 | string video_codec_type = 25; 98 | string video_vp9_params = 250; 99 | string video_av1_params = 251; 100 | string video_h264_params = 252; 101 | int32 video_bit_rate = 26; 102 | bool unity_audio_input = 27; 103 | bool unity_audio_output = 28; 104 | string audio_recording_device = 29; 105 | string audio_playout_device = 30; 106 | string audio_codec_type = 31; 107 | int32 audio_bit_rate = 34; 108 | optional bool data_channel_signaling = 36; 109 | int32 data_channel_signaling_timeout = 37; 110 | optional bool ignore_disconnect_websocket = 39; 111 | int32 disconnect_wait_timeout = 40; 112 | repeated DataChannel data_channels = 41; 113 | bool insecure = 42; 114 | string bundle_id = 43; 115 | string proxy_url = 44; 116 | string proxy_username = 45; 117 | string proxy_password = 46; 118 | string proxy_agent = 47; 119 | string audio_streaming_language_code = 48; 120 | string signaling_notify_metadata = 49; 121 | optional ForwardingFilter forwarding_filter = 51; 122 | optional ForwardingFilters forwarding_filters = 52; 123 | VideoCodecCapabilityConfig video_codec_capability_config = 530; 124 | optional VideoCodecPreference video_codec_preference = 531; 125 | optional string client_cert = 54; 126 | optional string client_key = 55; 127 | optional string ca_cert = 56; 128 | } -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | line-length = 100 2 | -------------------------------------------------------------------------------- /src/android_helper/android_context.cpp: -------------------------------------------------------------------------------- 1 | #include "android_context.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace sora_unity_sdk { 8 | 9 | webrtc::ScopedJavaLocalRef GetAndroidApplicationContext(JNIEnv* env) { 10 | // Context context = UnityPlayer.currentActivity.getApplicationContext() 11 | // を頑張って C++ から呼んでるだけ 12 | webrtc::ScopedJavaLocalRef upcls = 13 | webrtc::GetClass(env, "com/unity3d/player/UnityPlayer"); 14 | jfieldID actid = env->GetStaticFieldID(upcls.obj(), "currentActivity", 15 | "Landroid/app/Activity;"); 16 | webrtc::ScopedJavaLocalRef activity( 17 | env, env->GetStaticObjectField(upcls.obj(), actid)); 18 | 19 | webrtc::ScopedJavaLocalRef actcls( 20 | env, env->GetObjectClass(activity.obj())); 21 | jmethodID ctxid = env->GetMethodID(actcls.obj(), "getApplicationContext", 22 | "()Landroid/content/Context;"); 23 | webrtc::ScopedJavaLocalRef context( 24 | env, env->CallObjectMethod(activity.obj(), ctxid)); 25 | 26 | // org.webrtc.ContextUtils.initialize(context) 27 | // を呼ぶ。これをしないと ADM が生成できない 28 | webrtc::ScopedJavaLocalRef cucls = 29 | webrtc::GetClass(env, "org/webrtc/ContextUtils"); 30 | jmethodID initid = env->GetStaticMethodID(cucls.obj(), "initialize", 31 | "(Landroid/content/Context;)V"); 32 | env->CallStaticVoidMethod(cucls.obj(), initid, context.obj()); 33 | 34 | return context; 35 | } 36 | 37 | } // namespace sora_unity_sdk 38 | -------------------------------------------------------------------------------- /src/android_helper/android_context.h: -------------------------------------------------------------------------------- 1 | #ifndef SORA_UNITY_SDK_ANDROID_HELPER_ANDROID_CONTEXT_H_ 2 | #define SORA_UNITY_SDK_ANDROID_HELPER_ANDROID_CONTEXT_H_ 3 | 4 | #include 5 | 6 | namespace sora_unity_sdk { 7 | 8 | webrtc::ScopedJavaLocalRef GetAndroidApplicationContext(JNIEnv* env); 9 | 10 | } 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/android_helper/jni_onload.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 The WebRTC project authors. All Rights Reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license 5 | * that can be found in the LICENSE file in the root of the source 6 | * tree. An additional intellectual property rights grant can be found 7 | * in the file PATENTS. All contributing project authors may 8 | * be found in the AUTHORS file in the root of the source tree. 9 | */ 10 | 11 | #include 12 | #undef JNIEXPORT 13 | #define JNIEXPORT __attribute__((visibility("default"))) 14 | 15 | #include "rtc_base/logging.h" 16 | #include "rtc_base/ssl_adapter.h" 17 | #include "sdk/android/native_api/jni/class_loader.h" 18 | #include "sdk/android/src/jni/jni_helpers.h" 19 | 20 | namespace webrtc { 21 | namespace jni { 22 | 23 | extern "C" jint JNIEXPORT JNICALL JNI_OnLoad(JavaVM* jvm, void* reserved) { 24 | jint ret = InitGlobalJniVariables(jvm); 25 | RTC_DCHECK_GE(ret, 0); 26 | if (ret < 0) 27 | return -1; 28 | 29 | RTC_CHECK(rtc::InitializeSSL()) << "Failed to InitializeSSL()"; 30 | webrtc::InitClassLoader(GetEnv()); 31 | //LoadGlobalClassReferenceHolder(); 32 | //unity_plugin::LoadGlobalClassReferenceHolder(); 33 | RTC_LOG(LS_INFO) << "JNI_OnLoad ret=" << ret; 34 | 35 | return ret; 36 | } 37 | 38 | extern "C" void JNIEXPORT JNICALL JNI_OnUnLoad(JavaVM* jvm, void* reserved) { 39 | //FreeGlobalClassReferenceHolder(); 40 | //unity_plugin::FreeGlobalClassReferenceHolder(); 41 | RTC_CHECK(rtc::CleanupSSL()) << "Failed to CleanupSSL()"; 42 | 43 | RTC_LOG(LS_INFO) << "JNI_OnUnLoad"; 44 | } 45 | 46 | } // namespace jni 47 | } // namespace webrtc 48 | -------------------------------------------------------------------------------- /src/converter.cpp: -------------------------------------------------------------------------------- 1 | #include "converter.h" 2 | 3 | // Boost 4 | #include 5 | 6 | // Sora 7 | #include 8 | #include 9 | 10 | namespace sora_unity_sdk { 11 | 12 | sora::SoraSignalingConfig::ForwardingFilter ConvertToForwardingFilter( 13 | const sora_conf::internal::ForwardingFilter& filter) { 14 | sora::SoraSignalingConfig::ForwardingFilter ff; 15 | 16 | if (filter.has_action()) { 17 | ff.action = filter.action; 18 | } 19 | if (filter.has_name()) { 20 | ff.name = filter.name; 21 | } 22 | if (filter.has_priority()) { 23 | ff.priority = filter.priority; 24 | } 25 | for (const auto& rs : filter.rules) { 26 | std::vector ffrs; 27 | for (const auto& r : rs.rules) { 28 | sora::SoraSignalingConfig::ForwardingFilter::Rule ffr; 29 | ffr.field = r.field; 30 | ffr.op = r.op; 31 | ffr.values = r.values; 32 | ffrs.push_back(ffr); 33 | } 34 | ff.rules.push_back(ffrs); 35 | } 36 | 37 | if (filter.has_version()) { 38 | ff.version = filter.version; 39 | } 40 | if (filter.has_metadata()) { 41 | boost::system::error_code ec; 42 | auto ffmd = boost::json::parse(filter.metadata, ec); 43 | if (ec) { 44 | RTC_LOG(LS_WARNING) << "Invalid JSON: forwarding_filter metadata=" 45 | << filter.metadata; 46 | } else { 47 | ff.metadata = ffmd; 48 | } 49 | } 50 | 51 | return ff; 52 | } 53 | 54 | sora::VideoCodecCapabilityConfig ConvertToVideoCodecCapabilityConfig( 55 | const sora_conf::internal::VideoCodecCapabilityConfig& config) { 56 | sora::VideoCodecCapabilityConfig vccc; 57 | if (config.has_openh264_path()) { 58 | vccc.openh264_path = config.openh264_path; 59 | } 60 | return vccc; 61 | } 62 | sora::VideoCodecCapabilityConfig ConvertToVideoCodecCapabilityConfigWithSession( 63 | const sora_conf::internal::VideoCodecCapabilityConfig& config) { 64 | sora::VideoCodecCapabilityConfig vccc = 65 | ConvertToVideoCodecCapabilityConfig(config); 66 | if (sora::CudaContext::CanCreate()) { 67 | vccc.cuda_context = sora::CudaContext::Create(); 68 | } 69 | if (sora::AMFContext::CanCreate()) { 70 | vccc.amf_context = sora::AMFContext::Create(); 71 | } 72 | vccc.jni_env = sora::GetJNIEnv(); 73 | return vccc; 74 | } 75 | 76 | sora::VideoCodecCapability ConvertToVideoCodecCapability( 77 | const sora_conf::internal::VideoCodecCapability& capability) { 78 | sora::VideoCodecCapability vcc; 79 | for (const auto& engine : capability.engines) { 80 | sora::VideoCodecCapability::Engine e( 81 | boost::json::value_to( 82 | boost::json::value(engine.name))); 83 | for (const auto& codec : engine.codecs) { 84 | sora::VideoCodecCapability::Codec c( 85 | boost::json::value_to( 86 | boost::json::value(codec.type)), 87 | codec.encoder, codec.decoder); 88 | c.parameters = 89 | boost::json::value_to( 90 | boost::json::parse(codec.parameters)); 91 | e.codecs.push_back(c); 92 | } 93 | e.parameters = 94 | boost::json::value_to( 95 | boost::json::parse(engine.parameters)); 96 | vcc.engines.push_back(e); 97 | } 98 | return vcc; 99 | } 100 | sora_conf::internal::VideoCodecCapability ConvertToInternalVideoCodecCapability( 101 | const sora::VideoCodecCapability& capability) { 102 | sora_conf::internal::VideoCodecCapability vcc; 103 | for (const auto& engine : capability.engines) { 104 | sora_conf::internal::VideoCodecCapability::Engine e; 105 | e.name = boost::json::value_from(engine.name).as_string().c_str(); 106 | e.parameters = 107 | boost::json::serialize(boost::json::value_from(engine.parameters)); 108 | for (const auto& codec : engine.codecs) { 109 | sora_conf::internal::VideoCodecCapability::Codec c; 110 | c.type = boost::json::value_from(codec.type).as_string().c_str(); 111 | c.encoder = codec.encoder; 112 | c.decoder = codec.decoder; 113 | c.parameters = 114 | boost::json::serialize(boost::json::value_from(codec.parameters)); 115 | e.codecs.push_back(c); 116 | } 117 | vcc.engines.push_back(e); 118 | } 119 | return vcc; 120 | } 121 | 122 | sora::VideoCodecPreference ConvertToVideoCodecPreference( 123 | const sora_conf::internal::VideoCodecPreference& preference) { 124 | sora::VideoCodecPreference vcp; 125 | for (const auto& codec : preference.codecs) { 126 | sora::VideoCodecPreference::Codec c; 127 | c.type = boost::json::value_to( 128 | boost::json::value(codec.type)); 129 | if (codec.has_encoder()) { 130 | c.encoder = boost::json::value_to( 131 | boost::json::value(codec.encoder)); 132 | } 133 | if (codec.has_decoder()) { 134 | c.decoder = boost::json::value_to( 135 | boost::json::value(codec.decoder)); 136 | } 137 | c.parameters = 138 | boost::json::value_to( 139 | boost::json::parse(codec.parameters)); 140 | vcp.codecs.push_back(c); 141 | } 142 | return vcp; 143 | } 144 | 145 | sora_conf::internal::VideoCodecPreference ConvertToInternalVideoCodecPreference( 146 | const sora::VideoCodecPreference& preference) { 147 | sora_conf::internal::VideoCodecPreference vcp; 148 | for (const auto& codec : preference.codecs) { 149 | sora_conf::internal::VideoCodecPreference::Codec c; 150 | c.type = boost::json::value_from(codec.type).as_string().c_str(); 151 | if (codec.encoder.has_value()) { 152 | c.set_encoder( 153 | boost::json::value_from(codec.encoder.value()).as_string().c_str()); 154 | } 155 | if (codec.decoder.has_value()) { 156 | c.set_decoder( 157 | boost::json::value_from(codec.decoder.value()).as_string().c_str()); 158 | } 159 | c.parameters = 160 | boost::json::serialize(boost::json::value_from(codec.parameters)); 161 | vcp.codecs.push_back(c); 162 | } 163 | return vcp; 164 | } 165 | 166 | } // namespace sora_unity_sdk 167 | -------------------------------------------------------------------------------- /src/converter.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "sora_conf.json.h" 5 | #include "sora_conf_internal.json.h" 6 | 7 | namespace sora_unity_sdk { 8 | 9 | sora::SoraSignalingConfig::ForwardingFilter ConvertToForwardingFilter( 10 | const sora_conf::internal::ForwardingFilter& filter); 11 | sora::VideoCodecCapabilityConfig ConvertToVideoCodecCapabilityConfig( 12 | const sora_conf::internal::VideoCodecCapabilityConfig& config); 13 | sora::VideoCodecCapabilityConfig ConvertToVideoCodecCapabilityConfigWithSession( 14 | const sora_conf::internal::VideoCodecCapabilityConfig& config); 15 | sora::VideoCodecCapability ConvertToVideoCodecCapability( 16 | const sora_conf::internal::VideoCodecCapability& capability); 17 | sora_conf::internal::VideoCodecCapability ConvertToInternalVideoCodecCapability( 18 | const sora::VideoCodecCapability& capability); 19 | sora::VideoCodecPreference ConvertToVideoCodecPreference( 20 | const sora_conf::internal::VideoCodecPreference& preference); 21 | sora_conf::internal::VideoCodecPreference ConvertToInternalVideoCodecPreference( 22 | const sora::VideoCodecPreference& preference); 23 | 24 | } // namespace sora_unity_sdk 25 | -------------------------------------------------------------------------------- /src/device_list.cpp: -------------------------------------------------------------------------------- 1 | #include "device_list.h" 2 | 3 | // webrtc 4 | #include "api/task_queue/default_task_queue_factory.h" 5 | #include "modules/audio_device/include/audio_device.h" 6 | #include "modules/audio_device/include/audio_device_factory.h" 7 | #include "modules/video_capture/video_capture.h" 8 | #include "modules/video_capture/video_capture_factory.h" 9 | #include "rtc_base/logging.h" 10 | 11 | #ifdef SORA_UNITY_SDK_ANDROID 12 | #include "sdk/android/native_api/audio_device_module/audio_device_android.h" 13 | #include "sdk/android/native_api/jni/jvm.h" 14 | #endif 15 | 16 | #if defined(SORA_UNITY_SDK_MACOS) || defined(SORA_UNITY_SDK_IOS) 17 | #include 18 | #elif defined(SORA_UNITY_SDK_ANDROID) 19 | #include 20 | #include "android_helper/android_context.h" 21 | #endif 22 | 23 | namespace sora_unity_sdk { 24 | 25 | bool DeviceList::EnumVideoCapturer( 26 | std::function f) { 27 | #if defined(SORA_UNITY_SDK_MACOS) || defined(SORA_UNITY_SDK_IOS) 28 | 29 | return sora::MacCapturer::EnumVideoDevice(f); 30 | 31 | #elif defined(SORA_UNITY_SDK_ANDROID) 32 | 33 | JNIEnv* env = webrtc::AttachCurrentThreadIfNeeded(); 34 | auto context = GetAndroidApplicationContext(env); 35 | return sora::AndroidCapturer::EnumVideoDevice(env, context.obj(), f); 36 | 37 | #else 38 | 39 | std::unique_ptr info( 40 | webrtc::VideoCaptureFactory::CreateDeviceInfo()); 41 | if (!info) { 42 | RTC_LOG(LS_WARNING) << "Failed to CreateDeviceInfo"; 43 | return false; 44 | } 45 | 46 | int num_devices = info->NumberOfDevices(); 47 | for (int i = 0; i < num_devices; i++) { 48 | char device_name[256]; 49 | char unique_name[256]; 50 | if (info->GetDeviceName(i, device_name, sizeof(device_name), unique_name, 51 | sizeof(unique_name)) != 0) { 52 | RTC_LOG(LS_WARNING) << "Failed to GetDeviceName: index=" << i; 53 | continue; 54 | } 55 | 56 | RTC_LOG(LS_INFO) << "EnumVideoCapturer: device_name=" << device_name 57 | << " unique_name=" << unique_name; 58 | f(device_name, unique_name); 59 | } 60 | return true; 61 | 62 | #endif 63 | } 64 | 65 | bool DeviceList::EnumAudioRecording( 66 | std::function f) { 67 | auto task_queue_factory = webrtc::CreateDefaultTaskQueueFactory(); 68 | #if defined(SORA_UNITY_SDK_ANDROID) || defined(SORA_UNITY_SDK_IOS) 69 | // Android や iOS の場合常に1個しかなく、かつ adm->RecordingDeviceName() を呼ぶと fatal error が起きるので 70 | // 適当な名前で1回だけコールバックする 71 | f("0", "0"); 72 | return true; 73 | #else 74 | 75 | #if defined(SORA_UNITY_SDK_WINDOWS) 76 | auto adm = 77 | webrtc::CreateWindowsCoreAudioAudioDeviceModule(task_queue_factory.get()); 78 | #else 79 | auto adm = webrtc::AudioDeviceModule::Create( 80 | webrtc::AudioDeviceModule::kPlatformDefaultAudio, 81 | task_queue_factory.get()); 82 | #endif 83 | if (adm->Init() != 0) { 84 | RTC_LOG(LS_WARNING) << "Failed to ADM Init"; 85 | return false; 86 | } 87 | 88 | int devices = adm->RecordingDevices(); 89 | for (int i = 0; i < devices; i++) { 90 | char name[webrtc::kAdmMaxDeviceNameSize]; 91 | char guid[webrtc::kAdmMaxGuidSize]; 92 | if (adm->SetRecordingDevice(i) != 0) { 93 | RTC_LOG(LS_WARNING) << "Failed to SetRecordingDevice: index=" << i; 94 | continue; 95 | } 96 | bool available = false; 97 | if (adm->RecordingIsAvailable(&available) != 0) { 98 | RTC_LOG(LS_WARNING) << "Failed to RecordingIsAvailable: index=" << i; 99 | continue; 100 | } 101 | 102 | if (!available) { 103 | continue; 104 | } 105 | if (adm->RecordingDeviceName(i, name, guid) != 0) { 106 | RTC_LOG(LS_WARNING) << "Failed to RecordingDeviceName: index=" << i; 107 | continue; 108 | } 109 | 110 | RTC_LOG(LS_INFO) << "EnumAudioRecording: device_name=" << name 111 | << " unique_name=" << guid; 112 | f(name, guid); 113 | } 114 | return true; 115 | #endif 116 | } 117 | 118 | bool DeviceList::EnumAudioPlayout( 119 | std::function f) { 120 | auto task_queue_factory = webrtc::CreateDefaultTaskQueueFactory(); 121 | #if defined(SORA_UNITY_SDK_ANDROID) || defined(SORA_UNITY_SDK_IOS) 122 | // Android や iOS の場合常に1個しかなく、かつ adm->PlayoutDeviceName() を呼ぶと fatal error が起きるので 123 | // 適当な名前で1回だけコールバックする 124 | f("0", "0"); 125 | return true; 126 | #else 127 | 128 | #if defined(SORA_UNITY_SDK_WINDOWS) 129 | auto adm = 130 | webrtc::CreateWindowsCoreAudioAudioDeviceModule(task_queue_factory.get()); 131 | #else 132 | auto adm = webrtc::AudioDeviceModule::Create( 133 | webrtc::AudioDeviceModule::kPlatformDefaultAudio, 134 | task_queue_factory.get()); 135 | #endif 136 | if (adm->Init() != 0) { 137 | RTC_LOG(LS_WARNING) << "Failed to ADM Init"; 138 | return false; 139 | } 140 | 141 | int devices = adm->PlayoutDevices(); 142 | for (int i = 0; i < devices; i++) { 143 | char name[webrtc::kAdmMaxDeviceNameSize]; 144 | char guid[webrtc::kAdmMaxGuidSize]; 145 | if (adm->SetPlayoutDevice(i) != 0) { 146 | RTC_LOG(LS_WARNING) << "Failed to SetPlayoutDevice: index=" << i; 147 | continue; 148 | } 149 | bool available = false; 150 | if (adm->PlayoutIsAvailable(&available) != 0) { 151 | RTC_LOG(LS_WARNING) << "Failed to PlayoutIsAvailable: index=" << i; 152 | continue; 153 | } 154 | 155 | if (!available) { 156 | continue; 157 | } 158 | if (adm->PlayoutDeviceName(i, name, guid) != 0) { 159 | RTC_LOG(LS_WARNING) << "Failed to PlayoutDeviceName: index=" << i; 160 | continue; 161 | } 162 | 163 | RTC_LOG(LS_INFO) << "EnumAudioPlayout: device_name=" << name 164 | << " unique_name=" << guid; 165 | f(name, guid); 166 | } 167 | return true; 168 | #endif 169 | } 170 | 171 | } // namespace sora_unity_sdk 172 | -------------------------------------------------------------------------------- /src/device_list.h: -------------------------------------------------------------------------------- 1 | #ifndef SORA_UNITY_SDK_DEVICE_LIST_H_ 2 | #define SORA_UNITY_SDK_DEVICE_LIST_H_ 3 | 4 | #include 5 | #include 6 | 7 | namespace sora_unity_sdk { 8 | 9 | class DeviceList { 10 | public: 11 | static bool EnumVideoCapturer( 12 | std::function f); 13 | static bool EnumAudioRecording( 14 | std::function f); 15 | static bool EnumAudioPlayout(std::function f); 16 | }; 17 | 18 | } // namespace sora_unity_sdk 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/id_pointer.cpp: -------------------------------------------------------------------------------- 1 | #include "id_pointer.h" 2 | 3 | namespace sora_unity_sdk { 4 | 5 | IdPointer& IdPointer::Instance() { 6 | static IdPointer ip; 7 | return ip; 8 | } 9 | ptrid_t IdPointer::Register(void* p) { 10 | std::lock_guard guard(mutex_); 11 | map_[counter_] = p; 12 | return counter_++; 13 | } 14 | void IdPointer::Unregister(ptrid_t id) { 15 | std::lock_guard guard(mutex_); 16 | map_.erase(id); 17 | } 18 | void* IdPointer::Lookup(ptrid_t id) { 19 | std::lock_guard guard(mutex_); 20 | auto it = map_.find(id); 21 | if (it == map_.end()) { 22 | return nullptr; 23 | } 24 | return it->second; 25 | } 26 | 27 | } // namespace sora_unity_sdk 28 | -------------------------------------------------------------------------------- /src/id_pointer.h: -------------------------------------------------------------------------------- 1 | #ifndef SORA_UNITY_SDK_ID_POINTER_H_INCLUDED 2 | #define SORA_UNITY_SDK_ID_POINTER_H_INCLUDED 3 | 4 | #include 5 | #include 6 | 7 | #include "unity.h" 8 | 9 | namespace sora_unity_sdk { 10 | 11 | // TextureUpdateCallback のユーザデータが 32bit 整数しか扱えないので、 12 | // ID からポインタに変換する仕組みを用意する 13 | class IdPointer { 14 | std::mutex mutex_; 15 | ptrid_t counter_ = 1; 16 | std::map map_; 17 | 18 | public: 19 | static IdPointer& Instance(); 20 | ptrid_t Register(void* p); 21 | void Unregister(ptrid_t id); 22 | void* Lookup(ptrid_t id); 23 | }; 24 | 25 | } // namespace sora_unity_sdk 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/mac_helper/ios_audio_init.h: -------------------------------------------------------------------------------- 1 | #ifndef SORA_UNITY_SDK_MAC_HELPER_IOS_AUDIO_INIT_H_ 2 | #define SORA_UNITY_SDK_MAC_HELPER_IOS_AUDIO_INIT_H_ 3 | 4 | #include 5 | #include 6 | 7 | namespace sora_unity_sdk { 8 | 9 | void IosAudioInit(std::function on_complete); 10 | 11 | } 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/mac_helper/ios_audio_init.mm: -------------------------------------------------------------------------------- 1 | #include "ios_audio_init.h" 2 | 3 | #include 4 | 5 | #import 6 | #import 7 | 8 | namespace sora_unity_sdk { 9 | 10 | void IosAudioInit(std::function on_complete) { 11 | auto config = [RTCAudioSessionConfiguration webRTCConfiguration]; 12 | config.category = AVAudioSessionCategoryPlayAndRecord; 13 | [[RTCAudioSession sharedInstance] initializeInput:^(NSError* error) { 14 | if (error != nil) { 15 | on_complete([error.localizedDescription UTF8String]); 16 | } else { 17 | on_complete(""); 18 | } 19 | }]; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/sora.h: -------------------------------------------------------------------------------- 1 | #ifndef SORA_UNITY_SDK_SORA_H_INCLUDED 2 | #define SORA_UNITY_SDK_SORA_H_INCLUDED 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // Sora 9 | #include 10 | #include 11 | 12 | // Boost 13 | #include 14 | 15 | // WebRTC 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "id_pointer.h" 23 | #include "sora_conf.json.h" 24 | #include "sora_conf_internal.json.h" 25 | #include "unity.h" 26 | #include "unity_audio_device.h" 27 | #include "unity_camera_capturer.h" 28 | #include "unity_context.h" 29 | #include "unity_renderer.h" 30 | 31 | #ifdef SORA_UNITY_SDK_ANDROID 32 | #include 33 | #endif 34 | 35 | namespace sora_unity_sdk { 36 | 37 | class Sora : public std::enable_shared_from_this, 38 | public sora::SoraSignalingObserver { 39 | public: 40 | Sora(UnityContext* context); 41 | ~Sora(); 42 | 43 | void SetOnAddTrack(std::function on_add_track); 44 | void SetOnRemoveTrack( 45 | std::function on_remove_track); 46 | void SetOnSetOffer(std::function on_set_offer); 47 | void SetOnNotify(std::function on_notify); 48 | void SetOnPush(std::function on_push); 49 | void SetOnMessage(std::function on_message); 50 | void SetOnDisconnect(std::function on_disconnect); 51 | void SetOnDataChannel(std::function on_data_channel); 52 | void SetOnCapturerFrame(std::function on_capturer_frame); 53 | void DispatchEvents(); 54 | 55 | void Connect(const sora_conf::internal::ConnectConfig& cc); 56 | void Disconnect(); 57 | void SwitchCamera(const sora_conf::internal::CameraConfig& cc); 58 | 59 | static void UNITY_INTERFACE_API RenderCallbackStatic(int event_id); 60 | int GetRenderCallbackEventID() const; 61 | 62 | void RenderCallback(); 63 | 64 | void ProcessAudio(const void* p, int offset, int samples); 65 | void SetOnHandleAudio(std::function f); 66 | 67 | void GetStats(std::function on_get_stats); 68 | 69 | void SendMessage(const std::string& label, const std::string& data); 70 | 71 | bool GetAudioEnabled() const; 72 | void SetAudioEnabled(bool enabled); 73 | bool GetVideoEnabled() const; 74 | void SetVideoEnabled(bool enabled); 75 | 76 | std::string GetSelectedSignalingURL() const; 77 | std::string GetConnectedSignalingURL() const; 78 | 79 | private: 80 | void* GetAndroidApplicationContext(void* env); 81 | static sora_conf::ErrorCode ToErrorCode(sora::SoraSignalingErrorCode ec); 82 | 83 | // SoraSignalingObserver の実装 84 | void OnSetOffer(std::string offer) override; 85 | void OnDisconnect(sora::SoraSignalingErrorCode ec, 86 | std::string message) override; 87 | void OnNotify(std::string text) override; 88 | void OnPush(std::string text) override; 89 | void OnMessage(std::string label, std::string data) override; 90 | void OnTrack( 91 | rtc::scoped_refptr transceiver) override; 92 | void OnRemoveTrack( 93 | rtc::scoped_refptr receiver) override; 94 | void OnDataChannel(std::string label) override; 95 | 96 | private: 97 | void DoConnect(const sora_conf::internal::ConnectConfig& config, 98 | std::function on_disconnect); 99 | void DoSwitchCamera( 100 | const sora_conf::internal::CameraConfig& cc, 101 | rtc::scoped_refptr video_track); 102 | 103 | static rtc::scoped_refptr CreateADM( 104 | webrtc::TaskQueueFactory* task_queue_factory, 105 | bool dummy_audio, 106 | bool unity_audio_input, 107 | bool unity_audio_output, 108 | std::function on_handle_audio, 109 | std::string audio_recording_device, 110 | std::string audio_playout_device, 111 | rtc::Thread* worker_thread, 112 | void* jni_env, 113 | void* android_context); 114 | static bool InitADM(rtc::scoped_refptr adm, 115 | std::string audio_recording_device, 116 | std::string audio_playout_device); 117 | 118 | static rtc::scoped_refptr 119 | CreateVideoCapturer( 120 | int capturer_type, 121 | void* unity_camera_texture, 122 | bool no_video_device, 123 | std::string video_capturer_device, 124 | int video_width, 125 | int video_height, 126 | int video_fps, 127 | std::function on_frame, 128 | rtc::Thread* signaling_thread, 129 | void* jni_env, 130 | void* android_context, 131 | UnityContext* unity_context); 132 | 133 | void PushEvent(std::function f); 134 | 135 | struct CapturerSink : rtc::VideoSinkInterface { 136 | CapturerSink(rtc::scoped_refptr capturer, 137 | std::function on_frame); 138 | ~CapturerSink() override; 139 | void OnFrame(const webrtc::VideoFrame& frame) override; 140 | 141 | private: 142 | rtc::scoped_refptr capturer_; 143 | std::function on_frame_; 144 | }; 145 | 146 | private: 147 | std::unique_ptr ioc_; 148 | std::shared_ptr signaling_; 149 | UnityContext* unity_context_; 150 | std::unique_ptr renderer_; 151 | rtc::scoped_refptr audio_track_; 152 | rtc::scoped_refptr video_track_; 153 | rtc::scoped_refptr video_sender_; 154 | std::function on_add_track_; 155 | std::function on_remove_track_; 156 | std::function on_set_offer_; 157 | std::function on_notify_; 158 | std::function on_push_; 159 | std::function on_message_; 160 | std::function on_disconnect_; 161 | std::function on_data_channel_; 162 | std::function on_handle_audio_; 163 | std::function on_capturer_frame_; 164 | 165 | std::shared_ptr sora_context_; 166 | std::unique_ptr io_thread_; 167 | 168 | std::mutex event_mutex_; 169 | std::deque> event_queue_; 170 | 171 | ptrid_t ptrid_; 172 | 173 | std::map connection_ids_; 174 | 175 | std::string stream_id_; 176 | 177 | rtc::scoped_refptr capturer_; 178 | int capturer_type_ = 0; 179 | std::shared_ptr capturer_sink_; 180 | 181 | rtc::scoped_refptr unity_adm_; 182 | webrtc::TaskQueueFactory* task_queue_factory_; 183 | #if defined(SORA_UNITY_SDK_ANDROID) 184 | webrtc::ScopedJavaGlobalRef android_context_; 185 | #endif 186 | 187 | std::atomic set_offer_ = false; 188 | }; 189 | 190 | } // namespace sora_unity_sdk 191 | 192 | #endif 193 | -------------------------------------------------------------------------------- /src/sora_version.h.template: -------------------------------------------------------------------------------- 1 | #ifndef SORA_VERSION_H_INCLUDED 2 | #define SORA_VERSION_H_INCLUDED 3 | 4 | #define SORA_UNITY_SDK_VERSION "@SORA_UNITY_SDK_VERSION@" 5 | #define SORA_UNITY_SDK_COMMIT_SHORT "@SORA_UNITY_SDK_COMMIT_SHORT@" 6 | #define WEBRTC_BUILD_VERSION "@WEBRTC_BUILD_VERSION@" 7 | #define WEBRTC_READABLE_VERSION "@WEBRTC_READABLE_VERSION@" 8 | #define WEBRTC_COMMIT_SHORT "@WEBRTC_COMMIT_SHORT@" 9 | #define SORA_UNITY_SDK_PLATFORM "@SORA_UNITY_SDK_PLATFORM@" 10 | 11 | #endif // SORA_VERSION_H_INCLUDED 12 | -------------------------------------------------------------------------------- /src/unity.cpp: -------------------------------------------------------------------------------- 1 | #include "unity.h" 2 | 3 | // Sora 4 | #include 5 | #include 6 | 7 | #include "converter.h" 8 | #include "device_list.h" 9 | #include "sora.h" 10 | #include "sora_conf.json.h" 11 | #include "sora_conf_internal.json.h" 12 | #include "unity_context.h" 13 | 14 | #if defined(SORA_UNITY_SDK_WINDOWS) || defined(SORA_UNITY_SDK_UBUNTU) 15 | #include 16 | #include 17 | #endif 18 | 19 | struct SoraWrapper { 20 | std::shared_ptr sora; 21 | }; 22 | 23 | extern "C" { 24 | #if defined(SORA_UNITY_SDK_IOS) 25 | // PlaybackEngines/iOSSupport/Trampoline/Classes/Unity/UnityInterface.h から必要な定義だけ拾ってきた 26 | typedef void (*UnityPluginLoadFunc)(IUnityInterfaces* unityInterfaces); 27 | typedef void (*UnityPluginUnloadFunc)(); 28 | void UnityRegisterRenderingPluginV5(UnityPluginLoadFunc loadPlugin, 29 | UnityPluginUnloadFunc unloadPlugin); 30 | 31 | bool g_ios_plugin_registered = false; 32 | 33 | void UNITY_INTERFACE_API SoraUnitySdk_UnityPluginLoad(IUnityInterfaces* ifs); 34 | void UNITY_INTERFACE_API SoraUnitySdk_UnityPluginUnload(); 35 | #endif 36 | 37 | void* sora_create() { 38 | #if defined(SORA_UNITY_SDK_IOS) 39 | if (!g_ios_plugin_registered) { 40 | UnityRegisterRenderingPluginV5(&SoraUnitySdk_UnityPluginLoad, 41 | &SoraUnitySdk_UnityPluginUnload); 42 | g_ios_plugin_registered = true; 43 | } 44 | #endif 45 | 46 | auto context = &sora_unity_sdk::UnityContext::Instance(); 47 | if (!context->IsInitialized()) { 48 | return nullptr; 49 | } 50 | 51 | auto wsora = std::unique_ptr(new SoraWrapper()); 52 | wsora->sora = std::make_shared(context); 53 | return wsora.release(); 54 | } 55 | 56 | void sora_set_on_add_track(void* p, track_cb_t on_add_track, void* userdata) { 57 | auto wsora = (SoraWrapper*)p; 58 | wsora->sora->SetOnAddTrack( 59 | [on_add_track, userdata](ptrid_t track_id, std::string connection_id) { 60 | on_add_track(track_id, connection_id.c_str(), userdata); 61 | }); 62 | } 63 | 64 | void sora_set_on_remove_track(void* p, 65 | track_cb_t on_remove_track, 66 | void* userdata) { 67 | auto wsora = (SoraWrapper*)p; 68 | wsora->sora->SetOnRemoveTrack( 69 | [on_remove_track, userdata](ptrid_t track_id, std::string connection_id) { 70 | on_remove_track(track_id, connection_id.c_str(), userdata); 71 | }); 72 | } 73 | 74 | void sora_set_on_set_offer(void* p, 75 | set_offer_cb_t on_set_offer, 76 | void* userdata) { 77 | auto wsora = (SoraWrapper*)p; 78 | wsora->sora->SetOnSetOffer([on_set_offer, userdata](std::string json) { 79 | on_set_offer(json.c_str(), userdata); 80 | }); 81 | } 82 | 83 | void sora_set_on_notify(void* p, notify_cb_t on_notify, void* userdata) { 84 | auto wsora = (SoraWrapper*)p; 85 | wsora->sora->SetOnNotify([on_notify, userdata](std::string json) { 86 | on_notify(json.c_str(), userdata); 87 | }); 88 | } 89 | 90 | void sora_set_on_push(void* p, push_cb_t on_push, void* userdata) { 91 | auto wsora = (SoraWrapper*)p; 92 | wsora->sora->SetOnPush([on_push, userdata](std::string json) { 93 | on_push(json.c_str(), userdata); 94 | }); 95 | } 96 | 97 | void sora_set_on_message(void* p, message_cb_t on_message, void* userdata) { 98 | auto wsora = (SoraWrapper*)p; 99 | wsora->sora->SetOnMessage( 100 | [on_message, userdata](std::string label, std::string data) { 101 | on_message(label.c_str(), data.c_str(), (int)data.size(), userdata); 102 | }); 103 | } 104 | 105 | void sora_set_on_disconnect(void* p, 106 | disconnect_cb_t on_disconnect, 107 | void* userdata) { 108 | auto wsora = (SoraWrapper*)p; 109 | wsora->sora->SetOnDisconnect( 110 | [on_disconnect, userdata](int error_code, std::string reason) { 111 | on_disconnect(error_code, reason.c_str(), userdata); 112 | }); 113 | } 114 | 115 | void sora_set_on_data_channel(void* p, 116 | data_channel_cb_t on_data_channel, 117 | void* userdata) { 118 | auto wsora = (SoraWrapper*)p; 119 | wsora->sora->SetOnDataChannel([on_data_channel, userdata](std::string label) { 120 | on_data_channel(label.c_str(), userdata); 121 | }); 122 | } 123 | 124 | void sora_set_on_capturer_frame(void* p, 125 | capturer_frame_cb_t on_capturer_frame, 126 | void* userdata) { 127 | auto wsora = (SoraWrapper*)p; 128 | wsora->sora->SetOnCapturerFrame( 129 | [on_capturer_frame, userdata](std::string data) { 130 | on_capturer_frame(data.c_str(), userdata); 131 | }); 132 | } 133 | 134 | void sora_dispatch_events(void* p) { 135 | auto wsora = (SoraWrapper*)p; 136 | wsora->sora->DispatchEvents(); 137 | } 138 | 139 | void sora_connect(void* p, const char* config_json) { 140 | auto wsora = (SoraWrapper*)p; 141 | auto config = 142 | jsonif::from_json(config_json); 143 | wsora->sora->Connect(config); 144 | } 145 | 146 | void sora_disconnect(void* p) { 147 | auto wsora = (SoraWrapper*)p; 148 | wsora->sora->Disconnect(); 149 | } 150 | 151 | void sora_switch_camera(void* p, const char* config_json) { 152 | auto wsora = (SoraWrapper*)p; 153 | auto config = 154 | jsonif::from_json(config_json); 155 | wsora->sora->SwitchCamera(config); 156 | } 157 | 158 | void* sora_get_texture_update_callback() { 159 | return (void*)&sora_unity_sdk::UnityRenderer::Sink::TextureUpdateCallback; 160 | } 161 | 162 | void sora_destroy(void* sora) { 163 | delete (SoraWrapper*)sora; 164 | } 165 | 166 | void* sora_get_render_callback() { 167 | return (void*)&sora_unity_sdk::Sora::RenderCallbackStatic; 168 | } 169 | int sora_get_render_callback_event_id(void* p) { 170 | auto wsora = (SoraWrapper*)p; 171 | return wsora->sora->GetRenderCallbackEventID(); 172 | } 173 | 174 | void sora_process_audio(void* p, const void* buf, int offset, int samples) { 175 | auto wsora = (SoraWrapper*)p; 176 | wsora->sora->ProcessAudio(buf, offset, samples); 177 | } 178 | void sora_set_on_handle_audio(void* p, handle_audio_cb_t f, void* userdata) { 179 | auto wsora = (SoraWrapper*)p; 180 | wsora->sora->SetOnHandleAudio( 181 | [f, userdata](const int16_t* buf, int samples, int channels) { 182 | f(buf, samples, channels, userdata); 183 | }); 184 | } 185 | 186 | void sora_get_stats(void* p, stats_cb_t f, void* userdata) { 187 | auto wsora = (SoraWrapper*)p; 188 | wsora->sora->GetStats( 189 | [f, userdata](std::string json) { f(json.c_str(), userdata); }); 190 | } 191 | 192 | void sora_send_message(void* p, const char* label, void* buf, int size) { 193 | auto wsora = (SoraWrapper*)p; 194 | const char* s = (const char*)buf; 195 | wsora->sora->SendMessage(label, std::string(s, s + size)); 196 | } 197 | 198 | unity_bool_t sora_device_enum_video_capturer(device_enum_cb_t f, 199 | void* userdata) { 200 | return sora_unity_sdk::DeviceList::EnumVideoCapturer( 201 | [f, userdata](std::string device_name, std::string unique_name) { 202 | f(device_name.c_str(), unique_name.c_str(), userdata); 203 | }); 204 | } 205 | unity_bool_t sora_device_enum_audio_recording(device_enum_cb_t f, 206 | void* userdata) { 207 | return sora_unity_sdk::DeviceList::EnumAudioRecording( 208 | [f, userdata](std::string device_name, std::string unique_name) { 209 | f(device_name.c_str(), unique_name.c_str(), userdata); 210 | }); 211 | } 212 | unity_bool_t sora_device_enum_audio_playout(device_enum_cb_t f, 213 | void* userdata) { 214 | return sora_unity_sdk::DeviceList::EnumAudioPlayout( 215 | [f, userdata](std::string device_name, std::string unique_name) { 216 | f(device_name.c_str(), unique_name.c_str(), userdata); 217 | }); 218 | } 219 | 220 | void sora_setenv(const char* name, const char* value) { 221 | #if defined(SORA_UNITY_SDK_WINDOWS) 222 | _putenv_s(name, value); 223 | #else 224 | setenv(name, value, 1); 225 | #endif 226 | } 227 | 228 | unity_bool_t sora_get_audio_enabled(void* p) { 229 | auto wsora = (SoraWrapper*)p; 230 | return wsora->sora->GetAudioEnabled(); 231 | } 232 | void sora_set_audio_enabled(void* p, unity_bool_t enabled) { 233 | auto wsora = (SoraWrapper*)p; 234 | wsora->sora->SetAudioEnabled(enabled); 235 | } 236 | unity_bool_t sora_get_video_enabled(void* p) { 237 | auto wsora = (SoraWrapper*)p; 238 | return wsora->sora->GetVideoEnabled(); 239 | } 240 | void sora_set_video_enabled(void* p, unity_bool_t enabled) { 241 | auto wsora = (SoraWrapper*)p; 242 | wsora->sora->SetVideoEnabled(enabled); 243 | } 244 | 245 | // get_*_signaling_url_size() から get_*_signaling_url() までの間に値が変わった場合、 246 | // 落ちることは無いが、文字列が切り詰められる可能性があるので注意 247 | int sora_get_selected_signaling_url_size(void* p) { 248 | auto wsora = (SoraWrapper*)p; 249 | return wsora->sora->GetSelectedSignalingURL().size(); 250 | } 251 | int sora_get_connected_signaling_url_size(void* p) { 252 | auto wsora = (SoraWrapper*)p; 253 | return wsora->sora->GetConnectedSignalingURL().size(); 254 | } 255 | void sora_get_selected_signaling_url(void* p, void* buf, int size) { 256 | auto wsora = (SoraWrapper*)p; 257 | std::string str = wsora->sora->GetSelectedSignalingURL(); 258 | std::memcpy(buf, str.c_str(), std::min(size, (int)str.size())); 259 | } 260 | void sora_get_connected_signaling_url(void* p, void* buf, int size) { 261 | auto wsora = (SoraWrapper*)p; 262 | std::string str = wsora->sora->GetConnectedSignalingURL(); 263 | std::memcpy(buf, str.c_str(), std::min(size, (int)str.size())); 264 | } 265 | static std::optional g_video_codec_capability; 266 | int sora_get_video_codec_capability_size(const char* config) { 267 | auto c = jsonif::from_json( 268 | config); 269 | auto cc = sora_unity_sdk::ConvertToVideoCodecCapabilityConfigWithSession(c); 270 | auto capability = sora::GetVideoCodecCapability(cc); 271 | auto vcc = sora_unity_sdk::ConvertToInternalVideoCodecCapability(capability); 272 | auto result = jsonif::to_json(vcc); 273 | g_video_codec_capability = capability; 274 | return (int)result.size(); 275 | } 276 | void sora_get_video_codec_capability(const char* config, void* buf, int size) { 277 | sora::VideoCodecCapability capability; 278 | // 今の実装は sora_get_video_codec_capability_size → sora_get_video_codec_capability の順で呼ぶのは確定しているので、 279 | // sora_get_video_codec_capability_size で取得した capability を流用する 280 | if (g_video_codec_capability) { 281 | capability = *g_video_codec_capability; 282 | g_video_codec_capability.reset(); 283 | } else { 284 | // 普通に調べる 285 | auto c = jsonif::from_json( 286 | config); 287 | auto cc = sora_unity_sdk::ConvertToVideoCodecCapabilityConfigWithSession(c); 288 | capability = sora::GetVideoCodecCapability(cc); 289 | } 290 | auto vcc = sora_unity_sdk::ConvertToInternalVideoCodecCapability(capability); 291 | auto result = jsonif::to_json(vcc); 292 | if (size < (int)result.size()) { 293 | return; 294 | } 295 | std::memcpy(buf, result.c_str(), result.size()); 296 | } 297 | unity_bool_t sora_video_codec_preference_has_implementation( 298 | const char* self, 299 | const char* implementation) { 300 | auto preference = sora_unity_sdk::ConvertToVideoCodecPreference( 301 | jsonif::from_json(self)); 302 | return preference.HasImplementation( 303 | boost::json::value_to( 304 | boost::json::value(implementation))) 305 | ? 1 306 | : 0; 307 | } 308 | int sora_video_codec_preference_merge_size(const char* self, 309 | const char* preference) { 310 | auto selfobj = sora_unity_sdk::ConvertToVideoCodecPreference( 311 | jsonif::from_json(self)); 312 | auto preferenceobj = sora_unity_sdk::ConvertToVideoCodecPreference( 313 | jsonif::from_json(preference)); 314 | selfobj.Merge(preferenceobj); 315 | auto selfinternal = 316 | sora_unity_sdk::ConvertToInternalVideoCodecPreference(selfobj); 317 | auto json = jsonif::to_json(selfinternal); 318 | return (int)json.size(); 319 | } 320 | void sora_video_codec_preference_merge(const char* self, 321 | const char* preference, 322 | void* buf, 323 | int size) { 324 | auto selfobj = sora_unity_sdk::ConvertToVideoCodecPreference( 325 | jsonif::from_json(self)); 326 | auto preferenceobj = sora_unity_sdk::ConvertToVideoCodecPreference( 327 | jsonif::from_json(preference)); 328 | selfobj.Merge(preferenceobj); 329 | auto selfinternal = 330 | sora_unity_sdk::ConvertToInternalVideoCodecPreference(selfobj); 331 | auto json = jsonif::to_json(selfinternal); 332 | if (size < (int)json.size()) { 333 | return; 334 | } 335 | std::memcpy(buf, json.c_str(), json.size()); 336 | } 337 | int sora_create_video_codec_preference_from_implementation_size( 338 | const char* capability, 339 | const char* implementation) { 340 | auto vcc = sora_unity_sdk::ConvertToVideoCodecCapability( 341 | jsonif::from_json(capability)); 342 | auto r = sora::CreateVideoCodecPreferenceFromImplementation( 343 | vcc, boost::json::value_to( 344 | boost::json::value(implementation))); 345 | auto rinternal = sora_unity_sdk::ConvertToInternalVideoCodecPreference(r); 346 | auto json = jsonif::to_json(rinternal); 347 | return (int)json.size(); 348 | } 349 | void sora_create_video_codec_preference_from_implementation( 350 | const char* capability, 351 | const char* implementation, 352 | void* buf, 353 | int size) { 354 | auto vcc = sora_unity_sdk::ConvertToVideoCodecCapability( 355 | jsonif::from_json(capability)); 356 | auto r = sora::CreateVideoCodecPreferenceFromImplementation( 357 | vcc, boost::json::value_to( 358 | boost::json::value(implementation))); 359 | auto rinternal = sora_unity_sdk::ConvertToInternalVideoCodecPreference(r); 360 | auto json = jsonif::to_json(rinternal); 361 | if (size < (int)json.size()) { 362 | return; 363 | } 364 | std::memcpy(buf, json.c_str(), json.size()); 365 | } 366 | int sora_video_codec_capability_to_json_size(const char* self) { 367 | auto capability = sora_unity_sdk::ConvertToVideoCodecCapability( 368 | jsonif::from_json(self)); 369 | auto json = boost::json::serialize(boost::json::value_from(capability)); 370 | return (int)json.size(); 371 | } 372 | void sora_video_codec_capability_to_json(const char* self, 373 | void* buf, 374 | int size) { 375 | auto capability = sora_unity_sdk::ConvertToVideoCodecCapability( 376 | jsonif::from_json(self)); 377 | auto json = boost::json::serialize(boost::json::value_from(capability)); 378 | if (size < (int)json.size()) { 379 | return; 380 | } 381 | std::memcpy(buf, json.c_str(), json.size()); 382 | } 383 | int sora_video_codec_preference_to_json_size(const char* self) { 384 | auto preference = sora_unity_sdk::ConvertToVideoCodecPreference( 385 | jsonif::from_json(self)); 386 | auto json = boost::json::serialize(boost::json::value_from(preference)); 387 | return (int)json.size(); 388 | } 389 | void sora_video_codec_preference_to_json(const char* self, 390 | void* buf, 391 | int size) { 392 | auto preference = sora_unity_sdk::ConvertToVideoCodecPreference( 393 | jsonif::from_json(self)); 394 | auto json = boost::json::serialize(boost::json::value_from(preference)); 395 | if (size < (int)json.size()) { 396 | return; 397 | } 398 | std::memcpy(buf, json.c_str(), json.size()); 399 | } 400 | 401 | struct AudioOutputHelperImpl : public sora::AudioChangeRouteObserver { 402 | AudioOutputHelperImpl(change_route_cb_t cb, void* userdata) 403 | : helper_(sora::CreateAudioOutputHelper(this)), 404 | cb_(cb), 405 | userdata_(userdata) {} 406 | void OnChangeRoute() override { cb_(userdata_); } 407 | 408 | bool IsHandsfree() { return helper_->IsHandsfree(); } 409 | void SetHandsfree(bool enabled) { helper_->SetHandsfree(enabled); } 410 | 411 | private: 412 | std::unique_ptr helper_; 413 | change_route_cb_t cb_; 414 | void* userdata_; 415 | }; 416 | 417 | void* sora_audio_output_helper_create(change_route_cb_t cb, void* userdata) { 418 | return new AudioOutputHelperImpl(cb, userdata); 419 | } 420 | void sora_audio_output_helper_destroy(void* p) { 421 | delete (AudioOutputHelperImpl*)p; 422 | } 423 | unity_bool_t sora_audio_output_helper_is_handsfree(void* p) { 424 | auto helper = (AudioOutputHelperImpl*)p; 425 | return helper->IsHandsfree() ? 1 : 0; 426 | } 427 | void sora_audio_output_helper_set_handsfree(void* p, unity_bool_t enabled) { 428 | auto helper = (AudioOutputHelperImpl*)p; 429 | helper->SetHandsfree(enabled != 0); 430 | } 431 | 432 | // iOS の場合は static link で名前が被る可能性があるので、別の名前にしておく 433 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API 434 | #if defined(SORA_UNITY_SDK_IOS) 435 | SoraUnitySdk_UnityPluginLoad(IUnityInterfaces* ifs) 436 | #else 437 | UnityPluginLoad(IUnityInterfaces* ifs) 438 | #endif 439 | { 440 | sora_unity_sdk::UnityContext::Instance().Init(ifs); 441 | } 442 | 443 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API 444 | #if defined(SORA_UNITY_SDK_IOS) 445 | SoraUnitySdk_UnityPluginUnload() 446 | #else 447 | UnityPluginUnload() 448 | #endif 449 | { 450 | sora_unity_sdk::UnityContext::Instance().Shutdown(); 451 | } 452 | } 453 | -------------------------------------------------------------------------------- /src/unity.h: -------------------------------------------------------------------------------- 1 | #ifndef SORA_UNITY_SDK_UNITY_H_ 2 | #define SORA_UNITY_SDK_UNITY_H_ 3 | 4 | #include 5 | 6 | #include "unity/IUnityInterface.h" 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | typedef unsigned int ptrid_t; 13 | typedef int32_t unity_bool_t; 14 | 15 | typedef void (*track_cb_t)(ptrid_t track_id, 16 | const char* connection_id, 17 | void* userdata); 18 | typedef void (*set_offer_cb_t)(const char* json, void* userdata); 19 | typedef void (*notify_cb_t)(const char* json, void* userdata); 20 | typedef void (*push_cb_t)(const char* json, void* userdata); 21 | typedef void (*stats_cb_t)(const char* json, void* userdata); 22 | typedef void (*message_cb_t)(const char* label, 23 | const void* buf, 24 | int size, 25 | void* userdata); 26 | typedef void (*disconnect_cb_t)(int error_code, 27 | const char* reason, 28 | void* userdata); 29 | typedef void (*data_channel_cb_t)(const char* reason, void* userdata); 30 | typedef void (*capturer_frame_cb_t)(const char* data, void* userdata); 31 | 32 | UNITY_INTERFACE_EXPORT void* sora_create(); 33 | UNITY_INTERFACE_EXPORT void sora_set_on_add_track(void* p, 34 | track_cb_t on_add_track, 35 | void* userdata); 36 | UNITY_INTERFACE_EXPORT void sora_set_on_remove_track(void* p, 37 | track_cb_t on_remove_track, 38 | void* userdata); 39 | UNITY_INTERFACE_EXPORT void sora_set_on_set_offer(void* p, 40 | set_offer_cb_t on_set_offer, 41 | void* userdata); 42 | UNITY_INTERFACE_EXPORT void sora_set_on_notify(void* p, 43 | notify_cb_t on_notify, 44 | void* userdata); 45 | UNITY_INTERFACE_EXPORT void sora_set_on_push(void* p, 46 | push_cb_t on_push, 47 | void* userdata); 48 | UNITY_INTERFACE_EXPORT void sora_set_on_message(void* p, 49 | message_cb_t on_message, 50 | void* userdata); 51 | UNITY_INTERFACE_EXPORT void 52 | sora_set_on_disconnect(void* p, disconnect_cb_t on_disconnect, void* userdata); 53 | UNITY_INTERFACE_EXPORT void sora_set_on_data_channel( 54 | void* p, 55 | data_channel_cb_t on_data_channel, 56 | void* userdata); 57 | UNITY_INTERFACE_EXPORT void sora_set_on_capturer_frame(void* p, 58 | capturer_frame_cb_t f, 59 | void* userdata); 60 | UNITY_INTERFACE_EXPORT void sora_dispatch_events(void* p); 61 | UNITY_INTERFACE_EXPORT void sora_connect(void* p, const char* config); 62 | UNITY_INTERFACE_EXPORT void sora_disconnect(void* p); 63 | UNITY_INTERFACE_EXPORT void sora_switch_camera(void* p, const char* config); 64 | UNITY_INTERFACE_EXPORT void* sora_get_texture_update_callback(); 65 | UNITY_INTERFACE_EXPORT void sora_destroy(void* sora); 66 | 67 | UNITY_INTERFACE_EXPORT void* sora_get_render_callback(); 68 | UNITY_INTERFACE_EXPORT int sora_get_render_callback_event_id(void* p); 69 | 70 | UNITY_INTERFACE_EXPORT void sora_process_audio(void* p, 71 | const void* buf, 72 | int offset, 73 | int samples); 74 | typedef void (*handle_audio_cb_t)(const int16_t* buf, 75 | int samples, 76 | int channels, 77 | void* userdata); 78 | UNITY_INTERFACE_EXPORT void sora_set_on_handle_audio(void* p, 79 | handle_audio_cb_t f, 80 | void* userdata); 81 | 82 | UNITY_INTERFACE_EXPORT void sora_get_stats(void* p, 83 | stats_cb_t f, 84 | void* userdata); 85 | 86 | UNITY_INTERFACE_EXPORT void sora_send_message(void* p, 87 | const char* label, 88 | void* buf, 89 | int size); 90 | 91 | typedef void (*device_enum_cb_t)(const char* device_name, 92 | const char* unique_name, 93 | void* userdata); 94 | UNITY_INTERFACE_EXPORT unity_bool_t 95 | sora_device_enum_video_capturer(device_enum_cb_t f, void* userdata); 96 | UNITY_INTERFACE_EXPORT unity_bool_t 97 | sora_device_enum_audio_recording(device_enum_cb_t f, void* userdata); 98 | UNITY_INTERFACE_EXPORT unity_bool_t 99 | sora_device_enum_audio_playout(device_enum_cb_t f, void* userdata); 100 | UNITY_INTERFACE_EXPORT void sora_setenv(const char* name, const char* value); 101 | 102 | UNITY_INTERFACE_EXPORT unity_bool_t sora_get_audio_enabled(void* p); 103 | UNITY_INTERFACE_EXPORT void sora_set_audio_enabled(void* p, 104 | unity_bool_t enabled); 105 | UNITY_INTERFACE_EXPORT unity_bool_t sora_get_video_enabled(void* p); 106 | UNITY_INTERFACE_EXPORT void sora_set_video_enabled(void* p, 107 | unity_bool_t enabled); 108 | 109 | UNITY_INTERFACE_EXPORT int sora_get_selected_signaling_url_size(void* p); 110 | UNITY_INTERFACE_EXPORT int sora_get_connected_signaling_url_size(void* p); 111 | UNITY_INTERFACE_EXPORT void sora_get_selected_signaling_url(void* p, 112 | void* buf, 113 | int size); 114 | UNITY_INTERFACE_EXPORT void sora_get_connected_signaling_url(void* p, 115 | void* buf, 116 | int size); 117 | UNITY_INTERFACE_EXPORT int sora_get_video_codec_capability_size( 118 | const char* config); 119 | UNITY_INTERFACE_EXPORT void sora_get_video_codec_capability(const char* config, 120 | void* buf, 121 | int size); 122 | UNITY_INTERFACE_EXPORT unity_bool_t 123 | sora_video_codec_preference_has_implementation(const char* self, 124 | const char* implementation); 125 | UNITY_INTERFACE_EXPORT int sora_video_codec_preference_merge_size( 126 | const char* self, 127 | const char* preference); 128 | UNITY_INTERFACE_EXPORT void sora_video_codec_preference_merge( 129 | const char* self, 130 | const char* preference, 131 | void* buf, 132 | int size); 133 | UNITY_INTERFACE_EXPORT int 134 | sora_create_video_codec_preference_from_implementation_size( 135 | const char* capability, 136 | const char* implementation); 137 | UNITY_INTERFACE_EXPORT void 138 | sora_create_video_codec_preference_from_implementation( 139 | const char* capability, 140 | const char* implementation, 141 | void* buf, 142 | int size); 143 | UNITY_INTERFACE_EXPORT int sora_video_codec_capability_to_json_size( 144 | const char* self); 145 | UNITY_INTERFACE_EXPORT void 146 | sora_video_codec_capability_to_json(const char* self, void* buf, int size); 147 | UNITY_INTERFACE_EXPORT int sora_video_codec_preference_to_json_size( 148 | const char* self); 149 | UNITY_INTERFACE_EXPORT void 150 | sora_video_codec_preference_to_json(const char* self, void* buf, int size); 151 | 152 | typedef void (*change_route_cb_t)(void* userdata); 153 | UNITY_INTERFACE_EXPORT void* sora_audio_output_helper_create( 154 | change_route_cb_t cb, 155 | void* userdata); 156 | UNITY_INTERFACE_EXPORT void sora_audio_output_helper_destroy(void* p); 157 | UNITY_INTERFACE_EXPORT unity_bool_t 158 | sora_audio_output_helper_is_handsfree(void* p); 159 | UNITY_INTERFACE_EXPORT void sora_audio_output_helper_set_handsfree( 160 | void* p, 161 | unity_bool_t enabled); 162 | 163 | #ifdef __cplusplus 164 | } 165 | #endif 166 | 167 | #endif 168 | -------------------------------------------------------------------------------- /src/unity/IUnityGraphics.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "IUnityInterface.h" 3 | 4 | typedef enum UnityGfxRenderer 5 | { 6 | //kUnityGfxRendererOpenGL = 0, // Legacy OpenGL, removed 7 | //kUnityGfxRendererD3D9 = 1, // Direct3D 9, removed 8 | kUnityGfxRendererD3D11 = 2, // Direct3D 11 9 | kUnityGfxRendererNull = 4, // "null" device (used in batch mode) 10 | kUnityGfxRendererOpenGLES20 = 8, // OpenGL ES 2.0 11 | kUnityGfxRendererOpenGLES30 = 11, // OpenGL ES 3.0 12 | //kUnityGfxRendererGXM = 12, // PlayStation Vita, removed 13 | kUnityGfxRendererPS4 = 13, // PlayStation 4 14 | kUnityGfxRendererXboxOne = 14, // Xbox One 15 | kUnityGfxRendererMetal = 16, // iOS Metal 16 | kUnityGfxRendererOpenGLCore = 17, // OpenGL core 17 | kUnityGfxRendererD3D12 = 18, // Direct3D 12 18 | kUnityGfxRendererVulkan = 21, // Vulkan 19 | kUnityGfxRendererNvn = 22, // Nintendo Switch NVN API 20 | kUnityGfxRendererXboxOneD3D12 = 23 // MS XboxOne Direct3D 12 21 | } UnityGfxRenderer; 22 | 23 | typedef enum UnityGfxDeviceEventType 24 | { 25 | kUnityGfxDeviceEventInitialize = 0, 26 | kUnityGfxDeviceEventShutdown = 1, 27 | kUnityGfxDeviceEventBeforeReset = 2, 28 | kUnityGfxDeviceEventAfterReset = 3, 29 | } UnityGfxDeviceEventType; 30 | 31 | typedef void (UNITY_INTERFACE_API * IUnityGraphicsDeviceEventCallback)(UnityGfxDeviceEventType eventType); 32 | 33 | // Should only be used on the rendering thread unless noted otherwise. 34 | UNITY_DECLARE_INTERFACE(IUnityGraphics) 35 | { 36 | UnityGfxRenderer(UNITY_INTERFACE_API * GetRenderer)(); // Thread safe 37 | 38 | // This callback will be called when graphics device is created, destroyed, reset, etc. 39 | // It is possible to miss the kUnityGfxDeviceEventInitialize event in case plugin is loaded at a later time, 40 | // when the graphics device is already created. 41 | void(UNITY_INTERFACE_API * RegisterDeviceEventCallback)(IUnityGraphicsDeviceEventCallback callback); 42 | void(UNITY_INTERFACE_API * UnregisterDeviceEventCallback)(IUnityGraphicsDeviceEventCallback callback); 43 | int(UNITY_INTERFACE_API * ReserveEventIDRange)(int count); // reserves 'count' event IDs. Plugins should use the result as a base index when issuing events back and forth to avoid event id clashes. 44 | }; 45 | UNITY_REGISTER_INTERFACE_GUID(0x7CBA0A9CA4DDB544ULL, 0x8C5AD4926EB17B11ULL, IUnityGraphics) 46 | 47 | 48 | // Certain Unity APIs (GL.IssuePluginEvent, CommandBuffer.IssuePluginEvent) can callback into native plugins. 49 | // Provide them with an address to a function of this signature. 50 | typedef void (UNITY_INTERFACE_API * UnityRenderingEvent)(int eventId); 51 | typedef void (UNITY_INTERFACE_API * UnityRenderingEventAndData)(int eventId, void* data); 52 | -------------------------------------------------------------------------------- /src/unity/IUnityGraphicsD3D11.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "IUnityInterface.h" 3 | #include "d3d11.h" 4 | 5 | // Should only be used on the rendering thread unless noted otherwise. 6 | UNITY_DECLARE_INTERFACE(IUnityGraphicsD3D11) { 7 | ID3D11Device*(UNITY_INTERFACE_API * GetDevice)(); 8 | 9 | ID3D11Resource*(UNITY_INTERFACE_API * 10 | TextureFromRenderBuffer)(UnityRenderBuffer buffer); 11 | ID3D11Resource*(UNITY_INTERFACE_API * 12 | TextureFromNativeTexture)(UnityTextureID texture); 13 | 14 | ID3D11RenderTargetView*(UNITY_INTERFACE_API * 15 | RTVFromRenderBuffer)(UnityRenderBuffer surface); 16 | ID3D11ShaderResourceView*(UNITY_INTERFACE_API * 17 | SRVFromNativeTexture)(UnityTextureID texture); 18 | }; 19 | 20 | UNITY_REGISTER_INTERFACE_GUID(0xAAB37EF87A87D748ULL, 21 | 0xBF76967F07EFB177ULL, 22 | IUnityGraphicsD3D11) -------------------------------------------------------------------------------- /src/unity/IUnityGraphicsMetal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "IUnityInterface.h" 3 | 4 | #ifndef __OBJC__ 5 | #error metal plugin is objc code. 6 | #endif 7 | #ifndef __clang__ 8 | #error only clang compiler is supported. 9 | #endif 10 | 11 | @class NSBundle; 12 | @protocol MTLDevice; 13 | @protocol MTLCommandBuffer; 14 | @protocol MTLCommandEncoder; 15 | @protocol MTLTexture; 16 | @class MTLRenderPassDescriptor; 17 | 18 | 19 | UNITY_DECLARE_INTERFACE(IUnityGraphicsMetalV1) 20 | { 21 | NSBundle* (UNITY_INTERFACE_API * MetalBundle)(); 22 | id(UNITY_INTERFACE_API * MetalDevice)(); 23 | 24 | id(UNITY_INTERFACE_API * CurrentCommandBuffer)(); 25 | 26 | // for custom rendering support there are two scenarios: 27 | // you want to use current in-flight MTLCommandEncoder (NB: it might be nil) 28 | id(UNITY_INTERFACE_API * CurrentCommandEncoder)(); 29 | // or you might want to create your own encoder. 30 | // In that case you should end unity's encoder before creating your own and end yours before returning control to unity 31 | void(UNITY_INTERFACE_API * EndCurrentCommandEncoder)(); 32 | 33 | // returns MTLRenderPassDescriptor used to create current MTLCommandEncoder 34 | MTLRenderPassDescriptor* (UNITY_INTERFACE_API * CurrentRenderPassDescriptor)(); 35 | 36 | // converting trampoline UnityRenderBufferHandle into native RenderBuffer 37 | UnityRenderBuffer(UNITY_INTERFACE_API * RenderBufferFromHandle)(void* bufferHandle); 38 | 39 | // access to RenderBuffer's texure 40 | // NB: you pass here *native* RenderBuffer, acquired by calling (C#) RenderBuffer.GetNativeRenderBufferPtr 41 | // AAResolvedTextureFromRenderBuffer will return nil in case of non-AA RenderBuffer or if called for depth RenderBuffer 42 | // StencilTextureFromRenderBuffer will return nil in case of no-stencil RenderBuffer or if called for color RenderBuffer 43 | id(UNITY_INTERFACE_API * TextureFromRenderBuffer)(UnityRenderBuffer buffer); 44 | id(UNITY_INTERFACE_API * AAResolvedTextureFromRenderBuffer)(UnityRenderBuffer buffer); 45 | id(UNITY_INTERFACE_API * StencilTextureFromRenderBuffer)(UnityRenderBuffer buffer); 46 | }; 47 | UNITY_REGISTER_INTERFACE_GUID(0x29F8F3D03833465EULL, 0x92138551C15D823DULL, IUnityGraphicsMetalV1) 48 | 49 | 50 | // deprecated: please use versioned interface above 51 | 52 | UNITY_DECLARE_INTERFACE(IUnityGraphicsMetal) 53 | { 54 | NSBundle* (UNITY_INTERFACE_API * MetalBundle)(); 55 | id(UNITY_INTERFACE_API * MetalDevice)(); 56 | 57 | id(UNITY_INTERFACE_API * CurrentCommandBuffer)(); 58 | id(UNITY_INTERFACE_API * CurrentCommandEncoder)(); 59 | void(UNITY_INTERFACE_API * EndCurrentCommandEncoder)(); 60 | MTLRenderPassDescriptor* (UNITY_INTERFACE_API * CurrentRenderPassDescriptor)(); 61 | 62 | UnityRenderBuffer(UNITY_INTERFACE_API * RenderBufferFromHandle)(void* bufferHandle); 63 | 64 | id(UNITY_INTERFACE_API * TextureFromRenderBuffer)(UnityRenderBuffer buffer); 65 | id(UNITY_INTERFACE_API * AAResolvedTextureFromRenderBuffer)(UnityRenderBuffer buffer); 66 | id(UNITY_INTERFACE_API * StencilTextureFromRenderBuffer)(UnityRenderBuffer buffer); 67 | }; 68 | UNITY_REGISTER_INTERFACE_GUID(0x992C8EAEA95811E5ULL, 0x9A62C4B5B9876117ULL, IUnityGraphicsMetal) 69 | -------------------------------------------------------------------------------- /src/unity/IUnityGraphicsVulkan.h: -------------------------------------------------------------------------------- 1 | // Unity Native Plugin API copyright © 2015 Unity Technologies ApS 2 | // 3 | // Licensed under the Unity Companion License for Unity - dependent projects--see[Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License). 4 | // 5 | // Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.Please review the license for details on these and other terms and conditions. 6 | 7 | #pragma once 8 | #include "IUnityInterface.h" 9 | 10 | #ifndef UNITY_VULKAN_HEADER 11 | #define UNITY_VULKAN_HEADER 12 | #endif 13 | 14 | #include UNITY_VULKAN_HEADER 15 | 16 | struct UnityVulkanInstance 17 | { 18 | VkPipelineCache pipelineCache; // Unity's pipeline cache is serialized to disk 19 | VkInstance instance; 20 | VkPhysicalDevice physicalDevice; 21 | VkDevice device; 22 | VkQueue graphicsQueue; 23 | PFN_vkGetInstanceProcAddr getInstanceProcAddr; // vkGetInstanceProcAddr of the Vulkan loader, same as the one passed to UnityVulkanInitCallback 24 | unsigned int queueFamilyIndex; 25 | 26 | void* reserved[8]; 27 | }; 28 | 29 | struct UnityVulkanMemory 30 | { 31 | VkDeviceMemory memory; // Vulkan memory handle 32 | VkDeviceSize offset; // offset within memory 33 | VkDeviceSize size; // size in bytes, may be less than the total size of memory; 34 | void* mapped; // pointer to mapped memory block, NULL if not mappable, offset is already applied, remaining block still has at least the given size. 35 | VkMemoryPropertyFlags flags; // Vulkan memory properties 36 | unsigned int memoryTypeIndex; // index into VkPhysicalDeviceMemoryProperties::memoryTypes 37 | 38 | void* reserved[4]; 39 | }; 40 | 41 | enum UnityVulkanResourceAccessMode 42 | { 43 | // Does not imply any pipeline barriers, should only be used to query resource attributes 44 | kUnityVulkanResourceAccess_ObserveOnly, 45 | 46 | // Handles layout transition and barriers 47 | kUnityVulkanResourceAccess_PipelineBarrier, 48 | 49 | // Recreates the backing resource (VkBuffer/VkImage) but keeps the previous one alive if it's in use 50 | kUnityVulkanResourceAccess_Recreate, 51 | }; 52 | 53 | struct UnityVulkanImage 54 | { 55 | UnityVulkanMemory memory; // memory that backs the image 56 | VkImage image; // Vulkan image handle 57 | VkImageLayout layout; // current layout, may change resource access 58 | VkImageAspectFlags aspect; 59 | VkImageUsageFlags usage; 60 | VkFormat format; 61 | VkExtent3D extent; 62 | VkImageTiling tiling; 63 | VkImageType type; 64 | VkSampleCountFlagBits samples; 65 | int layers; 66 | int mipCount; 67 | 68 | void* reserved[4]; 69 | }; 70 | 71 | struct UnityVulkanBuffer 72 | { 73 | UnityVulkanMemory memory; // memory that backs the buffer 74 | VkBuffer buffer; // Vulkan buffer handle 75 | size_t sizeInBytes; // size of the buffer in bytes, may be less than memory size 76 | VkBufferUsageFlags usage; 77 | 78 | void* reserved[4]; 79 | }; 80 | 81 | struct UnityVulkanRecordingState 82 | { 83 | VkCommandBuffer commandBuffer; // Vulkan command buffer that is currently recorded by Unity 84 | VkCommandBufferLevel commandBufferLevel; 85 | VkRenderPass renderPass; // Current render pass, a compatible one or VK_NULL_HANDLE 86 | VkFramebuffer framebuffer; // Current framebuffer or VK_NULL_HANDLE 87 | int subPassIndex; // index of the current sub pass, -1 if not inside a render pass 88 | 89 | // Resource life-time tracking counters, only relevant for resources allocated by the plugin 90 | unsigned long long currentFrameNumber; // can be used to track lifetime of own resources 91 | unsigned long long safeFrameNumber; // all resources that were used in this frame (or before) are safe to be released 92 | 93 | void* reserved[4]; 94 | }; 95 | 96 | enum UnityVulkanEventRenderPassPreCondition 97 | { 98 | // Don't care about the state on Unity's current command buffer 99 | // This is the default precondition 100 | kUnityVulkanRenderPass_DontCare, 101 | 102 | // Make sure that there is currently no RenderPass in progress. 103 | // This allows e.g. resource uploads. 104 | // There are no guarantees about the currently bound descriptor sets, vertex buffers, index buffers and pipeline objects 105 | // Unity does however set dynamic pipeline set VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR based on the current settings 106 | // If used in combination with the SRP RenderPass API the resuls is undefined 107 | kUnityVulkanRenderPass_EnsureInside, 108 | 109 | // Make sure that there is currently no RenderPass in progress. 110 | // Ends the current render pass (and resumes it afterwards if needed) 111 | // If used in combination with the SRP RenderPass API the resuls is undefined. 112 | kUnityVulkanRenderPass_EnsureOutside 113 | }; 114 | 115 | enum UnityVulkanGraphicsQueueAccess 116 | { 117 | // No queue acccess, no work must be submitted to UnityVulkanInstance::graphicsQueue from the plugin event callback 118 | kUnityVulkanGraphicsQueueAccess_DontCare, 119 | 120 | // Make sure that Unity worker threads don't access the Vulkan graphics queue 121 | // This disables access to the current Unity command buffer 122 | kUnityVulkanGraphicsQueueAccess_Allow, 123 | }; 124 | 125 | enum UnityVulkanEventConfigFlagBits 126 | { 127 | kUnityVulkanEventConfigFlag_EnsurePreviousFrameSubmission = (1 << 0), // default: set 128 | kUnityVulkanEventConfigFlag_FlushCommandBuffers = (1 << 1), // submit existing command buffers, default: not set 129 | kUnityVulkanEventConfigFlag_SyncWorkerThreads = (1 << 2), // wait for worker threads to finish, default: not set 130 | kUnityVulkanEventConfigFlag_ModifiesCommandBuffersState = (1 << 3), // should be set when descriptor set bindings, vertex buffer bindings, etc are changed (default: set) 131 | }; 132 | 133 | struct UnityVulkanPluginEventConfig 134 | { 135 | UnityVulkanEventRenderPassPreCondition renderPassPrecondition; 136 | UnityVulkanGraphicsQueueAccess graphicsQueueAccess; 137 | uint32_t flags; 138 | }; 139 | 140 | // Constant that can be used to reference the whole image 141 | const VkImageSubresource* const UnityVulkanWholeImage = NULL; 142 | 143 | // callback function, see InterceptInitialization 144 | typedef PFN_vkGetInstanceProcAddr(UNITY_INTERFACE_API * UnityVulkanInitCallback)(PFN_vkGetInstanceProcAddr getInstanceProcAddr, void* userdata); 145 | 146 | enum UnityVulkanSwapchainMode 147 | { 148 | kUnityVulkanSwapchainMode_Default, 149 | kUnityVulkanSwapchainMode_Offscreen 150 | }; 151 | 152 | struct UnityVulkanSwapchainConfiguration 153 | { 154 | UnityVulkanSwapchainMode mode; 155 | }; 156 | 157 | UNITY_DECLARE_INTERFACE(IUnityGraphicsVulkan) 158 | { 159 | // Vulkan API hooks 160 | // 161 | // Must be called before kUnityGfxDeviceEventInitialize (preload plugin) 162 | // Unity will call 'func' when initializing the Vulkan API 163 | // The 'getInstanceProcAddr' passed to the callback is the function pointer from the Vulkan Loader 164 | // The function pointer returned from UnityVulkanInitCallback may be a different implementation 165 | // This allows intercepting all Vulkan API calls 166 | // 167 | // Most rules/restrictions for implementing a Vulkan layer apply 168 | // Returns true on success, false on failure (typically because it is used too late) 169 | bool(UNITY_INTERFACE_API * InterceptInitialization)(UnityVulkanInitCallback func, void* userdata); 170 | 171 | // Intercept Vulkan API function of the given name with the given function 172 | // In contrast to InterceptInitialization this interface can be used at any time 173 | // The user must handle all synchronization 174 | // Generally this cannot be used to wrap Vulkan object because there might because there may already be non-wrapped instances 175 | // returns the previous function pointer 176 | PFN_vkVoidFunction(UNITY_INTERFACE_API * InterceptVulkanAPI)(const char* name, PFN_vkVoidFunction func); 177 | 178 | // Change the precondition for a specific user-defined event 179 | // Should be called during initialization 180 | void(UNITY_INTERFACE_API * ConfigureEvent)(int eventID, const UnityVulkanPluginEventConfig * pluginEventConfig); 181 | 182 | // Access the Vulkan instance and render queue created by Unity 183 | // UnityVulkanInstance does not change between kUnityGfxDeviceEventInitialize and kUnityGfxDeviceEventShutdown 184 | UnityVulkanInstance(UNITY_INTERFACE_API * Instance)(); 185 | 186 | // Access the current command buffer 187 | // 188 | // outCommandRecordingState is invalidated by any resource access calls. 189 | // queueAccess must be kUnityVulkanGraphicsQueueAccess_Allow when called from from a AccessQueue callback or from a event that is configured for queue access. 190 | // Otherwise queueAccess must be kUnityVulkanGraphicsQueueAccess_DontCare. 191 | bool(UNITY_INTERFACE_API * CommandRecordingState)(UnityVulkanRecordingState * outCommandRecordingState, UnityVulkanGraphicsQueueAccess queueAccess); 192 | 193 | // Resource access 194 | // 195 | // Using the following resource query APIs will mark the resources as used for the current frame. 196 | // Pipeline barriers will be inserted when needed. 197 | // 198 | // Resource access APIs may record commands, so the current UnityVulkanRecordingState is invalidated 199 | // Must not be called from event callbacks configured for queue access (UnityVulkanGraphicsQueueAccess_Allow) 200 | // or from a AccessQueue callback of an event 201 | bool(UNITY_INTERFACE_API * AccessTexture)(void* nativeTexture, const VkImageSubresource * subResource, VkImageLayout layout, 202 | VkPipelineStageFlags pipelineStageFlags, VkAccessFlags accessFlags, UnityVulkanResourceAccessMode accessMode, UnityVulkanImage * outImage); 203 | 204 | bool(UNITY_INTERFACE_API * AccessRenderBufferTexture)(UnityRenderBuffer nativeRenderBuffer, const VkImageSubresource * subResource, VkImageLayout layout, 205 | VkPipelineStageFlags pipelineStageFlags, VkAccessFlags accessFlags, UnityVulkanResourceAccessMode accessMode, UnityVulkanImage * outImage); 206 | 207 | bool(UNITY_INTERFACE_API * AccessRenderBufferResolveTexture)(UnityRenderBuffer nativeRenderBuffer, const VkImageSubresource * subResource, VkImageLayout layout, 208 | VkPipelineStageFlags pipelineStageFlags, VkAccessFlags accessFlags, UnityVulkanResourceAccessMode accessMode, UnityVulkanImage * outImage); 209 | 210 | bool(UNITY_INTERFACE_API * AccessBuffer)(void* nativeBuffer, VkPipelineStageFlags pipelineStageFlags, VkAccessFlags accessFlags, UnityVulkanResourceAccessMode accessMode, UnityVulkanBuffer * outBuffer); 211 | 212 | // Control current state of render pass 213 | // 214 | // Must not be called from event callbacks configured for queue access (UnityVulkanGraphicsQueueAccess_Allow, UnityVulkanGraphicsQueueAccess_FlushAndAllow) 215 | // or from a AccessQueue callback of an event 216 | // See kUnityVulkanRenderPass_EnsureInside, kUnityVulkanRenderPass_EnsureOutside 217 | void(UNITY_INTERFACE_API * EnsureOutsideRenderPass)(); 218 | void(UNITY_INTERFACE_API * EnsureInsideRenderPass)(); 219 | 220 | // Allow command buffer submission to the the Vulkan graphics queue from the given UnityRenderingEventAndData callback. 221 | // This is an alternative to using ConfigureEvent with kUnityVulkanGraphicsQueueAccess_Allow. 222 | // 223 | // eventId and userdata are passed to the callback 224 | // This may or may not be called synchronously or from the submission thread. 225 | // If flush is true then all Unity command buffers of this frame are submitted before UnityQueueAccessCallback 226 | void(UNITY_INTERFACE_API * AccessQueue)(UnityRenderingEventAndData, int eventId, void* userData, bool flush); 227 | 228 | // Configure swapchains that are created by Unity. 229 | // Must be called before kUnityGfxDeviceEventInitialize (preload plugin) 230 | bool(UNITY_INTERFACE_API * ConfigureSwapchain)(const UnityVulkanSwapchainConfiguration * swapChainConfig); 231 | 232 | // see AccessTexture 233 | // Accepts UnityTextureID (UnityRenderingExtTextureUpdateParamsV2::textureID, UnityRenderingExtCustomBlitParams::source) 234 | bool(UNITY_INTERFACE_API * AccessTextureByID)(UnityTextureID textureID, const VkImageSubresource * subResource, VkImageLayout layout, 235 | VkPipelineStageFlags pipelineStageFlags, VkAccessFlags accessFlags, UnityVulkanResourceAccessMode accessMode, UnityVulkanImage * outImage); 236 | }; 237 | UNITY_REGISTER_INTERFACE_GUID(0x95355348d4ef4e11ULL, 0x9789313dfcffcc87ULL, IUnityGraphicsVulkan) 238 | -------------------------------------------------------------------------------- /src/unity/IUnityInterface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Unity native plugin API 4 | // Compatible with C99 5 | 6 | #if defined(__CYGWIN32__) 7 | #define UNITY_INTERFACE_API __stdcall 8 | #define UNITY_INTERFACE_EXPORT __declspec(dllexport) 9 | #elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(WINAPI_FAMILY) 10 | #define UNITY_INTERFACE_API __stdcall 11 | #define UNITY_INTERFACE_EXPORT __declspec(dllexport) 12 | #elif defined(__MACH__) || defined(__ANDROID__) || defined(__linux__) || defined(LUMIN) 13 | #define UNITY_INTERFACE_API 14 | #define UNITY_INTERFACE_EXPORT __attribute__ ((visibility ("default"))) 15 | #else 16 | #define UNITY_INTERFACE_API 17 | #define UNITY_INTERFACE_EXPORT 18 | #endif 19 | 20 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 21 | // IUnityInterface is a registry of interfaces we choose to expose to plugins. 22 | // 23 | // USAGE: 24 | // --------- 25 | // To retrieve an interface a user can do the following from a plugin, assuming they have the header file for the interface: 26 | // 27 | // IMyInterface * ptr = registry->Get(); 28 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 29 | 30 | // Unity Interface GUID 31 | // Ensures global uniqueness. 32 | // 33 | // Template specialization is used to produce a means of looking up a GUID from its interface type at compile time. 34 | // The net result should compile down to passing around the GUID. 35 | // 36 | // UNITY_REGISTER_INTERFACE_GUID should be placed in the header file of any interface definition outside of all namespaces. 37 | // The interface structure and the registration GUID are all that is required to expose the interface to other systems. 38 | struct UnityInterfaceGUID 39 | { 40 | #ifdef __cplusplus 41 | UnityInterfaceGUID(unsigned long long high, unsigned long long low) 42 | : m_GUIDHigh(high) 43 | , m_GUIDLow(low) 44 | { 45 | } 46 | 47 | UnityInterfaceGUID(const UnityInterfaceGUID& other) 48 | { 49 | m_GUIDHigh = other.m_GUIDHigh; 50 | m_GUIDLow = other.m_GUIDLow; 51 | } 52 | 53 | UnityInterfaceGUID& operator=(const UnityInterfaceGUID& other) 54 | { 55 | m_GUIDHigh = other.m_GUIDHigh; 56 | m_GUIDLow = other.m_GUIDLow; 57 | return *this; 58 | } 59 | 60 | bool Equals(const UnityInterfaceGUID& other) const { return m_GUIDHigh == other.m_GUIDHigh && m_GUIDLow == other.m_GUIDLow; } 61 | bool LessThan(const UnityInterfaceGUID& other) const { return m_GUIDHigh < other.m_GUIDHigh || (m_GUIDHigh == other.m_GUIDHigh && m_GUIDLow < other.m_GUIDLow); } 62 | #endif 63 | unsigned long long m_GUIDHigh; 64 | unsigned long long m_GUIDLow; 65 | }; 66 | #ifdef __cplusplus 67 | inline bool operator==(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return left.Equals(right); } 68 | inline bool operator!=(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return !left.Equals(right); } 69 | inline bool operator<(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return left.LessThan(right); } 70 | inline bool operator>(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return right.LessThan(left); } 71 | inline bool operator>=(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return !operator<(left, right); } 72 | inline bool operator<=(const UnityInterfaceGUID& left, const UnityInterfaceGUID& right) { return !operator>(left, right); } 73 | #else 74 | typedef struct UnityInterfaceGUID UnityInterfaceGUID; 75 | #endif 76 | 77 | 78 | #ifdef __cplusplus 79 | #define UNITY_DECLARE_INTERFACE(NAME) \ 80 | struct NAME : IUnityInterface 81 | 82 | // Generic version of GetUnityInterfaceGUID to allow us to specialize it 83 | // per interface below. The generic version has no actual implementation 84 | // on purpose. 85 | // 86 | // If you get errors about return values related to this method then 87 | // you have forgotten to include UNITY_REGISTER_INTERFACE_GUID with 88 | // your interface, or it is not visible at some point when you are 89 | // trying to retrieve or add an interface. 90 | template 91 | inline const UnityInterfaceGUID GetUnityInterfaceGUID(); 92 | 93 | // This is the macro you provide in your public interface header 94 | // outside of a namespace to allow us to map between type and GUID 95 | // without the user having to worry about it when attempting to 96 | // add or retrieve and interface from the registry. 97 | #define UNITY_REGISTER_INTERFACE_GUID(HASHH, HASHL, TYPE) \ 98 | template<> \ 99 | inline const UnityInterfaceGUID GetUnityInterfaceGUID() \ 100 | { \ 101 | return UnityInterfaceGUID(HASHH,HASHL); \ 102 | } 103 | 104 | // Same as UNITY_REGISTER_INTERFACE_GUID but allows the interface to live in 105 | // a particular namespace. As long as the namespace is visible at the time you call 106 | // GetUnityInterfaceGUID< INTERFACETYPE >() or you explicitly qualify it in the template 107 | // calls this will work fine, only the macro here needs to have the additional parameter 108 | #define UNITY_REGISTER_INTERFACE_GUID_IN_NAMESPACE(HASHH, HASHL, TYPE, NAMESPACE) \ 109 | const UnityInterfaceGUID TYPE##_GUID(HASHH, HASHL); \ 110 | template<> \ 111 | inline const UnityInterfaceGUID GetUnityInterfaceGUID< NAMESPACE :: TYPE >() \ 112 | { \ 113 | return UnityInterfaceGUID(HASHH,HASHL); \ 114 | } 115 | 116 | // These macros allow for C compatibility in user code. 117 | #define UNITY_GET_INTERFACE_GUID(TYPE) GetUnityInterfaceGUID< TYPE >() 118 | 119 | 120 | #else 121 | #define UNITY_DECLARE_INTERFACE(NAME) \ 122 | typedef struct NAME NAME; \ 123 | struct NAME 124 | 125 | // NOTE: This has the downside that one some compilers it will not get stripped from all compilation units that 126 | // can see a header containing this constant. However, it's only for C compatibility and thus should have 127 | // minimal impact. 128 | #define UNITY_REGISTER_INTERFACE_GUID(HASHH, HASHL, TYPE) \ 129 | const UnityInterfaceGUID TYPE##_GUID = {HASHH, HASHL}; 130 | 131 | // In general namespaces are going to be a problem for C code any interfaces we expose in a namespace are 132 | // not going to be usable from C. 133 | #define UNITY_REGISTER_INTERFACE_GUID_IN_NAMESPACE(HASHH, HASHL, TYPE, NAMESPACE) 134 | 135 | // These macros allow for C compatibility in user code. 136 | #define UNITY_GET_INTERFACE_GUID(TYPE) TYPE##_GUID 137 | #endif 138 | 139 | // Using this in user code rather than INTERFACES->Get() will be C compatible for those places in plugins where 140 | // this may be needed. Unity code itself does not need this. 141 | #define UNITY_GET_INTERFACE(INTERFACES, TYPE) (TYPE*)INTERFACES->GetInterfaceSplit (UNITY_GET_INTERFACE_GUID(TYPE).m_GUIDHigh, UNITY_GET_INTERFACE_GUID(TYPE).m_GUIDLow); 142 | 143 | 144 | #ifdef __cplusplus 145 | struct IUnityInterface 146 | { 147 | }; 148 | #else 149 | typedef void IUnityInterface; 150 | #endif 151 | 152 | 153 | typedef struct IUnityInterfaces 154 | { 155 | // Returns an interface matching the guid. 156 | // Returns nullptr if the given interface is unavailable in the active Unity runtime. 157 | IUnityInterface* (UNITY_INTERFACE_API * GetInterface)(UnityInterfaceGUID guid); 158 | 159 | // Registers a new interface. 160 | void(UNITY_INTERFACE_API * RegisterInterface)(UnityInterfaceGUID guid, IUnityInterface * ptr); 161 | 162 | // Split APIs for C 163 | IUnityInterface* (UNITY_INTERFACE_API * GetInterfaceSplit)(unsigned long long guidHigh, unsigned long long guidLow); 164 | void(UNITY_INTERFACE_API * RegisterInterfaceSplit)(unsigned long long guidHigh, unsigned long long guidLow, IUnityInterface * ptr); 165 | 166 | #ifdef __cplusplus 167 | // Helper for GetInterface. 168 | template 169 | INTERFACE* Get() 170 | { 171 | return static_cast(GetInterface(GetUnityInterfaceGUID())); 172 | } 173 | 174 | // Helper for RegisterInterface. 175 | template 176 | void Register(IUnityInterface* ptr) 177 | { 178 | RegisterInterface(GetUnityInterfaceGUID(), ptr); 179 | } 180 | 181 | #endif 182 | } IUnityInterfaces; 183 | 184 | 185 | #ifdef __cplusplus 186 | extern "C" { 187 | #endif 188 | 189 | // If exported by a plugin, this function will be called when the plugin is loaded. 190 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces); 191 | // If exported by a plugin, this function will be called when the plugin is about to be unloaded. 192 | void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginUnload(); 193 | 194 | #ifdef __cplusplus 195 | } 196 | #endif 197 | 198 | struct RenderSurfaceBase; 199 | typedef struct RenderSurfaceBase* UnityRenderBuffer; 200 | typedef unsigned int UnityTextureID; 201 | -------------------------------------------------------------------------------- /src/unity_audio_device.h: -------------------------------------------------------------------------------- 1 | #ifndef SORA_UNITY_SDK_UNITY_AUDIO_DEVICE_H_INCLUDED 2 | #define SORA_UNITY_SDK_UNITY_AUDIO_DEVICE_H_INCLUDED 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // webrtc 10 | #include "modules/audio_device/audio_device_buffer.h" 11 | #include "modules/audio_device/include/audio_device.h" 12 | #include "rtc_base/ref_counted_object.h" 13 | #include "rtc_base/thread.h" 14 | 15 | namespace sora_unity_sdk { 16 | 17 | class UnityAudioDevice : public webrtc::AudioDeviceModule { 18 | public: 19 | UnityAudioDevice( 20 | rtc::scoped_refptr adm, 21 | bool adm_recording, 22 | bool adm_playout, 23 | std::function 24 | on_handle_audio, 25 | webrtc::TaskQueueFactory* task_queue_factory) 26 | : adm_(adm), 27 | adm_recording_(adm_recording), 28 | adm_playout_(adm_playout), 29 | on_handle_audio_(on_handle_audio), 30 | task_queue_factory_(task_queue_factory) {} 31 | 32 | ~UnityAudioDevice() override { 33 | RTC_LOG(LS_INFO) << "~UnityAudioDevice"; 34 | Terminate(); 35 | } 36 | 37 | static rtc::scoped_refptr Create( 38 | rtc::scoped_refptr adm, 39 | bool adm_recording, 40 | bool adm_playout, 41 | std::function 42 | on_handle_audio, 43 | webrtc::TaskQueueFactory* task_queue_factory) { 44 | return rtc::make_ref_counted( 45 | adm, adm_recording, adm_playout, on_handle_audio, task_queue_factory); 46 | } 47 | 48 | void ProcessAudioData(const float* data, int32_t size) { 49 | if (!adm_recording_ && initialized_ && is_recording_) { 50 | for (int i = 0; i < size; i++) { 51 | #pragma warning(suppress : 4244) 52 | converted_audio_data_.push_back(data[i] >= 0 ? data[i] * SHRT_MAX 53 | : data[i] * -SHRT_MIN); 54 | } 55 | //opus supports up to 48khz sample rate, enforce 48khz here for quality 56 | int chunk_size = 48000 * 2 / 100; 57 | while (converted_audio_data_.size() > chunk_size) { 58 | device_buffer_->SetRecordedBuffer(converted_audio_data_.data(), 59 | chunk_size / 2); 60 | device_buffer_->DeliverRecordedData(); 61 | converted_audio_data_.erase(converted_audio_data_.begin(), 62 | converted_audio_data_.begin() + chunk_size); 63 | } 64 | } 65 | } 66 | 67 | //webrtc::AudioDeviceModule 68 | // Retrieve the currently utilized audio layer 69 | virtual int32_t ActiveAudioLayer(AudioLayer* audioLayer) const override { 70 | //*audioLayer = AudioDeviceModule::kPlatformDefaultAudio; 71 | adm_->ActiveAudioLayer(audioLayer); 72 | return 0; 73 | } 74 | // Full-duplex transportation of PCM audio 75 | virtual int32_t RegisterAudioCallback( 76 | webrtc::AudioTransport* audioCallback) override { 77 | RTC_LOG(LS_INFO) << "RegisterAudioCallback"; 78 | if (!adm_recording_ || !adm_playout_) { 79 | device_buffer_->RegisterAudioCallback(audioCallback); 80 | } 81 | return adm_->RegisterAudioCallback(audioCallback); 82 | } 83 | 84 | // Main initialization and termination 85 | virtual int32_t Init() override { 86 | RTC_LOG(LS_INFO) << "Init"; 87 | device_buffer_ = 88 | std::make_unique(task_queue_factory_); 89 | initialized_ = true; 90 | return adm_->Init(); 91 | } 92 | virtual int32_t Terminate() override { 93 | RTC_LOG(LS_INFO) << "Terminate"; 94 | 95 | DoStopPlayout(); 96 | 97 | initialized_ = false; 98 | is_recording_ = false; 99 | is_playing_ = false; 100 | device_buffer_.reset(); 101 | 102 | auto result = adm_->Terminate(); 103 | 104 | RTC_LOG(LS_INFO) << "Terminate Completed"; 105 | 106 | return result; 107 | } 108 | virtual bool Initialized() const override { 109 | return initialized_ && adm_->Initialized(); 110 | } 111 | 112 | // Device enumeration 113 | virtual int16_t PlayoutDevices() override { 114 | RTC_LOG(LS_INFO) << "PlayoutDevices"; 115 | return adm_playout_ ? adm_->PlayoutDevices() : 0; 116 | } 117 | virtual int16_t RecordingDevices() override { 118 | return adm_recording_ ? adm_->RecordingDevices() : 0; 119 | } 120 | virtual int32_t PlayoutDeviceName( 121 | uint16_t index, 122 | char name[webrtc::kAdmMaxDeviceNameSize], 123 | char guid[webrtc::kAdmMaxGuidSize]) override { 124 | return adm_playout_ ? adm_->PlayoutDeviceName(index, name, guid) : 0; 125 | } 126 | virtual int32_t RecordingDeviceName( 127 | uint16_t index, 128 | char name[webrtc::kAdmMaxDeviceNameSize], 129 | char guid[webrtc::kAdmMaxGuidSize]) override { 130 | return adm_recording_ ? adm_->RecordingDeviceName(index, name, guid) : 0; 131 | } 132 | 133 | // Device selection 134 | virtual int32_t SetPlayoutDevice(uint16_t index) override { 135 | RTC_LOG(LS_INFO) << "SetPlayoutDevice(" << index << ")"; 136 | return adm_playout_ ? adm_->SetPlayoutDevice(index) : 0; 137 | } 138 | virtual int32_t SetPlayoutDevice(WindowsDeviceType device) override { 139 | return adm_playout_ ? adm_->SetPlayoutDevice(device) : 0; 140 | } 141 | virtual int32_t SetRecordingDevice(uint16_t index) override { 142 | RTC_LOG(LS_INFO) << "SetRecordingDevice(" << index << ")"; 143 | return adm_recording_ ? adm_->SetRecordingDevice(index) : 0; 144 | } 145 | virtual int32_t SetRecordingDevice(WindowsDeviceType device) override { 146 | return adm_recording_ ? adm_->SetRecordingDevice(device) : 0; 147 | } 148 | 149 | void HandleAudioData() { 150 | int channels = stereo_playout_ ? 2 : 1; 151 | auto next_at = std::chrono::steady_clock::now(); 152 | while (!handle_audio_thread_stopped_) { 153 | // 10 ミリ秒ごとにオーディオデータを取得する 154 | next_at += std::chrono::milliseconds(10); 155 | std::this_thread::sleep_until(next_at); 156 | 157 | int chunk_size = 48000 / 100; 158 | int samples = device_buffer_->RequestPlayoutData(chunk_size); 159 | 160 | //RTC_LOG(LS_INFO) << "handle audio data: chunk_size=" << chunk_size 161 | // << " samples=" << samples; 162 | 163 | std::unique_ptr audio_buffer(new int16_t[samples * channels]); 164 | device_buffer_->GetPlayoutData(audio_buffer.get()); 165 | if (on_handle_audio_) { 166 | on_handle_audio_(audio_buffer.get(), samples, channels); 167 | } 168 | } 169 | } 170 | 171 | // Audio transport initialization 172 | virtual int32_t PlayoutIsAvailable(bool* available) override { 173 | RTC_LOG(LS_INFO) << "PlayoutIsAvailable"; 174 | 175 | if (adm_playout_) { 176 | return adm_->PlayoutIsAvailable(available); 177 | } else { 178 | *available = true; 179 | return 0; 180 | } 181 | } 182 | virtual int32_t InitPlayout() override { 183 | RTC_LOG(LS_INFO) << "InitPlayout"; 184 | 185 | if (adm_playout_) { 186 | return adm_->InitPlayout(); 187 | } else { 188 | DoStopPlayout(); 189 | 190 | is_playing_ = true; 191 | device_buffer_->SetPlayoutSampleRate(48000); 192 | device_buffer_->SetPlayoutChannels(stereo_playout_ ? 2 : 1); 193 | 194 | return 0; 195 | } 196 | } 197 | virtual bool PlayoutIsInitialized() const override { 198 | auto result = 199 | adm_playout_ ? adm_->PlayoutIsInitialized() : (bool)is_playing_; 200 | RTC_LOG(LS_INFO) << "PlayoutIsInitialized: result=" << result; 201 | return result; 202 | } 203 | virtual int32_t RecordingIsAvailable(bool* available) override { 204 | if (adm_recording_) { 205 | return adm_->RecordingIsAvailable(available); 206 | } else { 207 | *available = true; 208 | return 0; 209 | } 210 | } 211 | virtual int32_t InitRecording() override { 212 | if (adm_recording_) { 213 | return adm_->InitRecording(); 214 | } else { 215 | is_recording_ = true; 216 | device_buffer_->SetRecordingSampleRate(48000); 217 | device_buffer_->SetRecordingChannels(2); 218 | return 0; 219 | } 220 | } 221 | virtual bool RecordingIsInitialized() const override { 222 | return adm_recording_ ? adm_->RecordingIsInitialized() 223 | : (bool)is_recording_; 224 | } 225 | 226 | // Audio transport control 227 | virtual int32_t StartPlayout() override { 228 | RTC_LOG(LS_INFO) << "StartPlayout"; 229 | if (adm_playout_) { 230 | return adm_->StartPlayout(); 231 | } else { 232 | is_playing_ = true; 233 | handle_audio_thread_.reset(new std::thread([this]() { 234 | RTC_LOG(LS_INFO) << "Sora Audio Playout Thread started"; 235 | HandleAudioData(); 236 | RTC_LOG(LS_INFO) << "Sora Audio Playout Thread finished"; 237 | })); 238 | 239 | return 0; 240 | } 241 | } 242 | void DoStopPlayout() { 243 | if (handle_audio_thread_) { 244 | RTC_LOG(LS_INFO) << "Terminating Audio Thread"; 245 | handle_audio_thread_stopped_ = true; 246 | handle_audio_thread_->join(); 247 | handle_audio_thread_.reset(); 248 | handle_audio_thread_stopped_ = false; 249 | RTC_LOG(LS_INFO) << "Terminated Audio Thread"; 250 | } 251 | } 252 | virtual int32_t StopPlayout() override { 253 | RTC_LOG(LS_INFO) << "StopPlayout"; 254 | if (adm_playout_) { 255 | return adm_->StopPlayout(); 256 | } else { 257 | DoStopPlayout(); 258 | is_playing_ = false; 259 | return 0; 260 | } 261 | } 262 | virtual bool Playing() const override { 263 | return adm_playout_ ? adm_->Playing() : (bool)is_playing_; 264 | } 265 | virtual int32_t StartRecording() override { 266 | return adm_recording_ ? adm_->StartRecording() : 0; 267 | } 268 | virtual int32_t StopRecording() override { 269 | return adm_recording_ ? adm_->StopRecording() : 0; 270 | } 271 | virtual bool Recording() const override { 272 | return adm_recording_ ? adm_->Recording() : (bool)is_recording_; 273 | } 274 | 275 | // Audio mixer initialization 276 | virtual int32_t InitSpeaker() override { 277 | return adm_playout_ ? adm_->InitSpeaker() : 0; 278 | } 279 | virtual bool SpeakerIsInitialized() const override { 280 | return adm_playout_ ? adm_->SpeakerIsInitialized() : false; 281 | } 282 | virtual int32_t InitMicrophone() override { 283 | return adm_recording_ ? adm_->InitMicrophone() : 0; 284 | } 285 | virtual bool MicrophoneIsInitialized() const override { 286 | return adm_recording_ ? adm_->MicrophoneIsInitialized() : false; 287 | } 288 | 289 | // Speaker volume controls 290 | virtual int32_t SpeakerVolumeIsAvailable(bool* available) override { 291 | return adm_playout_ ? adm_->SpeakerVolumeIsAvailable(available) : 0; 292 | } 293 | virtual int32_t SetSpeakerVolume(uint32_t volume) override { 294 | return adm_playout_ ? adm_->SetSpeakerVolume(volume) : 0; 295 | } 296 | virtual int32_t SpeakerVolume(uint32_t* volume) const override { 297 | return adm_playout_ ? adm_->SpeakerVolume(volume) : 0; 298 | } 299 | virtual int32_t MaxSpeakerVolume(uint32_t* maxVolume) const override { 300 | return adm_playout_ ? adm_->MaxSpeakerVolume(maxVolume) : 0; 301 | } 302 | virtual int32_t MinSpeakerVolume(uint32_t* minVolume) const override { 303 | return adm_playout_ ? adm_->MinSpeakerVolume(minVolume) : 0; 304 | } 305 | 306 | // Microphone volume controls 307 | virtual int32_t MicrophoneVolumeIsAvailable(bool* available) override { 308 | return adm_recording_ ? adm_->MicrophoneVolumeIsAvailable(available) : 0; 309 | } 310 | virtual int32_t SetMicrophoneVolume(uint32_t volume) override { 311 | return adm_recording_ ? adm_->SetMicrophoneVolume(volume) : 0; 312 | } 313 | virtual int32_t MicrophoneVolume(uint32_t* volume) const override { 314 | return adm_recording_ ? adm_->MicrophoneVolume(volume) : 0; 315 | } 316 | virtual int32_t MaxMicrophoneVolume(uint32_t* maxVolume) const override { 317 | return adm_recording_ ? adm_->MaxMicrophoneVolume(maxVolume) : 0; 318 | } 319 | virtual int32_t MinMicrophoneVolume(uint32_t* minVolume) const override { 320 | return adm_recording_ ? adm_->MinMicrophoneVolume(minVolume) : 0; 321 | } 322 | 323 | // Speaker mute control 324 | virtual int32_t SpeakerMuteIsAvailable(bool* available) override { 325 | return adm_playout_ ? adm_->SpeakerMuteIsAvailable(available) : 0; 326 | } 327 | virtual int32_t SetSpeakerMute(bool enable) override { 328 | return adm_playout_ ? adm_->SetSpeakerMute(enable) : 0; 329 | } 330 | virtual int32_t SpeakerMute(bool* enabled) const override { 331 | return adm_playout_ ? adm_->SpeakerMute(enabled) : 0; 332 | } 333 | 334 | // Microphone mute control 335 | virtual int32_t MicrophoneMuteIsAvailable(bool* available) override { 336 | return adm_recording_ ? adm_->MicrophoneMuteIsAvailable(available) : 0; 337 | } 338 | virtual int32_t SetMicrophoneMute(bool enable) override { 339 | return adm_recording_ ? adm_->SetMicrophoneMute(enable) : 0; 340 | } 341 | virtual int32_t MicrophoneMute(bool* enabled) const override { 342 | return adm_recording_ ? adm_->MicrophoneMute(enabled) : 0; 343 | } 344 | 345 | // Stereo support 346 | virtual int32_t StereoPlayoutIsAvailable(bool* available) const override { 347 | if (adm_playout_) { 348 | return adm_->StereoPlayoutIsAvailable(available); 349 | } else { 350 | // 今はステレオには対応しない 351 | //*available = true; 352 | *available = false; 353 | return 0; 354 | } 355 | } 356 | virtual int32_t SetStereoPlayout(bool enable) override { 357 | if (adm_playout_) { 358 | return adm_->SetStereoPlayout(enable); 359 | } else { 360 | stereo_playout_ = enable; 361 | return 0; 362 | }; 363 | } 364 | virtual int32_t StereoPlayout(bool* enabled) const override { 365 | if (adm_playout_) { 366 | return adm_->StereoPlayoutIsAvailable(enabled); 367 | } else { 368 | *enabled = stereo_playout_; 369 | return 0; 370 | } 371 | } 372 | virtual int32_t StereoRecordingIsAvailable(bool* available) const override { 373 | if (adm_recording_) { 374 | return adm_->StereoRecordingIsAvailable(available); 375 | } else { 376 | *available = true; 377 | return 0; 378 | } 379 | } 380 | virtual int32_t SetStereoRecording(bool enable) override { 381 | return adm_recording_ ? adm_->SetStereoRecording(enable) : 0; 382 | } 383 | virtual int32_t StereoRecording(bool* enabled) const override { 384 | if (adm_recording_) { 385 | return adm_->StereoRecording(enabled); 386 | } else { 387 | *enabled = true; 388 | return 0; 389 | } 390 | } 391 | 392 | // Playout delay 393 | virtual int32_t PlayoutDelay(uint16_t* delayMS) const override { 394 | return adm_playout_ ? adm_->PlayoutDelay(delayMS) : 0; 395 | } 396 | 397 | // Only supported on Android. 398 | virtual bool BuiltInAECIsAvailable() const override { 399 | return false; 400 | } 401 | virtual bool BuiltInAGCIsAvailable() const override { 402 | return false; 403 | } 404 | virtual bool BuiltInNSIsAvailable() const override { 405 | return false; 406 | } 407 | 408 | // Enables the built-in audio effects. Only supported on Android. 409 | virtual int32_t EnableBuiltInAEC(bool enable) override { 410 | return 0; 411 | } 412 | virtual int32_t EnableBuiltInAGC(bool enable) override { 413 | return 0; 414 | } 415 | virtual int32_t EnableBuiltInNS(bool enable) override { 416 | return 0; 417 | } 418 | 419 | // Only supported on iOS. 420 | #if defined(WEBRTC_IOS) 421 | virtual int GetPlayoutAudioParameters( 422 | webrtc::AudioParameters* params) const override { 423 | return -1; 424 | } 425 | virtual int GetRecordAudioParameters( 426 | webrtc::AudioParameters* params) const override { 427 | return -1; 428 | } 429 | #endif // WEBRTC_IOS 430 | 431 | private: 432 | rtc::scoped_refptr adm_; 433 | bool adm_recording_; 434 | bool adm_playout_; 435 | webrtc::TaskQueueFactory* task_queue_factory_; 436 | std::function 437 | on_handle_audio_; 438 | std::unique_ptr handle_audio_thread_; 439 | std::atomic_bool handle_audio_thread_stopped_ = {false}; 440 | std::unique_ptr device_buffer_; 441 | std::atomic_bool initialized_ = {false}; 442 | std::atomic_bool is_recording_ = {false}; 443 | std::atomic_bool is_playing_ = {false}; 444 | std::atomic_bool stereo_playout_ = {false}; 445 | std::vector converted_audio_data_; 446 | }; 447 | 448 | } // namespace sora_unity_sdk 449 | 450 | #endif 451 | -------------------------------------------------------------------------------- /src/unity_camera_capturer.cpp: -------------------------------------------------------------------------------- 1 | #include "unity_camera_capturer.h" 2 | 3 | namespace sora_unity_sdk { 4 | 5 | UnityCameraCapturer::UnityCameraCapturer( 6 | const UnityCameraCapturerConfig& config) 7 | : sora::ScalableVideoTrackSource(config) {} 8 | 9 | rtc::scoped_refptr UnityCameraCapturer::Create( 10 | const UnityCameraCapturerConfig& config) { 11 | rtc::scoped_refptr p = 12 | rtc::make_ref_counted(config); 13 | if (!p->Init(config.context, config.unity_camera_texture, config.width, 14 | config.height)) { 15 | return nullptr; 16 | } 17 | return p; 18 | } 19 | 20 | void UnityCameraCapturer::OnRender() { 21 | std::lock_guard guard(mutex_); 22 | if (stopped_) { 23 | return; 24 | } 25 | 26 | #if defined(SORA_UNITY_SDK_WINDOWS) || defined(SORA_UNITY_SDK_MACOS) || \ 27 | defined(SORA_UNITY_SDK_IOS) || defined(SORA_UNITY_SDK_ANDROID) || \ 28 | defined(SORA_UNITY_SDK_UBUNTU) 29 | auto i420_buffer = capturer_->Capture(); 30 | if (!i420_buffer) { 31 | return; 32 | } 33 | 34 | auto video_frame = webrtc::VideoFrame::Builder() 35 | .set_video_frame_buffer(i420_buffer) 36 | .set_rotation(webrtc::kVideoRotation_0) 37 | .set_timestamp_us(clock_->TimeInMicroseconds()) 38 | .build(); 39 | this->OnFrame(video_frame); 40 | #endif 41 | } 42 | 43 | void UnityCameraCapturer::OnFrame(const webrtc::VideoFrame& frame) { 44 | OnCapturedFrame(frame); 45 | } 46 | 47 | void UnityCameraCapturer::Stop() { 48 | std::lock_guard guard(mutex_); 49 | stopped_ = true; 50 | } 51 | 52 | bool UnityCameraCapturer::Init(UnityContext* context, 53 | void* unity_camera_texture, 54 | int width, 55 | int height) { 56 | capturer_.reset(); 57 | 58 | auto renderer_type = 59 | context->GetInterfaces()->Get()->GetRenderer(); 60 | 61 | switch (renderer_type) { 62 | case kUnityGfxRendererD3D11: 63 | #ifdef SORA_UNITY_SDK_WINDOWS 64 | RTC_LOG(LS_INFO) << "Init UnityCameraCapturer with D3D11Impl"; 65 | capturer_.reset(new D3D11Impl()); 66 | #endif 67 | break; 68 | case kUnityGfxRendererMetal: 69 | #if defined(SORA_UNITY_SDK_MACOS) || defined(SORA_UNITY_SDK_IOS) 70 | RTC_LOG(LS_INFO) << "Init UnityCameraCapturer with MetalImpl"; 71 | capturer_.reset(new MetalImpl()); 72 | #endif 73 | break; 74 | case kUnityGfxRendererVulkan: 75 | #ifdef SORA_UNITY_SDK_ANDROID 76 | RTC_LOG(LS_INFO) << "Init UnityCameraCapturer with VulkanImpl"; 77 | capturer_.reset(new VulkanImpl()); 78 | #endif 79 | break; 80 | case kUnityGfxRendererOpenGLCore: 81 | case kUnityGfxRendererOpenGLES20: 82 | case kUnityGfxRendererOpenGLES30: 83 | #if defined(SORA_UNITY_SDK_ANDROID) || defined(SORA_UNITY_SDK_UBUNTU) 84 | RTC_LOG(LS_INFO) << "Init UnityCameraCapturer with OpenglImpl"; 85 | capturer_.reset(new OpenglImpl()); 86 | #endif 87 | break; 88 | default: 89 | break; 90 | } 91 | 92 | if (capturer_ == nullptr) { 93 | RTC_LOG(LS_INFO) << "Failed to Init for UnityCameraCapturer"; 94 | return false; 95 | } 96 | 97 | if (!capturer_->Init(context, unity_camera_texture, width, height)) { 98 | return false; 99 | } 100 | return true; 101 | } 102 | 103 | } // namespace sora_unity_sdk 104 | -------------------------------------------------------------------------------- /src/unity_camera_capturer.h: -------------------------------------------------------------------------------- 1 | #ifndef SORA_UNITY_SDK_UNITY_CAMERA_CAPTURER_H_INCLUDED 2 | #define SORA_UNITY_SDK_UNITY_CAMERA_CAPTURER_H_INCLUDED 3 | 4 | // WebRTC 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // sora 14 | #include 15 | 16 | #include "unity_context.h" 17 | 18 | #ifdef SORA_UNITY_SDK_ANDROID 19 | #include 20 | #endif 21 | 22 | namespace sora_unity_sdk { 23 | 24 | struct UnityCameraCapturerConfig : sora::ScalableVideoTrackSourceConfig { 25 | UnityContext* context; 26 | void* unity_camera_texture; 27 | int width; 28 | int height; 29 | }; 30 | 31 | class UnityCameraCapturer : public sora::ScalableVideoTrackSource, 32 | public rtc::VideoSinkInterface { 33 | webrtc::Clock* clock_ = webrtc::Clock::GetRealTimeClock(); 34 | 35 | struct Impl { 36 | virtual ~Impl() {} 37 | virtual bool Init(UnityContext* context, 38 | void* camera_texture, 39 | int width, 40 | int height) = 0; 41 | virtual rtc::scoped_refptr Capture() = 0; 42 | }; 43 | 44 | #ifdef SORA_UNITY_SDK_WINDOWS 45 | class D3D11Impl : public Impl { 46 | UnityContext* context_; 47 | void* camera_texture_; 48 | void* frame_texture_; 49 | int width_; 50 | int height_; 51 | 52 | public: 53 | bool Init(UnityContext* context, 54 | void* camera_texture, 55 | int width, 56 | int height) override; 57 | rtc::scoped_refptr Capture() override; 58 | }; 59 | #endif 60 | 61 | #if defined(SORA_UNITY_SDK_MACOS) || defined(SORA_UNITY_SDK_IOS) 62 | class MetalImpl : public Impl { 63 | UnityContext* context_; 64 | void* camera_texture_; 65 | void* frame_texture_; 66 | int width_; 67 | int height_; 68 | 69 | public: 70 | bool Init(UnityContext* context, 71 | void* camera_texture, 72 | int width, 73 | int height) override; 74 | rtc::scoped_refptr Capture() override; 75 | }; 76 | #endif 77 | 78 | #ifdef SORA_UNITY_SDK_ANDROID 79 | class VulkanImpl : public Impl { 80 | UnityContext* context_; 81 | void* camera_texture_; 82 | VkImage image_ = VK_NULL_HANDLE; 83 | VkDeviceMemory memory_ = VK_NULL_HANDLE; 84 | VkCommandPool pool_ = VK_NULL_HANDLE; 85 | int width_; 86 | int height_; 87 | 88 | public: 89 | ~VulkanImpl() override; 90 | bool Init(UnityContext* context, 91 | void* camera_texture, 92 | int width, 93 | int height) override; 94 | rtc::scoped_refptr Capture() override; 95 | }; 96 | #endif 97 | 98 | #if defined(SORA_UNITY_SDK_ANDROID) || defined(SORA_UNITY_SDK_UBUNTU) 99 | class OpenglImpl : public Impl { 100 | UnityContext* context_; 101 | void* camera_texture_; 102 | int width_; 103 | int height_; 104 | unsigned int fbo_ = 0; 105 | bool initialized_ = false; 106 | 107 | public: 108 | ~OpenglImpl() override; 109 | bool Init(UnityContext* context, 110 | void* camera_texture, 111 | int width, 112 | int height) override; 113 | rtc::scoped_refptr Capture() override; 114 | }; 115 | #endif 116 | 117 | std::unique_ptr capturer_; 118 | std::mutex mutex_; 119 | bool stopped_ = false; 120 | 121 | public: 122 | static rtc::scoped_refptr Create( 123 | const UnityCameraCapturerConfig& config); 124 | 125 | UnityCameraCapturer(const UnityCameraCapturerConfig& config); 126 | 127 | void OnRender(); 128 | 129 | void Stop(); 130 | 131 | void OnFrame(const webrtc::VideoFrame& frame) override; 132 | 133 | private: 134 | bool Init(UnityContext* context, 135 | void* unity_camera_texture, 136 | int width, 137 | int height); 138 | }; 139 | 140 | } // namespace sora_unity_sdk 141 | 142 | #endif 143 | -------------------------------------------------------------------------------- /src/unity_camera_capturer_d3d11.cpp: -------------------------------------------------------------------------------- 1 | #include "unity_camera_capturer.h" 2 | 3 | namespace sora_unity_sdk { 4 | 5 | bool UnityCameraCapturer::D3D11Impl::Init(UnityContext* context, 6 | void* camera_texture, 7 | int width, 8 | int height) { 9 | context_ = context; 10 | camera_texture_ = camera_texture; 11 | width_ = width; 12 | height_ = height; 13 | 14 | auto device = context->GetDevice(); 15 | if (device == nullptr) { 16 | return false; 17 | } 18 | 19 | // ピクセルデータにアクセスする用のテクスチャを用意する 20 | ID3D11Texture2D* texture = nullptr; 21 | D3D11_TEXTURE2D_DESC desc = {0}; 22 | desc.Width = width; 23 | desc.Height = height; 24 | desc.MipLevels = 1; 25 | desc.ArraySize = 1; 26 | desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; 27 | desc.SampleDesc.Count = 1; 28 | desc.Usage = D3D11_USAGE_STAGING; 29 | desc.BindFlags = 0; 30 | desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; 31 | HRESULT hr = device->CreateTexture2D(&desc, NULL, &texture); 32 | if (!SUCCEEDED(hr)) { 33 | RTC_LOG(LS_ERROR) << "ID3D11Device::CreateTexture2D is failed: hr=" << hr; 34 | return false; 35 | } 36 | 37 | frame_texture_ = texture; 38 | return true; 39 | } 40 | 41 | rtc::scoped_refptr 42 | UnityCameraCapturer::D3D11Impl::Capture() { 43 | D3D11_MAPPED_SUBRESOURCE resource; 44 | 45 | auto dc = context_->GetDeviceContext(); 46 | if (dc == nullptr) { 47 | RTC_LOG(LS_ERROR) << "ID3D11DeviceContext is null"; 48 | return nullptr; 49 | } 50 | 51 | // ピクセルデータが取れない(と思う)ので、カメラテクスチャから自前のテクスチャにコピーする 52 | dc->CopyResource((ID3D11Texture2D*)frame_texture_, 53 | (ID3D11Resource*)camera_texture_); 54 | 55 | HRESULT hr = 56 | dc->Map((ID3D11Resource*)frame_texture_, 0, D3D11_MAP_READ, 0, &resource); 57 | if (!SUCCEEDED(hr)) { 58 | RTC_LOG(LS_ERROR) << "ID3D11DeviceContext::Map is failed: hr=" << hr; 59 | return nullptr; 60 | } 61 | 62 | // Windows の場合は座標系の関係で上下反転してるので、頑張って元の向きに戻す 63 | // TODO(melpon): Graphics.Blit を使って効率よく反転させる 64 | // (ref: https://github.com/Unity-Technologies/com.unity.webrtc/blob/a526753e0c18d681d20f3eb9878b9c28442e2bfb/Runtime/Scripts/WebRTC.cs#L264-L268) 65 | std::unique_ptr buf(new uint8_t[width_ * height_ * 4]); 66 | for (int i = 0; i < height_; i++) { 67 | std::memcpy(buf.get() + width_ * 4 * i, 68 | ((const uint8_t*)resource.pData) + 69 | resource.RowPitch * (height_ - i - 1), 70 | width_ * 4); 71 | } 72 | 73 | // I420 に変換して VideoFrame 作って OnFrame 呼び出し 74 | //RTC_LOG(LS_INFO) << "GOT FRAME: pData=0x" << resource.pData 75 | // << " RowPitch=" << resource.RowPitch 76 | // << " DepthPitch=" << resource.DepthPitch; 77 | rtc::scoped_refptr i420_buffer = 78 | webrtc::I420Buffer::Create(width_, height_); 79 | //libyuv::ARGBToI420((const uint8_t*)resource.pData, resource.RowPitch, 80 | // i420_buffer->MutableDataY(), i420_buffer->StrideY(), 81 | // i420_buffer->MutableDataU(), i420_buffer->StrideU(), 82 | // i420_buffer->MutableDataV(), i420_buffer->StrideV(), 83 | // width_, height_); 84 | libyuv::ARGBToI420(buf.get(), width_ * 4, i420_buffer->MutableDataY(), 85 | i420_buffer->StrideY(), i420_buffer->MutableDataU(), 86 | i420_buffer->StrideU(), i420_buffer->MutableDataV(), 87 | i420_buffer->StrideV(), width_, height_); 88 | 89 | dc->Unmap((ID3D11Resource*)frame_texture_, 0); 90 | 91 | return i420_buffer; 92 | } 93 | 94 | } // namespace sora_unity_sdk 95 | -------------------------------------------------------------------------------- /src/unity_camera_capturer_metal.mm: -------------------------------------------------------------------------------- 1 | #include "unity_camera_capturer.h" 2 | 3 | #import 4 | 5 | // .mm ファイルからじゃないと IUnityGraphicsMetal.h を読み込めないので、ここで import する 6 | #import "unity/IUnityGraphicsMetal.h" 7 | 8 | namespace sora_unity_sdk { 9 | 10 | static std::string MTLStorageModeToString(MTLStorageMode mode) { 11 | switch (mode) { 12 | case MTLStorageModeShared: 13 | return "MTLStorageModeShared"; 14 | #if defined(SORA_UNITY_SDK_MACOS) 15 | case MTLStorageModeManaged: 16 | return "MTLStorageModeManaged"; 17 | #endif 18 | case MTLStorageModePrivate: 19 | return "MTLStorageModePrivate"; 20 | //case MTLStorageModeMemoryless: 21 | // return "MTLStorageModeMemoryless"; 22 | default: 23 | return "Unknown"; 24 | } 25 | } 26 | 27 | bool UnityCameraCapturer::MetalImpl::Init(UnityContext* context, 28 | void* camera_texture, 29 | int width, 30 | int height) { 31 | context_ = context; 32 | camera_texture_ = camera_texture; 33 | width_ = width; 34 | height_ = height; 35 | 36 | // camera_texture_ は private storage なので、getBytes でピクセルを取り出すことができない 37 | // なのでもう一個 shared storage なテクスチャを作り、キャプチャする時はそのテクスチャに転送して利用する 38 | auto tex = (id)camera_texture_; 39 | // 多分 MTLStorageModePrivate になる 40 | RTC_LOG(LS_INFO) << "Passed Texture width=" << tex.width 41 | << " height=" << tex.height << " MTLStorageMode=" 42 | << MTLStorageModeToString(tex.storageMode); 43 | 44 | auto graphics = context_->GetInterfaces()->Get(); 45 | auto device = graphics->MetalDevice(); 46 | 47 | auto descriptor = 48 | [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:tex.pixelFormat 49 | width:width_ 50 | height:height_ 51 | mipmapped:NO]; 52 | auto tex2 = [device newTextureWithDescriptor:descriptor]; 53 | // 多分 MTLStorageModeManaged になる 54 | RTC_LOG(LS_INFO) << "Created Texture width=" << tex2.width 55 | << " height=" << tex2.height << " MTLStorageMode=" 56 | << MTLStorageModeToString(tex2.storageMode); 57 | frame_texture_ = tex2; 58 | return true; 59 | } 60 | 61 | rtc::scoped_refptr 62 | UnityCameraCapturer::MetalImpl::Capture() { 63 | auto camera_tex = (id)camera_texture_; 64 | auto tex = (id)frame_texture_; 65 | auto graphics = context_->GetInterfaces()->Get(); 66 | 67 | graphics->EndCurrentCommandEncoder(); 68 | 69 | auto commandBuffer = graphics->CurrentCommandBuffer(); 70 | 71 | id blit = [commandBuffer blitCommandEncoder]; 72 | if (blit == nil) { 73 | return nullptr; 74 | } 75 | // [blit copyFromTexture:camera_tex toTexture:tex]; 76 | [blit copyFromTexture:camera_tex 77 | sourceSlice:0 78 | sourceLevel:0 79 | sourceOrigin:MTLOriginMake(0, 0, 0) 80 | sourceSize:MTLSizeMake(width_, height_, 1) 81 | toTexture:tex 82 | destinationSlice:0 83 | destinationLevel:0 84 | destinationOrigin:MTLOriginMake(0, 0, 0)]; 85 | #if defined(SORA_UNITY_SDK_MACOS) 86 | [blit synchronizeResource:tex]; 87 | #endif 88 | [blit endEncoding]; 89 | blit = nil; 90 | 91 | std::unique_ptr buf(new uint8_t[width_ * height_ * 4]); 92 | auto region = MTLRegionMake2D(0, 0, width_, height_); 93 | [tex getBytes:buf.get() 94 | bytesPerRow:width_ * 4 95 | fromRegion:region 96 | mipmapLevel:0]; 97 | 98 | // Metal の場合は座標系の関係で上下反転してるので、頑張って元の向きに戻す 99 | std::unique_ptr buf2(new uint8_t[width_ * height_ * 4]); 100 | for (int i = 0; i < height_; i++) { 101 | std::memcpy(buf2.get() + width_ * 4 * i, 102 | buf.get() + width_ * 4 * (height_ - i - 1), width_ * 4); 103 | } 104 | 105 | rtc::scoped_refptr i420_buffer = 106 | webrtc::I420Buffer::Create(width_, height_); 107 | libyuv::ARGBToI420(buf2.get(), width_ * 4, i420_buffer->MutableDataY(), 108 | i420_buffer->StrideY(), i420_buffer->MutableDataU(), 109 | i420_buffer->StrideU(), i420_buffer->MutableDataV(), 110 | i420_buffer->StrideV(), width_, height_); 111 | 112 | return i420_buffer; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/unity_camera_capturer_opengl.cpp: -------------------------------------------------------------------------------- 1 | #include "unity_camera_capturer.h" 2 | 3 | #if defined(SORA_UNITY_SDK_UBUNTU) 4 | #define GL_GLEXT_PROTOTYPES 5 | #include 6 | #elif defined(SORA_UNITY_SDK_ANDROID) 7 | #include 8 | #endif 9 | 10 | namespace sora_unity_sdk { 11 | 12 | UnityCameraCapturer::OpenglImpl::~OpenglImpl() { 13 | if (fbo_ != 0) { 14 | glDeleteFramebuffers(1, &fbo_); 15 | } 16 | } 17 | 18 | #define GL_ERRCHECK_(name, value) \ 19 | { \ 20 | auto error = glGetError(); \ 21 | if (error != GL_NO_ERROR) { \ 22 | RTC_LOG(LS_ERROR) << "Failed to " << name << ": error=" << (int)error; \ 23 | return value; \ 24 | } \ 25 | } 26 | 27 | #define GL_ERRCHECK(name) GL_ERRCHECK_(name, false) 28 | 29 | bool UnityCameraCapturer::OpenglImpl::Init(UnityContext* context, 30 | void* camera_texture, 31 | int width, 32 | int height) { 33 | camera_texture_ = camera_texture; 34 | width_ = width; 35 | height_ = height; 36 | return true; 37 | } 38 | 39 | #undef GL_ERRCHECK 40 | #define GL_ERRCHECK(name) GL_ERRCHECK_(name, nullptr) 41 | 42 | rtc::scoped_refptr 43 | UnityCameraCapturer::OpenglImpl::Capture() { 44 | // Init 関数とは別のスレッドから呼ばれることがあるので、 45 | // ここに初期化処理を入れる 46 | if (!initialized_) { 47 | initialized_ = true; 48 | 49 | glGenFramebuffers(1, &fbo_); 50 | GL_ERRCHECK("glGenFramebuffers"); 51 | 52 | glBindFramebuffer(GL_FRAMEBUFFER, fbo_); 53 | GL_ERRCHECK("glBindFramebuffer"); 54 | 55 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 56 | (GLuint)(intptr_t)camera_texture_, 0); 57 | GL_ERRCHECK("glFramebufferTexture2D"); 58 | } 59 | 60 | glBindFramebuffer(GL_FRAMEBUFFER, fbo_); 61 | GL_ERRCHECK("glBindFramebuffer"); 62 | 63 | std::unique_ptr buf(new uint8_t[width_ * height_ * 4]()); 64 | 65 | glReadPixels(0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, buf.get()); 66 | GL_ERRCHECK("glReadPixels"); 67 | 68 | // OpenGL の座標は上下反転してるので、頑張って元の向きに戻す 69 | std::unique_ptr buf2(new uint8_t[width_ * height_ * 4]); 70 | for (int i = 0; i < height_; i++) { 71 | std::memcpy(buf2.get() + width_ * 4 * i, 72 | buf.get() + width_ * 4 * (height_ - i - 1), width_ * 4); 73 | } 74 | 75 | rtc::scoped_refptr i420_buffer = 76 | webrtc::I420Buffer::Create(width_, height_); 77 | libyuv::ABGRToI420(buf2.get(), width_ * 4, i420_buffer->MutableDataY(), 78 | i420_buffer->StrideY(), i420_buffer->MutableDataU(), 79 | i420_buffer->StrideU(), i420_buffer->MutableDataV(), 80 | i420_buffer->StrideV(), width_, height_); 81 | 82 | return i420_buffer; 83 | } 84 | 85 | } // namespace sora_unity_sdk 86 | -------------------------------------------------------------------------------- /src/unity_camera_capturer_vulkan.cpp: -------------------------------------------------------------------------------- 1 | #include "unity_camera_capturer.h" 2 | 3 | // unity 4 | #include "unity/IUnityGraphicsVulkan.h" 5 | 6 | namespace sora_unity_sdk { 7 | 8 | UnityCameraCapturer::VulkanImpl::~VulkanImpl() { 9 | UnityVulkanInstance instance = 10 | context_->GetInterfaces()->Get()->Instance(); 11 | VkDevice device = instance.device; 12 | 13 | if (pool_ != VK_NULL_HANDLE) { 14 | vkDestroyCommandPool(device, pool_, nullptr); 15 | } 16 | if (memory_ != VK_NULL_HANDLE) { 17 | vkFreeMemory(device, memory_, nullptr); 18 | } 19 | if (image_ != VK_NULL_HANDLE) { 20 | vkDestroyImage(device, image_, nullptr); 21 | } 22 | } 23 | 24 | bool UnityCameraCapturer::VulkanImpl::Init(UnityContext* context, 25 | void* camera_texture, 26 | int width, 27 | int height) { 28 | context_ = context; 29 | camera_texture_ = camera_texture; 30 | width_ = width; 31 | height_ = height; 32 | 33 | UnityVulkanInstance instance = 34 | context->GetInterfaces()->Get()->Instance(); 35 | 36 | VkDevice device = instance.device; 37 | VkPhysicalDevice physical_device = instance.physicalDevice; 38 | VkQueue queue = instance.graphicsQueue; 39 | uint32_t queue_family_index = instance.queueFamilyIndex; 40 | 41 | VkImageCreateInfo imageInfo = {}; 42 | imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; 43 | imageInfo.pNext = nullptr; 44 | imageInfo.flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; 45 | imageInfo.imageType = VK_IMAGE_TYPE_2D; 46 | imageInfo.format = VK_FORMAT_R8G8B8A8_UINT; 47 | imageInfo.extent.width = width; 48 | imageInfo.extent.height = height; 49 | imageInfo.extent.depth = 1; 50 | imageInfo.mipLevels = 1; 51 | imageInfo.arrayLayers = 1; 52 | imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; 53 | imageInfo.tiling = VK_IMAGE_TILING_LINEAR; // VK_IMAGE_TILING_OPTIMAL; 54 | imageInfo.usage = 55 | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; 56 | imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; 57 | imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; 58 | imageInfo.queueFamilyIndexCount = 1; 59 | imageInfo.pQueueFamilyIndices = &queue_family_index; 60 | if (vkCreateImage(device, &imageInfo, nullptr, &image_) != VK_SUCCESS) { 61 | RTC_LOG(LS_ERROR) << "vkCreateImage failed"; 62 | return false; 63 | } 64 | 65 | VkMemoryRequirements mem_requirements; 66 | vkGetImageMemoryRequirements(device, image_, &mem_requirements); 67 | RTC_LOG(LS_INFO) << "memoryTypeBits=" << mem_requirements.memoryTypeBits; 68 | 69 | VkMemoryAllocateInfo allocInfo = {}; 70 | allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; 71 | allocInfo.allocationSize = mem_requirements.size; 72 | 73 | bool found = false; 74 | VkPhysicalDeviceMemoryProperties mem_properties; 75 | vkGetPhysicalDeviceMemoryProperties(physical_device, &mem_properties); 76 | 77 | int prop = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; 78 | for (uint32_t i = 0; i < mem_properties.memoryTypeCount; ++i) { 79 | int flags = mem_properties.memoryTypes[i].propertyFlags; 80 | RTC_LOG(LS_INFO) << "type[" << i << "]=" << flags; 81 | 82 | if ((mem_requirements.memoryTypeBits & (1 << i)) && 83 | (flags & prop) == prop) { 84 | allocInfo.memoryTypeIndex = i; 85 | found = true; 86 | break; 87 | } 88 | } 89 | if (!found) { 90 | RTC_LOG(LS_ERROR) << "VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT not found"; 91 | return false; 92 | } 93 | 94 | if (vkAllocateMemory(device, &allocInfo, nullptr, &memory_) != VK_SUCCESS) { 95 | RTC_LOG(LS_ERROR) << "vkAllocateMemory failed"; 96 | return false; 97 | } 98 | 99 | if (vkBindImageMemory(device, image_, memory_, 0) != VK_SUCCESS) { 100 | RTC_LOG(LS_ERROR) << "vkBindImageMemory failed"; 101 | return false; 102 | } 103 | 104 | VkCommandPoolCreateInfo cmdPoolInfo = {}; 105 | cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; 106 | cmdPoolInfo.queueFamilyIndex = queue_family_index; 107 | cmdPoolInfo.flags = 0; 108 | if (vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &pool_) != 109 | VK_SUCCESS) { 110 | RTC_LOG(LS_ERROR) << "vkCreateCommandPool failed"; 111 | return false; 112 | } 113 | 114 | return true; 115 | } 116 | 117 | rtc::scoped_refptr 118 | UnityCameraCapturer::VulkanImpl::Capture() { 119 | IUnityGraphicsVulkan* graphics = 120 | context_->GetInterfaces()->Get(); 121 | 122 | UnityVulkanInstance instance = graphics->Instance(); 123 | VkDevice device = instance.device; 124 | VkQueue queue = instance.graphicsQueue; 125 | 126 | UnityVulkanImage image; 127 | bool result = graphics->AccessTexture( 128 | camera_texture_, UnityVulkanWholeImage, 129 | VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, 130 | VK_ACCESS_TRANSFER_READ_BIT, kUnityVulkanResourceAccess_PipelineBarrier, 131 | &image); 132 | if (!result) { 133 | RTC_LOG(LS_ERROR) << "IUnityGraphicsVulkan::AccessTexture Failed"; 134 | return nullptr; 135 | } 136 | 137 | VkCommandBuffer command_buffer; 138 | 139 | { 140 | VkCommandBufferAllocateInfo allocInfo = {}; 141 | allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; 142 | allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; 143 | allocInfo.commandPool = pool_; 144 | allocInfo.commandBufferCount = 1; 145 | 146 | if (vkAllocateCommandBuffers(device, &allocInfo, &command_buffer) != 147 | VK_SUCCESS) { 148 | RTC_LOG(LS_ERROR) << "vkAllocateCommandBuffers failed"; 149 | return nullptr; 150 | } 151 | 152 | VkCommandBufferBeginInfo beginInfo = {}; 153 | beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; 154 | beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; 155 | 156 | if (vkBeginCommandBuffer(command_buffer, &beginInfo) != VK_SUCCESS) { 157 | vkFreeCommandBuffers(device, pool_, 1, &command_buffer); 158 | RTC_LOG(LS_ERROR) << "vkBeginCommandBuffer failed"; 159 | return nullptr; 160 | } 161 | } 162 | 163 | //{ 164 | // VkImageMemoryBarrier barrier = {}; 165 | // barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; 166 | // barrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; 167 | // barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; 168 | // barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; 169 | // barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; 170 | 171 | // barrier.image = image.image; 172 | // barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 173 | // barrier.subresourceRange.baseMipLevel = 0; 174 | // barrier.subresourceRange.levelCount = 1; 175 | // barrier.subresourceRange.baseArrayLayer = 0; 176 | // barrier.subresourceRange.layerCount = 1; 177 | 178 | // barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; 179 | // barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; 180 | 181 | // vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, 182 | // VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, 183 | // nullptr, 1, &barrier); 184 | //} 185 | 186 | //{ 187 | // VkImageMemoryBarrier barrier = {}; 188 | // barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; 189 | // barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; 190 | // barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; 191 | // barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; 192 | // barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; 193 | 194 | // barrier.image = image_; 195 | // barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 196 | // barrier.subresourceRange.baseMipLevel = 0; 197 | // barrier.subresourceRange.levelCount = 1; 198 | // barrier.subresourceRange.baseArrayLayer = 0; 199 | // barrier.subresourceRange.layerCount = 1; 200 | 201 | // barrier.srcAccessMask = 0; 202 | // barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; 203 | 204 | // vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, 205 | // VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, 206 | // nullptr, 1, &barrier); 207 | //} 208 | 209 | VkImageCopy copyRegion{}; 210 | copyRegion.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; 211 | copyRegion.srcOffset = {0, 0, 0}; 212 | copyRegion.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; 213 | copyRegion.dstOffset = {0, 0, 0}; 214 | copyRegion.extent = {(uint32_t)width_, (uint32_t)height_, 1}; 215 | vkCmdCopyImage(command_buffer, image.image, 216 | VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image_, 217 | VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Region); 218 | 219 | //{ 220 | // VkImageMemoryBarrier barrier = {}; 221 | // barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; 222 | // barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; 223 | // barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; 224 | // barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; 225 | // barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; 226 | 227 | // barrier.image = image_; 228 | // barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 229 | // barrier.subresourceRange.baseMipLevel = 0; 230 | // barrier.subresourceRange.levelCount = 1; 231 | // barrier.subresourceRange.baseArrayLayer = 0; 232 | // barrier.subresourceRange.layerCount = 1; 233 | 234 | // barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; 235 | // barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; 236 | 237 | // vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, 238 | // VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, 239 | // nullptr, 1, &barrier); 240 | //} 241 | 242 | //{ 243 | // VkImageMemoryBarrier barrier = {}; 244 | // barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; 245 | // barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; 246 | // barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; 247 | // barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; 248 | // barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; 249 | 250 | // barrier.image = image.image; 251 | // barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 252 | // barrier.subresourceRange.baseMipLevel = 0; 253 | // barrier.subresourceRange.levelCount = 1; 254 | // barrier.subresourceRange.baseArrayLayer = 0; 255 | // barrier.subresourceRange.layerCount = 1; 256 | 257 | // barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; 258 | // barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; 259 | 260 | // vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, 261 | // VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, 262 | // nullptr, 1, &barrier); 263 | //} 264 | 265 | { 266 | if (vkEndCommandBuffer(command_buffer) != VK_SUCCESS) { 267 | vkFreeCommandBuffers(device, pool_, 1, &command_buffer); 268 | RTC_LOG(LS_ERROR) << "vkEndCommandBuffer failed"; 269 | return nullptr; 270 | } 271 | 272 | VkSubmitInfo submitInfo = {}; 273 | submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; 274 | submitInfo.commandBufferCount = 1; 275 | submitInfo.pCommandBuffers = &command_buffer; 276 | 277 | if (vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { 278 | vkFreeCommandBuffers(device, pool_, 1, &command_buffer); 279 | RTC_LOG(LS_ERROR) << "vkQueueSubmit failed"; 280 | return nullptr; 281 | } 282 | if (vkQueueWaitIdle(queue) != VK_SUCCESS) { 283 | vkFreeCommandBuffers(device, pool_, 1, &command_buffer); 284 | RTC_LOG(LS_ERROR) << "vkQueueWaitIdle failed"; 285 | return nullptr; 286 | } 287 | 288 | vkFreeCommandBuffers(device, pool_, 1, &command_buffer); 289 | } 290 | 291 | //UnityVulkanImage image; 292 | ////bool result = graphics->AccessTexture( 293 | //// image_, UnityVulkanWholeImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, 294 | //// VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_READ_BIT, 295 | //// kUnityVulkanResourceAccess_PipelineBarrier, &image); 296 | //bool result = graphics->AccessTexture( 297 | // image_, UnityVulkanWholeImage, VK_IMAGE_LAYOUT_GENERAL, 298 | // VK_PIPELINE_STAGE_HOST_BIT, 299 | // VK_ACCESS_HOST_READ_BIT | VK_ACCESS_MEMORY_READ_BIT, 300 | // kUnityVulkanResourceAccess_PipelineBarrier, &image); 301 | //if (!result) { 302 | // RTC_LOG(LS_ERROR) << "IUnityGraphicsVulkan::AccessTexture Failed"; 303 | // return nullptr; 304 | //} 305 | //RTC_LOG(LS_INFO) << "memory.memory=" << image.memory.memory; 306 | //RTC_LOG(LS_INFO) << "memory.offset=" << image.memory.offset; 307 | //RTC_LOG(LS_INFO) << "memory.size=" << image.memory.size; 308 | //RTC_LOG(LS_INFO) << "memory.mapped=" << image.memory.mapped; 309 | //RTC_LOG(LS_INFO) << "memory.flags=" << image.memory.flags; 310 | //RTC_LOG(LS_INFO) << "memory.memoryTypeIndex=" << image.memory.memoryTypeIndex; 311 | //RTC_LOG(LS_INFO) << "image=" << image.image; 312 | //RTC_LOG(LS_INFO) << "layout=" << image.layout; 313 | //RTC_LOG(LS_INFO) << "aspect=" << image.aspect; 314 | //RTC_LOG(LS_INFO) << "usage=" << image.usage; 315 | //RTC_LOG(LS_INFO) << "format=" << image.format; 316 | //RTC_LOG(LS_INFO) << "extent.width=" << image.extent.width; 317 | //RTC_LOG(LS_INFO) << "extent.height=" << image.extent.height; 318 | //RTC_LOG(LS_INFO) << "extent.depth=" << image.extent.depth; 319 | //RTC_LOG(LS_INFO) << "tiling=" << image.tiling; 320 | //RTC_LOG(LS_INFO) << "type=" << image.type; 321 | //RTC_LOG(LS_INFO) << "samples=" << image.samples; 322 | //// memory.flags == 0b1 // VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT 323 | //// image.usage == 0b10010111 // VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT 324 | //// image.format == 41 // VK_FORMAT_R8G8B8A8_UINT 325 | //// image.format == 44 // VK_FORMAT_B8G8R8A8_UNORM 326 | //if (image.memory.mapped == nullptr) { 327 | // return nullptr; 328 | //} 329 | //if (image.format != VK_FORMAT_R8G8B8A8_UINT) { 330 | // return nullptr; 331 | //} 332 | //uint8_t* data = (uint8_t*)image.memory.mapped; 333 | //int pitch = image.extent.width * 4; 334 | 335 | VkImageSubresource subresource{VK_IMAGE_ASPECT_COLOR_BIT, 0, 0}; 336 | VkSubresourceLayout subresource_layout; 337 | vkGetImageSubresourceLayout(device, image_, &subresource, 338 | &subresource_layout); 339 | 340 | uint8_t* data; 341 | if (vkMapMemory(device, memory_, 0, VK_WHOLE_SIZE, 0, (void**)&data) != 342 | VK_SUCCESS) { 343 | RTC_LOG(LS_ERROR) << "vkMapMemory failed"; 344 | return nullptr; 345 | } 346 | int pitch = subresource_layout.rowPitch; 347 | 348 | // Vulkan の場合は座標系の関係で上下反転してるので、頑張って元の向きに戻す 349 | std::unique_ptr buf(new uint8_t[pitch * height_]); 350 | for (int i = 0; i < height_; i++) { 351 | std::memcpy(buf.get() + pitch * i, data + pitch * (height_ - i - 1), pitch); 352 | } 353 | 354 | vkUnmapMemory(device, memory_); 355 | 356 | rtc::scoped_refptr i420_buffer = 357 | webrtc::I420Buffer::Create(width_, height_); 358 | libyuv::ARGBToI420(buf.get(), pitch, i420_buffer->MutableDataY(), 359 | i420_buffer->StrideY(), i420_buffer->MutableDataU(), 360 | i420_buffer->StrideU(), i420_buffer->MutableDataV(), 361 | i420_buffer->StrideV(), width_, height_); 362 | 363 | return i420_buffer; 364 | } 365 | 366 | } // namespace sora_unity_sdk 367 | -------------------------------------------------------------------------------- /src/unity_context.cpp: -------------------------------------------------------------------------------- 1 | #include "unity_context.h" 2 | 3 | namespace sora_unity_sdk { 4 | 5 | void UnityContext::OnGraphicsDeviceEventStatic( 6 | UnityGfxDeviceEventType eventType) { 7 | Instance().OnGraphicsDeviceEvent(eventType); 8 | } 9 | 10 | static std::string UnityGfxRendererToString(UnityGfxRenderer renderer) { 11 | switch (renderer) { 12 | case kUnityGfxRendererD3D11: 13 | return "kUnityGfxRendererD3D11"; 14 | case kUnityGfxRendererNull: 15 | return "kUnityGfxRendererNull"; 16 | case kUnityGfxRendererOpenGLES20: 17 | return "kUnityGfxRendererOpenGLES20"; 18 | case kUnityGfxRendererOpenGLES30: 19 | return "kUnityGfxRendererOpenGLES30"; 20 | case kUnityGfxRendererPS4: 21 | return "kUnityGfxRendererPS4"; 22 | case kUnityGfxRendererXboxOne: 23 | return "kUnityGfxRendererXboxOne"; 24 | case kUnityGfxRendererMetal: 25 | return "kUnityGfxRendererMetal"; 26 | case kUnityGfxRendererOpenGLCore: 27 | return "kUnityGfxRendererOpenGLCore"; 28 | case kUnityGfxRendererD3D12: 29 | return "kUnityGfxRendererD3D12"; 30 | case kUnityGfxRendererVulkan: 31 | return "kUnityGfxRendererVulkan"; 32 | case kUnityGfxRendererNvn: 33 | return "kUnityGfxRendererNvn"; 34 | case kUnityGfxRendererXboxOneD3D12: 35 | return "kUnityGfxRendererXboxOneD3D12"; 36 | default: 37 | return "Unknown"; 38 | } 39 | } 40 | 41 | void UnityContext::OnGraphicsDeviceEvent(UnityGfxDeviceEventType eventType) { 42 | switch (eventType) { 43 | case kUnityGfxDeviceEventInitialize: { 44 | graphics_ = ifs_->Get(); 45 | auto renderer_type = graphics_->GetRenderer(); 46 | RTC_LOG(LS_INFO) << "Renderer Type is " 47 | << UnityGfxRendererToString(renderer_type); 48 | #ifdef SORA_UNITY_SDK_WINDOWS 49 | if (renderer_type == kUnityGfxRendererD3D11) { 50 | device_ = ifs_->Get()->GetDevice(); 51 | device_->GetImmediateContext(&context_); 52 | } 53 | #endif 54 | break; 55 | } 56 | case kUnityGfxDeviceEventShutdown: 57 | #ifdef SORA_UNITY_SDK_WINDOWS 58 | if (context_ != nullptr) { 59 | context_->Release(); 60 | context_ = nullptr; 61 | } 62 | device_ = nullptr; 63 | #endif 64 | 65 | if (graphics_ != nullptr) { 66 | graphics_->UnregisterDeviceEventCallback(OnGraphicsDeviceEventStatic); 67 | graphics_ = nullptr; 68 | } 69 | break; 70 | case kUnityGfxDeviceEventBeforeReset: 71 | break; 72 | case kUnityGfxDeviceEventAfterReset: 73 | break; 74 | }; 75 | } 76 | 77 | UnityContext& UnityContext::Instance() { 78 | static UnityContext instance; 79 | return instance; 80 | } 81 | 82 | bool UnityContext::IsInitialized() { 83 | std::lock_guard guard(mutex_); 84 | #ifdef SORA_UNITY_SDK_WINDOWS 85 | return ifs_ != nullptr && device_ != nullptr; 86 | #endif 87 | 88 | #if defined(SORA_UNITY_SDK_MACOS) || defined(SORA_UNITY_SDK_IOS) 89 | if (ifs_ == nullptr || graphics_ == nullptr) { 90 | return false; 91 | } 92 | 93 | // Metal だけ対応する 94 | auto renderer_type = graphics_->GetRenderer(); 95 | if (renderer_type != kUnityGfxRendererMetal) { 96 | return false; 97 | } 98 | 99 | return true; 100 | #endif 101 | return true; 102 | } 103 | 104 | void UnityContext::Init(IUnityInterfaces* ifs) { 105 | std::lock_guard guard(mutex_); 106 | 107 | #if defined(SORA_UNITY_SDK_WINDOWS) || defined(SORA_UNITY_SDK_MACOS) || \ 108 | defined(SORA_UNITY_SDK_UBUNTU) 109 | const size_t kDefaultMaxLogFileSize = 10 * 1024 * 1024; 110 | rtc::LogMessage::LogToDebug((rtc::LoggingSeverity)rtc::LS_NONE); 111 | rtc::LogMessage::LogTimestamps(); 112 | rtc::LogMessage::LogThreads(); 113 | 114 | log_sink_.reset(new rtc::FileRotatingLogSink("./", "webrtc_logs", 115 | kDefaultMaxLogFileSize, 10)); 116 | if (!log_sink_->Init()) { 117 | RTC_LOG(LS_ERROR) << __FUNCTION__ << ": Failed to open log file"; 118 | log_sink_.reset(); 119 | return; 120 | } 121 | log_sink_->DisableBuffering(); 122 | 123 | rtc::LogMessage::AddLogToStream(log_sink_.get(), rtc::LS_INFO); 124 | 125 | RTC_LOG(LS_INFO) << "Log initialized"; 126 | #endif 127 | 128 | #if defined(SORA_UNITY_SDK_ANDROID) || defined(SORA_UNITY_SDK_IOS) 129 | rtc::LogMessage::LogToDebug((rtc::LoggingSeverity)rtc::LS_INFO); 130 | rtc::LogMessage::LogTimestamps(); 131 | rtc::LogMessage::LogThreads(); 132 | #endif 133 | 134 | ifs_ = ifs; 135 | OnGraphicsDeviceEvent(kUnityGfxDeviceEventInitialize); 136 | } 137 | 138 | void UnityContext::Shutdown() { 139 | std::lock_guard guard(mutex_); 140 | OnGraphicsDeviceEvent(kUnityGfxDeviceEventShutdown); 141 | ifs_ = nullptr; 142 | 143 | RTC_LOG(LS_INFO) << "Log uninitialized"; 144 | 145 | rtc::LogMessage::RemoveLogToStream(log_sink_.get()); 146 | log_sink_.reset(); 147 | } 148 | 149 | IUnityInterfaces* UnityContext::GetInterfaces() { 150 | return ifs_; 151 | } 152 | 153 | #ifdef SORA_UNITY_SDK_WINDOWS 154 | ID3D11Device* UnityContext::GetDevice() { 155 | std::lock_guard guard(mutex_); 156 | return device_; 157 | } 158 | 159 | ID3D11DeviceContext* UnityContext::GetDeviceContext() { 160 | std::lock_guard guard(mutex_); 161 | return context_; 162 | } 163 | #endif 164 | 165 | } // namespace sora_unity_sdk 166 | -------------------------------------------------------------------------------- /src/unity_context.h: -------------------------------------------------------------------------------- 1 | #ifndef SORA_UNITY_SDK_UNITY_CONTEXT_H_INCLUDED 2 | #define SORA_UNITY_SDK_UNITY_CONTEXT_H_INCLUDED 3 | 4 | #include 5 | 6 | // webrtc 7 | #include "rtc_base/log_sinks.h" 8 | 9 | #include "unity/IUnityGraphics.h" 10 | #include "unity/IUnityInterface.h" 11 | 12 | #ifdef SORA_UNITY_SDK_WINDOWS 13 | #include "unity/IUnityGraphicsD3D11.h" 14 | #endif 15 | 16 | namespace sora_unity_sdk { 17 | 18 | class UnityContext { 19 | std::mutex mutex_; 20 | std::unique_ptr log_sink_; 21 | IUnityInterfaces* ifs_ = nullptr; 22 | IUnityGraphics* graphics_ = nullptr; 23 | 24 | private: 25 | // Unity のプラグインイベント 26 | static void UNITY_INTERFACE_API 27 | OnGraphicsDeviceEventStatic(UnityGfxDeviceEventType eventType); 28 | 29 | void UNITY_INTERFACE_API 30 | OnGraphicsDeviceEvent(UnityGfxDeviceEventType eventType); 31 | 32 | public: 33 | static UnityContext& Instance(); 34 | 35 | bool IsInitialized(); 36 | void Init(IUnityInterfaces* ifs); 37 | void Shutdown(); 38 | 39 | IUnityInterfaces* GetInterfaces(); 40 | 41 | #ifdef SORA_UNITY_SDK_WINDOWS 42 | private: 43 | ID3D11Device* device_ = nullptr; 44 | ID3D11DeviceContext* context_ = nullptr; 45 | 46 | public: 47 | ID3D11Device* GetDevice(); 48 | ID3D11DeviceContext* GetDeviceContext(); 49 | #endif 50 | }; 51 | 52 | } // namespace sora_unity_sdk 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /src/unity_renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "unity_renderer.h" 2 | 3 | #include 4 | 5 | // libwebrtc 6 | #include 7 | 8 | namespace sora_unity_sdk { 9 | 10 | // UnityRenderer::Sink 11 | 12 | UnityRenderer::Sink::Sink(webrtc::VideoTrackInterface* track) : track_(track) { 13 | RTC_LOG(LS_INFO) << "[" << (void*)this << "] Sink::Sink"; 14 | deleting_ = false; 15 | updating_ = false; 16 | ptrid_ = IdPointer::Instance().Register(this); 17 | track_->AddOrUpdateSink(this, rtc::VideoSinkWants()); 18 | } 19 | UnityRenderer::Sink::~Sink() { 20 | RTC_LOG(LS_INFO) << "[" << (void*)this << "] Sink::~Sink"; 21 | // デストラクタとテクスチャのアップデートが同時に走る可能性があるので、 22 | // 削除中フラグを立てて、テクスチャのアップデートが終わるまで待つ。 23 | deleting_ = true; 24 | while (updating_) { 25 | // RTC_LOG(LS_INFO) << "[" << (void*)this << "] Sink::~Sink waiting..."; 26 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 27 | } 28 | track_->RemoveSink(this); 29 | IdPointer::Instance().Unregister(ptrid_); 30 | } 31 | ptrid_t UnityRenderer::Sink::GetSinkID() const { 32 | return ptrid_; 33 | } 34 | void UnityRenderer::Sink::SetTrack(webrtc::VideoTrackInterface* track) { 35 | track_->RemoveSink(this); 36 | track->AddOrUpdateSink(this, rtc::VideoSinkWants()); 37 | track_ = track; 38 | } 39 | 40 | rtc::scoped_refptr 41 | UnityRenderer::Sink::GetFrameBuffer() { 42 | std::lock_guard guard(mutex_); 43 | return frame_buffer_; 44 | } 45 | void UnityRenderer::Sink::SetFrameBuffer( 46 | rtc::scoped_refptr v) { 47 | std::lock_guard guard(mutex_); 48 | frame_buffer_ = v; 49 | } 50 | 51 | void UnityRenderer::Sink::OnFrame(const webrtc::VideoFrame& frame) { 52 | rtc::scoped_refptr frame_buffer = 53 | frame.video_frame_buffer(); 54 | 55 | // kNative の場合は別スレッドで変換が出来ない可能性が高いため、 56 | // ここで I420 に変換する。 57 | if (frame_buffer->type() == webrtc::VideoFrameBuffer::Type::kNative) { 58 | frame_buffer = frame_buffer->ToI420(); 59 | } 60 | 61 | SetFrameBuffer(frame_buffer); 62 | } 63 | 64 | void UnityRenderer::Sink::TextureUpdateCallback(int eventID, void* data) { 65 | auto event = static_cast(eventID); 66 | 67 | if (event == kUnityRenderingExtEventUpdateTextureBeginV2) { 68 | auto params = 69 | reinterpret_cast(data); 70 | Sink* p = (Sink*)IdPointer::Instance().Lookup(params->userData); 71 | if (p == nullptr) { 72 | //RTC_LOG(LS_INFO) << "[" << (void*)p 73 | // << "] Sink::TextureUpdateCallback Begin Sink is null"; 74 | return; 75 | } 76 | // TODO(melpon): p を取得した直後、updating_ = true にするまでの間に Sink が削除されたら 77 | // セグフォしてしまうので、問題になるようなら Lookup の時点でロックを獲得する必要がある 78 | 79 | if (p->deleting_) { 80 | //RTC_LOG(LS_INFO) << "[" << (void*)p 81 | // << "] Sink::TextureUpdateCallback Begin deleting"; 82 | p->updating_ = false; 83 | return; 84 | } 85 | p->updating_ = true; 86 | //RTC_LOG(LS_INFO) << "[" << (void*)p 87 | // << "] Sink::TextureUpdateCallback Begin Start"; 88 | auto video_frame_buffer = p->GetFrameBuffer(); 89 | if (!video_frame_buffer) { 90 | return; 91 | } 92 | 93 | // UpdateTextureBegin: Generate and return texture image data. 94 | rtc::scoped_refptr i420_buffer = 95 | webrtc::I420Buffer::Create(params->width, params->height); 96 | i420_buffer->ScaleFrom(*video_frame_buffer->ToI420()); 97 | delete[] p->temp_buf_; 98 | p->temp_buf_ = new uint8_t[params->width * params->height * 4]; 99 | libyuv::I420ToABGR( 100 | i420_buffer->DataY(), i420_buffer->StrideY(), i420_buffer->DataU(), 101 | i420_buffer->StrideU(), i420_buffer->DataV(), i420_buffer->StrideV(), 102 | p->temp_buf_, params->width * 4, params->width, params->height); 103 | params->texData = p->temp_buf_; 104 | //RTC_LOG(LS_INFO) << "[" << (void*)p 105 | // << "] Sink::TextureUpdateCallback Begin Finish"; 106 | } else if (event == kUnityRenderingExtEventUpdateTextureEndV2) { 107 | auto params = 108 | reinterpret_cast(data); 109 | Sink* p = (Sink*)IdPointer::Instance().Lookup(params->userData); 110 | if (p == nullptr) { 111 | //RTC_LOG(LS_INFO) << "[" << (void*)p 112 | // << "] Sink::TextureUpdateCallback End Sink is null"; 113 | return; 114 | } 115 | //RTC_LOG(LS_INFO) << "[" << (void*)p << "] Sink::TextureUpdateCallback End"; 116 | delete[] p->temp_buf_; 117 | p->temp_buf_ = nullptr; 118 | p->updating_ = false; 119 | } 120 | } 121 | 122 | ptrid_t UnityRenderer::AddTrack(webrtc::VideoTrackInterface* track) { 123 | RTC_LOG(LS_INFO) << "UnityRenderer::AddTrack"; 124 | std::unique_ptr sink(new Sink(track)); 125 | auto sink_id = sink->GetSinkID(); 126 | sinks_.push_back(std::make_pair(track, std::move(sink))); 127 | return sink_id; 128 | } 129 | 130 | ptrid_t UnityRenderer::RemoveTrack(webrtc::VideoTrackInterface* track) { 131 | RTC_LOG(LS_INFO) << "UnityRenderer::RemoveTrack"; 132 | auto f = [track](const VideoSinkVector::value_type& sink) { 133 | return sink.first == track; 134 | }; 135 | auto it = std::find_if(sinks_.begin(), sinks_.end(), f); 136 | if (it == sinks_.end()) { 137 | return 0; 138 | } 139 | auto sink_id = it->second->GetSinkID(); 140 | sinks_.erase(std::remove_if(sinks_.begin(), sinks_.end(), f), sinks_.end()); 141 | return sink_id; 142 | } 143 | 144 | void UnityRenderer::ReplaceTrack(webrtc::VideoTrackInterface* oldTrack, 145 | webrtc::VideoTrackInterface* newTrack) { 146 | RTC_LOG(LS_INFO) << "UnityRenderer::ReplaceTrack"; 147 | auto it = std::find_if(sinks_.begin(), sinks_.end(), 148 | [oldTrack](const VideoSinkVector::value_type& sink) { 149 | return sink.first == oldTrack; 150 | }); 151 | if (it == sinks_.end()) { 152 | return; 153 | } 154 | it->first = newTrack; 155 | it->second->SetTrack(newTrack); 156 | } 157 | 158 | } // namespace sora_unity_sdk 159 | -------------------------------------------------------------------------------- /src/unity_renderer.h: -------------------------------------------------------------------------------- 1 | #ifndef SORA_UNITY_SDK_UNITY_RENDERER_H_INCLUDED 2 | #define SORA_UNITY_SDK_UNITY_RENDERER_H_INCLUDED 3 | 4 | // webrtc 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // sora 12 | #include "id_pointer.h" 13 | #include "unity/IUnityRenderingExtensions.h" 14 | 15 | namespace sora_unity_sdk { 16 | 17 | class UnityRenderer { 18 | public: 19 | class Sink : public rtc::VideoSinkInterface { 20 | rtc::scoped_refptr track_; 21 | ptrid_t ptrid_; 22 | std::mutex mutex_; 23 | rtc::scoped_refptr frame_buffer_; 24 | uint8_t* temp_buf_ = nullptr; 25 | std::atomic deleting_; 26 | std::atomic updating_; 27 | 28 | public: 29 | Sink(webrtc::VideoTrackInterface* track); 30 | ~Sink(); 31 | ptrid_t GetSinkID() const; 32 | void SetTrack(webrtc::VideoTrackInterface* track); 33 | 34 | private: 35 | rtc::scoped_refptr GetFrameBuffer(); 36 | void SetFrameBuffer(rtc::scoped_refptr v); 37 | 38 | public: 39 | void OnFrame(const webrtc::VideoFrame& frame) override; 40 | static void UNITY_INTERFACE_API TextureUpdateCallback(int eventID, 41 | void* data); 42 | }; 43 | 44 | private: 45 | typedef std::vector< 46 | std::pair>> 47 | VideoSinkVector; 48 | VideoSinkVector sinks_; 49 | 50 | public: 51 | ptrid_t AddTrack(webrtc::VideoTrackInterface* track); 52 | ptrid_t RemoveTrack(webrtc::VideoTrackInterface* track); 53 | void ReplaceTrack(webrtc::VideoTrackInterface* oldTrack, 54 | webrtc::VideoTrackInterface* newTrack); 55 | }; 56 | 57 | } // namespace sora_unity_sdk 58 | 59 | #endif 60 | --------------------------------------------------------------------------------