diff --git a/.github/workflows/build-unienc.yml b/.github/workflows/build-unienc.yml index c343e7e..1f94e8b 100644 --- a/.github/workflows/build-unienc.yml +++ b/.github/workflows/build-unienc.yml @@ -23,7 +23,7 @@ jobs: timeout-minutes: 30 defaults: run: - working-directory: InstantReplay.Externals/unienc/crates/unienc + working-directory: InstantReplay.Externals/unienc/crates/unienc_c steps: - uses: actions/checkout@v4 with: @@ -31,12 +31,12 @@ jobs: - run: rustup default stable - run: rustup target add ${{ matrix.arch }}-pc-windows-msvc - run: cargo build --target ${{ matrix.arch }}-pc-windows-msvc --profile ${{ env._RUST_BUILD_CONFIG == 'debug' && 'dev' || env._RUST_BUILD_CONFIG }} - - run: Move-Item -Path unienc.dll -Destination libunienc.dll -Force + - run: Move-Item -Path unienc_c.dll -Destination libunienc_c.dll -Force working-directory: InstantReplay.Externals/unienc/target/${{ matrix.arch }}-pc-windows-msvc/${{ env._RUST_BUILD_CONFIG }} - uses: actions/upload-artifact@v4 with: name: ${{ matrix.arch }}-pc-windows - path: InstantReplay.Externals/unienc/target/${{ matrix.arch }}-pc-windows-msvc/${{ env._RUST_BUILD_CONFIG }}/libunienc.dll + path: InstantReplay.Externals/unienc/target/${{ matrix.arch }}-pc-windows-msvc/${{ env._RUST_BUILD_CONFIG }}/libunienc_c.dll retention-days: 1 build-android: name: Build unienc (Android) @@ -47,7 +47,7 @@ jobs: timeout-minutes: 30 defaults: run: - working-directory: InstantReplay.Externals/unienc/crates/unienc + working-directory: InstantReplay.Externals/unienc/crates/unienc_c steps: - uses: actions/checkout@v4 with: @@ -57,11 +57,11 @@ jobs: - run: sudo apt update && sudo apt install gcc-multilib - run: echo "ANDROID_NDK_HOME=$(echo $ANDROID_NDK_LATEST_HOME)" >> $GITHUB_ENV - run: cargo install cargo-ndk - - run: cargo ndk -t ${{ matrix.triple }} --platform 26 build --profile ${{ env._RUST_BUILD_CONFIG == 'debug' && 'dev' || env._RUST_BUILD_CONFIG }} + - run: cargo ndk -t ${{ matrix.triple }} --platform 26 build --profile ${{ env._RUST_BUILD_CONFIG == 'debug' && 'dev' || env._RUST_BUILD_CONFIG }} -F unity - uses: actions/upload-artifact@v4 with: name: ${{ matrix.triple }} - path: InstantReplay.Externals/unienc/target/${{ matrix.triple }}/${{ env._RUST_BUILD_CONFIG }}/libunienc.so + path: InstantReplay.Externals/unienc/target/${{ matrix.triple }}/${{ env._RUST_BUILD_CONFIG }}/libunienc_c.so retention-days: 1 build-macos: name: Build unienc (macOS) @@ -72,18 +72,18 @@ jobs: timeout-minutes: 30 defaults: run: - working-directory: InstantReplay.Externals/unienc/crates/unienc + working-directory: InstantReplay.Externals/unienc/crates/unienc_c steps: - uses: actions/checkout@v4 with: submodules: 'true' - run: rustup default stable - run: rustup target add ${{ matrix.arch }}-apple-darwin - - run: cargo build --target ${{ matrix.arch }}-apple-darwin --profile ${{ env._RUST_BUILD_CONFIG == 'debug' && 'dev' || env._RUST_BUILD_CONFIG }} + - run: cargo build --target ${{ matrix.arch }}-apple-darwin --profile ${{ env._RUST_BUILD_CONFIG == 'debug' && 'dev' || env._RUST_BUILD_CONFIG }} -F unity - uses: actions/upload-artifact@v4 with: name: ${{ matrix.arch }}-apple-darwin - path: InstantReplay.Externals/unienc/target/${{ matrix.arch }}-apple-darwin/${{ env._RUST_BUILD_CONFIG }}/libunienc.dylib + path: InstantReplay.Externals/unienc/target/${{ matrix.arch }}-apple-darwin/${{ env._RUST_BUILD_CONFIG }}/libunienc_c.dylib retention-days: 1 build-ios: name: Build unienc (iOS) @@ -94,18 +94,18 @@ jobs: timeout-minutes: 30 defaults: run: - working-directory: InstantReplay.Externals/unienc/crates/unienc + working-directory: InstantReplay.Externals/unienc/crates/unienc_c steps: - uses: actions/checkout@v4 with: submodules: 'true' - run: rustup default stable - run: rustup target add ${{ matrix.arch }}-apple-ios - - run: cargo build --target ${{ matrix.arch }}-apple-ios --profile ${{ env._RUST_BUILD_CONFIG == 'debug' && 'dev' || env._RUST_BUILD_CONFIG }} + - run: cargo build --target ${{ matrix.arch }}-apple-ios --profile ${{ env._RUST_BUILD_CONFIG == 'debug' && 'dev' || env._RUST_BUILD_CONFIG }} -F unity - uses: actions/upload-artifact@v4 with: name: ${{ matrix.arch }}-apple-ios - path: InstantReplay.Externals/unienc/target/${{ matrix.arch }}-apple-ios/${{ env._RUST_BUILD_CONFIG }}/libunienc.a + path: InstantReplay.Externals/unienc/target/${{ matrix.arch }}-apple-ios/${{ env._RUST_BUILD_CONFIG }}/libunienc_c.a retention-days: 1 build-linux: name: Build unienc (Linux) @@ -116,7 +116,7 @@ jobs: timeout-minutes: 30 defaults: run: - working-directory: InstantReplay.Externals/unienc/crates/unienc + working-directory: InstantReplay.Externals/unienc/crates/unienc_c steps: - uses: actions/checkout@v4 with: @@ -128,7 +128,7 @@ jobs: - uses: actions/upload-artifact@v4 with: name: ${{ matrix.arch }}-unknown-linux - path: InstantReplay.Externals/unienc/target/${{ matrix.arch }}-unknown-linux-gnu/${{ env._RUST_BUILD_CONFIG }}/libunienc.so + path: InstantReplay.Externals/unienc/target/${{ matrix.arch }}-unknown-linux-gnu/${{ env._RUST_BUILD_CONFIG }}/libunienc_c.so retention-days: 1 update-unity-native: name: Push unienc native libraries diff --git a/InstantReplay.Externals/unienc/Cargo.lock b/InstantReplay.Externals/unienc/Cargo.lock index 132d0cb..32245d2 100644 --- a/InstantReplay.Externals/unienc/Cargo.lock +++ b/InstantReplay.Externals/unienc/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - [[package]] name = "aho-corasick" version = "1.1.3" @@ -26,15 +11,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "anyhow" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" -dependencies = [ - "backtrace", -] - [[package]] name = "ash" version = "0.38.0+1.3.281" @@ -44,21 +20,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "bincode" version = "2.0.1" @@ -143,9 +104,9 @@ dependencies = [ [[package]] name = "csbindgen" -version = "1.9.3" +version = "1.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c26b9831049b947d154bba920e4124053def72447be6fb106a96f483874b482a" +checksum = "710604f525e7b68070458083252602c2bf2afe255dfe019e83bd57bdfa50bdb4" dependencies = [ "regex", "syn", @@ -270,12 +231,6 @@ dependencies = [ "wasi 0.14.2+wasi-0.2.4", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "jni" version = "0.21.1" @@ -287,7 +242,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -326,15 +281,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "miniz_oxide" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" -dependencies = [ - "adler2", -] - [[package]] name = "mio" version = "1.0.4" @@ -612,15 +558,6 @@ dependencies = [ "objc2-metal", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "pin-project-lite" version = "0.2.16" @@ -724,12 +661,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - [[package]] name = "same-file" version = "1.0.6" @@ -782,12 +713,12 @@ checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "socket2" -version = "0.5.10" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -807,7 +738,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", ] [[package]] @@ -821,13 +761,23 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio" -version = "1.45.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", "libc", "mio", @@ -835,14 +785,14 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -857,14 +807,9 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unienc" -version = "0.1.0" +version = "1.4.1" dependencies = [ - "anyhow", "bincode", - "csbindgen", - "futures", - "libc", - "ndk-sys", "rand", "tokio", "unienc_android_mc", @@ -872,20 +817,19 @@ dependencies = [ "unienc_common", "unienc_ffmpeg", "unienc_windows_mf", - "unity-native-plugin", ] [[package]] name = "unienc_android_mc" -version = "0.1.0" +version = "1.4.1" dependencies = [ - "anyhow", "ash", "bincode", "bitflags", "jni", "libc", "ndk-sys", + "thiserror 2.0.17", "tokio", "unienc_common", "unity-native-plugin", @@ -894,9 +838,8 @@ dependencies = [ [[package]] name = "unienc_apple_vt" -version = "0.1.0" +version = "1.4.1" dependencies = [ - "anyhow", "bincode", "bitflags", "block2", @@ -912,39 +855,54 @@ dependencies = [ "objc2-foundation", "objc2-metal", "objc2-video-toolbox", + "thiserror 2.0.17", "tokio", "unienc_common", "unity-native-plugin", ] [[package]] -name = "unienc_common" -version = "0.1.0" +name = "unienc_c" +version = "1.4.1" dependencies = [ - "anyhow", "bincode", + "csbindgen", + "futures", + "libc", + "ndk-sys", + "thiserror 2.0.17", "tokio", + "unienc", + "unity-native-plugin", +] + +[[package]] +name = "unienc_common" +version = "1.4.1" +dependencies = [ + "bincode", + "thiserror 2.0.17", "unity-native-plugin", ] [[package]] name = "unienc_ffmpeg" -version = "0.1.0" +version = "1.4.1" dependencies = [ - "anyhow", "bincode", "cros-codecs", "libc", + "thiserror 2.0.17", "tokio", "unienc_common", ] [[package]] name = "unienc_windows_mf" -version = "0.1.0" +version = "1.4.1" dependencies = [ - "anyhow", "bincode", + "thiserror 2.0.17", "tokio", "unienc_common", "windows", @@ -1139,20 +1097,29 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", ] [[package]] @@ -1179,13 +1146,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + [[package]] name = "windows-threading" version = "0.1.0" @@ -1207,6 +1191,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -1219,6 +1209,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -1231,12 +1227,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -1249,6 +1257,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -1261,6 +1275,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -1273,6 +1293,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -1285,6 +1311,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/InstantReplay.Externals/unienc/Cargo.toml b/InstantReplay.Externals/unienc/Cargo.toml index a2c92a7..ab28c76 100644 --- a/InstantReplay.Externals/unienc/Cargo.toml +++ b/InstantReplay.Externals/unienc/Cargo.toml @@ -1,25 +1,34 @@ [workspace] members = [ "./crates/*", "./external/unity-native-plugin-rs/Cargo.toml"] +default-members = ["./crates/unienc_c"] resolver = "2" [workspace.dependencies] objc2 = { version = "0.6.1", features = ["catch-all"] } bincode = { version = "2.0.1", features = ["derive"] } -anyhow = { version = "1.0.98", features = ["backtrace", "std"] } +thiserror = "2.0" unienc_common = { path = "./crates/unienc_common" } +unienc = { path = "./crates/unienc" } unienc_android_mc = { path = "./crates/unienc_android_mc" } unienc_windows_mf = { path = "./crates/unienc_windows_mf" } unienc_apple_vt = { path = "./crates/unienc_apple_vt" } -unienc_ffmpeg = { path = "./crates/unienc_ffmpeg" } +unienc_ffmpeg = { path = "./crates/unienc_ffmpeg" } +unity-native-plugin = { path = "./external/unity-native-plugin-rs/unity-native-plugin" } +unity-native-plugin-vulkan = { path = "./external/unity-native-plugin-rs/unity-native-plugin-vulkan" } [patch.crates-io] unity-native-plugin = { path = "./external/unity-native-plugin-rs/unity-native-plugin" } unity-native-plugin-vulkan = { path = "./external/unity-native-plugin-rs/unity-native-plugin-vulkan" } [workspace.package] -edition = "2021" +edition = "2024" license = "MIT" authors = [ "CyberAgent" ] +version = "1.4.1" [profile.release] -panic = "abort" # It helps making crash stacktraces readable +panic = "abort" # It helps to make crash stacktraces readable + +[profile.release-unwind] # for platforms that support unwinding (e.g. wasm) +inherits = "release" +panic = "unwind" diff --git a/InstantReplay.Externals/unienc/README.md b/InstantReplay.Externals/unienc/README.md index 3b80e3c..6e52204 100644 --- a/InstantReplay.Externals/unienc/README.md +++ b/InstantReplay.Externals/unienc/README.md @@ -5,8 +5,8 @@ A Rust-based unified encoding system that provides cross-platform video and audi The project implements platform-specific encoders: - **Apple platforms**: VideoToolbox for video, AudioToolbox for audio, AVFoundation for muxing - **Android**: MediaCodec for video/audio encoding and MediaMuxer for muxing -- **Windows**: Media Foundation (implementation in progress) -- **Linux and others**: FFmpeg (installed on the system) +- **Windows**: Media Foundation +- **Linux and other UNIX-based system**: FFmpeg (installed on the system) ## Architecture @@ -17,7 +17,8 @@ The codebase follows a modular architecture with platform-specific implementatio - `crates/unienc_android_mc/`: Android MediaCodec implementation - `crates/unienc_windows_mf/`: Windows Media Foundation implementation - `crates/unienc_ffmpeg/`: FFmpeg implementation for unix-like systems -- `crates/unienc/`: Main crate that conditionally compiles platform-specific implementations and exposes C FFI functions for the Unity plugin. `csbindgen` generates the C# bindings for the functions. +- `crates/unienc/`: A crate that conditionally compiles platform-specific implementations +- `crates/unienc_c/` Entrypoint crate that exposes C FFI functions for the Unity plugin. `csbindgen` generates the C# bindings for the functions Key traits: - `EncodingSystem`: Factory for creating encoders and muxers diff --git a/InstantReplay.Externals/unienc/crates/unienc/Cargo.toml b/InstantReplay.Externals/unienc/crates/unienc/Cargo.toml index 98a421b..edd1839 100644 --- a/InstantReplay.Externals/unienc/crates/unienc/Cargo.toml +++ b/InstantReplay.Externals/unienc/crates/unienc/Cargo.toml @@ -1,29 +1,19 @@ [package] name = "unienc" -version = "0.1.0" +version.workspace = true edition.workspace = true license.workspace = true authors.workspace = true -[lib] -crate-type = ["cdylib", "staticlib", "rlib"] - [dependencies] -anyhow = { workspace = true, features = ["backtrace", "std"] } bincode = { version = "2.0.1", features = ["serde"] } -futures = "0.3.31" -rand = "0.9.1" -tokio = { version = "1.45.1", features = ["rt", "macros", "rt-multi-thread"] } unienc_common = { workspace = true } -unity-native-plugin = "0.8.0" [target.'cfg(all(target_family = "unix", not(target_vendor = "apple"), not(target_os = "android")))'.dependencies] unienc_ffmpeg = { workspace = true } [target.'cfg(target_os = "android")'.dependencies] unienc_android_mc = { workspace = true } -libc = "0.2.174" -ndk-sys = "0.6.0" [target.'cfg(windows)'.dependencies] unienc_windows_mf = { workspace = true } @@ -31,5 +21,10 @@ unienc_windows_mf = { workspace = true } [target.'cfg(target_vendor = "apple")'.dependencies] unienc_apple_vt = { workspace = true } -[build-dependencies] -csbindgen = "1.9.3" +[dev-dependencies] +tokio = "1.48.0" +rand = "0.9.1" + +[features] +default = [] +unity = ["unienc_common/unity"] \ No newline at end of file diff --git a/InstantReplay.Externals/unienc/crates/unienc/src/graphics.rs b/InstantReplay.Externals/unienc/crates/unienc/src/graphics.rs deleted file mode 100644 index 28c35ce..0000000 --- a/InstantReplay.Externals/unienc/crates/unienc/src/graphics.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::os::raw::{c_int, c_void}; -use unienc_common::{EncodingSystem, GraphicsEventIssuer}; -use crate::*; - -pub struct UniencGraphicsEventIssuer { - func: UniencIssueGraphicsEventCallback, - weak_runtime: WeakRuntime, -} - -impl UniencGraphicsEventIssuer { - pub fn new(func: UniencIssueGraphicsEventCallback, weak_runtime: WeakRuntime) -> Self { - Self { func, weak_runtime } - } -} - -struct GraphicsEventContext { - callback: Box, - weak_runtime: WeakRuntime, -} - -impl GraphicsEventIssuer for UniencGraphicsEventIssuer { - fn issue_graphics_event(&self, callback: Box, event_id: c_int) { - - let user_data = Box::into_raw(Box::new(GraphicsEventContext{ callback, weak_runtime: self.weak_runtime.clone() })) as *mut c_void; - unsafe { - (self.func)( - Some(graphics_event_callback_trampoline), - event_id, - user_data, - ) - } - } -} - -unsafe extern "system" fn graphics_event_callback_trampoline( - _event_id: c_int, - user_data: *mut c_void, -) { - let context = Box::::from_raw(user_data as *mut _); - let Some(runtime) = context.weak_runtime.upgrade() else { - println!("Failed to upgrade runtime in graphics event callback"); - return; - }; - let _guard = runtime.enter(); - let callback = context.callback; - callback(); -} - -#[no_mangle] -pub unsafe extern "C" fn unienc_is_blit_supported(system: *const PlatformEncodingSystem) -> bool { - (&*system).is_blit_supported() -} - -#[no_mangle] -pub unsafe extern "C" fn unienc_free_graphics_event_context( - context: *mut c_void, -) { - if !context.is_null() { - unsafe { - let _ = Box::>::from_raw(context as *mut Box); - } - } -} \ No newline at end of file diff --git a/InstantReplay.Externals/unienc/crates/unienc/src/lib.rs b/InstantReplay.Externals/unienc/crates/unienc/src/lib.rs index c25144f..8100ea1 100644 --- a/InstantReplay.Externals/unienc/crates/unienc/src/lib.rs +++ b/InstantReplay.Externals/unienc/crates/unienc/src/lib.rs @@ -1,18 +1,9 @@ - -mod buffer; -mod graphics; -mod utils; -mod runtime; -mod types; -mod api; -mod ffi; mod platform; -mod unity; -pub(crate) use crate::ffi::*; -pub(crate) use crate::platform::*; -pub(crate) use crate::runtime::*; -pub(crate) use crate::types::*; -pub(crate) use crate::utils::*; -pub(crate) use crate::graphics::*; +pub use platform::*; +pub use unienc_common::*; +#[cfg(target_os = "android")] +pub mod android { + pub use unienc_android_mc::set_java_vm; +} \ No newline at end of file diff --git a/InstantReplay.Externals/unienc/crates/unienc/src/platform.rs b/InstantReplay.Externals/unienc/crates/unienc/src/platform.rs index 253faa0..e16d2ed 100644 --- a/InstantReplay.Externals/unienc/crates/unienc/src/platform.rs +++ b/InstantReplay.Externals/unienc/crates/unienc/src/platform.rs @@ -1,54 +1,23 @@ -use crate::*; - #[cfg(target_vendor = "apple")] -pub type PlatformEncodingSystem = unienc_apple_vt::VideoToolboxEncodingSystem< - VideoEncoderOptionsNative, - AudioEncoderOptionsNative, ->; +pub type PlatformEncodingSystem = unienc_apple_vt::VideoToolboxEncodingSystem; #[cfg(target_os = "android")] -pub type PlatformEncodingSystem = unienc_android_mc::MediaCodecEncodingSystem< - VideoEncoderOptionsNative, - AudioEncoderOptionsNative, ->; +pub type PlatformEncodingSystem = unienc_android_mc::MediaCodecEncodingSystem; #[cfg(windows)] -pub type PlatformEncodingSystem = unienc_windows_mf::MediaFoundationEncodingSystem< - VideoEncoderOptionsNative, - AudioEncoderOptionsNative, ->; +pub type PlatformEncodingSystem = unienc_windows_mf::MediaFoundationEncodingSystem; #[cfg(all( unix, not(any(target_vendor = "apple", target_os = "android", windows)) ))] -pub type PlatformEncodingSystem = -unienc_ffmpeg::FFmpegEncodingSystem; +pub type PlatformEncodingSystem = unienc_ffmpeg::FFmpegEncodingSystem; #[cfg(not(any(target_vendor = "apple", target_os = "android", windows, unix)))] -pub type PlatformEncodingSystem = (); +pub type PlatformEncodingSystem = (); #[cfg(not(any(target_vendor = "apple", target_os = "android", windows, unix)))] compile_error!("Unsupported platform"); - -use unienc_common::EncoderOutput; - -type VideoEncoder = -::VideoEncoderType; -pub type VideoEncoderInput = ::InputType; -pub type VideoEncoderOutput = ::OutputType; -type AudioEncoder = -::AudioEncoderType; -pub type AudioEncoderInput = ::InputType; -pub type AudioEncoderOutput = ::OutputType; -type Muxer = ::MuxerType; -pub type VideoMuxerInput = ::VideoInputType; -pub type AudioMuxerInput = ::AudioInputType; -pub type MuxerCompletionHandle = ::CompletionHandleType; - -pub type VideoEncodedData = ::Data; -pub type AudioEncodedData = ::Data; -pub type BlitSource = ::BlitSourceType; \ No newline at end of file diff --git a/InstantReplay.Externals/unienc/crates/unienc/src/unity.rs b/InstantReplay.Externals/unienc/crates/unienc/src/unity.rs deleted file mode 100644 index 005f276..0000000 --- a/InstantReplay.Externals/unienc/crates/unienc/src/unity.rs +++ /dev/null @@ -1,39 +0,0 @@ - -#[cfg(not(target_os = "ios"))] -mod entry_points { - use unienc_common::EncodingSystem; - use crate::platform::PlatformEncodingSystem; - - unity_native_plugin::unity_native_plugin_entry_point! { - fn unity_plugin_load(interfaces: &unity_native_plugin::interface::UnityInterfaces) { - PlatformEncodingSystem::unity_plugin_load(interfaces); - } - fn unity_plugin_unload() { - PlatformEncodingSystem::unity_plugin_unload(); - } - } -} - -// statically linked for iOS -// we add `unienc_` prefix to avoid name collision with other plugins -#[cfg(target_os = "ios")] -mod entry_points { - use unienc_common::EncodingSystem; - use crate::platform::PlatformEncodingSystem; - #[unsafe(no_mangle)] - #[allow(non_snake_case)] - extern "system" fn unienc_UnityPluginLoad( - interfaces: *mut unity_native_plugin::IUnityInterfaces, - ) { - unity_native_plugin::interface::UnityInterfaces::set_native_unity_interfaces(interfaces); - PlatformEncodingSystem::unity_plugin_load(unity_native_plugin::interface::UnityInterfaces::get()); - } - - #[unsafe(no_mangle)] - #[allow(non_snake_case)] - extern "system" fn unienc_UnityPluginUnload() { - PlatformEncodingSystem::unity_plugin_unload(); - unity_native_plugin::interface::UnityInterfaces::set_native_unity_interfaces(std::ptr::null_mut()); - } -} - diff --git a/InstantReplay.Externals/unienc/crates/unienc/tests/integration_test.rs b/InstantReplay.Externals/unienc/crates/unienc/tests/integration_test.rs index ea74812..7049b78 100644 --- a/InstantReplay.Externals/unienc/crates/unienc/tests/integration_test.rs +++ b/InstantReplay.Externals/unienc/crates/unienc/tests/integration_test.rs @@ -1,20 +1,64 @@ -use unienc_common::{ - buffer::SharedBuffer, AudioSample, CompletionHandle, EncodedData, Encoder, EncoderInput, - EncoderOutput, EncodingSystem, Muxer, MuxerInput, VideoFrame, VideoFrameBgra32, VideoSample, -}; +use unienc_common::{buffer::SharedBuffer, AudioSample, CompletionHandle, EncodedData, Encoder, EncoderInput, EncoderOutput, EncodingSystem, Muxer, MuxerInput, VideoFrame, VideoFrameBgra32, VideoSample}; use unienc::PlatformEncodingSystem; +#[derive(Copy, Clone)] +pub struct VideoEncoderOptions { + pub width: u32, + pub height: u32, + pub fps_hint: u32, + pub bitrate: u32, +} + +#[derive(Copy, Clone)] +pub struct AudioEncoderOptions { + pub sample_rate: u32, + pub channels: u32, + pub bitrate: u32, +} + +impl unienc::VideoEncoderOptions for VideoEncoderOptions { + fn width(&self) -> u32 { + self.width + } + + fn height(&self) -> u32 { + self.height + } + + fn fps_hint(&self) -> u32 { + self.fps_hint + } + + fn bitrate(&self) -> u32 { + self.bitrate + } +} + +impl unienc::AudioEncoderOptions for AudioEncoderOptions { + fn sample_rate(&self) -> u32 { + self.sample_rate + } + + fn channels(&self) -> u32 { + self.channels + } + + fn bitrate(&self) -> u32 { + self.bitrate + } +} + #[tokio::test(flavor = "multi_thread")] async fn test_e2e() { test_e2e_typed(PlatformEncodingSystem::new( - &unienc::VideoEncoderOptionsNative { + &VideoEncoderOptions { width: 1280, height: 720, fps_hint: 5, bitrate: 1000000, }, - &unienc::AudioEncoderOptionsNative { + &AudioEncoderOptions { sample_rate: 48000, channels: 2, bitrate: 128000, diff --git a/InstantReplay.Externals/unienc/crates/unienc_android_mc/Cargo.toml b/InstantReplay.Externals/unienc/crates/unienc_android_mc/Cargo.toml index 2b7f6d5..babd8fb 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_android_mc/Cargo.toml +++ b/InstantReplay.Externals/unienc/crates/unienc_android_mc/Cargo.toml @@ -1,19 +1,19 @@ [package] name = "unienc_android_mc" -version = "0.1.0" +version.workspace = true edition.workspace = true license.workspace = true authors.workspace = true [dependencies] -anyhow = "1.0.98" +thiserror = { workspace = true } jni = "0.21.1" tokio = { version = "1.45.1", features = ["rt", "time", "macros", "sync"] } -unienc_common = { workspace = true } +unienc_common = { workspace = true, features = ["unity"] } bincode = { workspace = true } ndk-sys = { version = "0.6.0", features = ["media"] } bitflags = "2.9.1" libc = "0.2.174" ash = "0.38.0" -unity-native-plugin = { version = "0.8.0", features = ["profiler"] } -unity-native-plugin-vulkan = "0.8.0" +unity-native-plugin = { workspace = true, features = ["profiler"] } +unity-native-plugin-vulkan.workspace = true diff --git a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/audio/mod.rs b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/audio/mod.rs index 985ce3d..e8f1b2b 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/audio/mod.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/audio/mod.rs @@ -1,8 +1,8 @@ -use anyhow::Result; use jni::{objects::JValue, signature::ReturnType, sys::jint, JNIEnv}; use std::time::Duration; use unienc_common::{AudioSample, Encoder, EncoderInput, EncoderOutput}; +use crate::error::{AndroidError, Result}; use crate::{ common::{media_codec_buffer_flag::BUFFER_FLAG_END_OF_STREAM, *}, config::{format_keys::*, *}, @@ -32,7 +32,7 @@ impl Encoder for MediaCodecAudioEncoder { type InputType = MediaCodecAudioEncoderInput; type OutputType = MediaCodecAudioEncoderOutput; - fn get(self) -> Result<(Self::InputType, Self::OutputType)> { + fn get(self) -> unienc_common::Result<(Self::InputType, Self::OutputType)> { Ok((self.input, self.output)) } } @@ -92,7 +92,7 @@ impl Drop for MediaCodecAudioEncoderInput { if buffer_index == media_codec_errors::INFO_TRY_AGAIN_LATER { std::thread::sleep(Duration::from_millis(10)); } else { - return Err(anyhow::anyhow!("No input buffer available")); + return Err(AndroidError::NoInputBuffer); } } }() @@ -103,63 +103,72 @@ impl Drop for MediaCodecAudioEncoderInput { impl EncoderInput for MediaCodecAudioEncoderInput { type Data = AudioSample; - async fn push(&mut self, data: Self::Data) -> Result<()> { - // Convert i16 samples to byte array - let byte_data_vec = i16_to_bytes(&data.data); - let mut byte_data = byte_data_vec.as_slice(); - - while !byte_data.is_empty() { - // Get input buffer - let buffer_index = self - .codec - .dequeue_input_buffer(Duration::from_millis(100))?; - if buffer_index >= 0 { - let input_buffer = self.codec.get_input_buffer(buffer_index)?; - { - let env = &mut attach_current_thread()?; - let (_base_ptr, capacity, position) = - get_direct_buffer_info(env, input_buffer.as_obj())?; - - let bytes_to_write = std::cmp::min(byte_data.len(), capacity - position); - crate::common::write_to_buffer( - env, - &input_buffer, - &byte_data[..bytes_to_write], - )?; - byte_data = &byte_data[bytes_to_write..]; - - // Calculate timestamp in microseconds - let timestamp_us = (data.timestamp_in_samples as f64 / self.sample_rate as f64 - * 1_000_000.0) as i64; - - self.last_timestamp = timestamp_us; + async fn push(&mut self, data: Self::Data) -> unienc_common::Result<()> { + push_impl(self, data).await.map_err(Into::into) + } +} - // Queue input buffer - self.codec.queue_input_buffer( - buffer_index, - 0, - bytes_to_write, - timestamp_us, - 0, - )?; - } - } else if buffer_index == media_codec_errors::INFO_TRY_AGAIN_LATER { - std::thread::sleep(Duration::from_millis(10)); - continue; - } else { - return Err(anyhow::anyhow!("No input buffer available")); +async fn push_impl( + this: &mut MediaCodecAudioEncoderInput, + data: AudioSample, +) -> Result<()> { + // Convert i16 samples to byte array + let byte_data_vec = i16_to_bytes(&data.data); + let mut byte_data = byte_data_vec.as_slice(); + + while !byte_data.is_empty() { + // Get input buffer + let buffer_index = this + .codec + .dequeue_input_buffer(Duration::from_millis(100))?; + if buffer_index >= 0 { + let input_buffer = this.codec.get_input_buffer(buffer_index)?; + { + let env = &mut attach_current_thread()?; + let (_base_ptr, capacity, position) = + get_direct_buffer_info(env, input_buffer.as_obj())?; + + let bytes_to_write = std::cmp::min(byte_data.len(), capacity - position); + crate::common::write_to_buffer( + env, + &input_buffer, + &byte_data[..bytes_to_write], + )?; + byte_data = &byte_data[bytes_to_write..]; + + // Calculate timestamp in microseconds + let timestamp_us = (data.timestamp_in_samples as f64 / this.sample_rate as f64 + * 1_000_000.0) as i64; + + this.last_timestamp = timestamp_us; + + // Queue input buffer + this.codec.queue_input_buffer( + buffer_index, + 0, + bytes_to_write, + timestamp_us, + 0, + )?; } + } else if buffer_index == media_codec_errors::INFO_TRY_AGAIN_LATER { + std::thread::sleep(Duration::from_millis(10)); + continue; + } else { + return Err(AndroidError::NoInputBuffer); } - - Ok(()) } + + Ok(()) } impl EncoderOutput for MediaCodecAudioEncoderOutput { type Data = CommonEncodedData; - async fn pull(&mut self) -> Result> { - pull_encoded_data_with_codec(&self.codec, &mut self.end_of_stream).await + async fn pull(&mut self) -> unienc_common::Result> { + pull_encoded_data_with_codec(&self.codec, &mut self.end_of_stream) + .await + .map_err(Into::into) } } diff --git a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/common.rs b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/common.rs index 5345992..e35d110 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/common.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/common.rs @@ -1,4 +1,3 @@ -use anyhow::Result; use bincode::{Decode, Encode}; use jni::{ objects::{JByteArray, JObject, JString, JValue}, @@ -8,6 +7,7 @@ use jni::{ use std::{collections::HashMap, fmt::Display, sync::Arc, time::Duration}; use unienc_common::{EncodedData, UniencSampleKind, VideoFrameBgra32}; +use crate::error::{AndroidError, Result}; use crate::java::*; /// Inner struct for MediaCodec @@ -123,7 +123,7 @@ impl MediaCodec { let image = result.l()?; if image.is_null() { - return Err(anyhow::anyhow!("Image is null")); + return Err(AndroidError::ImageNull); } // Get width and height @@ -978,7 +978,7 @@ impl ImageWriter { .l()?; if image.is_null() { - return Err(anyhow::anyhow!("dequeueInputImage returned null")); + return Err(AndroidError::DequeueImageNull); } let image_ref = SafeGlobalRef::new(env, image)?; @@ -1038,7 +1038,7 @@ impl ImageWriterImage { .l()?; if hardware_buffer.is_null() { - return Err(anyhow::anyhow!("getHardwareBuffer returned null")); + return Err(AndroidError::HardwareBufferNull); } // Convert Java HardwareBuffer to native AHardwareBuffer* @@ -1052,9 +1052,7 @@ impl ImageWriterImage { env.call_method(&hardware_buffer, "close", "()V", &[])?; if ahb.is_null() { - return Err(anyhow::anyhow!( - "AHardwareBuffer_fromHardwareBuffer returned null" - )); + return Err(AndroidError::AHardwareBufferNull); } Ok(ahb) @@ -1077,10 +1075,7 @@ pub fn write_bgra_to_yuv_planes_with_padding( planes: &[ImagePlane], ) -> Result<()> { if planes.len() != 3 { - return Err(anyhow::anyhow!( - "Unsupported number of planes: {}", - planes.len() - )); + return Err(AndroidError::UnsupportedPlaneCount(planes.len())); } let (y_data, u_data, v_data) = sample.to_yuv420_planes(Some((padded_width, padded_height)))?; @@ -1163,7 +1158,7 @@ pub(crate) fn map_to_format<'a>( &[JValue::Object(&byte_array)], )? else { - return Err(anyhow::anyhow!("Failed to create byte buffer")); + return Err(AndroidError::ByteBufferCreationFailed); }; env.call_method( diff --git a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/error.rs b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/error.rs new file mode 100644 index 0000000..51d6ff3 --- /dev/null +++ b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/error.rs @@ -0,0 +1,252 @@ +use thiserror::Error; +use unienc_common::{CategorizedError, ErrorCategory}; + +/// Error type for unienc_android_mc +#[derive(Error, Debug)] +pub enum AndroidError { + // JNI related errors + #[error("JavaVM not initialized")] + JavaVmNotInitialized, + + #[error("Failed to attach current thread to JVM: {0}")] + JvmAttachFailed(String), + + #[error("JNI exception occurred")] + JniException, + + #[error("Failed to create global reference")] + JniGlobalRefFailed, + + #[error("Failed to call method '{0}'")] + JniMethodCallFailed(String), + + #[error("Expected {expected} return value")] + JniUnexpectedReturnValue { expected: &'static str }, + + #[error("Failed to get field '{0}'")] + JniFieldGetFailed(String), + + #[error("Failed to create Java string")] + JniStringCreationFailed, + + #[error("Buffer is not a direct buffer")] + NotDirectBuffer, + + // MediaCodec related errors + #[error("Image is null")] + ImageNull, + + #[error("No input buffer available")] + NoInputBuffer, + + #[error("Unsupported number of planes: {0}")] + UnsupportedPlaneCount(usize), + + #[error("Failed to create byte buffer")] + ByteBufferCreationFailed, + + // ImageWriter related errors + #[error("dequeueInputImage returned null")] + DequeueImageNull, + + #[error("getHardwareBuffer returned null")] + HardwareBufferNull, + + #[error("AHardwareBuffer_fromHardwareBuffer returned null")] + AHardwareBufferNull, + + // Vulkan related errors + #[error("Null Vulkan texture pointer")] + NullVulkanTexture, + + #[error("Vulkan context is not initialized")] + ContextNotInitialized, + + #[error("Failed to lock mutex")] + MutexPoisoned, + + #[error("Failed to set global state")] + GlobalStateSetFailed, + + #[error("Vulkan error: {0}")] + Vulkan(ash::vk::Result), + + #[error("Failed to create graphics pipeline: {0}")] + GraphicsPipelineCreationFailed(ash::vk::Result), + + #[error("Failed to create framebuffer: {0}")] + FramebufferCreationFailed(ash::vk::Result), + + #[error("Failed to create image view: {0}")] + ImageViewCreationFailed(ash::vk::Result), + + #[error("Failed to create image from AHardwareBuffer: {0}")] + HardwareBufferImageCreationFailed(ash::vk::Result), + + #[error("Failed to allocate memory for AHardwareBuffer: {0}")] + HardwareBufferMemoryAllocationFailed(ash::vk::Result), + + #[error("Failed to bind image memory: {0}")] + ImageMemoryBindFailed(ash::vk::Result), + + #[error("Failed to wait for fence: {0}")] + FenceWaitFailed(ash::vk::Result), + + #[error("No available descriptor sets")] + NoAvailableDescriptorSets, + + #[error("No suitable memory type found")] + NoSuitableMemoryType, + + #[error("AHardwareBuffer properties query failed: {0}")] + HardwareBufferPropertiesFailed(ash::vk::Result), + + #[error("Unsupported graphics format: {0}")] + UnsupportedGraphicsFormat(u32), + + // Muxer related errors + #[error("Muxer already started")] + MuxerAlreadyStarted, + + #[error("Track does not have metadata")] + MissingTrackMetadata, + + #[error("Failed to send {0} signal")] + ChannelSendFailed(&'static str), + + #[error("Invalid output path")] + InvalidOutputPath, + + // Encoder related errors + #[error("This encoder is initialized for other input")] + EncoderInputMismatch, + + #[error("Event ID is not reserved")] + EventIdNotReserved, + + #[error("Failed to send from render thread")] + RenderThreadSendFailed, + + // External error conversions + #[error(transparent)] + Jni(#[from] jni::errors::Error), + + #[error(transparent)] + Common(#[from] unienc_common::CommonError), + + #[error(transparent)] + OneshotRecv(#[from] tokio::sync::oneshot::error::RecvError), + + #[error("Vulkan operation failed: {0:?}")] + VulkanResult(#[from] ash::vk::Result), + + #[error("UTF-8 encoding error: {0}")] + Utf8(#[from] std::str::Utf8Error), + + // Generic errors + #[error("{0}")] + Other(String), +} + +/// Result type alias for unienc_android_mc +pub type Result = std::result::Result; + +impl CategorizedError for AndroidError { + fn category(&self) -> ErrorCategory { + match self { + // Initialization errors + AndroidError::JavaVmNotInitialized => ErrorCategory::Initialization, + AndroidError::JvmAttachFailed(_) => ErrorCategory::Initialization, + AndroidError::ContextNotInitialized => ErrorCategory::Initialization, + + // Platform/JNI errors + AndroidError::JniException => ErrorCategory::Platform, + AndroidError::JniGlobalRefFailed => ErrorCategory::Platform, + AndroidError::JniMethodCallFailed(_) => ErrorCategory::Platform, + AndroidError::JniUnexpectedReturnValue { .. } => ErrorCategory::Platform, + AndroidError::JniFieldGetFailed(_) => ErrorCategory::Platform, + AndroidError::JniStringCreationFailed => ErrorCategory::Platform, + AndroidError::Jni(_) => ErrorCategory::Platform, + + // Resource allocation errors + AndroidError::NotDirectBuffer => ErrorCategory::ResourceAllocation, + AndroidError::ImageNull => ErrorCategory::ResourceAllocation, + AndroidError::NoInputBuffer => ErrorCategory::ResourceAllocation, + AndroidError::ByteBufferCreationFailed => ErrorCategory::ResourceAllocation, + AndroidError::DequeueImageNull => ErrorCategory::ResourceAllocation, + AndroidError::HardwareBufferNull => ErrorCategory::ResourceAllocation, + AndroidError::AHardwareBufferNull => ErrorCategory::ResourceAllocation, + AndroidError::NullVulkanTexture => ErrorCategory::ResourceAllocation, + AndroidError::NoAvailableDescriptorSets => ErrorCategory::ResourceAllocation, + AndroidError::NoSuitableMemoryType => ErrorCategory::ResourceAllocation, + AndroidError::HardwareBufferMemoryAllocationFailed(_) => ErrorCategory::ResourceAllocation, + + // Encoding errors (Vulkan pipeline errors) + AndroidError::MutexPoisoned => ErrorCategory::Encoding, + AndroidError::GlobalStateSetFailed => ErrorCategory::Encoding, + AndroidError::Vulkan(_) => ErrorCategory::Encoding, + AndroidError::GraphicsPipelineCreationFailed(_) => ErrorCategory::Encoding, + AndroidError::FramebufferCreationFailed(_) => ErrorCategory::Encoding, + AndroidError::ImageViewCreationFailed(_) => ErrorCategory::Encoding, + AndroidError::HardwareBufferImageCreationFailed(_) => ErrorCategory::Encoding, + AndroidError::ImageMemoryBindFailed(_) => ErrorCategory::Encoding, + AndroidError::FenceWaitFailed(_) => ErrorCategory::Encoding, + AndroidError::HardwareBufferPropertiesFailed(_) => ErrorCategory::Encoding, + AndroidError::VulkanResult(_) => ErrorCategory::Encoding, + AndroidError::EncoderInputMismatch => ErrorCategory::Encoding, + + // Muxing errors + AndroidError::MuxerAlreadyStarted => ErrorCategory::Muxing, + AndroidError::MissingTrackMetadata => ErrorCategory::Muxing, + AndroidError::InvalidOutputPath => ErrorCategory::Muxing, + + // Communication errors + AndroidError::ChannelSendFailed(_) => ErrorCategory::Communication, + AndroidError::OneshotRecv(_) => ErrorCategory::Communication, + AndroidError::RenderThreadSendFailed => ErrorCategory::Communication, + AndroidError::EventIdNotReserved => ErrorCategory::Communication, + + // Invalid input errors + AndroidError::UnsupportedPlaneCount(_) => ErrorCategory::InvalidInput, + AndroidError::UnsupportedGraphicsFormat(_) => ErrorCategory::InvalidInput, + AndroidError::Utf8(_) => ErrorCategory::InvalidInput, + + // Wrapped common errors - delegate to inner + AndroidError::Common(e) => e.category(), + + // Generic fallback + AndroidError::Other(_) => ErrorCategory::General, + } + } +} + +impl From for unienc_common::CommonError { + fn from(err: AndroidError) -> Self { + unienc_common::CommonError::Categorized { + category: err.category(), + message: err.to_string(), + } + } +} + +/// Extension trait for adding context to Results +pub trait ResultExt { + fn context>(self, context: C) -> Result; +} + +impl ResultExt for std::result::Result { + fn context>(self, context: C) -> Result { + self.map_err(|e| AndroidError::Other(format!("{}: {}", context.into(), e))) + } +} + +/// Extension trait for Option types +pub trait OptionExt { + fn context>(self, context: C) -> Result; +} + +impl OptionExt for Option { + fn context>(self, context: C) -> Result { + self.ok_or_else(|| AndroidError::Other(context.into())) + } +} diff --git a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/java.rs b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/java.rs index c6be0fa..b6f5495 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/java.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/java.rs @@ -1,21 +1,20 @@ use std::sync::Arc; use jni::{objects::{GlobalRef, JObject, JString}, AttachGuard, JNIEnv, JavaVM}; -use anyhow::{Context, Result}; - +use crate::error::{AndroidError, Result}; /// Get the global JavaVM instance pub fn get_java_vm() -> Result<&'static JavaVM> { crate::JAVA_VM .get() - .ok_or_else(|| anyhow::anyhow!("JavaVM not initialized")) + .ok_or(AndroidError::JavaVmNotInitialized) } /// Attach current thread to JVM and get JNIEnv pub fn attach_current_thread() -> Result> { let vm = get_java_vm()?; vm.attach_current_thread() - .map_err(|e| anyhow::anyhow!("Failed to attach current thread to JVM: {:?}", e)) + .map_err(|e| AndroidError::JvmAttachFailed(format!("{:?}", e))) } /// Thread-safe wrapper for Java GlobalRef @@ -27,7 +26,7 @@ impl SafeGlobalRef { pub fn new(env: &JNIEnv, obj: JObject) -> Result { let global_ref = env .new_global_ref(obj) - .context("Failed to create global reference")?; + .map_err(|_| AndroidError::JniGlobalRefFailed)?; Ok(Self { inner: Arc::new(global_ref), }) @@ -54,7 +53,7 @@ pub fn check_jni_exception(env: &JNIEnv) -> Result<()> { if env.exception_check()? { env.exception_describe()?; env.exception_clear()?; - return Err(anyhow::anyhow!("JNI exception occurred")); + return Err(AndroidError::JniException); } Ok(()) } @@ -69,7 +68,7 @@ pub fn call_void_method( ) -> Result<()> { let env = unsafe { &mut env.unsafe_clone() }; env.call_method(obj, name, sig, args) - .context(format!("Failed to call method: {}", name))?; + .map_err(|_| AndroidError::JniMethodCallFailed(name.to_string()))?; check_jni_exception(env)?; Ok(()) } @@ -84,9 +83,9 @@ pub fn call_int_method( ) -> Result { let result = env .call_method(obj, name, sig, args) - .context(format!("Failed to call method: {}", name))?; + .map_err(|_| AndroidError::JniMethodCallFailed(name.to_string()))?; check_jni_exception(env)?; - result.i().context("Expected int return value") + result.i().map_err(|_| AndroidError::JniUnexpectedReturnValue { expected: "int" }) } /// Helper to call Java methods returning object @@ -99,32 +98,32 @@ pub fn call_object_method<'a>( ) -> Result> { let result = env .call_method(obj, name, sig, args) - .context(format!("Failed to call method: {}", name))?; + .map_err(|_| AndroidError::JniMethodCallFailed(name.to_string()))?; check_jni_exception(env)?; - result.l().context("Expected object return value") + result.l().map_err(|_| AndroidError::JniUnexpectedReturnValue { expected: "object" }) } /// Helper to get int field pub fn get_int_field(env: &mut JNIEnv, obj: &JObject, name: &str) -> Result { let result = env .get_field(obj, name, "I") - .context(format!("Failed to get field: {}", name))?; + .map_err(|_| AndroidError::JniFieldGetFailed(name.to_string()))?; check_jni_exception(env)?; - result.i().context("Expected int field") + result.i().map_err(|_| AndroidError::JniUnexpectedReturnValue { expected: "int" }) } /// Helper to get long field pub fn get_long_field(env: &mut JNIEnv, obj: &JObject, name: &str) -> Result { let result = env .get_field(obj, name, "J") - .context(format!("Failed to get field: {}", name))?; + .map_err(|_| AndroidError::JniFieldGetFailed(name.to_string()))?; check_jni_exception(env)?; - result.j().context("Expected long field") + result.j().map_err(|_| AndroidError::JniUnexpectedReturnValue { expected: "long" }) } /// Convert Rust string to Java string pub fn to_java_string<'a>(env: &JNIEnv<'a>, s: &str) -> Result> { - env.new_string(s).context("Failed to create Java string") + env.new_string(s).map_err(|_| AndroidError::JniStringCreationFailed) } /// Get direct buffer address, capacity and position from DirectByteBuffer @@ -138,7 +137,7 @@ pub fn get_direct_buffer_info( // Get direct buffer address (always points to the beginning of the buffer) let base_address = env.get_direct_buffer_address(byte_buffer)?; if base_address.is_null() { - return Err(anyhow::anyhow!("Buffer is not a direct buffer")); + return Err(AndroidError::NotDirectBuffer); } // Get buffer capacity diff --git a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/lib.rs b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/lib.rs index f0eea97..2da4302 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/lib.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/lib.rs @@ -1,4 +1,3 @@ -use anyhow::Result; use jni::sys::JNI_VERSION_1_6; use jni::JavaVM; use std::ffi::{c_int, c_void}; @@ -9,22 +8,26 @@ use unienc_common::{EncodingSystem, TryFromUnityNativeTexturePointer}; pub mod audio; pub mod common; pub mod config; +pub mod error; mod java; pub mod mux; pub mod video; mod vulkan; +pub use error::{AndroidError, Result}; + use audio::MediaCodecAudioEncoder; use mux::MediaMuxer; +use unienc_common::unity::UnityPlugin; use video::MediaCodecVideoEncoder; static JAVA_VM: OnceLock = OnceLock::new(); -pub unsafe fn set_java_vm(vm: *mut jni::sys::JavaVM, _reserved: *mut c_void) -> c_int { +pub unsafe fn set_java_vm(vm: *mut jni::sys::JavaVM, _reserved: *mut c_void) -> c_int { unsafe { JAVA_VM.set(JavaVM::from_raw(vm).unwrap()).unwrap(); println!("JNI_OnLoad: {:?}", vm); JNI_VERSION_1_6 -} +}} pub struct MediaCodecEncodingSystem< V: unienc_common::VideoEncoderOptions, @@ -51,16 +54,16 @@ impl Result { - MediaCodecVideoEncoder::new(&self.video_options) + fn new_video_encoder(&self) -> unienc_common::Result { + MediaCodecVideoEncoder::new(&self.video_options).map_err(Into::into) } - fn new_audio_encoder(&self) -> Result { - MediaCodecAudioEncoder::new(&self.audio_options) + fn new_audio_encoder(&self) -> unienc_common::Result { + MediaCodecAudioEncoder::new(&self.audio_options).map_err(Into::into) } - fn new_muxer(&self, output_path: &Path) -> Result { - MediaMuxer::new(output_path, &self.video_options, &self.audio_options) + fn new_muxer(&self, output_path: &Path) -> unienc_common::Result { + MediaMuxer::new(output_path, &self.video_options, &self.audio_options).map_err(Into::into) } fn is_blit_supported(&self) -> bool { @@ -70,6 +73,9 @@ impl= 29 && vulkan::is_initialized() } +} + +impl UnityPlugin for MediaCodecEncodingSystem { fn unity_plugin_load(interfaces: &unity_native_plugin::interface::UnityInterfaces) { vulkan::unity_plugin_load(interfaces); } @@ -81,11 +87,13 @@ pub struct VulkanTexture { } impl TryFromUnityNativeTexturePointer for VulkanTexture { - fn try_from_unity_native_texture_ptr(ptr: *mut c_void) -> Result { + fn try_from_unity_native_texture_ptr( + ptr: *mut c_void, + ) -> unienc_common::Result { // ptr is VkImage* let ptr = ptr as *mut ash::vk::Image; if ptr.is_null() { - return Err(anyhow::anyhow!("Null Vulkan texture pointer")); + return Err(AndroidError::NullVulkanTexture.into()); } Ok(VulkanTexture { tex: unsafe { *ptr }, diff --git a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/mux/mod.rs b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/mux/mod.rs index ef67e0e..d71637f 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/mux/mod.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/mux/mod.rs @@ -1,4 +1,3 @@ -use anyhow::Result; use jni::{objects::JValue, sys::jint, JNIEnv}; use std::{path::Path, sync::Arc}; use tokio::sync::{oneshot, RwLock}; @@ -6,7 +5,7 @@ use unienc_common::{CompletionHandle, Muxer, MuxerInput}; use crate::common::*; use crate::config::MUXER_OUTPUT_FORMAT_MPEG_4; - +use crate::error::{AndroidError, Result}; use crate::java::*; pub struct MediaMuxer { @@ -51,7 +50,7 @@ impl Muxer for MediaMuxer { fn get_inputs( self, - ) -> Result<( + ) -> unienc_common::Result<( Self::VideoInputType, Self::AudioInputType, Self::CompletionHandleType, @@ -154,16 +153,16 @@ async fn push( }; sender .send(Ok(())) - .map_err(|_| anyhow::anyhow!("failed to send start signal"))?; + .map_err(|_| AndroidError::ChannelSendFailed("start"))?; } MuxerSharedState::Started => { - return Err(anyhow::anyhow!("muxer already started")); + return Err(AndroidError::MuxerAlreadyStarted); } }; } CommonEncodedDataContent::Buffer { data, buffer_flag } => { let Some(track_index) = track_index else { - return Err(anyhow::anyhow!("track does not have metadata")); + return Err(AndroidError::MissingTrackMetadata); }; let env = &mut attach_current_thread()?; let flags = buffer_flag; @@ -178,7 +177,7 @@ async fn push( impl MuxerInput for MediaMuxerVideoInput { type Data = CommonEncodedData; - async fn push(&mut self, data: Self::Data) -> Result<()> { + async fn push(&mut self, data: Self::Data) -> unienc_common::Result<()> { push( data, self.shared_state.clone(), @@ -188,12 +187,13 @@ impl MuxerInput for MediaMuxerVideoInput { Some(self.original_height), ) .await + .map_err(Into::into) } - async fn finish(self) -> Result<()> { + async fn finish(self) -> unienc_common::Result<()> { self.finish_tx .send(Ok(())) - .map_err(|_| anyhow::anyhow!("failed to send finish signal"))?; + .map_err(|_| unienc_common::CommonError::from(AndroidError::ChannelSendFailed("finish")))?; Ok(()) } } @@ -201,7 +201,7 @@ impl MuxerInput for MediaMuxerVideoInput { impl MuxerInput for MediaMuxerAudioInput { type Data = CommonEncodedData; - async fn push(&mut self, data: Self::Data) -> Result<()> { + async fn push(&mut self, data: Self::Data) -> unienc_common::Result<()> { push( data, self.shared_state.clone(), @@ -211,33 +211,40 @@ impl MuxerInput for MediaMuxerAudioInput { None, ) .await + .map_err(Into::into) } - async fn finish(self) -> Result<()> { + async fn finish(self) -> unienc_common::Result<()> { self.finish_tx .send(Ok(())) - .map_err(|_| anyhow::anyhow!("failed to send finish signal"))?; + .map_err(|_| unienc_common::CommonError::from(AndroidError::ChannelSendFailed("finish")))?; Ok(()) } } impl CompletionHandle for MediaMuxerCompletionHandle { - async fn finish(self) -> Result<()> { - println!("waiting for all tracks to finish"); + async fn finish(self) -> unienc_common::Result<()> { + finish_completion_handle_impl(self).await.map_err(Into::into) + } +} - self.video_finish_rx.await??; - self.audio_finish_rx.await??; - // Stop and release muxer - let shared_state = self.shared_state.read().await; - let env = &mut attach_current_thread()?; - if let MuxerSharedState::Started = *shared_state { - stop_muxer(env, &self.muxer)?; - } +async fn finish_completion_handle_impl( + handle: MediaMuxerCompletionHandle, +) -> Result<()> { + println!("waiting for all tracks to finish"); + + handle.video_finish_rx.await??; + handle.audio_finish_rx.await??; + // Stop and release muxer + let shared_state = handle.shared_state.read().await; + let env = &mut attach_current_thread()?; + if let MuxerSharedState::Started = *shared_state { + stop_muxer(env, &handle.muxer)?; + } - release_muxer(env, &self.muxer)?; + release_muxer(env, &handle.muxer)?; - Ok(()) - } + Ok(()) } // Helper functions for MediaMuxer @@ -247,7 +254,7 @@ fn create_media_muxer(env: &mut JNIEnv, output_path: &Path) -> Result Result<(Self::InputType, Self::OutputType)> { + fn get(self) -> unienc_common::Result<(Self::InputType, Self::OutputType)> { Ok((self.input, self.output)) } } @@ -80,7 +80,7 @@ impl Drop for MediaCodecVideoEncoderInput { if buffer_index == media_codec_errors::INFO_TRY_AGAIN_LATER { std::thread::sleep(Duration::from_millis(10)); } else { - return Err(anyhow::anyhow!("No input buffer available")); + return Err(AndroidError::NoInputBuffer); } }, MediaCodecVideoEncoderInputProcessor::HardwareBuffer(_) => { @@ -142,191 +142,194 @@ impl MediaCodecVideoEncoder { impl EncoderInput for MediaCodecVideoEncoderInput { type Data = VideoSample; - async fn push(&mut self, data: Self::Data) -> Result<()> { - match data.frame { - VideoFrame::Bgra32(frame) => { - match &self.processor { - MediaCodecVideoEncoderInputProcessor::Uninitialized(_) => { - // setup for buffer input mode with YUV420_FLEXIBLE - let MediaCodecVideoEncoderInputProcessor::Uninitialized(state) = - std::mem::replace( - &mut self.processor, - MediaCodecVideoEncoderInputProcessor::Buffer(), - ) - else { - unreachable!(); - }; - - // Configure encoder with YUV420_FLEXIBLE format for buffer input - let env = &mut attach_current_thread()?; - let format = create_video_format_raw( - env, - self.padded_width, - self.padded_height, - state.bitrate, - state.fps_hint, - false, // use_surface = false for buffer mode - )?; - self.codec.configure(&format)?; - _ = self.codec.print_codec_info(); - - self.codec.start()?; - _ = state.tx.send(()); - } - MediaCodecVideoEncoderInputProcessor::Buffer() => {} - _ => { - return Err(anyhow::anyhow!( - "This encoder is initialized for other input" - )); - } - } - - let mut buffer_index; - loop { - let sleep; - { - // Get input buffer - buffer_index = self - .codec - .dequeue_input_buffer(Duration::from_millis(100))?; - if buffer_index == media_codec_errors::INFO_TRY_AGAIN_LATER { - sleep = true; - } else if buffer_index < 0 { - return Err(anyhow::anyhow!("No input buffer available")); - } else { - break; - } - } - if sleep { - tokio::time::sleep(Duration::from_millis(10)).await; - } - } - - let buffer = self.codec.get_input_buffer(buffer_index)?; - let env = &mut attach_current_thread()?; - let (_base_ptr, capacity, position) = get_direct_buffer_info(env, buffer.as_obj())?; - let size = capacity - position; - - let image = self.codec.get_input_image(buffer_index)?; - - // Use Image-based approach with dynamic plane layout and padding - let planes = image.get_planes()?; - crate::common::write_bgra_to_yuv_planes_with_padding( - &frame, - self.padded_width, - self.padded_height, - &planes, - )?; - - let timestamp = (data.timestamp * 1_000_000.0) as i64; - self.last_timestamp = timestamp; - - // Queue input buffer - size is determined by the Image object - self.codec.queue_input_buffer( - buffer_index, - 0, - size, - timestamp, // Convert to microseconds - 0, - )?; + async fn push(&mut self, data: Self::Data) -> unienc_common::Result<()> { + push_video_impl(self, data).await.map_err(Into::into) + } +} - Ok(()) - } - VideoFrame::BlitSource { - source, - width, - height, - graphics_format, - flip_vertically, - is_gamma_workflow, - event_issuer, - } => { - // Use HardwareBuffer mode for better compatibility with Tensor/Exynos SoCs - if let MediaCodecVideoEncoderInputProcessor::Uninitialized(_) = &self.processor { +async fn push_video_impl( + this: &mut MediaCodecVideoEncoderInput, + data: VideoSample, +) -> Result<()> { + match data.frame { + VideoFrame::Bgra32(frame) => { + match &this.processor { + MediaCodecVideoEncoderInputProcessor::Uninitialized(_) => { + // setup for buffer input mode with YUV420_FLEXIBLE let MediaCodecVideoEncoderInputProcessor::Uninitialized(state) = std::mem::replace( - &mut self.processor, - MediaCodecVideoEncoderInputProcessor::Buffer(), // temporary placeholder + &mut this.processor, + MediaCodecVideoEncoderInputProcessor::Buffer(), ) else { unreachable!(); }; - // Configure encoder with SURFACE format for hardware buffer input + // Configure encoder with YUV420_FLEXIBLE format for buffer input let env = &mut attach_current_thread()?; let format = create_video_format_raw( env, - self.padded_width, - self.padded_height, + this.padded_width, + this.padded_height, state.bitrate, state.fps_hint, - true, // use_surface = true for hardware buffer mode - )?; - self.codec.configure(&format)?; - _ = self.codec.print_codec_info(); - - // Create input surface after configure, before start - let surface = self.codec.create_input_surface()?; - let hardware_buffer_surface = HardwareBufferSurface::new( - &surface, - self.padded_width, - self.padded_height, - 3, // max_images + false, // use_surface = false for buffer mode )?; - self.codec.start()?; + this.codec.configure(&format)?; + _ = this.codec.print_codec_info(); - // Replace temporary placeholder with actual HardwareBuffer processor - self.processor = MediaCodecVideoEncoderInputProcessor::HardwareBuffer(Arc::new( - hardware_buffer_surface, - )); + this.codec.start()?; _ = state.tx.send(()); } + MediaCodecVideoEncoderInputProcessor::Buffer() => {} + _ => { + return Err(AndroidError::EncoderInputMismatch); + } + } + + let mut buffer_index; + loop { + let sleep; + { + // Get input buffer + buffer_index = this + .codec + .dequeue_input_buffer(Duration::from_millis(100))?; + if buffer_index == media_codec_errors::INFO_TRY_AGAIN_LATER { + sleep = true; + } else if buffer_index < 0 { + return Err(AndroidError::NoInputBuffer); + } else { + break; + } + } + if sleep { + tokio::time::sleep(Duration::from_millis(10)).await; + } + } - let MediaCodecVideoEncoderInputProcessor::HardwareBuffer(hb_surface) = - &self.processor + let buffer = this.codec.get_input_buffer(buffer_index)?; + let env = &mut attach_current_thread()?; + let (_base_ptr, capacity, position) = get_direct_buffer_info(env, buffer.as_obj())?; + let size = capacity - position; + + let image = this.codec.get_input_image(buffer_index)?; + + // Use Image-based approach with dynamic plane layout and padding + let planes = image.get_planes()?; + crate::common::write_bgra_to_yuv_planes_with_padding( + &frame, + this.padded_width, + this.padded_height, + &planes, + )?; + + let timestamp = (data.timestamp * 1_000_000.0) as i64; + this.last_timestamp = timestamp; + + // Queue input buffer - size is determined by the Image object + this.codec.queue_input_buffer( + buffer_index, + 0, + size, + timestamp, // Convert to microseconds + 0, + )?; + + Ok(()) + } + VideoFrame::BlitSource { + source, + width, + height, + graphics_format, + flip_vertically, + is_gamma_workflow, + event_issuer, + } => { + // Use HardwareBuffer mode for better compatibility with Tensor/Exynos SoCs + if let MediaCodecVideoEncoderInputProcessor::Uninitialized(_) = &this.processor { + let MediaCodecVideoEncoderInputProcessor::Uninitialized(state) = + std::mem::replace( + &mut this.processor, + MediaCodecVideoEncoderInputProcessor::Buffer(), // temporary placeholder + ) else { - return Err(anyhow::anyhow!( - "This encoder is initialized for other input" - )); + unreachable!(); }; - // Dequeue a frame from ImageWriter - let frame = hb_surface.dequeue_frame()?; - - let (tx, rx) = tokio::sync::oneshot::channel(); - - event_issuer.issue_graphics_event( - Box::new(move || { - let image = source.tex; - // Blit to hardware buffer and return the future - let result = crate::vulkan::blit_to_hardware_buffer( - &image, - width, - height, - graphics_format, - flip_vertically, - is_gamma_workflow, - &frame, - ); - tx.send((result, frame)) - .map_err(|_e| anyhow!("Failed to send from render thread to push")) - .unwrap(); - }), - *crate::vulkan::EVENT_ID - .get() - .context("Event ID is not reserved")?, - ); - - let (blit_result, frame) = rx.await?; - let future = blit_result?; - future.await?; - - // Queue the frame to MediaCodec - hb_surface - .queue_frame(frame, (data.timestamp * 1000.0 * 1000.0 * 1000.0) as i64)?; - - Ok(()) + // Configure encoder with SURFACE format for hardware buffer input + let env = &mut attach_current_thread()?; + let format = create_video_format_raw( + env, + this.padded_width, + this.padded_height, + state.bitrate, + state.fps_hint, + true, // use_surface = true for hardware buffer mode + )?; + this.codec.configure(&format)?; + _ = this.codec.print_codec_info(); + + // Create input surface after configure, before start + let surface = this.codec.create_input_surface()?; + let hardware_buffer_surface = HardwareBufferSurface::new( + &surface, + this.padded_width, + this.padded_height, + 3, // max_images + )?; + this.codec.start()?; + + // Replace temporary placeholder with actual HardwareBuffer processor + this.processor = MediaCodecVideoEncoderInputProcessor::HardwareBuffer(Arc::new( + hardware_buffer_surface, + )); + _ = state.tx.send(()); } + + let MediaCodecVideoEncoderInputProcessor::HardwareBuffer(hb_surface) = + &this.processor + else { + return Err(AndroidError::EncoderInputMismatch); + }; + + // Dequeue a frame from ImageWriter + let frame = hb_surface.dequeue_frame()?; + + let (tx, rx) = tokio::sync::oneshot::channel(); + + event_issuer.issue_graphics_event( + Box::new(move || { + let image = source.tex; + // Blit to hardware buffer and return the future + let result = crate::vulkan::blit_to_hardware_buffer( + &image, + width, + height, + graphics_format, + flip_vertically, + is_gamma_workflow, + &frame, + ); + tx.send((result, frame)) + .map_err(|_| AndroidError::RenderThreadSendFailed) + .unwrap(); + }), + *crate::vulkan::EVENT_ID + .get() + .context("Event ID is not reserved")?, + ); + + let (blit_result, frame) = rx.await?; + let future = blit_result?; + future.await?; + + // Queue the frame to MediaCodec + hb_surface + .queue_frame(frame, (data.timestamp * 1000.0 * 1000.0 * 1000.0) as i64)?; + + Ok(()) } } } @@ -334,14 +337,20 @@ impl EncoderInput for MediaCodecVideoEncoderInput { impl EncoderOutput for MediaCodecVideoEncoderOutput { type Data = CommonEncodedData; - async fn pull(&mut self) -> Result> { - if let Some(rx) = &mut self.initialization { - rx.await?; - self.initialization = None; - } + async fn pull(&mut self) -> unienc_common::Result> { + pull_video_output_impl(self).await.map_err(Into::into) + } +} - pull_encoded_data_with_codec(&self.codec, &mut self.end_of_stream).await +async fn pull_video_output_impl( + this: &mut MediaCodecVideoEncoderOutput, +) -> Result> { + if let Some(rx) = &mut this.initialization { + rx.await?; + this.initialization = None; } + + pull_encoded_data_with_codec(&this.codec, &mut this.end_of_stream).await } // Helper functions for JNI MediaCodec calls diff --git a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/hardware_buffer.rs b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/hardware_buffer.rs index ebfaed2..9d97758 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/hardware_buffer.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/hardware_buffer.rs @@ -1,5 +1,5 @@ +use crate::error::{AndroidError, Result}; use crate::vulkan::types::{VulkanImageHandle, VulkanImageViewHandle, VulkanMemoryHandle}; -use anyhow::{anyhow, Result}; use ash::vk; use std::sync::Arc; @@ -49,7 +49,7 @@ impl HardwareBufferImage { &mut ahb_properties, ) } - .map_err(|e| anyhow!("Failed to get AHardwareBuffer properties: {:?}", e))?; + .map_err(AndroidError::HardwareBufferPropertiesFailed)?; // Extract values after the mutable borrow ends let allocation_size = ahb_properties.allocation_size; @@ -90,7 +90,7 @@ impl HardwareBufferImage { let image = VulkanImageHandle::new( unsafe { device.create_image(&image_create_info, None) } - .map_err(|e| anyhow!("Failed to create image from AHardwareBuffer: {:?}", e))?, + .map_err(AndroidError::HardwareBufferImageCreationFailed)?, device.clone(), ); @@ -111,13 +111,13 @@ impl HardwareBufferImage { let memory = VulkanMemoryHandle::new( unsafe { device.allocate_memory(&alloc_info, None) } - .map_err(|e| anyhow!("Failed to allocate memory for AHardwareBuffer: {:?}", e))?, + .map_err(AndroidError::HardwareBufferMemoryAllocationFailed)?, device.clone(), ); // Bind memory to image unsafe { device.bind_image_memory(*image, *memory, 0) } - .map_err(|e| anyhow!("Failed to bind image memory: {:?}", e))?; + .map_err(AndroidError::ImageMemoryBindFailed)?; // Create image view let view_create_info = vk::ImageViewCreateInfo::default() @@ -143,14 +143,12 @@ impl HardwareBufferImage { if format == vk::Format::UNDEFINED { // External format requires YCbCr conversion, which is more complex // For VIDEO_ENCODE usage with RGBA_8888, we shouldn't hit this path - return Err(anyhow!( - "External format (YUV) not yet supported, expected RGBA format" - )); + return Err(AndroidError::UnsupportedGraphicsFormat(0)); } let view = VulkanImageViewHandle::new( unsafe { device.create_image_view(&view_create_info, None) } - .map_err(|e| anyhow!("Failed to create image view: {:?}", e))?, + .map_err(AndroidError::ImageViewCreationFailed)?, device.clone(), ); @@ -190,5 +188,5 @@ fn find_memory_type_index( return Ok(i); } } - Err(anyhow!("No suitable memory type found")) + Err(AndroidError::NoSuitableMemoryType) } diff --git a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/hardware_buffer_surface.rs b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/hardware_buffer_surface.rs index 7dc8776..218c1c0 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/hardware_buffer_surface.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/hardware_buffer_surface.rs @@ -1,9 +1,9 @@ use crate::common::{ImageWriter, ImageWriterImage}; +use crate::error::{AndroidError, Result}; use crate::java::SafeGlobalRef; use crate::vulkan::hardware_buffer::HardwareBufferImage; use crate::vulkan::types::VulkanFramebufferHandle; use crate::vulkan::CONTEXT; -use anyhow::{anyhow, Result}; use ash::vk; /// A surface backed by ImageWriter and HardwareBuffer @@ -43,9 +43,9 @@ impl HardwareBufferSurface { // Get the Vulkan context let cx = CONTEXT .get() - .ok_or_else(|| anyhow!("Vulkan context not initialized"))? + .ok_or(AndroidError::ContextNotInitialized)? .lock() - .map_err(|_| anyhow!("Failed to lock Vulkan context"))?; + .map_err(|_| AndroidError::MutexPoisoned)?; // Import the hardware buffer as a Vulkan image let vk_image = HardwareBufferImage::from_hardware_buffer( @@ -67,7 +67,7 @@ impl HardwareBufferSurface { None, ) } - .map_err(|e| anyhow!("Failed to create framebuffer: {:?}", e))?, + .map_err(AndroidError::FramebufferCreationFailed)?, cx.device.clone(), ); diff --git a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/mod.rs b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/mod.rs index 25b6840..dfdde92 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/mod.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/mod.rs @@ -6,7 +6,6 @@ pub mod types; mod utils; mod format; -use anyhow::{anyhow, Context, Result}; use ash::vk; use std::fmt::Debug; use std::future::Future; @@ -16,6 +15,7 @@ use std::{ os::raw::c_void, sync::{Mutex, OnceLock}, }; +use crate::error::{AndroidError, Result, ResultExt}; use unity_native_plugin::graphics::{GfxDeviceEventType, UnityGraphics}; use unity_native_plugin::profiler::{ BuiltinProfilerCategory, ProfilerCategoryId, ProfilerMarkerDesc, ProfilerMarkerEventType, @@ -128,7 +128,7 @@ pub(crate) fn unity_plugin_load(interfaces: &unity_native_plugin::interface::Uni GRAPHICS .set(Mutex::new(graphics)) - .map_err(|_e| anyhow!("Failed to set graphics")) + .map_err(|_| AndroidError::GlobalStateSetFailed) .unwrap(); graphics.register_device_event_callback(Some(on_device_event)); @@ -203,7 +203,7 @@ extern "system" fn on_device_event(ev_type: GfxDeviceEventType) { render_pass: Arc::new(render_pass), fence_pool: Arc::new(FencePool::new(device)), })) - .map_err(|_e| anyhow!("Failed to set metal")) + .map_err(|_| AndroidError::GlobalStateSetFailed) .unwrap(); } GfxDeviceEventType::Shutdown => {} @@ -220,12 +220,12 @@ pub fn blit_to_hardware_buffer( flip_vertically: bool, is_gamma_workflow: bool, frame: &hardware_buffer_surface::HardwareBufferFrame, -) -> Result>> { +) -> Result> + use<>> { let cx = crate::vulkan::CONTEXT .get() - .context("Failed to get context")? + .ok_or(AndroidError::ContextNotInitialized)? .lock() - .map_err(|_| anyhow!("Failed to get context"))?; + .map_err(|_| AndroidError::MutexPoisoned)?; preprocess::blit_to_hardware_buffer(&cx, src, src_width, src_height, src_graphics_format, flip_vertically, is_gamma_workflow, frame) } diff --git a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/preprocess/mod.rs b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/preprocess/mod.rs index 8872b05..b8532c7 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/preprocess/mod.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/preprocess/mod.rs @@ -1,3 +1,4 @@ +use crate::error::{AndroidError, Result, ResultExt}; use crate::vulkan::format::GRAPHICS_FORMAT_TO_VULKAN; use crate::vulkan::hardware_buffer_surface::HardwareBufferFrame; use crate::vulkan::types::{ @@ -8,7 +9,6 @@ use crate::vulkan::types::{ }; use crate::vulkan::utils::{create_shader_module, FenceGuard}; use crate::vulkan::{GlobalContext, ProfilerMarkerDescExt, MARKERS}; -use anyhow::{anyhow, Context, Result}; use ash::vk; use std::future::Future; use std::sync::{Arc, Mutex}; @@ -80,7 +80,7 @@ struct VertPushConstants { pub fn create_pass( device: Arc, queue_family_index: u32, -) -> anyhow::Result { +) -> Result { // create render pass let render_pass = unsafe { device.create_render_pass( @@ -228,7 +228,7 @@ pub fn create_pass( for pipeline in pipelines { unsafe { device.destroy_pipeline(pipeline, None) }; } - return Err(anyhow!("Failed to create graphics pipeline: {:?}", result)); + return Err(AndroidError::GraphicsPipelineCreationFailed(result)); } }; @@ -333,7 +333,7 @@ pub fn blit_to_hardware_buffer( flip_vertically: bool, is_gamma_workflow: bool, frame: &HardwareBufferFrame, -) -> Result>> { +) -> Result> + use<>> { let markers = MARKERS.get(); let _guard = markers.map(|m| m.preprocess_blit.get()); let vulkan = &cx.vulkan; @@ -341,7 +341,7 @@ pub fn blit_to_hardware_buffer( let pass = &cx.render_pass; let Some(desc_set) = pass.desc_sets.pop() else { - return Err(anyhow!("No available descriptor sets in preprocess blit")); + return Err(AndroidError::NoAvailableDescriptorSets); }; let (src_view, queue, mut command_buffers, fence) = { @@ -353,7 +353,7 @@ pub fn blit_to_hardware_buffer( .copied() .flatten() .next() - .context(format!("Unsupported graphics format: {}", src_graphics_format))?; + .ok_or(AndroidError::UnsupportedGraphicsFormat(src_graphics_format))?; // A format of AHardwareBuffer doesn't seem to be mapped to SRGB formats directly while MediaCodec accepts sRGB pixels. // (mapping table: https://docs.vulkan.org/spec/latest/chapters/memory.html#memory-external-android-hardware-buffer-formats) @@ -620,7 +620,7 @@ pub fn blit_to_hardware_buffer( Ok(async move { join_handle .await - .map_err(|e| anyhow!("Failed to wait for fence: {:?}", e))?; + .context("Failed to wait for fence")?; Ok(()) }) } diff --git a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/utils.rs b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/utils.rs index 2e8162b..45bf241 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/utils.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_android_mc/src/vulkan/utils.rs @@ -1,17 +1,18 @@ +use crate::error::{AndroidError, Result}; use crate::vulkan::types::{VulkanFenceHandle, VulkanShaderModuleHandle}; -use anyhow::anyhow; use std::collections::VecDeque; use std::sync::{Arc, Mutex}; pub fn create_shader_module( device: &Arc, code: &'static [u8], -) -> anyhow::Result { +) -> Result { let mut info = ash::vk::ShaderModuleCreateInfo::default(); info.code_size = code.len(); info.p_code = code.as_ptr() as *const u32; Ok(VulkanShaderModuleHandle::new( - unsafe { device.create_shader_module(&info, None) }?, + unsafe { device.create_shader_module(&info, None) } + .map_err(AndroidError::Vulkan)?, device.clone(), )) } @@ -50,8 +51,8 @@ impl FencePool { } impl FencePool { - pub fn pop(self: &Arc) -> anyhow::Result { - let mut pool = self.pool.lock().map_err(|e| anyhow!(e.to_string()))?; + pub fn pop(self: &Arc) -> Result { + let mut pool = self.pool.lock().map_err(|_| AndroidError::MutexPoisoned)?; if let Some(fence) = pool.pop_front() { Ok(FenceGuard { fence: Some(fence), @@ -62,7 +63,8 @@ impl FencePool { let fence_info = ash::vk::FenceCreateInfo::default(); Ok(FenceGuard { fence: VulkanFenceHandle::new( - unsafe { self.device.create_fence(&fence_info, None) }?, + unsafe { self.device.create_fence(&fence_info, None) } + .map_err(AndroidError::Vulkan)?, self.device.clone(), ) .into(), @@ -71,9 +73,9 @@ impl FencePool { } } - pub fn push(&self, fence: VulkanFenceHandle) -> anyhow::Result<()> { - let mut pool = self.pool.lock().map_err(|e| anyhow!(e.to_string()))?; - unsafe { self.device.reset_fences(&[*fence])? }; + pub fn push(&self, fence: VulkanFenceHandle) -> Result<()> { + let mut pool = self.pool.lock().map_err(|_| AndroidError::MutexPoisoned)?; + unsafe { self.device.reset_fences(&[*fence]).map_err(AndroidError::Vulkan)? }; pool.push_back(fence); Ok(()) } diff --git a/InstantReplay.Externals/unienc/crates/unienc_apple_vt/Cargo.toml b/InstantReplay.Externals/unienc/crates/unienc_apple_vt/Cargo.toml index 3805822..e09f895 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_apple_vt/Cargo.toml +++ b/InstantReplay.Externals/unienc/crates/unienc_apple_vt/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "unienc_apple_vt" -version = "0.1.0" +version.workspace = true edition.workspace = true license.workspace = true authors.workspace = true [dependencies] -anyhow = "1.0.98" +thiserror = { workspace = true } bincode = { workspace = true } bitflags = "2.9.1" block2 = "0.6.1" @@ -23,5 +23,5 @@ objc2-foundation = "0.3.1" objc2-metal = "0.3.2" objc2-video-toolbox = "0.3.1" tokio = { version = "1.45.1", features = ["sync"] } -unienc_common = { workspace = true } -unity-native-plugin = { version = "0.8.0", features = ["metal_objc2"] } +unienc_common = { workspace = true, features = ["unity"] } +unity-native-plugin = { workspace = true, features = ["metal_objc2"] } diff --git a/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/audio/mod.rs b/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/audio/mod.rs index fa0ff0c..ae8fdc4 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/audio/mod.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/audio/mod.rs @@ -1,6 +1,6 @@ use std::{ffi::c_void, ptr::NonNull}; -use anyhow::Result; +use crate::error::{AppleError, Result, OsStatusExt}; use bincode::{Decode, Encode}; use objc2_audio_toolbox::{ kAudioConverterCompressionMagicCookie, kAudioConverterEncodeBitRate, @@ -18,7 +18,6 @@ use unienc_common::{ AudioSample, EncodedData, Encoder, EncoderInput, EncoderOutput, UniencSampleKind, }; -use crate::OsStatus; pub struct AudioToolboxEncoder { input: AudioToolboxEncoderInput, @@ -52,7 +51,7 @@ impl Encoder for AudioToolboxEncoder { type OutputType = AudioToolboxEncoderOutput; - fn get(self) -> anyhow::Result<(Self::InputType, Self::OutputType)> { + fn get(self) -> unienc_common::Result<(Self::InputType, Self::OutputType)> { Ok((self.input, self.output)) } } @@ -60,7 +59,7 @@ impl Encoder for AudioToolboxEncoder { impl EncoderInput for AudioToolboxEncoderInput { type Data = AudioSample; - async fn push(&mut self, data: Self::Data) -> anyhow::Result<()> { + async fn push(&mut self, data: Self::Data) -> unienc_common::Result<()> { let mut output_buffer_data = vec![0; self.max_output_packet_size as usize]; let max_output_packets = 1; @@ -92,7 +91,7 @@ impl EncoderInput for AudioToolboxEncoderInput { sample_rate: self.sample_rate, magic_cookie: magic_cookie.clone(), }; - self.tx.send(packet).await?; + self.tx.send(packet).await.map_err(AppleError::from)?; } sample.is_some() @@ -105,7 +104,7 @@ impl EncoderInput for AudioToolboxEncoderInput { } impl AudioToolboxEncoder { - pub fn new(options: &impl unienc_common::AudioEncoderOptions) -> anyhow::Result { + pub fn new(options: &impl unienc_common::AudioEncoderOptions) -> Result { let mut from = AudioStreamBasicDescription { mSampleRate: options.sample_rate() as f64, mFormatID: kAudioFormatLinearPCM, @@ -158,7 +157,7 @@ impl AudioToolboxEncoder { impl EncoderOutput for AudioToolboxEncoderOutput { type Data = AudioPacket; - async fn pull(&mut self) -> Result> { + async fn pull(&mut self) -> unienc_common::Result> { Ok(self.rx.recv().await) } } diff --git a/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/error.rs b/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/error.rs new file mode 100644 index 0000000..e918660 --- /dev/null +++ b/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/error.rs @@ -0,0 +1,194 @@ +use objc2::rc::Retained; +use objc2_foundation::NSError; +use thiserror::Error; +use unienc_common::{CategorizedError, ErrorCategory}; + +/// Error type for unienc_apple_vt +#[derive(Error, Debug)] +pub enum AppleError { + // OSStatus errors + #[error("OSStatus error: {0}")] + OsStatus(i32), + + // Metal related errors + #[error("Metal context is not initialized")] + MetalNotInitialized, + + #[error("Failed to retain MTLTexture from raw pointer")] + MetalTextureRetainFailed, + + #[error("Failed to set global state")] + GlobalStateSetFailed, + + #[error("Failed to create CVMetalTextureCache")] + MetalTextureCacheCreationFailed, + + #[error("CVMetalTextureCache is null")] + MetalTextureCacheNull, + + #[error("Failed to get current command buffer")] + CommandBufferNotAvailable, + + #[error("Failed to create render command encoder")] + RenderCommandEncoderCreationFailed, + + #[error("Failed to create sampler state")] + SamplerStateCreationFailed, + + #[error("Failed to create vertex uniforms buffer")] + VertexUniformsBufferCreationFailed, + + // VideoToolbox related errors + #[error("VTCompressionSession is null")] + CompressionSessionNull, + + #[error("Failed to create NonNull pointer")] + NonNullCreationFailed, + + #[error("CVPixelBuffer is null")] + PixelBufferNull, + + #[error("Failed to send blit future")] + BlitFutureSendFailed, + + #[error("Event ID is not reserved")] + EventIdNotReserved, + + // AudioToolbox related errors + #[error("Failed to create audio converter")] + AudioConverterCreationFailed, + + // Muxer related errors + #[error("Failed to start writing: {0}")] + AssetWriterStartFailed(String), + + #[error("Failed to start writing")] + AssetWriterStartFailedUnknown, + + // CVPixelBuffer/CVMetalTexture related errors + #[error("CVMetalTexture is null")] + MetalTextureNull, + + #[error("Failed to get MTLTexture from CVMetalTexture")] + MetalTextureGetFailed, + + // Channel related errors + #[error("Failed to send to channel")] + ChannelSendFailed, + + // External error conversions + #[error(transparent)] + Common(#[from] unienc_common::CommonError), + + #[error(transparent)] + OneshotRecv(#[from] tokio::sync::oneshot::error::RecvError), + + // Generic errors + #[error("{0}")] + Other(String), +} + +impl From> for AppleError { + fn from(_: tokio::sync::mpsc::error::SendError) -> Self { + AppleError::ChannelSendFailed + } +} + +impl From> for AppleError { + fn from(err: Retained) -> Self { + AppleError::Other(err.to_string()) + } +} + +/// Result type alias for unienc_apple_vt +pub type Result = std::result::Result; + +impl CategorizedError for AppleError { + fn category(&self) -> ErrorCategory { + match self { + // Platform errors (OSStatus) + AppleError::OsStatus(_) => ErrorCategory::Platform, + + // Initialization errors + AppleError::MetalNotInitialized => ErrorCategory::Initialization, + AppleError::GlobalStateSetFailed => ErrorCategory::Initialization, + AppleError::MetalTextureCacheCreationFailed => ErrorCategory::Initialization, + AppleError::AudioConverterCreationFailed => ErrorCategory::Initialization, + + // Resource allocation errors + AppleError::MetalTextureRetainFailed => ErrorCategory::ResourceAllocation, + AppleError::MetalTextureCacheNull => ErrorCategory::ResourceAllocation, + AppleError::CommandBufferNotAvailable => ErrorCategory::ResourceAllocation, + AppleError::RenderCommandEncoderCreationFailed => ErrorCategory::ResourceAllocation, + AppleError::SamplerStateCreationFailed => ErrorCategory::ResourceAllocation, + AppleError::VertexUniformsBufferCreationFailed => ErrorCategory::ResourceAllocation, + AppleError::CompressionSessionNull => ErrorCategory::ResourceAllocation, + AppleError::NonNullCreationFailed => ErrorCategory::ResourceAllocation, + AppleError::PixelBufferNull => ErrorCategory::ResourceAllocation, + AppleError::MetalTextureNull => ErrorCategory::ResourceAllocation, + AppleError::MetalTextureGetFailed => ErrorCategory::ResourceAllocation, + + // Communication errors + AppleError::BlitFutureSendFailed => ErrorCategory::Communication, + AppleError::EventIdNotReserved => ErrorCategory::Communication, + AppleError::ChannelSendFailed => ErrorCategory::Communication, + AppleError::OneshotRecv(_) => ErrorCategory::Communication, + + // Muxing errors + AppleError::AssetWriterStartFailed(_) => ErrorCategory::Muxing, + AppleError::AssetWriterStartFailedUnknown => ErrorCategory::Muxing, + + // Wrapped common errors - delegate to inner + AppleError::Common(e) => e.category(), + + // Generic fallback + AppleError::Other(_) => ErrorCategory::General, + } + } +} + +impl From for unienc_common::CommonError { + fn from(err: AppleError) -> Self { + unienc_common::CommonError::Categorized { + category: err.category(), + message: err.to_string(), + } + } +} + +/// Extension trait for adding context to Results +pub trait ResultExt { + fn context>(self, context: C) -> Result; +} + +impl ResultExt for std::result::Result { + fn context>(self, context: C) -> Result { + self.map_err(|e| AppleError::Other(format!("{}: {}", context.into(), e))) + } +} + +/// Extension trait for Option types +pub trait OptionExt { + fn context>(self, context: C) -> Result; +} + +impl OptionExt for Option { + fn context>(self, context: C) -> Result { + self.ok_or_else(|| AppleError::Other(context.into())) + } +} + +/// Trait for converting OSStatus to Result +pub trait OsStatusExt { + fn to_result(&self) -> Result<()>; +} + +impl OsStatusExt for i32 { + fn to_result(&self) -> Result<()> { + if *self == 0 { + Ok(()) + } else { + Err(AppleError::OsStatus(*self)) + } + } +} diff --git a/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/lib.rs b/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/lib.rs index 456ce1e..efb58b6 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/lib.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/lib.rs @@ -12,14 +12,15 @@ use crate::{ audio::AudioToolboxEncoder, common::UnsafeSendRetained, mux::AVFMuxer, video::VideoToolboxEncoder, }; -use anyhow::Result; - pub mod audio; mod common; +pub mod error; mod metal; pub mod mux; pub mod video; +pub use error::{AppleError, OsStatusExt, Result}; + pub struct VideoToolboxEncodingSystem< V: unienc_common::VideoEncoderOptions, A: unienc_common::AudioEncoderOptions, @@ -49,22 +50,24 @@ impl Result { - VideoToolboxEncoder::new(&self.video_options) + fn new_video_encoder(&self) -> unienc_common::Result { + VideoToolboxEncoder::new(&self.video_options).map_err(|e| e.into()) } - fn new_audio_encoder(&self) -> Result { - AudioToolboxEncoder::new(&self.audio_options) + fn new_audio_encoder(&self) -> unienc_common::Result { + AudioToolboxEncoder::new(&self.audio_options).map_err(|e| e.into()) } - fn new_muxer(&self, output_path: &Path) -> Result { - AVFMuxer::new(output_path, &self.video_options, &self.audio_options) + fn new_muxer(&self, output_path: &Path) -> unienc_common::Result { + AVFMuxer::new(output_path, &self.video_options, &self.audio_options).map_err(|e| e.into()) } fn is_blit_supported(&self) -> bool { metal::is_initialized() } +} +impl unienc_common::unity::UnityPlugin for VideoToolboxEncodingSystem { fn unity_plugin_load(interfaces: &unity_native_plugin::interface::UnityInterfaces) { metal::unity_plugin_load(interfaces); } @@ -76,28 +79,14 @@ pub struct MetalTexture { } impl TryFromUnityNativeTexturePointer for MetalTexture { - fn try_from_unity_native_texture_ptr(ptr: *mut c_void) -> Result { + fn try_from_unity_native_texture_ptr(ptr: *mut c_void) -> unienc_common::Result { metal::is_initialized() .then_some(()) - .ok_or_else(|| anyhow::anyhow!("Metal context is not initialized"))?; + .ok_or(AppleError::MetalNotInitialized)?; let retained = unsafe { Retained::>::retain(ptr as *mut _) } - .ok_or_else(|| anyhow::anyhow!("Failed to retain MTLTexture from raw pointer"))?; + .ok_or(AppleError::MetalTextureRetainFailed)?; Ok(MetalTexture { texture: UnsafeSendRetained { inner: retained }, }) } } - -pub(crate) trait OsStatus { - fn to_result(&self) -> Result<()>; -} - -impl OsStatus for i32 { - fn to_result(&self) -> Result<()> { - if *self == 0 { - Ok(()) - } else { - Err(anyhow::anyhow!("OSStatus: {}", *self)) - } - } -} diff --git a/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/metal/mod.rs b/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/metal/mod.rs index 2b02137..991eaf2 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/metal/mod.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/metal/mod.rs @@ -25,9 +25,9 @@ use unity_native_plugin::{ metal::objc2::{UnityGraphicsMetalV1, UnityGraphicsMetalV1Interface}, }; -use anyhow::{anyhow, Context, Result}; +use crate::error::{AppleError, Result, OsStatusExt}; -use crate::{common::UnsafeSendRetained, OsStatus}; +use crate::common::UnsafeSendRetained; static GRAPHICS: OnceLock> = OnceLock::new(); static CONTEXT: OnceLock> = OnceLock::new(); @@ -52,7 +52,7 @@ pub(crate) fn unity_plugin_load(interfaces: &unity_native_plugin::interface::Uni GRAPHICS .set(Mutex::new(graphics)) - .map_err(|_e| anyhow!("Failed to set graphics")) + .map_err(|_e| AppleError::GlobalStateSetFailed) .unwrap(); graphics.register_device_event_callback(Some(on_device_event)); @@ -240,7 +240,7 @@ fragment FShaderOutput fragment_main(VertexOut in [[stage_in]], vertices: vertices.into(), indices: indices.into(), })) - .map_err(|_e| anyhow!("Failed to set metal")) + .map_err(|_e| AppleError::GlobalStateSetFailed) .unwrap(); } } @@ -256,12 +256,12 @@ pub(crate) fn custom_blit( dst_height: u32, flip_vertically: bool, is_gamma_workflow: bool, -) -> Result> + Send> { +) -> Result> + Send + use<>> { let context = CONTEXT .get() - .context("Context is not initialized")? + .ok_or(AppleError::MetalNotInitialized)? .lock() - .map_err(|e| anyhow!(e.to_string()))?; + .map_err(|e| AppleError::Other(e.to_string()))?; let metal = context.metal; let device = &context.device; @@ -272,13 +272,13 @@ pub(crate) fn custom_blit( None, device, None, - NonNull::new(&mut cache).context("Failed to get NonNull for CVMetalTextureCache")?, + NonNull::new(&mut cache).ok_or(AppleError::MetalTextureCacheCreationFailed)?, ) .to_result()? }; let cache = unsafe { - Retained::from_raw(cache).context("Failed to create Retained for CVMetalTextureCache")? + Retained::from_raw(cache).ok_or(AppleError::MetalTextureCacheNull)? }; let width = dst_width; @@ -288,7 +288,7 @@ pub(crate) fn custom_blit( let command_buffer = metal .current_command_buffer() - .context("Failed to get current command buffer")?; + .ok_or(AppleError::CommandBufferNotAvailable)?; metal.end_current_command_encoder(); let color_attachment_desc = MTLRenderPassColorAttachmentDescriptor::new(); @@ -305,7 +305,7 @@ pub(crate) fn custom_blit( let encoder = command_buffer .renderCommandEncoderWithDescriptor(&render_pass_descriptor) - .context("Failed to create render command encoder")?; + .ok_or(AppleError::RenderCommandEncoderCreationFailed)?; let sampler_desc = MTLSamplerDescriptor::new(); sampler_desc.setSAddressMode(MTLSamplerAddressMode::ClampToEdge); @@ -316,7 +316,7 @@ pub(crate) fn custom_blit( let sampler_state = device .newSamplerStateWithDescriptor(&sampler_desc) - .context("Failed to create sampler state")?; + .ok_or(AppleError::SamplerStateCreationFailed)?; if is_gamma_workflow { encoder.setRenderPipelineState(&context.pipeline_state); @@ -350,12 +350,12 @@ pub(crate) fn custom_blit( let vert_uniforms = unsafe { device.newBufferWithBytes_length_options( NonNull::new(&mut vert_uniforms as *mut VertexUniforms as *mut _) - .context("Failed to create NonNull for vertex uniforms")?, + .ok_or(AppleError::NonNullCreationFailed)?, std::mem::size_of::(), MTLResourceOptions::CPUCacheModeWriteCombined, ) } - .context("Failed to create vertex uniforms buffer")?; + .ok_or(AppleError::VertexUniformsBufferCreationFailed)?; unsafe { encoder.setVertexBuffer_offset_atIndex(Some(&vert_uniforms), 0, 1) }; @@ -401,7 +401,7 @@ pub(crate) fn custom_blit( let block_ptr = RcBlock::into_raw(block); unsafe { command_buffer.addCompletedHandler(block_ptr) }; - Ok(async move { rx.await.map_err(|e| anyhow!(e)) }) + Ok(async move { rx.await.map_err(AppleError::from) }) } #[derive(Debug)] @@ -434,12 +434,12 @@ impl SharedTexture { height, pixel_format, Some(pixel_buffer_attrs.as_opaque()), - NonNull::new(&mut buffer).context("Failed to create CVPixelBuffer")?, + NonNull::new(&mut buffer).ok_or(AppleError::NonNullCreationFailed)?, ) } .to_result()?; - let buffer = unsafe { Retained::from_raw(buffer) }.context("CVPixelBuffer is null")?; + let buffer = unsafe { Retained::from_raw(buffer) }.ok_or(AppleError::PixelBufferNull)?; let mut texture: *mut CVMetalTexture = std::ptr::null_mut(); unsafe { @@ -452,13 +452,12 @@ impl SharedTexture { width, height, 0, - NonNull::new(&mut texture).context("Failed to create CVMetalTexture")?, + NonNull::new(&mut texture).ok_or(AppleError::NonNullCreationFailed)?, ) } - .to_result() - .context("Failed to create CVMetalTexture")?; + .to_result()?; let texture = unsafe { Retained::from_raw(texture) } - .context("Failed to get MTLTexture from CVMetalTexture")?; + .ok_or(AppleError::MetalTextureGetFailed)?; Ok(Self { inner: Arc::new(Mutex::new(SharedTextureInner { diff --git a/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/mux/mod.rs b/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/mux/mod.rs index 31277b5..be85267 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/mux/mod.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/mux/mod.rs @@ -4,7 +4,7 @@ use std::fs; use std::sync::Mutex; use std::{path::Path, ptr::NonNull}; -use anyhow::{anyhow, Result}; +use crate::error::{AppleError, Result, OsStatusExt}; use block2::RcBlock; use dispatch2::DispatchQueue; use objc2::rc::Retained; @@ -23,10 +23,9 @@ use objc2_core_media::{ }; use objc2_foundation::{NSString, NSURL}; use tokio::sync::{mpsc, oneshot}; -use unienc_common::{CompletionHandle, Muxer, MuxerInput}; +use unienc_common::{CommonError, CompletionHandle, Muxer, MuxerInput, ResultExt}; use crate::common::UnsafeSendRetained; -use crate::OsStatus; use crate::{audio::AudioPacket, video::VideoEncodedData}; pub struct AVFMuxer { @@ -50,17 +49,17 @@ pub struct AVFMuxerAudioInput { impl MuxerInput for AVFMuxerVideoInput { type Data = VideoEncodedData; - async fn push(&mut self, data: Self::Data) -> Result<()> { - self.tx.send(Mutex::new(data.sample_buffer)).await?; + async fn push(&mut self, data: Self::Data) -> unienc_common::Result<()> { + self.tx.send(Mutex::new(data.sample_buffer)).await.map_err(AppleError::from)?; Ok(()) } - async fn finish(self) -> Result<()> { + async fn finish(self) -> unienc_common::Result<()> { drop(self.tx); match self.finish_rx.await { - Ok(inner) => inner, - Err(inner) => Err(inner.into()), + Ok(inner) => inner.map_err(|e| e.into()), + Err(inner) => Err(AppleError::from(inner).into()), } } } @@ -68,20 +67,20 @@ impl MuxerInput for AVFMuxerVideoInput { impl MuxerInput for AVFMuxerAudioInput { type Data = AudioPacket; - async fn push(&mut self, data: Self::Data) -> Result<()> { + async fn push(&mut self, data: Self::Data) -> unienc_common::Result<()> { let sample_buffer = create_audio_sample_buffer(&data, &mut self.asbd, !self.magic_cookie_applied)?; self.magic_cookie_applied = true; - self.tx.send(Mutex::new(sample_buffer.into())).await?; + self.tx.send(Mutex::new(sample_buffer.into())).await.map_err(AppleError::from)?; Ok(()) } - async fn finish(self) -> Result<()> { + async fn finish(self) -> unienc_common::Result<()> { drop(self.tx); match self.finish_rx.await { - Ok(inner) => inner, - Err(inner) => Err(inner.into()), + Ok(inner) => inner.map_err(|e| e.into()), + Err(inner) => Err(AppleError::from(inner).into()), } } } @@ -93,7 +92,7 @@ pub struct AVFMuxerCompletionHandle { } impl CompletionHandle for AVFMuxerCompletionHandle { - async fn finish(self) -> Result<()> { + async fn finish(self) -> unienc_common::Result<()> { let writer = self.writer; let writer1 = writer.clone(); @@ -104,7 +103,7 @@ impl CompletionHandle for AVFMuxerCompletionHandle { if let Some(tx) = tx.borrow_mut().take() { if let Some(err) = writer1.error() { println!("Failed to finish writing: {}", err); - tx.send(Err(anyhow!(err.to_string()))).unwrap(); + tx.send(Err(CommonError::Other(err.to_string()))).unwrap(); } else { tx.send(Ok(())).unwrap(); } @@ -112,7 +111,7 @@ impl CompletionHandle for AVFMuxerCompletionHandle { })); } - rx.await? + rx.await.context("failed to finish writing")? } } @@ -123,7 +122,7 @@ impl Muxer for AVFMuxer { fn get_inputs( self, - ) -> Result<( + ) -> unienc_common::Result<( Self::VideoInputType, Self::AudioInputType, Self::CompletionHandleType, @@ -214,12 +213,11 @@ impl AVFMuxer { } if !unsafe { writer.startWriting() } { - if unsafe { writer.status() } == AVAssetWriterStatus::Failed { - if let Some(err) = unsafe { writer.error() } { - return Err(anyhow::anyhow!("Failed to start writing: {}", err)); + if unsafe { writer.status() } == AVAssetWriterStatus::Failed + && let Some(err) = unsafe { writer.error() } { + return Err(AppleError::AssetWriterStartFailed(err.to_string())); } - } - return Err(anyhow::anyhow!("Failed to start writing")); + return Err(AppleError::AssetWriterStartFailedUnknown); } unsafe { writer.startSessionAtSourceTime(kCMTimeZero) }; diff --git a/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/video/mod.rs b/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/video/mod.rs index 61ddefd..71c2bc2 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/video/mod.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/video/mod.rs @@ -1,7 +1,7 @@ mod serialization; use std::{ffi::c_void, ptr::NonNull}; -use anyhow::{anyhow, Context, Result}; +use crate::error::{AppleError, Result, OsStatusExt}; use objc2::rc::Retained; use objc2_core_foundation::{ kCFAllocatorDefault, kCFBooleanFalse, kCFBooleanTrue, CFBoolean, CFDictionary, CFNumber, @@ -19,7 +19,7 @@ use objc2_video_toolbox::{ use tokio::sync::mpsc; use unienc_common::{buffer::SharedBuffer, EncodedData, Encoder, EncoderInput, EncoderOutput, VideoSample}; -use crate::{OsStatus, common::UnsafeSendRetained, metal, MetalTexture}; +use crate::{common::UnsafeSendRetained, metal, MetalTexture}; pub struct VideoToolboxEncoder { input: VideoToolboxEncoderInput, @@ -118,16 +118,16 @@ unsafe extern "C-unwind" fn handle_video_encode_output( unsafe extern "C-unwind" fn release_pixel_buffer( release_ref_con: *mut c_void, _base_address: *const c_void, -) { +) { unsafe { drop(Box::::from_raw(release_ref_con as *mut _)); -} +}} impl Encoder for VideoToolboxEncoder { type InputType = VideoToolboxEncoderInput; type OutputType = VideoToolboxEncoderOutput; - fn get(self) -> Result<(Self::InputType, Self::OutputType)> { + fn get(self) -> unienc_common::Result<(Self::InputType, Self::OutputType)> { Ok((self.input, self.output)) } } @@ -135,7 +135,7 @@ impl Encoder for VideoToolboxEncoder { impl EncoderInput for VideoToolboxEncoderInput { type Data = VideoSample; - async fn push(&mut self, data: Self::Data) -> Result<()> { + async fn push(&mut self, data: Self::Data) -> unienc_common::Result<()> { let buffer = match data.frame { unienc_common::VideoFrame::Bgra32(bgra32) => { let buffer = bgra32.buffer; @@ -151,12 +151,12 @@ impl EncoderInput for VideoToolboxEncoderInput { bgra32.height as usize, kCVPixelFormatType_32BGRA, NonNull::new(pixel_data_ptr as *mut c_void) - .context("Failed to create NonNull from pixel data pointer")?, + .ok_or(AppleError::NonNullCreationFailed)?, (bgra32.width * 4) as usize, Some(release_pixel_buffer), buffer_boxed_raw as *mut _, None, - NonNull::new(&mut buffer).context("Failed to create CVPixelBuffer")?, + NonNull::new(&mut buffer).ok_or(AppleError::NonNullCreationFailed)?, ) } .to_result() @@ -165,7 +165,7 @@ impl EncoderInput for VideoToolboxEncoderInput { _ = unsafe { Box::from_raw(buffer_boxed_raw) }; })?; - unsafe { Retained::from_raw(buffer) }.context("CVPixelBuffer is null")? + unsafe { Retained::from_raw(buffer) }.ok_or(AppleError::PixelBufferNull)? } unienc_common::VideoFrame::BlitSource { source, @@ -178,17 +178,17 @@ impl EncoderInput for VideoToolboxEncoderInput { } => { let width = self.width; let height = self.height; - + let (tx, rx) = tokio::sync::oneshot::channel(); event_issuer .issue_graphics_event(Box::new(move || { let r = metal::custom_blit(&source.texture, width, height, flip_vertically, is_gamma_workflow); - tx.send(r).map_err(|_e| anyhow!("Failed to send blit future")).unwrap(); + tx.send(r).map_err(|_e| AppleError::BlitFutureSendFailed).unwrap(); }), *crate::metal::EVENT_ID .get() - .context("Event ID is not reserved")?); - - let texture = rx.await? // failed to receive + .ok_or(AppleError::EventIdNotReserved)?); + + let texture = rx.await.map_err(AppleError::from)? // failed to receive ? // failed to issue blit .await?; // blit failed texture.pixel_buffer() @@ -228,7 +228,7 @@ impl EncoderInput for VideoToolboxEncoderInput { impl EncoderOutput for VideoToolboxEncoderOutput { type Data = VideoEncodedData; - async fn pull(&mut self) -> Result> { + async fn pull(&mut self) -> unienc_common::Result> { Ok(self.rx.recv().await) } } @@ -270,13 +270,13 @@ impl CompressionSession { Some(handle_video_encode_output), tx as *mut c_void, NonNull::new(&mut session) - .context("Failed to create NonNull from session pointer")?, + .ok_or(AppleError::NonNullCreationFailed)?, ) .to_result()?; } let session = - unsafe { Retained::from_raw(session).context("VTCompressionSession is null.")? }; + unsafe { Retained::from_raw(session).ok_or(AppleError::CompressionSessionNull)? }; unsafe { VTSessionSetProperty( &session, diff --git a/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/video/serialization.rs b/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/video/serialization.rs index 34df211..f8e244f 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/video/serialization.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_apple_vt/src/video/serialization.rs @@ -15,7 +15,7 @@ use objc2_core_media::{ CMVideoFormatDescriptionGetH264ParameterSetAtIndex, }; -use crate::{video::VideoEncodedData, OsStatus}; +use crate::{video::VideoEncodedData, error::OsStatusExt}; #[derive(Encode, Decode, Clone, Copy, Debug, PartialEq)] struct CMTimeForSerialization { diff --git a/InstantReplay.Externals/unienc/crates/unienc/Cargo.lock b/InstantReplay.Externals/unienc/crates/unienc_c/Cargo.lock similarity index 100% rename from InstantReplay.Externals/unienc/crates/unienc/Cargo.lock rename to InstantReplay.Externals/unienc/crates/unienc_c/Cargo.lock diff --git a/InstantReplay.Externals/unienc/crates/unienc_c/Cargo.toml b/InstantReplay.Externals/unienc/crates/unienc_c/Cargo.toml new file mode 100644 index 0000000..ebf4909 --- /dev/null +++ b/InstantReplay.Externals/unienc/crates/unienc_c/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "unienc_c" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true + +[lib] +crate-type = ["cdylib", "staticlib", "rlib"] + +[dependencies] +unienc = { workspace = true } +bincode = { workspace = true } +futures = "0.3.31" +tokio = { version = "1.45.1", features = ["rt", "macros", "sync"] } +unity-native-plugin = { workspace = true, optional = true } +thiserror = { workspace = true } + +[target.'cfg(target_os = "android")'.dependencies] +libc = "0.2.174" +ndk-sys = "0.6.0" + +[features] +default = ["multi-thread"] +unity = ["unity-native-plugin"] +multi-thread = ["tokio/rt-multi-thread"] + +[build-dependencies] +csbindgen = "1.9.7" diff --git a/InstantReplay.Externals/unienc/crates/unienc/build.rs b/InstantReplay.Externals/unienc/crates/unienc_c/build.rs similarity index 90% rename from InstantReplay.Externals/unienc/crates/unienc/build.rs rename to InstantReplay.Externals/unienc/crates/unienc_c/build.rs index 2b1e7ad..e8fb3ca 100644 --- a/InstantReplay.Externals/unienc/crates/unienc/build.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_c/build.rs @@ -8,9 +8,9 @@ fn main() { .input_extern_file("src/api/video.rs") .input_extern_file("src/api/runtime.rs") .input_extern_file("src/api/encoding_system.rs") + .input_extern_file("src/api/unity.rs") .input_extern_file("src/types.rs") .input_extern_file("src/buffer.rs") - .input_extern_file("src/graphics.rs") .input_extern_file("src/ffi.rs") .generate_csharp_file("../../../../Packages/jp.co.cyberagent.instant-replay/UniEnc/Runtime/Generated/NativeMethods.g.cs") .unwrap(); @@ -18,7 +18,7 @@ fn main() { fn common_builder() -> Builder { Builder::default() - .csharp_dll_name("libunienc") + .csharp_dll_name("libunienc_c") .csharp_dll_name_if("UNITY_IOS && !UNITY_EDITOR", "__Internal") .csharp_namespace("UniEnc.Native") .csharp_use_nint_types(true) diff --git a/InstantReplay.Externals/unienc/crates/unienc/src/api/android.rs b/InstantReplay.Externals/unienc/crates/unienc_c/src/api/android.rs similarity index 77% rename from InstantReplay.Externals/unienc/crates/unienc/src/api/android.rs rename to InstantReplay.Externals/unienc/crates/unienc_c/src/api/android.rs index 7cf258a..8b0b7dd 100644 --- a/InstantReplay.Externals/unienc/crates/unienc/src/api/android.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_c/src/api/android.rs @@ -3,18 +3,24 @@ use std::ffi::{c_int, CString}; use std::io::{BufRead, BufReader, PipeReader}; use std::os::unix::io::FromRawFd; use std::thread; -use anyhow::Result; +use thiserror::Error; use ndk_sys::__android_log_write; const ANDROID_LOG_INFO: c_int = 4; -#[no_mangle] +#[derive(Error, Debug)] +pub enum AndroidApiError { + #[error("Failed to create pipe")] + PipeCreationFailed, +} + +#[unsafe(no_mangle)] pub unsafe extern "C" fn JNI_OnLoad(vm: *mut c_void, reserved: *mut c_void) -> c_int { set_stdout_redirect("unienc").unwrap_or_else(|e| { log_to_logcat("unienc", &format!("Failed to redirect stdout: {}", e)); }); - unienc_android_mc::set_java_vm(vm as *mut _, reserved) + unienc::android::set_java_vm(vm as *mut _, reserved) } pub fn log_to_logcat(tag: &str, message: &str) { @@ -26,10 +32,10 @@ pub fn log_to_logcat(tag: &str, message: &str) { } // redirect stdout to logcat -pub unsafe fn set_stdout_redirect(log_tag: &'static str) -> Result<()> { +pub unsafe fn set_stdout_redirect(log_tag: &'static str) -> Result<(), AndroidApiError> { let mut pipe_fds = [0; 2]; if libc::pipe(pipe_fds.as_mut_ptr()) == -1 { - return Err(anyhow::anyhow!("Failed to create pipe")); + return Err(AndroidApiError::PipeCreationFailed); } libc::dup2(pipe_fds[1], libc::STDOUT_FILENO); libc::dup2(pipe_fds[1], libc::STDERR_FILENO); @@ -38,7 +44,7 @@ pub unsafe fn set_stdout_redirect(log_tag: &'static str) -> Result<()> { let pipe_read_end = PipeReader::from_raw_fd(pipe_fds[0]); let reader = BufReader::new(pipe_read_end); - for line in reader.lines().map_while(Result::ok) { + for line in reader.lines().map_while(|r| r.ok()) { log_to_logcat(log_tag, &line); } }); diff --git a/InstantReplay.Externals/unienc/crates/unienc/src/api/audio.rs b/InstantReplay.Externals/unienc/crates/unienc_c/src/api/audio.rs similarity index 83% rename from InstantReplay.Externals/unienc/crates/unienc/src/api/audio.rs rename to InstantReplay.Externals/unienc/crates/unienc_c/src/api/audio.rs index bcc8428..7e57424 100644 --- a/InstantReplay.Externals/unienc/crates/unienc/src/api/audio.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_c/src/api/audio.rs @@ -1,12 +1,11 @@ use std::ffi::c_void; -use anyhow::Context; use tokio::sync::Mutex; -use unienc_common::{AudioSample, EncoderInput, EncoderOutput}; +use unienc::{AudioSample, EncoderInput, EncoderOutput, ResultExt}; use crate::*; // Audio encoder input/output functions -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_audio_encoder_push( runtime: *mut Runtime, input: SendPtr>>, @@ -16,8 +15,8 @@ pub unsafe extern "C" fn unienc_audio_encoder_push( callback: usize, /*UniencCallback*/ user_data: SendPtr, ) { - let _guard = (*runtime).enter(); - let callback: UniencCallback = std::mem::transmute(callback); + let _guard = unsafe {&*runtime}.enter(); + let callback: UniencCallback = unsafe { std::mem::transmute(callback) }; if input.is_null() || data.is_null() { UniencError::invalid_input_error("Invalid input parameters") .apply_callback(callback, user_data); @@ -41,7 +40,7 @@ pub unsafe extern "C" fn unienc_audio_encoder_push( .push(sample) .await .context("Failed to push audio sample") - .map_err(UniencError::from_anyhow), + .map_err(UniencError::from_common), Err(err) => Err(err), }; result.apply_callback(callback, user_data); @@ -49,15 +48,15 @@ pub unsafe extern "C" fn unienc_audio_encoder_push( } } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_audio_encoder_pull( runtime: *mut Runtime, output: SendPtr>>, callback: usize, /*UniencDataCallback*/ user_data: SendPtr, ) { - let _guard = (*runtime).enter(); - let callback: UniencDataCallback = std::mem::transmute(callback); + let _guard = unsafe { &*runtime }.enter(); + let callback: UniencDataCallback = unsafe { std::mem::transmute(callback) }; if output.is_null() { UniencError::invalid_input_error("Invalid input parameters") .apply_callback(callback, user_data); @@ -75,14 +74,14 @@ pub unsafe extern "C" fn unienc_audio_encoder_pull( .pull() .await .context("Failed to pull audio sample") - .map_err(UniencError::from_anyhow), + .map_err(UniencError::from_common), Err(err) => Err(err), }; result.apply_callback(callback, user_data); }); } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_free_audio_encoder_input( audio_input: SendPtr>>, ) { @@ -91,7 +90,7 @@ pub unsafe extern "C" fn unienc_free_audio_encoder_input( } } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_free_audio_encoder_output( audio_output: SendPtr>>, ) { diff --git a/InstantReplay.Externals/unienc/crates/unienc/src/api/encoding_system.rs b/InstantReplay.Externals/unienc/crates/unienc_c/src/api/encoding_system.rs similarity index 81% rename from InstantReplay.Externals/unienc/crates/unienc/src/api/encoding_system.rs rename to InstantReplay.Externals/unienc/crates/unienc_c/src/api/encoding_system.rs index 3023d51..8a473f5 100644 --- a/InstantReplay.Externals/unienc/crates/unienc/src/api/encoding_system.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_c/src/api/encoding_system.rs @@ -2,25 +2,24 @@ use std::ffi::{c_char, CStr}; use std::os::raw::c_void; use std::path::Path; use std::sync::Arc; -use anyhow::Context; use tokio::sync::Mutex; -use unienc_common::{Encoder, EncodingSystem, Muxer}; +use unienc::{Encoder, EncodingSystem, Muxer, ResultExt}; use crate::*; -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_new_encoding_system( runtime: *mut Runtime, video_options: *const VideoEncoderOptionsNative, audio_options: *const AudioEncoderOptionsNative, ) -> *mut PlatformEncodingSystem { - let _guard = (*runtime).enter(); + let _guard = unsafe { &*runtime }.enter(); unsafe { let system = PlatformEncodingSystem::new(&*video_options, &*audio_options); Box::into_raw(Box::new(system)) } } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_free_encoding_system(system: *mut PlatformEncodingSystem) { if !system.is_null() { unsafe { @@ -29,7 +28,7 @@ pub unsafe extern "C" fn unienc_free_encoding_system(system: *mut PlatformEncodi } } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_new_video_encoder( runtime: *mut Runtime, system: *const PlatformEncodingSystem, @@ -38,8 +37,8 @@ pub unsafe extern "C" fn unienc_new_video_encoder( on_error: usize, /*UniencCallback*/ user_data: SendPtr, ) -> bool { - let _guard = (*runtime).enter(); - let on_error: UniencCallback = std::mem::transmute(on_error); + let _guard = unsafe { &*runtime }.enter(); + let on_error: UniencCallback = unsafe { std::mem::transmute(on_error) }; if system.is_null() { UniencError::invalid_input_error("Invalid input parameters") @@ -56,19 +55,19 @@ pub unsafe extern "C" fn unienc_new_video_encoder( true } Err(err) => { - UniencError::from_anyhow(err).apply_callback(on_error, user_data); + UniencError::from_common(err).apply_callback(on_error, user_data); false } }, Err(err) => { - UniencError::from_anyhow(err).apply_callback(on_error, user_data); + UniencError::from_common(err).apply_callback(on_error, user_data); false } } } } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_new_audio_encoder( runtime: *mut Runtime, system: *const PlatformEncodingSystem, @@ -77,8 +76,8 @@ pub unsafe extern "C" fn unienc_new_audio_encoder( on_error: usize, /*UniencCallback*/ user_data: SendPtr, ) -> bool { - let _guard = (*runtime).enter(); - let on_error: UniencCallback = std::mem::transmute(on_error); + let _guard = unsafe { &*runtime }.enter(); + let on_error: UniencCallback = unsafe { std::mem::transmute(on_error) }; if system.is_null() { UniencError::invalid_input_error("Invalid input parameters") @@ -95,19 +94,19 @@ pub unsafe extern "C" fn unienc_new_audio_encoder( true } Err(err) => { - UniencError::from_anyhow(err).apply_callback(on_error, user_data); + UniencError::from_common(err).apply_callback(on_error, user_data); false } }, Err(err) => { - UniencError::from_anyhow(err).apply_callback(on_error, user_data); + UniencError::from_common(err).apply_callback(on_error, user_data); false } } } } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_new_muxer( runtime: *mut Runtime, system: *const PlatformEncodingSystem, @@ -118,8 +117,8 @@ pub unsafe extern "C" fn unienc_new_muxer( on_error: usize, /*UniencCallback*/ user_data: SendPtr, ) -> bool { - let _guard = (*runtime).enter(); - let on_error: UniencCallback = std::mem::transmute(on_error); + let _guard = unsafe { &*runtime }.enter(); + let on_error: UniencCallback = unsafe { std::mem::transmute(on_error) }; if system.is_null() || output_path.is_null() { UniencError::invalid_input_error("Invalid input parameters") @@ -151,15 +150,20 @@ pub unsafe extern "C" fn unienc_new_muxer( true } Err(err) => { - UniencError::from_anyhow(err).apply_callback(on_error, user_data); + UniencError::from_common(err).apply_callback(on_error, user_data); false } } } Err(err) => { - UniencError::from_anyhow(err).apply_callback(on_error, user_data); + UniencError::from_common(err).apply_callback(on_error, user_data); false } } } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn unienc_is_blit_supported(system: *const PlatformEncodingSystem) -> bool { + unsafe {&*system}.is_blit_supported() } \ No newline at end of file diff --git a/InstantReplay.Externals/unienc/crates/unienc/src/api/mod.rs b/InstantReplay.Externals/unienc/crates/unienc_c/src/api/mod.rs similarity index 66% rename from InstantReplay.Externals/unienc/crates/unienc/src/api/mod.rs rename to InstantReplay.Externals/unienc/crates/unienc_c/src/api/mod.rs index aef4277..664ccec 100644 --- a/InstantReplay.Externals/unienc/crates/unienc/src/api/mod.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_c/src/api/mod.rs @@ -6,4 +6,6 @@ mod video; #[cfg(target_os = "android")] mod android; mod encoding_system; -mod runtime; \ No newline at end of file +mod runtime; +#[cfg(feature = "unity")] +mod unity; \ No newline at end of file diff --git a/InstantReplay.Externals/unienc/crates/unienc/src/api/mux.rs b/InstantReplay.Externals/unienc/crates/unienc_c/src/api/mux.rs similarity index 85% rename from InstantReplay.Externals/unienc/crates/unienc/src/api/mux.rs rename to InstantReplay.Externals/unienc/crates/unienc_c/src/api/mux.rs index d171ee4..e0342e0 100644 --- a/InstantReplay.Externals/unienc/crates/unienc/src/api/mux.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_c/src/api/mux.rs @@ -1,12 +1,11 @@ use std::ffi::c_void; -use anyhow::Context; use tokio::sync::Mutex; -use unienc_common::{CompletionHandle, EncodedData, MuxerInput}; +use unienc::{CompletionHandle, EncodedData, MuxerInput, ResultExt}; use crate::*; // Muxer input functions -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_muxer_push_video( runtime: *mut Runtime, video_input: SendPtr>>, @@ -16,8 +15,8 @@ pub unsafe extern "C" fn unienc_muxer_push_video( callback: usize, /*UniencCallback*/ user_data: SendPtr, ) { - let _guard = (*runtime).enter(); - let callback: UniencCallback = std::mem::transmute(callback); + let _guard = unsafe { &*runtime }.enter(); + let callback: UniencCallback = unsafe { std::mem::transmute(callback) }; if video_input.is_null() || data.is_null() { UniencError::invalid_input_error("Invalid input parameters") .apply_callback(callback, user_data); @@ -52,7 +51,7 @@ pub unsafe extern "C" fn unienc_muxer_push_video( .push(decoded_data) .await .context("Failed to push encoded video sample to muxer") - .map_err(UniencError::from_anyhow), + .map_err(UniencError::from_common), Err(err) => Err(err), }; result.apply_callback(callback, user_data); @@ -60,7 +59,7 @@ pub unsafe extern "C" fn unienc_muxer_push_video( } } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_muxer_push_audio( runtime: *mut Runtime, audio_input: SendPtr>>, @@ -70,8 +69,8 @@ pub unsafe extern "C" fn unienc_muxer_push_audio( callback: usize, /*UniencCallback*/ user_data: SendPtr, ) { - let _guard = (*runtime).enter(); - let callback: UniencCallback = std::mem::transmute(callback); + let _guard = unsafe { &*runtime }.enter(); + let callback: UniencCallback = unsafe { std::mem::transmute(callback) }; if audio_input.is_null() || data.is_null() { UniencError::invalid_input_error("Invalid input parameters") .apply_callback(callback, user_data); @@ -106,7 +105,7 @@ pub unsafe extern "C" fn unienc_muxer_push_audio( .push(decoded_data) .await .context("Failed to push encoded audio sample to muxer") - .map_err(UniencError::from_anyhow), + .map_err(UniencError::from_common), Err(err) => Err(err), }; result.apply_callback(callback, user_data); @@ -114,15 +113,15 @@ pub unsafe extern "C" fn unienc_muxer_push_audio( } } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_muxer_finish_video( runtime: *mut Runtime, video_input: SendPtr>>, callback: usize, /*UniencCallback*/ user_data: SendPtr, ) { - let _guard = (*runtime).enter(); - let callback: UniencCallback = std::mem::transmute(callback); + let _guard = unsafe { &*runtime }.enter(); + let callback: UniencCallback = unsafe { std::mem::transmute(callback) }; if video_input.is_null() { UniencError::invalid_input_error("Invalid input parameters") .apply_callback(callback, user_data); @@ -141,22 +140,22 @@ pub unsafe extern "C" fn unienc_muxer_finish_video( .finish() .await .context("Failed to finish video of muxer") - .map_err(UniencError::from_anyhow), + .map_err(UniencError::from_common), Err(err) => Err(err), }; result.apply_callback(callback, user_data); }); } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_muxer_finish_audio( runtime: *mut Runtime, audio_input: SendPtr>>, callback: usize, /*UniencCallback*/ user_data: SendPtr, ) { - let _guard = (*runtime).enter(); - let callback: UniencCallback = std::mem::transmute(callback); + let _guard = unsafe { &*runtime }.enter(); + let callback: UniencCallback = unsafe { std::mem::transmute(callback) }; if audio_input.is_null() { UniencError::invalid_input_error("Invalid input parameters") .apply_callback(callback, user_data); @@ -171,22 +170,22 @@ pub unsafe extern "C" fn unienc_muxer_finish_audio( .take() .ok_or(UniencError::resource_allocation_error("Resource is None")) { - Ok(audio_input) => audio_input.finish().await.context("Failed to finish audio of muxer").map_err(UniencError::from_anyhow), + Ok(audio_input) => audio_input.finish().await.context("Failed to finish audio of muxer").map_err(UniencError::from_common), Err(err) => Err(err), }; result.apply_callback(callback, user_data); }); } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_muxer_complete( runtime: *mut Runtime, completion_handle: SendPtr>>, callback: usize, /*UniencCallback*/ user_data: SendPtr, ) { - let _guard = (*runtime).enter(); - let callback: UniencCallback = std::mem::transmute(callback); + let _guard = unsafe { &*runtime }.enter(); + let callback: UniencCallback = unsafe { std::mem::transmute(callback) }; if completion_handle.is_null() { UniencError::invalid_input_error("Invalid input parameters") .apply_callback(callback, user_data); @@ -202,7 +201,7 @@ pub unsafe extern "C" fn unienc_muxer_complete( .take() .ok_or(UniencError::resource_allocation_error("Resource is None")) { - Ok(handle) => handle.finish().await.context("Failed to complete muxer").map_err(UniencError::from_anyhow), + Ok(handle) => handle.finish().await.context("Failed to complete muxer").map_err(UniencError::from_common), Err(err) => Err(err), }; result.apply_callback(callback, user_data); @@ -210,7 +209,7 @@ pub unsafe extern "C" fn unienc_muxer_complete( } // Free functions for muxer components -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_free_muxer_video_input( video_input: SendPtr>>, ) { @@ -219,7 +218,7 @@ pub unsafe extern "C" fn unienc_free_muxer_video_input( } } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_free_muxer_audio_input( audio_input: SendPtr>>, ) { @@ -228,7 +227,7 @@ pub unsafe extern "C" fn unienc_free_muxer_audio_input( } } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_free_muxer_completion_handle( completion_handle: SendPtr>>, ) { diff --git a/InstantReplay.Externals/unienc/crates/unienc/src/api/runtime.rs b/InstantReplay.Externals/unienc/crates/unienc_c/src/api/runtime.rs similarity index 72% rename from InstantReplay.Externals/unienc/crates/unienc/src/api/runtime.rs rename to InstantReplay.Externals/unienc/crates/unienc_c/src/api/runtime.rs index 6d248db..9eb021e 100644 --- a/InstantReplay.Externals/unienc/crates/unienc/src/api/runtime.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_c/src/api/runtime.rs @@ -1,13 +1,13 @@ use crate::*; -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_new_runtime() -> *mut Runtime { let runtime = Runtime::new().unwrap(); Box::into_raw(Box::new(runtime)) } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_drop_runtime(runtime: *mut Runtime) { - drop(Box::from_raw(runtime)); + drop(unsafe { Box::from_raw(runtime) }); } \ No newline at end of file diff --git a/InstantReplay.Externals/unienc/crates/unienc_c/src/api/unity.rs b/InstantReplay.Externals/unienc/crates/unienc_c/src/api/unity.rs new file mode 100644 index 0000000..53b2268 --- /dev/null +++ b/InstantReplay.Externals/unienc/crates/unienc_c/src/api/unity.rs @@ -0,0 +1,12 @@ +use std::os::raw::c_void; + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn unienc_free_graphics_event_context( + context: *mut c_void, +) { + if !context.is_null() { + unsafe { + let _ = Box::>::from_raw(context as *mut Box); + } + } +} \ No newline at end of file diff --git a/InstantReplay.Externals/unienc/crates/unienc/src/api/video.rs b/InstantReplay.Externals/unienc/crates/unienc_c/src/api/video.rs similarity index 74% rename from InstantReplay.Externals/unienc/crates/unienc/src/api/video.rs rename to InstantReplay.Externals/unienc/crates/unienc_c/src/api/video.rs index 1b5141b..df61f69 100644 --- a/InstantReplay.Externals/unienc/crates/unienc/src/api/video.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_c/src/api/video.rs @@ -1,15 +1,14 @@ use std::ffi::c_void; use crate::*; -use anyhow::Context; use tokio::sync::Mutex; -use unienc_common::{ - buffer::SharedBuffer, EncoderInput, EncoderOutput, TryFromUnityNativeTexturePointer, +use unienc::{ + buffer::SharedBuffer, EncoderInput, EncoderOutput, ResultExt, VideoFrame, VideoFrameBgra32, VideoSample, }; // Video encoder input/output functions -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_video_encoder_push_shared_buffer( runtime: *mut Runtime, input: SendPtr>>, @@ -20,13 +19,13 @@ pub unsafe extern "C" fn unienc_video_encoder_push_shared_buffer( callback: usize, /*UniencCallback*/ user_data: SendPtr, ) { - let callback: UniencCallback = std::mem::transmute(callback); + let callback: UniencCallback = unsafe { std::mem::transmute(callback) }; if input.is_null() || buffer.is_null() { UniencError::invalid_input_error("Invalid input parameters") .apply_callback(callback, user_data); return; } - let buffer = Box::from_raw(*buffer); + let buffer = unsafe { Box::from_raw(*buffer) }; let sample = VideoSample { frame: VideoFrame::Bgra32(VideoFrameBgra32 { buffer: *buffer, @@ -36,10 +35,11 @@ pub unsafe extern "C" fn unienc_video_encoder_push_shared_buffer( timestamp, }; - video_encoder_push_video_sample(runtime, input, sample, callback, user_data); + unsafe { video_encoder_push_video_sample(runtime, input, sample, callback, user_data) }; } -#[no_mangle] +#[cfg(feature = "unity")] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_video_encoder_push_blit_source( runtime: *mut Runtime, input: SendPtr>>, @@ -54,23 +54,23 @@ pub unsafe extern "C" fn unienc_video_encoder_push_blit_source( callback: usize, /*UniencCallback*/ user_data: SendPtr, ) { - let callback: UniencCallback = std::mem::transmute(callback); + let callback: UniencCallback = unsafe { std::mem::transmute(callback) }; if input.is_null() || source_native_texture_ptr.is_null() { UniencError::invalid_input_error("Invalid input parameters") .apply_callback(callback, user_data); return; } - let unienc_issue_graphics_event_callback: UniencIssueGraphicsEventCallback = - std::mem::transmute(issue_graphics_event_callback); + let unienc_issue_graphics_event_callback: crate::unity::UniencIssueGraphicsEventCallback = + unsafe { std::mem::transmute(issue_graphics_event_callback) }; // weak runtime for graphics event - let Some(weak) = runtime.as_ref().map(|r| r.weak()) else { + let Some(weak) = unsafe { runtime.as_ref() }.map(|r| r.weak()) else { UniencError::invalid_input_error("Invalid runtime pointer") .apply_callback(callback, user_data); return; }; - match BlitSource::try_from_unity_native_texture_ptr(source_native_texture_ptr) { + match ::try_from_unity_native_texture_ptr(source_native_texture_ptr) { Ok(blit_source) => { let sample = VideoSample { frame: VideoFrame::BlitSource { @@ -80,17 +80,17 @@ pub unsafe extern "C" fn unienc_video_encoder_push_blit_source( graphics_format, flip_vertically, is_gamma_workflow, - event_issuer: Box::new(UniencGraphicsEventIssuer::new( + event_issuer: Box::new(crate::unity::UniencGraphicsEventIssuer::new( unienc_issue_graphics_event_callback, weak )), }, timestamp, }; - video_encoder_push_video_sample(runtime, input, sample, callback, user_data); + unsafe { video_encoder_push_video_sample(runtime, input, sample, callback, user_data) }; } Err(err) => { - UniencError::from_anyhow(err).apply_callback(callback, user_data); + UniencError::from_common(err).apply_callback(callback, user_data); } } } @@ -102,7 +102,7 @@ unsafe fn video_encoder_push_video_sample( callback: UniencCallback, user_data: SendPtr, ) { - let _guard = (*runtime).enter(); + let _guard = unsafe { &*runtime }.enter(); let input = arc_from_raw_retained(*input); @@ -117,7 +117,7 @@ unsafe fn video_encoder_push_video_sample( .push(sample) .await .context("Failed to push video sample") - .map_err(UniencError::from_anyhow), + .map_err(UniencError::from_common), Err(err) => Err(err), }; @@ -125,15 +125,15 @@ unsafe fn video_encoder_push_video_sample( }); } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_video_encoder_pull( runtime: *mut Runtime, output: SendPtr>>, callback: usize, /*UniencDataCallback*/ user_data: SendPtr, ) { - let _guard = (*runtime).enter(); - let callback: UniencDataCallback = std::mem::transmute(callback); + let _guard = unsafe { &*runtime }.enter(); + let callback: UniencDataCallback = unsafe { std::mem::transmute(callback) }; if output.is_null() { UniencError::invalid_input_error("Invalid input parameters") .apply_callback(callback, user_data); @@ -149,12 +149,12 @@ pub unsafe extern "C" fn unienc_video_encoder_pull( .ok_or(UniencError::resource_allocation_error("Resource is None")) { Ok(output) => { - let result = output + + output .pull() .await .context("Failed to pull video sample") - .map_err(UniencError::from_anyhow); - result + .map_err(UniencError::from_common) } Err(err) => Err(err), }; @@ -162,7 +162,7 @@ pub unsafe extern "C" fn unienc_video_encoder_pull( }); } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_free_video_encoder_input( video_input: SendPtr>>, ) { @@ -171,7 +171,7 @@ pub unsafe extern "C" fn unienc_free_video_encoder_input( } } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_free_video_encoder_output( video_output: SendPtr>>, ) { diff --git a/InstantReplay.Externals/unienc/crates/unienc/src/buffer.rs b/InstantReplay.Externals/unienc/crates/unienc_c/src/buffer.rs similarity index 88% rename from InstantReplay.Externals/unienc/crates/unienc/src/buffer.rs rename to InstantReplay.Externals/unienc/crates/unienc_c/src/buffer.rs index b81f1c2..d996ebc 100644 --- a/InstantReplay.Externals/unienc/crates/unienc/src/buffer.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_c/src/buffer.rs @@ -3,10 +3,10 @@ use std::{ sync::{Arc, Mutex}, }; -use unienc_common::buffer::{SharedBuffer, SharedBufferPool}; +use unienc::buffer::{SharedBuffer, SharedBufferPool}; use crate::*; -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn unienc_new_shared_buffer_pool( limit: usize, pool_out: *mut *const Mutex, @@ -21,7 +21,7 @@ pub extern "C" fn unienc_new_shared_buffer_pool( true } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn unienc_shared_buffer_pool_alloc( pool: *mut Mutex, size: usize, @@ -43,13 +43,13 @@ pub extern "C" fn unienc_shared_buffer_pool_alloc( true } Err(err) => { - UniencError::from_anyhow(err).apply_callback(on_error, user_data); + UniencError::from_common(err).apply_callback(on_error, user_data); false } } } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn unienc_free_shared_buffer_pool(pool: *const Mutex) { if !pool.is_null() { unsafe { @@ -58,7 +58,7 @@ pub extern "C" fn unienc_free_shared_buffer_pool(pool: *const Mutex = unsafe extern "C" fn(data: Data, user_data: *mut c_void, error: UniencErrorNative); -pub type UniencIssueGraphicsEventCallback = -unsafe extern "C" fn(func: RenderingEventAndData, event_id: i32, user_data: *mut c_void); // Send-safe wrappers for raw pointers #[repr(transparent)] @@ -62,6 +59,23 @@ pub enum UniencErrorKind { PlatformError = 10, } +impl From for UniencErrorKind { + fn from(category: ErrorCategory) -> Self { + match category { + ErrorCategory::General => UniencErrorKind::Error, + ErrorCategory::Initialization => UniencErrorKind::InitializationError, + ErrorCategory::Configuration => UniencErrorKind::ConfigurationError, + ErrorCategory::ResourceAllocation => UniencErrorKind::ResourceAllocationError, + ErrorCategory::Encoding => UniencErrorKind::EncodingError, + ErrorCategory::Muxing => UniencErrorKind::MuxingError, + ErrorCategory::Communication => UniencErrorKind::CommunicationError, + ErrorCategory::Timeout => UniencErrorKind::TimeoutError, + ErrorCategory::InvalidInput => UniencErrorKind::InvalidInput, + ErrorCategory::Platform => UniencErrorKind::PlatformError, + } + } +} + #[derive(Clone, Debug, PartialEq)] pub struct UniencError { pub kind: UniencErrorKind, @@ -83,6 +97,7 @@ impl UniencErrorNative { } impl UniencError { + #[allow(dead_code)] pub const SUCCESS: Self = Self { kind: UniencErrorKind::Success, message: None, @@ -102,55 +117,16 @@ impl UniencError { drop(message); } - /// Convert an anyhow::Error to UniencError with appropriate categorization - pub fn from_anyhow(err: anyhow::Error) -> Self { - let message = format!("{err:?}").to_string(); - let kind = Self::categorize_error(&message); + /// Convert a CommonError to UniencError using the error's category + pub fn from_common(err: unienc::CommonError) -> Self { + let kind = UniencErrorKind::from(err.category()); + let message = err.to_string(); Self { kind, message: Some(message), } } - /// Categorize error based on error message content - fn categorize_error(message: &str) -> UniencErrorKind { - // Convert to lowercase for case-insensitive matching - let lower_message = message.to_lowercase(); - - if lower_message.contains("failed to create") - || lower_message.contains("failed to initialize") - { - UniencErrorKind::InitializationError - } else if lower_message.contains("failed to configure") - || lower_message.contains("configuration") - { - UniencErrorKind::ConfigurationError - } else if lower_message.contains("null") - || lower_message.contains("buffer too small") - || lower_message.contains("no input buffer") - || lower_message.contains("memory") - { - UniencErrorKind::ResourceAllocationError - } else if lower_message.contains("encoding") || lower_message.contains("encode") { - UniencErrorKind::EncodingError - } else if lower_message.contains("mux") || lower_message.contains("writing") { - UniencErrorKind::MuxingError - } else if lower_message.contains("failed to send") - || lower_message.contains("channel") - || lower_message.contains("communication") - { - UniencErrorKind::CommunicationError - } else if lower_message.contains("timeout") { - UniencErrorKind::TimeoutError - } else if lower_message.contains("invalid") || lower_message.contains("unsupported") { - UniencErrorKind::InvalidInput - } else if lower_message.contains("osstatus") || lower_message.contains("media") { - UniencErrorKind::PlatformError - } else { - UniencErrorKind::Error // Default fallback - } - } - // Specific error constructors for each error category pub fn initialization_error(msg: impl Into) -> Self { Self { @@ -237,7 +213,7 @@ impl ApplyCallback for UniencError { } } -impl ApplyCallback for anyhow::Result<(), UniencError> { +impl ApplyCallback for Result<(), UniencError> { fn apply_callback(&self, callback: UniencCallback, user_data: SendPtr) { match self { Ok(()) => unsafe { callback(user_data.into(), UniencErrorNative::SUCCESS) }, @@ -252,7 +228,7 @@ impl ApplyCallback> for UniencError { } } impl ApplyCallback> -for anyhow::Result, UniencError> +for Result, UniencError> { fn apply_callback( &self, @@ -301,7 +277,7 @@ for anyhow::Result, UniencError> } // These are unused but required to let csbindgen generate the binding for specific types. -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn unienc_dummy(_error_kind: UniencErrorKind, _error_native: UniencErrorNative, _sample: UniencSampleData) { } \ No newline at end of file diff --git a/InstantReplay.Externals/unienc/crates/unienc_c/src/lib.rs b/InstantReplay.Externals/unienc/crates/unienc_c/src/lib.rs new file mode 100644 index 0000000..603b802 --- /dev/null +++ b/InstantReplay.Externals/unienc/crates/unienc_c/src/lib.rs @@ -0,0 +1,17 @@ + +mod buffer; +mod utils; +mod runtime; +mod types; +mod api; +mod ffi; +mod platform; +#[cfg(feature = "unity")] +pub mod unity; + +pub(crate) use crate::ffi::*; +pub(crate) use crate::platform::*; +pub(crate) use crate::runtime::*; +pub(crate) use crate::types::*; +pub(crate) use crate::utils::*; + diff --git a/InstantReplay.Externals/unienc/crates/unienc_c/src/platform.rs b/InstantReplay.Externals/unienc/crates/unienc_c/src/platform.rs new file mode 100644 index 0000000..f3d29eb --- /dev/null +++ b/InstantReplay.Externals/unienc/crates/unienc_c/src/platform.rs @@ -0,0 +1,22 @@ + +use unienc::EncoderOutput; +use crate::types::{AudioEncoderOptionsNative, VideoEncoderOptionsNative}; + +pub type PlatformEncodingSystem = unienc::PlatformEncodingSystem; + +type VideoEncoder = +::VideoEncoderType; +pub type VideoEncoderInput = ::InputType; +pub type VideoEncoderOutput = ::OutputType; +type AudioEncoder = +::AudioEncoderType; +pub type AudioEncoderInput = ::InputType; +pub type AudioEncoderOutput = ::OutputType; +type Muxer = ::MuxerType; +pub type VideoMuxerInput = ::VideoInputType; +pub type AudioMuxerInput = ::AudioInputType; +pub type MuxerCompletionHandle = ::CompletionHandleType; + +pub type VideoEncodedData = ::Data; +pub type AudioEncodedData = ::Data; +pub type BlitSource = ::BlitSourceType; \ No newline at end of file diff --git a/InstantReplay.Externals/unienc/crates/unienc/src/runtime.rs b/InstantReplay.Externals/unienc/crates/unienc_c/src/runtime.rs similarity index 52% rename from InstantReplay.Externals/unienc/crates/unienc/src/runtime.rs rename to InstantReplay.Externals/unienc/crates/unienc_c/src/runtime.rs index d04d2f2..871ccbd 100644 --- a/InstantReplay.Externals/unienc/crates/unienc/src/runtime.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_c/src/runtime.rs @@ -1,14 +1,21 @@ -use anyhow::Result; +use std::io; use std::sync::{Arc, Weak}; +use thiserror::Error; use tokio::runtime::EnterGuard; +#[derive(Error, Debug)] +pub enum RuntimeError { + #[error("Failed to create tokio runtime: {0}")] + TokioRuntimeCreation(#[from] io::Error), +} + pub struct Runtime { tokio_runtime: Arc, } impl Runtime { - pub fn new() -> Result { - let tokio_runtime = Arc::new(tokio::runtime::Runtime::new()?); + pub fn new() -> Result { + let tokio_runtime = Arc::new(new_runtime_builder().build()?); Ok(Self { tokio_runtime }) } @@ -30,4 +37,16 @@ impl WeakRuntime { .upgrade() .map(|tokio_runtime| Runtime { tokio_runtime }) } +} + +fn new_runtime_builder() -> tokio::runtime::Builder { + #[cfg(not(feature = "multi-thread"))] + { + tokio::runtime::Builder::new_current_thread() + } + + #[cfg(feature = "multi-thread")] + { + tokio::runtime::Builder::new_multi_thread() + } } \ No newline at end of file diff --git a/InstantReplay.Externals/unienc/crates/unienc/src/types.rs b/InstantReplay.Externals/unienc/crates/unienc_c/src/types.rs similarity index 93% rename from InstantReplay.Externals/unienc/crates/unienc/src/types.rs rename to InstantReplay.Externals/unienc/crates/unienc_c/src/types.rs index 501329f..49b1f3d 100644 --- a/InstantReplay.Externals/unienc/crates/unienc/src/types.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_c/src/types.rs @@ -1,4 +1,4 @@ -use unienc_common::{AudioEncoderOptions, UniencSampleKind, VideoEncoderOptions}; +use unienc::{AudioEncoderOptions, UniencSampleKind, VideoEncoderOptions}; #[repr(C)] pub struct UniencSampleData { diff --git a/InstantReplay.Externals/unienc/crates/unienc_c/src/unity.rs b/InstantReplay.Externals/unienc/crates/unienc_c/src/unity.rs new file mode 100644 index 0000000..8ff328e --- /dev/null +++ b/InstantReplay.Externals/unienc/crates/unienc_c/src/unity.rs @@ -0,0 +1,90 @@ +use unity_native_plugin::graphics::RenderingEventAndData; +use std::os::raw::{c_int, c_void}; +use unienc::{EncodingSystem, GraphicsEventIssuer}; +use crate::*; + +pub type UniencIssueGraphicsEventCallback = +unsafe extern "C" fn(func: RenderingEventAndData, event_id: i32, user_data: *mut c_void); + +pub struct UniencGraphicsEventIssuer { + func: UniencIssueGraphicsEventCallback, + weak_runtime: WeakRuntime, +} + +impl UniencGraphicsEventIssuer { + pub fn new(func: UniencIssueGraphicsEventCallback, weak_runtime: WeakRuntime) -> Self { + Self { func, weak_runtime } + } +} + +struct GraphicsEventContext { + callback: Box, + weak_runtime: WeakRuntime, +} + +impl GraphicsEventIssuer for UniencGraphicsEventIssuer { + fn issue_graphics_event(&self, callback: Box, event_id: c_int) { + + let user_data = Box::into_raw(Box::new(GraphicsEventContext{ callback, weak_runtime: self.weak_runtime.clone() })) as *mut c_void; + unsafe { + (self.func)( + Some(graphics_event_callback_trampoline), + event_id, + user_data, + ) + } + } +} + +unsafe extern "system" fn graphics_event_callback_trampoline( + _event_id: c_int, + user_data: *mut c_void, +) { + let context = unsafe { Box::::from_raw(user_data as *mut _) }; + let Some(runtime) = context.weak_runtime.upgrade() else { + println!("Failed to upgrade runtime in graphics event callback"); + return; + }; + let _guard = runtime.enter(); + let callback = context.callback; + callback(); +} + +#[cfg(not(target_os = "ios"))] +mod entry_points { + use unienc::unity::UnityPlugin; + use crate::platform::PlatformEncodingSystem; + + unity_native_plugin::unity_native_plugin_entry_point! { + fn unity_plugin_load(interfaces: &unity_native_plugin::interface::UnityInterfaces) { + PlatformEncodingSystem::unity_plugin_load(interfaces); + } + fn unity_plugin_unload() { + PlatformEncodingSystem::unity_plugin_unload(); + } + } +} + +// statically linked for iOS +// we add `unienc_` prefix to avoid name collision with other plugins +#[cfg(target_os = "ios")] +mod entry_points { + use unienc::unity::UnityPlugin; + use crate::platform::PlatformEncodingSystem; + #[unsafe(no_mangle)] + #[allow(non_snake_case)] + extern "system" fn unienc_UnityPluginLoad( + interfaces: *mut unity_native_plugin::IUnityInterfaces, + ) { + unity_native_plugin::interface::UnityInterfaces::set_native_unity_interfaces(interfaces); + PlatformEncodingSystem::unity_plugin_load(unity_native_plugin::interface::UnityInterfaces::get()); + } + + #[unsafe(no_mangle)] + #[allow(non_snake_case)] + extern "system" fn unienc_UnityPluginUnload() { + PlatformEncodingSystem::unity_plugin_unload(); + unity_native_plugin::interface::UnityInterfaces::set_native_unity_interfaces(std::ptr::null_mut()); + } +} + diff --git a/InstantReplay.Externals/unienc/crates/unienc/src/utils.rs b/InstantReplay.Externals/unienc/crates/unienc_c/src/utils.rs similarity index 100% rename from InstantReplay.Externals/unienc/crates/unienc/src/utils.rs rename to InstantReplay.Externals/unienc/crates/unienc_c/src/utils.rs diff --git a/InstantReplay.Externals/unienc/crates/unienc_common/Cargo.toml b/InstantReplay.Externals/unienc/crates/unienc_common/Cargo.toml index ba5af0d..d32c4af 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_common/Cargo.toml +++ b/InstantReplay.Externals/unienc/crates/unienc_common/Cargo.toml @@ -1,12 +1,15 @@ [package] name = "unienc_common" -version = "0.1.0" +version.workspace = true edition.workspace = true license.workspace = true authors.workspace = true [dependencies] -anyhow = { workspace = true } +thiserror = { workspace = true } bincode = { workspace = true } -tokio = { version = "1.45.1", features = ["rt", "macros", "rt-multi-thread"] } -unity-native-plugin = "0.8.0" +unity-native-plugin = { workspace = true, optional = true } + +[features] +default = [] +unity = ["unity-native-plugin"] \ No newline at end of file diff --git a/InstantReplay.Externals/unienc/crates/unienc_common/src/buffer.rs b/InstantReplay.Externals/unienc/crates/unienc_common/src/buffer.rs index 5a749b8..de4cd6a 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_common/src/buffer.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_common/src/buffer.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use crate::error::{CommonError, Result}; use std::{sync::Arc, sync::Weak}; pub struct SharedBufferPool { @@ -32,7 +32,7 @@ impl SharedBufferPool { // limit=0 means unlimited if self.limit > 0 && current + size > self.limit { - return Err(anyhow::anyhow!("Buffer pool limit exceeded")); + return Err(CommonError::BufferPoolExceeded); } let len = Arc::new(size); diff --git a/InstantReplay.Externals/unienc/crates/unienc_common/src/error.rs b/InstantReplay.Externals/unienc/crates/unienc_common/src/error.rs new file mode 100644 index 0000000..0033fb3 --- /dev/null +++ b/InstantReplay.Externals/unienc/crates/unienc_common/src/error.rs @@ -0,0 +1,122 @@ +use thiserror::Error; + +/// Error category for FFI communication +/// This enum is used to categorize errors at the FFI boundary +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ErrorCategory { + /// General/unspecified error + General = 1, + /// Failed to initialize a component + Initialization = 2, + /// Configuration error + Configuration = 3, + /// Resource allocation failure (memory, buffers, etc.) + ResourceAllocation = 4, + /// Encoding error + Encoding = 5, + /// Muxing error + Muxing = 6, + /// Communication/channel error + Communication = 7, + /// Timeout error + Timeout = 8, + /// Invalid input + InvalidInput = 9, + /// Platform-specific error + Platform = 10, +} + +/// Trait for errors that can provide an error category +pub trait CategorizedError: std::error::Error { + /// Returns the error category for this error + fn category(&self) -> ErrorCategory; +} + +/// Common error type for unienc_common +#[derive(Error, Debug, Clone)] +pub enum CommonError { + #[error("Buffer pool limit exceeded")] + BufferPoolExceeded, + + #[error("Blit not supported in this encoding system")] + BlitNotSupported, + + /// Error with explicit category from platform code + #[error("{message}")] + Categorized { + category: ErrorCategory, + message: String, + }, + + #[error("{0}")] + Other(String), +} + +impl CategorizedError for CommonError { + fn category(&self) -> ErrorCategory { + match self { + CommonError::BufferPoolExceeded => ErrorCategory::ResourceAllocation, + CommonError::BlitNotSupported => ErrorCategory::Configuration, + CommonError::Categorized { category, .. } => *category, + CommonError::Other(_) => ErrorCategory::General, + } + } +} + +/// Result type alias for unienc_common +pub type Result = std::result::Result; + +/// Extension trait for adding context to Results (similar to anyhow::Context) +pub trait ResultExt { + /// Wrap the error with additional context + fn context>(self, context: C) -> Result; + + /// Wrap the error with lazily-evaluated context + fn with_context(self, f: F) -> Result + where + C: Into, + F: FnOnce() -> C; +} + +impl ResultExt + for std::result::Result +{ + fn context>(self, context: C) -> Result { + self.map_err(|e| CommonError::Other(format!("{}: {}", context.into(), e))) + } + + fn with_context(self, f: F) -> Result + where + C: Into, + F: FnOnce() -> C, + { + self.map_err(|e| CommonError::Other(format!("{}: {}", f().into(), e))) + } +} + +/// Extension trait for Option types +pub trait OptionExt { + /// Convert Option to Result with context message + fn context>(self, context: C) -> Result; + + /// Convert Option to Result with lazily-evaluated context + fn with_context(self, f: F) -> Result + where + C: Into, + F: FnOnce() -> C; +} + +impl OptionExt for Option { + fn context>(self, context: C) -> Result { + self.ok_or_else(|| CommonError::Other(context.into())) + } + + fn with_context(self, f: F) -> Result + where + C: Into, + F: FnOnce() -> C, + { + self.ok_or_else(|| CommonError::Other(f().into())) + } +} diff --git a/InstantReplay.Externals/unienc/crates/unienc_common/src/lib.rs b/InstantReplay.Externals/unienc/crates/unienc_common/src/lib.rs index 5725c60..713574e 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_common/src/lib.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_common/src/lib.rs @@ -4,10 +4,14 @@ use std::future::Future; use std::path::Path; use crate::buffer::SharedBuffer; -use anyhow::Result; use bincode::{Decode, Encode}; pub mod buffer; +pub mod error; +#[cfg(feature = "unity")] +pub mod unity; + +pub use error::{CategorizedError, CommonError, ErrorCategory, OptionExt, Result, ResultExt}; pub trait Encoder { type InputType: EncoderInput + 'static; @@ -67,10 +71,6 @@ pub trait EncodingSystem { fn is_blit_supported(&self) -> bool { false } - - #[allow(unused_variables)] - fn unity_plugin_load(interfaces: &unity_native_plugin::interface::UnityInterfaces) {} - fn unity_plugin_unload() {} } pub trait TryFromUnityNativeTexturePointer: Sized { @@ -81,9 +81,7 @@ pub struct UnsupportedBlitData; impl TryFromUnityNativeTexturePointer for UnsupportedBlitData { fn try_from_unity_native_texture_ptr(_ptr: *mut c_void) -> Result { - Err(anyhow::anyhow!( - "Blit not supported in this encoding system" - )) + Err(CommonError::BlitNotSupported) } } diff --git a/InstantReplay.Externals/unienc/crates/unienc_common/src/unity.rs b/InstantReplay.Externals/unienc/crates/unienc_common/src/unity.rs new file mode 100644 index 0000000..115d822 --- /dev/null +++ b/InstantReplay.Externals/unienc/crates/unienc_common/src/unity.rs @@ -0,0 +1,7 @@ +pub trait UnityPlugin { + #[allow(unused_variables)] + #[cfg(feature = "unity")] + fn unity_plugin_load(interfaces: &unity_native_plugin::interface::UnityInterfaces) {} + #[cfg(feature = "unity")] + fn unity_plugin_unload() {} +} \ No newline at end of file diff --git a/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/Cargo.toml b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/Cargo.toml index 0dbf14d..2216865 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/Cargo.toml +++ b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "unienc_ffmpeg" -version = "0.1.0" +version.workspace = true edition.workspace = true license.workspace = true authors.workspace = true [dependencies] -anyhow = { workspace = true } +thiserror = { workspace = true } tokio = { version = "1.45.1", features = ["rt", "time", "macros", "sync", "net", "process", "io-util"] } unienc_common = { workspace = true } bincode = { workspace = true } diff --git a/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/audio/mod.rs b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/audio/mod.rs index a535848..64462ba 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/audio/mod.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/audio/mod.rs @@ -1,6 +1,5 @@ use std::{sync::Arc, vec}; -use anyhow::{Context, Result}; use bincode::{Decode, Encode}; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, @@ -11,6 +10,7 @@ use unienc_common::{ UniencSampleKind, }; +use crate::error::{FFmpegError, Result}; use crate::ffmpeg; pub struct FFmpegAudioEncoder { @@ -51,9 +51,9 @@ impl FFmpegAudioEncoder { let input = ffmpeg .inputs .take() - .context("failed to get input")? + .ok_or(FFmpegError::InputNotAvailable)? .remove(0); - let output = ffmpeg.stdout.take().context("failed to get output")?; + let output = ffmpeg.stdout.take().ok_or(FFmpegError::OutputNotAvailable)?; let ffmpeg = Arc::new(ffmpeg); @@ -68,7 +68,7 @@ impl Encoder for FFmpegAudioEncoder { type InputType = FFmpegAudioEncoderInput; type OutputType = FFmpegAudioEncoderOutput; - fn get(self) -> Result<(Self::InputType, Self::OutputType)> { + fn get(self) -> unienc_common::Result<(Self::InputType, Self::OutputType)> { Ok((self.input, self.output)) } } @@ -76,7 +76,7 @@ impl Encoder for FFmpegAudioEncoder { impl EncoderInput for FFmpegAudioEncoderInput { type Data = AudioSample; - async fn push(&mut self, data: Self::Data) -> Result<()> { + async fn push(&mut self, data: Self::Data) -> unienc_common::Result<()> { let data = unsafe { std::slice::from_raw_parts::( data.data.as_ptr() as *const u8, @@ -84,8 +84,8 @@ impl EncoderInput for FFmpegAudioEncoderInput { ) }; - self.input.write_all(data).await?; - self.input.flush().await?; + self.input.write_all(data).await.map_err(FFmpegError::from)?; + self.input.flush().await.map_err(FFmpegError::from)?; Ok(()) } @@ -94,14 +94,13 @@ impl EncoderInput for FFmpegAudioEncoderInput { impl EncoderOutput for FFmpegAudioEncoderOutput { type Data = AudioEncodedData; - async fn pull(&mut self) -> Result> { + async fn pull(&mut self) -> unienc_common::Result> { // read ADTS header let mut header = vec![0u8; 7]; - if let Err(err) = self.output.read_exact(&mut header).await { - if err.kind() == std::io::ErrorKind::UnexpectedEof { + if let Err(err) = self.output.read_exact(&mut header).await + && err.kind() == std::io::ErrorKind::UnexpectedEof { return Ok(None); } - } // get frame length let mut length = ((header[3]& 0b11) as u16) << 11; @@ -115,7 +114,7 @@ impl EncoderOutput for FFmpegAudioEncoderOutput { self.timestamp_in_samples += 1024; let mut buf = vec![0u8; length as usize]; - self.output.read_exact(&mut buf).await?; + self.output.read_exact(&mut buf).await.map_err(FFmpegError::from)?; let data = AudioEncodedData { header, payload: buf, timestamp_in_samples, sample_rate: self.sample_rate }; diff --git a/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/error.rs b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/error.rs new file mode 100644 index 0000000..25ebeee --- /dev/null +++ b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/error.rs @@ -0,0 +1,104 @@ +use thiserror::Error; +use unienc_common::{CategorizedError, ErrorCategory}; + +#[derive(Error, Debug)] +pub enum FFmpegError { + #[error("FFmpeg not found in PATH")] + FFmpegNotFound, + + #[error("Failed to duplicate pipe file descriptor")] + PipeDupFailed, + + #[error("Failed to get stdin from child process")] + StdinNotAvailable, + + #[error("Failed to get inputs from FFmpeg process")] + InputsNotAvailable, + + #[error("Input is not available")] + InputNotAvailable, + + #[error("Failed to get output from FFmpeg process")] + OutputNotAvailable, + + #[error("FFmpeg process failed with exit status")] + ProcessFailed, + + #[error("No suitable H.264 encoder found")] + NoSuitableEncoder, + + #[error("Unsupported video frame format: only Bgra32 is supported")] + UnsupportedFrameFormat, + + #[error(transparent)] + Io(#[from] std::io::Error), + + #[error(transparent)] + Common(#[from] unienc_common::CommonError), + + #[error("{0}")] + Other(String), +} + +pub type Result = std::result::Result; + +impl CategorizedError for FFmpegError { + fn category(&self) -> ErrorCategory { + match self { + // Initialization errors + FFmpegError::FFmpegNotFound => ErrorCategory::Initialization, + FFmpegError::NoSuitableEncoder => ErrorCategory::Initialization, + + // Resource allocation errors + FFmpegError::PipeDupFailed => ErrorCategory::ResourceAllocation, + FFmpegError::StdinNotAvailable => ErrorCategory::ResourceAllocation, + FFmpegError::InputsNotAvailable => ErrorCategory::ResourceAllocation, + FFmpegError::InputNotAvailable => ErrorCategory::ResourceAllocation, + FFmpegError::OutputNotAvailable => ErrorCategory::ResourceAllocation, + + // Encoding errors + FFmpegError::ProcessFailed => ErrorCategory::Encoding, + + // Invalid input errors + FFmpegError::UnsupportedFrameFormat => ErrorCategory::InvalidInput, + + // IO errors (platform) + FFmpegError::Io(_) => ErrorCategory::Platform, + + // Wrapped common errors - delegate to inner + FFmpegError::Common(e) => e.category(), + + // Generic fallback + FFmpegError::Other(_) => ErrorCategory::General, + } + } +} + +impl From for unienc_common::CommonError { + fn from(err: FFmpegError) -> Self { + unienc_common::CommonError::Categorized { + category: err.category(), + message: err.to_string(), + } + } +} + +pub trait ResultExt { + fn context>(self, context: C) -> Result; +} + +impl ResultExt for std::result::Result { + fn context>(self, context: C) -> Result { + self.map_err(|e| FFmpegError::Other(format!("{}: {}", context.into(), e))) + } +} + +pub trait OptionExt { + fn context>(self, context: C) -> Result; +} + +impl OptionExt for Option { + fn context>(self, context: C) -> Result { + self.ok_or_else(|| FFmpegError::Other(context.into())) + } +} diff --git a/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/ffmpeg.rs b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/ffmpeg.rs index c1c5199..07f0ce1 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/ffmpeg.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/ffmpeg.rs @@ -5,40 +5,42 @@ use std::{ sync::LazyLock, }; -use anyhow::{Context, Result}; use tokio::{ io::AsyncWrite, process::{Child, ChildStdin, ChildStdout, Command}, }; +use crate::error::{FFmpegError, Result}; + pub static FFMPEG_PATH: LazyLock = LazyLock::new(|| { - let res = std::process::Command::new("which") + let res: Result = std::process::Command::new("which") .arg("ffmpeg") .output() - .context("Failed to find ffmpeg") + .map_err(|_| FFmpegError::FFmpegNotFound) .and_then(|o| { if o.status.success() { Ok(String::from_utf8_lossy(&o.stdout).trim().into()) } else { - Err(anyhow::anyhow!("Failed to find ffmpeg")) + Err(FFmpegError::FFmpegNotFound) } - }) - .unwrap_or_else(|_| { - std::process::Command::new("/bin/bash") - .arg("-cl") - .arg("which ffmpeg") - .output() - .context("Failed to find ffmpeg") - .and_then(|o| { - if o.status.success() { - Ok(String::from_utf8_lossy(&o.stdout).trim().into()) - } else { - Err(anyhow::anyhow!("Failed to find ffmpeg")) - } - }) - .unwrap_or(OsString::from("ffmpeg")) }); + let res = res.unwrap_or_else(|_| { + let fallback: Result = std::process::Command::new("/bin/bash") + .arg("-cl") + .arg("which ffmpeg") + .output() + .map_err(|_| FFmpegError::FFmpegNotFound) + .and_then(|o| { + if o.status.success() { + Ok(String::from_utf8_lossy(&o.stdout).trim().into()) + } else { + Err(FFmpegError::FFmpegNotFound) + } + }); + fallback.unwrap_or(OsString::from("ffmpeg")) + }); + println!("using FFmpeg at: {}", res.to_str().unwrap()); res @@ -142,7 +144,7 @@ impl Builder { // dup will remove O_CLOEXEC let rx_dup = unsafe { libc::dup(rx.as_raw_fd()) }; if rx_dup < 0 { - return Err(anyhow::anyhow!("Failed to dup pipe read end")); + return Err(FFmpegError::PipeDupFailed); } // keep rx lifetime until fork @@ -173,7 +175,7 @@ impl Builder { for input in inputs { inputs_result.push(match input { Some(tx) => Input::Pipe(tx), - None => Input::Stdin(child.stdin.take().context("Failed to get stdin")?), + None => Input::Stdin(child.stdin.take().ok_or(FFmpegError::StdinNotAvailable)?), }); } diff --git a/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/lib.rs b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/lib.rs index fb9b18e..27b92e8 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/lib.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/lib.rs @@ -1,13 +1,15 @@ -use anyhow::Result; -use std::{path::Path}; +use std::path::Path; use unienc_common::{EncodingSystem, UnsupportedBlitData}; pub mod audio; +pub mod error; mod ffmpeg; pub mod mux; mod utils; pub mod video; +pub use error::{FFmpegError, Result}; + use audio::FFmpegAudioEncoder; use mux::FFmpegMuxer; use video::FFmpegVideoEncoder; @@ -37,15 +39,15 @@ impl Result { - FFmpegVideoEncoder::new(&self.video_options) + fn new_video_encoder(&self) -> unienc_common::Result { + FFmpegVideoEncoder::new(&self.video_options).map_err(|e| e.into()) } - fn new_audio_encoder(&self) -> Result { - FFmpegAudioEncoder::new(&self.audio_options) + fn new_audio_encoder(&self) -> unienc_common::Result { + FFmpegAudioEncoder::new(&self.audio_options).map_err(|e| e.into()) } - fn new_muxer(&self, output_path: &Path) -> Result { - FFmpegMuxer::new(output_path, &self.video_options, &self.audio_options) + fn new_muxer(&self, output_path: &Path) -> unienc_common::Result { + FFmpegMuxer::new(output_path, &self.video_options, &self.audio_options).map_err(|e| e.into()) } } diff --git a/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/mux/mod.rs b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/mux/mod.rs index 1662bb8..1c4641f 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/mux/mod.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/mux/mod.rs @@ -1,11 +1,11 @@ use std::path::Path; -use anyhow::{Context, Result}; use tokio::io::AsyncWriteExt; use unienc_common::{CompletionHandle, Muxer, MuxerInput}; use crate::{ audio::AudioEncodedData, + error::{FFmpegError, Result}, ffmpeg::{self, FFmpeg}, video::VideoEncodedData, }; @@ -58,7 +58,7 @@ impl FFmpegMuxer { ffmpeg::Destination::Path(output_path.as_ref().as_os_str().to_owned()), )?; - let mut inputs = ffmpeg.inputs.take().context("failed to get inputs")?; + let mut inputs = ffmpeg.inputs.take().ok_or(FFmpegError::InputsNotAvailable)?; let audio_input = inputs.remove(1); let video_input = inputs.remove(0); @@ -77,7 +77,7 @@ impl Muxer for FFmpegMuxer { fn get_inputs( self, - ) -> anyhow::Result<( + ) -> unienc_common::Result<( Self::VideoInputType, Self::AudioInputType, Self::CompletionHandleType, @@ -89,25 +89,25 @@ impl Muxer for FFmpegMuxer { impl MuxerInput for FFmpegMuxerVideoInput { type Data = VideoEncodedData; - async fn push(&mut self, data: Self::Data) -> anyhow::Result<()> { - let input = self.input.as_mut().context(anyhow::anyhow!("Input is None"))?; + async fn push(&mut self, data: Self::Data) -> unienc_common::Result<()> { + let input = self.input.as_mut().ok_or(FFmpegError::InputNotAvailable)?; match data { VideoEncodedData::ParameterSet(payload) => { - input.write_all(&payload).await?; + input.write_all(&payload).await.map_err(FFmpegError::from)?; } VideoEncodedData::Slice { payload, .. } => { - input.write_all(&payload).await?; + input.write_all(&payload).await.map_err(FFmpegError::from)?; } } - input.flush().await?; + input.flush().await.map_err(FFmpegError::from)?; Ok(()) } - async fn finish(mut self) -> Result<()> { + async fn finish(mut self) -> unienc_common::Result<()> { // take input to drop it to ensure stdin / pipe is closed - self.input.take().context("Failed to take input")?.shutdown().await?; + self.input.take().ok_or(FFmpegError::InputNotAvailable)?.shutdown().await.map_err(FFmpegError::from)?; Ok(()) } } @@ -115,31 +115,31 @@ impl MuxerInput for FFmpegMuxerVideoInput { impl MuxerInput for FFmpegMuxerAudioInput { type Data = AudioEncodedData; - async fn push(&mut self, data: Self::Data) -> Result<()> { - let input = self.input.as_mut().context("Input is None")?; - input.write_all(&data.header).await?; - input.write_all(&data.payload).await?; + async fn push(&mut self, data: Self::Data) -> unienc_common::Result<()> { + let input = self.input.as_mut().ok_or(FFmpegError::InputNotAvailable)?; + input.write_all(&data.header).await.map_err(FFmpegError::from)?; + input.write_all(&data.payload).await.map_err(FFmpegError::from)?; - input.flush().await?; + input.flush().await.map_err(FFmpegError::from)?; Ok(()) } - async fn finish(mut self) -> Result<()> { + async fn finish(mut self) -> unienc_common::Result<()> { // take input to drop it to ensure stdin / pipe is closed - self.input.take().context("Failed to take input")?.shutdown().await?; + self.input.take().ok_or(FFmpegError::InputNotAvailable)?.shutdown().await.map_err(FFmpegError::from)?; Ok(()) } } impl CompletionHandle for FFmpegCompletionHandle { - async fn finish(self) -> Result<()> { + async fn finish(self) -> unienc_common::Result<()> { let result = self.child.wait().await?; println!("FFmpeg exited: {}", result); if result.success() { Ok(()) } else { - Err(anyhow::anyhow!("FFmpeg process failed")) + Err(FFmpegError::ProcessFailed.into()) } } } diff --git a/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/utils.rs b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/utils.rs index 370189f..4039689 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/utils.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/utils.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use crate::error::Result; // an utility to calculate frame repeating / discarding to keep constant frame rate pub struct Cfr { diff --git a/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/video/mod.rs b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/video/mod.rs index 0e55429..90f97ca 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/video/mod.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/video/mod.rs @@ -4,7 +4,6 @@ use std::{ vec, }; -use anyhow::{Context, Result}; use bincode::{Decode, Encode}; use cros_codecs::codec::h264::parser::NaluType; use tokio::{ @@ -16,6 +15,7 @@ use unienc_common::{ }; use crate::{ + error::{FFmpegError, Result}, ffmpeg, utils::Cfr, video::nalu::{NalUnit, NaluReader}, @@ -111,7 +111,7 @@ static FFMPEG_CODEC: LazyLock = LazyLock::new(|| { } }); - let encoder = encoder.context("No suitable H.264 encoder found")?; + let encoder = encoder.ok_or(FFmpegError::NoSuitableEncoder)?; println!("Using H.264 encoder: {}", encoder); @@ -164,9 +164,9 @@ impl FFmpegVideoEncoder { let input = ffmpeg .inputs .take() - .context("failed to get input")? + .ok_or(FFmpegError::InputNotAvailable)? .remove(0); - let output = ffmpeg.stdout.take().context("failed to get output")?; + let output = ffmpeg.stdout.take().ok_or(FFmpegError::OutputNotAvailable)?; let (buffer_tx, buffer_rx) = std::sync::mpsc::channel(); @@ -199,7 +199,7 @@ impl Encoder for FFmpegVideoEncoder { type InputType = FFmpegVideoEncoderInput; type OutputType = FFmpegVideoEncoderOutput; - fn get(self) -> Result<(Self::InputType, Self::OutputType)> { + fn get(self) -> unienc_common::Result<(Self::InputType, Self::OutputType)> { Ok((self.input, self.output)) } } @@ -207,11 +207,9 @@ impl Encoder for FFmpegVideoEncoder { impl EncoderInput for FFmpegVideoEncoderInput { type Data = VideoSample; - async fn push(&mut self, data: Self::Data) -> Result<()> { + async fn push(&mut self, data: Self::Data) -> unienc_common::Result<()> { let VideoFrame::Bgra32(frame) = data.frame else { - return Err(anyhow::anyhow!( - "FFmpegVideoEncoderInput only supports Bgra32 frames" - )); + return Err(FFmpegError::UnsupportedFrameFormat.into()); }; let timestamp = data.timestamp; @@ -248,11 +246,11 @@ impl EncoderInput for FFmpegVideoEncoderInput { }; for _i in 0..count { - self.input.write_all(frame.buffer.data()).await?; + self.input.write_all(frame.buffer.data()).await.map_err(FFmpegError::from)?; } drop(frame); - self.input.flush().await?; + self.input.flush().await.map_err(FFmpegError::from)?; Ok(()) } @@ -261,7 +259,7 @@ impl EncoderInput for FFmpegVideoEncoderInput { impl EncoderOutput for FFmpegVideoEncoderOutput { type Data = VideoEncodedData; - async fn pull(&mut self) -> Result> { + async fn pull(&mut self) -> unienc_common::Result> { loop { match self.buffer_rx.try_recv() { Ok(data) => { @@ -279,7 +277,7 @@ impl EncoderOutput for FFmpegVideoEncoderOutput { // H.264 byte stream is sequence of NAL units and each frame is a NAL unit let mut buf = vec![0; 65536]; - let read = self.output.read(&mut buf).await?; + let read = self.output.read(&mut buf).await.map_err(FFmpegError::from)?; fn create_emit<'a>(state: &'a mut ReaderState, cfr: u32) -> impl FnMut(&NalUnit) + 'a { move |nalu: &NalUnit| { diff --git a/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/video/nalu.rs b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/video/nalu.rs index 947af48..fac8f36 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/video/nalu.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_ffmpeg/src/video/nalu.rs @@ -1,7 +1,8 @@ use std::io::Cursor; -use anyhow::{anyhow, Result}; -use cros_codecs::codec::h264::{nalu, parser::Nalu}; +use cros_codecs::codec::h264::parser::Nalu; + +use crate::error::{FFmpegError, Result}; #[derive(Default)] pub struct NaluReader { @@ -37,12 +38,12 @@ impl NaluReader { fn drain(&mut self, emit: &mut impl FnMut(&NalUnit)) -> Result<()> { if let Some((start_pos, mut nalu_pos)) = get_start_position(&self.current) { if start_pos != 0 { - return Err(anyhow!("Invalid start code")); + return Err(FFmpegError::Other("Invalid start code".into())); } while let Some((next, next_nalu_pos)) = get_start_position(&self.current[nalu_pos..]) { let Ok(nalu) = Nalu::next(&mut Cursor::new(&self.current)) else { - return Err(anyhow!("Invalid NALU")); + return Err(FFmpegError::Other("Invalid NALU".into())); }; let nal_unit = NalUnit { @@ -71,7 +72,7 @@ impl NaluReader { emit(&nal_unit); Ok(()) } - Err(err) => Err(anyhow!("{}", err)), + Err(err) => Err(FFmpegError::Other(err.to_string())), } } } diff --git a/InstantReplay.Externals/unienc/crates/unienc_windows_mf/Cargo.toml b/InstantReplay.Externals/unienc/crates/unienc_windows_mf/Cargo.toml index 06311b3..e1188a4 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_windows_mf/Cargo.toml +++ b/InstantReplay.Externals/unienc/crates/unienc_windows_mf/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "unienc_windows_mf" -version = "0.1.0" +version.workspace = true edition.workspace = true license.workspace = true authors.workspace = true [dependencies] -anyhow = { workspace = true } +thiserror = { workspace = true } tokio = { version = "1.45.1", features = ["rt", "time", "macros", "sync"] } unienc_common = { workspace = true } bincode = { workspace = true } diff --git a/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/audio/mod.rs b/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/audio/mod.rs index 7a3b743..cb17d7c 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/audio/mod.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/audio/mod.rs @@ -1,14 +1,12 @@ -use anyhow::Result; +use crate::error::Result; use bincode::{Decode, Encode}; use tokio::sync::mpsc; -use unienc_common::{ - AudioEncoderOptions, AudioSample, EncodedData, Encoder, EncoderInput, EncoderOutput, - UniencSampleKind, -}; +use unienc_common::{AudioEncoderOptions, AudioSample, EncodedData, Encoder, EncoderInput, EncoderOutput, UniencSampleKind}; use windows::Win32::Media::MediaFoundation::*; use crate::common::*; use crate::mft::Transform; +use crate::WindowsError; pub struct MediaFoundationAudioEncoder { transform: Transform, @@ -67,7 +65,7 @@ impl Encoder for MediaFoundationAudioEncoder { type InputType = AudioEncoderInputImpl; type OutputType = AudioEncoderOutputImpl; - fn get(self) -> Result<(Self::InputType, Self::OutputType)> { + fn get(self) -> unienc_common::Result<(Self::InputType, Self::OutputType)> { let media_type = Some(UnsafeSend(self.transform.output_type()?.clone())); Ok(( AudioEncoderInputImpl { @@ -97,18 +95,18 @@ pub struct AudioEncoderOutputImpl { impl EncoderInput for AudioEncoderInputImpl { type Data = AudioSample; - async fn push(&mut self, data: Self::Data) -> Result<()> { - let sample = UnsafeSend(unsafe { MFCreateSample()? }); + async fn push(&mut self, data: Self::Data) -> unienc_common::Result<()> { + let sample = UnsafeSend(unsafe { MFCreateSample().map_err(WindowsError::from)? }); // BGRA to NV12 { let length = (data.data.len() * std::mem::size_of::()) as u32; - let buffer = unsafe { MFCreateMemoryBuffer(length)? }; + let buffer = unsafe { MFCreateMemoryBuffer(length).map_err(WindowsError::from)? }; - unsafe { sample.AddBuffer(&buffer)? }; + unsafe { sample.AddBuffer(&buffer).map_err(WindowsError::from)? }; let mut buffer_ptr: *mut u8 = std::ptr::null_mut(); - unsafe { buffer.Lock(&mut buffer_ptr, None, None)? }; + unsafe { buffer.Lock(&mut buffer_ptr, None, None).map_err(WindowsError::from)? }; unsafe { std::ptr::copy_nonoverlapping( @@ -118,31 +116,31 @@ impl EncoderInput for AudioEncoderInputImpl { ); } - unsafe { buffer.SetCurrentLength(length)? } + unsafe { buffer.SetCurrentLength(length).map_err(WindowsError::from)? } - unsafe { buffer.Unlock()? }; + unsafe { buffer.Unlock().map_err(WindowsError::from)? }; } unsafe { sample.SetSampleTime( (data.timestamp_in_samples as f64 / self.sample_rate as f64 * 10_000_000_f64) as i64, - )? + ).map_err(WindowsError::from)? }; unsafe { sample.SetSampleDuration( ((data.data.len() / self.channels as usize) as f64 / self.sample_rate as f64 * 10_000_000_f64) as i64, - )? + ).map_err(WindowsError::from)? }; - self.transform.push(sample).await + Ok(self.transform.push(sample).await?) } } impl EncoderOutput for AudioEncoderOutputImpl { type Data = AudioEncodedData; - async fn pull(&mut self) -> Result> { + async fn pull(&mut self) -> unienc_common::Result> { if let Some(media_type) = self.media_type.take() { return Ok(Some(AudioEncodedData { payload: Payload::Format(media_type), diff --git a/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/common.rs b/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/common.rs index 893d896..970d241 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/common.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/common.rs @@ -1,11 +1,11 @@ use std::collections::HashMap; use std::{fmt::Display, ops::Deref}; -use anyhow::Result; use bincode::{BorrowDecode, Decode, Encode}; use windows::core::GUID; use windows::core::{Interface, BSTR}; use windows::Win32::Media::MediaFoundation::*; +use crate::WindowsError; #[derive(Debug)] @@ -56,7 +56,7 @@ impl std::fmt::Debug for Payload { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let serializable: SerializablePayload = self .try_into() - .map_err(|e: anyhow::Error| bincode::error::EncodeError::OtherString(e.to_string())).unwrap(); + .map_err(|e: crate::error::WindowsError| bincode::error::EncodeError::OtherString(e.to_string())).unwrap(); serializable.fmt(f) } } @@ -65,10 +65,10 @@ impl Encode for Payload { fn encode( &self, encoder: &mut E, - ) -> Result<(), bincode::error::EncodeError> { + ) -> std::result::Result<(), bincode::error::EncodeError> { let serializable: SerializablePayload = self .try_into() - .map_err(|e: anyhow::Error| bincode::error::EncodeError::OtherString(e.to_string()))?; + .map_err(|e: crate::error::WindowsError| bincode::error::EncodeError::OtherString(e.to_string()))?; serializable.encode(encoder) } } @@ -76,22 +76,22 @@ impl Encode for Payload { impl Decode for Payload { fn decode>( decoder: &mut D, - ) -> Result { + ) -> std::result::Result { let serializable = &SerializablePayload::decode(decoder)?; serializable .try_into() - .map_err(|e: anyhow::Error| bincode::error::DecodeError::OtherString(e.to_string())) + .map_err(|e: crate::error::WindowsError| bincode::error::DecodeError::OtherString(e.to_string())) } } impl<'de, Context> BorrowDecode<'de, Context> for Payload { fn borrow_decode>( decoder: &mut D, - ) -> Result { + ) -> std::result::Result { let serializable = &SerializablePayload::borrow_decode(decoder)?; serializable .try_into() - .map_err(|e: anyhow::Error| bincode::error::DecodeError::OtherString(e.to_string())) + .map_err(|e: crate::error::WindowsError| bincode::error::DecodeError::OtherString(e.to_string())) } } @@ -102,7 +102,7 @@ enum SerializablePayload { } impl TryFrom<&Payload> for SerializablePayload { - type Error = anyhow::Error; + type Error = crate::error::WindowsError; fn try_from(value: &Payload) -> std::result::Result { match value { @@ -115,7 +115,7 @@ impl TryFrom<&Payload> for SerializablePayload { } impl TryFrom<&SerializablePayload> for Payload { - type Error = anyhow::Error; + type Error = crate::error::WindowsError; fn try_from(value: &SerializablePayload) -> std::result::Result { match value { @@ -180,7 +180,7 @@ enum AttributeValue { } impl TryFrom<&IMFSample> for SerializableMFSample { - type Error = anyhow::Error; + type Error = crate::error::WindowsError; fn try_from(value: &IMFSample) -> std::result::Result { let attributes: SerializableMFAttributes = (&value.cast::()?).try_into()?; @@ -210,7 +210,7 @@ impl TryFrom<&IMFSample> for SerializableMFSample { } impl TryInto for &SerializableMFSample { - type Error = anyhow::Error; + type Error = crate::error::WindowsError; fn try_into(self) -> std::result::Result { let sample = unsafe { MFCreateSample()? }; @@ -236,9 +236,9 @@ impl TryInto for &SerializableMFSample { } impl TryFrom<&IMFAttributes> for SerializableMFAttributes { - type Error = anyhow::Error; + type Error = crate::error::WindowsError; - fn try_from(from: &IMFAttributes) -> Result { + fn try_from(from: &IMFAttributes) -> std::result::Result { let count = unsafe { from.GetCount()? }; let mut map = HashMap::::new(); for i in 0..count { @@ -259,7 +259,7 @@ impl TryFrom<&IMFAttributes> for SerializableMFAttributes { unsafe { from.GetString(&guid, &mut buffer, Some(&mut length))? }; - let value: String = BSTR::from_wide(&buffer[..length as usize]).try_into()?; + let value: String = BSTR::from_wide(&buffer[..length as usize]).try_into().map_err(|_| WindowsError::Utf16ToStringConversionFailed)?; AttributeValue::String(value) } MF_ATTRIBUTE_BLOB => { @@ -282,7 +282,7 @@ impl TryFrom<&IMFAttributes> for SerializableMFAttributes { } impl SerializableMFAttributes { - pub fn apply(&self, target: &IMFAttributes) -> Result<()> { + pub fn apply(&self, target: &IMFAttributes) -> std::result::Result<(), crate::error::WindowsError> { for (guid, value) in &self.attributes { let guid = GUID::from_u128(guid.0); match value { diff --git a/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/error.rs b/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/error.rs new file mode 100644 index 0000000..76c40cd --- /dev/null +++ b/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/error.rs @@ -0,0 +1,158 @@ +use thiserror::Error; +use unienc_common::{CategorizedError, ErrorCategory}; + +/// Error type for unienc_windows_mf +#[derive(Error, Debug, Clone)] +pub enum WindowsError { + // MFT (Media Foundation Transform) related errors + #[error("No suitable MFT found")] + NoSuitableMft, + + #[error("Expected 1 input and 1 output stream for encoder")] + InvalidStreamCount, + + #[error("Input type is None")] + InputTypeNone, + + #[error("Output type is None")] + OutputTypeNone, + + #[error("Failed to get output")] + OutputGetFailed, + + // Media event related errors + #[error("Failed to receive media event")] + MediaEventReceiveFailed, + + #[error("Failed to send video sample: {0}")] + SampleSendFailed(String), + + // Stream related errors + #[error("Stream is not initialized")] + StreamNotInitialized, + + #[error("Failed to get stream")] + StreamGetFailed, + + #[error("Failed to send media type")] + MediaTypeSendFailed, + + #[error("Failed to send stream")] + StreamSendFailed, + + #[error("Failed to send finish signal")] + FinishSignalSendFailed, + + // Video encoder related errors + #[error("MediaFoundationVideoEncoder only supports Bgra32 frames")] + UnsupportedVideoFrameFormat, + + // Muxer related errors + #[error("Failed to send video data to muxer: {0}")] + MuxerSendFailed(String), + + #[error("Failed to wait for muxer completion: {0}")] + MuxerCompletionWaitFailed(String), + + // Channel related errors + #[error("Failed to send to channel")] + ChannelSendFailed, + + // External error conversions + #[error(transparent)] + Windows(#[from] windows_core::Error), + + #[error(transparent)] + Common(#[from] unienc_common::CommonError), + + #[error(transparent)] + OneshotRecv(#[from] tokio::sync::oneshot::error::RecvError), + + #[error("Failed to convert UTF-16 into String")] + Utf16ToStringConversionFailed, + + // Generic errors + #[error("{0}")] + Other(String), +} + +/// Result type alias for unienc_windows_mf +pub type Result = std::result::Result; + +impl CategorizedError for WindowsError { + fn category(&self) -> ErrorCategory { + match self { + // Initialization/Configuration errors + WindowsError::NoSuitableMft => ErrorCategory::Initialization, + WindowsError::InvalidStreamCount => ErrorCategory::Configuration, + WindowsError::InputTypeNone => ErrorCategory::Configuration, + WindowsError::OutputTypeNone => ErrorCategory::Configuration, + WindowsError::StreamNotInitialized => ErrorCategory::Initialization, + + // Encoding errors + WindowsError::OutputGetFailed => ErrorCategory::Encoding, + WindowsError::UnsupportedVideoFrameFormat => ErrorCategory::InvalidInput, + + // Communication errors + WindowsError::MediaEventReceiveFailed => ErrorCategory::Communication, + WindowsError::SampleSendFailed(_) => ErrorCategory::Communication, + WindowsError::StreamGetFailed => ErrorCategory::Communication, + WindowsError::MediaTypeSendFailed => ErrorCategory::Communication, + WindowsError::StreamSendFailed => ErrorCategory::Communication, + WindowsError::FinishSignalSendFailed => ErrorCategory::Communication, + WindowsError::ChannelSendFailed => ErrorCategory::Communication, + WindowsError::OneshotRecv(_) => ErrorCategory::Communication, + + // Muxing errors + WindowsError::MuxerSendFailed(_) => ErrorCategory::Muxing, + WindowsError::MuxerCompletionWaitFailed(_) => ErrorCategory::Muxing, + + // Platform errors + WindowsError::Windows(_) => ErrorCategory::Platform, + + // Wrapped common errors - delegate to inner + WindowsError::Common(e) => e.category(), + + // Generic fallback + WindowsError::Other(_) => ErrorCategory::General, + WindowsError::Utf16ToStringConversionFailed => ErrorCategory::General, + } + } +} + +impl From for unienc_common::CommonError { + fn from(err: WindowsError) -> Self { + unienc_common::CommonError::Categorized { + category: err.category(), + message: err.to_string(), + } + } +} + +impl From> for WindowsError { + fn from(_: tokio::sync::mpsc::error::SendError) -> Self { + WindowsError::ChannelSendFailed + } +} + +/// Extension trait for adding context to Results +pub trait ResultExt { + fn context>(self, context: C) -> Result; +} + +impl ResultExt for std::result::Result { + fn context>(self, context: C) -> Result { + self.map_err(|e| WindowsError::Other(format!("{}: {}", context.into(), e))) + } +} + +/// Extension trait for Option types +pub trait OptionExt { + fn context>(self, context: C) -> Result; +} + +impl OptionExt for Option { + fn context>(self, context: C) -> Result { + self.ok_or_else(|| WindowsError::Other(context.into())) + } +} diff --git a/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/lib.rs b/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/lib.rs index 73d7bb3..d7d89f4 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/lib.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/lib.rs @@ -2,16 +2,18 @@ #[cfg(not(any(target_os = "windows")))] compile_error!("This crate can only be compiled for Windows platforms."); -use anyhow::Result; -use std::{future::Future, path::Path}; +use std::path::Path; use unienc_common::{EncodingSystem, UnsupportedBlitData}; pub mod audio; mod common; +pub mod error; pub(crate) mod mft; pub mod mux; pub mod video; +pub use error::{WindowsError, Result}; + use audio::MediaFoundationAudioEncoder; use mux::MediaFoundationMuxer; use video::MediaFoundationVideoEncoder; @@ -49,16 +51,16 @@ impl Result { - MediaFoundationVideoEncoder::new(&self.video_options) + fn new_video_encoder(&self) -> unienc_common::Result { + MediaFoundationVideoEncoder::new(&self.video_options).map_err(|e| e.into()) } - fn new_audio_encoder(&self) -> Result { - MediaFoundationAudioEncoder::new(&self.audio_options) + fn new_audio_encoder(&self) -> unienc_common::Result { + MediaFoundationAudioEncoder::new(&self.audio_options).map_err(|e| e.into()) } - fn new_muxer(&self, output_path: &Path) -> Result { - MediaFoundationMuxer::new(output_path, &self.video_options, &self.audio_options) + fn new_muxer(&self, output_path: &Path) -> unienc_common::Result { + MediaFoundationMuxer::new(output_path, &self.video_options, &self.audio_options).map_err(|e| e.into()) } } diff --git a/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/mft.rs b/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/mft.rs index 56cb2a7..3efa868 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/mft.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/mft.rs @@ -3,7 +3,7 @@ use windows::Win32::Media::MediaFoundation::{IMFSample, IMFTransform, MFT_OUTPUT use windows::Win32::System::Com::CoTaskMemFree; use crate::common::UnsafeSend; -use anyhow::{anyhow, Context, Result}; +use crate::error::{WindowsError, Result}; use std::cell::Cell; use std::future::Future; use std::mem::ManuallyDrop; @@ -27,8 +27,8 @@ impl MediaEventGeneratorCustom for IMFMediaEventGenerator { tx.send(unsafe { generator .EndGetEvent(result.unwrap()) - .context("Failed to get media event") .map(UnsafeSend::::from) + .map_err(WindowsError::from) }) .unwrap(); }) @@ -41,7 +41,7 @@ impl MediaEventGeneratorCustom for IMFMediaEventGenerator { result?; match rx.await { Ok(event) => event, - Err(_) => Err(anyhow!("Failed to receive media event")), + Err(_) => Err(WindowsError::MediaEventReceiveFailed), } } } @@ -150,7 +150,7 @@ fn process_output( let buffer = &mut buffers[0]; - let sample = buffer.pSample.take().context("Failed to get output")?; + let sample = buffer.pSample.take().ok_or(WindowsError::OutputGetFailed)?; Ok(sample.into()) } @@ -289,7 +289,7 @@ impl Transform { }; } - result.context("No suitable MFT found") + result.ok_or(WindowsError::NoSuitableMft) } fn get_name(activate: &IMFActivate) -> Result { @@ -300,7 +300,7 @@ impl Transform { activate.GetString(&MFT_FRIENDLY_NAME_Attribute, &mut buffer, Some(&mut length))? }; - let value: String = BSTR::from_wide(&buffer[..length as usize]).try_into()?; + let value: String = BSTR::from_wide(&buffer[..length as usize]).try_into().map_err(|_| WindowsError::Utf16ToStringConversionFailed)?; Ok(value) } @@ -324,9 +324,7 @@ impl Transform { unsafe { transform.GetStreamCount(&mut input_streams, &mut output_streams)? }; if input_streams != 1 || output_streams != 1 { - return Err(anyhow!( - "Expected 1 input and 1 output stream for video encoder" - )); + return Err(WindowsError::InvalidStreamCount); } let mut input_ids = [0; 1]; @@ -344,11 +342,11 @@ impl Transform { { let Some(input_type) = &input_type else { - return Err(anyhow!("Input type is None")); + return Err(WindowsError::InputTypeNone); }; let Some(output_type) = &output_type else { - return Err(anyhow!("Output type is None")); + return Err(WindowsError::OutputTypeNone); }; unsafe { transform.SetOutputType(output_id, output_type, 0)? }; @@ -429,8 +427,8 @@ impl Transform { Ok(( Self { pipeline: Pipeline::Async { sample_tx }, - input_type: UnsafeSend(input_type.take().context("Input type is None")?), - output_type: UnsafeSend(output_type.take().context("Output type is None")?), + input_type: UnsafeSend(input_type.take().ok_or(WindowsError::InputTypeNone)?), + output_type: UnsafeSend(output_type.take().ok_or(WindowsError::OutputTypeNone)?), }, output_rx, )) @@ -446,8 +444,8 @@ impl Transform { output_id, output_info, }, - input_type: UnsafeSend(input_type.take().context("Input type is None")?), - output_type: UnsafeSend(output_type.take().context("Output type is None")?), + input_type: UnsafeSend(input_type.take().ok_or(WindowsError::InputTypeNone)?), + output_type: UnsafeSend(output_type.take().ok_or(WindowsError::OutputTypeNone)?), }, output_rx, )) @@ -459,7 +457,7 @@ impl Transform { Pipeline::Async { sample_tx } => sample_tx .send(sample) .await - .map_err(|e| anyhow!("Failed to send video sample: {}", e)), + .map_err(|e| WindowsError::SampleSendFailed(e.to_string())), Pipeline::Sync { output_tx, transform, @@ -475,7 +473,7 @@ impl Transform { continue; } Err(err) => { - if let Ok(err) = err.downcast::() { + if let WindowsError::Windows(err) = err { if err.code() == MF_E_TRANSFORM_NEED_MORE_INPUT { return Ok(()); } else { @@ -534,7 +532,7 @@ impl Drop for Transform { continue; } Err(err) => { - if let Ok(err) = err.downcast::() { + if let WindowsError::Windows(err) = err { if err.code() == MF_E_TRANSFORM_NEED_MORE_INPUT { return; } else { diff --git a/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/mux/mod.rs b/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/mux/mod.rs index 0aec9e3..80c3168 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/mux/mod.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/mux/mod.rs @@ -1,12 +1,8 @@ -use anyhow::Context; -use anyhow::{anyhow, Result}; +use crate::error::{WindowsError, Result}; use std::path::Path; -use std::sync::Arc; use tokio::sync::mpsc; use tokio::sync::oneshot; -use unienc_common::{ - AudioEncoderOptions, CompletionHandle, Muxer, MuxerInput, VideoEncoderOptions, -}; +use unienc_common::{AudioEncoderOptions, CompletionHandle, Muxer, MuxerInput, VideoEncoderOptions}; use windows::Win32::Media::MediaFoundation::*; use windows_core::IUnknown; use windows_core::HSTRING; @@ -23,7 +19,7 @@ enum LazyStream { tx: oneshot::Sender>>, rx: oneshot::Receiver>, }, - Some(Result>), + Some(Result), } impl LazyStream { @@ -37,29 +33,29 @@ impl LazyStream { pub async fn get( &mut self, media_type: UnsafeSend, - ) -> Result<&Stream, Arc> { + ) -> Result<()> { let result = async { match std::mem::replace( self, - LazyStream::Some(Err(Arc::new(anyhow!("Failed to get stream")))), + LazyStream::Some(Err(WindowsError::StreamGetFailed)), ) { LazyStream::None { tx, rx } => { tx.send(Ok(media_type)) - .map_err(|_| anyhow!("Failed to send video media type"))?; + .map_err(|_| WindowsError::MediaTypeSendFailed)?; let stream = rx.await??; Ok(stream) } - LazyStream::Some(stream) => Ok(stream.map_err(|e| anyhow!(e))?), + LazyStream::Some(stream) => Ok(stream?), } } .await; - let result = result.map_err(Arc::new); *self = LazyStream::Some(result); let LazyStream::Some(result) = self else { unreachable!() }; - result.as_ref().map_err(|e| e.clone()) + result.as_ref().map_err(|e| e.clone())?; + Ok(()) } } @@ -144,10 +140,10 @@ impl MediaFoundationMuxer { video_stream_tx .send(Ok(video_stream)) - .map_err(|_| anyhow!("Failed to send video stream"))?; + .map_err(|_| WindowsError::StreamSendFailed)?; audio_stream_tx .send(Ok(audio_stream)) - .map_err(|_| anyhow!("Failed to send audio stream"))?; + .map_err(|_| WindowsError::StreamSendFailed)?; Result::<()>::Ok(()) }); @@ -204,7 +200,7 @@ impl Stream { if let Some(finish_tx) = finish_tx.take() { finish_tx .send(()) - .map_err(|_e| anyhow!("Failed to send finish signal"))? + .map_err(|_e| WindowsError::FinishSignalSendFailed)? }; } } @@ -229,7 +225,7 @@ impl Muxer for MediaFoundationMuxer { fn get_inputs( self, - ) -> Result<( + ) -> unienc_common::Result<( Self::VideoInputType, Self::AudioInputType, Self::CompletionHandleType, @@ -255,24 +251,25 @@ pub struct VideoMuxerInputImpl { impl MuxerInput for VideoMuxerInputImpl { type Data = VideoEncodedData; - async fn push(&mut self, data: Self::Data) -> Result<()> { + async fn push(&mut self, data: Self::Data) -> unienc_common::Result<()> { match data.payload { Payload::Format(media_type) => { - self.stream.get(media_type).await.map_err(|e| anyhow!(e))?; + self.stream.get(media_type).await.map_err(|e| WindowsError::Other(e.to_string()))?; Ok(()) } Payload::Sample(sample) => { - let stream = self.stream.some().context("stream is not initialized")?; + let stream = self.stream.some().ok_or(WindowsError::StreamNotInitialized)?; stream .sample_tx .send(sample) .await - .map_err(|e| anyhow!("Failed to send video data to muxer: {}", e)) + .map_err(|e| WindowsError::MuxerSendFailed(e.to_string()))?; + Ok(()) } } } - async fn finish(self) -> Result<()> { + async fn finish(self) -> unienc_common::Result<()> { drop(self.stream); Ok(()) } @@ -285,24 +282,25 @@ pub struct AudioMuxerInputImpl { impl MuxerInput for AudioMuxerInputImpl { type Data = AudioEncodedData; - async fn push(&mut self, data: Self::Data) -> Result<()> { + async fn push(&mut self, data: Self::Data) -> unienc_common::Result<()> { match data.payload { Payload::Format(media_type) => { - self.stream.get(media_type).await.map_err(|e| anyhow!(e))?; + self.stream.get(media_type).await.map_err(|e| WindowsError::Other(e.to_string()))?; Ok(()) } Payload::Sample(sample) => { - let stream = self.stream.some().context("stream is not initialized")?; + let stream = self.stream.some().ok_or(WindowsError::StreamNotInitialized)?; stream .sample_tx .send(sample) .await - .map_err(|e| anyhow!("Failed to send video data to muxer: {}", e)) + .map_err(|e| WindowsError::MuxerSendFailed(e.to_string()))?; + Ok(()) } } } - async fn finish(self) -> Result<()> { + async fn finish(self) -> unienc_common::Result<()> { drop(self.stream); Ok(()) } @@ -313,9 +311,9 @@ pub struct MuxerCompletionHandleImpl { } impl CompletionHandle for MuxerCompletionHandleImpl { - async fn finish(self) -> Result<()> { + async fn finish(self) -> unienc_common::Result<()> { self.receiver .await - .map_err(|e| anyhow!("Failed to wait for muxer completion: {}", e))? + .map_err(|e| WindowsError::MuxerCompletionWaitFailed(e.to_string()))?.map_err(|e| e.into()) } } diff --git a/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/video/mod.rs b/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/video/mod.rs index c509cef..8bee3af 100644 --- a/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/video/mod.rs +++ b/InstantReplay.Externals/unienc/crates/unienc_windows_mf/src/video/mod.rs @@ -1,9 +1,7 @@ -use anyhow::Result; +use crate::error::{WindowsError, Result}; use bincode::{Decode, Encode}; use tokio::sync::mpsc; -use unienc_common::{ - EncodedData, Encoder, EncoderInput, EncoderOutput, UniencSampleKind, UnsupportedBlitData, VideoEncoderOptions, VideoFrame, VideoSample -}; +use unienc_common::{EncodedData, Encoder, EncoderInput, EncoderOutput, UniencSampleKind, UnsupportedBlitData, VideoEncoderOptions, VideoFrame, VideoSample}; use windows::Win32::Media::MediaFoundation::*; use crate::common::*; @@ -73,7 +71,7 @@ impl Encoder for MediaFoundationVideoEncoder { type InputType = VideoEncoderInputImpl; type OutputType = VideoEncoderOutputImpl; - fn get(self) -> Result<(Self::InputType, Self::OutputType)> { + fn get(self) -> unienc_common::Result<(Self::InputType, Self::OutputType)> { let media_type = Some(UnsafeSend(self.transform.output_type()?.clone())); Ok(( VideoEncoderInputImpl { @@ -101,24 +99,22 @@ pub struct VideoEncoderOutputImpl { impl EncoderInput for VideoEncoderInputImpl { type Data = VideoSample; - async fn push(&mut self, data: Self::Data) -> Result<()> { + async fn push(&mut self, data: Self::Data) -> unienc_common::Result<()> { let VideoFrame::Bgra32(frame) = data.frame else { - return Err(anyhow::anyhow!( - "MediaFoundationVideoEncoder only supports Bgra32 frames" - )); + return Err(WindowsError::UnsupportedVideoFrameFormat.into()); }; - let sample = UnsafeSend(unsafe { MFCreateSample()? }); + let sample = UnsafeSend(unsafe { MFCreateSample().map_err(WindowsError::from)? }); // BGRA to NV12 { let (y, u, v) = frame.to_yuv420_planes(None)?; let length = (y.len() + u.len() + v.len()) as u32; - let buffer = unsafe { MFCreateMemoryBuffer(length)? }; + let buffer = unsafe { MFCreateMemoryBuffer(length).map_err(WindowsError::from)? }; - unsafe { sample.AddBuffer(&buffer)? }; + unsafe { sample.AddBuffer(&buffer).map_err(WindowsError::from)? }; let mut buffer_ptr: *mut u8 = std::ptr::null_mut(); - unsafe { buffer.Lock(&mut buffer_ptr, None, None)? }; + unsafe { buffer.Lock(&mut buffer_ptr, None, None).map_err(WindowsError::from)? }; unsafe { std::ptr::copy_nonoverlapping(y.as_ptr(), buffer_ptr, y.len()); @@ -131,21 +127,21 @@ impl EncoderInput for VideoEncoderInputImpl { } } - unsafe { buffer.SetCurrentLength(length)? } + unsafe { buffer.SetCurrentLength(length).map_err(WindowsError::from)? } - unsafe { buffer.Unlock()? }; + unsafe { buffer.Unlock().map_err(WindowsError::from)? }; } - unsafe { sample.SetSampleTime((data.timestamp * 10_000_000_f64) as i64)? }; - unsafe { sample.SetSampleDuration((1.0_f64 / self.fps_hint * 10_000_000_f64) as i64)? }; - self.transform.push(sample).await + unsafe { sample.SetSampleTime((data.timestamp * 10_000_000_f64) as i64).map_err(WindowsError::from)? }; + unsafe { sample.SetSampleDuration((1.0_f64 / self.fps_hint * 10_000_000_f64) as i64).map_err(WindowsError::from)? }; + Ok(self.transform.push(sample).await?) } } impl EncoderOutput for VideoEncoderOutputImpl { type Data = VideoEncodedData; - async fn pull(&mut self) -> Result> { + async fn pull(&mut self) -> unienc_common::Result> { if let Some(media_type) = self.media_type.take() { return Ok(Some(VideoEncodedData { payload: Payload::Format(media_type), diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-darwin/libunienc.dylib b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-darwin/libunienc.dylib deleted file mode 100755 index 84bce1b..0000000 Binary files a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-darwin/libunienc.dylib and /dev/null differ diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-darwin/libunienc_c.dylib b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-darwin/libunienc_c.dylib new file mode 100755 index 0000000..a4b35b8 Binary files /dev/null and b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-darwin/libunienc_c.dylib differ diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-darwin/libunienc.dylib.meta b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-darwin/libunienc_c.dylib.meta similarity index 100% rename from Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-darwin/libunienc.dylib.meta rename to Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-darwin/libunienc_c.dylib.meta diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-ios/libunienc.a b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-ios/libunienc_c.a similarity index 92% rename from Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-ios/libunienc.a rename to Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-ios/libunienc_c.a index f905e49..3f42335 100644 Binary files a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-ios/libunienc.a and b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-ios/libunienc_c.a differ diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-ios/libunienc.a.meta b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-ios/libunienc_c.a.meta similarity index 100% rename from Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-ios/libunienc.a.meta rename to Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-apple-ios/libunienc_c.a.meta diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-linux-android/libunienc.so b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-linux-android/libunienc.so deleted file mode 100755 index 2de292b..0000000 Binary files a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-linux-android/libunienc.so and /dev/null differ diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-linux-android/libunienc_c.so b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-linux-android/libunienc_c.so new file mode 100755 index 0000000..e8bf711 Binary files /dev/null and b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-linux-android/libunienc_c.so differ diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-linux-android/libunienc.so.meta b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-linux-android/libunienc_c.so.meta similarity index 100% rename from Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-linux-android/libunienc.so.meta rename to Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/aarch64-linux-android/libunienc_c.so.meta diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-apple-darwin/libunienc.dylib b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-apple-darwin/libunienc.dylib deleted file mode 100644 index 8a364f4..0000000 Binary files a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-apple-darwin/libunienc.dylib and /dev/null differ diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-apple-darwin/libunienc_c.dylib b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-apple-darwin/libunienc_c.dylib new file mode 100644 index 0000000..71ff5f0 Binary files /dev/null and b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-apple-darwin/libunienc_c.dylib differ diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-apple-darwin/libunienc.dylib.meta b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-apple-darwin/libunienc_c.dylib.meta similarity index 100% rename from Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-apple-darwin/libunienc.dylib.meta rename to Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-apple-darwin/libunienc_c.dylib.meta diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-linux-android/libunienc.so b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-linux-android/libunienc.so deleted file mode 100644 index 19eb376..0000000 Binary files a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-linux-android/libunienc.so and /dev/null differ diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-linux-android/libunienc_c.so b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-linux-android/libunienc_c.so new file mode 100644 index 0000000..0f372ab Binary files /dev/null and b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-linux-android/libunienc_c.so differ diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-linux-android/libunienc.so.meta b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-linux-android/libunienc_c.so.meta similarity index 100% rename from Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-linux-android/libunienc.so.meta rename to Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-linux-android/libunienc_c.so.meta diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-pc-windows/libunienc.dll b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-pc-windows/libunienc.dll deleted file mode 100644 index c64ef3c..0000000 Binary files a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-pc-windows/libunienc.dll and /dev/null differ diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-pc-windows/libunienc_c.dll b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-pc-windows/libunienc_c.dll new file mode 100644 index 0000000..1e124dc Binary files /dev/null and b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-pc-windows/libunienc_c.dll differ diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-pc-windows/libunienc.dll.meta b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-pc-windows/libunienc_c.dll.meta similarity index 100% rename from Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-pc-windows/libunienc.dll.meta rename to Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-pc-windows/libunienc_c.dll.meta diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-unknown-linux/libunienc.so b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-unknown-linux/libunienc.so deleted file mode 100644 index 0a15087..0000000 Binary files a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-unknown-linux/libunienc.so and /dev/null differ diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-unknown-linux/libunienc_c.so b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-unknown-linux/libunienc_c.so new file mode 100644 index 0000000..6a71742 Binary files /dev/null and b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-unknown-linux/libunienc_c.so differ diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-unknown-linux/libunienc.so.meta b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-unknown-linux/libunienc_c.so.meta similarity index 100% rename from Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-unknown-linux/libunienc.so.meta rename to Packages/jp.co.cyberagent.instant-replay/UniEnc/Plugins/x86_64-unknown-linux/libunienc_c.so.meta diff --git a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Runtime/Generated/NativeMethods.g.cs b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Runtime/Generated/NativeMethods.g.cs index 91d9269..ff8cd4f 100644 --- a/Packages/jp.co.cyberagent.instant-replay/UniEnc/Runtime/Generated/NativeMethods.g.cs +++ b/Packages/jp.co.cyberagent.instant-replay/UniEnc/Runtime/Generated/NativeMethods.g.cs @@ -15,12 +15,14 @@ internal static unsafe partial class NativeMethods #if UNITY_IOS && !UNITY_EDITOR const string __DllName = "__Internal"; #else - const string __DllName = "libunienc"; + const string __DllName = "libunienc_c"; #endif + + [DllImport(__DllName, EntryPoint = "unienc_audio_encoder_push", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] internal static extern void unienc_audio_encoder_push(Runtime* runtime, SendPtr input, SendPtr data, nuint sample_count, ulong timestamp_in_samples, nuint callback, SendPtr user_data); @@ -96,6 +98,13 @@ internal static unsafe partial class NativeMethods [return: MarshalAs(UnmanagedType.U1)] internal static extern bool unienc_new_muxer(Runtime* runtime, PlatformEncodingSystem* system, byte* output_path, Mutex** video_input_out, Mutex** audio_input_out, Mutex** completion_handle_out, nuint on_error, SendPtr user_data); + [DllImport(__DllName, EntryPoint = "unienc_is_blit_supported", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + internal static extern bool unienc_is_blit_supported(PlatformEncodingSystem* system); + + [DllImport(__DllName, EntryPoint = "unienc_free_graphics_event_context", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void unienc_free_graphics_event_context(void* context); + [DllImport(__DllName, EntryPoint = "unienc_new_shared_buffer_pool", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] [return: MarshalAs(UnmanagedType.U1)] internal static extern bool unienc_new_shared_buffer_pool(nuint limit, Mutex** pool_out, nuint _on_error, void* _user_data); @@ -110,13 +119,6 @@ internal static unsafe partial class NativeMethods [DllImport(__DllName, EntryPoint = "unienc_free_shared_buffer", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] internal static extern void unienc_free_shared_buffer(SharedBuffer* buffer); - [DllImport(__DllName, EntryPoint = "unienc_is_blit_supported", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - [return: MarshalAs(UnmanagedType.U1)] - internal static extern bool unienc_is_blit_supported(PlatformEncodingSystem* system); - - [DllImport(__DllName, EntryPoint = "unienc_free_graphics_event_context", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - internal static extern void unienc_free_graphics_event_context(void* context); - [DllImport(__DllName, EntryPoint = "unienc_dummy", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] internal static extern void unienc_dummy(UniencErrorKind _error_kind, UniencErrorNative _error_native, UniencSampleData _sample); diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index 3d277af..39ec3f2 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -2,8 +2,8 @@ ## Overview -- Apache License 2.0 (103) -- MIT License (28) +- Apache License 2.0 (107) +- MIT License (29) - BSD 3-Clause "New" or "Revised" License (1) - ISC License (1) - Unicode License v3 (1) @@ -235,27 +235,37 @@ - [windows-result 0.3.4](https://github.com/microsoft/windows-rs) - [windows-strings 0.4.2](https://github.com/microsoft/windows-rs) - [windows-sys 0.45.0](https://github.com/microsoft/windows-rs) -- [windows-sys 0.52.0](https://github.com/microsoft/windows-rs) - [windows-sys 0.59.0](https://github.com/microsoft/windows-rs) +- [windows-sys 0.60.2](https://github.com/microsoft/windows-rs) +- [windows-sys 0.61.2](https://github.com/microsoft/windows-rs) - [windows-targets 0.42.2](https://github.com/microsoft/windows-rs) - [windows-targets 0.52.6](https://github.com/microsoft/windows-rs) +- [windows-targets 0.53.5](https://github.com/microsoft/windows-rs) - [windows-threading 0.1.0](https://github.com/microsoft/windows-rs) - [windows 0.61.3](https://github.com/microsoft/windows-rs) - [windows_aarch64_gnullvm 0.42.2](https://github.com/microsoft/windows-rs) - [windows_aarch64_gnullvm 0.52.6](https://github.com/microsoft/windows-rs) +- [windows_aarch64_gnullvm 0.53.1](https://github.com/microsoft/windows-rs) - [windows_aarch64_msvc 0.42.2](https://github.com/microsoft/windows-rs) - [windows_aarch64_msvc 0.52.6](https://github.com/microsoft/windows-rs) +- [windows_aarch64_msvc 0.53.1](https://github.com/microsoft/windows-rs) - [windows_i686_gnu 0.42.2](https://github.com/microsoft/windows-rs) - [windows_i686_gnu 0.52.6](https://github.com/microsoft/windows-rs) +- [windows_i686_gnu 0.53.1](https://github.com/microsoft/windows-rs) - [windows_i686_gnullvm 0.52.6](https://github.com/microsoft/windows-rs) +- [windows_i686_gnullvm 0.53.1](https://github.com/microsoft/windows-rs) - [windows_i686_msvc 0.42.2](https://github.com/microsoft/windows-rs) - [windows_i686_msvc 0.52.6](https://github.com/microsoft/windows-rs) +- [windows_i686_msvc 0.53.1](https://github.com/microsoft/windows-rs) - [windows_x86_64_gnu 0.42.2](https://github.com/microsoft/windows-rs) - [windows_x86_64_gnu 0.52.6](https://github.com/microsoft/windows-rs) +- [windows_x86_64_gnu 0.53.1](https://github.com/microsoft/windows-rs) - [windows_x86_64_gnullvm 0.42.2](https://github.com/microsoft/windows-rs) - [windows_x86_64_gnullvm 0.52.6](https://github.com/microsoft/windows-rs) +- [windows_x86_64_gnullvm 0.53.1](https://github.com/microsoft/windows-rs) - [windows_x86_64_msvc 0.42.2](https://github.com/microsoft/windows-rs) - [windows_x86_64_msvc 0.52.6](https://github.com/microsoft/windows-rs) +- [windows_x86_64_msvc 0.53.1](https://github.com/microsoft/windows-rs) ``` Apache License @@ -1526,22 +1536,17 @@ limitations under the License. ### Apache License 2.0 -- [addr2line 0.24.2](https://github.com/gimli-rs/addr2line) -- [backtrace 0.3.75](https://github.com/rust-lang/backtrace-rs) - [bitflags 2.9.1](https://github.com/bitflags/bitflags) - [cc 1.2.49](https://github.com/rust-lang/cc-rs) - [cfg-if 1.0.0](https://github.com/alexcrichton/cfg-if) - [find-msvc-tools 0.1.5](https://github.com/rust-lang/cc-rs) -- [gimli 0.31.1](https://github.com/gimli-rs/gimli) - [jni 0.21.1](https://github.com/jni-rs/jni-rs) - [log 0.4.27](https://github.com/rust-lang/log) -- [object 0.36.7](https://github.com/gimli-rs/object) - [regex-automata 0.4.9](https://github.com/rust-lang/regex/tree/master/regex-automata) - [regex-syntax 0.8.5](https://github.com/rust-lang/regex/tree/master/regex-syntax) - [regex 1.11.1](https://github.com/rust-lang/regex) -- [rustc-demangle 0.1.24](https://github.com/rust-lang/rustc-demangle) - [signal-hook-registry 1.4.6](https://github.com/vorner/signal-hook) -- [socket2 0.5.10](https://github.com/rust-lang/socket2) +- [socket2 0.6.1](https://github.com/rust-lang/socket2) - [wasi 0.11.1+wasi-snapshot-preview1](https://github.com/bytecodealliance/wasi) - [wasi 0.14.2+wasi-0.2.4](https://github.com/bytecodealliance/wasi-rs) @@ -2157,216 +2162,6 @@ limitations under the License. ``` -### Apache License 2.0 - -- [adler2 2.0.0](https://github.com/oyvindln/adler2) - -``` - Apache License - Version 2.0, January 2004 - https://www.apache.org/licenses/LICENSE-2.0 - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -``` - - ### Apache License 2.0 - [ash 0.38.0+1.3.281](https://github.com/ash-rs/ash) @@ -2445,11 +2240,9 @@ limitations under the License. ### Apache License 2.0 -- [anyhow 1.0.98](https://github.com/dtolnay/anyhow) - [cesu8 1.1.0](https://github.com/emk/cesu8-rs) - [dispatch2 0.3.0](https://github.com/madsmtm/objc2) - [libc 0.2.175](https://github.com/rust-lang/libc) -- [miniz_oxide 0.8.8](https://github.com/Frommi/miniz_oxide/tree/master/miniz_oxide) - [ndk-sys 0.6.0+11769913](https://github.com/rust-mobile/ndk) - [objc2-audio-toolbox 0.3.2](https://github.com/madsmtm/objc2) - [objc2-av-foundation 0.3.2](https://github.com/madsmtm/objc2) @@ -2477,7 +2270,9 @@ limitations under the License. - [shlex 1.3.0](https://github.com/comex/rust-shlex) - [syn 2.0.101](https://github.com/dtolnay/syn) - [thiserror-impl 1.0.69](https://github.com/dtolnay/thiserror) +- [thiserror-impl 2.0.17](https://github.com/dtolnay/thiserror) - [thiserror 1.0.69](https://github.com/dtolnay/thiserror) +- [thiserror 2.0.17](https://github.com/dtolnay/thiserror) - [unicode-ident 1.0.18](https://github.com/dtolnay/unicode-ident) - [wit-bindgen-rt 0.39.0](https://github.com/bytecodealliance/wit-bindgen) @@ -2713,7 +2508,7 @@ DEALINGS IN THE SOFTWARE. ### MIT License -- [tokio-macros 2.5.0](https://github.com/tokio-rs/tokio) +- [tokio-macros 2.6.0](https://github.com/tokio-rs/tokio) ``` MIT License @@ -2744,17 +2539,18 @@ SOFTWARE. ### MIT License -- [unienc 0.1.0]( https://crates.io/crates/unienc) -- [unienc_android_mc 0.1.0]( https://crates.io/crates/unienc_android_mc) -- [unienc_apple_vt 0.1.0]( https://crates.io/crates/unienc_apple_vt) -- [unienc_common 0.1.0]( https://crates.io/crates/unienc_common) -- [unienc_ffmpeg 0.1.0]( https://crates.io/crates/unienc_ffmpeg) -- [unienc_windows_mf 0.1.0]( https://crates.io/crates/unienc_windows_mf) +- [unienc 1.4.1]( https://crates.io/crates/unienc) +- [unienc_android_mc 1.4.1]( https://crates.io/crates/unienc_android_mc) +- [unienc_apple_vt 1.4.1]( https://crates.io/crates/unienc_apple_vt) +- [unienc_c 1.4.1]( https://crates.io/crates/unienc_c) +- [unienc_common 1.4.1]( https://crates.io/crates/unienc_common) +- [unienc_ffmpeg 1.4.1]( https://crates.io/crates/unienc_ffmpeg) +- [unienc_windows_mf 1.4.1]( https://crates.io/crates/unienc_windows_mf) - [unity-native-plugin 0.8.0](https://github.com/aosoft/unity-native-plugin-rs) - [unity-native-plugin-sys 0.8.0](https://github.com/aosoft/unity-native-plugin-rs) - [unity-native-plugin-vulkan 0.8.0](https://github.com/aosoft/unity-native-plugin-rs) - [block2 0.6.1](https://github.com/madsmtm/objc2) -- [csbindgen 1.9.3](https://github.com/Cysharp/csbindgen/) +- [csbindgen 1.9.7](https://github.com/Cysharp/csbindgen/) - [objc2-encode 4.1.0](https://github.com/madsmtm/objc2) - [objc2-foundation 0.3.2](https://github.com/madsmtm/objc2) - [objc2 0.6.3](https://github.com/madsmtm/objc2) @@ -2784,7 +2580,7 @@ USE OR OTHER DEALINGS IN THE SOFTWARE. ### MIT License -- [tokio 1.45.1](https://github.com/tokio-rs/tokio) +- [tokio 1.48.0](https://github.com/tokio-rs/tokio) ``` MIT License