Revert "replace video with letsplay_av_ffmpeg"
Unfortunately, I think this was a bit too brash of a move to make for right now.
This reverts commit 0eb2aaa8c1
.
This commit is contained in:
parent
0eb2aaa8c1
commit
fac32bd41d
14 changed files with 1520 additions and 141 deletions
299
server/Cargo.lock
generated
299
server/Cargo.lock
generated
|
@ -4,18 +4,18 @@ version = 3
|
|||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.24.2"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.0"
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
|
@ -28,9 +28,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.89"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
|
||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
|
@ -45,15 +45,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.7"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae"
|
||||
checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
|
@ -89,9 +89,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.5"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
|
||||
checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
|
@ -102,7 +102,7 @@ dependencies = [
|
|||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper 1.0.1",
|
||||
"sync_wrapper 0.1.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
|
@ -110,10 +110,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "axum-macros"
|
||||
version = "0.4.2"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
|
||||
checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
|
@ -121,30 +122,30 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.74"
|
||||
version = "0.3.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
|
||||
checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
version = "0.21.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.69.5"
|
||||
version = "0.69.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
|
||||
checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
|
@ -183,15 +184,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.7.2"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
|
||||
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.30"
|
||||
version = "1.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945"
|
||||
checksum = "e9d013ecb737093c0e86b151a7b837993cf9ec6c502946cfb44bedc392421e0b"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
@ -224,9 +225,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.14"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
|
||||
checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -318,9 +319,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
|
@ -333,9 +334,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
|
@ -343,15 +344,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
|
@ -360,15 +361,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -377,21 +378,21 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.31"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
|
@ -428,9 +429,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
|
||||
|
||||
[[package]]
|
||||
name = "gl"
|
||||
|
@ -464,6 +465,12 @@ version = "0.15.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
|
@ -506,9 +513,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.9.5"
|
||||
version = "1.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
|
||||
checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
|
@ -518,9 +525,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
|||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.5.0"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a"
|
||||
checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
|
@ -537,9 +544,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.9"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b"
|
||||
checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
|
@ -548,7 +555,16 @@ dependencies = [
|
|||
"hyper",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -594,20 +610,6 @@ version = "1.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "letsplay_av_ffmpeg"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cudarc",
|
||||
"ffmpeg-next",
|
||||
"gl",
|
||||
"letsplay_gpu",
|
||||
"libloading",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "letsplay_gpu"
|
||||
version = "0.1.0"
|
||||
|
@ -618,9 +620,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.159"
|
||||
version = "0.2.158"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
|
||||
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
|
@ -683,11 +685,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.0"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
|
||||
checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -734,18 +736,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.5"
|
||||
version = "0.36.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
|
||||
checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
|
@ -782,6 +784,26 @@ version = "2.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.14"
|
||||
|
@ -796,9 +818,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
|||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.31"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
|
@ -811,9 +833,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.88"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
@ -859,18 +881,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.7"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
|
||||
checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.0"
|
||||
version = "1.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
|
||||
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -880,9 +902,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.8"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -891,9 +913,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||
|
||||
[[package]]
|
||||
name = "retro_frontend"
|
||||
|
@ -903,12 +925,19 @@ dependencies = [
|
|||
"libc",
|
||||
"libloading",
|
||||
"libretro-sys",
|
||||
"rgb565",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rgb565"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d43e85498d0bb728f77a88b4313eaf4ed21673f3f8a05c36e835cf6c9c0d066"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
|
@ -923,9 +952,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
|||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.18"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
|
||||
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
|
@ -941,18 +970,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.210"
|
||||
version = "1.0.209"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
|
||||
checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.210"
|
||||
version = "1.0.209"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
|
||||
checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1064,9 +1093,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.79"
|
||||
version = "2.0.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
|
||||
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1087,18 +1116,18 @@ checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.64"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.64"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1115,6 +1144,21 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.40.0"
|
||||
|
@ -1146,9 +1190,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.24.0"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9"
|
||||
checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
|
@ -1192,14 +1236,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.1"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f"
|
||||
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper 0.1.2",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
|
@ -1278,9 +1322,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.24.0"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
|
||||
checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
|
@ -1291,6 +1335,7 @@ dependencies = [
|
|||
"rand",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"url",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
|
@ -1301,10 +1346,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.13"
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
|
@ -1338,10 +1409,10 @@ dependencies = [
|
|||
"async-trait",
|
||||
"axum",
|
||||
"cudarc",
|
||||
"ffmpeg-next",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"gl",
|
||||
"letsplay_av_ffmpeg",
|
||||
"letsplay_gpu",
|
||||
"libloading",
|
||||
"rand",
|
||||
|
|
|
@ -10,7 +10,6 @@ anyhow = "1.0.86"
|
|||
# Libretro Sex
|
||||
letsplay_gpu.path = "/home/lily/source/lets-play/crates/letsplay_gpu"
|
||||
retro_frontend.path = "/home/lily/source/lets-play/crates/retro_frontend"
|
||||
letsplay_av_ffmpeg.path = "/home/lily/source/lets-play/crates/letsplay_av_ffmpeg"
|
||||
gl = "0.14.0"
|
||||
|
||||
# async
|
||||
|
@ -21,7 +20,8 @@ axum = { version = "0.7.5", features = ["ws", "macros"] }
|
|||
futures = "0.3"
|
||||
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
|
||||
|
||||
|
||||
# ffmpeg
|
||||
ffmpeg = { version = "7.0.0", package = "ffmpeg-next" }
|
||||
|
||||
# misc stuff
|
||||
rand = "0.8.5"
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
mod retro_thread;
|
||||
mod surface;
|
||||
mod types;
|
||||
//mod video;
|
||||
|
||||
use letsplay_av_ffmpeg as video;
|
||||
mod video;
|
||||
|
||||
mod transport;
|
||||
|
||||
|
@ -179,7 +177,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
|
||||
let resource = Arc::new(Mutex::new(GraphicsResource::new(&device)));
|
||||
|
||||
let (mut encoder_rx, encoder_tx) = video::encoder_thread::hardware_frame::spawn(
|
||||
let (mut encoder_rx, encoder_tx) = encoder_thread::encoder_thread_spawn_hwframe(
|
||||
&device.clone(),
|
||||
&resource.clone(),
|
||||
&egl_ctx.clone(),
|
||||
|
@ -207,24 +205,13 @@ async fn main() -> anyhow::Result<()> {
|
|||
));
|
||||
*/
|
||||
|
||||
/*
|
||||
let _ = retro_input_event_tx.blocking_send(retro_thread::RetroInEvent::LoadCore(
|
||||
"cores/pcsx2_new_libretro.so".into(),
|
||||
"cores/pcsx2_libretro.so".into(),
|
||||
));
|
||||
let _ = retro_input_event_tx.blocking_send(retro_thread::RetroInEvent::LoadGame(
|
||||
"/data/sda/lily/ISOs/Sony PlayStation 2/Jonny Moseley Mad Trix (USA).bin".into(),
|
||||
"/data/sda/lily/ISOs/Sony PlayStation 2/ztx-hl.bin".into(),
|
||||
));
|
||||
*/
|
||||
|
||||
let _ = retro_input_event_tx.blocking_send(retro_thread::RetroInEvent::LoadCore(
|
||||
"cores/swanstation_libretro.so".into(),
|
||||
));
|
||||
let _ = retro_input_event_tx.blocking_send(retro_thread::RetroInEvent::LoadGame(
|
||||
// "/data/sda/lily/ISOs/Nintendo GameCube/Dave Mirra Freestyle BMX 2 (USA).ciso".into(),
|
||||
// "roms/smb1.nes".into(),
|
||||
"roms/merged/nmv2/jagb/nmv2jagb.cue".into(),
|
||||
));
|
||||
|
||||
// start the libretro thread looping now that we're alive
|
||||
let _ = retro_input_event_tx.blocking_send(retro_thread::RetroInEvent::Start);
|
||||
|
||||
|
|
|
@ -17,9 +17,7 @@ use retro_frontend::{
|
|||
use gpu::egl_helpers::DeviceContext;
|
||||
use letsplay_gpu as gpu;
|
||||
|
||||
use letsplay_av_ffmpeg::types::*;
|
||||
|
||||
use crate::{surface::Surface, video::cuda_gl::safe::GraphicsResource};
|
||||
use crate::{surface::Surface, types::Size, video::cuda_gl::safe::GraphicsResource};
|
||||
|
||||
/// Called by OpenGL. We use this to dump errors.
|
||||
extern "system" fn opengl_message_callback(
|
||||
|
@ -185,19 +183,19 @@ impl RetroState {
|
|||
let size = framebuffer.size.clone();
|
||||
let buffer = framebuffer.get_buffer();
|
||||
|
||||
//let has_disconnected_pitch = pitch != size.width as u32;
|
||||
let has_disconnected_pitch = pitch != size.width as u32;
|
||||
|
||||
for _y in 0..size.height {
|
||||
// Trick to flip the buffer the way OpenGL likes.
|
||||
let y = (size.height - 1) - _y;
|
||||
|
||||
let src_line_off = (y as u32 * pitch) as usize;
|
||||
let dest_line_off = (_y as u32 * size.width) as usize;
|
||||
let mut dest_line_off = (_y as u32 * size.width) as usize;
|
||||
|
||||
// copy only
|
||||
//if has_disconnected_pitch {
|
||||
if has_disconnected_pitch {
|
||||
//dest_line_off = (y * pitch) as usize;
|
||||
//}
|
||||
}
|
||||
|
||||
// Create slices repressenting each part
|
||||
let src_slice = &slice[src_line_off..src_line_off + size.width as usize];
|
||||
|
@ -219,7 +217,7 @@ impl FrontendInterface for RetroState {
|
|||
self.get_frontend().set_gl_fbo(raw);
|
||||
|
||||
if !self.gl_rendering {
|
||||
self.software_framebuffer.resize(crate::types::Size { width, height });
|
||||
self.software_framebuffer.resize(Size { width, height });
|
||||
}
|
||||
|
||||
// register the FBO's texture to our cuda interop resource
|
||||
|
|
27
server/src/video/cuda_gl/bindgen.sh
Executable file
27
server/src/video/cuda_gl/bindgen.sh
Executable file
|
@ -0,0 +1,27 @@
|
|||
#!/bin/bash
|
||||
# Does bindgen for CUDA cudaGL. (needs postprocessing)
|
||||
set -exu
|
||||
|
||||
# --allowlist-type="^CU.*" \
|
||||
# --allowlist-type="^cuuint(32|64)_t" \
|
||||
# --allowlist-type="^cudaError_enum" \
|
||||
# --allowlist-type="^cu.*Complex$" \
|
||||
# --allowlist-type="^cuda.*" \
|
||||
# --allowlist-type="^libraryPropertyType.*" \
|
||||
# --allowlist-var="^CU.*" \
|
||||
|
||||
echo "use cudarc::sys::*; /* Hack :3 */" > ./sys.rs
|
||||
|
||||
bindgen \
|
||||
--allowlist-type="" \
|
||||
--allowlist-function="^cuGraphicsGL.*" \
|
||||
--default-enum-style=rust \
|
||||
--no-doc-comments \
|
||||
--with-derive-default \
|
||||
--with-derive-eq \
|
||||
--with-derive-hash \
|
||||
--with-derive-ord \
|
||||
--use-core \
|
||||
--dynamic-loading Lib \
|
||||
gl.h -- -I/opt/cuda/include \
|
||||
>> ./sys.rs
|
1
server/src/video/cuda_gl/gl.h
Normal file
1
server/src/video/cuda_gl/gl.h
Normal file
|
@ -0,0 +1 @@
|
|||
#include "cudaGL.h"
|
15
server/src/video/cuda_gl/mod.rs
Normal file
15
server/src/video/cuda_gl/mod.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
#[allow(non_snake_case)]
|
||||
pub mod sys;
|
||||
use sys::*;
|
||||
|
||||
pub mod safe;
|
||||
|
||||
pub unsafe fn lib() -> &'static Lib {
|
||||
static LIB: std::sync::OnceLock<Lib> = std::sync::OnceLock::new();
|
||||
LIB.get_or_init(|| {
|
||||
if let Ok(lib) = Lib::new(libloading::library_filename("cuda")) {
|
||||
return lib;
|
||||
}
|
||||
panic!("cuda library doesn't exist.");
|
||||
})
|
||||
}
|
151
server/src/video/cuda_gl/safe.rs
Normal file
151
server/src/video/cuda_gl/safe.rs
Normal file
|
@ -0,0 +1,151 @@
|
|||
use cudarc::driver::{result as cuda_result, sys as cuda_sys, CudaDevice};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct MappedGraphicsResource {
|
||||
resource: cuda_sys::CUgraphicsResource,
|
||||
}
|
||||
|
||||
impl MappedGraphicsResource {
|
||||
fn new(resource: cuda_sys::CUgraphicsResource) -> Self {
|
||||
Self { resource }
|
||||
}
|
||||
|
||||
pub fn map(&mut self) -> Result<(), cuda_result::DriverError> {
|
||||
unsafe {
|
||||
cuda_sys::lib()
|
||||
.cuGraphicsMapResources(1, &mut self.resource, std::ptr::null_mut())
|
||||
.result()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unmap(&mut self) -> Result<(), cuda_result::DriverError> {
|
||||
unsafe {
|
||||
cuda_sys::lib()
|
||||
.cuGraphicsUnmapResources(1, &mut self.resource, std::ptr::null_mut())
|
||||
.result()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_mapped_array(&mut self) -> Result<cuda_sys::CUarray, cuda_result::DriverError> {
|
||||
assert!(
|
||||
!self.resource.is_null(),
|
||||
"do not call GraphicsResource::get_mapped_array if no resource is actually registered"
|
||||
);
|
||||
|
||||
let mut array: cuda_sys::CUarray = std::ptr::null_mut();
|
||||
|
||||
unsafe {
|
||||
cuda_sys::lib()
|
||||
.cuGraphicsSubResourceGetMappedArray(&mut array, self.resource, 0, 0)
|
||||
.result()?;
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
pub fn get_device_pointer(
|
||||
&mut self,
|
||||
) -> Result<cuda_sys::CUdeviceptr, cuda_result::DriverError> {
|
||||
assert!(
|
||||
!self.resource.is_null(),
|
||||
"do not call GraphicsResource::get_mapped_array if no resource is actually registered"
|
||||
);
|
||||
|
||||
let mut array: cuda_sys::CUdeviceptr = 0;
|
||||
let mut size: usize = 0;
|
||||
|
||||
unsafe {
|
||||
cuda_sys::lib()
|
||||
.cuGraphicsResourceGetMappedPointer_v2(&mut array, &mut size, self.resource)
|
||||
.result()?;
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MappedGraphicsResource {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.unmap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper over cuGraphicsGL* apis
|
||||
pub struct GraphicsResource {
|
||||
context: Arc<CudaDevice>,
|
||||
resource: cuda_sys::CUgraphicsResource,
|
||||
}
|
||||
|
||||
impl GraphicsResource {
|
||||
pub fn new(device: &Arc<CudaDevice>) -> Self {
|
||||
Self {
|
||||
context: device.clone(),
|
||||
resource: std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn device(&self) -> Arc<CudaDevice> {
|
||||
self.context.clone()
|
||||
}
|
||||
|
||||
/// Maps this resource.
|
||||
pub fn map(&mut self) -> Result<MappedGraphicsResource, cuda_result::DriverError> {
|
||||
let mut res = MappedGraphicsResource::new(self.resource);
|
||||
res.map()?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn register(
|
||||
&mut self,
|
||||
texture_id: gl::types::GLuint,
|
||||
texture_kind: gl::types::GLuint,
|
||||
) -> Result<(), cuda_result::DriverError> {
|
||||
// better to be safe than leak memory? idk.
|
||||
if !self.resource.is_null() {
|
||||
self.unregister()?;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
super::lib()
|
||||
.cuGraphicsGLRegisterImage(&mut self.resource, texture_id, texture_kind, 1)
|
||||
.result()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_registered(&self) -> bool {
|
||||
!self.resource.is_null()
|
||||
}
|
||||
|
||||
pub fn unregister(&mut self) -> Result<(), cuda_result::DriverError> {
|
||||
assert!(
|
||||
!self.resource.is_null(),
|
||||
"do not call if no resource is actually registered"
|
||||
);
|
||||
|
||||
unsafe {
|
||||
cuda_sys::lib()
|
||||
.cuGraphicsUnregisterResource(self.resource)
|
||||
.result()?;
|
||||
}
|
||||
|
||||
self.resource = std::ptr::null_mut();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GraphicsResource {
|
||||
fn drop(&mut self) {
|
||||
if self.is_registered() {
|
||||
let _ = self.unregister();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for GraphicsResource {}
|
73
server/src/video/cuda_gl/sys.rs
Normal file
73
server/src/video/cuda_gl/sys.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use cudarc::driver::sys::*; /* Hack :3 */
|
||||
use gl::types::{GLenum, GLuint};
|
||||
/* automatically generated by rust-bindgen 0.69.4 */
|
||||
|
||||
pub struct Lib {
|
||||
__library: ::libloading::Library,
|
||||
pub cuGraphicsGLRegisterBuffer: Result<
|
||||
unsafe extern "C" fn(
|
||||
pCudaResource: *mut CUgraphicsResource,
|
||||
buffer: GLuint,
|
||||
Flags: ::core::ffi::c_uint,
|
||||
) -> CUresult,
|
||||
::libloading::Error,
|
||||
>,
|
||||
pub cuGraphicsGLRegisterImage: Result<
|
||||
unsafe extern "C" fn(
|
||||
pCudaResource: *mut CUgraphicsResource,
|
||||
image: GLuint,
|
||||
target: GLenum,
|
||||
Flags: ::core::ffi::c_uint,
|
||||
) -> CUresult,
|
||||
::libloading::Error,
|
||||
>,
|
||||
}
|
||||
impl Lib {
|
||||
pub unsafe fn new<P>(path: P) -> Result<Self, ::libloading::Error>
|
||||
where
|
||||
P: AsRef<::std::ffi::OsStr>,
|
||||
{
|
||||
let library = ::libloading::Library::new(path)?;
|
||||
Self::from_library(library)
|
||||
}
|
||||
pub unsafe fn from_library<L>(library: L) -> Result<Self, ::libloading::Error>
|
||||
where
|
||||
L: Into<::libloading::Library>,
|
||||
{
|
||||
let __library = library.into();
|
||||
let cuGraphicsGLRegisterBuffer = __library
|
||||
.get(b"cuGraphicsGLRegisterBuffer\0")
|
||||
.map(|sym| *sym);
|
||||
let cuGraphicsGLRegisterImage = __library
|
||||
.get(b"cuGraphicsGLRegisterImage\0")
|
||||
.map(|sym| *sym);
|
||||
Ok(Lib {
|
||||
__library,
|
||||
cuGraphicsGLRegisterBuffer,
|
||||
cuGraphicsGLRegisterImage,
|
||||
})
|
||||
}
|
||||
pub unsafe fn cuGraphicsGLRegisterBuffer(
|
||||
&self,
|
||||
pCudaResource: *mut CUgraphicsResource,
|
||||
buffer: GLuint,
|
||||
Flags: ::core::ffi::c_uint,
|
||||
) -> CUresult {
|
||||
(self
|
||||
.cuGraphicsGLRegisterBuffer
|
||||
.as_ref()
|
||||
.expect("Expected function, got error."))(pCudaResource, buffer, Flags)
|
||||
}
|
||||
pub unsafe fn cuGraphicsGLRegisterImage(
|
||||
&self,
|
||||
pCudaResource: *mut CUgraphicsResource,
|
||||
image: GLuint,
|
||||
target: GLenum,
|
||||
Flags: ::core::ffi::c_uint,
|
||||
) -> CUresult {
|
||||
(self
|
||||
.cuGraphicsGLRegisterImage
|
||||
.as_ref()
|
||||
.expect("Expected function, got error."))(pCudaResource, image, target, Flags)
|
||||
}
|
||||
}
|
471
server/src/video/encoder_thread.rs
Normal file
471
server/src/video/encoder_thread.rs
Normal file
|
@ -0,0 +1,471 @@
|
|||
use anyhow::Context;
|
||||
use cudarc::{
|
||||
driver::{
|
||||
sys::{CUdeviceptr, CUmemorytype},
|
||||
CudaDevice, CudaSlice, DevicePtr, LaunchAsync,
|
||||
},
|
||||
nvrtc::CompileOptions,
|
||||
};
|
||||
use letsplay_gpu::egl_helpers::DeviceContext;
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::sync::mpsc::{self, error::TryRecvError};
|
||||
|
||||
use super::h264_encoder::H264Encoder;
|
||||
use super::{cuda_gl::safe::GraphicsResource, ffmpeg};
|
||||
|
||||
pub enum EncodeThreadInput {
|
||||
Init { size: crate::types::Size },
|
||||
ForceKeyframe,
|
||||
SendFrame,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum EncodeThreadOutput {
|
||||
Frame { packet: ffmpeg::Packet },
|
||||
}
|
||||
|
||||
struct EncoderState {
|
||||
encoder: Option<H264Encoder>,
|
||||
frame: Arc<Mutex<Option<ffmpeg::frame::Video>>>,
|
||||
packet: ffmpeg::Packet,
|
||||
}
|
||||
|
||||
impl EncoderState {
|
||||
fn new(frame: Arc<Mutex<Option<ffmpeg::frame::Video>>>) -> Self {
|
||||
Self {
|
||||
encoder: None,
|
||||
frame: frame,
|
||||
packet: ffmpeg::Packet::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
fn init(&mut self, size: crate::types::Size) -> anyhow::Result<()> {
|
||||
self.encoder = Some(H264Encoder::new_nvenc_swframe(
|
||||
size.clone(),
|
||||
60,
|
||||
2 * (1024 * 1024),
|
||||
)?);
|
||||
|
||||
// replace packet
|
||||
self.packet = ffmpeg::Packet::empty();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
//fn frame(&mut self) -> Arc<Mutex<Option<ffmpeg::frame::Video>>> {
|
||||
// self.frame.clone()
|
||||
//}
|
||||
|
||||
fn send_frame(&mut self, pts: u64, force_keyframe: bool) -> Option<ffmpeg::Packet> {
|
||||
let mut lk = self.frame.lock().expect("fuck");
|
||||
let frame = lk.as_mut().unwrap();
|
||||
let encoder = self.encoder.as_mut().unwrap();
|
||||
|
||||
// set frame metadata
|
||||
unsafe {
|
||||
if force_keyframe {
|
||||
(*frame.as_mut_ptr()).pict_type = ffmpeg::sys::AVPictureType::AV_PICTURE_TYPE_I;
|
||||
(*frame.as_mut_ptr()).flags = ffmpeg::sys::AV_FRAME_FLAG_KEY;
|
||||
(*frame.as_mut_ptr()).key_frame = 1;
|
||||
} else {
|
||||
(*frame.as_mut_ptr()).pict_type = ffmpeg::sys::AVPictureType::AV_PICTURE_TYPE_NONE;
|
||||
(*frame.as_mut_ptr()).flags = 0i32;
|
||||
(*frame.as_mut_ptr()).key_frame = 0;
|
||||
}
|
||||
|
||||
(*frame.as_mut_ptr()).pts = pts as i64;
|
||||
}
|
||||
|
||||
encoder.send_frame(&*frame);
|
||||
encoder
|
||||
.receive_packet(&mut self.packet)
|
||||
.expect("Failed to recieve packet");
|
||||
|
||||
unsafe {
|
||||
if !self.packet.is_empty() {
|
||||
return Some(self.packet.clone());
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
struct EncoderStateHW {
|
||||
encoder: Option<H264Encoder>,
|
||||
frame: ffmpeg::frame::Video,
|
||||
packet: ffmpeg::Packet,
|
||||
}
|
||||
|
||||
impl EncoderStateHW {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
encoder: None,
|
||||
frame: ffmpeg::frame::Video::empty(),
|
||||
packet: ffmpeg::Packet::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
fn init(&mut self, device: &Arc<CudaDevice>, size: crate::types::Size) -> anyhow::Result<()> {
|
||||
self.encoder = Some(H264Encoder::new_nvenc_hwframe(
|
||||
&device,
|
||||
size.clone(),
|
||||
60,
|
||||
2 * (1024 * 1024),
|
||||
)?);
|
||||
|
||||
// replace packet
|
||||
self.packet = ffmpeg::Packet::empty();
|
||||
self.frame = self.encoder.as_mut().unwrap().create_frame()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn frame(&mut self) -> &mut ffmpeg::frame::Video {
|
||||
&mut self.frame
|
||||
}
|
||||
|
||||
fn send_frame(&mut self, pts: u64, force_keyframe: bool) -> Option<ffmpeg::Packet> {
|
||||
let frame = &mut self.frame;
|
||||
let encoder = self.encoder.as_mut().unwrap();
|
||||
|
||||
// set frame metadata
|
||||
unsafe {
|
||||
if force_keyframe {
|
||||
(*frame.as_mut_ptr()).pict_type = ffmpeg::sys::AVPictureType::AV_PICTURE_TYPE_I;
|
||||
(*frame.as_mut_ptr()).flags = ffmpeg::sys::AV_FRAME_FLAG_KEY;
|
||||
(*frame.as_mut_ptr()).key_frame = 1;
|
||||
} else {
|
||||
(*frame.as_mut_ptr()).pict_type = ffmpeg::sys::AVPictureType::AV_PICTURE_TYPE_NONE;
|
||||
(*frame.as_mut_ptr()).flags = 0i32;
|
||||
(*frame.as_mut_ptr()).key_frame = 0;
|
||||
}
|
||||
|
||||
(*frame.as_mut_ptr()).pts = pts as i64;
|
||||
}
|
||||
|
||||
encoder.send_frame(&*frame);
|
||||
encoder
|
||||
.receive_packet(&mut self.packet)
|
||||
.expect("Failed to recieve packet");
|
||||
|
||||
unsafe {
|
||||
if !self.packet.is_empty() {
|
||||
return Some(self.packet.clone());
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
fn encoder_thread_swframe_main(
|
||||
mut rx: mpsc::Receiver<EncodeThreadInput>,
|
||||
tx: mpsc::Sender<EncodeThreadOutput>,
|
||||
frame: &Arc<Mutex<Option<ffmpeg::frame::Video>>>,
|
||||
) -> anyhow::Result<()> {
|
||||
// FIXME: for HW frame support
|
||||
//let dev = cudarc::driver::CudaDevice::new(0)?;
|
||||
|
||||
let mut frame_number = 0u64;
|
||||
let mut force_keyframe = false;
|
||||
|
||||
let mut encoder = EncoderState::new(frame.clone());
|
||||
|
||||
loop {
|
||||
match rx.try_recv() {
|
||||
Ok(msg) => match msg {
|
||||
EncodeThreadInput::Init { size } => {
|
||||
frame_number = 0;
|
||||
|
||||
if force_keyframe {
|
||||
force_keyframe = false;
|
||||
}
|
||||
|
||||
encoder.init(size).expect("encoder init failed");
|
||||
}
|
||||
|
||||
EncodeThreadInput::ForceKeyframe => {
|
||||
force_keyframe = true;
|
||||
}
|
||||
|
||||
EncodeThreadInput::SendFrame => {
|
||||
if let Some(pkt) = encoder.send_frame(frame_number as u64, force_keyframe) {
|
||||
// A bit less clear than ::empty(), but it's "Safe"
|
||||
if let Some(_) = pkt.data() {
|
||||
let _ = tx.blocking_send(EncodeThreadOutput::Frame {
|
||||
packet: pkt.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
frame_number += 1;
|
||||
}
|
||||
|
||||
if force_keyframe {
|
||||
force_keyframe = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Err(TryRecvError::Disconnected) => break,
|
||||
Err(TryRecvError::Empty) => {
|
||||
std::thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn encoder_thread_spawn_swframe(
|
||||
frame: &Arc<Mutex<Option<ffmpeg::frame::Video>>>,
|
||||
) -> (
|
||||
mpsc::Receiver<EncodeThreadOutput>,
|
||||
mpsc::Sender<EncodeThreadInput>,
|
||||
) {
|
||||
let (in_tx, in_rx) = mpsc::channel(1);
|
||||
let (out_tx, out_rx) = mpsc::channel(1);
|
||||
|
||||
let clone = Arc::clone(frame);
|
||||
|
||||
std::thread::spawn(move || encoder_thread_swframe_main(in_rx, out_tx, &clone));
|
||||
|
||||
(out_rx, in_tx)
|
||||
}
|
||||
|
||||
/// Source for the kernel used to flip OpenGL framebuffers right-side up.
|
||||
const OPENGL_FLIP_KERNEL_SRC: &str = "
|
||||
extern \"C\" __global__ void flip_opengl(
|
||||
const unsigned* pSrc,
|
||||
unsigned* pDest,
|
||||
int width,
|
||||
int height
|
||||
) {
|
||||
const unsigned x = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
const unsigned y = blockIdx.y * blockDim.y + threadIdx.y;
|
||||
|
||||
if (x < width && y < height) {
|
||||
unsigned reversed_y = (height - 1) - y;
|
||||
((unsigned*)pDest)[y * width + x] = ((unsigned*)pSrc)[reversed_y * width + x];
|
||||
}
|
||||
}";
|
||||
|
||||
fn encoder_thread_hwframe_main(
|
||||
mut rx: mpsc::Receiver<EncodeThreadInput>,
|
||||
tx: mpsc::Sender<EncodeThreadOutput>,
|
||||
|
||||
cuda_device: &Arc<CudaDevice>,
|
||||
cuda_resource: &Arc<Mutex<GraphicsResource>>,
|
||||
gl_context: &Arc<Mutex<DeviceContext>>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut frame_number = 0u64;
|
||||
let mut force_keyframe = false;
|
||||
|
||||
let mut encoder = EncoderStateHW::new();
|
||||
|
||||
// :)
|
||||
cuda_device.bind_to_thread()?;
|
||||
|
||||
// Compile the support kernel
|
||||
let ptx = cudarc::nvrtc::compile_ptx_with_opts(
|
||||
&OPENGL_FLIP_KERNEL_SRC,
|
||||
CompileOptions {
|
||||
//options: vec!["--gpu-architecture=compute_50".into()],
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.with_context(|| "compiling support kernel")?;
|
||||
|
||||
// pop it in
|
||||
cuda_device.load_ptx(ptx, "module", &["flip_opengl"])?;
|
||||
|
||||
let mut memcpy = cudarc::driver::sys::CUDA_MEMCPY2D_st::default();
|
||||
|
||||
// setup the things that won't change about the cuda memcpy
|
||||
|
||||
// src
|
||||
memcpy.srcXInBytes = 0;
|
||||
memcpy.srcY = 0;
|
||||
memcpy.srcMemoryType = CUmemorytype::CU_MEMORYTYPE_ARRAY;
|
||||
|
||||
// dest
|
||||
memcpy.dstXInBytes = 0;
|
||||
memcpy.dstY = 0;
|
||||
memcpy.dstMemoryType = CUmemorytype::CU_MEMORYTYPE_DEVICE;
|
||||
|
||||
// Temporary buffer used for opengl flip on the GPU. We copy to this buffer,
|
||||
// then copy the flipped version (using the launched support kernel) to the CUDA device memory ffmpeg
|
||||
// allocated.
|
||||
let mut temp_buffer: CudaSlice<u32> = cuda_device.alloc_zeros::<u32>(48).expect("over");
|
||||
|
||||
loop {
|
||||
match rx.blocking_recv() {
|
||||
Some(msg) => match msg {
|
||||
EncodeThreadInput::Init { size } => {
|
||||
frame_number = 0;
|
||||
|
||||
if force_keyframe {
|
||||
force_keyframe = false;
|
||||
}
|
||||
|
||||
temp_buffer = cuda_device
|
||||
.alloc_zeros::<u32>((size.width * size.height) as usize)
|
||||
.expect("oh youre fucked anyways");
|
||||
|
||||
encoder
|
||||
.init(cuda_device, size)
|
||||
.expect("encoder init failed");
|
||||
}
|
||||
|
||||
EncodeThreadInput::ForceKeyframe => {
|
||||
force_keyframe = true;
|
||||
}
|
||||
|
||||
EncodeThreadInput::SendFrame => {
|
||||
// benchmarking
|
||||
//use std::time::Instant;
|
||||
//let start = Instant::now();
|
||||
|
||||
// copy gl frame *ON THE GPU* to ffmpeg frame
|
||||
{
|
||||
let gl_ctx = gl_context.lock().expect("you dumb fuck");
|
||||
let mut gl_resource =
|
||||
cuda_resource.lock().expect("couldnt lock GL resource!");
|
||||
|
||||
gl_ctx.make_current();
|
||||
|
||||
let mut mapped = gl_resource
|
||||
.map()
|
||||
.expect("couldnt map graphics resource. Its joever");
|
||||
|
||||
let array = mapped
|
||||
.get_mapped_array()
|
||||
.expect("well its all over anyways");
|
||||
|
||||
let frame = encoder.frame();
|
||||
|
||||
// setup the cuMemcpy2D operation to copy to the temporary buffer
|
||||
// (we should probably abstract source and provide a way to elide this,
|
||||
// and instead feed ffmpeg directly. for now it's *just* used with gl so /shrug)
|
||||
{
|
||||
memcpy.srcArray = array;
|
||||
|
||||
unsafe {
|
||||
let frame_ptr = frame.as_mut_ptr();
|
||||
memcpy.dstDevice = temp_buffer.device_ptr().clone();
|
||||
memcpy.dstPitch = (*frame_ptr).linesize[0] as usize;
|
||||
memcpy.WidthInBytes = ((*frame_ptr).width * 4) as usize;
|
||||
memcpy.Height = (*frame_ptr).height as usize;
|
||||
}
|
||||
}
|
||||
|
||||
// copy to the temporary buffer and synchronize
|
||||
unsafe {
|
||||
cudarc::driver::sys::lib()
|
||||
.cuMemcpy2DAsync_v2(&memcpy, std::ptr::null_mut())
|
||||
.result()
|
||||
.expect("cuMemcpy2D fail epic");
|
||||
|
||||
cudarc::driver::sys::lib()
|
||||
.cuStreamSynchronize(std::ptr::null_mut())
|
||||
.result()?;
|
||||
}
|
||||
|
||||
// launch kernel to flip the opengl framebuffer right-side up
|
||||
{
|
||||
let width = frame.width();
|
||||
let height = frame.height();
|
||||
|
||||
let launch_config = cudarc::driver::LaunchConfig {
|
||||
grid_dim: (width / 16 + 1, height / 2 + 1, 1),
|
||||
block_dim: (16, 2, 1),
|
||||
shared_mem_bytes: 0,
|
||||
};
|
||||
|
||||
let flip_opengl = cuda_device.get_func("module", "flip_opengl").expect(
|
||||
"for some reason we couldn't get the support kenrel function",
|
||||
);
|
||||
|
||||
unsafe {
|
||||
let frame_ptr = frame.as_mut_ptr();
|
||||
|
||||
let mut slice = cuda_device.upgrade_device_ptr::<u32>(
|
||||
(*frame_ptr).data[0] as CUdeviceptr,
|
||||
(width * height) as usize * 4usize,
|
||||
);
|
||||
|
||||
flip_opengl.launch(
|
||||
launch_config,
|
||||
(&mut temp_buffer, &mut slice, width, height),
|
||||
)?;
|
||||
|
||||
// leak so it doesn't free the memory
|
||||
// (the device pointer we convert into a slice is owned by ffmpeg, so we shouldn't be the ones
|
||||
// trying to free it!)
|
||||
let _ = slice.leak();
|
||||
|
||||
// Synchronize for the final time
|
||||
cudarc::driver::sys::lib()
|
||||
.cuStreamSynchronize(std::ptr::null_mut())
|
||||
.result()?;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: ideally this would work on-drop but it doesn't.
|
||||
mapped.unmap().expect("fuck you asshole");
|
||||
gl_ctx.release();
|
||||
}
|
||||
|
||||
if let Some(pkt) = encoder.send_frame(frame_number as u64, force_keyframe) {
|
||||
// A bit less clear than ::empty(), but it's "Safe"
|
||||
if let Some(_) = pkt.data() {
|
||||
let _ = tx.blocking_send(EncodeThreadOutput::Frame {
|
||||
packet: pkt.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
frame_number += 1;
|
||||
}
|
||||
|
||||
if force_keyframe {
|
||||
force_keyframe = false;
|
||||
}
|
||||
|
||||
//tracing::info!("encoding frame {frame_number} took {:2?}", start.elapsed());
|
||||
}
|
||||
},
|
||||
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
//std::thread::sleep(Duration::from_millis(1));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn encoder_thread_spawn_hwframe(
|
||||
cuda_device: &Arc<CudaDevice>,
|
||||
cuda_resource: &Arc<Mutex<GraphicsResource>>,
|
||||
gl_context: &Arc<Mutex<DeviceContext>>,
|
||||
) -> (
|
||||
mpsc::Receiver<EncodeThreadOutput>,
|
||||
mpsc::Sender<EncodeThreadInput>,
|
||||
) {
|
||||
let (in_tx, in_rx) = mpsc::channel(1);
|
||||
let (out_tx, out_rx) = mpsc::channel(1);
|
||||
|
||||
let dev_clone = Arc::clone(cuda_device);
|
||||
let rsrc_clone = Arc::clone(cuda_resource);
|
||||
let gl_clone = Arc::clone(gl_context);
|
||||
|
||||
std::thread::spawn(move || {
|
||||
encoder_thread_hwframe_main(in_rx, out_tx, &dev_clone, &rsrc_clone, &gl_clone)
|
||||
});
|
||||
|
||||
(out_rx, in_tx)
|
||||
}
|
354
server/src/video/h264_encoder.rs
Normal file
354
server/src/video/h264_encoder.rs
Normal file
|
@ -0,0 +1,354 @@
|
|||
use super::ffmpeg;
|
||||
use super::hwframe::HwFrameContext;
|
||||
use anyhow::Context;
|
||||
use cudarc::driver::CudaDevice;
|
||||
use ffmpeg::error::EAGAIN;
|
||||
|
||||
use ffmpeg::codec as lavc; // lavc
|
||||
|
||||
use crate::types::Size;
|
||||
|
||||
/// this is required for libx264 to like. Work
|
||||
pub fn create_context_from_codec(codec: ffmpeg::Codec) -> Result<lavc::Context, ffmpeg::Error> {
|
||||
unsafe {
|
||||
let context = ffmpeg::sys::avcodec_alloc_context3(codec.as_ptr());
|
||||
if context.is_null() {
|
||||
return Err(ffmpeg::Error::Unknown);
|
||||
}
|
||||
|
||||
let context = lavc::Context::wrap(context, None);
|
||||
Ok(context)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_context_and_set_common_parameters(
|
||||
codec: &str,
|
||||
size: &Size,
|
||||
max_framerate: u32,
|
||||
bitrate: usize,
|
||||
) -> anyhow::Result<(ffmpeg::Codec, ffmpeg::encoder::video::Video)> {
|
||||
let encoder = ffmpeg::encoder::find_by_name(codec)
|
||||
.expect(&format!("could not find the codec \"{codec}\""));
|
||||
|
||||
let mut video_encoder_context = create_context_from_codec(encoder)?.encoder().video()?;
|
||||
|
||||
video_encoder_context.set_width(size.width);
|
||||
video_encoder_context.set_height(size.height);
|
||||
video_encoder_context.set_frame_rate(Some(ffmpeg::Rational(1, max_framerate as i32)));
|
||||
|
||||
video_encoder_context.set_bit_rate(bitrate / 4);
|
||||
video_encoder_context.set_max_bit_rate(bitrate);
|
||||
|
||||
// qp TODO:
|
||||
//video_encoder_context.set_qmax(30);
|
||||
//video_encoder_context.set_qmin(35);
|
||||
|
||||
video_encoder_context.set_time_base(ffmpeg::Rational(1, max_framerate as i32).invert());
|
||||
video_encoder_context.set_format(ffmpeg::format::Pixel::YUV420P);
|
||||
|
||||
// The GOP here is setup to balance keyframe retransmission with bandwidth.
|
||||
//video_encoder_context.set_gop((max_framerate * 4) as u32);
|
||||
video_encoder_context.set_gop(i32::MAX as u32);
|
||||
video_encoder_context.set_max_b_frames(0);
|
||||
|
||||
unsafe {
|
||||
(*video_encoder_context.as_mut_ptr()).delay = 0;
|
||||
(*video_encoder_context.as_mut_ptr()).refs = 0;
|
||||
}
|
||||
|
||||
Ok((encoder, video_encoder_context))
|
||||
}
|
||||
|
||||
/// A simple H.264 encoder. Currently software only, however
|
||||
/// pieces are being put in place to eventually allow HW encoding.
|
||||
pub enum H264Encoder {
|
||||
Software {
|
||||
encoder: ffmpeg::encoder::video::Encoder,
|
||||
},
|
||||
|
||||
/// Hardware encoding, with frames uploaded to the GPU by ffmpeg.
|
||||
NvencSWFrame {
|
||||
encoder: ffmpeg::encoder::video::Encoder,
|
||||
},
|
||||
|
||||
/// Hardware encoding, with frames already on the GPU.
|
||||
NvencHWFrame {
|
||||
encoder: ffmpeg::encoder::video::Encoder,
|
||||
hw_context: HwFrameContext,
|
||||
},
|
||||
}
|
||||
|
||||
impl H264Encoder {
|
||||
/// Creates a new software encoder.
|
||||
pub fn new_software(size: Size, max_framerate: u32, bitrate: usize) -> anyhow::Result<Self> {
|
||||
// Create the libx264 context
|
||||
let (encoder, mut video_encoder_context) =
|
||||
create_context_and_set_common_parameters("libx264", &size, max_framerate, bitrate)?;
|
||||
|
||||
video_encoder_context.set_format(ffmpeg::format::Pixel::YUV420P);
|
||||
|
||||
let threads = std::thread::available_parallelism().expect("ggg").get() / 8;
|
||||
|
||||
// FIXME: tracing please.
|
||||
println!("H264Encoder::new_software(): Using {threads} threads to encode");
|
||||
|
||||
// Frame-level threading causes [N] frames of latency
|
||||
// so we use slice-level threading to reduce the latency
|
||||
// as much as possible while still allowing threading
|
||||
video_encoder_context.set_threading(ffmpeg::threading::Config {
|
||||
kind: ffmpeg::threading::Type::Slice,
|
||||
count: threads,
|
||||
});
|
||||
|
||||
// Set libx264 applicable dictionary options
|
||||
let mut dict = ffmpeg::Dictionary::new();
|
||||
dict.set("tune", "zerolatency");
|
||||
dict.set("preset", "veryfast");
|
||||
|
||||
// This could probably be moved but then it would mean returning the dictionary too
|
||||
// which is fine I guess it just seems a bit rickity
|
||||
dict.set("profile", "main");
|
||||
|
||||
// TODO:
|
||||
dict.set("crf", "43");
|
||||
dict.set("crf_max", "48");
|
||||
|
||||
dict.set("forced-idr", "1");
|
||||
|
||||
let encoder = video_encoder_context
|
||||
.open_as_with(encoder, dict)
|
||||
.with_context(|| "While opening x264 video codec")?;
|
||||
|
||||
Ok(Self::Software { encoder: encoder })
|
||||
}
|
||||
|
||||
/// Creates a new hardware (NVIDIA NVENC) encoder, which encodes
|
||||
/// frames from software input. FFmpeg handles uploading frames to the GPU.
|
||||
pub fn new_nvenc_swframe(
|
||||
size: Size,
|
||||
max_framerate: u32,
|
||||
bitrate: usize,
|
||||
) -> anyhow::Result<Self> {
|
||||
let (encoder, mut video_encoder_context) =
|
||||
create_context_and_set_common_parameters("h264_nvenc", &size, max_framerate, bitrate)
|
||||
.with_context(|| "while trying to create encoder")?;
|
||||
|
||||
video_encoder_context.set_format(ffmpeg::format::Pixel::ZRGB32);
|
||||
|
||||
video_encoder_context.set_qmin(37);
|
||||
video_encoder_context.set_qmax(33);
|
||||
|
||||
// set h264_nvenc options
|
||||
let mut dict = ffmpeg::Dictionary::new();
|
||||
|
||||
dict.set("tune", "ull");
|
||||
dict.set("preset", "p1");
|
||||
|
||||
dict.set("profile", "main");
|
||||
|
||||
// TODO:
|
||||
dict.set("rc", "vbr");
|
||||
dict.set("qp", "35");
|
||||
|
||||
dict.set("forced-idr", "1");
|
||||
|
||||
// damn you
|
||||
dict.set("delay", "0");
|
||||
dict.set("zerolatency", "1");
|
||||
|
||||
let encoder = video_encoder_context
|
||||
.open_as_with(encoder, dict)
|
||||
.with_context(|| "While opening h264_nvenc video codec")?;
|
||||
|
||||
Ok(Self::NvencSWFrame { encoder: encoder })
|
||||
}
|
||||
|
||||
/// Creates a new hardware (NVIDIA NVENC) encoder, which encodes
|
||||
/// frames from GPU memory, via CUDA.
|
||||
/// FFmpeg handles uploading frames to the GPU.
|
||||
/// You are expected to handle uploading or otherwise working with a frame on the GPU.
|
||||
pub fn new_nvenc_hwframe(
|
||||
cuda_device: &CudaDevice,
|
||||
size: Size,
|
||||
max_framerate: u32,
|
||||
bitrate: usize,
|
||||
) -> anyhow::Result<Self> {
|
||||
let cuda_device_context = super::hwdevice::CudaDeviceContextBuilder::new()?
|
||||
.set_cuda_context((*cuda_device.cu_primary_ctx()) as *mut _)
|
||||
.build()
|
||||
.with_context(|| "while trying to create CUDA device context")?;
|
||||
|
||||
let mut hw_frame_context = super::hwframe::HwFrameContextBuilder::new(cuda_device_context)?
|
||||
.set_width(size.width)
|
||||
.set_height(size.height)
|
||||
.set_sw_format(ffmpeg::format::Pixel::ZBGR32)
|
||||
.set_format(ffmpeg::format::Pixel::CUDA)
|
||||
.build()
|
||||
.with_context(|| "while trying to create CUDA frame context")?;
|
||||
|
||||
let (encoder, mut video_encoder_context) =
|
||||
create_context_and_set_common_parameters("h264_nvenc", &size, max_framerate, bitrate)
|
||||
.with_context(|| "while trying to create encoder")?;
|
||||
|
||||
video_encoder_context.set_format(ffmpeg::format::Pixel::CUDA);
|
||||
|
||||
video_encoder_context.set_qmin(35);
|
||||
video_encoder_context.set_qmax(38);
|
||||
|
||||
unsafe {
|
||||
// FIXME: this currently breaks the avbufferref system a bit
|
||||
(*video_encoder_context.as_mut_ptr()).hw_frames_ctx =
|
||||
ffmpeg::sys::av_buffer_ref(hw_frame_context.as_raw_mut());
|
||||
(*video_encoder_context.as_mut_ptr()).hw_device_ctx =
|
||||
ffmpeg::sys::av_buffer_ref(hw_frame_context.as_device_context_mut());
|
||||
}
|
||||
|
||||
// set h264_nvenc options
|
||||
let mut dict = ffmpeg::Dictionary::new();
|
||||
|
||||
dict.set("tune", "ull");
|
||||
dict.set("preset", "p1");
|
||||
|
||||
dict.set("profile", "main");
|
||||
|
||||
// TODO:
|
||||
dict.set("rc", "vbr");
|
||||
dict.set("qp", "35");
|
||||
|
||||
dict.set("forced-idr", "1");
|
||||
|
||||
// damn you
|
||||
dict.set("delay", "0");
|
||||
dict.set("zerolatency", "1");
|
||||
|
||||
let encoder = video_encoder_context
|
||||
.open_as_with(encoder, dict)
|
||||
.with_context(|| "While opening h264_nvenc video codec")?;
|
||||
|
||||
Ok(Self::NvencHWFrame {
|
||||
encoder: encoder,
|
||||
hw_context: hw_frame_context,
|
||||
})
|
||||
}
|
||||
|
||||
// NOTE: It's a bit pointless to have this have a mut borrow,
|
||||
// but you'll probably have a mutable borrow on this already..
|
||||
pub fn is_hardware(&mut self) -> bool {
|
||||
match self {
|
||||
Self::Software { .. } => false,
|
||||
Self::NvencSWFrame { .. } => true,
|
||||
Self::NvencHWFrame { .. } => true,
|
||||
}
|
||||
}
|
||||
|
||||
//pub fn get_hw_context(&mut self) -> &mut HwFrameContext {
|
||||
// match self {
|
||||
// Self::Nvenc { encoder: _, hw_context } => hw_context,
|
||||
// _ => panic!("should not use H264Encoder::get_hw_context() on a Software encoder")
|
||||
// }
|
||||
//}
|
||||
|
||||
pub fn create_frame(&mut self) -> anyhow::Result<ffmpeg::frame::Video> {
|
||||
match self {
|
||||
Self::Software { encoder } | Self::NvencSWFrame { encoder } => {
|
||||
return Ok(ffmpeg::frame::Video::new(
|
||||
encoder.format(),
|
||||
encoder.width(),
|
||||
encoder.height(),
|
||||
));
|
||||
}
|
||||
|
||||
Self::NvencHWFrame {
|
||||
encoder,
|
||||
hw_context,
|
||||
} => {
|
||||
let mut frame = ffmpeg::frame::Video::empty();
|
||||
|
||||
unsafe {
|
||||
(*frame.as_mut_ptr()).format = ffmpeg::format::Pixel::CUDA as i32;
|
||||
(*frame.as_mut_ptr()).width = encoder.width() as i32;
|
||||
(*frame.as_mut_ptr()).height = encoder.height() as i32;
|
||||
(*frame.as_mut_ptr()).hw_frames_ctx = hw_context.as_raw_mut();
|
||||
|
||||
hw_context.get_buffer(&mut frame)?;
|
||||
|
||||
(*frame.as_mut_ptr()).linesize[0] = (*frame.as_ptr()).width * 4;
|
||||
|
||||
return Ok(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_frame(&mut self, frame: &ffmpeg::Frame) {
|
||||
match self {
|
||||
Self::Software { encoder } => {
|
||||
encoder.send_frame(frame).unwrap();
|
||||
}
|
||||
|
||||
Self::NvencSWFrame { encoder } => {
|
||||
encoder.send_frame(frame).unwrap();
|
||||
}
|
||||
|
||||
Self::NvencHWFrame {
|
||||
encoder,
|
||||
hw_context: _,
|
||||
} => {
|
||||
encoder.send_frame(frame).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_eof(&mut self) {
|
||||
match self {
|
||||
Self::Software { encoder } => {
|
||||
encoder.send_eof().unwrap();
|
||||
}
|
||||
|
||||
Self::NvencSWFrame { encoder } => {
|
||||
// Realistically this should be the same right?
|
||||
encoder.send_eof().unwrap();
|
||||
// todo!("Requires support.");
|
||||
}
|
||||
|
||||
Self::NvencHWFrame {
|
||||
encoder,
|
||||
hw_context: _,
|
||||
} => {
|
||||
encoder.send_eof().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn receive_packet_impl(&mut self, packet: &mut ffmpeg::Packet) -> Result<(), ffmpeg::Error> {
|
||||
return match self {
|
||||
Self::Software { encoder } => encoder.receive_packet(packet),
|
||||
Self::NvencSWFrame { encoder } => encoder.receive_packet(packet),
|
||||
Self::NvencHWFrame {
|
||||
encoder,
|
||||
hw_context: _,
|
||||
} => encoder.receive_packet(packet),
|
||||
};
|
||||
}
|
||||
|
||||
// Shuold this return a Result<ControlFlow> so we can make it easier to know when to continue?
|
||||
pub fn receive_packet(&mut self, packet: &mut ffmpeg::Packet) -> anyhow::Result<()> {
|
||||
loop {
|
||||
match self.receive_packet_impl(packet) {
|
||||
Ok(_) => break,
|
||||
Err(ffmpeg::Error::Other { errno }) => {
|
||||
if errno != EAGAIN {
|
||||
return Err(ffmpeg::Error::Other { errno: errno }.into());
|
||||
} else {
|
||||
// EAGAIN is not fatal, and simply means
|
||||
// we should just try again
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
88
server/src/video/hwdevice.rs
Normal file
88
server/src/video/hwdevice.rs
Normal file
|
@ -0,0 +1,88 @@
|
|||
use std::ptr::null_mut;
|
||||
|
||||
use super::check_ret;
|
||||
|
||||
use super::ffmpeg;
|
||||
|
||||
pub struct CudaDeviceContext {
|
||||
buffer: *mut ffmpeg::sys::AVBufferRef,
|
||||
}
|
||||
|
||||
impl CudaDeviceContext {
|
||||
fn new(buffer: *mut ffmpeg::sys::AVBufferRef) -> Self {
|
||||
Self { buffer }
|
||||
}
|
||||
|
||||
// pub fn as_device_mut(&mut self) -> &mut ffmpeg::sys::AVHWDeviceContext {
|
||||
// unsafe { &mut *((*self.buffer).data as *mut ffmpeg::sys::AVHWDeviceContext) }
|
||||
// }
|
||||
|
||||
// pub fn as_device(&self) -> &ffmpeg::sys::AVHWDeviceContext {
|
||||
// unsafe { &*((*self.buffer).data as *const ffmpeg::sys::AVHWDeviceContext) }
|
||||
// }
|
||||
|
||||
pub fn as_raw_mut(&mut self) -> &mut ffmpeg::sys::AVBufferRef {
|
||||
unsafe { &mut *self.buffer }
|
||||
}
|
||||
|
||||
// pub fn as_raw(&self) -> &ffmpeg::sys::AVBufferRef {
|
||||
// unsafe { &*self.buffer }
|
||||
// }
|
||||
}
|
||||
|
||||
impl Drop for CudaDeviceContext {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if !self.buffer.is_null() {
|
||||
ffmpeg::sys::av_buffer_unref(&mut self.buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CudaDeviceContextBuilder {
|
||||
buffer: *mut ffmpeg::sys::AVBufferRef,
|
||||
}
|
||||
|
||||
impl CudaDeviceContextBuilder {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let buffer = unsafe { ffmpeg::sys::av_hwdevice_ctx_alloc(ffmpeg::sys::AVHWDeviceType::AV_HWDEVICE_TYPE_CUDA) };
|
||||
if buffer.is_null() {
|
||||
return Err(anyhow::anyhow!("could not allocate a hwdevice".to_string()));
|
||||
}
|
||||
|
||||
Ok(Self { buffer })
|
||||
}
|
||||
|
||||
pub fn build(mut self) -> Result<CudaDeviceContext, ffmpeg::Error> {
|
||||
check_ret(unsafe { ffmpeg::sys::av_hwdevice_ctx_init(self.buffer) })?;
|
||||
let result = Ok(CudaDeviceContext::new(self.buffer));
|
||||
self.buffer = null_mut();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn set_cuda_context(mut self, context: ffmpeg::sys::CUcontext) -> Self {
|
||||
unsafe {
|
||||
(*(self.as_device_mut().hwctx as *mut ffmpeg::sys::AVCUDADeviceContext)).cuda_ctx = context;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn as_device_mut(&mut self) -> &mut ffmpeg::sys::AVHWDeviceContext {
|
||||
unsafe { &mut *((*self.buffer).data as *mut ffmpeg::sys::AVHWDeviceContext) }
|
||||
}
|
||||
|
||||
// pub fn as_device(&self) -> &ffmpeg::sys::AVHWDeviceContext {
|
||||
// unsafe { &*((*self.buffer).data as *const ffmpeg::sys::AVHWDeviceContext) }
|
||||
// }
|
||||
|
||||
// pub fn as_raw_mut(&mut self) -> &mut ffmpeg::sys::AVBufferRef {
|
||||
// unsafe { &mut *self.buffer }
|
||||
// }
|
||||
|
||||
// pub fn as_raw(&self) -> &ffmpeg::sys::AVBufferRef {
|
||||
// unsafe { &*self.buffer }
|
||||
// }
|
||||
}
|
121
server/src/video/hwframe.rs
Normal file
121
server/src/video/hwframe.rs
Normal file
|
@ -0,0 +1,121 @@
|
|||
use std::ptr::null_mut;
|
||||
|
||||
use super::ffmpeg;
|
||||
|
||||
use ffmpeg::format::Pixel;
|
||||
|
||||
use super::{check_ret, hwdevice::CudaDeviceContext};
|
||||
|
||||
pub struct HwFrameContext {
|
||||
_cuda_device_context: CudaDeviceContext,
|
||||
buffer: *mut ffmpeg::sys::AVBufferRef,
|
||||
}
|
||||
|
||||
impl HwFrameContext {
|
||||
fn new(cuda_device_context: CudaDeviceContext, buffer: *mut ffmpeg::sys::AVBufferRef) -> Self {
|
||||
Self {
|
||||
_cuda_device_context: cuda_device_context,
|
||||
buffer,
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn as_context_mut(&mut self) -> &mut ffmpeg::sys::AVHWFramesContext {
|
||||
// unsafe { &mut *((*self.buffer).data as *mut ffmpeg::sys::AVHWFramesContext) }
|
||||
// }
|
||||
|
||||
// pub fn as_context(&self) -> &ffmpeg::sys::AVHWFramesContext {
|
||||
// unsafe { &*((*self.buffer).data as *const ffmpeg::sys::AVHWFramesContext) }
|
||||
// }
|
||||
|
||||
pub fn as_raw_mut(&mut self) -> &mut ffmpeg::sys::AVBufferRef {
|
||||
unsafe { &mut *self.buffer }
|
||||
}
|
||||
|
||||
pub fn as_device_context_mut(&mut self) -> &mut ffmpeg::sys::AVBufferRef {
|
||||
self._cuda_device_context.as_raw_mut()
|
||||
}
|
||||
|
||||
/// call once to allocate frame
|
||||
pub fn get_buffer(&mut self, frame: &mut ffmpeg::frame::Video) -> Result<(), ffmpeg::Error> {
|
||||
unsafe {
|
||||
super::check_ret(ffmpeg::sys::av_hwframe_get_buffer(
|
||||
self.buffer,
|
||||
frame.as_mut_ptr(),
|
||||
0,
|
||||
))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// pub fn as_raw(&self) -> &ffmpeg::sys::AVBufferRef {
|
||||
// unsafe { &*self.buffer }
|
||||
// }
|
||||
}
|
||||
|
||||
unsafe impl Send for HwFrameContext {}
|
||||
|
||||
impl Drop for HwFrameContext {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if !self.buffer.is_null() {
|
||||
ffmpeg::sys::av_buffer_unref(&mut self.buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HwFrameContextBuilder {
|
||||
cuda_device_context: CudaDeviceContext,
|
||||
buffer: *mut ffmpeg::sys::AVBufferRef,
|
||||
}
|
||||
|
||||
impl HwFrameContextBuilder {
|
||||
pub fn new(mut cuda_device_context: CudaDeviceContext) -> anyhow::Result<Self> {
|
||||
let buffer = unsafe { ffmpeg::sys::av_hwframe_ctx_alloc(cuda_device_context.as_raw_mut()) };
|
||||
if buffer.is_null() {
|
||||
return Err(anyhow::anyhow!("could not allocate a hwframe context"));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
cuda_device_context,
|
||||
buffer,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build(mut self) -> Result<HwFrameContext, ffmpeg::Error> {
|
||||
check_ret(unsafe { ffmpeg::sys::av_hwframe_ctx_init(self.buffer) })?;
|
||||
let result = Ok(HwFrameContext::new(self.cuda_device_context, self.buffer));
|
||||
self.buffer = null_mut();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn set_width(mut self, width: u32) -> Self {
|
||||
self.as_frame_mut().width = width as i32;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_height(mut self, height: u32) -> Self {
|
||||
self.as_frame_mut().height = height as i32;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_sw_format(mut self, sw_format: Pixel) -> Self {
|
||||
self.as_frame_mut().sw_format = sw_format.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_format(mut self, format: Pixel) -> Self {
|
||||
self.as_frame_mut().format = format.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn as_frame_mut(&mut self) -> &mut ffmpeg::sys::AVHWFramesContext {
|
||||
unsafe { &mut *((*self.buffer).data as *mut ffmpeg::sys::AVHWFramesContext) }
|
||||
}
|
||||
|
||||
// pub fn as_frame(&self) -> &ffmpeg::sys::AVHWFramesContext {
|
||||
// unsafe { &*((*self.buffer).data as *const ffmpeg::sys::AVHWFramesContext) }
|
||||
// }
|
||||
}
|
22
server/src/video/mod.rs
Normal file
22
server/src/video/mod.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
pub mod h264_encoder;
|
||||
//pub mod lc_muxer;
|
||||
|
||||
/// Re-export of `ffmpeg` crate.
|
||||
pub use ffmpeg as ffmpeg;
|
||||
|
||||
pub mod hwdevice;
|
||||
pub mod hwframe;
|
||||
|
||||
#[allow(unused)] // FIXME
|
||||
pub mod encoder_thread;
|
||||
|
||||
pub mod cuda_gl;
|
||||
|
||||
// from hgaiser/moonshine
|
||||
pub fn check_ret(error_code: i32) -> Result<(), ffmpeg::Error> {
|
||||
if error_code != 0 {
|
||||
return Err(ffmpeg::Error::from(error_code));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in a new issue