mirror of
https://github.com/moonlight-stream/moonlight-embedded.git
synced 2025-07-01 23:35:47 +00:00
Compare commits
247 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a6bf7154a7 | ||
|
274d3db34d | ||
|
014af67397 | ||
|
eb4a202358 | ||
|
bac5360494 | ||
|
0e12282311 | ||
|
883498d642 | ||
|
55b49221d8 | ||
|
454f67b0ed | ||
|
2f03600bae | ||
|
fc904d2dac | ||
|
d6c9650a32 | ||
|
a9d6f17d5e | ||
|
ca4c517510 | ||
|
3b1b2ab51d | ||
|
b533c52edd | ||
|
d89bd8d20f | ||
|
8125d2194b | ||
|
8fb2c72ca1 | ||
|
9255f774f2 | ||
|
d3d79c3224 | ||
|
b08a04c378 | ||
|
5bd2799209 | ||
|
0712e05aab | ||
|
02cddf762b | ||
|
810ef140cb | ||
|
50a7454bc1 | ||
|
a610eddd97 | ||
|
b3fb22d427 | ||
|
3840b1409e | ||
|
3b72f5195b | ||
|
000d9da4a0 | ||
|
2aba7a10e7 | ||
|
0131274dce | ||
|
dc03235a62 | ||
|
c007eabf50 | ||
|
d578fd8c7d | ||
|
807f9027c7 | ||
|
36c1636f3c | ||
|
19442743a6 | ||
|
74714e6fb1 | ||
|
b1d111b5f7 | ||
|
eb46891f4c | ||
|
c883b6786f | ||
|
861cdbc014 | ||
|
41fddcf5ef | ||
|
d1db1d5231 | ||
|
8ef8891969 | ||
|
642b498aee | ||
|
68234081f8 | ||
|
3904c82ee9 | ||
|
bf14d37cd5 | ||
|
b1b3e274b3 | ||
|
93f5dc6cae | ||
|
e15466e909 | ||
|
859965a5a6 | ||
|
0deeb94655 | ||
|
c158833258 | ||
|
ab53f149f2 | ||
|
606e6d1181 | ||
|
6538d855d8 | ||
|
0169cedb27 | ||
|
5bb47ce8b6 | ||
|
0d0728f3c4 | ||
|
a585072ff8 | ||
|
dc786e3033 | ||
|
dede55c7f9 | ||
|
a328a53d59 | ||
|
6ea4a768e4 | ||
|
91022ea3bb | ||
|
6310edb0bd | ||
|
c779c1970d | ||
|
1caf24beab | ||
|
f486c8e356 | ||
|
5edf4d3dc7 | ||
|
0b80cc197a | ||
|
21eb932e52 | ||
|
db52ac901f | ||
|
2e0a014496 | ||
|
6ab37d0036 | ||
|
a9f8a77b66 | ||
|
a8a4e8908e | ||
|
b8442bfa95 | ||
|
4c58ac9f39 | ||
|
8384a243b2 | ||
|
32ebb00292 | ||
|
7988594e9e | ||
|
00c28f53d8 | ||
|
a8d5f7e2e7 | ||
|
4cb30a2a46 | ||
|
858c884099 | ||
|
5c23f0494d | ||
|
e7c611bb1e | ||
|
62007b912e | ||
|
1feeab9c71 | ||
|
c985b9ea0d | ||
|
66bc1b2904 | ||
|
32e87994cd | ||
|
098f53cd0b | ||
|
56f84ab662 | ||
|
c3325a8887 | ||
|
f16dc469af | ||
|
b2192eda25 | ||
|
b26d747e95 | ||
|
c2f21b955d | ||
|
28ace51874 | ||
|
a9302d02f5 | ||
|
30b563a2fc | ||
|
cb4b5d55b5 | ||
|
58958ca32a | ||
|
f021439d1b | ||
|
13390594f5 | ||
|
7ab48fb6d4 | ||
|
254f41686b | ||
|
758dc68958 | ||
|
0a9cadb729 | ||
|
0325a3b88c | ||
|
3f00f25a39 | ||
|
3859949b0f | ||
|
42980d09ee | ||
|
60c4b514af | ||
|
9de64d25b9 | ||
|
20a02acb07 | ||
|
b7cf7a130b | ||
|
1b95f027a2 | ||
|
4ebd3fb8ba | ||
|
b9703e7a1e | ||
|
543dc087fc | ||
|
5fe7b36b40 | ||
|
d74cc63038 | ||
|
039040e247 | ||
|
22f75b74f9 | ||
|
6c215e47bf | ||
|
44623c4a5e | ||
|
5449b521aa | ||
|
786d4a66ec | ||
|
8323eeb23c | ||
|
f871b663b1 | ||
|
23e6854a84 | ||
|
fe5dd11893 | ||
|
fbf6a2e2f7 | ||
|
5761f533ab | ||
|
bbdd7e5b24 | ||
|
634a0eee15 | ||
|
18fd1637a6 | ||
|
807565de8b | ||
|
1d32f894f7 | ||
|
89030f0701 | ||
|
ba8a23725d | ||
|
81322a2b91 | ||
|
d58d8f70e8 | ||
|
76b0de3566 | ||
|
bf7149e75d | ||
|
87613b3176 | ||
|
4164dc512f | ||
|
81a49e4564 | ||
|
3611f1dd89 | ||
|
4b41692fd8 | ||
|
d45567b5ee | ||
|
21b6af3493 | ||
|
6f15b257d7 | ||
|
3b51b40178 | ||
|
7ff0bcd8fd | ||
|
469b2ed866 | ||
|
3a63466fe2 | ||
|
cd2f1bb012 | ||
|
663e4b4b7a | ||
|
836e61b7c3 | ||
|
12efeda8e1 | ||
|
4dd6ab97df | ||
|
45c1a504e9 | ||
|
781afc7c66 | ||
|
0698e8b43f | ||
|
86140eb192 | ||
|
e8fbb2ec9c | ||
|
35af4c4ede | ||
|
e2c2575efb | ||
|
5215271b60 | ||
|
b71862d622 | ||
|
30da7e2bc2 | ||
|
0cfc557c9c | ||
|
1478d69123 | ||
|
fb57f3cb4d | ||
|
4e09dccfc0 | ||
|
d1937cb8e6 | ||
|
ef719dc938 | ||
|
66ec344a31 | ||
|
8602c85714 | ||
|
48de7760dc | ||
|
572644800d | ||
|
30464979dc | ||
|
b907c4b608 | ||
|
56d1fee17b | ||
|
2fa276182f | ||
|
0ebd86184b | ||
|
1cb4699057 | ||
|
01d43ff596 | ||
|
98092f8baf | ||
|
83982d3981 | ||
|
fc44f010f8 | ||
|
615ed824ac | ||
|
7109301a46 | ||
|
e84ae26ccc | ||
|
faa7eef9a4 | ||
|
296c8de759 | ||
|
b4ef1dde42 | ||
|
d7f9bc0b25 | ||
|
8f4d9aa5ee | ||
|
660cc7de41 | ||
|
a97b2cc3af | ||
|
7c8795febc | ||
|
5baa2c9023 | ||
|
338063fc2c | ||
|
9cec4208a4 | ||
|
f9a20348ab | ||
|
5b6639c8a7 | ||
|
957bc49da9 | ||
|
0c6f98e544 | ||
|
14644f1269 | ||
|
95104759ab | ||
|
4711129d14 | ||
|
5beb32d066 | ||
|
6b8260c1e5 | ||
|
b22b07e5a0 | ||
|
aae24557f9 | ||
|
4cd2d4b8dc | ||
|
ceacef6329 | ||
|
4bfeee690a | ||
|
c9a624595b | ||
|
a205edc1c7 | ||
|
7f856d3284 | ||
|
a66d75b145 | ||
|
922cd29adc | ||
|
70e54ef300 | ||
|
8f9e2704a6 | ||
|
3a86fecd9e | ||
|
4528b78d81 | ||
|
fe1c203b10 | ||
|
ecf09e8907 | ||
|
4ee5015557 | ||
|
6c8b3bb29d | ||
|
434dba31de | ||
|
c8e090a5e1 | ||
|
243ef8ae0b | ||
|
eb94742266 | ||
|
519a14a427 | ||
|
52efef7a66 |
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@ -1,7 +1,7 @@
|
||||
# Contribution Guide
|
||||
|
||||
## Got a Question or Problem?
|
||||
Please take a look at the [wiki](https://github.com/irtimmer/moonlight-embedded/wiki) for answers to your questions about using Moonlight Embedded.
|
||||
Please take a look at the [wiki](https://github.com/moonlight-stream/moonlight-embedded/wiki) for answers to your questions about using Moonlight Embedded.
|
||||
|
||||
If you still have questions about Moonlight Embedded, please use one of the different forums discussing Moonlight Embedded.
|
||||
|
||||
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build/
|
5
.gitmodules
vendored
5
.gitmodules
vendored
@ -1,3 +1,6 @@
|
||||
[submodule "common"]
|
||||
path = third_party/moonlight-common-c
|
||||
url = https://github.com/irtimmer/moonlight-common-c.git
|
||||
url = https://github.com/moonlight-stream/moonlight-common-c.git
|
||||
[submodule "third_party/SDL_GameControllerDB"]
|
||||
path = third_party/SDL_GameControllerDB
|
||||
url = https://github.com/gabomdq/SDL_GameControllerDB.git
|
||||
|
102
CMakeLists.txt
102
CMakeLists.txt
@ -1,8 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
project(moonlight-embedded VERSION 2.4.10 LANGUAGES C)
|
||||
cmake_minimum_required(VERSION 3.6)
|
||||
project(moonlight-embedded VERSION 2.7.0 LANGUAGES C)
|
||||
SET(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
|
||||
SET(CMAKE_C_STANDARD 99)
|
||||
include(${CMAKE_ROOT}/Modules/GNUInstallDirs.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/generate_version_header.cmake)
|
||||
|
||||
include(CheckCSourceCompiles)
|
||||
|
||||
add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-pointer-sign -Wno-sign-compare -Wno-switch)
|
||||
|
||||
aux_source_directory(./src SRC_LIST)
|
||||
list(APPEND SRC_LIST ./src/input/evdev.c ./src/input/mapping.c ./src/input/udev.c)
|
||||
@ -11,25 +16,48 @@ set(MOONLIGHT_DEFINITIONS)
|
||||
|
||||
find_package(ALSA)
|
||||
find_package(Opus REQUIRED)
|
||||
find_package(Broadcom)
|
||||
find_package(Broadcom-OMX)
|
||||
find_package(Freescale)
|
||||
find_package(Amlogic)
|
||||
find_package(Rockchip)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
option(ENABLE_SDL "Compile SDL support" ON)
|
||||
option(ENABLE_FFMPEG "Compile FFMPEG support" ON)
|
||||
option(ENABLE_X11 "Compile X11 support (requires ENABLE_FFMPEG)" ON)
|
||||
option(ENABLE_CEC "Compile CEC support" ON)
|
||||
option(ENABLE_PULSE "Compile PulseAudio support" ON)
|
||||
|
||||
pkg_check_modules(EVDEV REQUIRED libevdev)
|
||||
pkg_check_modules(UDEV REQUIRED libudev)
|
||||
pkg_check_modules(SDL sdl2>=2.0.4)
|
||||
pkg_check_modules(AVCODEC libavcodec)
|
||||
pkg_check_modules(AVUTIL libavutil)
|
||||
pkg_check_modules(XLIB x11)
|
||||
pkg_check_modules(VDPAU vdpau)
|
||||
pkg_check_modules(LIBVA libva)
|
||||
pkg_check_modules(LIBVA_X11 libva-x11)
|
||||
pkg_check_modules(PULSE libpulse-simple)
|
||||
pkg_check_modules(CEC libcec>=4)
|
||||
pkg_check_modules(EGL egl)
|
||||
pkg_check_modules(GLES glesv2)
|
||||
if (ENABLE_SDL)
|
||||
pkg_check_modules(SDL sdl2>=2.0.4)
|
||||
endif()
|
||||
if (ENABLE_FFMPEG)
|
||||
pkg_check_modules(AVCODEC libavcodec)
|
||||
pkg_check_modules(AVUTIL libavutil)
|
||||
pkg_check_modules(VDPAU vdpau)
|
||||
pkg_check_modules(LIBVA libva)
|
||||
pkg_check_modules(EGL egl)
|
||||
pkg_check_modules(GLES glesv2)
|
||||
|
||||
if (ENABLE_X11)
|
||||
pkg_check_modules(XLIB x11)
|
||||
pkg_check_modules(LIBVA_X11 libva-x11)
|
||||
endif()
|
||||
endif()
|
||||
if (ENABLE_PULSE)
|
||||
pkg_check_modules(PULSE libpulse-simple)
|
||||
endif()
|
||||
if (ENABLE_CEC)
|
||||
pkg_check_modules(CEC libcec>=4)
|
||||
endif()
|
||||
|
||||
pkg_check_modules(MMAL mmal)
|
||||
if (NOT MMAL_FOUND)
|
||||
find_package(MMAL)
|
||||
endif()
|
||||
|
||||
set(VDPAU_ACCEL_FOUND FALSE)
|
||||
set(VA_ACCEL_FOUND FALSE)
|
||||
@ -58,8 +86,25 @@ include_directories("${PROJECT_BINARY_DIR}")
|
||||
add_subdirectory(libgamestream)
|
||||
|
||||
add_executable(moonlight ${SRC_LIST})
|
||||
target_link_libraries(moonlight m)
|
||||
target_link_libraries(moonlight gamestream)
|
||||
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
|
||||
set(ALSA_FOUND FALSE)
|
||||
target_sources(moonlight PRIVATE ./src/audio/oss.c)
|
||||
endif()
|
||||
|
||||
check_c_source_compiles("#include <sys/auxv.h>
|
||||
int main(void) { return getauxval(AT_HWCAP); }" HAVE_GETAUXVAL)
|
||||
if (HAVE_GETAUXVAL)
|
||||
list(APPEND MOONLIGHT_DEFINITIONS HAVE_GETAUXVAL)
|
||||
endif()
|
||||
|
||||
check_c_source_compiles("int main(void) { return __builtin_cpu_supports(\"aes\"); }" HAVE_BICS_AES)
|
||||
if (HAVE_BICS_AES)
|
||||
list(APPEND MOONLIGHT_DEFINITIONS HAVE_BICS_AES)
|
||||
endif()
|
||||
|
||||
if (CEC_FOUND)
|
||||
list(APPEND MOONLIGHT_DEFINITIONS HAVE_LIBCEC)
|
||||
list(APPEND MOONLIGHT_OPTIONS CEC)
|
||||
@ -71,28 +116,37 @@ endif()
|
||||
if(AMLOGIC_FOUND)
|
||||
list(APPEND MOONLIGHT_DEFINITIONS HAVE_AML)
|
||||
list(APPEND MOONLIGHT_OPTIONS AML)
|
||||
add_library(moonlight-aml SHARED ./src/video/aml.c ${ILCLIENT_SRC_LIST})
|
||||
add_library(moonlight-aml SHARED ./src/video/aml.c ./src/util.c ${ILCLIENT_SRC_LIST})
|
||||
target_include_directories(moonlight-aml PRIVATE ${AMLOGIC_INCLUDE_DIRS} ${GAMESTREAM_INCLUDE_DIR} ${MOONLIGHT_COMMON_INCLUDE_DIR})
|
||||
target_link_libraries(moonlight-aml gamestream ${AMLOGIC_LIBRARIES})
|
||||
set_property(TARGET moonlight-aml PROPERTY COMPILE_DEFINITIONS ${AMLOGIC_DEFINITIONS})
|
||||
install(TARGETS moonlight-aml DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
endif()
|
||||
|
||||
if(BROADCOM_FOUND)
|
||||
if(BROADCOM-OMX_FOUND)
|
||||
list(APPEND MOONLIGHT_DEFINITIONS HAVE_PI)
|
||||
list(APPEND MOONLIGHT_OPTIONS PI)
|
||||
aux_source_directory(./third_party/ilclient ILCLIENT_SRC_LIST)
|
||||
add_library(moonlight-pi SHARED ./src/video/pi.c ./src/audio/omx.c ${ILCLIENT_SRC_LIST})
|
||||
add_library(moonlight-pi SHARED ./src/video/pi.c ./src/audio/omx.c ./src/util.c ${ILCLIENT_SRC_LIST})
|
||||
target_include_directories(moonlight-pi PRIVATE ./third_party/ilclient ${BROADCOM_INCLUDE_DIRS} ${GAMESTREAM_INCLUDE_DIR} ${MOONLIGHT_COMMON_INCLUDE_DIR} ${OPUS_INCLUDE_DIRS})
|
||||
target_link_libraries(moonlight-pi gamestream ${BROADCOM_LIBRARIES} ${OPUS_LIBRARY})
|
||||
set_property(TARGET moonlight-pi PROPERTY COMPILE_DEFINITIONS ${BROADCOM_DEFINITIONS})
|
||||
target_link_libraries(moonlight-pi gamestream ${BROADCOM_OMX_LIBRARIES} ${OPUS_LIBRARY})
|
||||
set_property(TARGET moonlight-pi PROPERTY COMPILE_DEFINITIONS ${BROADCOM_OMX_DEFINITIONS})
|
||||
install(TARGETS moonlight-pi DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
endif()
|
||||
|
||||
if(MMAL_FOUND)
|
||||
list(APPEND MOONLIGHT_DEFINITIONS HAVE_MMAL)
|
||||
list(APPEND MOONLIGHT_OPTIONS MMAL)
|
||||
add_library(moonlight-mmal SHARED ./src/video/mmal.c ./src/util.c)
|
||||
target_include_directories(moonlight-mmal PRIVATE ${MMAL_INCLUDE_DIRS} ${GAMESTREAM_INCLUDE_DIR} ${MOONLIGHT_COMMON_INCLUDE_DIR})
|
||||
target_link_libraries(moonlight-mmal gamestream ${MMAL_LINK_LIBRARIES})
|
||||
install(TARGETS moonlight-mmal DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
endif()
|
||||
|
||||
if(FREESCALE_FOUND)
|
||||
list(APPEND MOONLIGHT_DEFINITIONS HAVE_IMX)
|
||||
list(APPEND MOONLIGHT_OPTIONS IMX)
|
||||
add_library(moonlight-imx SHARED ./src/video/imx.c ./src/video/imx_vpu.c)
|
||||
add_library(moonlight-imx SHARED ./src/video/imx.c ./src/video/imx_vpu.c ./src/util.c)
|
||||
target_include_directories(moonlight-imx PRIVATE ${FREESCALE_INCLUDE_DIRS} ${GAMESTREAM_INCLUDE_DIR} ${MOONLIGHT_COMMON_INCLUDE_DIR})
|
||||
target_link_libraries(moonlight-imx gamestream ${FREESCALE_LIBRARIES})
|
||||
install(TARGETS moonlight-imx DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
@ -101,7 +155,7 @@ endif()
|
||||
if(ROCKCHIP_FOUND)
|
||||
list(APPEND MOONLIGHT_DEFINITIONS HAVE_ROCKCHIP)
|
||||
list(APPEND MOONLIGHT_OPTIONS ROCKCHIP)
|
||||
add_library(moonlight-rk SHARED ./src/video/rk.c)
|
||||
add_library(moonlight-rk SHARED ./src/video/rk.c ./src/util.c)
|
||||
target_include_directories(moonlight-rk PRIVATE ${ROCKCHIP_INCLUDE_DIRS} ${GAMESTREAM_INCLUDE_DIR} ${MOONLIGHT_COMMON_INCLUDE_DIR})
|
||||
target_link_libraries(moonlight-rk gamestream ${ROCKCHIP_LIBRARIES})
|
||||
set_property(TARGET moonlight-rk PROPERTY COMPILE_DEFINITIONS ${ROCKCHIP_DEFINITIONS})
|
||||
@ -157,12 +211,12 @@ if (PULSE_FOUND)
|
||||
target_link_libraries(moonlight ${PULSE_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if (AMLOGIC_FOUND OR BROADCOM_FOUND OR FREESCALE_FOUND OR ROCKCHIP_FOUND OR X11_FOUND)
|
||||
if (AMLOGIC_FOUND OR BROADCOM-OMX_FOUND OR MMAL_FOUND OR FREESCALE_FOUND OR ROCKCHIP_FOUND OR X11_FOUND)
|
||||
list(APPEND MOONLIGHT_DEFINITIONS HAVE_EMBEDDED)
|
||||
list(APPEND MOONLIGHT_OPTIONS EMBEDDED)
|
||||
endif()
|
||||
|
||||
if(NOT AMLOGIC_FOUND AND NOT BROADCOM_FOUND AND NOT FREESCALE_FOUND AND NOT ROCKCHIP_FOUND AND NOT SOFTWARE_FOUND)
|
||||
if(NOT AMLOGIC_FOUND AND NOT BROADCOM-OMX_FOUND AND NOT MMAL_FOUND AND NOT FREESCALE_FOUND AND NOT ROCKCHIP_FOUND AND NOT SOFTWARE_FOUND)
|
||||
message(FATAL_ERROR "No video output available")
|
||||
endif()
|
||||
|
||||
@ -175,5 +229,5 @@ target_link_libraries(moonlight ${EVDEV_LIBRARIES} ${OPUS_LIBRARY} ${UDEV_LIBRAR
|
||||
add_subdirectory(docs)
|
||||
|
||||
install(TARGETS moonlight DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install(FILES gamecontrollerdb.txt DESTINATION ${CMAKE_INSTALL_DATADIR}/moonlight)
|
||||
install(FILES ./third_party/SDL_GameControllerDB/gamecontrollerdb.txt DESTINATION ${CMAKE_INSTALL_DATADIR}/moonlight)
|
||||
install(FILES moonlight.conf DESTINATION ${CMAKE_INSTALL_SYSCONFDIR})
|
||||
|
41
README.md
41
README.md
@ -1,49 +1,24 @@
|
||||
# Moonlight Embedded
|
||||
|
||||
Moonlight Embedded is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield, but built for Linux.
|
||||
[](https://ci.appveyor.com/project/cgutman/moonlight-embedded/branch/master)
|
||||
|
||||
Moonlight Embedded allows you to stream your full collection of games from
|
||||
your powerful Windows desktop to your (embedded) Linux system, like Raspberry Pi, CuBox-i and ODROID.
|
||||
Moonlight Embedded is an open source client for [Sunshine](https://github.com/LizardByte/Sunshine) and NVIDIA GameStream for embedded Linux systems, like Raspberry Pi, CuBox-i and ODROID. Moonlight allows you to stream your full collection of games and applications from your PC to other devices to play them remotely.
|
||||
|
||||
Moonlight also has [PC](https://github.com/moonlight-stream/moonlight-qt), [Android](https://github.com/moonlight-stream/moonlight-android), and [iOS](https://github.com/moonlight-stream/moonlight-ios) clients.
|
||||
|
||||
## Documentation
|
||||
|
||||
More information about installing and runnning Moonlight Embedded is available on the [wiki](https://github.com/irtimmer/moonlight-embedded/wiki).
|
||||
|
||||
## Requirements
|
||||
|
||||
* [GFE compatible](http://shield.nvidia.com/play-pc-games/) computer with GTX 600/700/900/1000 series GPU (for the PC you're streaming from)
|
||||
* High-end wireless router (802.11n dual-band recommended) or wired network
|
||||
* Geforce Experience 2.1.1 or higher
|
||||
|
||||
## Quick Start
|
||||
|
||||
* Ensure your GFE server and client are on the same network
|
||||
* Turn on Shield Streaming in the GFE settings
|
||||
* Pair Moonlight Embedded with the GFE server
|
||||
* Accept the pairing confirmation on your PC
|
||||
* Connect to the GFE Server with Moonlight Embedded
|
||||
* Play games!
|
||||
More information about installing and runnning Moonlight Embedded is available on the [wiki](https://github.com/moonlight-stream/moonlight-embedded/wiki).
|
||||
|
||||
## Bugs
|
||||
|
||||
Please check the fora, wiki and old bug reports before submitting a new bug report.
|
||||
Please check the wiki and old bug reports before submitting a new bug report.
|
||||
|
||||
Bugs can be reported to the [issue tracker](https://github.com/irtimmer/moonlight-embedded/issues).
|
||||
Bugs can be reported to the [issue tracker](https://github.com/moonlight-stream/moonlight-embedded/issues).
|
||||
|
||||
## See also
|
||||
|
||||
[Moonlight-common-c](https://github.com/moonlight-stream/moonlight-common-c) is the shared codebase between
|
||||
different C implementations of Moonlight
|
||||
|
||||
[Moonlight-common-c](https://github.com/irtimmer/moonlight-common-c) is the fork used by Moonlight Embedded
|
||||
|
||||
## Discussion
|
||||
|
||||
[Discord](https://discord.gg/6ERtzFY) Moonlight in General with Moonlight Embedded channel
|
||||
[XDA](http://forum.xda-developers.com/showthread.php?t=2505510) Moonlight in General
|
||||
[Raspberry Pi Forum](http://www.raspberrypi.org/forums/viewtopic.php?f=78&t=65878) Moonlight Embedded for Raspberry Pi
|
||||
[SolidRun Community](http://www.solid-run.com/community/viewtopic.php?f=13&t=1489&p=11173) Moonlight Embedded for Cubox-i and Hummingboard
|
||||
[ODROID Forum](http://forum.odroid.com/viewtopic.php?f=91&t=15456) Moonlight Embedded on ODROID
|
||||
[Moonlight-common-c](https://github.com/moonlight-stream/moonlight-common-c) is the shared codebase between different Moonlight implementations
|
||||
|
||||
## Contribute
|
||||
|
||||
|
30
appveyor.yml
Normal file
30
appveyor.yml
Normal file
@ -0,0 +1,30 @@
|
||||
version: 0.0.0.{build}
|
||||
|
||||
clone_depth: 1
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004
|
||||
PACKAGES: libssl-dev libopus-dev libasound2-dev libudev-dev libavahi-client-dev libcurl4-openssl-dev libevdev-dev libexpat1-dev libpulse-dev uuid-dev cmake gcc g++ libavcodec-dev libavutil-dev libsdl2-dev libva-dev libvdpau-dev libcec-dev libp8-platform-dev
|
||||
BUILD_TARGET: ubuntu
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004
|
||||
PACKAGES: qemu binfmt-support qemu-user-static
|
||||
BUILD_TARGET: raspbian
|
||||
|
||||
install:
|
||||
- 'sudo apt update || true'
|
||||
- 'sudo apt install -y $PACKAGES'
|
||||
- '[ "$BUILD_TARGET" != raspbian ] || docker run --rm --privileged multiarch/qemu-user-static --reset -p yes'
|
||||
|
||||
before_build:
|
||||
- 'git submodule update --init --recursive'
|
||||
|
||||
build_script:
|
||||
- 'if [[ "$BUILD_TARGET" = ubuntu ]]; then mkdir build && cd build && cmake -DCMAKE_INSTALL_PREFIX=/tmp .. && make -j$(nproc) && make install; fi'
|
||||
- 'if [[ "$BUILD_TARGET" = raspbian ]]; then git clone --recursive https://github.com/cgutman/moonlight-embedded-packaging.git && cd moonlight-embedded-packaging && sh -c "./build-rpi-buster.sh $APPVEYOR_REPO_COMMIT"; fi'
|
||||
|
||||
after_build:
|
||||
- sh: '[ "$BUILD_TARGET" != raspbian ] || appveyor PushArtifact out_*/moonlight-embedded_*.deb'
|
||||
- sh: '[ "$BUILD_TARGET" != raspbian ] || appveyor PushArtifact out_*/moonlight-embedded-dbgsym_*.deb'
|
||||
|
||||
deploy: off
|
@ -1,29 +1,17 @@
|
||||
find_path(AMLOGIC_INCLUDE_DIR
|
||||
NAMES codec.h
|
||||
DOC "Amlogic include directory"
|
||||
PATHS /usr/local/include/amcodec /usr/include/amcodec /usr/include/)
|
||||
PATHS /usr/local/include/amcodec /usr/osmc/include/amcodec /usr/include/amcodec /usr/include/)
|
||||
mark_as_advanced(AMLOGIC_INCLUDE_DIR)
|
||||
|
||||
find_library(AMAVUTILS_LIBRARY
|
||||
NAMES libamavutils.so
|
||||
DOC "Path to Amlogic Audio Video Utils Library"
|
||||
PATHS /usr/lib/aml_libs /usr/local/lib /usr/lib)
|
||||
mark_as_advanced(AMAVUTILS_LIBRARY)
|
||||
|
||||
find_library(AMADEC_LIBRARY
|
||||
NAMES libamadec.so
|
||||
DOC "Path to Amlogic Audio Decoder Library"
|
||||
PATHS /usr/lib/aml_libs /usr/local/lib /usr/lib)
|
||||
mark_as_advanced(AMADEC_LIBRARY)
|
||||
|
||||
find_library(AMCODEC_LIBRARY
|
||||
NAMES libamcodec.so
|
||||
DOC "Path to Amlogic Video Codec Library"
|
||||
PATHS /usr/lib/aml_libs /usr/local/lib /usr/lib)
|
||||
PATHS /usr/lib/aml_libs /usr/osmc/lib /usr/local/lib /usr/lib)
|
||||
mark_as_advanced(AMCODEC_LIBRARY)
|
||||
|
||||
include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Amlogic DEFAULT_MSG AMLOGIC_INCLUDE_DIR AMCODEC_LIBRARY AMADEC_LIBRARY AMAVUTILS_LIBRARY)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Amlogic DEFAULT_MSG AMLOGIC_INCLUDE_DIR AMCODEC_LIBRARY)
|
||||
|
||||
set(AMLOGIC_LIBRARIES ${AMCODEC_LIBRARY} ${AMADEC_LIBRARY} ${AMAVUTILS_LIBRARY})
|
||||
set(AMLOGIC_LIBRARIES ${AMCODEC_LIBRARY})
|
||||
set(AMLOGIC_INCLUDE_DIRS ${AMLOGIC_INCLUDE_DIR})
|
||||
|
@ -29,8 +29,8 @@ find_library(BCM_HOST_LIBRARY
|
||||
mark_as_advanced(BCM_HOST_LIBRARY)
|
||||
|
||||
include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Broadcom DEFAULT_MSG BROADCOM_INCLUDE_DIR VCOS_LIBRARY VCHIQ_LIBRARY OPENMAXIL_LIBRARY BCM_HOST_LIBRARY)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Broadcom-OMX DEFAULT_MSG BROADCOM_INCLUDE_DIR VCOS_LIBRARY VCHIQ_LIBRARY OPENMAXIL_LIBRARY BCM_HOST_LIBRARY)
|
||||
|
||||
set(BROADCOM_LIBRARIES ${BCM_HOST_LIBRARY} ${OPENMAXIL_LIBRARY} ${VCHIQ_LIBRARY} ${VCOS_LIBRARY})
|
||||
set(BROADCOM_OMX_LIBRARIES ${BCM_HOST_LIBRARY} ${OPENMAXIL_LIBRARY} ${VCHIQ_LIBRARY} ${VCOS_LIBRARY})
|
||||
set(BROADCOM_INCLUDE_DIRS ${BROADCOM_INCLUDE_DIR} ${BROADCOM_INCLUDE_DIR}/interface/vmcs_host/linux ${BROADCOM_INCLUDE_DIR}/interface/vcos/pthreads)
|
||||
set(BROADCOM_DEFINITIONS USE_VCHIQ_ARM HAVE_LIBOPENMAX=2 OMX OMX_SKIP64BIT USE_EXTERNAL_OMX HAVE_LIBBCM_HOST USE_EXTERNAL_LIBBCM_HOST)
|
||||
set(BROADCOM_OMX_DEFINITIONS USE_VCHIQ_ARM HAVE_LIBOPENMAX=2 OMX OMX_SKIP64BIT USE_EXTERNAL_OMX HAVE_LIBBCM_HOST USE_EXTERNAL_LIBBCM_HOST)
|
@ -1,51 +1,77 @@
|
||||
# - Try to find LIBUUID
|
||||
# Find LIBUUID headers, libraries and the answer to all questions.
|
||||
# CMake - Cross Platform Makefile Generator
|
||||
# Copyright 2000-2024 Kitware, Inc. and Contributors
|
||||
# All rights reserved.
|
||||
#
|
||||
# LIBUUID_FOUND True if libuuid got found
|
||||
# LIBUUID_INCLUDE_DIRS Location of libuuid headers
|
||||
# LIBUUID_LIBRARIES List of libraries to use libuuid
|
||||
#
|
||||
# Copyright (c) 2008 Bjoern Ricks <bjoern.ricks@googlemail.com>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the New
|
||||
# BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License. See
|
||||
# https://cmake.org/licensing for details.
|
||||
#
|
||||
#[=======================================================================[.rst:
|
||||
FindLibUUID
|
||||
------------
|
||||
|
||||
INCLUDE( FindPkgConfig )
|
||||
Find LibUUID include directory and library.
|
||||
|
||||
IF ( LibUuid_FIND_REQUIRED )
|
||||
SET( _pkgconfig_REQUIRED "REQUIRED" )
|
||||
ELSE( LibUuid_FIND_REQUIRED )
|
||||
SET( _pkgconfig_REQUIRED "" )
|
||||
ENDIF ( LibUuid_FIND_REQUIRED )
|
||||
Imported Targets
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
IF ( LIBUUID_MIN_VERSION )
|
||||
PKG_SEARCH_MODULE( LIBUUID ${_pkgconfig_REQUIRED} uuid>=${LIBUUID_MIN_VERSION} )
|
||||
ELSE ( LIBUUID_MIN_VERSION )
|
||||
PKG_SEARCH_MODULE( LIBUUID ${_pkgconfig_REQUIRED} uuid )
|
||||
ENDIF ( LIBUUID_MIN_VERSION )
|
||||
An :ref:`imported target <Imported targets>` named
|
||||
``LibUUID::LibUUID`` is provided if LibUUID has been found.
|
||||
|
||||
Result Variables
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
IF( NOT LIBUUID_FOUND AND NOT PKG_CONFIG_FOUND )
|
||||
FIND_PATH( LIBUUID_INCLUDE_DIRS uuid/uuid.h )
|
||||
FIND_LIBRARY( LIBUUID_LIBRARIES uuid)
|
||||
This module defines the following variables:
|
||||
|
||||
# Report results
|
||||
IF ( LIBUUID_LIBRARIES AND LIBUUID_INCLUDE_DIRS )
|
||||
SET( LIBUUID_FOUND 1 )
|
||||
IF ( NOT LIBUUID_FIND_QUIETLY )
|
||||
MESSAGE( STATUS "Found libuuid: ${LIBUUID_LIBRARIES}" )
|
||||
ENDIF ( NOT LIBUUID_FIND_QUIETLY )
|
||||
ELSE ( LIBUUID_LIBRARIES AND LIBUUID_INCLUDE_DIRS )
|
||||
IF ( LIBUUID_FIND_REQUIRED )
|
||||
MESSAGE( SEND_ERROR "Could NOT find libuuid" )
|
||||
ELSE ( LIBUUID_FIND_REQUIRED )
|
||||
IF ( NOT LIBUUID_FIND_QUIETLY )
|
||||
MESSAGE( STATUS "Could NOT find libuuid" )
|
||||
ENDIF ( NOT LIBUUID_FIND_QUIETLY )
|
||||
ENDIF ( LIBUUID_FIND_REQUIRED )
|
||||
ENDIF ( LIBUUID_LIBRARIES AND LIBUUID_INCLUDE_DIRS )
|
||||
ENDIF( NOT LIBUUID_FOUND AND NOT PKG_CONFIG_FOUND )
|
||||
``LibUUID_FOUND``
|
||||
True if LibUUID was found, false otherwise.
|
||||
``LibUUID_INCLUDE_DIRS``
|
||||
Include directories needed to include LibUUID headers.
|
||||
``LibUUID_LIBRARIES``
|
||||
Libraries needed to link to LibUUID.
|
||||
|
||||
MARK_AS_ADVANCED( LIBUUID_LIBRARIES LIBUUID_INCLUDE_DIRS )
|
||||
Cache Variables
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
This module uses the following cache variables:
|
||||
|
||||
``LibUUID_LIBRARY``
|
||||
The location of the LibUUID library file.
|
||||
``LibUUID_INCLUDE_DIR``
|
||||
The location of the LibUUID include directory containing ``uuid/uuid.h``.
|
||||
|
||||
The cache variables should not be used by project code.
|
||||
They may be set by end users to point at LibUUID components.
|
||||
#]=======================================================================]
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
find_library(LibUUID_LIBRARY
|
||||
NAMES uuid
|
||||
)
|
||||
mark_as_advanced(LibUUID_LIBRARY)
|
||||
|
||||
find_path(LibUUID_INCLUDE_DIR
|
||||
NAMES uuid/uuid.h
|
||||
)
|
||||
mark_as_advanced(LibUUID_INCLUDE_DIR)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibUUID
|
||||
FOUND_VAR LibUUID_FOUND
|
||||
REQUIRED_VARS LibUUID_LIBRARY LibUUID_INCLUDE_DIR
|
||||
)
|
||||
set(LIBUUID_FOUND ${LibUUID_FOUND})
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Provide documented result variables and targets.
|
||||
if(LibUUID_FOUND)
|
||||
set(LibUUID_INCLUDE_DIRS ${LibUUID_INCLUDE_DIR})
|
||||
set(LibUUID_LIBRARIES ${LibUUID_LIBRARY})
|
||||
if(NOT TARGET LibUUID::LibUUID)
|
||||
add_library(LibUUID::LibUUID UNKNOWN IMPORTED)
|
||||
set_target_properties(LibUUID::LibUUID PROPERTIES
|
||||
IMPORTED_LOCATION "${LibUUID_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${LibUUID_INCLUDE_DIRS}"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
41
cmake/FindMMAL.cmake
Normal file
41
cmake/FindMMAL.cmake
Normal file
@ -0,0 +1,41 @@
|
||||
find_path(BROADCOM_INCLUDE_DIR
|
||||
NAMES bcm_host.h
|
||||
DOC "Broadcom include directory"
|
||||
PATHS /opt/vc/include)
|
||||
mark_as_advanced(BROADCOM_INCLUDE_DIR)
|
||||
|
||||
find_library(VCOS_LIBRARY
|
||||
NAMES libvcos.so
|
||||
DOC "Path to VCOS Library"
|
||||
PATHS /opt/vc/lib)
|
||||
mark_as_advanced(VCOS_LIBRARY)
|
||||
|
||||
find_library(BCM_HOST_LIBRARY
|
||||
NAMES libbcm_host.so
|
||||
DOC "Path to Broadcom Host Library"
|
||||
PATHS /opt/vc/lib)
|
||||
mark_as_advanced(BCM_HOST_LIBRARY)
|
||||
|
||||
find_library(MMAL_CORE_LIBRARY
|
||||
NAMES libmmal_core.so
|
||||
DOC "Path to MMAL Core Library"
|
||||
PATHS /opt/vc/lib)
|
||||
mark_as_advanced(MMAL_CORE_LIBRARY)
|
||||
|
||||
find_library(MMAL_UTIL_LIBRARY
|
||||
NAMES libmmal_util.so
|
||||
DOC "Path to MMAL Util Library"
|
||||
PATHS /opt/vc/lib)
|
||||
mark_as_advanced(MMAL_UTIL_LIBRARY)
|
||||
|
||||
find_library(MMAL_VC_CLIENT_LIBRARY
|
||||
NAMES libmmal_vc_client.so
|
||||
DOC "Path to MMAL Client Library"
|
||||
PATHS /opt/vc/lib)
|
||||
mark_as_advanced(MMAL_VC_CLIENT_LIBRARY)
|
||||
|
||||
include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(MMAL DEFAULT_MSG BROADCOM_INCLUDE_DIR VCOS_LIBRARY MMAL_CORE_LIBRARY MMAL_UTIL_LIBRARY MMAL_VC_CLIENT_LIBRARY BCM_HOST_LIBRARY)
|
||||
|
||||
set(MMAL_LINK_LIBRARIES ${BCM_HOST_LIBRARY} ${VCOS_LIBRARY} ${MMAL_CORE_LIBRARY} ${MMAL_UTIL_LIBRARY} ${MMAL_VC_CLIENT_LIBRARY})
|
||||
set(MMAL_INCLUDE_DIRS ${BROADCOM_INCLUDE_DIR})
|
29
cmake/generate_version_header.cmake
Normal file
29
cmake/generate_version_header.cmake
Normal file
@ -0,0 +1,29 @@
|
||||
include(FindGit)
|
||||
|
||||
if(GIT_FOUND AND IS_DIRECTORY "${PROJECT_SOURCE_DIR}/.git")
|
||||
set(GIT_BRANCH "")
|
||||
set(GIT_COMMIT_HASH "")
|
||||
|
||||
# Get the current working branch
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} name-rev --name-only HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_BRANCH0
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
# Clean Branch name to have only the branch
|
||||
STRING(REGEX REPLACE "((origin))+" "" GIT_BRANCH1 ${GIT_BRANCH0})
|
||||
STRING(REGEX REPLACE "((remotes))+" "" GIT_BRANCH2 ${GIT_BRANCH1})
|
||||
STRING(REGEX REPLACE "\\/+" "" GIT_BRANCH3 ${GIT_BRANCH2})
|
||||
STRING(REGEX REPLACE "~[0-9]*" "" GIT_BRANCH4 ${GIT_BRANCH3})
|
||||
STRING(REGEX REPLACE "\\^[0-9]*" "" GIT_BRANCH ${GIT_BRANCH4})
|
||||
|
||||
# Get the latest abbreviated commit hash of the working branch
|
||||
execute_process(
|
||||
COMMAND git log -1 --format=%h
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_COMMIT_HASH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
endif()
|
@ -1,4 +1,4 @@
|
||||
add_custom_command(OUTPUT moonlight.1 COMMAND pod2man --section=1 --center="Moonlight Embedded Manual" --name="MOONLIGHT" --release="moonlight 2.1.0" ${CMAKE_CURRENT_SOURCE_DIR}/README.pod > moonlight.1)
|
||||
add_custom_command(OUTPUT moonlight.1 COMMAND pod2man --section=1 --center="Moonlight Embedded Manual" --name="MOONLIGHT" --release="moonlight 2.5.0" ${CMAKE_CURRENT_SOURCE_DIR}/README.pod > moonlight.1)
|
||||
add_custom_target(docs ALL DEPENDS moonlight.1)
|
||||
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/moonlight.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
|
||||
|
@ -79,34 +79,33 @@ Change the vertical resolution to I<HEIGHT>
|
||||
=item B<-fps> [I<FPS>]
|
||||
|
||||
Change the number of frame per second to I<FPS>.
|
||||
Defaults to 60fps for 720p and 30fps for 1080p and higher.
|
||||
Only 30 and 60 fps are currently supported by Gamestream.
|
||||
Defaults to 60 FPS.
|
||||
|
||||
=item B<-bitrate> [I<BITRATE>]
|
||||
|
||||
Change bitrate to I<BITRATE> Kbps.
|
||||
By default the bitrate depends on the selected resolution and fps.
|
||||
For resolution 1080p and 60 fps and higher 20 Mbps is used.
|
||||
For resolution 1080p or 60 fps and higher 10 Mbps is used
|
||||
For other configurations 5 Mbps is used by default.
|
||||
By default the bitrate depends on the selected resolution and FPS.
|
||||
For resolution 1080p and 60 FPS and higher, 20 Mbps is used.
|
||||
For resolution 1080p or 60 FPS and higher, 10 Mbps is used
|
||||
For other configurations, 5 Mbps is used by default.
|
||||
|
||||
=item B<-packetsize> [I<PACKETSIZE>]
|
||||
|
||||
Change the network packetsize to I<PACKETSIZE> bytes.
|
||||
The packetsize should the smaller than the MTU of the network.
|
||||
This value must be a multiply of 16.
|
||||
By default a safe value of 1024 is used.
|
||||
This value must be a multiple of 16.
|
||||
By default, 1392 is used on LAN and 1024 on WAN.
|
||||
|
||||
=item B<-codec> [I<CODEC>]
|
||||
|
||||
Select codec to use.
|
||||
Can be 'auto', 'h264', 'h265' or 'hevc'.
|
||||
Not all video decoders do support H.265/HEVC.
|
||||
Will still use H.264 if server doesn't support HEVC.
|
||||
Can be 'auto', 'h264', 'h265', 'hevc', or 'av1'.
|
||||
Not all video decoders support H.265/HEVC or AV1.
|
||||
Will still use H.264 if server doesn't support HEVC or AV1.
|
||||
|
||||
=item B<-remote>
|
||||
=item B<-remote> [I<yes/no/auto>]
|
||||
|
||||
Enable the optimizations for remote connections in GFE.
|
||||
Enable optimizations for LAN or WAN streaming.
|
||||
|
||||
=item B<-app> [I<APP>]
|
||||
|
||||
@ -121,9 +120,9 @@ Stop GFE from changing the graphical settings of the requested game or applicati
|
||||
|
||||
Play the audio on the host computer instead of this device.
|
||||
|
||||
=item B<-surround>
|
||||
=item B<-surround> [I<5.1/7.1>]
|
||||
|
||||
Enable 5.1 surround sound instead of stereo.
|
||||
Enable surround sound instead of stereo.
|
||||
|
||||
=item B<-keydir> [I<DIRECTORY>]
|
||||
|
||||
@ -141,14 +140,22 @@ By default the gamecontrollerdb.txt provided by Moonlight Embedded is used.
|
||||
Select platform for audio and video output and input.
|
||||
<PLATFORM> can be pi, imx, aml, x11, x11_vdpau, sdl or fake.
|
||||
|
||||
=item B<-unsupported>
|
||||
=item B<-nounsupported>
|
||||
|
||||
Try streaming if GFE version or options are unsupported
|
||||
Don't stream if resolution is not officially supported by the server
|
||||
|
||||
=item B<-quitappafter>
|
||||
|
||||
Send quit app request to remote after quitting session
|
||||
|
||||
=item B<-viewonly>
|
||||
|
||||
Disable all input processing (view-only mode)
|
||||
|
||||
=item B<-nomouseemulation>
|
||||
|
||||
Disable gamepad mouse emulation (activated by long pressing Start button)
|
||||
|
||||
=item B<-verbose>
|
||||
|
||||
Enable verbose output
|
||||
|
@ -1,226 +0,0 @@
|
||||
default,Default Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
xwc,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
0000000058626f782033363020576900,Xbox 360 Wireless Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,
|
||||
0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,
|
||||
0300000000f000000300000000010000,RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,
|
||||
0300000000f00000f100000000010000,Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,
|
||||
03000000022000000090000011010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,
|
||||
030000000d0500000308000010010000,Nostromo n45 Dual Analog Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,
|
||||
030000000d0f00000900000010010000,Natec Genesis P44,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
030000000d0f00000d00000000010000,hori,a:b0,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftx:b4,lefty:b5,rightshoulder:b7,start:b9,x:b1,y:b2,
|
||||
030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,
|
||||
030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,
|
||||
030000000d0f00005e00000011010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
030000000d0f00005f00000011010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
030000000d0f00006600000011010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
030000000d0f00006700000001010000,HORIPAD ONE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000000d0f00006a00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
030000000d0f00006b00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
030000000d0f00006e00000011010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
030000000d0f00009200000011010000,Hori Pokken Tournament DX Pro Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,
|
||||
030000000d0f0000ee00000011010000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
03000000100000008200000011010000,Akishop Customs PS360+ v1.66,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,
|
||||
03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,
|
||||
03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,
|
||||
03000000120c00000500000000010000,Manta Dualshock 2,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,
|
||||
03000000120c00000500000010010000,AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,
|
||||
03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,
|
||||
03000000250900006688000000010000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,
|
||||
0300000025090000e803000001010000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:a5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,
|
||||
03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,
|
||||
03000000280400000140000000010000,Gravis GamePad Pro USB ,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,
|
||||
03000000300f00000b01000010010000,Jess Tech GGE909 PC Recoil Pad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,
|
||||
03000000300f00001001000010010000,Jess Tech Dual Analog Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,
|
||||
03000000300f00001211000011010000,QanBa Arcade JoyStick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,
|
||||
03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,
|
||||
03000000321500000010000011010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
03000000321500000104000011010000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
03000000321500000204000011010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,
|
||||
03000000341a00000908000010010000,SL-6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,
|
||||
03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
03000000380700001888000010010000,MadCatz PC USB Wired Stick 8818,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
03000000380700003847000090040000,Mad Catz Wired Xbox 360 Controller (SFIV),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,
|
||||
03000000380700003888000010010000,MadCatz PC USB Wired Stick 8838,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
03000000380700005032000011010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
03000000380700005082000011010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
03000000380700008034000011010000,Mad Catz fightstick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
03000000380700008084000011010000,Mad Catz fightstick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
03000000380700008433000011010000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
03000000380700008483000011010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
03000000450c00002043000010010000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,
|
||||
03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,
|
||||
030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,
|
||||
030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,
|
||||
030000004c0500006802000011810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,
|
||||
030000004c050000a00b000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
030000004c050000a00b000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,
|
||||
030000004c050000c405000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
030000004c050000c405000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,
|
||||
030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
030000004c050000cc09000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
030000004c050000cc09000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,
|
||||
030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,
|
||||
030000004f04000008d0000000010000,Thrustmaster Run N Drive Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
030000004f04000012b3000010010000,Thrustmaster vibrating gamepad,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,
|
||||
030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,
|
||||
030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,
|
||||
030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
030000004f04000026b3000002040000,Thrustmaster Gamepad GP XID,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,
|
||||
03000000550900001472000011010000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,
|
||||
030000005e0400000202000000010000,Old Xbox pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,
|
||||
030000005e0400000e00000000010000,Microsoft SideWinder,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b7,start:b8,x:b3,y:b4,
|
||||
030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000005e0400008502000000010000,Microsoft X-Box pad (Japan),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,
|
||||
030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,
|
||||
030000005e0400008e02000004010000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000005e0400008e02000062230000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000005e040000a102000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000005e040000a102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000005e040000a102000014010000,Xbox 360 Wireless Receiver (XBOX),a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000005e040000d102000001010000,Microsoft X-Box One pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000005e040000d102000003020000,Microsoft X-Box One pad v2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000005e040000dd02000003020000,Microsoft X-Box One pad (Firmware 2015),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000005e040000e302000003020000,Microsoft X-Box One Elite pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000005e040000ea02000001030000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
03000000632500002305000010010000,ShanWan USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,
|
||||
03000000632500007505000010010000,SHANWAN PS3/PC Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,
|
||||
03000000666600000488000000010000,Super Joy Box 5 Pro,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,
|
||||
03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,
|
||||
030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
030000006d0400000ac2000010010000,Logitech Inc. WingMan RumblePad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b2,rightx:a3,righty:a4,x:b3,y:b4,
|
||||
030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,
|
||||
030000006d04000015c2000010010000,Logitech Logitech Extreme 3D,a:b0,b:b4,back:b6,guide:b8,leftshoulder:b9,leftstick:h0.8,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:h0.2,start:b7,x:b2,y:b5,
|
||||
030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,
|
||||
030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000006f0e00001302000000010000,Afterglow,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000006f0e00001304000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:a0,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:a3,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000006f0e00001402000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
030000006f0e00001e01000011010000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
030000006f0e00001f01000000010000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
030000006f0e00003901000000430000,Afterglow Prismatic Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000006f0e00003901000020060000,Afterglow Controller for Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000006f0e00004601000001010000,Rock Candy Xbox One Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
03000000780000000600000010010000,Microntek USB Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,
|
||||
03000000790000000600000007010000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,
|
||||
03000000790000000600000010010000,DragonRise Inc. Generic USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,
|
||||
03000000790000001100000000010000,USB Gamepad1,a:b2,b:b1,back:b8,dpdown:a0,dpleft:a1,dpright:a2,dpup:a4,start:b9,
|
||||
03000000790000004318000010010000,Mayflash GameCube Controller Adapter,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,
|
||||
03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,
|
||||
0300000079000000d218000011010000,MAGIC-NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
0300000079000000d418000000010000,GPD Win 2 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,
|
||||
030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,
|
||||
030000008916000000fd000024010000,Razer Onza Tournament Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000008916000000fe000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
030000008f0e00000300000010010000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,
|
||||
030000008f0e00000610000000010000,GreenAsia Electronics 4Axes 12Keys GamePad ,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,
|
||||
030000008f0e00000800000010010000,Gasia Co. Ltd PS(R) Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,
|
||||
030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,
|
||||
030000009b2800000300000001010000,raphnet.net 4nes4snes v1.5,a:b0,b:b4,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,
|
||||
03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,
|
||||
03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,
|
||||
03000000a30600000c04000011010000,Saitek P2900 Wireless Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b12,x:b0,y:b3,
|
||||
03000000a30600000cff000010010000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,
|
||||
03000000a306000018f5000010010000,Saitek PLC Saitek P3200 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,
|
||||
03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,
|
||||
03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,
|
||||
03000000ad1b000038f0000090040000,Street Fighter IV FightStick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
03000000b40400000a01000000010000,CYPRESS USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,
|
||||
03000000b50700001503000010010000,impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,
|
||||
03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,
|
||||
03000000bc2000000055000010010000,ShanWan PS3/PC Wired GamePad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,
|
||||
03000000bc2000000055000011010000,GameSir G3w,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,
|
||||
03000000c01600008704000011010000,Serial/Keyboard/Mouse/Joystick,a:b12,b:b10,back:b4,dpdown:b2,dpleft:b3,dpright:b1,dpup:b0,leftshoulder:b9,leftstick:b14,lefttrigger:b6,leftx:a1,lefty:a0,rightshoulder:b8,rightstick:b15,righttrigger:b7,rightx:a2,righty:a3,start:b5,x:b13,y:b11,
|
||||
03000000c0160000e105000001010000,Xin-Mo Xin-Mo Dual Arcade,a:b4,b:b3,back:b6,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b9,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b1,y:b0,
|
||||
03000000c62400000053000000010000,PowerA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
03000000c6240000025b000002020000,Thrustmaster GPX Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
03000000c6240000045d000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
03000000c62400003a54000001010000,PowerA 1428124-01,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
03000000c82d00000190000011010000,8Bitdo NES30 Pro 8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,
|
||||
03000000c9110000f055000011010000,HJC Game GAMEPAD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,
|
||||
03000000d62000006dca000011010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
03000000d814000007cd000011010000,Toodles 2008 Chimp PC/PS3,a:b0,b:b1,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,
|
||||
03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,a:b1,b:b2,back:b8,guide:b9,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b12,x:b0,y:b3,
|
||||
03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,
|
||||
03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,
|
||||
03000000de2800004211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,
|
||||
03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
03000000e82000006058000001010000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,
|
||||
03000000f025000021c1000010010000,ShanWan Gioteck PS3 Wired Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,
|
||||
03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),a:b3,b:b4,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b7,x:b0,y:b1,
|
||||
03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,
|
||||
03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,
|
||||
05000000010000000100000003000000,Nintendo Wiimote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,
|
||||
05000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,
|
||||
05000000050b00000045000040000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,
|
||||
05000000102800000900000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,
|
||||
05000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,
|
||||
05000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,
|
||||
050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,
|
||||
05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,
|
||||
05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,
|
||||
05000000380700006652000025010000,Mad Catz C.T.R.L.R ,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,
|
||||
0500000047532067616d657061640000,GS gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,
|
||||
0500000049190000020400001b010000,Ipega PG-9069 - Bluetooth Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b161,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,
|
||||
05000000491900000204000021000000,Amazon Fire Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,
|
||||
050000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12,
|
||||
050000004c0500006802000000800000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b2,
|
||||
050000004c0500006802000000810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,
|
||||
050000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
050000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,
|
||||
050000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,
|
||||
050000004c050000cc09000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,
|
||||
050000004c050000cc09000001800000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,
|
||||
05000000504c415953544154494f4e00,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,
|
||||
05000000550900001472000001000000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,
|
||||
050000005e040000e002000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
||||
050000005e040000fd02000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,
|
||||
050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,
|
||||
050000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,
|
||||
050000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,
|
||||
050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,
|
||||
050000007e0500003003000001000000,Nintendo Wii Remote Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,
|
||||
05000000a00500003232000001000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,
|
||||
05000000ac0500003232000001000000,VR-BOX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,
|
||||
05000000c82d00000061000000010000,8Bitdo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,
|
||||
05000000c82d00001038000000010000,8Bitdo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,
|
||||
05000000c82d00002038000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,
|
||||
05000000c82d00003028000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,
|
||||
05000000d62000007162000001000000,Moga Pro 2 HID,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,
|
||||
05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,
|
||||
05000000d6200000e589000001000000,Moga 2 HID,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,
|
||||
05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,
|
||||
05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,
|
||||
05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,
|
||||
060000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,
|
||||
06000000adde0000efbe000002010000,Hidromancer Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,
|
@ -7,11 +7,11 @@ find_package(OpenSSL 1.0.2 REQUIRED)
|
||||
find_package(EXPAT REQUIRED)
|
||||
|
||||
pkg_check_modules(AVAHI REQUIRED avahi-client)
|
||||
pkg_check_modules(ENET REQUIRED libenet)
|
||||
|
||||
aux_source_directory(./ GAMESTREAM_SRC_LIST)
|
||||
aux_source_directory(../third_party/h264bitstream GAMESTREAM_SRC_LIST)
|
||||
|
||||
aux_source_directory(../third_party/moonlight-common-c/enet MOONLIGHT_COMMON_SRC_LIST)
|
||||
aux_source_directory(../third_party/moonlight-common-c/src MOONLIGHT_COMMON_SRC_LIST)
|
||||
aux_source_directory(../third_party/moonlight-common-c/reedsolomon MOONLIGHT_COMMON_SRC_LIST)
|
||||
|
||||
@ -23,10 +23,9 @@ target_link_libraries(gamestream moonlight-common)
|
||||
set_target_properties(gamestream PROPERTIES SOVERSION ${SO_VERSION} VERSION ${PROJECT_VERSION})
|
||||
set_target_properties(moonlight-common PROPERTIES SOVERSION ${SO_VERSION} VERSION ${PROJECT_VERSION})
|
||||
|
||||
target_include_directories(gamestream PRIVATE ../third_party/moonlight-common-c/src ../third_party/h264bitstream ${AVAHI_INCLUDE_DIRS} ${LIBUUID_INCLUDE_DIRS})
|
||||
target_include_directories(moonlight-common PRIVATE ../third_party/moonlight-common-c/reedsolomon ${ENET_INCLUDE_DIRS})
|
||||
target_link_libraries(gamestream ${CURL_LIBRARIES} ${OPENSSL_LIBRARIES} ${EXPAT_LIBRARIES} ${AVAHI_LIBRARIES} ${LIBUUID_LIBRARIES})
|
||||
target_link_libraries(moonlight-common ${ENET_LIBRARIES})
|
||||
target_include_directories(gamestream PRIVATE ../third_party/moonlight-common-c/src ../third_party/h264bitstream ${AVAHI_INCLUDE_DIRS} ${LibUUID_INCLUDE_DIRS})
|
||||
target_include_directories(moonlight-common PRIVATE ../third_party/moonlight-common-c/reedsolomon ../third_party/moonlight-common-c/enet/include)
|
||||
target_link_libraries(gamestream ${CURL_LIBRARIES} ${OPENSSL_LIBRARIES} ${EXPAT_LIBRARIES} ${AVAHI_LIBRARIES} ${LibUUID_LIBRARIES})
|
||||
|
||||
target_link_libraries(gamestream ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS})
|
||||
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <uuid/uuid.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/aes.h>
|
||||
@ -45,19 +46,18 @@
|
||||
#define UNIQUEID_BYTES 8
|
||||
#define UNIQUEID_CHARS (UNIQUEID_BYTES*2)
|
||||
|
||||
#define CHANNEL_COUNT_STEREO 2
|
||||
#define CHANNEL_COUNT_51_SURROUND 6
|
||||
|
||||
#define CHANNEL_MASK_STEREO 0x3
|
||||
#define CHANNEL_MASK_51_SURROUND 0xFC
|
||||
|
||||
static char unique_id[UNIQUEID_CHARS+1];
|
||||
static X509 *cert;
|
||||
static char cert_hex[4096];
|
||||
static char cert_hex[8192];
|
||||
static EVP_PKEY *privateKey;
|
||||
|
||||
const char* gs_error;
|
||||
|
||||
#define LEN_AS_HEX_STR(x) ((x) * 2 + 1)
|
||||
#define SIZEOF_AS_HEX_STR(x) LEN_AS_HEX_STR(sizeof(x))
|
||||
|
||||
#define UUID_STRLEN 37
|
||||
|
||||
static int mkdirtree(const char* directory) {
|
||||
char buffer[PATH_MAX];
|
||||
char* p = buffer;
|
||||
@ -92,19 +92,16 @@ static int load_unique_id(const char* keyDirectory) {
|
||||
snprintf(uniqueFilePath, PATH_MAX, "%s/%s", keyDirectory, UNIQUE_FILE_NAME);
|
||||
|
||||
FILE *fd = fopen(uniqueFilePath, "r");
|
||||
if (fd == NULL) {
|
||||
unsigned char unique_data[UNIQUEID_BYTES];
|
||||
RAND_bytes(unique_data, UNIQUEID_BYTES);
|
||||
for (int i = 0; i < UNIQUEID_BYTES; i++) {
|
||||
sprintf(unique_id + (i * 2), "%02x", unique_data[i]);
|
||||
}
|
||||
if (fd == NULL || fread(unique_id, UNIQUEID_CHARS, 1, fd) != UNIQUEID_CHARS) {
|
||||
snprintf(unique_id,UNIQUEID_CHARS+1,"0123456789ABCDEF");
|
||||
|
||||
if (fd)
|
||||
fclose(fd);
|
||||
fd = fopen(uniqueFilePath, "w");
|
||||
if (fd == NULL)
|
||||
return GS_FAILED;
|
||||
|
||||
fwrite(unique_id, UNIQUEID_CHARS, 1, fd);
|
||||
} else {
|
||||
fread(unique_id, UNIQUEID_CHARS, 1, fd);
|
||||
}
|
||||
fclose(fd);
|
||||
unique_id[UNIQUEID_CHARS] = 0;
|
||||
@ -167,109 +164,129 @@ static int load_cert(const char* keyDirectory) {
|
||||
return GS_OK;
|
||||
}
|
||||
|
||||
static int load_server_status(PSERVER_DATA server) {
|
||||
|
||||
static int load_serverinfo(PSERVER_DATA server, bool https) {
|
||||
uuid_t uuid;
|
||||
char uuid_str[37];
|
||||
|
||||
int ret;
|
||||
char uuid_str[UUID_STRLEN];
|
||||
char url[4096];
|
||||
int ret = GS_INVALID;
|
||||
char *pairedText = NULL;
|
||||
char *currentGameText = NULL;
|
||||
char *stateText = NULL;
|
||||
char *serverCodecModeSupportText = NULL;
|
||||
char *httpsPortText = NULL;
|
||||
|
||||
uuid_generate_random(uuid);
|
||||
uuid_unparse(uuid, uuid_str);
|
||||
|
||||
snprintf(url, sizeof(url), "%s://%s:%d/serverinfo?uniqueid=%s&uuid=%s",
|
||||
https ? "https" : "http", server->serverInfo.address, https ? server->httpsPort : server->httpPort, unique_id, uuid_str);
|
||||
|
||||
PHTTP_DATA data = http_create_data();
|
||||
if (data == NULL) {
|
||||
ret = GS_OUT_OF_MEMORY;
|
||||
goto cleanup;
|
||||
}
|
||||
if (http_request(url, data) != GS_OK) {
|
||||
ret = GS_IO_ERROR;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (xml_status(data->memory, data->size) == GS_ERROR) {
|
||||
ret = GS_ERROR;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (xml_search(data->memory, data->size, "currentgame", ¤tGameText) != GS_OK) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (xml_search(data->memory, data->size, "PairStatus", &pairedText) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "appversion", (char**) &server->serverInfo.serverInfoAppVersion) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "state", &stateText) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "ServerCodecModeSupport", &serverCodecModeSupportText) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "gputype", &server->gpuType) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "GsVersion", &server->gsVersion) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "GfeVersion", (char**) &server->serverInfo.serverInfoGfeVersion) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "HttpsPort", &httpsPortText) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_modelist(data->memory, data->size, &server->modes) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
// These fields are present on all version of GFE that this client supports
|
||||
if (!strlen(currentGameText) || !strlen(pairedText) || !strlen(server->serverInfo.serverInfoAppVersion) || !strlen(stateText))
|
||||
goto cleanup;
|
||||
|
||||
server->paired = pairedText != NULL && strcmp(pairedText, "1") == 0;
|
||||
server->currentGame = currentGameText == NULL ? 0 : atoi(currentGameText);
|
||||
server->serverInfo.serverCodecModeSupport = serverCodecModeSupportText == NULL ? SCM_H264 : atoi(serverCodecModeSupportText);
|
||||
server->serverMajorVersion = atoi(server->serverInfo.serverInfoAppVersion);
|
||||
server->isNvidiaSoftware = strstr(stateText, "MJOLNIR") != NULL;
|
||||
|
||||
server->httpsPort = atoi(httpsPortText);
|
||||
if (!server->httpsPort)
|
||||
server->httpsPort = 47984;
|
||||
|
||||
if (strstr(stateText, "_SERVER_BUSY") == NULL) {
|
||||
// After GFE 2.8, current game remains set even after streaming
|
||||
// has ended. We emulate the old behavior by forcing it to zero
|
||||
// if streaming is not active.
|
||||
server->currentGame = 0;
|
||||
}
|
||||
ret = GS_OK;
|
||||
|
||||
cleanup:
|
||||
if (data != NULL)
|
||||
http_free_data(data);
|
||||
|
||||
if (pairedText != NULL)
|
||||
free(pairedText);
|
||||
|
||||
if (currentGameText != NULL)
|
||||
free(currentGameText);
|
||||
|
||||
if (serverCodecModeSupportText != NULL)
|
||||
free(serverCodecModeSupportText);
|
||||
|
||||
if (httpsPortText != NULL)
|
||||
free(httpsPortText);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int load_server_status(PSERVER_DATA server) {
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
i = 0;
|
||||
do {
|
||||
char *pairedText = NULL;
|
||||
char *currentGameText = NULL;
|
||||
char *stateText = NULL;
|
||||
char *serverCodecModeSupportText = NULL;
|
||||
/* Fetch the HTTPS port if we don't have one yet */
|
||||
if (!server->httpsPort) {
|
||||
ret = load_serverinfo(server, false);
|
||||
if (ret != GS_OK)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = GS_INVALID;
|
||||
|
||||
uuid_generate_random(uuid);
|
||||
uuid_unparse(uuid, uuid_str);
|
||||
|
||||
// Modern GFE versions don't allow serverinfo to be fetched over HTTPS if the client
|
||||
// is not already paired. Since we can't pair without knowing the server version, we
|
||||
// make another request over HTTP if the HTTPS request fails. We can't just use HTTP
|
||||
// for everything because it doesn't accurately tell us if we're paired.
|
||||
snprintf(url, sizeof(url), "%s://%s:%d/serverinfo?uniqueid=%s&uuid=%s",
|
||||
i == 0 ? "https" : "http", server->serverInfo.address, i == 0 ? 47984 : 47989, unique_id, uuid_str);
|
||||
|
||||
PHTTP_DATA data = http_create_data();
|
||||
if (data == NULL) {
|
||||
ret = GS_OUT_OF_MEMORY;
|
||||
goto cleanup;
|
||||
}
|
||||
if (http_request(url, data) != GS_OK) {
|
||||
ret = GS_IO_ERROR;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (xml_status(data->memory, data->size) == GS_ERROR) {
|
||||
ret = GS_ERROR;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (xml_search(data->memory, data->size, "currentgame", ¤tGameText) != GS_OK) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (xml_search(data->memory, data->size, "PairStatus", &pairedText) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "appversion", (char**) &server->serverInfo.serverInfoAppVersion) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "state", &stateText) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "ServerCodecModeSupport", &serverCodecModeSupportText) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "gputype", &server->gpuType) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "GsVersion", &server->gsVersion) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "GfeVersion", (char**) &server->serverInfo.serverInfoGfeVersion) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_modelist(data->memory, data->size, &server->modes) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
// These fields are present on all version of GFE that this client supports
|
||||
if (!strlen(currentGameText) || !strlen(pairedText) || !strlen(server->serverInfo.serverInfoAppVersion) || !strlen(stateText))
|
||||
goto cleanup;
|
||||
|
||||
server->paired = pairedText != NULL && strcmp(pairedText, "1") == 0;
|
||||
server->currentGame = currentGameText == NULL ? 0 : atoi(currentGameText);
|
||||
server->supports4K = serverCodecModeSupportText != NULL;
|
||||
server->serverMajorVersion = atoi(server->serverInfo.serverInfoAppVersion);
|
||||
|
||||
if (strstr(stateText, "_SERVER_BUSY") == NULL) {
|
||||
// After GFE 2.8, current game remains set even after streaming
|
||||
// has ended. We emulate the old behavior by forcing it to zero
|
||||
// if streaming is not active.
|
||||
server->currentGame = 0;
|
||||
}
|
||||
ret = GS_OK;
|
||||
|
||||
cleanup:
|
||||
if (data != NULL)
|
||||
http_free_data(data);
|
||||
|
||||
if (pairedText != NULL)
|
||||
free(pairedText);
|
||||
|
||||
if (currentGameText != NULL)
|
||||
free(currentGameText);
|
||||
|
||||
if (serverCodecModeSupportText != NULL)
|
||||
free(serverCodecModeSupportText);
|
||||
|
||||
i++;
|
||||
} while (ret != GS_OK && i < 2);
|
||||
// Modern GFE versions don't allow serverinfo to be fetched over HTTPS if the client
|
||||
// is not already paired. Since we can't pair without knowing the server version, we
|
||||
// make another request over HTTP if the HTTPS request fails. We can't just use HTTP
|
||||
// for everything because it doesn't accurately tell us if we're paired.
|
||||
ret = GS_INVALID;
|
||||
for (i = 0; i < 2 && ret != GS_OK; i++) {
|
||||
ret = load_serverinfo(server, i == 0);
|
||||
}
|
||||
|
||||
if (ret == GS_OK && !server->unsupported) {
|
||||
if (server->serverMajorVersion > MAX_SUPPORTED_GFE_VERSION) {
|
||||
@ -291,6 +308,12 @@ static void bytes_to_hex(unsigned char *in, char *out, size_t len) {
|
||||
out[len * 2] = 0;
|
||||
}
|
||||
|
||||
static void hex_to_bytes(const char *in, unsigned char* out, size_t len) {
|
||||
for (int count = 0; count < len; count += 2) {
|
||||
sscanf(&in[count], "%2hhx", &out[count / 2]);
|
||||
}
|
||||
}
|
||||
|
||||
static int sign_it(const char *msg, size_t mlen, unsigned char **sig, size_t *slen, EVP_PKEY *pkey) {
|
||||
int result = GS_FAILED;
|
||||
|
||||
@ -301,15 +324,7 @@ static int sign_it(const char *msg, size_t mlen, unsigned char **sig, size_t *sl
|
||||
if (ctx == NULL)
|
||||
return GS_FAILED;
|
||||
|
||||
const EVP_MD *md = EVP_get_digestbyname("SHA256");
|
||||
if (md == NULL)
|
||||
goto cleanup;
|
||||
|
||||
int rc = EVP_DigestInit_ex(ctx, md, NULL);
|
||||
if (rc != 1)
|
||||
goto cleanup;
|
||||
|
||||
rc = EVP_DigestSignInit(ctx, NULL, md, NULL, pkey);
|
||||
int rc = EVP_DigestSignInit(ctx, NULL, EVP_sha256(), NULL, pkey);
|
||||
if (rc != 1)
|
||||
goto cleanup;
|
||||
|
||||
@ -345,39 +360,63 @@ static bool verifySignature(const char *data, int dataLength, char *signature, i
|
||||
BIO* bio = BIO_new(BIO_s_mem());
|
||||
BIO_puts(bio, cert);
|
||||
x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
||||
|
||||
|
||||
BIO_free(bio);
|
||||
|
||||
|
||||
if (!x509) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
EVP_PKEY* pubKey = X509_get_pubkey(x509);
|
||||
EVP_MD_CTX *mdctx = NULL;
|
||||
mdctx = EVP_MD_CTX_create();
|
||||
EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pubKey);
|
||||
EVP_DigestVerifyUpdate(mdctx, data, dataLength);
|
||||
int result = EVP_DigestVerifyFinal(mdctx, signature, signatureLength);
|
||||
|
||||
|
||||
X509_free(x509);
|
||||
EVP_PKEY_free(pubKey);
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
|
||||
|
||||
return result > 0;
|
||||
}
|
||||
|
||||
static void encrypt(const unsigned char *plaintext, int plaintextLen, const unsigned char *key, unsigned char *ciphertext) {
|
||||
EVP_CIPHER_CTX* cipher = EVP_CIPHER_CTX_new();
|
||||
|
||||
EVP_EncryptInit(cipher, EVP_aes_128_ecb(), key, NULL);
|
||||
EVP_CIPHER_CTX_set_padding(cipher, 0);
|
||||
|
||||
int ciphertextLen = 0;
|
||||
EVP_EncryptUpdate(cipher, ciphertext, &ciphertextLen, plaintext, plaintextLen);
|
||||
|
||||
EVP_CIPHER_CTX_free(cipher);
|
||||
}
|
||||
|
||||
static void decrypt(const unsigned char *ciphertext, int ciphertextLen, const unsigned char *key, unsigned char *plaintext) {
|
||||
EVP_CIPHER_CTX* cipher = EVP_CIPHER_CTX_new();
|
||||
|
||||
EVP_DecryptInit(cipher, EVP_aes_128_ecb(), key, NULL);
|
||||
EVP_CIPHER_CTX_set_padding(cipher, 0);
|
||||
|
||||
int plaintextLen = 0;
|
||||
EVP_DecryptUpdate(cipher, plaintext, &plaintextLen, ciphertext, ciphertextLen);
|
||||
|
||||
EVP_CIPHER_CTX_free(cipher);
|
||||
}
|
||||
|
||||
int gs_unpair(PSERVER_DATA server) {
|
||||
int ret = GS_OK;
|
||||
char url[4096];
|
||||
uuid_t uuid;
|
||||
char uuid_str[37];
|
||||
char uuid_str[UUID_STRLEN];
|
||||
PHTTP_DATA data = http_create_data();
|
||||
if (data == NULL)
|
||||
return GS_OUT_OF_MEMORY;
|
||||
|
||||
uuid_generate_random(uuid);
|
||||
uuid_unparse(uuid, uuid_str);
|
||||
snprintf(url, sizeof(url), "http://%s:47989/unpair?uniqueid=%s&uuid=%s", server->serverInfo.address, unique_id, uuid_str);
|
||||
snprintf(url, sizeof(url), "http://%s:%u/unpair?uniqueid=%s&uuid=%s", server->serverInfo.address, server->httpPort, unique_id, uuid_str);
|
||||
ret = http_request(url, data);
|
||||
|
||||
http_free_data(data);
|
||||
@ -387,29 +426,32 @@ int gs_unpair(PSERVER_DATA server) {
|
||||
int gs_pair(PSERVER_DATA server, char* pin) {
|
||||
int ret = GS_OK;
|
||||
char* result = NULL;
|
||||
char url[4096];
|
||||
size_t url_max_len = 16384;
|
||||
char* url = malloc(url_max_len);
|
||||
uuid_t uuid;
|
||||
char uuid_str[37];
|
||||
char uuid_str[UUID_STRLEN];
|
||||
char* plaincert = NULL;
|
||||
char* challenge_response = NULL;
|
||||
char* pairing_secret = NULL;
|
||||
char* client_pairing_secret = NULL;
|
||||
char* client_pairing_secret_hex = NULL;
|
||||
PHTTP_DATA data = NULL;
|
||||
|
||||
if (server->paired) {
|
||||
gs_error = "Already paired";
|
||||
return GS_WRONG_STATE;
|
||||
}
|
||||
|
||||
if (server->currentGame != 0) {
|
||||
gs_error = "The computer is currently in a game. You must close the game before pairing";
|
||||
return GS_WRONG_STATE;
|
||||
ret = GS_WRONG_STATE;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
unsigned char salt_data[16];
|
||||
char salt_hex[33];
|
||||
RAND_bytes(salt_data, 16);
|
||||
bytes_to_hex(salt_data, salt_hex, 16);
|
||||
char salt_hex[SIZEOF_AS_HEX_STR(salt_data)];
|
||||
RAND_bytes(salt_data, sizeof(salt_data));
|
||||
bytes_to_hex(salt_data, salt_hex, sizeof(salt_data));
|
||||
|
||||
uuid_generate_random(uuid);
|
||||
uuid_unparse(uuid, uuid_str);
|
||||
snprintf(url, sizeof(url), "http://%s:47989/pair?uniqueid=%s&uuid=%s&devicename=roth&updateState=1&phrase=getservercert&salt=%s&clientcert=%s", server->serverInfo.address, unique_id, uuid_str, salt_hex, cert_hex);
|
||||
PHTTP_DATA data = http_create_data();
|
||||
snprintf(url, url_max_len, "http://%s:%u/pair?uniqueid=%s&uuid=%s&devicename=roth&updateState=1&phrase=getservercert&salt=%s&clientcert=%s", server->serverInfo.address, server->httpPort, unique_id, uuid_str, salt_hex, cert_hex);
|
||||
data = http_create_data();
|
||||
if (data == NULL)
|
||||
return GS_OUT_OF_MEMORY;
|
||||
else if ((ret = http_request(url, data)) != GS_OK)
|
||||
@ -431,43 +473,33 @@ int gs_pair(PSERVER_DATA server, char* pin) {
|
||||
if ((ret = xml_search(data->memory, data->size, "plaincert", &result)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (strlen(result)/2 > 8191) {
|
||||
gs_error = "Server certificate too big";
|
||||
ret = GS_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
char plaincert[8192];
|
||||
for (int count = 0; count < strlen(result); count += 2) {
|
||||
sscanf(&result[count], "%2hhx", &plaincert[count / 2]);
|
||||
}
|
||||
plaincert[strlen(result)/2] = '\0';
|
||||
size_t plaincertlen = strlen(result)/2;
|
||||
plaincert = malloc(plaincertlen + 1);
|
||||
hex_to_bytes(result, plaincert, plaincertlen*2);
|
||||
plaincert[plaincertlen] = 0;
|
||||
|
||||
unsigned char salt_pin[20];
|
||||
unsigned char aes_key_hash[32];
|
||||
AES_KEY enc_key, dec_key;
|
||||
memcpy(salt_pin, salt_data, 16);
|
||||
memcpy(salt_pin+16, pin, 4);
|
||||
unsigned char salt_pin[sizeof(salt_data) + 4];
|
||||
unsigned char aes_key[32]; // Must fit SHA256
|
||||
memcpy(salt_pin, salt_data, sizeof(salt_data));
|
||||
memcpy(salt_pin+sizeof(salt_data), pin, 4);
|
||||
|
||||
int hash_length = server->serverMajorVersion >= 7 ? 32 : 20;
|
||||
if (server->serverMajorVersion >= 7)
|
||||
SHA256(salt_pin, 20, aes_key_hash);
|
||||
SHA256(salt_pin, sizeof(salt_pin), aes_key);
|
||||
else
|
||||
SHA1(salt_pin, 20, aes_key_hash);
|
||||
|
||||
AES_set_encrypt_key((unsigned char *)aes_key_hash, 128, &enc_key);
|
||||
AES_set_decrypt_key((unsigned char *)aes_key_hash, 128, &dec_key);
|
||||
SHA1(salt_pin, sizeof(salt_pin), aes_key);
|
||||
|
||||
unsigned char challenge_data[16];
|
||||
unsigned char challenge_enc[16];
|
||||
char challenge_hex[33];
|
||||
RAND_bytes(challenge_data, 16);
|
||||
AES_encrypt(challenge_data, challenge_enc, &enc_key);
|
||||
bytes_to_hex(challenge_enc, challenge_hex, 16);
|
||||
unsigned char challenge_enc[sizeof(challenge_data)];
|
||||
char challenge_hex[SIZEOF_AS_HEX_STR(challenge_enc)];
|
||||
RAND_bytes(challenge_data, sizeof(challenge_data));
|
||||
encrypt(challenge_data, sizeof(challenge_data), aes_key, challenge_enc);
|
||||
bytes_to_hex(challenge_enc, challenge_hex, sizeof(challenge_enc));
|
||||
|
||||
uuid_generate_random(uuid);
|
||||
uuid_unparse(uuid, uuid_str);
|
||||
snprintf(url, sizeof(url), "http://%s:47989/pair?uniqueid=%s&uuid=%s&devicename=roth&updateState=1&clientchallenge=%s", server->serverInfo.address, unique_id, uuid_str, challenge_hex);
|
||||
snprintf(url, url_max_len, "http://%s:%u/pair?uniqueid=%s&uuid=%s&devicename=roth&updateState=1&clientchallenge=%s", server->serverInfo.address, server->httpPort, unique_id, uuid_str, challenge_hex);
|
||||
if ((ret = http_request(url, data)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
@ -491,42 +523,42 @@ int gs_pair(PSERVER_DATA server, char* pin) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
char challenge_response_data_enc[48];
|
||||
char challenge_response_data[48];
|
||||
for (int count = 0; count < strlen(result); count += 2) {
|
||||
sscanf(&result[count], "%2hhx", &challenge_response_data_enc[count / 2]);
|
||||
char challenge_response_data_enc[64];
|
||||
char challenge_response_data[sizeof(challenge_response_data_enc)];
|
||||
|
||||
if (strlen(result) / 2 > sizeof(challenge_response_data_enc)) {
|
||||
gs_error = "Server challenge response too big";
|
||||
ret = GS_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 48; i += 16) {
|
||||
AES_decrypt(&challenge_response_data_enc[i], &challenge_response_data[i], &dec_key);
|
||||
}
|
||||
hex_to_bytes(result, challenge_response_data_enc, strlen(result));
|
||||
decrypt(challenge_response_data_enc, sizeof(challenge_response_data_enc), aes_key, challenge_response_data);
|
||||
|
||||
char client_secret_data[16];
|
||||
RAND_bytes(client_secret_data, 16);
|
||||
RAND_bytes(client_secret_data, sizeof(client_secret_data));
|
||||
|
||||
const ASN1_BIT_STRING *asnSignature;
|
||||
X509_get0_signature(&asnSignature, NULL, cert);
|
||||
|
||||
char challenge_response[16 + 256 + 16];
|
||||
challenge_response = malloc(16 + asnSignature->length + sizeof(client_secret_data));
|
||||
char challenge_response_hash[32];
|
||||
char challenge_response_hash_enc[32];
|
||||
char challenge_response_hex[65];
|
||||
char challenge_response_hash_enc[sizeof(challenge_response_hash)];
|
||||
char challenge_response_hex[SIZEOF_AS_HEX_STR(challenge_response_hash_enc)];
|
||||
memcpy(challenge_response, challenge_response_data + hash_length, 16);
|
||||
memcpy(challenge_response + 16, asnSignature->data, 256);
|
||||
memcpy(challenge_response + 16 + 256, client_secret_data, 16);
|
||||
memcpy(challenge_response + 16, asnSignature->data, asnSignature->length);
|
||||
memcpy(challenge_response + 16 + asnSignature->length, client_secret_data, sizeof(client_secret_data));
|
||||
if (server->serverMajorVersion >= 7)
|
||||
SHA256(challenge_response, 16 + 256 + 16, challenge_response_hash);
|
||||
SHA256(challenge_response, 16 + asnSignature->length + sizeof(client_secret_data), challenge_response_hash);
|
||||
else
|
||||
SHA1(challenge_response, 16 + 256 + 16, challenge_response_hash);
|
||||
SHA1(challenge_response, 16 + asnSignature->length + sizeof(client_secret_data), challenge_response_hash);
|
||||
|
||||
for (int i = 0; i < 32; i += 16) {
|
||||
AES_encrypt(&challenge_response_hash[i], &challenge_response_hash_enc[i], &enc_key);
|
||||
}
|
||||
bytes_to_hex(challenge_response_hash_enc, challenge_response_hex, 32);
|
||||
encrypt(challenge_response_hash, sizeof(challenge_response_hash), aes_key, challenge_response_hash_enc);
|
||||
bytes_to_hex(challenge_response_hash_enc, challenge_response_hex, sizeof(challenge_response_hash_enc));
|
||||
|
||||
uuid_generate_random(uuid);
|
||||
uuid_unparse(uuid, uuid_str);
|
||||
snprintf(url, sizeof(url), "http://%s:47989/pair?uniqueid=%s&uuid=%s&devicename=roth&updateState=1&serverchallengeresp=%s", server->serverInfo.address, unique_id, uuid_str, challenge_response_hex);
|
||||
snprintf(url, url_max_len, "http://%s:%u/pair?uniqueid=%s&uuid=%s&devicename=roth&updateState=1&serverchallengeresp=%s", server->serverInfo.address, server->httpPort, unique_id, uuid_str, challenge_response_hex);
|
||||
if ((ret = http_request(url, data)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
@ -550,12 +582,15 @@ int gs_pair(PSERVER_DATA server, char* pin) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
char pairing_secret[16 + 256];
|
||||
for (int count = 0; count < strlen(result); count += 2) {
|
||||
sscanf(&result[count], "%2hhx", &pairing_secret[count / 2]);
|
||||
size_t pairing_secret_len = strlen(result) / 2;
|
||||
if (pairing_secret_len <= 16) {
|
||||
ret = GS_INVALID;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!verifySignature(pairing_secret, 16, pairing_secret+16, 256, plaincert)) {
|
||||
pairing_secret = malloc(pairing_secret_len);
|
||||
hex_to_bytes(result, pairing_secret, pairing_secret_len*2);
|
||||
if (!verifySignature(pairing_secret, 16, pairing_secret+16, pairing_secret_len-16, plaincert)) {
|
||||
gs_error = "MITM attack detected";
|
||||
ret = GS_FAILED;
|
||||
goto cleanup;
|
||||
@ -563,21 +598,21 @@ int gs_pair(PSERVER_DATA server, char* pin) {
|
||||
|
||||
unsigned char *signature = NULL;
|
||||
size_t s_len;
|
||||
if (sign_it(client_secret_data, 16, &signature, &s_len, privateKey) != GS_OK) {
|
||||
if (sign_it(client_secret_data, sizeof(client_secret_data), &signature, &s_len, privateKey) != GS_OK) {
|
||||
gs_error = "Failed to sign data";
|
||||
ret = GS_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
char client_pairing_secret[16 + 256];
|
||||
char client_pairing_secret_hex[(16 + 256) * 2 + 1];
|
||||
memcpy(client_pairing_secret, client_secret_data, 16);
|
||||
memcpy(client_pairing_secret + 16, signature, 256);
|
||||
bytes_to_hex(client_pairing_secret, client_pairing_secret_hex, 16 + 256);
|
||||
client_pairing_secret = malloc(sizeof(client_secret_data) + s_len);
|
||||
client_pairing_secret_hex = malloc(LEN_AS_HEX_STR(sizeof(client_secret_data) + s_len));
|
||||
memcpy(client_pairing_secret, client_secret_data, sizeof(client_secret_data));
|
||||
memcpy(client_pairing_secret + sizeof(client_secret_data), signature, s_len);
|
||||
bytes_to_hex(client_pairing_secret, client_pairing_secret_hex, sizeof(client_secret_data) + s_len);
|
||||
|
||||
uuid_generate_random(uuid);
|
||||
uuid_unparse(uuid, uuid_str);
|
||||
snprintf(url, sizeof(url), "http://%s:47989/pair?uniqueid=%s&uuid=%s&devicename=roth&updateState=1&clientpairingsecret=%s", server->serverInfo.address, unique_id, uuid_str, client_pairing_secret_hex);
|
||||
snprintf(url, url_max_len, "http://%s:%u/pair?uniqueid=%s&uuid=%s&devicename=roth&updateState=1&clientpairingsecret=%s", server->serverInfo.address, server->httpPort, unique_id, uuid_str, client_pairing_secret_hex);
|
||||
if ((ret = http_request(url, data)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
@ -596,7 +631,7 @@ int gs_pair(PSERVER_DATA server, char* pin) {
|
||||
|
||||
uuid_generate_random(uuid);
|
||||
uuid_unparse(uuid, uuid_str);
|
||||
snprintf(url, sizeof(url), "https://%s:47984/pair?uniqueid=%s&uuid=%s&devicename=roth&updateState=1&phrase=pairchallenge", server->serverInfo.address, unique_id, uuid_str);
|
||||
snprintf(url, url_max_len, "https://%s:%u/pair?uniqueid=%s&uuid=%s&devicename=roth&updateState=1&phrase=pairchallenge", server->serverInfo.address, server->httpsPort, unique_id, uuid_str);
|
||||
if ((ret = http_request(url, data)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
@ -618,12 +653,24 @@ int gs_pair(PSERVER_DATA server, char* pin) {
|
||||
cleanup:
|
||||
if (ret != GS_OK)
|
||||
gs_unpair(server);
|
||||
|
||||
if (result != NULL)
|
||||
free(result);
|
||||
|
||||
free(url);
|
||||
free(plaincert);
|
||||
free(challenge_response);
|
||||
free(pairing_secret);
|
||||
free(client_pairing_secret);
|
||||
free(client_pairing_secret_hex);
|
||||
free(result);
|
||||
|
||||
http_free_data(data);
|
||||
|
||||
// If we failed when attempting to pair with a game running, that's likely the issue.
|
||||
// Sunshine supports pairing with an active session, but GFE does not.
|
||||
if (ret != GS_OK && server->currentGame != 0) {
|
||||
gs_error = "The computer is currently in a game. You must close the game before pairing.";
|
||||
ret = GS_WRONG_STATE;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -631,14 +678,14 @@ int gs_applist(PSERVER_DATA server, PAPP_LIST *list) {
|
||||
int ret = GS_OK;
|
||||
char url[4096];
|
||||
uuid_t uuid;
|
||||
char uuid_str[37];
|
||||
char uuid_str[UUID_STRLEN];
|
||||
PHTTP_DATA data = http_create_data();
|
||||
if (data == NULL)
|
||||
return GS_OUT_OF_MEMORY;
|
||||
|
||||
uuid_generate_random(uuid);
|
||||
uuid_unparse(uuid, uuid_str);
|
||||
snprintf(url, sizeof(url), "https://%s:47984/applist?uniqueid=%s&uuid=%s", server->serverInfo.address, unique_id, uuid_str);
|
||||
snprintf(url, sizeof(url), "https://%s:%u/applist?uniqueid=%s&uuid=%s", server->serverInfo.address, server->httpsPort, unique_id, uuid_str);
|
||||
if (http_request(url, data) != GS_OK)
|
||||
ret = GS_IO_ERROR;
|
||||
else if (xml_status(data->memory, data->size) == GS_ERROR)
|
||||
@ -654,13 +701,15 @@ int gs_start_app(PSERVER_DATA server, STREAM_CONFIGURATION *config, int appId, b
|
||||
int ret = GS_OK;
|
||||
uuid_t uuid;
|
||||
char* result = NULL;
|
||||
char uuid_str[37];
|
||||
char uuid_str[UUID_STRLEN];
|
||||
|
||||
PDISPLAY_MODE mode = server->modes;
|
||||
bool correct_mode = false;
|
||||
while (mode != NULL) {
|
||||
if (mode->width == config->width && mode->height == config->height && mode->refresh == config->fps)
|
||||
correct_mode = true;
|
||||
if (mode->width == config->width && mode->height == config->height) {
|
||||
if (mode->refresh == config->fps)
|
||||
correct_mode = true;
|
||||
}
|
||||
|
||||
mode = mode->next;
|
||||
}
|
||||
@ -668,31 +717,34 @@ int gs_start_app(PSERVER_DATA server, STREAM_CONFIGURATION *config, int appId, b
|
||||
if (!correct_mode && !server->unsupported)
|
||||
return GS_NOT_SUPPORTED_MODE;
|
||||
|
||||
if (config->height >= 2160 && !server->supports4K)
|
||||
return GS_NOT_SUPPORTED_4K;
|
||||
RAND_bytes(config->remoteInputAesKey, sizeof(config->remoteInputAesKey));
|
||||
memset(config->remoteInputAesIv, 0, sizeof(config->remoteInputAesIv));
|
||||
|
||||
RAND_bytes(config->remoteInputAesKey, 16);
|
||||
memset(config->remoteInputAesIv, 0, 16);
|
||||
|
||||
srand(time(NULL));
|
||||
char url[4096];
|
||||
u_int32_t rikeyid = 0;
|
||||
char rikey_hex[33];
|
||||
bytes_to_hex(config->remoteInputAesKey, rikey_hex, 16);
|
||||
RAND_bytes(config->remoteInputAesIv, sizeof(rikeyid));
|
||||
memcpy(&rikeyid, config->remoteInputAesIv, sizeof(rikeyid));
|
||||
rikeyid = htonl(rikeyid);
|
||||
char rikey_hex[SIZEOF_AS_HEX_STR(config->remoteInputAesKey)];
|
||||
bytes_to_hex(config->remoteInputAesKey, rikey_hex, sizeof(config->remoteInputAesKey));
|
||||
|
||||
PHTTP_DATA data = http_create_data();
|
||||
if (data == NULL)
|
||||
return GS_OUT_OF_MEMORY;
|
||||
|
||||
// Using an FPS value over 60 causes SOPS to default to 720p60,
|
||||
// so force it to 0 to ensure the correct resolution is set. We
|
||||
// used to use 60 here but that locked the frame rate to 60 FPS
|
||||
// on GFE 3.20.3.
|
||||
int fps = (server->isNvidiaSoftware && config->fps > 60) ? 0 : config->fps;
|
||||
|
||||
uuid_generate_random(uuid);
|
||||
uuid_unparse(uuid, uuid_str);
|
||||
if (server->currentGame == 0) {
|
||||
int channelCounnt = config->audioConfiguration == AUDIO_CONFIGURATION_STEREO ? CHANNEL_COUNT_STEREO : CHANNEL_COUNT_51_SURROUND;
|
||||
int mask = config->audioConfiguration == AUDIO_CONFIGURATION_STEREO ? CHANNEL_MASK_STEREO : CHANNEL_MASK_51_SURROUND;
|
||||
snprintf(url, sizeof(url), "https://%s:47984/launch?uniqueid=%s&uuid=%s&appid=%d&mode=%dx%dx%d&additionalStates=1&sops=%d&rikey=%s&rikeyid=%d&localAudioPlayMode=%d&surroundAudioInfo=%d&remoteControllersBitmap=%d&gcmap=%d", server->serverInfo.address, unique_id, uuid_str, appId, config->width, config->height, config->fps, sops, rikey_hex, rikeyid, localaudio, (mask << 16) + channelCounnt, gamepad_mask, gamepad_mask);
|
||||
} else
|
||||
snprintf(url, sizeof(url), "https://%s:47984/resume?uniqueid=%s&uuid=%s&rikey=%s&rikeyid=%d", server->serverInfo.address, unique_id, uuid_str, rikey_hex, rikeyid);
|
||||
|
||||
int surround_info = SURROUNDAUDIOINFO_FROM_AUDIO_CONFIGURATION(config->audioConfiguration);
|
||||
snprintf(url, sizeof(url), "https://%s:%u/%s?uniqueid=%s&uuid=%s&appid=%d&mode=%dx%dx%d&additionalStates=1&sops=%d&rikey=%s&rikeyid=%d&localAudioPlayMode=%d&surroundAudioInfo=%d&remoteControllersBitmap=%d&gcmap=%d%s%s",
|
||||
server->serverInfo.address, server->httpsPort, server->currentGame ? "resume" : "launch", unique_id, uuid_str, appId, config->width, config->height, fps, sops, rikey_hex, rikeyid, localaudio, surround_info, gamepad_mask, gamepad_mask,
|
||||
(config->supportedVideoFormats & VIDEO_FORMAT_MASK_10BIT) ? "&hdrMode=1&clientHdrCapVersion=0&clientHdrCapSupportedFlagsInUint32=0&clientHdrCapMetaDataId=NV_STATIC_METADATA_TYPE_1&clientHdrCapDisplayData=0x0x0x0x0x0x0x0x0x0x0" : "",
|
||||
LiGetLaunchUrlQueryParameters());
|
||||
if ((ret = http_request(url, data)) == GS_OK)
|
||||
server->currentGame = appId;
|
||||
else
|
||||
@ -700,7 +752,8 @@ int gs_start_app(PSERVER_DATA server, STREAM_CONFIGURATION *config, int appId, b
|
||||
|
||||
if ((ret = xml_status(data->memory, data->size) != GS_OK))
|
||||
goto cleanup;
|
||||
else if ((ret = xml_search(data->memory, data->size, "gamesession", &result)) != GS_OK)
|
||||
else if ((ret = xml_search(data->memory, data->size, "gamesession", &result)) != GS_OK &&
|
||||
(ret = xml_search(data->memory, data->size, "resume", &result)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (!strcmp(result, "0")) {
|
||||
@ -708,6 +761,14 @@ int gs_start_app(PSERVER_DATA server, STREAM_CONFIGURATION *config, int appId, b
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
free(result);
|
||||
result = NULL;
|
||||
|
||||
if (xml_search(data->memory, data->size, "sessionUrl0", &result) == GS_OK) {
|
||||
server->serverInfo.rtspSessionUrl = result;
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (result != NULL)
|
||||
free(result);
|
||||
@ -720,7 +781,7 @@ int gs_quit_app(PSERVER_DATA server) {
|
||||
int ret = GS_OK;
|
||||
char url[4096];
|
||||
uuid_t uuid;
|
||||
char uuid_str[37];
|
||||
char uuid_str[UUID_STRLEN];
|
||||
char* result = NULL;
|
||||
PHTTP_DATA data = http_create_data();
|
||||
if (data == NULL)
|
||||
@ -728,7 +789,7 @@ int gs_quit_app(PSERVER_DATA server) {
|
||||
|
||||
uuid_generate_random(uuid);
|
||||
uuid_unparse(uuid, uuid_str);
|
||||
snprintf(url, sizeof(url), "https://%s:47984/cancel?uniqueid=%s&uuid=%s", server->serverInfo.address, unique_id, uuid_str);
|
||||
snprintf(url, sizeof(url), "https://%s:%u/cancel?uniqueid=%s&uuid=%s", server->serverInfo.address, server->httpsPort, unique_id, uuid_str);
|
||||
if ((ret = http_request(url, data)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
@ -750,7 +811,7 @@ int gs_quit_app(PSERVER_DATA server) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
int gs_init(PSERVER_DATA server, char *address, const char *keyDirectory, int log_level, bool unsupported) {
|
||||
int gs_init(PSERVER_DATA server, char *address, unsigned short httpPort, const char *keyDirectory, int log_level, bool unsupported) {
|
||||
mkdirtree(keyDirectory);
|
||||
if (load_unique_id(keyDirectory) != GS_OK)
|
||||
return GS_FAILED;
|
||||
@ -763,5 +824,7 @@ int gs_init(PSERVER_DATA server, char *address, const char *keyDirectory, int lo
|
||||
LiInitializeServerInformation(&server->serverInfo);
|
||||
server->serverInfo.address = address;
|
||||
server->unsupported = unsupported;
|
||||
server->httpPort = httpPort ? httpPort : 47989;
|
||||
server->httpsPort = 0; /* Populated by load_server_status() */
|
||||
return load_server_status(server);
|
||||
}
|
||||
|
@ -29,19 +29,20 @@
|
||||
#define MAX_SUPPORTED_GFE_VERSION 7
|
||||
|
||||
typedef struct _SERVER_DATA {
|
||||
const char* address;
|
||||
char* gpuType;
|
||||
bool paired;
|
||||
bool supports4K;
|
||||
bool unsupported;
|
||||
bool isNvidiaSoftware;
|
||||
int currentGame;
|
||||
int serverMajorVersion;
|
||||
char* gsVersion;
|
||||
PDISPLAY_MODE modes;
|
||||
SERVER_INFORMATION serverInfo;
|
||||
unsigned short httpPort;
|
||||
unsigned short httpsPort;
|
||||
} SERVER_DATA, *PSERVER_DATA;
|
||||
|
||||
int gs_init(PSERVER_DATA server, char* address, const char *keyDirectory, int logLevel, bool unsupported);
|
||||
int gs_init(PSERVER_DATA server, char* address, unsigned short httpPort, const char *keyDirectory, int logLevel, bool unsupported);
|
||||
int gs_start_app(PSERVER_DATA server, PSTREAM_CONFIGURATION config, int appId, bool sops, bool localaudio, int gamepad_mask);
|
||||
int gs_applist(PSERVER_DATA server, PAPP_LIST *app_list);
|
||||
int gs_unpair(PSERVER_DATA server);
|
||||
|
@ -33,6 +33,11 @@
|
||||
|
||||
static AvahiSimplePoll *simple_poll = NULL;
|
||||
|
||||
struct cb_ctx {
|
||||
char* address;
|
||||
unsigned short* port;
|
||||
};
|
||||
|
||||
static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
|
||||
if (state == AVAHI_CLIENT_FAILURE) {
|
||||
gs_error = "Server connection failure";
|
||||
@ -43,12 +48,14 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda
|
||||
static void resolve_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void *userdata) {
|
||||
if (event == AVAHI_RESOLVER_FOUND) {
|
||||
if (userdata != NULL) {
|
||||
avahi_address_snprint(userdata, AVAHI_ADDRESS_STR_MAX, address);
|
||||
struct cb_ctx* ctx = userdata;
|
||||
avahi_address_snprint(ctx->address, AVAHI_ADDRESS_STR_MAX, address);
|
||||
*ctx->port = port;
|
||||
avahi_simple_poll_quit(simple_poll);
|
||||
} else {
|
||||
char strAddress[AVAHI_ADDRESS_STR_MAX];
|
||||
avahi_address_snprint(strAddress, sizeof(strAddress), address);
|
||||
printf(" %s (%s)\n", host_name, strAddress);
|
||||
printf(" %s (%s:%u)\n", host_name, strAddress, port);
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,7 +80,7 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah
|
||||
}
|
||||
}
|
||||
|
||||
void gs_discover_server(char* dest) {
|
||||
void gs_discover_server(char* dest, unsigned short* port) {
|
||||
AvahiClient *client = NULL;
|
||||
AvahiServiceBrowser *sb = NULL;
|
||||
|
||||
@ -89,7 +96,10 @@ void gs_discover_server(char* dest) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, "_nvstream._tcp", NULL, 0, browse_callback, dest))) {
|
||||
struct cb_ctx ctx;
|
||||
ctx.address = dest;
|
||||
ctx.port = port;
|
||||
if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, "_nvstream._tcp", NULL, 0, browse_callback, &ctx))) {
|
||||
gs_error = "Failed to create service browser";
|
||||
goto cleanup;
|
||||
}
|
||||
|
@ -21,4 +21,4 @@
|
||||
|
||||
#define MAX_ADDRESS_SIZE 40
|
||||
|
||||
void gs_discover_server(char* dest);
|
||||
void gs_discover_server(char* dest, unsigned short* port);
|
||||
|
@ -29,5 +29,6 @@
|
||||
#define GS_UNSUPPORTED_VERSION -7
|
||||
#define GS_NOT_SUPPORTED_MODE -8
|
||||
#define GS_ERROR -9
|
||||
#define GS_NOT_SUPPORTED_SOPS_RESOLUTION -10
|
||||
|
||||
extern const char* gs_error;
|
||||
|
@ -26,24 +26,21 @@
|
||||
|
||||
static CURL *curl;
|
||||
|
||||
static const char *pCertFile = "./client.pem";
|
||||
static const char *pKeyFile = "./key.pem";
|
||||
|
||||
static bool debug;
|
||||
|
||||
static size_t _write_curl(void *contents, size_t size, size_t nmemb, void *userp)
|
||||
{
|
||||
size_t realsize = size * nmemb;
|
||||
PHTTP_DATA mem = (PHTTP_DATA)userp;
|
||||
|
||||
|
||||
mem->memory = realloc(mem->memory, mem->size + realsize + 1);
|
||||
if(mem->memory == NULL)
|
||||
return 0;
|
||||
|
||||
|
||||
memcpy(&(mem->memory[mem->size]), contents, realsize);
|
||||
mem->size += realsize;
|
||||
mem->memory[mem->size] = 0;
|
||||
|
||||
|
||||
return realsize;
|
||||
}
|
||||
|
||||
@ -54,10 +51,10 @@ int http_init(const char* keyDirectory, int logLevel) {
|
||||
return GS_FAILED;
|
||||
|
||||
char certificateFilePath[4096];
|
||||
sprintf(certificateFilePath, "%s/%s", keyDirectory, CERTIFICATE_FILE_NAME);
|
||||
snprintf(certificateFilePath, sizeof(certificateFilePath), "%s/%s", keyDirectory, CERTIFICATE_FILE_NAME);
|
||||
|
||||
char keyFilePath[4096];
|
||||
sprintf(&keyFilePath[0], "%s/%s", keyDirectory, KEY_FILE_NAME);
|
||||
snprintf(keyFilePath, sizeof(keyFilePath), "%s/%s", keyDirectory, KEY_FILE_NAME);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSLENGINE_DEFAULT, 1L);
|
||||
@ -76,6 +73,9 @@ int http_init(const char* keyDirectory, int logLevel) {
|
||||
int http_request(char* url, PHTTP_DATA data) {
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, data);
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
#ifdef __FreeBSD__
|
||||
curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1);
|
||||
#endif
|
||||
|
||||
if (debug)
|
||||
printf("Request %s\n", url);
|
||||
@ -89,7 +89,7 @@ int http_request(char* url, PHTTP_DATA data) {
|
||||
data->size = 0;
|
||||
}
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
|
||||
|
||||
if(res != CURLE_OK) {
|
||||
gs_error = curl_easy_strerror(res);
|
||||
return GS_FAILED;
|
||||
@ -99,7 +99,7 @@ int http_request(char* url, PHTTP_DATA data) {
|
||||
|
||||
if (debug)
|
||||
printf("Response:\n%s\n\n", data->memory);
|
||||
|
||||
|
||||
return GS_OK;
|
||||
}
|
||||
|
||||
|
@ -32,20 +32,18 @@ static const int SERIAL = 0;
|
||||
static const int NUM_YEARS = 10;
|
||||
|
||||
int mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years);
|
||||
int add_ext(X509 *cert, int nid, char *value);
|
||||
|
||||
CERT_KEY_PAIR mkcert_generate() {
|
||||
BIO *bio_err;
|
||||
X509 *x509 = NULL;
|
||||
EVP_PKEY *pkey = NULL;
|
||||
PKCS12 *p12 = NULL;
|
||||
|
||||
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
|
||||
|
||||
bio_err = BIO_new_fp(stderr, BIO_NOCLOSE);
|
||||
|
||||
|
||||
OpenSSL_add_all_algorithms();
|
||||
ERR_load_crypto_strings();
|
||||
|
||||
|
||||
mkcert(&x509, &pkey, NUM_BITS, SERIAL, NUM_YEARS);
|
||||
|
||||
p12 = PKCS12_create("limelight", "GameStream", pkey, x509, NULL, 0, 0, 0, 0, 0);
|
||||
@ -54,9 +52,9 @@ CERT_KEY_PAIR mkcert_generate() {
|
||||
ENGINE_cleanup();
|
||||
#endif
|
||||
CRYPTO_cleanup_all_ex_data();
|
||||
|
||||
|
||||
BIO_free(bio_err);
|
||||
|
||||
|
||||
return (CERT_KEY_PAIR) {x509, pkey, p12};
|
||||
}
|
||||
|
||||
@ -70,117 +68,62 @@ void mkcert_save(const char* certFile, const char* p12File, const char* keyPairF
|
||||
FILE* certFilePtr = fopen(certFile, "w");
|
||||
FILE* keyPairFilePtr = fopen(keyPairFile, "w");
|
||||
FILE* p12FilePtr = fopen(p12File, "wb");
|
||||
|
||||
|
||||
//TODO: error check
|
||||
PEM_write_PrivateKey(keyPairFilePtr, certKeyPair.pkey, NULL, NULL, 0, NULL, NULL);
|
||||
PEM_write_X509(certFilePtr, certKeyPair.x509);
|
||||
i2d_PKCS12_fp(p12FilePtr, certKeyPair.p12);
|
||||
|
||||
|
||||
fclose(p12FilePtr);
|
||||
fclose(certFilePtr);
|
||||
fclose(keyPairFilePtr);
|
||||
}
|
||||
|
||||
int mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years) {
|
||||
X509 *x;
|
||||
EVP_PKEY *pk;
|
||||
RSA *rsa;
|
||||
X509_NAME *name = NULL;
|
||||
|
||||
if (*pkeyp == NULL) {
|
||||
if ((pk=EVP_PKEY_new()) == NULL) {
|
||||
abort();
|
||||
return(0);
|
||||
}
|
||||
} else {
|
||||
pk = *pkeyp;
|
||||
}
|
||||
|
||||
if (*x509p == NULL) {
|
||||
if ((x = X509_new()) == NULL) {
|
||||
goto err;
|
||||
}
|
||||
} else {
|
||||
x = *x509p;
|
||||
}
|
||||
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
|
||||
EVP_PKEY_keygen_init(ctx);
|
||||
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits);
|
||||
|
||||
if ((rsa = RSA_new()) == NULL)
|
||||
goto err;
|
||||
|
||||
BIGNUM* bne = BN_new();
|
||||
if (bne == NULL) {
|
||||
abort();
|
||||
goto err;
|
||||
}
|
||||
// pk must be initialized on input
|
||||
EVP_PKEY *pk = NULL;;
|
||||
EVP_PKEY_keygen(ctx, &pk);
|
||||
|
||||
BN_set_word(bne, RSA_F4);
|
||||
if (RSA_generate_key_ex(rsa, bits, bne, NULL) == 0) {
|
||||
abort();
|
||||
goto err;
|
||||
}
|
||||
EVP_PKEY_CTX_free(ctx);
|
||||
|
||||
if (!EVP_PKEY_assign_RSA(pk, rsa)) {
|
||||
abort();
|
||||
goto err;
|
||||
}
|
||||
|
||||
X509_set_version(x, 2);
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(x), serial);
|
||||
X509_gmtime_adj(X509_get_notBefore(x), 0);
|
||||
X509_gmtime_adj(X509_get_notAfter(x), (long)60*60*24*365*years);
|
||||
X509_set_pubkey(x, pk);
|
||||
|
||||
name = X509_get_subject_name(x);
|
||||
|
||||
/* This function creates and adds the entry, working out the
|
||||
* correct string type and performing checks on its length.
|
||||
*/
|
||||
X509* cert = X509_new();
|
||||
X509_set_version(cert, 2);
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(cert), serial);
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
X509_gmtime_adj(X509_get_notBefore(cert), 0);
|
||||
X509_gmtime_adj(X509_get_notAfter(cert), 60 * 60 * 24 * 365 * years);
|
||||
#else
|
||||
ASN1_TIME* before = ASN1_STRING_dup(X509_get0_notBefore(cert));
|
||||
ASN1_TIME* after = ASN1_STRING_dup(X509_get0_notAfter(cert));
|
||||
|
||||
X509_gmtime_adj(before, 0);
|
||||
X509_gmtime_adj(after, 60 * 60 * 24 * 365 * years);
|
||||
|
||||
X509_set1_notBefore(cert, before);
|
||||
X509_set1_notAfter(cert, after);
|
||||
|
||||
ASN1_STRING_free(before);
|
||||
ASN1_STRING_free(after);
|
||||
#endif
|
||||
|
||||
X509_set_pubkey(cert, pk);
|
||||
|
||||
X509_NAME* name = X509_get_subject_name(cert);
|
||||
X509_NAME_add_entry_by_txt(name,"CN", MBSTRING_ASC, (unsigned char*)"NVIDIA GameStream Client", -1, -1, 0);
|
||||
|
||||
/* Its self signed so set the issuer name to be the same as the
|
||||
* subject.
|
||||
*/
|
||||
X509_set_issuer_name(x, name);
|
||||
|
||||
/* Add various extensions: standard extensions */
|
||||
add_ext(x, NID_key_usage, "critical,digitalSignature,keyEncipherment");
|
||||
|
||||
add_ext(x, NID_subject_key_identifier, "hash");
|
||||
|
||||
if (!X509_sign(x, pk, EVP_sha1())) {
|
||||
X509_set_issuer_name(cert, name);
|
||||
|
||||
if (!X509_sign(cert, pk, EVP_sha256())) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
*x509p = x;
|
||||
|
||||
*x509p = cert;
|
||||
*pkeyp = pk;
|
||||
|
||||
|
||||
return(1);
|
||||
err:
|
||||
return(0);
|
||||
}
|
||||
|
||||
/* Add extension using V3 code: we can set the config file as NULL
|
||||
* because we wont reference any other sections.
|
||||
*/
|
||||
|
||||
int add_ext(X509 *cert, int nid, char *value)
|
||||
{
|
||||
X509_EXTENSION *ex;
|
||||
X509V3_CTX ctx;
|
||||
/* This sets the 'context' of the extensions. */
|
||||
/* No configuration database */
|
||||
X509V3_set_ctx_nodb(&ctx);
|
||||
/* Issuer and subject certs: both the target since it is self signed,
|
||||
* no request and no CRL
|
||||
*/
|
||||
X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0);
|
||||
ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
|
||||
if (!ex) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
X509_add_ext(cert, ex, -1);
|
||||
X509_EXTENSION_free(ex);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -31,9 +31,9 @@ void gs_sps_init(int width, int height) {
|
||||
}
|
||||
|
||||
void gs_sps_fix(PLENTRY sps, int flags, uint8_t* out_buf, uint32_t* out_offset) {
|
||||
const char naluHeader[] = {0x00, 0x00, 0x00, 0x01};
|
||||
int start_len = sps->data[2] == 0x01 ? 3 : 4;
|
||||
|
||||
read_nal_unit(h264_stream, sps->data+4, sps->length-4);
|
||||
read_nal_unit(h264_stream, sps->data+start_len, sps->length-start_len);
|
||||
|
||||
// Some decoders rely on H264 level to decide how many buffers are needed
|
||||
// Since we only need one frame buffered, we'll set level as low as we can
|
||||
@ -48,14 +48,16 @@ void gs_sps_fix(PLENTRY sps, int flags, uint8_t* out_buf, uint32_t* out_offset)
|
||||
|
||||
// GFE 2.5.11 changed the SPS to add additional extensions
|
||||
// Some devices don't like these so we remove them here.
|
||||
h264_stream->sps->vui.video_signal_type_present_flag = 0;
|
||||
h264_stream->sps->vui.chroma_loc_info_present_flag = 0;
|
||||
if (flags & GS_SPS_REMOVE_VST_FIXUP)
|
||||
h264_stream->sps->vui.video_signal_type_present_flag = 0;
|
||||
if (flags & GS_SPS_REMOVE_CLI_FIXUP)
|
||||
h264_stream->sps->vui.chroma_loc_info_present_flag = 0;
|
||||
|
||||
if ((flags & GS_SPS_BITSTREAM_FIXUP) == GS_SPS_BITSTREAM_FIXUP) {
|
||||
// The SPS that comes in the current H264 bytestream doesn't set the bitstream_restriction_flag
|
||||
// or the max_dec_frame_buffering which increases decoding latency on some devices
|
||||
// or the max_dec_frame_buffering which increases decoding latency on some devices.
|
||||
// log2_max_mv_length_horizontal and log2_max_mv_length_vertical are set to more
|
||||
// conservite values by GFE 25.11. We'll let those values stand.
|
||||
// conservative values by GFE 2.5.11. We'll let those values stand.
|
||||
if (!h264_stream->sps->vui.bitstream_restriction_flag) {
|
||||
h264_stream->sps->vui.bitstream_restriction_flag = 1;
|
||||
h264_stream->sps->vui.motion_vectors_over_pic_boundaries_flag = 1;
|
||||
@ -72,11 +74,10 @@ void gs_sps_fix(PLENTRY sps, int flags, uint8_t* out_buf, uint32_t* out_offset)
|
||||
// than what GFE sends in 2.5.11, but it doesn't seem to cause picture problems.
|
||||
h264_stream->sps->vui.max_bytes_per_pic_denom = 2;
|
||||
h264_stream->sps->vui.max_bits_per_mb_denom = 1;
|
||||
} else // Devices that didn't/couldn't get bitstream restrictions before GFE 2.5.11 will continue to not receive them now
|
||||
h264_stream->sps->vui.bitstream_restriction_flag = 0;
|
||||
}
|
||||
|
||||
memcpy(out_buf+*out_offset, naluHeader, 4);
|
||||
*out_offset += 4;
|
||||
memcpy(out_buf+*out_offset, sps->data, start_len);
|
||||
*out_offset += start_len;
|
||||
|
||||
*out_offset += write_nal_unit(h264_stream, out_buf+*out_offset, 128);
|
||||
}
|
||||
|
@ -19,7 +19,9 @@
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
#define GS_SPS_BITSTREAM_FIXUP 0x01
|
||||
#define GS_SPS_BITSTREAM_FIXUP 0x01
|
||||
#define GS_SPS_REMOVE_VST_FIXUP 0x02
|
||||
#define GS_SPS_REMOVE_CLI_FIXUP 0x04
|
||||
|
||||
void gs_sps_init(int width, int height);
|
||||
void gs_sps_fix(PLENTRY sps, int flags, uint8_t* out_buf, uint32_t* out_offset);
|
||||
|
@ -25,8 +25,6 @@
|
||||
|
||||
#define STATUS_OK 200
|
||||
|
||||
static XML_Parser parser;
|
||||
|
||||
struct xml_query {
|
||||
char *memory;
|
||||
size_t size;
|
||||
@ -132,7 +130,7 @@ static void XMLCALL _xml_write_data(void *userData, const XML_Char *s, int len)
|
||||
search->memory = realloc(search->memory, search->size + len + 1);
|
||||
if(search->memory == NULL)
|
||||
return;
|
||||
|
||||
|
||||
memcpy(&(search->memory[search->size]), s, len);
|
||||
search->size += len;
|
||||
search->memory[search->size] = 0;
|
||||
@ -162,7 +160,7 @@ int xml_search(char* data, size_t len, char* node, char** result) {
|
||||
|
||||
XML_ParserFree(parser);
|
||||
*result = search.memory;
|
||||
|
||||
|
||||
return GS_OK;
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,10 @@
|
||||
#height = 720
|
||||
#fps = 60
|
||||
|
||||
## Output rotation (independent of xrandr or framebuffer settings!)
|
||||
## Allowed values: 0, 90, 180, 270
|
||||
#rotate = 0
|
||||
|
||||
## Bitrate depends by default on resolution and fps
|
||||
## Set to -1 to enable default
|
||||
## 20Mbps (20000) for 1080p (60 fps)
|
||||
@ -15,7 +19,8 @@
|
||||
#bitrate = -1
|
||||
|
||||
## Size of network packets should be lower than MTU
|
||||
#packetsize = 1024
|
||||
## If streaming with WAN optimizations, this will be capped at 1024.
|
||||
#packetsize = 1392
|
||||
|
||||
## Select video codec (auto/h264/h265)
|
||||
#codec = auto
|
||||
@ -43,6 +48,9 @@
|
||||
## Send quit app request to remote after quitting session
|
||||
#quitappafter = false
|
||||
|
||||
## Disable all input processing (view-only mode)
|
||||
#viewonly = false
|
||||
|
||||
## Select audio device to play sound on
|
||||
#audio = sysdefault
|
||||
|
||||
@ -61,11 +69,14 @@
|
||||
## By default keys are stored in $XDG_CACHE_DIR/moonlight or ~/.cache/moonlight
|
||||
#keydir = /dir/to/keys
|
||||
|
||||
## Enable QOS settings to optimize for internet instead of local network
|
||||
#remote = false
|
||||
## Enable QOS settings to optimize for internet or local network
|
||||
## yes - optimize for WAN streaming
|
||||
## no - optimize for LAN streaming
|
||||
## auto (default) - decide automatically based on target IP address
|
||||
#remote = auto
|
||||
|
||||
## Enable 5.1 surround sound
|
||||
#surround = false
|
||||
## Enable 5.1/7.1 surround sound
|
||||
#surround = 5.1
|
||||
|
||||
## Load additional configuration files
|
||||
#config = /path/to/config
|
||||
|
@ -20,6 +20,8 @@
|
||||
#include "audio.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <opus_multistream.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
@ -27,31 +29,36 @@
|
||||
|
||||
static snd_pcm_t *handle;
|
||||
static OpusMSDecoder* decoder;
|
||||
static short pcmBuffer[FRAME_SIZE * MAX_CHANNEL_COUNT];
|
||||
static short* pcmBuffer;
|
||||
static int samplesPerFrame;
|
||||
|
||||
static int alsa_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int arFlags) {
|
||||
int rc;
|
||||
unsigned char alsaMapping[MAX_CHANNEL_COUNT];
|
||||
unsigned char alsaMapping[AUDIO_CONFIGURATION_MAX_CHANNEL_COUNT];
|
||||
|
||||
/* The supplied mapping array has order: FL-FR-C-LFE-RL-RR
|
||||
* ALSA expects the order: FL-FR-RL-RR-C-LFE
|
||||
/* The supplied mapping array has order: FL-FR-C-LFE-RL-RR-SL-SR
|
||||
* ALSA expects the order: FL-FR-RL-RR-C-LFE-SL-SR
|
||||
* We need copy the mapping locally and swap the channels around.
|
||||
*/
|
||||
alsaMapping[0] = opusConfig->mapping[0];
|
||||
alsaMapping[1] = opusConfig->mapping[1];
|
||||
if (opusConfig->channelCount == 6) {
|
||||
memcpy(alsaMapping, opusConfig->mapping, sizeof(alsaMapping));
|
||||
if (opusConfig->channelCount >= 6) {
|
||||
alsaMapping[2] = opusConfig->mapping[4];
|
||||
alsaMapping[3] = opusConfig->mapping[5];
|
||||
alsaMapping[4] = opusConfig->mapping[2];
|
||||
alsaMapping[5] = opusConfig->mapping[3];
|
||||
}
|
||||
|
||||
samplesPerFrame = opusConfig->samplesPerFrame;
|
||||
pcmBuffer = malloc(sizeof(short) * opusConfig->channelCount * samplesPerFrame);
|
||||
if (pcmBuffer == NULL)
|
||||
return -1;
|
||||
|
||||
decoder = opus_multistream_decoder_create(opusConfig->sampleRate, opusConfig->channelCount, opusConfig->streams, opusConfig->coupledStreams, alsaMapping, &rc);
|
||||
|
||||
snd_pcm_hw_params_t *hw_params;
|
||||
snd_pcm_sw_params_t *sw_params;
|
||||
snd_pcm_uframes_t period_size = FRAME_SIZE * FRAME_BUFFER;
|
||||
snd_pcm_uframes_t buffer_size = 2 * period_size;
|
||||
snd_pcm_uframes_t period_size = (opusConfig->sampleRate * 20) / 1000; // 20 ms period
|
||||
snd_pcm_uframes_t buffer_size = 3 * period_size; // 60 ms buffer
|
||||
unsigned int sampleRate = opusConfig->sampleRate;
|
||||
|
||||
char* audio_device = (char*) context;
|
||||
@ -77,7 +84,7 @@ static int alsa_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGUR
|
||||
CHECK_RETURN(snd_pcm_sw_params_malloc(&sw_params));
|
||||
CHECK_RETURN(snd_pcm_sw_params_current(handle, sw_params));
|
||||
CHECK_RETURN(snd_pcm_sw_params_set_avail_min(handle, sw_params, period_size));
|
||||
CHECK_RETURN(snd_pcm_sw_params_set_start_threshold(handle, sw_params, 1));
|
||||
CHECK_RETURN(snd_pcm_sw_params_set_start_threshold(handle, sw_params, period_size));
|
||||
CHECK_RETURN(snd_pcm_sw_params(handle, sw_params));
|
||||
snd_pcm_sw_params_free(sw_params);
|
||||
|
||||
@ -87,27 +94,38 @@ static int alsa_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGUR
|
||||
}
|
||||
|
||||
static void alsa_renderer_cleanup() {
|
||||
if (decoder != NULL)
|
||||
if (decoder != NULL) {
|
||||
opus_multistream_decoder_destroy(decoder);
|
||||
decoder = NULL;
|
||||
}
|
||||
|
||||
if (handle != NULL) {
|
||||
snd_pcm_drain(handle);
|
||||
snd_pcm_close(handle);
|
||||
handle = NULL;
|
||||
}
|
||||
|
||||
if (pcmBuffer != NULL) {
|
||||
free(pcmBuffer);
|
||||
pcmBuffer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void alsa_renderer_decode_and_play_sample(char* data, int length) {
|
||||
int decodeLen = opus_multistream_decode(decoder, data, length, pcmBuffer, FRAME_SIZE, 0);
|
||||
int decodeLen = opus_multistream_decode(decoder, data, length, pcmBuffer, samplesPerFrame, 0);
|
||||
if (decodeLen > 0) {
|
||||
int rc = snd_pcm_writei(handle, pcmBuffer, decodeLen);
|
||||
if (rc == -EPIPE)
|
||||
snd_pcm_recover(handle, rc, 1);
|
||||
if (rc < 0) {
|
||||
rc = snd_pcm_recover(handle, rc, 0);
|
||||
if (rc == 0)
|
||||
rc = snd_pcm_writei(handle, pcmBuffer, decodeLen);
|
||||
}
|
||||
|
||||
if (rc<0)
|
||||
printf("Alsa error from writei: %d\n", rc);
|
||||
else if (decodeLen != rc)
|
||||
printf("Alsa shortm write, write %d frames\n", rc);
|
||||
} else {
|
||||
} else if (decodeLen < 0) {
|
||||
printf("Opus error from decode: %d\n", decodeLen);
|
||||
}
|
||||
}
|
||||
@ -116,5 +134,5 @@ AUDIO_RENDERER_CALLBACKS audio_callbacks_alsa = {
|
||||
.init = alsa_renderer_init,
|
||||
.cleanup = alsa_renderer_cleanup,
|
||||
.decodeAndPlaySample = alsa_renderer_decode_and_play_sample,
|
||||
.capabilities = CAPABILITY_DIRECT_SUBMIT,
|
||||
.capabilities = CAPABILITY_DIRECT_SUBMIT | CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION,
|
||||
};
|
||||
|
@ -21,10 +21,6 @@
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
#define MAX_CHANNEL_COUNT 6
|
||||
#define FRAME_SIZE 240
|
||||
#define FRAME_BUFFER 12
|
||||
|
||||
#ifdef HAVE_ALSA
|
||||
extern AUDIO_RENDERER_CALLBACKS audio_callbacks_alsa;
|
||||
#endif
|
||||
@ -35,3 +31,6 @@ extern AUDIO_RENDERER_CALLBACKS audio_callbacks_sdl;
|
||||
extern AUDIO_RENDERER_CALLBACKS audio_callbacks_pulse;
|
||||
bool audio_pulse_init(char* audio_device);
|
||||
#endif
|
||||
#ifdef __FreeBSD__
|
||||
extern AUDIO_RENDERER_CALLBACKS audio_callbacks_oss;
|
||||
#endif
|
||||
|
@ -29,18 +29,24 @@ static OpusMSDecoder* decoder;
|
||||
ILCLIENT_T* handle;
|
||||
COMPONENT_T* component;
|
||||
static OMX_BUFFERHEADERTYPE *buf;
|
||||
static short pcmBuffer[FRAME_SIZE * MAX_CHANNEL_COUNT];
|
||||
static short* pcmBuffer;
|
||||
static int channelCount;
|
||||
static int samplesPerFrame;
|
||||
|
||||
static int omx_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int arFlags) {
|
||||
int rc, error;
|
||||
int rc;
|
||||
OMX_ERRORTYPE err;
|
||||
unsigned char omxMapping[MAX_CHANNEL_COUNT];
|
||||
unsigned char omxMapping[AUDIO_CONFIGURATION_MAX_CHANNEL_COUNT];
|
||||
char* componentName = "audio_render";
|
||||
|
||||
channelCount = opusConfig->channelCount;
|
||||
/* The supplied mapping array has order: FL-FR-C-LFE-RL-RR
|
||||
* OMX expects the order: FL-FR-LFE-C-RL-RR
|
||||
samplesPerFrame = opusConfig->samplesPerFrame;
|
||||
pcmBuffer = malloc(sizeof(short) * channelCount * samplesPerFrame);
|
||||
if (pcmBuffer == NULL)
|
||||
return -1;
|
||||
|
||||
/* The supplied mapping array has order: FL-FR-C-LFE-RL-RR-SL-SR
|
||||
* OMX expects the order: FL-FR-LFE-C-RL-RR-SL-SR
|
||||
* We need copy the mapping locally and swap the channels around.
|
||||
*/
|
||||
memcpy(omxMapping, opusConfig->mapping, sizeof(omxMapping));
|
||||
@ -155,8 +161,10 @@ static int omx_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURA
|
||||
}
|
||||
|
||||
static void omx_renderer_cleanup() {
|
||||
if (decoder != NULL)
|
||||
if (decoder != NULL) {
|
||||
opus_multistream_decoder_destroy(decoder);
|
||||
decoder = NULL;
|
||||
}
|
||||
if (handle != NULL) {
|
||||
if((buf = ilclient_get_input_buffer(component, 100, 1)) == NULL){
|
||||
fprintf(stderr, "Can't get audio buffer\n");
|
||||
@ -174,23 +182,28 @@ static void omx_renderer_cleanup() {
|
||||
ilclient_disable_port_buffers(component, 100, NULL, NULL, NULL);
|
||||
ilclient_change_component_state(component, OMX_StateIdle);
|
||||
ilclient_change_component_state(component, OMX_StateLoaded);
|
||||
handle = NULL;
|
||||
}
|
||||
if (pcmBuffer != NULL) {
|
||||
free(pcmBuffer);
|
||||
pcmBuffer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void omx_renderer_decode_and_play_sample(char* data, int length) {
|
||||
int decodeLen = opus_multistream_decode(decoder, data, length, pcmBuffer, FRAME_SIZE, 0);
|
||||
int decodeLen = opus_multistream_decode(decoder, data, length, pcmBuffer, samplesPerFrame, 0);
|
||||
if (decodeLen > 0) {
|
||||
buf = ilclient_get_input_buffer(component, 100, 1);
|
||||
buf = ilclient_get_input_buffer(component, 100, 1);
|
||||
buf->nOffset = 0;
|
||||
buf->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN;
|
||||
int bufLength = decodeLen * sizeof(short) * channelCount;
|
||||
int bufLength = decodeLen * sizeof(short) * channelCount;
|
||||
memcpy(buf->pBuffer, pcmBuffer, bufLength);
|
||||
buf->nFilledLen = bufLength;
|
||||
int r = OMX_EmptyThisBuffer(ilclient_get_handle(component), buf);
|
||||
if (r != OMX_ErrorNone) {
|
||||
fprintf(stderr, "Empty buffer error\n");
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Empty buffer error\n");
|
||||
}
|
||||
} else if (decodeLen < 0) {
|
||||
printf("Opus error from decode: %d\n", decodeLen);
|
||||
}
|
||||
}
|
||||
@ -199,5 +212,5 @@ AUDIO_RENDERER_CALLBACKS audio_callbacks_omx = {
|
||||
.init = omx_renderer_init,
|
||||
.cleanup = omx_renderer_cleanup,
|
||||
.decodeAndPlaySample = omx_renderer_decode_and_play_sample,
|
||||
.capabilities = CAPABILITY_DIRECT_SUBMIT,
|
||||
.capabilities = CAPABILITY_DIRECT_SUBMIT | CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION,
|
||||
};
|
||||
|
105
src/audio/oss.c
Normal file
105
src/audio/oss.c
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* This file is part of Moonlight Embedded.
|
||||
*
|
||||
* Copyright (C) 2015-2017 Iwan Timmer
|
||||
*
|
||||
* Moonlight is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Moonlight is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Moonlight; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
#include <sys/soundcard.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include "audio.h"
|
||||
|
||||
#include <opus_multistream.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static OpusMSDecoder* decoder;
|
||||
static short* pcmBuffer;
|
||||
static int samplesPerFrame;
|
||||
static int channelCount;
|
||||
static int fd = -1;
|
||||
|
||||
static int oss_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int arFlags) {
|
||||
int rc;
|
||||
decoder = opus_multistream_decoder_create(opusConfig->sampleRate, opusConfig->channelCount, opusConfig->streams, opusConfig->coupledStreams, opusConfig->mapping, &rc);
|
||||
|
||||
channelCount = opusConfig->channelCount;
|
||||
samplesPerFrame = opusConfig->samplesPerFrame;
|
||||
pcmBuffer = malloc(sizeof(short) * channelCount * samplesPerFrame);
|
||||
if (pcmBuffer == NULL)
|
||||
return -1;
|
||||
|
||||
const char* oss_name = "/dev/dsp";
|
||||
fd = open(oss_name, O_WRONLY);
|
||||
if (fd == -1) {
|
||||
printf("Open audio device /dev/dsp failed! error %d\n", errno);
|
||||
return -1;
|
||||
}
|
||||
// buffer size for fragment ,selector 12 is 4096;11 is 2048;10 is 1024; 13is 8192
|
||||
int frag = 12;
|
||||
if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag) == -1)
|
||||
printf("Set fragment for /dev/dsp failed.");
|
||||
|
||||
int format = AFMT_S16_LE;
|
||||
int channels = opusConfig->channelCount;
|
||||
int rate = opusConfig->sampleRate;
|
||||
if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) == -1)
|
||||
printf("Set format for /dev/dsp failed.");
|
||||
if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) == -1)
|
||||
printf("Set channels for /dev/dsp failed.");
|
||||
if (ioctl(fd, SNDCTL_DSP_SPEED, &rate) == -1)
|
||||
printf("Set sample rate for /dev/dsp failed.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void oss_renderer_cleanup() {
|
||||
if (decoder != NULL) {
|
||||
opus_multistream_decoder_destroy(decoder);
|
||||
decoder = NULL;
|
||||
}
|
||||
|
||||
if (pcmBuffer != NULL) {
|
||||
free(pcmBuffer);
|
||||
pcmBuffer = NULL;
|
||||
}
|
||||
|
||||
if (fd != -1) {
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void oss_renderer_decode_and_play_sample(char* data, int length) {
|
||||
int decodeLen = opus_multistream_decode(decoder, data, length, pcmBuffer, samplesPerFrame, 0);
|
||||
if (decodeLen > 0) {
|
||||
write(fd, pcmBuffer, decodeLen * channelCount * sizeof(short));
|
||||
} else if (decodeLen < 0) {
|
||||
printf("Opus error from decode: %d\n", decodeLen);
|
||||
}
|
||||
}
|
||||
|
||||
AUDIO_RENDERER_CALLBACKS audio_callbacks_oss = {
|
||||
.init = oss_renderer_init,
|
||||
.cleanup = oss_renderer_cleanup,
|
||||
.decodeAndPlaySample = oss_renderer_decode_and_play_sample,
|
||||
.capabilities = CAPABILITY_DIRECT_SUBMIT | CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION,
|
||||
};
|
||||
#endif
|
@ -21,6 +21,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <opus_multistream.h>
|
||||
#include <pulse/simple.h>
|
||||
@ -28,18 +29,19 @@
|
||||
|
||||
static OpusMSDecoder* decoder;
|
||||
static pa_simple *dev = NULL;
|
||||
static short pcmBuffer[FRAME_SIZE * MAX_CHANNEL_COUNT];
|
||||
static short* pcmBuffer;
|
||||
static int samplesPerFrame;
|
||||
static int channelCount;
|
||||
|
||||
bool audio_pulse_init(char* audio_device) {
|
||||
pa_sample_spec spec = {
|
||||
.format = PA_SAMPLE_S16LE,
|
||||
.rate = 44000,
|
||||
.rate = 48000,
|
||||
.channels = 2
|
||||
};
|
||||
|
||||
int error;
|
||||
dev = pa_simple_new(audio_device, "Moonlight Embedded", PA_STREAM_PLAYBACK, NULL, "Streaming", &spec, NULL, NULL, &error);
|
||||
dev = pa_simple_new(NULL, "Moonlight Embedded", PA_STREAM_PLAYBACK, audio_device, "Streaming", &spec, NULL, NULL, &error);
|
||||
|
||||
if (dev)
|
||||
pa_simple_free(dev);
|
||||
@ -49,17 +51,20 @@ bool audio_pulse_init(char* audio_device) {
|
||||
|
||||
static int pulse_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int arFlags) {
|
||||
int rc, error;
|
||||
unsigned char alsaMapping[MAX_CHANNEL_COUNT];
|
||||
unsigned char alsaMapping[AUDIO_CONFIGURATION_MAX_CHANNEL_COUNT];
|
||||
|
||||
channelCount = opusConfig->channelCount;
|
||||
samplesPerFrame = opusConfig->samplesPerFrame;
|
||||
pcmBuffer = malloc(sizeof(short) * channelCount * samplesPerFrame);
|
||||
if (pcmBuffer == NULL)
|
||||
return -1;
|
||||
|
||||
/* The supplied mapping array has order: FL-FR-C-LFE-RL-RR
|
||||
* ALSA expects the order: FL-FR-RL-RR-C-LFE
|
||||
/* The supplied mapping array has order: FL-FR-C-LFE-RL-RR-SL-SR
|
||||
* ALSA expects the order: FL-FR-RL-RR-C-LFE-SL-SR
|
||||
* We need copy the mapping locally and swap the channels around.
|
||||
*/
|
||||
alsaMapping[0] = opusConfig->mapping[0];
|
||||
alsaMapping[1] = opusConfig->mapping[1];
|
||||
if (opusConfig->channelCount == 6) {
|
||||
memcpy(alsaMapping, opusConfig->mapping, sizeof(alsaMapping));
|
||||
if (opusConfig->channelCount >= 6) {
|
||||
alsaMapping[2] = opusConfig->mapping[4];
|
||||
alsaMapping[3] = opusConfig->mapping[5];
|
||||
alsaMapping[4] = opusConfig->mapping[2];
|
||||
@ -74,8 +79,11 @@ static int pulse_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGU
|
||||
.channels = opusConfig->channelCount
|
||||
};
|
||||
|
||||
pa_channel_map map;
|
||||
pa_channel_map_init_auto(&map, opusConfig->channelCount, PA_CHANNEL_MAP_ALSA);
|
||||
|
||||
char* audio_device = (char*) context;
|
||||
dev = pa_simple_new(audio_device, "Moonlight Embedded", PA_STREAM_PLAYBACK, NULL, "Streaming", &spec, NULL, NULL, &error);
|
||||
dev = pa_simple_new(NULL, "Moonlight Embedded", PA_STREAM_PLAYBACK, audio_device, "Streaming", &spec, &map, NULL, &error);
|
||||
|
||||
if (!dev) {
|
||||
printf("Pulseaudio error: %s\n", pa_strerror(error));
|
||||
@ -86,25 +94,36 @@ static int pulse_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGU
|
||||
}
|
||||
|
||||
static void pulse_renderer_decode_and_play_sample(char* data, int length) {
|
||||
int decodeLen = opus_multistream_decode(decoder, data, length, pcmBuffer, FRAME_SIZE, 0);
|
||||
int decodeLen = opus_multistream_decode(decoder, data, length, pcmBuffer, samplesPerFrame, 0);
|
||||
if (decodeLen > 0) {
|
||||
int error;
|
||||
int rc = pa_simple_write(dev, pcmBuffer, decodeLen * sizeof(short) * channelCount, &error);
|
||||
|
||||
if (rc<0)
|
||||
printf("Pulseaudio error: %s\n", pa_strerror(error));
|
||||
} else {
|
||||
} else if (decodeLen < 0) {
|
||||
printf("Opus error from decode: %d\n", decodeLen);
|
||||
}
|
||||
}
|
||||
|
||||
static void pulse_renderer_cleanup() {
|
||||
pa_simple_free(dev);
|
||||
if (decoder != NULL) {
|
||||
opus_multistream_decoder_destroy(decoder);
|
||||
decoder = NULL;
|
||||
}
|
||||
if (dev != NULL) {
|
||||
pa_simple_free(dev);
|
||||
dev = NULL;
|
||||
}
|
||||
if (pcmBuffer != NULL) {
|
||||
free(pcmBuffer);
|
||||
pcmBuffer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
AUDIO_RENDERER_CALLBACKS audio_callbacks_pulse = {
|
||||
.init = pulse_renderer_init,
|
||||
.cleanup = pulse_renderer_cleanup,
|
||||
.decodeAndPlaySample = pulse_renderer_decode_and_play_sample,
|
||||
.capabilities = CAPABILITY_DIRECT_SUBMIT,
|
||||
.capabilities = CAPABILITY_DIRECT_SUBMIT | CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION,
|
||||
};
|
||||
|
@ -26,7 +26,8 @@
|
||||
#include <opus_multistream.h>
|
||||
|
||||
static OpusMSDecoder* decoder;
|
||||
static short pcmBuffer[FRAME_SIZE * MAX_CHANNEL_COUNT];
|
||||
static short* pcmBuffer;
|
||||
static int samplesPerFrame;
|
||||
static SDL_AudioDeviceID dev;
|
||||
static int channelCount;
|
||||
|
||||
@ -35,6 +36,10 @@ static int sdl_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURA
|
||||
decoder = opus_multistream_decoder_create(opusConfig->sampleRate, opusConfig->channelCount, opusConfig->streams, opusConfig->coupledStreams, opusConfig->mapping, &rc);
|
||||
|
||||
channelCount = opusConfig->channelCount;
|
||||
samplesPerFrame = opusConfig->samplesPerFrame;
|
||||
pcmBuffer = malloc(sizeof(short) * channelCount * samplesPerFrame);
|
||||
if (pcmBuffer == NULL)
|
||||
return -1;
|
||||
|
||||
SDL_InitSubSystem(SDL_INIT_AUDIO);
|
||||
|
||||
@ -45,13 +50,11 @@ static int sdl_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURA
|
||||
want.channels = opusConfig->channelCount;
|
||||
want.samples = 4096;
|
||||
|
||||
dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, SDL_AUDIO_ALLOW_FORMAT_CHANGE);
|
||||
dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
||||
if (dev == 0) {
|
||||
printf("Failed to open audio: %s\n", SDL_GetError());
|
||||
return -1;
|
||||
} else {
|
||||
if (have.format != want.format) // we let this one thing change.
|
||||
printf("We didn't get requested audio format.\n");
|
||||
SDL_PauseAudioDevice(dev, 0); // start audio playing.
|
||||
}
|
||||
|
||||
@ -59,17 +62,27 @@ static int sdl_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURA
|
||||
}
|
||||
|
||||
static void sdl_renderer_cleanup() {
|
||||
if (decoder != NULL)
|
||||
if (decoder != NULL) {
|
||||
opus_multistream_decoder_destroy(decoder);
|
||||
decoder = NULL;
|
||||
}
|
||||
|
||||
SDL_CloseAudioDevice(dev);
|
||||
if (pcmBuffer != NULL) {
|
||||
free(pcmBuffer);
|
||||
pcmBuffer = NULL;
|
||||
}
|
||||
|
||||
if (dev != 0) {
|
||||
SDL_CloseAudioDevice(dev);
|
||||
dev = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void sdl_renderer_decode_and_play_sample(char* data, int length) {
|
||||
int decodeLen = opus_multistream_decode(decoder, data, length, pcmBuffer, FRAME_SIZE, 0);
|
||||
int decodeLen = opus_multistream_decode(decoder, data, length, pcmBuffer, samplesPerFrame, 0);
|
||||
if (decodeLen > 0) {
|
||||
SDL_QueueAudio(dev, pcmBuffer, decodeLen * channelCount * sizeof(short));
|
||||
} else {
|
||||
} else if (decodeLen < 0) {
|
||||
printf("Opus error from decode: %d\n", decodeLen);
|
||||
}
|
||||
}
|
||||
@ -78,5 +91,5 @@ AUDIO_RENDERER_CALLBACKS audio_callbacks_sdl = {
|
||||
.init = sdl_renderer_init,
|
||||
.cleanup = sdl_renderer_cleanup,
|
||||
.decodeAndPlaySample = sdl_renderer_decode_and_play_sample,
|
||||
.capabilities = CAPABILITY_DIRECT_SUBMIT,
|
||||
.capabilities = CAPABILITY_DIRECT_SUBMIT | CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION,
|
||||
};
|
||||
|
133
src/config.c
133
src/config.c
@ -17,7 +17,10 @@
|
||||
* along with Moonlight; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "platform.h"
|
||||
#include "config.h"
|
||||
#include "util.h"
|
||||
#include "cpu.h"
|
||||
|
||||
#include "input/evdev.h"
|
||||
#include "audio/audio.h"
|
||||
@ -37,7 +40,7 @@
|
||||
|
||||
#define write_config_string(fd, key, value) fprintf(fd, "%s = %s\n", key, value)
|
||||
#define write_config_int(fd, key, value) fprintf(fd, "%s = %d\n", key, value)
|
||||
#define write_config_bool(fd, key, value) fprintf(fd, "%s = %s\n", key, value?"true":"false");
|
||||
#define write_config_bool(fd, key, value) fprintf(fd, "%s = %s\n", key, value ? "true":"false")
|
||||
|
||||
bool inputAdded = false;
|
||||
|
||||
@ -56,18 +59,24 @@ static struct option long_options[] = {
|
||||
{"audio", required_argument, NULL, 'm'},
|
||||
{"localaudio", no_argument, NULL, 'n'},
|
||||
{"config", required_argument, NULL, 'o'},
|
||||
{"platform", required_argument, 0, 'p'},
|
||||
{"platform", required_argument, NULL, 'p'},
|
||||
{"save", required_argument, NULL, 'q'},
|
||||
{"keydir", required_argument, NULL, 'r'},
|
||||
{"remote", no_argument, NULL, 's'},
|
||||
{"remote", required_argument, NULL, 's'},
|
||||
{"windowed", no_argument, NULL, 't'},
|
||||
{"surround", no_argument, NULL, 'u'},
|
||||
{"surround", required_argument, NULL, 'u'},
|
||||
{"fps", required_argument, NULL, 'v'},
|
||||
{"codec", required_argument, NULL, 'x'},
|
||||
{"unsupported", no_argument, NULL, 'y'},
|
||||
{"nounsupported", no_argument, NULL, 'y'},
|
||||
{"quitappafter", no_argument, NULL, '1'},
|
||||
{"viewonly", no_argument, NULL, '2'},
|
||||
{"rotate", required_argument, NULL, '3'},
|
||||
{"verbose", no_argument, NULL, 'z'},
|
||||
{"debug", no_argument, NULL, 'Z'},
|
||||
{"nomouseemulation", no_argument, NULL, '4'},
|
||||
{"pin", required_argument, NULL, '5'},
|
||||
{"port", required_argument, NULL, '6'},
|
||||
{"hdr", no_argument, NULL, '7'},
|
||||
{0, 0, 0, 0},
|
||||
};
|
||||
|
||||
@ -102,7 +111,7 @@ char* get_path(char* name, char* extra_data_dirs) {
|
||||
char* end;
|
||||
do {
|
||||
end = strstr(data_dir, ":");
|
||||
int length = end != NULL?end - data_dir:strlen(data_dir);
|
||||
int length = end != NULL ? end - data_dir:strlen(data_dir);
|
||||
memcpy(path, data_dir, length);
|
||||
if (path[0] == '/')
|
||||
sprintf(path+length, MOONLIGHT_PATH "/%s", name);
|
||||
@ -191,13 +200,22 @@ static void parse_argument(int c, char* value, PCONFIGURATION config) {
|
||||
strcpy(config->key_dir, value);
|
||||
break;
|
||||
case 's':
|
||||
config->stream.streamingRemotely = 1;
|
||||
if (strcasecmp(value, "auto") == 0)
|
||||
config->stream.streamingRemotely = STREAM_CFG_AUTO;
|
||||
else if (strcasecmp(value, "true") == 0 || strcasecmp(value, "yes") == 0)
|
||||
config->stream.streamingRemotely = STREAM_CFG_REMOTE;
|
||||
else if (strcasecmp(value, "false") == 0 || strcasecmp(value, "no") == 0)
|
||||
config->stream.streamingRemotely = STREAM_CFG_LOCAL;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
config->fullscreen = false;
|
||||
break;
|
||||
case 'u':
|
||||
config->stream.audioConfiguration = AUDIO_CONFIGURATION_51_SURROUND;
|
||||
if (strcasecmp(value, "5.1") == 0)
|
||||
config->stream.audioConfiguration = AUDIO_CONFIGURATION_51_SURROUND;
|
||||
else if (strcasecmp(value, "7.1") == 0)
|
||||
config->stream.audioConfiguration = AUDIO_CONFIGURATION_71_SURROUND;
|
||||
break;
|
||||
case 'v':
|
||||
config->stream.fps = atoi(value);
|
||||
@ -207,21 +225,41 @@ static void parse_argument(int c, char* value, PCONFIGURATION config) {
|
||||
config->codec = CODEC_UNSPECIFIED;
|
||||
else if (strcasecmp(value, "h264") == 0)
|
||||
config->codec = CODEC_H264;
|
||||
if (strcasecmp(value, "h265") == 0 || strcasecmp(value, "hevc") == 0)
|
||||
else if (strcasecmp(value, "h265") == 0 || strcasecmp(value, "hevc") == 0)
|
||||
config->codec = CODEC_HEVC;
|
||||
else if (strcasecmp(value, "av1") == 0)
|
||||
config->codec = CODEC_AV1;
|
||||
break;
|
||||
case 'y':
|
||||
config->unsupported = true;
|
||||
config->unsupported = false;
|
||||
break;
|
||||
case '1':
|
||||
config->quitappafter = true;
|
||||
break;
|
||||
case '2':
|
||||
config->viewonly = true;
|
||||
break;
|
||||
case '3':
|
||||
config->rotate = atoi(value);
|
||||
break;
|
||||
case 'z':
|
||||
config->debug_level = 1;
|
||||
break;
|
||||
case 'Z':
|
||||
config->debug_level = 2;
|
||||
break;
|
||||
case '4':
|
||||
config->mouse_emulation = false;
|
||||
break;
|
||||
case '5':
|
||||
config->pin = atoi(value);
|
||||
break;
|
||||
case '6':
|
||||
config->port = atoi(value);
|
||||
break;
|
||||
case '7':
|
||||
config->hdr = true;
|
||||
break;
|
||||
case 1:
|
||||
if (config->action == NULL)
|
||||
config->action = value;
|
||||
@ -251,10 +289,6 @@ bool config_file_parse(char* filename, PCONFIGURATION config) {
|
||||
config->address = value;
|
||||
} else if (strcmp(key, "sops") == 0) {
|
||||
config->sops = strcmp("true", value) == 0;
|
||||
} else if (strcmp(key, "localaudio") == 0) {
|
||||
config->localaudio = strcmp("true", value) == 0;
|
||||
} else if (strcmp(key, "quitappafter") == 0) {
|
||||
config->quitappafter = strcmp("true", value) == 0;
|
||||
} else {
|
||||
for (int i=0;long_options[i].name != NULL;i++) {
|
||||
if (strcmp(long_options[i].name, key) == 0) {
|
||||
@ -293,6 +327,10 @@ void config_save(char* filename, PCONFIGURATION config) {
|
||||
write_config_bool(fd, "localaudio", config->localaudio);
|
||||
if (config->quitappafter)
|
||||
write_config_bool(fd, "quitappafter", config->quitappafter);
|
||||
if (config->viewonly)
|
||||
write_config_bool(fd, "viewonly", config->viewonly);
|
||||
if (config->rotate != 0)
|
||||
write_config_int(fd, "rotate", config->rotate);
|
||||
|
||||
if (strcmp(config->app, "Steam") != 0)
|
||||
write_config_string(fd, "app", config->app);
|
||||
@ -305,12 +343,25 @@ void config_parse(int argc, char* argv[], PCONFIGURATION config) {
|
||||
|
||||
config->stream.width = 1280;
|
||||
config->stream.height = 720;
|
||||
config->stream.fps = -1;
|
||||
config->stream.fps = 60;
|
||||
config->stream.bitrate = -1;
|
||||
config->stream.packetSize = 1024;
|
||||
config->stream.streamingRemotely = 0;
|
||||
config->stream.packetSize = 1392;
|
||||
config->stream.streamingRemotely = STREAM_CFG_AUTO;
|
||||
config->stream.audioConfiguration = AUDIO_CONFIGURATION_STEREO;
|
||||
config->stream.supportsHevc = false;
|
||||
config->stream.supportedVideoFormats = SCM_H264;
|
||||
|
||||
// Opt in for video encryption if the CPU has good AES performance
|
||||
if (has_fast_aes()) {
|
||||
config->stream.encryptionFlags = ENCFLG_ALL;
|
||||
}
|
||||
else if (has_slow_aes()) {
|
||||
// For extremely slow CPUs, opt out of audio encryption
|
||||
config->stream.encryptionFlags = ENCFLG_NONE;
|
||||
printf("Disabling encryption on low performance CPU\n");
|
||||
}
|
||||
else {
|
||||
config->stream.encryptionFlags = ENCFLG_AUDIO;
|
||||
}
|
||||
|
||||
config->debug_level = 0;
|
||||
config->platform = "auto";
|
||||
@ -322,9 +373,15 @@ void config_parse(int argc, char* argv[], PCONFIGURATION config) {
|
||||
config->sops = true;
|
||||
config->localaudio = false;
|
||||
config->fullscreen = true;
|
||||
config->unsupported = false;
|
||||
config->unsupported = true;
|
||||
config->quitappafter = false;
|
||||
config->viewonly = false;
|
||||
config->mouse_emulation = true;
|
||||
config->rotate = 0;
|
||||
config->codec = CODEC_UNSPECIFIED;
|
||||
config->hdr = false;
|
||||
config->pin = 0;
|
||||
config->port = 47989;
|
||||
|
||||
config->inputsCount = 0;
|
||||
config->mapping = get_path("gamecontrollerdb.txt", getenv("XDG_DATA_DIRS"));
|
||||
@ -333,7 +390,7 @@ void config_parse(int argc, char* argv[], PCONFIGURATION config) {
|
||||
char* config_file = get_path("moonlight.conf", "/etc");
|
||||
if (config_file)
|
||||
config_file_parse(config_file, config);
|
||||
|
||||
|
||||
if (argc == 2 && access(argv[1], F_OK) == 0) {
|
||||
config->action = "stream";
|
||||
if (!config_file_parse(argv[1], config))
|
||||
@ -342,7 +399,7 @@ void config_parse(int argc, char* argv[], PCONFIGURATION config) {
|
||||
} else {
|
||||
int option_index = 0;
|
||||
int c;
|
||||
while ((c = getopt_long_only(argc, argv, "-abc:d:efg:h:i:j:k:lm:no:p:q:r:stuv:w:xy", long_options, &option_index)) != -1) {
|
||||
while ((c = getopt_long_only(argc, argv, "-abc:d:efg:h:i:j:k:lm:no:p:q:r:s:tu:v:w:xy45:6:7", long_options, &option_index)) != -1) {
|
||||
parse_argument(c, optarg, config);
|
||||
}
|
||||
}
|
||||
@ -354,22 +411,32 @@ void config_parse(int argc, char* argv[], PCONFIGURATION config) {
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
const char *dir;
|
||||
if ((dir = getenv("XDG_CACHE_DIR")) != NULL)
|
||||
sprintf(config->key_dir, "%s" MOONLIGHT_PATH, dir);
|
||||
snprintf(config->key_dir, sizeof(config->key_dir), "%s" MOONLIGHT_PATH, dir);
|
||||
else if ((dir = getenv("HOME")) != NULL)
|
||||
sprintf(config->key_dir, "%s" DEFAULT_CACHE_DIR MOONLIGHT_PATH, dir);
|
||||
snprintf(config->key_dir, sizeof(config->key_dir), "%s" DEFAULT_CACHE_DIR MOONLIGHT_PATH, dir);
|
||||
else
|
||||
sprintf(config->key_dir, "%s" DEFAULT_CACHE_DIR MOONLIGHT_PATH, pw->pw_dir);
|
||||
snprintf(config->key_dir, sizeof(config->key_dir), "%s" DEFAULT_CACHE_DIR MOONLIGHT_PATH, pw->pw_dir);
|
||||
}
|
||||
|
||||
if (config->stream.fps == -1)
|
||||
config->stream.fps = config->stream.height >= 1080 ? 30 : 60;
|
||||
|
||||
if (config->stream.bitrate == -1) {
|
||||
if (config->stream.height >= 1080 && config->stream.fps >= 60)
|
||||
config->stream.bitrate = 20000;
|
||||
else if (config->stream.height >= 1080 || config->stream.fps >= 60)
|
||||
config->stream.bitrate = 10000;
|
||||
else
|
||||
config->stream.bitrate = 5000;
|
||||
// This table prefers 16:10 resolutions because they are
|
||||
// only slightly more pixels than the 16:9 equivalents, so
|
||||
// we don't want to bump those 16:10 resolutions up to the
|
||||
// next 16:9 slot.
|
||||
|
||||
if (config->stream.width * config->stream.height <= 640 * 360) {
|
||||
config->stream.bitrate = (int)(1000 * (config->stream.fps / 30.0));
|
||||
} else if (config->stream.width * config->stream.height <= 854 * 480) {
|
||||
config->stream.bitrate = (int)(1500 * (config->stream.fps / 30.0));
|
||||
} else if (config->stream.width * config->stream.height <= 1366 * 768) {
|
||||
// This covers 1280x720 and 1280x800 too
|
||||
config->stream.bitrate = (int)(5000 * (config->stream.fps / 30.0));
|
||||
} else if (config->stream.width * config->stream.height <= 1920 * 1200) {
|
||||
config->stream.bitrate = (int)(10000 * (config->stream.fps / 30.0));
|
||||
} else if (config->stream.width * config->stream.height <= 2560 * 1600) {
|
||||
config->stream.bitrate = (int)(20000 * (config->stream.fps / 30.0));
|
||||
} else /* if (config->stream.width * config->stream.height <= 3840 * 2160) */ {
|
||||
config->stream.bitrate = (int)(40000 * (config->stream.fps / 30.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
10
src/config.h
10
src/config.h
@ -23,8 +23,6 @@
|
||||
|
||||
#define MAX_INPUTS 6
|
||||
|
||||
enum codecs { CODEC_UNSPECIFIED, CODEC_H264, CODEC_HEVC };
|
||||
|
||||
typedef struct _CONFIGURATION {
|
||||
STREAM_CONFIGURATION stream;
|
||||
int debug_level;
|
||||
@ -39,14 +37,20 @@ typedef struct _CONFIGURATION {
|
||||
bool sops;
|
||||
bool localaudio;
|
||||
bool fullscreen;
|
||||
int rotate;
|
||||
bool unsupported;
|
||||
bool quitappafter;
|
||||
bool viewonly;
|
||||
bool mouse_emulation;
|
||||
char* inputs[MAX_INPUTS];
|
||||
int inputsCount;
|
||||
enum codecs codec;
|
||||
bool hdr;
|
||||
int pin;
|
||||
unsigned short port;
|
||||
} CONFIGURATION, *PCONFIGURATION;
|
||||
|
||||
bool inputAdded;
|
||||
extern bool inputAdded;
|
||||
|
||||
bool config_file_parse(char* filename, PCONFIGURATION config);
|
||||
void config_parse(int argc, char* argv[], PCONFIGURATION config);
|
||||
|
@ -20,5 +20,7 @@
|
||||
#define VERSION_MAJOR @PROJECT_VERSION_MAJOR@
|
||||
#define VERSION_MINOR @PROJECT_VERSION_MINOR@
|
||||
#define VERSION_PATCH @PROJECT_VERSION_PATCH@
|
||||
#cmakedefine GIT_BRANCH "@GIT_BRANCH@"
|
||||
#cmakedefine GIT_COMMIT_HASH "@GIT_COMMIT_HASH@"
|
||||
|
||||
#define COMPILE_OPTIONS "@MOONLIGHT_OPTIONS@"
|
||||
|
@ -23,11 +23,45 @@
|
||||
#include <stdarg.h>
|
||||
#include <signal.h>
|
||||
|
||||
#ifdef HAVE_SDL
|
||||
#include <SDL.h>
|
||||
#endif
|
||||
|
||||
pthread_t main_thread_id = 0;
|
||||
bool connection_debug;
|
||||
ConnListenerRumble rumble_handler = NULL;
|
||||
ConnListenerRumbleTriggers rumble_triggers_handler = NULL;
|
||||
ConnListenerSetMotionEventState set_motion_event_state_handler = NULL;
|
||||
ConnListenerSetControllerLED set_controller_led_handler = NULL;
|
||||
|
||||
static void connection_terminated(int errorCode) {
|
||||
switch (errorCode) {
|
||||
case ML_ERROR_GRACEFUL_TERMINATION:
|
||||
printf("Connection has been terminated gracefully.\n");
|
||||
break;
|
||||
case ML_ERROR_NO_VIDEO_TRAFFIC:
|
||||
printf("No video received from host. Check the host PC's firewall and port forwarding rules.\n");
|
||||
break;
|
||||
case ML_ERROR_NO_VIDEO_FRAME:
|
||||
printf("Your network connection isn't performing well. Reduce your video bitrate setting or try a faster connection.\n");
|
||||
break;
|
||||
case ML_ERROR_UNEXPECTED_EARLY_TERMINATION:
|
||||
printf("The connection was unexpectedly terminated by the host due to a video capture error. Make sure no DRM-protected content is playing on the host.\n");
|
||||
break;
|
||||
case ML_ERROR_PROTECTED_CONTENT:
|
||||
printf("The connection was terminated by the host due to DRM-protected content. Close any DRM-protected content on the host and try again.\n");
|
||||
break;
|
||||
default:
|
||||
printf("Connection terminated with error: %d\n", errorCode);
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef HAVE_SDL
|
||||
SDL_Event event;
|
||||
event.type = SDL_QUIT;
|
||||
SDL_PushEvent(&event);
|
||||
#endif
|
||||
|
||||
static void connection_terminated() {
|
||||
if (main_thread_id != 0)
|
||||
pthread_kill(main_thread_id, SIGTERM);
|
||||
}
|
||||
@ -44,6 +78,21 @@ static void rumble(unsigned short controllerNumber, unsigned short lowFreqMotor,
|
||||
rumble_handler(controllerNumber, lowFreqMotor, highFreqMotor);
|
||||
}
|
||||
|
||||
static void rumble_triggers(unsigned short controllerNumber, unsigned short leftTrigger, unsigned short rightTrigger) {
|
||||
if (rumble_handler)
|
||||
rumble_triggers_handler(controllerNumber, leftTrigger, rightTrigger);
|
||||
}
|
||||
|
||||
static void set_motion_event_state(unsigned short controllerNumber, unsigned char motionType, unsigned short reportRateHz) {
|
||||
if (set_motion_event_state_handler)
|
||||
set_motion_event_state_handler(controllerNumber, motionType, reportRateHz);
|
||||
}
|
||||
|
||||
static void set_controller_led(unsigned short controllerNumber, unsigned char r, unsigned char g, unsigned char b) {
|
||||
if (set_controller_led_handler)
|
||||
set_controller_led_handler(controllerNumber, r, g, b);
|
||||
}
|
||||
|
||||
static void connection_status_update(int status) {
|
||||
switch (status) {
|
||||
case CONN_STATUS_OKAY:
|
||||
@ -63,5 +112,9 @@ CONNECTION_LISTENER_CALLBACKS connection_callbacks = {
|
||||
.connectionTerminated = connection_terminated,
|
||||
.logMessage = connection_log_message,
|
||||
.rumble = rumble,
|
||||
.connectionStatusUpdate = connection_status_update
|
||||
.connectionStatusUpdate = connection_status_update,
|
||||
.setHdrMode = NULL,
|
||||
.rumbleTriggers = rumble_triggers,
|
||||
.setMotionEventState = set_motion_event_state,
|
||||
.setControllerLED = set_controller_led,
|
||||
};
|
||||
|
@ -26,3 +26,6 @@ extern CONNECTION_LISTENER_CALLBACKS connection_callbacks;
|
||||
extern pthread_t main_thread_id;
|
||||
extern bool connection_debug;
|
||||
extern ConnListenerRumble rumble_handler;
|
||||
extern ConnListenerRumbleTriggers rumble_triggers_handler;
|
||||
extern ConnListenerSetMotionEventState set_motion_event_state_handler;
|
||||
extern ConnListenerSetControllerLED set_controller_led_handler;
|
||||
|
121
src/cpu.c
Normal file
121
src/cpu.c
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* This file is part of Moonlight Embedded.
|
||||
*
|
||||
* Moonlight is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Moonlight is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Moonlight; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "cpu.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef HAVE_GETAUXVAL
|
||||
#include <sys/auxv.h>
|
||||
|
||||
#ifndef HWCAP2_AES
|
||||
#define HWCAP2_AES (1 << 0)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) && defined(__riscv)
|
||||
#if __has_include(<sys/hwprobe.h>)
|
||||
#include <sys/hwprobe.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
|
||||
#if __has_include(<asm/hwprobe.h>)
|
||||
#include <asm/hwprobe.h>
|
||||
#include <sys/syscall.h>
|
||||
#else
|
||||
#define __NR_riscv_hwprobe 258
|
||||
struct riscv_hwprobe {
|
||||
int64_t key;
|
||||
uint64_t value;
|
||||
};
|
||||
#define RISCV_HWPROBE_KEY_IMA_EXT_0 4
|
||||
#endif
|
||||
|
||||
// RISC-V Scalar AES [E]ncryption and [D]ecryption
|
||||
#ifndef RISCV_HWPROBE_EXT_ZKND
|
||||
#define RISCV_HWPROBE_EXT_ZKND (1 << 11)
|
||||
#define RISCV_HWPROBE_EXT_ZKNE (1 << 12)
|
||||
#endif
|
||||
|
||||
// RISC-V Vector AES
|
||||
#ifndef RISCV_HWPROBE_EXT_ZVKNED
|
||||
#define RISCV_HWPROBE_EXT_ZVKNED (1 << 21)
|
||||
#endif
|
||||
|
||||
static int __riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count,
|
||||
size_t cpu_count, unsigned long *cpus,
|
||||
unsigned int flags)
|
||||
{
|
||||
return syscall(__NR_riscv_hwprobe, pairs, pair_count, cpu_count, cpus, flags);
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
bool has_fast_aes() {
|
||||
#if defined(HAVE_GETAUXVAL) && (defined(__arm__) || defined(__aarch64__))
|
||||
#if defined(__arm__) && defined(HWCAP2_AES)
|
||||
return !!(getauxval(AT_HWCAP2) & HWCAP2_AES);
|
||||
#elif defined(__aarch64__)
|
||||
return !!(getauxval(AT_HWCAP) & HWCAP_AES);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
#elif defined(HAVE_BICS_AES)
|
||||
return __builtin_cpu_supports("aes");
|
||||
#elif defined(__BUILTIN_CPU_SUPPORTS__) && defined(__powerpc__)
|
||||
return __builtin_cpu_supports("vcrypto");
|
||||
#elif defined(__linux__) && defined(__riscv)
|
||||
struct riscv_hwprobe pairs[1] = {
|
||||
{ RISCV_HWPROBE_KEY_IMA_EXT_0, 0 },
|
||||
};
|
||||
|
||||
// If this syscall is not implemented, we'll get -ENOSYS
|
||||
// and the value field will remain zero.
|
||||
__riscv_hwprobe(pairs, sizeof(pairs) / sizeof(struct riscv_hwprobe), 0, NULL, 0);
|
||||
|
||||
return (pairs[0].value & (RISCV_HWPROBE_EXT_ZKNE | RISCV_HWPROBE_EXT_ZKND)) ==
|
||||
(RISCV_HWPROBE_EXT_ZKNE | RISCV_HWPROBE_EXT_ZKND) ||
|
||||
(pairs[0].value & RISCV_HWPROBE_EXT_ZVKNED);
|
||||
#elif __SIZEOF_SIZE_T__ == 4
|
||||
#warning Unknown 32-bit platform. Assuming AES is slow on this CPU.
|
||||
return false;
|
||||
#else
|
||||
#warning Unknown 64-bit platform. Assuming AES is fast on this CPU.
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool has_slow_aes() {
|
||||
#ifdef __arm__
|
||||
char cpuinfo[4096] = {};
|
||||
if (read_file("/proc/cpuinfo", cpuinfo, sizeof(cpuinfo) - 1) > 0) {
|
||||
// If this is a ARMv6 CPU (like the Pi 1), we'll assume it's not
|
||||
// powerful enough to handle audio encryption. The Pi 1 could
|
||||
// barely handle Opus decoding alone.
|
||||
if (strstr(cpuinfo, "ARMv6")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
21
src/cpu.h
Normal file
21
src/cpu.h
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* This file is part of Moonlight Embedded.
|
||||
*
|
||||
* Moonlight is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Moonlight is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Moonlight; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool has_fast_aes(void);
|
||||
bool has_slow_aes(void);
|
@ -64,7 +64,7 @@ static void on_cec_keypress(void* userdata, const cec_keypress* key) {
|
||||
value = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (value != 0) {
|
||||
short code = 0x80 << 8 | value;
|
||||
LiSendKeyboardEvent(code, (key->duration > 0)?KEY_ACTION_UP:KEY_ACTION_DOWN, 0);
|
||||
@ -80,25 +80,25 @@ void cec_init() {
|
||||
snprintf(g_config.strDeviceName, sizeof(g_config.strDeviceName), "Moonlight");
|
||||
g_config.callbacks = &g_callbacks;
|
||||
g_config.deviceTypes.types[0] = CEC_DEVICE_TYPE_PLAYBACK_DEVICE;
|
||||
|
||||
|
||||
if (libcecc_initialise(&g_config, &g_iface, NULL) != 1) {
|
||||
fprintf(stderr, "Failed to initialize libcec interface\n");
|
||||
fflush(stderr);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
g_iface.init_video_standalone(g_iface.connection);
|
||||
|
||||
|
||||
cec_adapter devices[10];
|
||||
int8_t iDevicesFound = g_iface.find_adapters(g_iface.connection, devices, sizeof(devices) / sizeof(devices), NULL);
|
||||
|
||||
int8_t iDevicesFound = g_iface.find_adapters(g_iface.connection, devices, sizeof(devices) / sizeof(devices[0]), NULL);
|
||||
|
||||
if (iDevicesFound <= 0) {
|
||||
fprintf(stderr, "No CEC devices found\n");
|
||||
fflush(stderr);
|
||||
libcecc_destroy(&g_iface);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
strcpy(g_strPort, devices[0].comm);
|
||||
if (!g_iface.open(g_iface.connection, g_strPort, 5000)) {
|
||||
fprintf(stderr, "Unable to open the device on port %s\n", g_strPort);
|
||||
@ -106,6 +106,6 @@ void cec_init() {
|
||||
libcecc_destroy(&g_iface);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
g_iface.set_active_source(g_iface.connection, g_config.deviceTypes.types[0]);
|
||||
}
|
||||
|
@ -38,7 +38,12 @@
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#ifdef __linux__
|
||||
#include <endian.h>
|
||||
#else
|
||||
#include <sys/endian.h>
|
||||
#endif
|
||||
#include <math.h>
|
||||
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#define int16_to_le(val) val
|
||||
@ -56,21 +61,35 @@ struct input_abs_parms {
|
||||
struct input_device {
|
||||
struct libevdev *dev;
|
||||
bool is_keyboard;
|
||||
bool is_mouse;
|
||||
bool is_touchscreen;
|
||||
int rotate;
|
||||
struct mapping* map;
|
||||
int key_map[KEY_MAX];
|
||||
int abs_map[ABS_MAX];
|
||||
int key_map[KEY_CNT];
|
||||
int abs_map[ABS_CNT];
|
||||
int hats_state[3][2];
|
||||
int fd;
|
||||
char modifiers;
|
||||
__s32 mouseDeltaX, mouseDeltaY, mouseScroll;
|
||||
#ifdef __linux__
|
||||
__s32 mouseDeltaX, mouseDeltaY, mouseVScroll, mouseHScroll;
|
||||
__s32 touchDownX, touchDownY, touchX, touchY;
|
||||
#else
|
||||
int32_t mouseDeltaX, mouseDeltaY, mouseVScroll, mouseHScroll;
|
||||
int32_t touchDownX, touchDownY, touchX, touchY;
|
||||
#endif
|
||||
struct timeval touchDownTime;
|
||||
struct timeval btnDownTime;
|
||||
short controllerId;
|
||||
int haptic_effect_id;
|
||||
int buttonFlags;
|
||||
char leftTrigger, rightTrigger;
|
||||
unsigned char leftTrigger, rightTrigger;
|
||||
short leftStickX, leftStickY;
|
||||
short rightStickX, rightStickY;
|
||||
bool gamepadModified;
|
||||
bool mouseEmulation;
|
||||
pthread_t meThread;
|
||||
struct input_abs_parms xParms, yParms, rxParms, ryParms, zParms, rzParms;
|
||||
struct input_abs_parms leftParms, rightParms, upParms, downParms;
|
||||
};
|
||||
|
||||
#define HAT_UP 1
|
||||
@ -81,6 +100,23 @@ static const int hat_constants[3][3] = {{HAT_UP | HAT_LEFT, HAT_UP, HAT_UP | HAT
|
||||
|
||||
#define set_hat(flags, flag, hat, hat_flag) flags = (hat & hat_flag) == hat_flag ? flags | flag : flags & ~flag
|
||||
|
||||
#define TOUCH_UP -1
|
||||
#define TOUCH_CLICK_RADIUS 10
|
||||
#define TOUCH_CLICK_DELAY 100000 // microseconds
|
||||
#define TOUCH_RCLICK_TIME 750 // milliseconds
|
||||
|
||||
// How long the Start button must be pressed to toggle mouse emulation
|
||||
#define MOUSE_EMULATION_LONG_PRESS_TIME 750
|
||||
// How long between polling the gamepad to send virtual mouse input
|
||||
#define MOUSE_EMULATION_POLLING_INTERVAL 50000
|
||||
// Determines how fast the mouse will move each interval
|
||||
#define MOUSE_EMULATION_MOTION_MULTIPLIER 3
|
||||
// Determines the maximum motion amount before allowing movement
|
||||
#define MOUSE_EMULATION_DEADZONE 2
|
||||
|
||||
// Limited by number of bits in activeGamepadMask
|
||||
#define MAX_GAMEPADS 16
|
||||
|
||||
static struct input_device* devices = NULL;
|
||||
static int numDevices = 0;
|
||||
static int assignedControllerIds = 0;
|
||||
@ -92,6 +128,9 @@ static short* currentAbs;
|
||||
static bool* currentReverse;
|
||||
|
||||
static bool grabbingDevices;
|
||||
static bool mouseEmulationEnabled;
|
||||
|
||||
static bool waitingToExitOnModifiersUp = false;
|
||||
|
||||
int evdev_gamepads = 0;
|
||||
|
||||
@ -129,13 +168,23 @@ static bool evdev_init_parms(struct input_device *dev, struct input_abs_parms *p
|
||||
static void evdev_remove(int devindex) {
|
||||
numDevices--;
|
||||
|
||||
if (devices[devindex].controllerId >= 0)
|
||||
printf("Input device removed: %s (player %d)\n", libevdev_get_name(devices[devindex].dev), devices[devindex].controllerId + 1);
|
||||
|
||||
if (devices[devindex].controllerId >= 0) {
|
||||
assignedControllerIds &= ~(1 << devices[devindex].controllerId);
|
||||
LiSendMultiControllerEvent(devices[devindex].controllerId, assignedControllerIds, 0, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
if (devices[devindex].mouseEmulation) {
|
||||
devices[devindex].mouseEmulation = false;
|
||||
pthread_join(devices[devindex].meThread, NULL);
|
||||
}
|
||||
|
||||
libevdev_free(devices[devindex].dev);
|
||||
loop_remove_fd(devices[devindex].fd);
|
||||
close(devices[devindex].fd);
|
||||
|
||||
if (devindex != numDevices && numDevices > 0)
|
||||
memcpy(&devices[devindex], &devices[numDevices], sizeof(struct input_device));
|
||||
|
||||
fprintf(stderr, "Removed input device\n");
|
||||
}
|
||||
|
||||
static short evdev_convert_value(struct input_event *ev, struct input_device *dev, struct input_abs_parms *parms, bool reverse) {
|
||||
@ -156,18 +205,121 @@ static short evdev_convert_value(struct input_event *ev, struct input_device *de
|
||||
return (long long)(ev->value - (ev->value>parms->avg?parms->flat*2:0) - parms->min) * (SHRT_MAX-SHRT_MIN) / (parms->max-parms->min-parms->flat*2) + SHRT_MIN;
|
||||
}
|
||||
|
||||
static char evdev_convert_value_byte(struct input_event *ev, struct input_device *dev, struct input_abs_parms *parms) {
|
||||
static unsigned char evdev_convert_value_byte(struct input_event *ev, struct input_device *dev, struct input_abs_parms *parms, char halfaxis) {
|
||||
if (parms->max == 0 && parms->min == 0) {
|
||||
fprintf(stderr, "Axis not found: %d\n", ev->code);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (abs(ev->value-parms->min)<parms->flat)
|
||||
return 0;
|
||||
else if (ev->value>parms->max)
|
||||
return UCHAR_MAX;
|
||||
else
|
||||
return (ev->value - parms->flat - parms->min) * UCHAR_MAX / (parms->diff - parms->flat);
|
||||
if (halfaxis == 0) {
|
||||
if (abs(ev->value-parms->min)<parms->flat)
|
||||
return 0;
|
||||
else if (ev->value>parms->max)
|
||||
return UCHAR_MAX;
|
||||
else
|
||||
return (ev->value - parms->flat - parms->min) * UCHAR_MAX / (parms->diff - parms->flat);
|
||||
} else {
|
||||
short val = evdev_convert_value(ev, dev, parms, false);
|
||||
if (halfaxis == '-' && val < 0)
|
||||
return -(int)val * UCHAR_MAX / (SHRT_MAX-SHRT_MIN);
|
||||
else if (halfaxis == '+' && val > 0)
|
||||
return (int)val * UCHAR_MAX / (SHRT_MAX-SHRT_MIN);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void *HandleMouseEmulation(void* param)
|
||||
{
|
||||
struct input_device* dev = (struct input_device*) param;
|
||||
|
||||
while (dev->mouseEmulation) {
|
||||
usleep(MOUSE_EMULATION_POLLING_INTERVAL);
|
||||
|
||||
short rawX;
|
||||
short rawY;
|
||||
|
||||
// Determine which analog stick is currently receiving the strongest input
|
||||
if ((uint32_t)abs(dev->leftStickX) + abs(dev->leftStickY) > (uint32_t)abs(dev->rightStickX) + abs(dev->rightStickY)) {
|
||||
rawX = dev->leftStickX;
|
||||
rawY = dev->leftStickY;
|
||||
} else {
|
||||
rawX = dev->rightStickX;
|
||||
rawY = dev->rightStickY;
|
||||
}
|
||||
|
||||
float deltaX;
|
||||
float deltaY;
|
||||
|
||||
// Produce a base vector for mouse movement with increased speed as we deviate further from center
|
||||
deltaX = pow((float)rawX / 32767.0f * MOUSE_EMULATION_MOTION_MULTIPLIER, 3);
|
||||
deltaY = pow((float)rawY / 32767.0f * MOUSE_EMULATION_MOTION_MULTIPLIER, 3);
|
||||
|
||||
// Enforce deadzones
|
||||
deltaX = fabs(deltaX) > MOUSE_EMULATION_DEADZONE ? deltaX - MOUSE_EMULATION_DEADZONE : 0;
|
||||
deltaY = fabs(deltaY) > MOUSE_EMULATION_DEADZONE ? deltaY - MOUSE_EMULATION_DEADZONE : 0;
|
||||
|
||||
if (deltaX != 0 || deltaY != 0)
|
||||
LiSendMouseMoveEvent(deltaX, -deltaY);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define SET_BTN_FLAG(x, y) supportedButtonFlags |= (x >= 0) ? y : 0
|
||||
|
||||
static void send_controller_arrival(struct input_device *dev) {
|
||||
unsigned char type = LI_CTYPE_UNKNOWN;
|
||||
unsigned int supportedButtonFlags = 0;
|
||||
unsigned short capabilities = 0;
|
||||
|
||||
switch (libevdev_get_id_vendor(dev->dev)) {
|
||||
case 0x045e: // Microsoft
|
||||
type = LI_CTYPE_XBOX;
|
||||
break;
|
||||
case 0x054c: // Sony
|
||||
type = LI_CTYPE_PS;
|
||||
break;
|
||||
case 0x057e: // Nintendo
|
||||
type = LI_CTYPE_NINTENDO;
|
||||
break;
|
||||
}
|
||||
|
||||
const char* name = libevdev_get_name(dev->dev);
|
||||
if (name && type == LI_CTYPE_UNKNOWN) {
|
||||
|
||||
// Try to guess based on the name
|
||||
if (strstr(name, "Xbox") || strstr(name, "X-Box") || strstr(name, "XBox") || strstr(name, "XBOX")) {
|
||||
type = LI_CTYPE_XBOX;
|
||||
}
|
||||
}
|
||||
|
||||
SET_BTN_FLAG(dev->map->btn_a, A_FLAG);
|
||||
SET_BTN_FLAG(dev->map->btn_b, B_FLAG);
|
||||
SET_BTN_FLAG(dev->map->btn_x, X_FLAG);
|
||||
SET_BTN_FLAG(dev->map->btn_y, Y_FLAG);
|
||||
SET_BTN_FLAG(dev->map->btn_back, BACK_FLAG);
|
||||
SET_BTN_FLAG(dev->map->btn_start, PLAY_FLAG);
|
||||
SET_BTN_FLAG(dev->map->btn_guide, SPECIAL_FLAG);
|
||||
SET_BTN_FLAG(dev->map->btn_leftstick, LS_CLK_FLAG);
|
||||
SET_BTN_FLAG(dev->map->btn_rightstick, RS_CLK_FLAG);
|
||||
SET_BTN_FLAG(dev->map->btn_leftshoulder, LB_FLAG);
|
||||
SET_BTN_FLAG(dev->map->btn_rightshoulder, RB_FLAG);
|
||||
SET_BTN_FLAG(dev->map->btn_misc1, MISC_FLAG);
|
||||
SET_BTN_FLAG(dev->map->btn_paddle1, PADDLE1_FLAG);
|
||||
SET_BTN_FLAG(dev->map->btn_paddle2, PADDLE2_FLAG);
|
||||
SET_BTN_FLAG(dev->map->btn_paddle3, PADDLE3_FLAG);
|
||||
SET_BTN_FLAG(dev->map->btn_paddle4, PADDLE4_FLAG);
|
||||
SET_BTN_FLAG(dev->map->btn_touchpad, TOUCHPAD_FLAG);
|
||||
|
||||
if (dev->map->abs_lefttrigger >= 0 && dev->map->abs_righttrigger >= 0)
|
||||
capabilities |= LI_CCAP_ANALOG_TRIGGERS;
|
||||
|
||||
// TODO: Probe for this properly
|
||||
capabilities |= LI_CCAP_RUMBLE;
|
||||
|
||||
LiSendControllerArrivalEvent(dev->controllerId, assignedControllerIds, type,
|
||||
supportedButtonFlags, capabilities);
|
||||
}
|
||||
|
||||
static bool evdev_handle_event(struct input_event *ev, struct input_device *dev) {
|
||||
@ -176,32 +328,57 @@ static bool evdev_handle_event(struct input_event *ev, struct input_device *dev)
|
||||
switch (ev->type) {
|
||||
case EV_SYN:
|
||||
if (dev->mouseDeltaX != 0 || dev->mouseDeltaY != 0) {
|
||||
LiSendMouseMoveEvent(dev->mouseDeltaX, dev->mouseDeltaY);
|
||||
switch (dev->rotate) {
|
||||
case 90:
|
||||
LiSendMouseMoveEvent(dev->mouseDeltaY, -dev->mouseDeltaX);
|
||||
break;
|
||||
case 180:
|
||||
LiSendMouseMoveEvent(-dev->mouseDeltaX, -dev->mouseDeltaY);
|
||||
break;
|
||||
case 270:
|
||||
LiSendMouseMoveEvent(-dev->mouseDeltaY, dev->mouseDeltaX);
|
||||
break;
|
||||
default:
|
||||
LiSendMouseMoveEvent(dev->mouseDeltaX, dev->mouseDeltaY);
|
||||
break;
|
||||
}
|
||||
dev->mouseDeltaX = 0;
|
||||
dev->mouseDeltaY = 0;
|
||||
}
|
||||
if (dev->mouseScroll != 0) {
|
||||
LiSendScrollEvent(dev->mouseScroll);
|
||||
dev->mouseScroll = 0;
|
||||
if (dev->mouseVScroll != 0) {
|
||||
LiSendScrollEvent(dev->mouseVScroll);
|
||||
dev->mouseVScroll = 0;
|
||||
}
|
||||
if (dev->mouseHScroll != 0) {
|
||||
LiSendHScrollEvent(dev->mouseHScroll);
|
||||
dev->mouseHScroll = 0;
|
||||
}
|
||||
if (dev->gamepadModified) {
|
||||
if (dev->controllerId < 0) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int i = 0; i < MAX_GAMEPADS; i++) {
|
||||
if ((assignedControllerIds & (1 << i)) == 0) {
|
||||
assignedControllerIds |= (1 << i);
|
||||
dev->controllerId = i;
|
||||
printf("Assigned %s as player %d\n", libevdev_get_name(dev->dev), i+1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
//Use id 0 when too many gamepads are connected
|
||||
if (dev->controllerId < 0)
|
||||
dev->controllerId = 0;
|
||||
|
||||
// Send controller arrival event to the host
|
||||
send_controller_arrival(dev);
|
||||
}
|
||||
LiSendMultiControllerEvent(dev->controllerId, assignedControllerIds, dev->buttonFlags, dev->leftTrigger, dev->rightTrigger, dev->leftStickX, dev->leftStickY, dev->rightStickX, dev->rightStickY);
|
||||
// Send event only if mouse emulation is disabled.
|
||||
if (dev->mouseEmulation == false)
|
||||
LiSendMultiControllerEvent(dev->controllerId, assignedControllerIds, dev->buttonFlags, dev->leftTrigger, dev->rightTrigger, dev->leftStickX, dev->leftStickY, dev->rightStickX, dev->rightStickY);
|
||||
dev->gamepadModified = false;
|
||||
}
|
||||
break;
|
||||
case EV_KEY:
|
||||
if (ev->code > KEY_MAX)
|
||||
return true;
|
||||
if (ev->code < sizeof(keyCodes)/sizeof(keyCodes[0])) {
|
||||
char modifier = 0;
|
||||
switch (ev->code) {
|
||||
@ -217,6 +394,10 @@ static bool evdev_handle_event(struct input_event *ev, struct input_device *dev)
|
||||
case KEY_RIGHTCTRL:
|
||||
modifier = MODIFIER_CTRL;
|
||||
break;
|
||||
case KEY_LEFTMETA:
|
||||
case KEY_RIGHTMETA:
|
||||
modifier = MODIFIER_META;
|
||||
break;
|
||||
}
|
||||
if (modifier != 0) {
|
||||
if (ev->value)
|
||||
@ -225,18 +406,20 @@ static bool evdev_handle_event(struct input_event *ev, struct input_device *dev)
|
||||
dev->modifiers &= ~modifier;
|
||||
}
|
||||
|
||||
// Quit the stream if all the required quit keys are down
|
||||
// After the quit key combo is pressed, quit once all keys are raised
|
||||
if ((dev->modifiers & ACTION_MODIFIERS) == ACTION_MODIFIERS &&
|
||||
ev->code == QUIT_KEY && ev->value != 0) {
|
||||
waitingToExitOnModifiersUp = true;
|
||||
return true;
|
||||
} else if (waitingToExitOnModifiersUp && dev->modifiers == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
short code = 0x80 << 8 | keyCodes[ev->code];
|
||||
LiSendKeyboardEvent(code, ev->value?KEY_ACTION_DOWN:KEY_ACTION_UP, dev->modifiers);
|
||||
} else {
|
||||
int mouseCode = 0;
|
||||
short gamepadCode = 0;
|
||||
int index = ev->code > BTN_MISC && ev->code < (BTN_MISC + KEY_MAX) ? dev->key_map[ev->code - BTN_MISC] : -1;
|
||||
int gamepadCode = 0;
|
||||
int index = dev->key_map[ev->code];
|
||||
|
||||
switch (ev->code) {
|
||||
case BTN_LEFT:
|
||||
@ -254,6 +437,27 @@ static bool evdev_handle_event(struct input_event *ev, struct input_device *dev)
|
||||
case BTN_EXTRA:
|
||||
mouseCode = BUTTON_X2;
|
||||
break;
|
||||
case BTN_TOUCH:
|
||||
if (ev->value == 1) {
|
||||
dev->touchDownTime = ev->time;
|
||||
} else {
|
||||
if (dev->touchDownX != TOUCH_UP && dev->touchDownY != TOUCH_UP) {
|
||||
int deltaX = dev->touchX - dev->touchDownX;
|
||||
int deltaY = dev->touchY - dev->touchDownY;
|
||||
if (deltaX * deltaX + deltaY * deltaY < TOUCH_CLICK_RADIUS * TOUCH_CLICK_RADIUS) {
|
||||
struct timeval elapsedTime;
|
||||
timersub(&ev->time, &dev->touchDownTime, &elapsedTime);
|
||||
int holdTimeMs = elapsedTime.tv_sec * 1000 + elapsedTime.tv_usec / 1000;
|
||||
int button = holdTimeMs >= TOUCH_RCLICK_TIME ? BUTTON_RIGHT : BUTTON_LEFT;
|
||||
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, button);
|
||||
usleep(TOUCH_CLICK_DELAY);
|
||||
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, button);
|
||||
}
|
||||
}
|
||||
dev->touchDownX = TOUCH_UP;
|
||||
dev->touchDownY = TOUCH_UP;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
gamepadModified = true;
|
||||
if (dev->map == NULL)
|
||||
@ -288,16 +492,68 @@ static bool evdev_handle_event(struct input_event *ev, struct input_device *dev)
|
||||
gamepadCode = BACK_FLAG;
|
||||
else if (index == dev->map->btn_guide)
|
||||
gamepadCode = SPECIAL_FLAG;
|
||||
else if (index == dev->map->btn_misc1)
|
||||
gamepadCode = MISC_FLAG;
|
||||
else if (index == dev->map->btn_paddle1)
|
||||
gamepadCode = PADDLE1_FLAG;
|
||||
else if (index == dev->map->btn_paddle2)
|
||||
gamepadCode = PADDLE2_FLAG;
|
||||
else if (index == dev->map->btn_paddle3)
|
||||
gamepadCode = PADDLE3_FLAG;
|
||||
else if (index == dev->map->btn_paddle4)
|
||||
gamepadCode = PADDLE4_FLAG;
|
||||
else if (index == dev->map->btn_touchpad)
|
||||
gamepadCode = TOUCHPAD_FLAG;
|
||||
}
|
||||
|
||||
if (mouseCode != 0) {
|
||||
LiSendMouseButtonEvent(ev->value?BUTTON_ACTION_PRESS:BUTTON_ACTION_RELEASE, mouseCode);
|
||||
gamepadModified = false;
|
||||
} else if (gamepadCode != 0) {
|
||||
if (ev->value)
|
||||
if (ev->value) {
|
||||
dev->buttonFlags |= gamepadCode;
|
||||
else
|
||||
dev->btnDownTime = ev->time;
|
||||
} else
|
||||
dev->buttonFlags &= ~gamepadCode;
|
||||
|
||||
if (mouseEmulationEnabled && gamepadCode == PLAY_FLAG && ev->value == 0) {
|
||||
struct timeval elapsedTime;
|
||||
timersub(&ev->time, &dev->btnDownTime, &elapsedTime);
|
||||
int holdTimeMs = elapsedTime.tv_sec * 1000 + elapsedTime.tv_usec / 1000;
|
||||
if (holdTimeMs >= MOUSE_EMULATION_LONG_PRESS_TIME) {
|
||||
if (dev->mouseEmulation) {
|
||||
dev->mouseEmulation = false;
|
||||
pthread_join(dev->meThread, NULL);
|
||||
dev->meThread = 0;
|
||||
printf("Mouse emulation disabled for controller %d.\n", dev->controllerId);
|
||||
} else {
|
||||
dev->mouseEmulation = true;
|
||||
pthread_create(&dev->meThread, NULL, HandleMouseEmulation, dev);
|
||||
printf("Mouse emulation enabled for controller %d.\n", dev->controllerId);
|
||||
}
|
||||
// clear gamepad state.
|
||||
LiSendMultiControllerEvent(dev->controllerId, assignedControllerIds, 0, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
} else if (dev->mouseEmulation) {
|
||||
char action = ev->value ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE;
|
||||
switch (gamepadCode) {
|
||||
case A_FLAG:
|
||||
LiSendMouseButtonEvent(action, BUTTON_LEFT);
|
||||
break;
|
||||
case B_FLAG:
|
||||
LiSendMouseButtonEvent(action, BUTTON_RIGHT);
|
||||
break;
|
||||
case X_FLAG:
|
||||
LiSendMouseButtonEvent(action, BUTTON_MIDDLE);
|
||||
break;
|
||||
case LB_FLAG:
|
||||
LiSendMouseButtonEvent(action, BUTTON_X1);
|
||||
break;
|
||||
case RB_FLAG:
|
||||
LiSendMouseButtonEvent(action, BUTTON_X2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (dev->map != NULL && index == dev->map->btn_lefttrigger)
|
||||
dev->leftTrigger = ev->value ? UCHAR_MAX : 0;
|
||||
else if (dev->map != NULL && index == dev->map->btn_righttrigger)
|
||||
@ -318,12 +574,41 @@ static bool evdev_handle_event(struct input_event *ev, struct input_device *dev)
|
||||
case REL_Y:
|
||||
dev->mouseDeltaY = ev->value;
|
||||
break;
|
||||
case REL_HWHEEL:
|
||||
dev->mouseHScroll = ev->value;
|
||||
break;
|
||||
case REL_WHEEL:
|
||||
dev->mouseScroll = ev->value;
|
||||
dev->mouseVScroll = ev->value;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case EV_ABS:
|
||||
if (ev->code > ABS_MAX)
|
||||
return true;
|
||||
if (dev->is_touchscreen) {
|
||||
switch (ev->code) {
|
||||
case ABS_X:
|
||||
if (dev->touchDownX == TOUCH_UP) {
|
||||
dev->touchDownX = ev->value;
|
||||
dev->touchX = ev->value;
|
||||
} else {
|
||||
dev->mouseDeltaX += (ev->value - dev->touchX);
|
||||
dev->touchX = ev->value;
|
||||
}
|
||||
break;
|
||||
case ABS_Y:
|
||||
if (dev->touchDownY == TOUCH_UP) {
|
||||
dev->touchDownY = ev->value;
|
||||
dev->touchY = ev->value;
|
||||
} else {
|
||||
dev->mouseDeltaY += (ev->value - dev->touchY);
|
||||
dev->touchY = ev->value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (dev->map == NULL)
|
||||
break;
|
||||
|
||||
@ -361,17 +646,57 @@ static bool evdev_handle_event(struct input_event *ev, struct input_device *dev)
|
||||
dev->rightStickX = evdev_convert_value(ev, dev, &dev->rxParms, dev->map->reverse_rightx);
|
||||
else if (index == dev->map->abs_righty)
|
||||
dev->rightStickY = evdev_convert_value(ev, dev, &dev->ryParms, !dev->map->reverse_righty);
|
||||
else if (index == dev->map->abs_lefttrigger)
|
||||
dev->leftTrigger = evdev_convert_value_byte(ev, dev, &dev->zParms);
|
||||
else if (index == dev->map->abs_righttrigger)
|
||||
dev->rightTrigger = evdev_convert_value_byte(ev, dev, &dev->rzParms);
|
||||
else
|
||||
gamepadModified = false;
|
||||
|
||||
if (index == dev->map->abs_lefttrigger) {
|
||||
dev->leftTrigger = evdev_convert_value_byte(ev, dev, &dev->zParms, dev->map->halfaxis_lefttrigger);
|
||||
gamepadModified = true;
|
||||
}
|
||||
if (index == dev->map->abs_righttrigger) {
|
||||
dev->rightTrigger = evdev_convert_value_byte(ev, dev, &dev->rzParms, dev->map->halfaxis_righttrigger);
|
||||
gamepadModified = true;
|
||||
}
|
||||
|
||||
if (index == dev->map->abs_dpright) {
|
||||
if (evdev_convert_value_byte(ev, dev, &dev->rightParms, dev->map->halfaxis_dpright) > 127)
|
||||
dev->buttonFlags |= RIGHT_FLAG;
|
||||
else
|
||||
dev->buttonFlags &= ~RIGHT_FLAG;
|
||||
|
||||
gamepadModified = true;
|
||||
}
|
||||
if (index == dev->map->abs_dpleft) {
|
||||
if (evdev_convert_value_byte(ev, dev, &dev->leftParms, dev->map->halfaxis_dpleft) > 127)
|
||||
dev->buttonFlags |= LEFT_FLAG;
|
||||
else
|
||||
dev->buttonFlags &= ~LEFT_FLAG;
|
||||
|
||||
gamepadModified = true;
|
||||
}
|
||||
if (index == dev->map->abs_dpup) {
|
||||
if (evdev_convert_value_byte(ev, dev, &dev->upParms, dev->map->halfaxis_dpup) > 127)
|
||||
dev->buttonFlags |= UP_FLAG;
|
||||
else
|
||||
dev->buttonFlags &= ~UP_FLAG;
|
||||
|
||||
gamepadModified = true;
|
||||
}
|
||||
if (index == dev->map->abs_dpdown) {
|
||||
if (evdev_convert_value_byte(ev, dev, &dev->downParms, dev->map->halfaxis_dpdown) > 127)
|
||||
dev->buttonFlags |= DOWN_FLAG;
|
||||
else
|
||||
dev->buttonFlags &= ~DOWN_FLAG;
|
||||
|
||||
gamepadModified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gamepadModified && (dev->buttonFlags & QUIT_BUTTONS) == QUIT_BUTTONS)
|
||||
if (gamepadModified && (dev->buttonFlags & QUIT_BUTTONS) == QUIT_BUTTONS) {
|
||||
LiSendMultiControllerEvent(dev->controllerId, assignedControllerIds, 0, 0, 0, 0, 0, 0, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
dev->gamepadModified |= gamepadModified;
|
||||
return true;
|
||||
@ -381,7 +706,7 @@ static bool evdev_handle_mapping_event(struct input_event *ev, struct input_devi
|
||||
int index, hat_index;
|
||||
switch (ev->type) {
|
||||
case EV_KEY:
|
||||
index = ev->code > BTN_MISC && ev->code < (BTN_MISC + KEY_MAX) ? dev->key_map[ev->code - BTN_MISC] : -1;
|
||||
index = dev->key_map[ev->code];
|
||||
if (currentKey != NULL) {
|
||||
if (ev->value)
|
||||
*currentKey = index;
|
||||
@ -450,7 +775,7 @@ static int evdev_handle(int fd) {
|
||||
return LOOP_OK;
|
||||
}
|
||||
|
||||
void evdev_create(const char* device, struct mapping* mappings, bool verbose) {
|
||||
void evdev_create(const char* device, struct mapping* mappings, bool verbose, int rotate) {
|
||||
int fd = open(device, O_RDWR|O_NONBLOCK);
|
||||
if (fd <= 0) {
|
||||
fprintf(stderr, "Failed to open device %s\n", device);
|
||||
@ -499,15 +824,55 @@ void evdev_create(const char* device, struct mapping* mappings, bool verbose) {
|
||||
|
||||
bool is_keyboard = libevdev_has_event_code(evdev, EV_KEY, KEY_Q);
|
||||
bool is_mouse = libevdev_has_event_type(evdev, EV_REL) || libevdev_has_event_code(evdev, EV_KEY, BTN_LEFT);
|
||||
bool is_touchscreen = libevdev_has_event_code(evdev, EV_KEY, BTN_TOUCH);
|
||||
|
||||
if (mappings == NULL && !(is_keyboard || is_mouse)) {
|
||||
fprintf(stderr, "No mapping available for %s (%s) on %s\n", name, str_guid, device);
|
||||
mappings = default_mapping;
|
||||
// This classification logic comes from SDL
|
||||
bool is_accelerometer =
|
||||
((libevdev_has_event_code(evdev, EV_ABS, ABS_X) &&
|
||||
libevdev_has_event_code(evdev, EV_ABS, ABS_Y) &&
|
||||
libevdev_has_event_code(evdev, EV_ABS, ABS_Z)) ||
|
||||
(libevdev_has_event_code(evdev, EV_ABS, ABS_RX) &&
|
||||
libevdev_has_event_code(evdev, EV_ABS, ABS_RY) &&
|
||||
libevdev_has_event_code(evdev, EV_ABS, ABS_RZ))) &&
|
||||
!libevdev_has_event_type(evdev, EV_KEY);
|
||||
bool is_gamepad =
|
||||
((libevdev_has_event_code(evdev, EV_ABS, ABS_X) &&
|
||||
libevdev_has_event_code(evdev, EV_ABS, ABS_Y)) ||
|
||||
(libevdev_has_event_code(evdev, EV_ABS, ABS_HAT0X) &&
|
||||
libevdev_has_event_code(evdev, EV_ABS, ABS_HAT0Y))) &&
|
||||
(libevdev_has_event_code(evdev, EV_KEY, BTN_TRIGGER) ||
|
||||
libevdev_has_event_code(evdev, EV_KEY, BTN_A) ||
|
||||
libevdev_has_event_code(evdev, EV_KEY, BTN_1) ||
|
||||
libevdev_has_event_code(evdev, EV_ABS, ABS_RX) ||
|
||||
libevdev_has_event_code(evdev, EV_ABS, ABS_RY) ||
|
||||
libevdev_has_event_code(evdev, EV_ABS, ABS_RZ) ||
|
||||
libevdev_has_event_code(evdev, EV_ABS, ABS_THROTTLE) ||
|
||||
libevdev_has_event_code(evdev, EV_ABS, ABS_RUDDER) ||
|
||||
libevdev_has_event_code(evdev, EV_ABS, ABS_WHEEL) ||
|
||||
libevdev_has_event_code(evdev, EV_ABS, ABS_GAS) ||
|
||||
libevdev_has_event_code(evdev, EV_ABS, ABS_BRAKE));
|
||||
|
||||
if (is_accelerometer) {
|
||||
if (verbose)
|
||||
printf("Ignoring accelerometer: %s\n", name);
|
||||
libevdev_free(evdev);
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_keyboard && !is_mouse)
|
||||
if (is_gamepad) {
|
||||
evdev_gamepads++;
|
||||
|
||||
if (mappings == NULL) {
|
||||
fprintf(stderr, "No mapping available for %s (%s) on %s\n", name, str_guid, device);
|
||||
mappings = default_mapping;
|
||||
}
|
||||
} else {
|
||||
if (verbose)
|
||||
printf("Not mapping %s as a gamepad\n", name);
|
||||
mappings = NULL;
|
||||
}
|
||||
|
||||
int dev = numDevices;
|
||||
numDevices++;
|
||||
|
||||
@ -526,18 +891,25 @@ void evdev_create(const char* device, struct mapping* mappings, bool verbose) {
|
||||
devices[dev].fd = fd;
|
||||
devices[dev].dev = evdev;
|
||||
devices[dev].map = mappings;
|
||||
/* Set unused evdev indices to -2 to avoid aliasing with the default -1 in our mappings */
|
||||
memset(&devices[dev].key_map, -2, sizeof(devices[dev].key_map));
|
||||
memset(&devices[dev].abs_map, -2, sizeof(devices[dev].abs_map));
|
||||
devices[dev].is_keyboard = is_keyboard;
|
||||
devices[dev].is_mouse = is_mouse;
|
||||
devices[dev].is_touchscreen = is_touchscreen;
|
||||
devices[dev].rotate = rotate;
|
||||
devices[dev].touchDownX = TOUCH_UP;
|
||||
devices[dev].touchDownY = TOUCH_UP;
|
||||
|
||||
int nbuttons = 0;
|
||||
/* Count joystick buttons first like SDL does */
|
||||
for (int i = BTN_JOYSTICK; i < KEY_MAX; ++i) {
|
||||
if (libevdev_has_event_code(devices[dev].dev, EV_KEY, i))
|
||||
devices[dev].key_map[i - BTN_MISC] = nbuttons++;
|
||||
devices[dev].key_map[i] = nbuttons++;
|
||||
}
|
||||
for (int i = BTN_MISC; i < BTN_JOYSTICK; ++i) {
|
||||
for (int i = 0; i < BTN_JOYSTICK; ++i) {
|
||||
if (libevdev_has_event_code(devices[dev].dev, EV_KEY, i))
|
||||
devices[dev].key_map[i - BTN_MISC] = nbuttons++;
|
||||
devices[dev].key_map[i] = nbuttons++;
|
||||
}
|
||||
|
||||
int naxes = 0;
|
||||
@ -559,11 +931,15 @@ void evdev_create(const char* device, struct mapping* mappings, bool verbose) {
|
||||
valid &= evdev_init_parms(&devices[dev], &(devices[dev].rxParms), devices[dev].map->abs_rightx);
|
||||
valid &= evdev_init_parms(&devices[dev], &(devices[dev].ryParms), devices[dev].map->abs_righty);
|
||||
valid &= evdev_init_parms(&devices[dev], &(devices[dev].rzParms), devices[dev].map->abs_righttrigger);
|
||||
valid &= evdev_init_parms(&devices[dev], &(devices[dev].leftParms), devices[dev].map->abs_dpleft);
|
||||
valid &= evdev_init_parms(&devices[dev], &(devices[dev].rightParms), devices[dev].map->abs_dpright);
|
||||
valid &= evdev_init_parms(&devices[dev], &(devices[dev].upParms), devices[dev].map->abs_dpup);
|
||||
valid &= evdev_init_parms(&devices[dev], &(devices[dev].downParms), devices[dev].map->abs_dpdown);
|
||||
if (!valid)
|
||||
fprintf(stderr, "Mapping for %s (%s) on %s is incorrect\n", name, str_guid, device);
|
||||
}
|
||||
|
||||
if (grabbingDevices && is_keyboard) {
|
||||
if (grabbingDevices && (is_keyboard || is_mouse || is_touchscreen)) {
|
||||
if (ioctl(fd, EVIOCGRAB, 1) < 0) {
|
||||
fprintf(stderr, "EVIOCGRAB failed with error %d\n", errno);
|
||||
}
|
||||
@ -643,10 +1019,10 @@ void evdev_map(char* device) {
|
||||
char* buf = str_guid;
|
||||
for (int i = 0; i < 16; i++)
|
||||
buf += sprintf(buf, "%02x", ((unsigned char*) guid)[i]);
|
||||
|
||||
struct mapping map;
|
||||
strncpy(map.name, libevdev_get_name(evdev), sizeof(map.name));
|
||||
strncpy(map.guid, str_guid, sizeof(map.guid));
|
||||
|
||||
struct mapping map = {0};
|
||||
strncpy(map.name, name, sizeof(map.name) - 1);
|
||||
strncpy(map.guid, str_guid, sizeof(map.guid) - 1);
|
||||
|
||||
libevdev_free(evdev);
|
||||
close(fd);
|
||||
@ -690,7 +1066,7 @@ void evdev_start() {
|
||||
// we're ready to take input events. Ctrl+C works up until
|
||||
// this point.
|
||||
for (int i = 0; i < numDevices; i++) {
|
||||
if (devices[i].is_keyboard && ioctl(devices[i].fd, EVIOCGRAB, 1) < 0) {
|
||||
if ((devices[i].is_keyboard || devices[i].is_mouse || devices[i].is_touchscreen) && ioctl(devices[i].fd, EVIOCGRAB, 1) < 0) {
|
||||
fprintf(stderr, "EVIOCGRAB failed with error %d\n", errno);
|
||||
}
|
||||
}
|
||||
@ -705,8 +1081,9 @@ void evdev_stop() {
|
||||
evdev_drain();
|
||||
}
|
||||
|
||||
void evdev_init() {
|
||||
void evdev_init(bool mouse_emulation_enabled) {
|
||||
handler = evdev_handle_event;
|
||||
mouseEmulationEnabled = mouse_emulation_enabled;
|
||||
}
|
||||
|
||||
static struct input_device* evdev_get_input_device(unsigned short controller_id) {
|
||||
|
@ -21,10 +21,10 @@
|
||||
|
||||
extern int evdev_gamepads;
|
||||
|
||||
void evdev_create(const char* device, struct mapping* mappings, bool verbose);
|
||||
void evdev_create(const char* device, struct mapping* mappings, bool verbose, int rotate);
|
||||
void evdev_loop();
|
||||
|
||||
void evdev_init();
|
||||
void evdev_init(bool mouse_emulation_enabled);
|
||||
void evdev_start();
|
||||
void evdev_stop();
|
||||
void evdev_map(char* device);
|
||||
|
@ -47,7 +47,7 @@ static const short keyCodes[] = {
|
||||
0xDB, //VK_BRACELEFT
|
||||
0xDD, //VK_BRACERIGHT
|
||||
0x0D, //VK_ENTER
|
||||
0x11, //VK_CONTROL Left control
|
||||
0xA2, //VK_CONTROL Left control
|
||||
0x41, //VK_A
|
||||
0x53, //VK_S
|
||||
0x44, //VK_D
|
||||
@ -60,7 +60,7 @@ static const short keyCodes[] = {
|
||||
0xBA, //VK_SEMICOLON
|
||||
0xDE, //VK_APOSTROPHE
|
||||
0xC0, //VK_GRAVE
|
||||
0x10, //VK_SHIFT Left shift
|
||||
0xA0, //VK_SHIFT Left shift
|
||||
0xDC, //VK_BACK_SLASH
|
||||
0x5A, //VK_Z
|
||||
0x58, //VK_X
|
||||
@ -72,9 +72,9 @@ static const short keyCodes[] = {
|
||||
0xBC, //VK_COMMA
|
||||
0xBE, //VK_DOT
|
||||
0xBF, //VK_SLASH
|
||||
0x10, //VK_SHIFT Right shift
|
||||
0xA1, //VK_SHIFT Right shift
|
||||
0x6A, //VK_KPASTERISK
|
||||
0x12, //VK_ALT Left alt
|
||||
0xA4, //VK_ALT Left alt
|
||||
0x20, //VK_SPACE
|
||||
0x14, //VK_CAPS_LOCK
|
||||
0x70, //VK_F1
|
||||
@ -115,10 +115,10 @@ static const short keyCodes[] = {
|
||||
0, //VK_MUHENKAN
|
||||
0, //VK_KPJPCOMMA
|
||||
0x0D, //VK_KPENTER
|
||||
0x11, //VK_CONTROL Right ctrl
|
||||
0xA3, //VK_CONTROL Right ctrl
|
||||
0x6F, //VK_KPSLASH
|
||||
0x2C, //VK_SYSRQ
|
||||
0x12, //VK_ALT Right alt
|
||||
0xA5, //VK_ALT Right alt
|
||||
0, //KeyEvent.VK_LINEFEED
|
||||
0x24, //VK_HOME
|
||||
0x26, //VK_UP
|
||||
@ -128,7 +128,7 @@ static const short keyCodes[] = {
|
||||
0x23, //VK_END
|
||||
0x28, //VK_DOWN
|
||||
0x22, //VK_PAGE_DOWN
|
||||
0x9B, //VK_INSERT
|
||||
0x2D, //VK_INSERT
|
||||
0x2E, //VK_DELETE
|
||||
0, //VK_MACRO
|
||||
0, //VK_MUTE
|
||||
@ -139,4 +139,11 @@ static const short keyCodes[] = {
|
||||
0, //VK_KPPLUSMINUS
|
||||
0x13, //VK_PAUSE
|
||||
0, //VK_SCALE AL Compiz Scale (Expose)
|
||||
0, //KEY_KPCOMMA
|
||||
0, //KEY_HANGEUL
|
||||
0, //KEY_HANJA
|
||||
0, //KEY_YEN
|
||||
0x5B, //KEY_LEFTMETA
|
||||
0x5C, //KEY_RIGHTMETA
|
||||
0, //KEY_COMPOSE
|
||||
};
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
struct mapping* mapping_parse(char* mapping) {
|
||||
@ -31,25 +32,33 @@ struct mapping* mapping_parse(char* mapping) {
|
||||
if (guid == NULL || name == NULL)
|
||||
return NULL;
|
||||
|
||||
struct mapping* map = malloc(sizeof(struct mapping));
|
||||
struct mapping* map = calloc(sizeof(struct mapping), 1);
|
||||
if (map == NULL) {
|
||||
fprintf(stderr, "Not enough memory");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
strncpy(map->guid, guid, sizeof(map->guid));
|
||||
strncpy(map->name, name, sizeof(map->name));
|
||||
memset(&map->abs_leftx, -1, sizeof(short) * 31);
|
||||
strncpy(map->guid, guid, sizeof(map->guid) - 1);
|
||||
strncpy(map->name, name, sizeof(map->name) - 1);
|
||||
|
||||
/* Initialize all mapping indices to -1 to ensure they won't match anything */
|
||||
memset(&map->abs_leftx, -1, offsetof(struct mapping, next) - offsetof(struct mapping, abs_leftx));
|
||||
|
||||
char* option;
|
||||
while ((option = strtok_r(NULL, ",", &strpoint)) != NULL) {
|
||||
char *key = NULL, *value = NULL;
|
||||
char *key = NULL, *orig_value = NULL;
|
||||
int ret;
|
||||
if ((ret = sscanf(option, "%m[^:]:%ms", &key, &value)) == 2) {
|
||||
if ((ret = sscanf(option, "%m[^:]:%ms", &key, &orig_value)) == 2) {
|
||||
int int_value, direction_value;
|
||||
char *value = orig_value;
|
||||
char flag = 0;
|
||||
char half_axis = 0;
|
||||
if (value[0] == '-' || value[0] == '+') {
|
||||
half_axis = value[0];
|
||||
value++;
|
||||
}
|
||||
if (strcmp("platform", key) == 0)
|
||||
strncpy(map->platform, value, sizeof(map->platform));
|
||||
strncpy(map->platform, value, sizeof(map->platform) - 1);
|
||||
else if (sscanf(value, "b%d", &int_value) == 1) {
|
||||
if (strcmp("a", key) == 0)
|
||||
map->btn_a = int_value;
|
||||
@ -85,6 +94,18 @@ struct mapping* mapping_parse(char* mapping) {
|
||||
map->btn_lefttrigger = int_value;
|
||||
else if (strcmp("righttrigger", key) == 0)
|
||||
map->btn_righttrigger = int_value;
|
||||
else if (strcmp("misc1", key) == 0)
|
||||
map->btn_misc1 = int_value;
|
||||
else if (strcmp("paddle1", key) == 0)
|
||||
map->btn_paddle1 = int_value;
|
||||
else if (strcmp("paddle2", key) == 0)
|
||||
map->btn_paddle2 = int_value;
|
||||
else if (strcmp("paddle3", key) == 0)
|
||||
map->btn_paddle3 = int_value;
|
||||
else if (strcmp("paddle4", key) == 0)
|
||||
map->btn_paddle4 = int_value;
|
||||
else if (strcmp("touchpad", key) == 0)
|
||||
map->btn_touchpad = int_value;
|
||||
} else if (sscanf(value, "a%d%c", &int_value, &flag) >= 1) {
|
||||
if (strcmp("leftx", key) == 0) {
|
||||
map->abs_leftx = int_value;
|
||||
@ -98,10 +119,25 @@ struct mapping* mapping_parse(char* mapping) {
|
||||
} else if (strcmp("righty", key) == 0) {
|
||||
map->abs_righty = int_value;
|
||||
map->reverse_righty = flag == '~';
|
||||
} else if (strcmp("lefttrigger", key) == 0)
|
||||
} else if (strcmp("lefttrigger", key) == 0) {
|
||||
map->abs_lefttrigger = int_value;
|
||||
else if (strcmp("righttrigger", key) == 0)
|
||||
map->halfaxis_lefttrigger = half_axis;
|
||||
} else if (strcmp("righttrigger", key) == 0) {
|
||||
map->abs_righttrigger = int_value;
|
||||
map->halfaxis_righttrigger = half_axis;
|
||||
} else if (strcmp("dpright", key) == 0) {
|
||||
map->abs_dpright = int_value;
|
||||
map->halfaxis_dpright = half_axis;
|
||||
} else if (strcmp("dpleft", key) == 0) {
|
||||
map->abs_dpleft = int_value;
|
||||
map->halfaxis_dpleft = half_axis;
|
||||
} else if (strcmp("dpup", key) == 0) {
|
||||
map->abs_dpup = int_value;
|
||||
map->halfaxis_dpup = half_axis;
|
||||
} else if (strcmp("dpdown", key) == 0) {
|
||||
map->abs_dpdown = int_value;
|
||||
map->halfaxis_dpdown = half_axis;
|
||||
}
|
||||
} else if (sscanf(value, "h%d.%d", &int_value, &direction_value) == 2) {
|
||||
if (strcmp("dpright", key) == 0) {
|
||||
map->hat_dpright = int_value;
|
||||
@ -116,6 +152,8 @@ struct mapping* mapping_parse(char* mapping) {
|
||||
map->hat_dpdown = int_value;
|
||||
map->hat_dir_dpdown = direction_value;
|
||||
}
|
||||
} else if (strcmp("crc", key) == 0) {
|
||||
/* CRC is not supported */
|
||||
} else
|
||||
fprintf(stderr, "Can't map (%s)\n", option);
|
||||
} else if (ret == 0 && option[0] != '\n')
|
||||
@ -124,8 +162,8 @@ struct mapping* mapping_parse(char* mapping) {
|
||||
if (key != NULL)
|
||||
free(key);
|
||||
|
||||
if (value != NULL)
|
||||
free(value);
|
||||
if (orig_value != NULL)
|
||||
free(orig_value);
|
||||
}
|
||||
map->guid[32] = '\0';
|
||||
map->name[256] = '\0';
|
||||
@ -189,5 +227,11 @@ void mapping_print(struct mapping* map) {
|
||||
print_abs("righttrigger", map->abs_righttrigger);
|
||||
print_btn("lefttrigger", map->btn_lefttrigger);
|
||||
print_btn("righttrigger", map->btn_righttrigger);
|
||||
print_btn("misc1", map->btn_misc1);
|
||||
print_btn("paddle1", map->btn_paddle1);
|
||||
print_btn("paddle2", map->btn_paddle2);
|
||||
print_btn("paddle3", map->btn_paddle3);
|
||||
print_btn("paddle4", map->btn_paddle4);
|
||||
print_btn("touchpad", map->btn_touchpad);
|
||||
printf("platform:Linux\n");
|
||||
}
|
||||
|
@ -28,22 +28,31 @@ struct mapping {
|
||||
|
||||
bool reverse_leftx, reverse_lefty;
|
||||
bool reverse_rightx, reverse_righty;
|
||||
|
||||
char halfaxis_lefttrigger, halfaxis_righttrigger;
|
||||
char halfaxis_dpright, halfaxis_dpleft;
|
||||
char halfaxis_dpup, halfaxis_dpdown;
|
||||
|
||||
/* abs_leftx must be the first member of the list of mapping indices! */
|
||||
short abs_leftx, abs_lefty;
|
||||
short abs_rightx, abs_righty;
|
||||
|
||||
short hat_dpright, hat_dpleft, hat_dpup, hat_dpdown;
|
||||
short hat_dir_dpright, hat_dir_dpleft, hat_dir_dpup, hat_dir_dpdown;
|
||||
short btn_dpup, btn_dpdown, btn_dpleft, btn_dpright;
|
||||
|
||||
short abs_dpright, abs_dpleft, abs_dpup, abs_dpdown;
|
||||
|
||||
short btn_a, btn_x, btn_y, btn_b;
|
||||
short btn_back, btn_start, btn_guide;
|
||||
short btn_leftstick, btn_rightstick;
|
||||
short btn_leftshoulder, btn_rightshoulder;
|
||||
|
||||
short abs_lefttrigger, abs_righttrigger;
|
||||
short btn_misc1;
|
||||
short btn_paddle1, btn_paddle2, btn_paddle3, btn_paddle4;
|
||||
short btn_touchpad;
|
||||
|
||||
short abs_lefttrigger, abs_righttrigger;
|
||||
short btn_lefttrigger, btn_righttrigger;
|
||||
|
||||
/* next must be the last member after the list of mapping indices! */
|
||||
struct mapping* next;
|
||||
};
|
||||
|
||||
|
673
src/input/sdl.c
673
src/input/sdl.c
@ -26,60 +26,406 @@
|
||||
#define QUIT_KEY SDLK_q
|
||||
#define QUIT_BUTTONS (PLAY_FLAG|BACK_FLAG|LB_FLAG|RB_FLAG)
|
||||
#define FULLSCREEN_KEY SDLK_f
|
||||
#define UNGRAB_KEY SDLK_z
|
||||
|
||||
static const int SDL_TO_LI_BUTTON_MAP[] = {
|
||||
A_FLAG, B_FLAG, X_FLAG, Y_FLAG,
|
||||
BACK_FLAG, SPECIAL_FLAG, PLAY_FLAG,
|
||||
LS_CLK_FLAG, RS_CLK_FLAG,
|
||||
LB_FLAG, RB_FLAG,
|
||||
UP_FLAG, DOWN_FLAG, LEFT_FLAG, RIGHT_FLAG,
|
||||
MISC_FLAG,
|
||||
PADDLE1_FLAG, PADDLE2_FLAG, PADDLE3_FLAG, PADDLE4_FLAG,
|
||||
TOUCHPAD_FLAG,
|
||||
};
|
||||
|
||||
typedef struct _GAMEPAD_STATE {
|
||||
char leftTrigger, rightTrigger;
|
||||
unsigned char leftTrigger, rightTrigger;
|
||||
short leftStickX, leftStickY;
|
||||
short rightStickX, rightStickY;
|
||||
int buttons;
|
||||
SDL_JoystickID sdl_id;
|
||||
SDL_GameController* controller;
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
SDL_Haptic* haptic;
|
||||
int haptic_effect_id;
|
||||
#endif
|
||||
short id;
|
||||
bool initialized;
|
||||
} GAMEPAD_STATE, *PGAMEPAD_STATE;
|
||||
|
||||
static GAMEPAD_STATE gamepads[4];
|
||||
// Limited by number of bits in activeGamepadMask
|
||||
#define MAX_GAMEPADS 16
|
||||
|
||||
static GAMEPAD_STATE gamepads[MAX_GAMEPADS];
|
||||
|
||||
static int keyboard_modifiers;
|
||||
static int activeGamepadMask = 0;
|
||||
|
||||
int sdl_gamepads = 0;
|
||||
|
||||
static PGAMEPAD_STATE get_gamepad(SDL_JoystickID sdl_id) {
|
||||
for (int i = 0;i<4;i++) {
|
||||
#define VK_0 0x30
|
||||
#define VK_A 0x41
|
||||
|
||||
// These are real Windows VK_* codes
|
||||
#ifndef VK_F1
|
||||
#define VK_F1 0x70
|
||||
#define VK_F13 0x7C
|
||||
#define VK_NUMPAD0 0x60
|
||||
#endif
|
||||
|
||||
int vk_for_sdl_scancode(SDL_Scancode scancode) {
|
||||
// Set keycode. We explicitly use scancode here because GFE will try to correct
|
||||
// for AZERTY layouts on the host but it depends on receiving VK_ values matching
|
||||
// a QWERTY layout to work.
|
||||
if (scancode >= SDL_SCANCODE_1 && scancode <= SDL_SCANCODE_9) {
|
||||
// SDL defines SDL_SCANCODE_0 > SDL_SCANCODE_9, so we need to handle that manually
|
||||
return (scancode - SDL_SCANCODE_1) + VK_0 + 1;
|
||||
}
|
||||
else if (scancode >= SDL_SCANCODE_A && scancode <= SDL_SCANCODE_Z) {
|
||||
return (scancode - SDL_SCANCODE_A) + VK_A;
|
||||
}
|
||||
else if (scancode >= SDL_SCANCODE_F1 && scancode <= SDL_SCANCODE_F12) {
|
||||
return (scancode - SDL_SCANCODE_F1) + VK_F1;
|
||||
}
|
||||
else if (scancode >= SDL_SCANCODE_F13 && scancode <= SDL_SCANCODE_F24) {
|
||||
return (scancode - SDL_SCANCODE_F13) + VK_F13;
|
||||
}
|
||||
else if (scancode >= SDL_SCANCODE_KP_1 && scancode <= SDL_SCANCODE_KP_9) {
|
||||
// SDL defines SDL_SCANCODE_KP_0 > SDL_SCANCODE_KP_9, so we need to handle that manually
|
||||
return (scancode - SDL_SCANCODE_KP_1) + VK_NUMPAD0 + 1;
|
||||
}
|
||||
else {
|
||||
switch (scancode) {
|
||||
case SDL_SCANCODE_BACKSPACE:
|
||||
return 0x08;
|
||||
|
||||
case SDL_SCANCODE_TAB:
|
||||
return 0x09;
|
||||
|
||||
case SDL_SCANCODE_CLEAR:
|
||||
return 0x0C;
|
||||
|
||||
case SDL_SCANCODE_KP_ENTER:
|
||||
case SDL_SCANCODE_RETURN:
|
||||
return 0x0D;
|
||||
|
||||
case SDL_SCANCODE_PAUSE:
|
||||
return 0x13;
|
||||
|
||||
case SDL_SCANCODE_CAPSLOCK:
|
||||
return 0x14;
|
||||
|
||||
case SDL_SCANCODE_ESCAPE:
|
||||
return 0x1B;
|
||||
|
||||
case SDL_SCANCODE_SPACE:
|
||||
return 0x20;
|
||||
|
||||
case SDL_SCANCODE_PAGEUP:
|
||||
return 0x21;
|
||||
|
||||
case SDL_SCANCODE_PAGEDOWN:
|
||||
return 0x22;
|
||||
|
||||
case SDL_SCANCODE_END:
|
||||
return 0x23;
|
||||
|
||||
case SDL_SCANCODE_HOME:
|
||||
return 0x24;
|
||||
|
||||
case SDL_SCANCODE_LEFT:
|
||||
return 0x25;
|
||||
|
||||
case SDL_SCANCODE_UP:
|
||||
return 0x26;
|
||||
|
||||
case SDL_SCANCODE_RIGHT:
|
||||
return 0x27;
|
||||
|
||||
case SDL_SCANCODE_DOWN:
|
||||
return 0x28;
|
||||
|
||||
case SDL_SCANCODE_SELECT:
|
||||
return 0x29;
|
||||
|
||||
case SDL_SCANCODE_EXECUTE:
|
||||
return 0x2B;
|
||||
|
||||
case SDL_SCANCODE_PRINTSCREEN:
|
||||
return 0x2C;
|
||||
|
||||
case SDL_SCANCODE_INSERT:
|
||||
return 0x2D;
|
||||
|
||||
case SDL_SCANCODE_DELETE:
|
||||
return 0x2E;
|
||||
|
||||
case SDL_SCANCODE_HELP:
|
||||
return 0x2F;
|
||||
|
||||
case SDL_SCANCODE_KP_0:
|
||||
// See comment above about why we only handle SDL_SCANCODE_KP_0 here
|
||||
return VK_NUMPAD0;
|
||||
|
||||
case SDL_SCANCODE_0:
|
||||
// See comment above about why we only handle SDL_SCANCODE_0 here
|
||||
return VK_0;
|
||||
|
||||
case SDL_SCANCODE_KP_MULTIPLY:
|
||||
return 0x6A;
|
||||
|
||||
case SDL_SCANCODE_KP_PLUS:
|
||||
return 0x6B;
|
||||
|
||||
case SDL_SCANCODE_KP_COMMA:
|
||||
return 0x6C;
|
||||
|
||||
case SDL_SCANCODE_KP_MINUS:
|
||||
return 0x6D;
|
||||
|
||||
case SDL_SCANCODE_KP_PERIOD:
|
||||
return 0x6E;
|
||||
|
||||
case SDL_SCANCODE_KP_DIVIDE:
|
||||
return 0x6F;
|
||||
|
||||
case SDL_SCANCODE_NUMLOCKCLEAR:
|
||||
return 0x90;
|
||||
|
||||
case SDL_SCANCODE_SCROLLLOCK:
|
||||
return 0x91;
|
||||
|
||||
case SDL_SCANCODE_LSHIFT:
|
||||
return 0xA0;
|
||||
|
||||
case SDL_SCANCODE_RSHIFT:
|
||||
return 0xA1;
|
||||
|
||||
case SDL_SCANCODE_LCTRL:
|
||||
return 0xA2;
|
||||
|
||||
case SDL_SCANCODE_RCTRL:
|
||||
return 0xA3;
|
||||
|
||||
case SDL_SCANCODE_LALT:
|
||||
return 0xA4;
|
||||
|
||||
case SDL_SCANCODE_RALT:
|
||||
return 0xA5;
|
||||
|
||||
case SDL_SCANCODE_LGUI:
|
||||
return 0x5B;
|
||||
|
||||
case SDL_SCANCODE_RGUI:
|
||||
return 0x5C;
|
||||
|
||||
case SDL_SCANCODE_APPLICATION:
|
||||
return 0x5D;
|
||||
|
||||
case SDL_SCANCODE_AC_BACK:
|
||||
return 0xA6;
|
||||
|
||||
case SDL_SCANCODE_AC_FORWARD:
|
||||
return 0xA7;
|
||||
|
||||
case SDL_SCANCODE_AC_REFRESH:
|
||||
return 0xA8;
|
||||
|
||||
case SDL_SCANCODE_AC_STOP:
|
||||
return 0xA9;
|
||||
|
||||
case SDL_SCANCODE_AC_SEARCH:
|
||||
return 0xAA;
|
||||
|
||||
case SDL_SCANCODE_AC_BOOKMARKS:
|
||||
return 0xAB;
|
||||
|
||||
case SDL_SCANCODE_AC_HOME:
|
||||
return 0xAC;
|
||||
|
||||
case SDL_SCANCODE_SEMICOLON:
|
||||
return 0xBA;
|
||||
|
||||
case SDL_SCANCODE_EQUALS:
|
||||
return 0xBB;
|
||||
|
||||
case SDL_SCANCODE_COMMA:
|
||||
return 0xBC;
|
||||
|
||||
case SDL_SCANCODE_MINUS:
|
||||
return 0xBD;
|
||||
|
||||
case SDL_SCANCODE_PERIOD:
|
||||
return 0xBE;
|
||||
|
||||
case SDL_SCANCODE_SLASH:
|
||||
return 0xBF;
|
||||
|
||||
case SDL_SCANCODE_GRAVE:
|
||||
return 0xC0;
|
||||
|
||||
case SDL_SCANCODE_LEFTBRACKET:
|
||||
return 0xDB;
|
||||
|
||||
case SDL_SCANCODE_BACKSLASH:
|
||||
return 0xDC;
|
||||
|
||||
case SDL_SCANCODE_RIGHTBRACKET:
|
||||
return 0xDD;
|
||||
|
||||
case SDL_SCANCODE_APOSTROPHE:
|
||||
return 0xDE;
|
||||
|
||||
case SDL_SCANCODE_NONUSBACKSLASH:
|
||||
return 0xE2;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void send_controller_arrival(PGAMEPAD_STATE state) {
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 18)
|
||||
unsigned int supportedButtonFlags = 0;
|
||||
unsigned short capabilities = 0;
|
||||
unsigned char type = LI_CTYPE_UNKNOWN;
|
||||
|
||||
for (int i = 0; i < SDL_arraysize(SDL_TO_LI_BUTTON_MAP); i++) {
|
||||
if (SDL_GameControllerHasButton(state->controller, (SDL_GameControllerButton)i)) {
|
||||
supportedButtonFlags |= SDL_TO_LI_BUTTON_MAP[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (SDL_GameControllerGetBindForAxis(state->controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT).bindType == SDL_CONTROLLER_BINDTYPE_AXIS ||
|
||||
SDL_GameControllerGetBindForAxis(state->controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT).bindType == SDL_CONTROLLER_BINDTYPE_AXIS)
|
||||
capabilities |= LI_CCAP_ANALOG_TRIGGERS;
|
||||
if (SDL_GameControllerHasRumble(state->controller))
|
||||
capabilities |= LI_CCAP_RUMBLE;
|
||||
if (SDL_GameControllerHasRumbleTriggers(state->controller))
|
||||
capabilities |= LI_CCAP_TRIGGER_RUMBLE;
|
||||
if (SDL_GameControllerGetNumTouchpads(state->controller) > 0)
|
||||
capabilities |= LI_CCAP_TOUCHPAD;
|
||||
if (SDL_GameControllerHasSensor(state->controller, SDL_SENSOR_ACCEL))
|
||||
capabilities |= LI_CCAP_ACCEL;
|
||||
if (SDL_GameControllerHasSensor(state->controller, SDL_SENSOR_GYRO))
|
||||
capabilities |= LI_CCAP_GYRO;
|
||||
if (SDL_GameControllerHasLED(state->controller))
|
||||
capabilities |= LI_CCAP_RGB_LED;
|
||||
|
||||
switch (SDL_GameControllerGetType(state->controller)) {
|
||||
case SDL_CONTROLLER_TYPE_XBOX360:
|
||||
case SDL_CONTROLLER_TYPE_XBOXONE:
|
||||
type = LI_CTYPE_XBOX;
|
||||
break;
|
||||
case SDL_CONTROLLER_TYPE_PS3:
|
||||
case SDL_CONTROLLER_TYPE_PS4:
|
||||
case SDL_CONTROLLER_TYPE_PS5:
|
||||
type = LI_CTYPE_PS;
|
||||
break;
|
||||
case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO:
|
||||
#if SDL_VERSION_ATLEAST(2, 24, 0)
|
||||
case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT:
|
||||
case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT:
|
||||
case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR:
|
||||
#endif
|
||||
type = LI_CTYPE_NINTENDO;
|
||||
break;
|
||||
}
|
||||
|
||||
LiSendControllerArrivalEvent(state->id, activeGamepadMask, type, supportedButtonFlags, capabilities);
|
||||
#endif
|
||||
}
|
||||
|
||||
static PGAMEPAD_STATE get_gamepad(SDL_JoystickID sdl_id, bool add) {
|
||||
// See if a gamepad already exists
|
||||
for (int i = 0;i<MAX_GAMEPADS;i++) {
|
||||
if (gamepads[i].initialized && gamepads[i].sdl_id == sdl_id)
|
||||
return &gamepads[i];
|
||||
}
|
||||
|
||||
if (!add)
|
||||
return NULL;
|
||||
|
||||
for (int i = 0;i<MAX_GAMEPADS;i++) {
|
||||
if (!gamepads[i].initialized) {
|
||||
gamepads[i].sdl_id = sdl_id;
|
||||
gamepads[i].id = i;
|
||||
gamepads[i].initialized = true;
|
||||
|
||||
activeGamepadMask |= (1 << i);
|
||||
|
||||
return &gamepads[i];
|
||||
} else if (gamepads[i].sdl_id == sdl_id)
|
||||
return &gamepads[i];
|
||||
}
|
||||
}
|
||||
|
||||
return &gamepads[0];
|
||||
}
|
||||
|
||||
static void init_gamepad(int joystick_index) {
|
||||
if (SDL_IsGameController(joystick_index)) {
|
||||
sdl_gamepads++;
|
||||
SDL_GameController* controller = SDL_GameControllerOpen(joystick_index);
|
||||
if (!controller) {
|
||||
fprintf(stderr, "Could not open gamecontroller %i: %s\n", joystick_index, SDL_GetError());
|
||||
return;
|
||||
}
|
||||
static void add_gamepad(int joystick_index) {
|
||||
SDL_GameController* controller = SDL_GameControllerOpen(joystick_index);
|
||||
if (!controller) {
|
||||
fprintf(stderr, "Could not open gamecontroller %i: %s\n", joystick_index, SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Joystick* joystick = SDL_GameControllerGetJoystick(controller);
|
||||
SDL_Haptic* haptic = SDL_HapticOpenFromJoystick(joystick);
|
||||
if (haptic && (SDL_HapticQuery(haptic) & SDL_HAPTIC_LEFTRIGHT) == 0) {
|
||||
SDL_HapticClose(haptic);
|
||||
haptic = NULL;
|
||||
}
|
||||
SDL_Joystick* joystick = SDL_GameControllerGetJoystick(controller);
|
||||
SDL_JoystickID joystick_id = SDL_JoystickInstanceID(joystick);
|
||||
|
||||
SDL_JoystickID sdl_id = SDL_JoystickInstanceID(joystick);
|
||||
PGAMEPAD_STATE state = get_gamepad(joystick_index);
|
||||
state->haptic = haptic;
|
||||
state->haptic_effect_id = -1;
|
||||
// Check if we have already set up a state for this gamepad
|
||||
PGAMEPAD_STATE state = get_gamepad(joystick_id, false);
|
||||
if (state) {
|
||||
// This was probably a gamepad added during initialization, so we've already
|
||||
// got state set up. However, we still need to inform the host about it, since
|
||||
// we couldn't do that during initialization (since we weren't connected yet).
|
||||
send_controller_arrival(state);
|
||||
|
||||
SDL_GameControllerClose(controller);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new gamepad state
|
||||
state = get_gamepad(joystick_id, true);
|
||||
state->controller = controller;
|
||||
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
state->haptic = SDL_HapticOpenFromJoystick(joystick);
|
||||
if (haptic && (SDL_HapticQuery(state->haptic) & SDL_HAPTIC_LEFTRIGHT) == 0) {
|
||||
SDL_HapticClose(state->haptic);
|
||||
state->haptic = NULL;
|
||||
}
|
||||
state->haptic_effect_id = -1;
|
||||
#endif
|
||||
|
||||
// Send the controller arrival event to the host
|
||||
send_controller_arrival(state);
|
||||
|
||||
sdl_gamepads++;
|
||||
}
|
||||
|
||||
static void remove_gamepad(SDL_JoystickID sdl_id) {
|
||||
for (int i = 0;i<MAX_GAMEPADS;i++) {
|
||||
if (gamepads[i].initialized && gamepads[i].sdl_id == sdl_id) {
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
if (gamepads[i].haptic_effect_id >= 0) {
|
||||
SDL_HapticDestroyEffect(gamepads[i].haptic, gamepads[i].haptic_effect_id);
|
||||
}
|
||||
|
||||
if (gamepads[i].haptic) {
|
||||
SDL_HapticClose(gamepads[i].haptic);
|
||||
}
|
||||
#endif
|
||||
|
||||
SDL_GameControllerClose(gamepads[i].controller);
|
||||
|
||||
// This will cause disconnection of the virtual controller on the host PC
|
||||
activeGamepadMask &= ~(1 << i);
|
||||
LiSendMultiControllerEvent(i, activeGamepadMask, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
memset(&gamepads[i], 0, sizeof(*gamepads));
|
||||
sdl_gamepads--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,22 +433,41 @@ void sdlinput_init(char* mappings) {
|
||||
memset(gamepads, 0, sizeof(gamepads));
|
||||
|
||||
SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER);
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
SDL_InitSubSystem(SDL_INIT_HAPTIC);
|
||||
#endif
|
||||
SDL_GameControllerAddMappingsFromFile(mappings);
|
||||
|
||||
for (int i = 0; i < SDL_NumJoysticks(); ++i)
|
||||
init_gamepad(i);
|
||||
// Add game controllers here to ensure an accurate count
|
||||
// goes to the host when starting a new session.
|
||||
for (int i = 0; i < SDL_NumJoysticks(); ++i) {
|
||||
if (SDL_IsGameController(i))
|
||||
add_gamepad(i);
|
||||
}
|
||||
}
|
||||
|
||||
int sdlinput_handle_event(SDL_Event* event) {
|
||||
int sdlinput_handle_event(SDL_Window* window, SDL_Event* event) {
|
||||
int button = 0;
|
||||
unsigned char touchEventType;
|
||||
PGAMEPAD_STATE gamepad;
|
||||
switch (event->type) {
|
||||
case SDL_MOUSEMOTION:
|
||||
LiSendMouseMoveEvent(event->motion.xrel, event->motion.yrel);
|
||||
if (SDL_GetRelativeMouseMode())
|
||||
LiSendMouseMoveEvent(event->motion.xrel, event->motion.yrel);
|
||||
else {
|
||||
int w, h;
|
||||
SDL_GetWindowSize(window, &w, &h);
|
||||
LiSendMousePositionEvent(event->motion.x, event->motion.y, w, h);
|
||||
}
|
||||
break;
|
||||
case SDL_MOUSEWHEEL:
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 18)
|
||||
LiSendHighResHScrollEvent((short)(event->wheel.preciseX * 120)); // WHEEL_DELTA
|
||||
LiSendHighResScrollEvent((short)(event->wheel.preciseY * 120)); // WHEEL_DELTA
|
||||
#else
|
||||
LiSendHScrollEvent(event->wheel.x);
|
||||
LiSendScrollEvent(event->wheel.y);
|
||||
#endif
|
||||
break;
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
@ -127,78 +492,81 @@ int sdlinput_handle_event(SDL_Event* event) {
|
||||
if (button != 0)
|
||||
LiSendMouseButtonEvent(event->type==SDL_MOUSEBUTTONDOWN?BUTTON_ACTION_PRESS:BUTTON_ACTION_RELEASE, button);
|
||||
|
||||
return SDL_MOUSE_GRAB;
|
||||
return 0;
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP:
|
||||
button = event->key.keysym.sym;
|
||||
if (button >= 0x21 && button <= 0x2f)
|
||||
button = keyCodes1[button - 0x21];
|
||||
else if (button >= 0x3a && button <= 0x40)
|
||||
button = keyCodes2[button - 0x3a];
|
||||
else if (button >= 0x5b && button <= 0x60)
|
||||
button = keyCodes3[button - 0x5b];
|
||||
else if (button >= 0x40000039 && button < 0x40000039 + sizeof(keyCodes4))
|
||||
button = keyCodes4[button - 0x40000039];
|
||||
else if (button >= 0x400000E0 && button <= 0x400000E7)
|
||||
button = keyCodes5[button - 0x400000E0];
|
||||
else if (button >= 0x61 && button <= 0x7a)
|
||||
button -= 0x20;
|
||||
else if (button == 0x7f)
|
||||
button = 0x2e;
|
||||
button = vk_for_sdl_scancode(event->key.keysym.scancode);
|
||||
|
||||
int modifier = 0;
|
||||
switch (event->key.keysym.sym) {
|
||||
case SDLK_RSHIFT:
|
||||
case SDLK_LSHIFT:
|
||||
modifier = MODIFIER_SHIFT;
|
||||
break;
|
||||
case SDLK_RALT:
|
||||
case SDLK_LALT:
|
||||
modifier = MODIFIER_ALT;
|
||||
break;
|
||||
case SDLK_RCTRL:
|
||||
case SDLK_LCTRL:
|
||||
modifier = MODIFIER_CTRL;
|
||||
break;
|
||||
int modifiers = 0;
|
||||
if (event->key.keysym.mod & KMOD_CTRL) {
|
||||
modifiers |= MODIFIER_CTRL;
|
||||
}
|
||||
if (event->key.keysym.mod & KMOD_ALT) {
|
||||
modifiers |= MODIFIER_ALT;
|
||||
}
|
||||
if (event->key.keysym.mod & KMOD_SHIFT) {
|
||||
modifiers |= MODIFIER_SHIFT;
|
||||
}
|
||||
if (event->key.keysym.mod & KMOD_GUI) {
|
||||
modifiers |= MODIFIER_META;
|
||||
}
|
||||
|
||||
if (modifier != 0) {
|
||||
if (event->type==SDL_KEYDOWN)
|
||||
keyboard_modifiers |= modifier;
|
||||
else
|
||||
keyboard_modifiers &= ~modifier;
|
||||
}
|
||||
LiSendKeyboardEvent(0x80 << 8 | button, event->type==SDL_KEYDOWN?KEY_ACTION_DOWN:KEY_ACTION_UP, modifiers);
|
||||
|
||||
// Quit the stream if all the required quit keys are down
|
||||
if ((keyboard_modifiers & ACTION_MODIFIERS) == ACTION_MODIFIERS && event->key.keysym.sym == QUIT_KEY && event->type==SDL_KEYUP)
|
||||
if ((modifiers & ACTION_MODIFIERS) == ACTION_MODIFIERS && event->key.keysym.sym == QUIT_KEY && event->type==SDL_KEYUP)
|
||||
return SDL_QUIT_APPLICATION;
|
||||
else if ((keyboard_modifiers & ACTION_MODIFIERS) == ACTION_MODIFIERS && event->key.keysym.sym == FULLSCREEN_KEY && event->type==SDL_KEYUP)
|
||||
else if ((modifiers & ACTION_MODIFIERS) == ACTION_MODIFIERS && event->key.keysym.sym == FULLSCREEN_KEY && event->type==SDL_KEYUP)
|
||||
return SDL_TOGGLE_FULLSCREEN;
|
||||
else if ((keyboard_modifiers & ACTION_MODIFIERS) == ACTION_MODIFIERS)
|
||||
return SDL_MOUSE_UNGRAB;
|
||||
else if ((modifiers & ACTION_MODIFIERS) == ACTION_MODIFIERS && event->key.keysym.sym == UNGRAB_KEY && event->type==SDL_KEYUP)
|
||||
return SDL_GetRelativeMouseMode() ? SDL_MOUSE_UNGRAB : SDL_MOUSE_GRAB;
|
||||
break;
|
||||
case SDL_FINGERDOWN:
|
||||
case SDL_FINGERMOTION:
|
||||
case SDL_FINGERUP:
|
||||
switch (event->type) {
|
||||
case SDL_FINGERDOWN:
|
||||
touchEventType = LI_TOUCH_EVENT_DOWN;
|
||||
break;
|
||||
case SDL_FINGERMOTION:
|
||||
touchEventType = LI_TOUCH_EVENT_MOVE;
|
||||
break;
|
||||
case SDL_FINGERUP:
|
||||
touchEventType = LI_TOUCH_EVENT_UP;
|
||||
break;
|
||||
default:
|
||||
return SDL_NOTHING;
|
||||
}
|
||||
|
||||
LiSendKeyboardEvent(0x80 << 8 | button, event->type==SDL_KEYDOWN?KEY_ACTION_DOWN:KEY_ACTION_UP, keyboard_modifiers);
|
||||
// These are already window-relative normalized coordinates, so we just need to clamp them
|
||||
event->tfinger.x = SDL_max(SDL_min(1.0f, event->tfinger.x), 0.0f);
|
||||
event->tfinger.y = SDL_max(SDL_min(1.0f, event->tfinger.y), 0.0f);
|
||||
|
||||
LiSendTouchEvent(touchEventType, event->tfinger.fingerId, event->tfinger.x, event->tfinger.y,
|
||||
event->tfinger.pressure, 0.0f, 0.0f, LI_ROT_UNKNOWN);
|
||||
break;
|
||||
case SDL_CONTROLLERAXISMOTION:
|
||||
gamepad = get_gamepad(event->caxis.which);
|
||||
gamepad = get_gamepad(event->caxis.which, false);
|
||||
if (!gamepad)
|
||||
return SDL_NOTHING;
|
||||
switch (event->caxis.axis) {
|
||||
case SDL_CONTROLLER_AXIS_LEFTX:
|
||||
gamepad->leftStickX = event->caxis.value;
|
||||
break;
|
||||
case SDL_CONTROLLER_AXIS_LEFTY:
|
||||
gamepad->leftStickY = -event->caxis.value - 1;
|
||||
gamepad->leftStickY = -SDL_max(event->caxis.value, (short)-32767);
|
||||
break;
|
||||
case SDL_CONTROLLER_AXIS_RIGHTX:
|
||||
gamepad->rightStickX = event->caxis.value;
|
||||
break;
|
||||
case SDL_CONTROLLER_AXIS_RIGHTY:
|
||||
gamepad->rightStickY = -event->caxis.value - 1;
|
||||
gamepad->rightStickY = -SDL_max(event->caxis.value, (short)-32767);
|
||||
break;
|
||||
case SDL_CONTROLLER_AXIS_TRIGGERLEFT:
|
||||
gamepad->leftTrigger = (event->caxis.value >> 8) + 127;
|
||||
gamepad->leftTrigger = (unsigned char)(event->caxis.value * 255UL / 32767);
|
||||
break;
|
||||
case SDL_CONTROLLER_AXIS_TRIGGERRIGHT:
|
||||
gamepad->rightTrigger = (event->caxis.value >> 8) + 127;
|
||||
gamepad->rightTrigger = (unsigned char)(event->caxis.value * 255UL / 32767);
|
||||
break;
|
||||
default:
|
||||
return SDL_NOTHING;
|
||||
@ -207,76 +575,86 @@ int sdlinput_handle_event(SDL_Event* event) {
|
||||
break;
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
gamepad = get_gamepad(event->cbutton.which);
|
||||
switch (event->cbutton.button) {
|
||||
case SDL_CONTROLLER_BUTTON_A:
|
||||
button = A_FLAG;
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_B:
|
||||
button = B_FLAG;
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_Y:
|
||||
button = Y_FLAG;
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_X:
|
||||
button = X_FLAG;
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_UP:
|
||||
button = UP_FLAG;
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
|
||||
button = DOWN_FLAG;
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
|
||||
button = RIGHT_FLAG;
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
|
||||
button = LEFT_FLAG;
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_BACK:
|
||||
button = BACK_FLAG;
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_START:
|
||||
button = PLAY_FLAG;
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_GUIDE:
|
||||
button = SPECIAL_FLAG;
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_LEFTSTICK:
|
||||
button = LS_CLK_FLAG;
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSTICK:
|
||||
button = RS_CLK_FLAG;
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER:
|
||||
button = LB_FLAG;
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER:
|
||||
button = RB_FLAG;
|
||||
break;
|
||||
default:
|
||||
gamepad = get_gamepad(event->cbutton.which, false);
|
||||
if (!gamepad)
|
||||
return SDL_NOTHING;
|
||||
}
|
||||
if (event->cbutton.button >= SDL_arraysize(SDL_TO_LI_BUTTON_MAP))
|
||||
return SDL_NOTHING;
|
||||
|
||||
if (event->type == SDL_CONTROLLERBUTTONDOWN)
|
||||
gamepad->buttons |= button;
|
||||
gamepad->buttons |= SDL_TO_LI_BUTTON_MAP[event->cbutton.button];
|
||||
else
|
||||
gamepad->buttons &= ~button;
|
||||
gamepad->buttons &= ~SDL_TO_LI_BUTTON_MAP[event->cbutton.button];
|
||||
|
||||
if ((gamepad->buttons & QUIT_BUTTONS) == QUIT_BUTTONS)
|
||||
return SDL_QUIT_APPLICATION;
|
||||
|
||||
LiSendMultiControllerEvent(gamepad->id, activeGamepadMask, gamepad->buttons, gamepad->leftTrigger, gamepad->rightTrigger, gamepad->leftStickX, gamepad->leftStickY, gamepad->rightStickX, gamepad->rightStickY);
|
||||
break;
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
add_gamepad(event->cdevice.which);
|
||||
break;
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
remove_gamepad(event->cdevice.which);
|
||||
break;
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 14)
|
||||
case SDL_CONTROLLERSENSORUPDATE:
|
||||
gamepad = get_gamepad(event->csensor.which, false);
|
||||
if (!gamepad)
|
||||
return SDL_NOTHING;
|
||||
switch (event->csensor.sensor) {
|
||||
case SDL_SENSOR_ACCEL:
|
||||
LiSendControllerMotionEvent(gamepad->id, LI_MOTION_TYPE_ACCEL, event->csensor.data[0], event->csensor.data[1], event->csensor.data[2]);
|
||||
break;
|
||||
case SDL_SENSOR_GYRO:
|
||||
// Convert rad/s to deg/s
|
||||
LiSendControllerMotionEvent(gamepad->id, LI_MOTION_TYPE_GYRO,
|
||||
event->csensor.data[0] * 57.2957795f,
|
||||
event->csensor.data[1] * 57.2957795f,
|
||||
event->csensor.data[2] * 57.2957795f);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SDL_CONTROLLERTOUCHPADDOWN:
|
||||
case SDL_CONTROLLERTOUCHPADUP:
|
||||
case SDL_CONTROLLERTOUCHPADMOTION:
|
||||
gamepad = get_gamepad(event->ctouchpad.which, false);
|
||||
if (!gamepad)
|
||||
return SDL_NOTHING;
|
||||
switch (event->type) {
|
||||
case SDL_CONTROLLERTOUCHPADDOWN:
|
||||
touchEventType = LI_TOUCH_EVENT_DOWN;
|
||||
break;
|
||||
case SDL_CONTROLLERTOUCHPADUP:
|
||||
touchEventType = LI_TOUCH_EVENT_UP;
|
||||
break;
|
||||
case SDL_CONTROLLERTOUCHPADMOTION:
|
||||
touchEventType = LI_TOUCH_EVENT_MOVE;
|
||||
break;
|
||||
default:
|
||||
return SDL_NOTHING;
|
||||
}
|
||||
LiSendControllerTouchEvent(gamepad->id, touchEventType, event->ctouchpad.finger,
|
||||
event->ctouchpad.x, event->ctouchpad.y, event->ctouchpad.pressure);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
return SDL_NOTHING;
|
||||
}
|
||||
|
||||
void sdlinput_rumble(unsigned short controller_id, unsigned short low_freq_motor, unsigned short high_freq_motor) {
|
||||
if (controller_id >= 4)
|
||||
if (controller_id >= MAX_GAMEPADS)
|
||||
return;
|
||||
|
||||
PGAMEPAD_STATE state = &gamepads[controller_id];
|
||||
|
||||
if (!state->initialized)
|
||||
return;
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
SDL_GameControllerRumble(state->controller, low_freq_motor, high_freq_motor, 30000);
|
||||
#else
|
||||
SDL_Haptic* haptic = state->haptic;
|
||||
if (!haptic)
|
||||
return;
|
||||
@ -299,4 +677,45 @@ void sdlinput_rumble(unsigned short controller_id, unsigned short low_freq_motor
|
||||
state->haptic_effect_id = SDL_HapticNewEffect(haptic, &effect);
|
||||
if (state->haptic_effect_id >= 0)
|
||||
SDL_HapticRunEffect(haptic, state->haptic_effect_id, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void sdlinput_rumble_triggers(unsigned short controller_id, unsigned short left_trigger, unsigned short right_trigger) {
|
||||
PGAMEPAD_STATE state = &gamepads[controller_id];
|
||||
|
||||
if (!state->initialized)
|
||||
return;
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 14)
|
||||
SDL_GameControllerRumbleTriggers(state->controller, left_trigger, right_trigger, 30000);
|
||||
#endif
|
||||
}
|
||||
|
||||
void sdlinput_set_motion_event_state(unsigned short controller_id, unsigned char motion_type, unsigned short report_rate_hz) {
|
||||
PGAMEPAD_STATE state = &gamepads[controller_id];
|
||||
|
||||
if (!state->initialized)
|
||||
return;
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 14)
|
||||
switch (motion_type) {
|
||||
case LI_MOTION_TYPE_ACCEL:
|
||||
SDL_GameControllerSetSensorEnabled(state->controller, SDL_SENSOR_ACCEL, report_rate_hz ? SDL_TRUE : SDL_FALSE);
|
||||
break;
|
||||
case LI_MOTION_TYPE_GYRO:
|
||||
SDL_GameControllerSetSensorEnabled(state->controller, SDL_SENSOR_GYRO, report_rate_hz ? SDL_TRUE : SDL_FALSE);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void sdlinput_set_controller_led(unsigned short controller_id, unsigned char r, unsigned char g, unsigned char b) {
|
||||
PGAMEPAD_STATE state = &gamepads[controller_id];
|
||||
|
||||
if (!state->initialized)
|
||||
return;
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 14)
|
||||
SDL_GameControllerSetLED(state->controller, r, g, b);
|
||||
#endif
|
||||
}
|
@ -22,83 +22,9 @@
|
||||
|
||||
extern int sdl_gamepads;
|
||||
|
||||
static const short keyCodes1[] = {
|
||||
0, //SDLK_EXCLAIM
|
||||
0, //SDLK_QUOTEDBL
|
||||
0, //SDLK_HASH
|
||||
0, //SDLK_DOLLAR
|
||||
0, //SDLK_PERCENT
|
||||
0, //SDLK_AMPERSAND
|
||||
0xDE, //SDLK_QUOTE
|
||||
0, //SDLK_LEFTPAREN
|
||||
0, //SDLK_RIGHTPAREN
|
||||
0, //SDLK_ASTERISK
|
||||
0, //SDLK_PLUS
|
||||
0xBC, //SDLK_COMMA
|
||||
0xBD, //SDLK_MINUS
|
||||
0xBE, //SDLK_PERIOD
|
||||
0xBF, //SDLK_SLASH
|
||||
};
|
||||
|
||||
static const short keyCodes2[] = {
|
||||
0, //SDLK_COLON
|
||||
0xBA, //SDLK_SEMICOLON
|
||||
0, //SDLK_LESS
|
||||
0xBB, //SDLK_EQUALS
|
||||
0, //SDLK_GREATER
|
||||
0, //SDLK_QUESTION
|
||||
0, //SDLK_AT
|
||||
};
|
||||
|
||||
static const short keyCodes3[] = {
|
||||
0xDB, //SDLK_LEFTBRACKET
|
||||
0xDC, //SDLK_BACKSLASH
|
||||
0xDD, //SDLK_RIGHTBRACKET
|
||||
0, //SDLK_CARET
|
||||
0, //SDLK_UNDERSCORE
|
||||
0xC0, //SDLK_BACKQUOTE
|
||||
};
|
||||
|
||||
static const short keyCodes4[] = {
|
||||
0x14, //SDLK_CAPSLOCK
|
||||
0x70, //SDLK_F1
|
||||
0x71, //SDLK_F2
|
||||
0x72, //SDLK_F3
|
||||
0x73, //SDLK_F4
|
||||
0x74, //SDLK_F5
|
||||
0x75, //SDLK_F6
|
||||
0x76, //SDLK_F7
|
||||
0x77, //SDLK_F8
|
||||
0x78, //SDLK_F9
|
||||
0x79, //SDLK_F10
|
||||
0x7A, //SDLK_F11
|
||||
0x7B, //SDLK_F12
|
||||
0, //SDLK_PRINTSCREEN
|
||||
0x91, //SDLK_SCROLLLOCK
|
||||
0x13, //SDLK_PAUSE
|
||||
0x9B, //SDLK_INSERT
|
||||
0x24, //SDLK_HOME
|
||||
0x21, //SDLK_PAGEUP
|
||||
0, //Not used
|
||||
0x23, //SDLK_END
|
||||
0x22, //SDLK_PAGEDOWN
|
||||
0x27, //SDLK_RIGHT
|
||||
0x25, //SDLK_LEFT
|
||||
0x28, //SDLK_DOWN
|
||||
0x26, //SDLK_UP
|
||||
};
|
||||
|
||||
static const short keyCodes5[] = {
|
||||
0x11, //SDLK_LCTRL
|
||||
0x10, //SDLK_LSHIFT
|
||||
0x12, //SDLK_LALT
|
||||
0x5B, //SDLK_LGUI
|
||||
0x11, //SDLK_LRCTRL
|
||||
0x10, //SDLK_RSHIFT
|
||||
0x12, //SDLK_RALT
|
||||
0x5C, //SDLK_RGUI
|
||||
};
|
||||
|
||||
void sdlinput_init(char* mappings);
|
||||
int sdlinput_handle_event(SDL_Event* event);
|
||||
int sdlinput_handle_event(SDL_Window* window, SDL_Event* event);
|
||||
void sdlinput_rumble(unsigned short controller_id, unsigned short low_freq_motor, unsigned short high_freq_motor);
|
||||
void sdlinput_rumble_triggers(unsigned short controller_id, unsigned short left_trigger, unsigned short right_trigger);
|
||||
void sdlinput_set_motion_event_state(unsigned short controller_id, unsigned char motion_type, unsigned short report_rate_hz);
|
||||
void sdlinput_set_controller_led(unsigned short controller_id, unsigned char r, unsigned char g, unsigned char b);
|
||||
|
@ -36,7 +36,7 @@ static struct mapping* defaultMappings;
|
||||
|
||||
static struct udev *udev;
|
||||
static struct udev_monitor *udev_mon;
|
||||
static int udev_fd;
|
||||
static int inputRotate;
|
||||
|
||||
static int udev_handle(int fd) {
|
||||
struct udev_device *dev = udev_monitor_receive_device(udev_mon);
|
||||
@ -46,7 +46,7 @@ static int udev_handle(int fd) {
|
||||
const char *devnode = udev_device_get_devnode(dev);
|
||||
int id;
|
||||
if (devnode != NULL && sscanf(devnode, "/dev/input/event%d", &id) == 1) {
|
||||
evdev_create(devnode, defaultMappings, debug);
|
||||
evdev_create(devnode, defaultMappings, debug, inputRotate);
|
||||
}
|
||||
}
|
||||
udev_device_unref(dev);
|
||||
@ -54,7 +54,7 @@ static int udev_handle(int fd) {
|
||||
return LOOP_OK;
|
||||
}
|
||||
|
||||
void udev_init(bool autoload, struct mapping* mappings, bool verbose) {
|
||||
void udev_init(bool autoload, struct mapping* mappings, bool verbose, int rotate) {
|
||||
udev = udev_new();
|
||||
debug = verbose;
|
||||
if (!udev) {
|
||||
@ -76,7 +76,7 @@ void udev_init(bool autoload, struct mapping* mappings, bool verbose) {
|
||||
const char *devnode = udev_device_get_devnode(dev);
|
||||
int id;
|
||||
if (devnode != NULL && sscanf(devnode, "/dev/input/event%d", &id) == 1) {
|
||||
evdev_create(devnode, mappings, verbose);
|
||||
evdev_create(devnode, mappings, verbose, rotate);
|
||||
}
|
||||
udev_device_unref(dev);
|
||||
}
|
||||
@ -89,11 +89,13 @@ void udev_init(bool autoload, struct mapping* mappings, bool verbose) {
|
||||
udev_monitor_enable_receiving(udev_mon);
|
||||
|
||||
defaultMappings = mappings;
|
||||
inputRotate = rotate;
|
||||
|
||||
int udev_fd = udev_monitor_get_fd(udev_mon);
|
||||
loop_add_fd(udev_fd, &udev_handle, POLLIN);
|
||||
loop_add_fd(udev_monitor_get_fd(udev_mon), &udev_handle, POLLIN);
|
||||
}
|
||||
|
||||
void evdev_destroy() {
|
||||
void udev_destroy() {
|
||||
loop_remove_fd(udev_monitor_get_fd(udev_mon));
|
||||
udev_monitor_unref(udev_mon);
|
||||
udev_unref(udev);
|
||||
}
|
||||
|
@ -19,5 +19,5 @@
|
||||
|
||||
#include "mapping.h"
|
||||
|
||||
void udev_init(bool autoload, struct mapping* mappings, bool verbose);
|
||||
void evdev_destroy();
|
||||
void udev_init(bool autoload, struct mapping* mappings, bool verbose, int rotate);
|
||||
void udev_destroy();
|
||||
|
@ -111,6 +111,12 @@ static int x11_handler(int fd) {
|
||||
case Button5:
|
||||
LiSendScrollEvent(-1);
|
||||
break;
|
||||
case 6:
|
||||
LiSendHScrollEvent(-1);
|
||||
break;
|
||||
case 7:
|
||||
LiSendHScrollEvent(1);
|
||||
break;
|
||||
case 8:
|
||||
button = BUTTON_X1;
|
||||
break;
|
||||
|
11
src/loop.c
11
src/loop.c
@ -38,7 +38,8 @@ static int sigFd;
|
||||
|
||||
static int loop_sig_handler(int fd) {
|
||||
struct signalfd_siginfo info;
|
||||
read(fd, &info, sizeof(info));
|
||||
if (read(fd, &info, sizeof(info)) != sizeof(info))
|
||||
return LOOP_RETURN;
|
||||
switch (info.ssi_signo) {
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
@ -73,15 +74,15 @@ void loop_add_fd(int fd, FdHandler handler, int events) {
|
||||
|
||||
void loop_remove_fd(int fd) {
|
||||
numFds--;
|
||||
int fdindex;
|
||||
|
||||
for (int i=0;i<numFds;i++) {
|
||||
int fdindex = numFds;
|
||||
|
||||
for (int i=0;i<=numFds;i++) {
|
||||
if (fds[i].fd == fd) {
|
||||
fdindex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (fdindex != numFds && numFds > 0) {
|
||||
memcpy(&fds[fdindex], &fds[numFds], sizeof(struct pollfd));
|
||||
memcpy(&fdHandlers[fdindex], &fdHandlers[numFds], sizeof(FdHandler));
|
||||
|
193
src/main.c
193
src/main.c
@ -20,8 +20,8 @@
|
||||
#include "loop.h"
|
||||
#include "connection.h"
|
||||
#include "configuration.h"
|
||||
#include "config.h"
|
||||
#include "platform.h"
|
||||
#include "config.h"
|
||||
#include "sdl.h"
|
||||
|
||||
#include "audio/audio.h"
|
||||
@ -96,7 +96,7 @@ static void stream(PSERVER_DATA server, PCONFIGURATION config, enum platform sys
|
||||
gamepads += sdl_gamepads;
|
||||
#endif
|
||||
int gamepad_mask = 0;
|
||||
for (int i = 0; i < gamepads && i < 4; i++)
|
||||
for (int i = 0; i < gamepads; i++)
|
||||
gamepad_mask = (gamepad_mask << 1) + 1;
|
||||
|
||||
int ret = gs_start_app(server, &config->stream, appId, config->sops, config->localaudio, gamepad_mask);
|
||||
@ -104,7 +104,9 @@ static void stream(PSERVER_DATA server, PCONFIGURATION config, enum platform sys
|
||||
if (ret == GS_NOT_SUPPORTED_4K)
|
||||
fprintf(stderr, "Server doesn't support 4K\n");
|
||||
else if (ret == GS_NOT_SUPPORTED_MODE)
|
||||
fprintf(stderr, "Server doesn't support %dx%d (%d fps) or try --unsupported option\n", config->stream.width, config->stream.height, config->stream.fps);
|
||||
fprintf(stderr, "Server doesn't support %dx%d (%d fps) or remove --nounsupported option\n", config->stream.width, config->stream.height, config->stream.fps);
|
||||
else if (ret == GS_NOT_SUPPORTED_SOPS_RESOLUTION)
|
||||
fprintf(stderr, "Optimal Playable Settings isn't supported for the resolution %dx%d, use supported resolution or add --nosops option\n", config->stream.width, config->stream.height);
|
||||
else if (ret == GS_ERROR)
|
||||
fprintf(stderr, "Gamestream error: %s\n", gs_error);
|
||||
else
|
||||
@ -116,6 +118,22 @@ static void stream(PSERVER_DATA server, PCONFIGURATION config, enum platform sys
|
||||
if (config->fullscreen)
|
||||
drFlags |= DISPLAY_FULLSCREEN;
|
||||
|
||||
switch (config->rotate) {
|
||||
case 0:
|
||||
break;
|
||||
case 90:
|
||||
drFlags |= DISPLAY_ROTATE_90;
|
||||
break;
|
||||
case 180:
|
||||
drFlags |= DISPLAY_ROTATE_180;
|
||||
break;
|
||||
case 270:
|
||||
drFlags |= DISPLAY_ROTATE_270;
|
||||
break;
|
||||
default:
|
||||
printf("Ignoring invalid rotation value: %d\n", config->rotate);
|
||||
}
|
||||
|
||||
if (config->debug_level > 0) {
|
||||
printf("Stream %d x %d, %d fps, %d kbps\n", config->stream.width, config->stream.height, config->stream.fps, config->stream.bitrate);
|
||||
connection_debug = true;
|
||||
@ -128,9 +146,11 @@ static void stream(PSERVER_DATA server, PCONFIGURATION config, enum platform sys
|
||||
LiStartConnection(&server->serverInfo, &config->stream, &connection_callbacks, platform_get_video(system), platform_get_audio(system, config->audio_device), NULL, drFlags, config->audio_device, 0);
|
||||
|
||||
if (IS_EMBEDDED(system)) {
|
||||
evdev_start();
|
||||
if (!config->viewonly)
|
||||
evdev_start();
|
||||
loop_main();
|
||||
evdev_stop();
|
||||
if (!config->viewonly)
|
||||
evdev_stop();
|
||||
}
|
||||
#ifdef HAVE_SDL
|
||||
else if (system == SDL)
|
||||
@ -149,7 +169,11 @@ static void stream(PSERVER_DATA server, PCONFIGURATION config, enum platform sys
|
||||
}
|
||||
|
||||
static void help() {
|
||||
#ifdef GIT_BRANCH
|
||||
printf("Moonlight Embedded %d.%d.%d-%s-%s\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, GIT_BRANCH, GIT_COMMIT_HASH);
|
||||
#else
|
||||
printf("Moonlight Embedded %d.%d.%d\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
|
||||
#endif
|
||||
printf("Usage: moonlight [action] (options) [host]\n");
|
||||
printf(" moonlight [configfile]\n");
|
||||
printf("\n Actions\n\n");
|
||||
@ -171,20 +195,26 @@ static void help() {
|
||||
printf("\t-4k\t\t\tUse 3840x2160 resolution\n");
|
||||
printf("\t-width <width>\t\tHorizontal resolution (default 1280)\n");
|
||||
printf("\t-height <height>\tVertical resolution (default 720)\n");
|
||||
printf("\t-fps <fps>\t\tSpecify the fps to use (default -1)\n");
|
||||
#ifdef HAVE_EMBEDDED
|
||||
printf("\t-rotate <angle>\tRotate display: 0/90/180/270 (default 0)\n");
|
||||
#endif
|
||||
printf("\t-fps <fps>\t\tSpecify the fps to use (default 60)\n");
|
||||
printf("\t-bitrate <bitrate>\tSpecify the bitrate in Kbps\n");
|
||||
printf("\t-packetsize <size>\tSpecify the maximum packetsize in bytes\n");
|
||||
printf("\t-codec <codec>\t\tSelect used codec: auto/h264/h265 (default auto)\n");
|
||||
printf("\t-remote\t\t\tEnable remote optimizations\n");
|
||||
printf("\t-codec <codec>\t\tSelect used codec: auto/h264/h265/av1 (default auto)\n");
|
||||
printf("\t-hdr\t\tEnable HDR streaming (experimental, requires host and device support)\n");
|
||||
printf("\t-remote <yes/no/auto>\t\t\tEnable optimizations for WAN streaming (default auto)\n");
|
||||
printf("\t-app <app>\t\tName of app to stream\n");
|
||||
printf("\t-nosops\t\t\tDon't allow GFE to modify game settings\n");
|
||||
printf("\t-localaudio\t\tPlay audio locally\n");
|
||||
printf("\t-surround\t\tStream 5.1 surround sound (requires GFE 2.7)\n");
|
||||
printf("\t-localaudio\t\tPlay audio locally on the host computer\n");
|
||||
printf("\t-surround <5.1/7.1>\t\tStream 5.1 or 7.1 surround sound\n");
|
||||
printf("\t-keydir <directory>\tLoad encryption keys from directory\n");
|
||||
printf("\t-mapping <file>\t\tUse <file> as gamepad mappings configuration file\n");
|
||||
printf("\t-platform <system>\tSpecify system used for audio, video and input: pi/imx/aml/rk/x11/x11_vdpau/sdl/fake (default auto)\n");
|
||||
printf("\t-unsupported\t\tTry streaming if GFE version or options are unsupported\n");
|
||||
printf("\t-nounsupported\t\tDon't stream if resolution is not officially supported by the server\n");
|
||||
printf("\t-quitappafter\t\tSend quit app request to remote after quitting session\n");
|
||||
printf("\t-viewonly\t\tDisable all input processing (view-only mode)\n");
|
||||
printf("\t-nomouseemulation\t\tDisable gamepad mouse emulation support (long pressing Start button)\n");
|
||||
#if defined(HAVE_SDL) || defined(HAVE_X11)
|
||||
printf("\n WM options (SDL and X11 only)\n\n");
|
||||
printf("\t-windowed\t\tDisplay screen in a window\n");
|
||||
@ -211,19 +241,19 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
if (config.action == NULL || strcmp("help", config.action) == 0)
|
||||
help();
|
||||
|
||||
|
||||
if (config.debug_level > 0)
|
||||
printf("Moonlight Embedded %d.%d.%d (%s)\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, COMPILE_OPTIONS);
|
||||
|
||||
if (strcmp("map", config.action) == 0) {
|
||||
if (strcmp("map", config.action) == 0) {
|
||||
if (config.inputsCount != 1) {
|
||||
printf("You need to specify one input device using -input.\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
evdev_create(config.inputs[0], NULL, config.debug_level > 0);
|
||||
evdev_map(config.inputs[0]);
|
||||
exit(0);
|
||||
|
||||
evdev_create(config.inputs[0], NULL, config.debug_level > 0, config.rotate);
|
||||
evdev_map(config.inputs[0]);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (config.address == NULL) {
|
||||
@ -234,23 +264,23 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
config.address[0] = 0;
|
||||
printf("Searching for server...\n");
|
||||
gs_discover_server(config.address);
|
||||
gs_discover_server(config.address, &config.port);
|
||||
if (config.address[0] == 0) {
|
||||
fprintf(stderr, "Autodiscovery failed. Specify an IP address next time.\n");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
char host_config_file[128];
|
||||
sprintf(host_config_file, "hosts/%s.conf", config.address);
|
||||
if (access(host_config_file, R_OK) != -1)
|
||||
config_file_parse(host_config_file, &config);
|
||||
|
||||
SERVER_DATA server;
|
||||
printf("Connect to %s...\n", config.address);
|
||||
printf("Connecting to %s...\n", config.address);
|
||||
|
||||
int ret;
|
||||
if ((ret = gs_init(&server, config.address, config.key_dir, config.debug_level, config.unsupported)) == GS_OUT_OF_MEMORY) {
|
||||
if ((ret = gs_init(&server, config.address, config.port, config.key_dir, config.debug_level, config.unsupported)) == GS_OUT_OF_MEMORY) {
|
||||
fprintf(stderr, "Not enough memory\n");
|
||||
exit(-1);
|
||||
} else if (ret == GS_ERROR) {
|
||||
@ -267,8 +297,10 @@ int main(int argc, char* argv[]) {
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (config.debug_level > 0)
|
||||
printf("NVIDIA %s, GFE %s (%s, %s)\n", server.gpuType, server.serverInfo.serverInfoGfeVersion, server.gsVersion, server.serverInfo.serverInfoAppVersion);
|
||||
if (config.debug_level > 0) {
|
||||
printf("GPU: %s, GFE: %s (%s, %s)\n", server.gpuType, server.serverInfo.serverInfoGfeVersion, server.gsVersion, server.serverInfo.serverInfoAppVersion);
|
||||
printf("Server codec flags: 0x%x\n", server.serverInfo.serverCodecModeSupport);
|
||||
}
|
||||
|
||||
if (strcmp("list", config.action) == 0) {
|
||||
pair_check(&server);
|
||||
@ -286,57 +318,90 @@ int main(int argc, char* argv[]) {
|
||||
fprintf(stderr, "You can't select a audio device for SDL\n");
|
||||
exit(-1);
|
||||
}
|
||||
config.stream.supportsHevc = config.codec != CODEC_H264 && (config.codec == CODEC_HEVC || platform_supports_hevc(system));
|
||||
|
||||
if (IS_EMBEDDED(system)) {
|
||||
char* mapping_env = getenv("SDL_GAMECONTROLLERCONFIG");
|
||||
if (config.mapping == NULL && mapping_env == NULL) {
|
||||
fprintf(stderr, "Please specify mapping file as default mapping could not be found.\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
struct mapping* mappings = NULL;
|
||||
if (config.mapping != NULL)
|
||||
mappings = mapping_load(config.mapping, config.debug_level > 0);
|
||||
|
||||
if (mapping_env != NULL) {
|
||||
struct mapping* map = mapping_parse(mapping_env);
|
||||
map->next = mappings;
|
||||
mappings = map;
|
||||
}
|
||||
|
||||
for (int i=0;i<config.inputsCount;i++) {
|
||||
if (config.debug_level > 0)
|
||||
printf("Add input %s...\n", config.inputs[i]);
|
||||
|
||||
evdev_create(config.inputs[i], mappings, config.debug_level > 0);
|
||||
}
|
||||
|
||||
udev_init(!inputAdded, mappings, config.debug_level > 0);
|
||||
evdev_init();
|
||||
rumble_handler = evdev_rumble;
|
||||
#ifdef HAVE_LIBCEC
|
||||
cec_init();
|
||||
#endif /* HAVE_LIBCEC */
|
||||
config.stream.supportedVideoFormats = VIDEO_FORMAT_H264;
|
||||
if (config.codec == CODEC_HEVC || (config.codec == CODEC_UNSPECIFIED && platform_prefers_codec(system, CODEC_HEVC))) {
|
||||
config.stream.supportedVideoFormats |= VIDEO_FORMAT_H265;
|
||||
if (config.hdr)
|
||||
config.stream.supportedVideoFormats |= VIDEO_FORMAT_H265_MAIN10;
|
||||
}
|
||||
if (config.codec == CODEC_AV1 || (config.codec == CODEC_UNSPECIFIED && platform_prefers_codec(system, CODEC_AV1))) {
|
||||
config.stream.supportedVideoFormats |= VIDEO_FORMAT_AV1_MAIN8;
|
||||
if (config.hdr)
|
||||
config.stream.supportedVideoFormats |= VIDEO_FORMAT_AV1_MAIN10;
|
||||
}
|
||||
|
||||
if (config.hdr && !(config.stream.supportedVideoFormats & VIDEO_FORMAT_MASK_10BIT)) {
|
||||
fprintf(stderr, "HDR streaming requires HEVC or AV1 codec\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
#ifdef HAVE_SDL
|
||||
else if (system == SDL) {
|
||||
if (config.inputsCount > 0) {
|
||||
fprintf(stderr, "You can't select input devices as SDL will automatically use all available controllers\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (system == SDL)
|
||||
sdl_init(config.stream.width, config.stream.height, config.fullscreen);
|
||||
sdlinput_init(config.mapping);
|
||||
rumble_handler = sdlinput_rumble;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (config.viewonly) {
|
||||
if (config.debug_level > 0)
|
||||
printf("View-only mode enabled, no input will be sent to the host computer\n");
|
||||
} else {
|
||||
if (IS_EMBEDDED(system)) {
|
||||
char* mapping_env = getenv("SDL_GAMECONTROLLERCONFIG");
|
||||
if (config.mapping == NULL && mapping_env == NULL) {
|
||||
fprintf(stderr, "Please specify mapping file as default mapping could not be found.\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
struct mapping* mappings = NULL;
|
||||
if (config.mapping != NULL)
|
||||
mappings = mapping_load(config.mapping, config.debug_level > 0);
|
||||
|
||||
if (mapping_env != NULL) {
|
||||
struct mapping* map = mapping_parse(mapping_env);
|
||||
map->next = mappings;
|
||||
mappings = map;
|
||||
}
|
||||
|
||||
for (int i=0;i<config.inputsCount;i++) {
|
||||
if (config.debug_level > 0)
|
||||
printf("Adding input device %s...\n", config.inputs[i]);
|
||||
|
||||
evdev_create(config.inputs[i], mappings, config.debug_level > 0, config.rotate);
|
||||
}
|
||||
|
||||
udev_init(!inputAdded, mappings, config.debug_level > 0, config.rotate);
|
||||
evdev_init(config.mouse_emulation);
|
||||
rumble_handler = evdev_rumble;
|
||||
#ifdef HAVE_LIBCEC
|
||||
cec_init();
|
||||
#endif /* HAVE_LIBCEC */
|
||||
}
|
||||
#ifdef HAVE_SDL
|
||||
else if (system == SDL) {
|
||||
if (config.inputsCount > 0) {
|
||||
fprintf(stderr, "You can't select input devices as SDL will automatically use all available controllers\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
sdlinput_init(config.mapping);
|
||||
rumble_handler = sdlinput_rumble;
|
||||
rumble_triggers_handler = sdlinput_rumble_triggers;
|
||||
set_motion_event_state_handler = sdlinput_set_motion_event_state;
|
||||
set_controller_led_handler = sdlinput_set_controller_led;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
stream(&server, &config, system);
|
||||
} else if (strcmp("pair", config.action) == 0) {
|
||||
char pin[5];
|
||||
sprintf(pin, "%d%d%d%d", (int)random() % 10, (int)random() % 10, (int)random() % 10, (int)random() % 10);
|
||||
if (config.pin > 0 && config.pin <= 9999) {
|
||||
sprintf(pin, "%04d", config.pin);
|
||||
} else {
|
||||
sprintf(pin, "%d%d%d%d", (unsigned)random() % 10, (unsigned)random() % 10, (unsigned)random() % 10, (unsigned)random() % 10);
|
||||
}
|
||||
printf("Please enter the following PIN on the target PC: %s\n", pin);
|
||||
fflush(stdout);
|
||||
if (gs_pair(&server, &pin[0]) != GS_OK) {
|
||||
fprintf(stderr, "Failed to pair to server: %s\n", gs_error);
|
||||
} else {
|
||||
|
@ -52,9 +52,16 @@ enum platform platform_check(char* name) {
|
||||
return PI;
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_MMAL
|
||||
if (std || strcmp(name, "mmal") == 0) {
|
||||
void *handle = dlopen("libmoonlight-mmal.so", RTLD_NOW | RTLD_GLOBAL);
|
||||
if (handle != NULL && dlsym(RTLD_DEFAULT, "bcm_host_init") != NULL)
|
||||
return MMAL;
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_AML
|
||||
if (std || strcmp(name, "aml") == 0) {
|
||||
void *handle = dlopen("libmoonlight-aml.so", RTLD_NOW | RTLD_GLOBAL);
|
||||
void *handle = dlopen("libmoonlight-aml.so", RTLD_LAZY | RTLD_GLOBAL);
|
||||
if (handle != NULL && access("/dev/amvideo", F_OK) != -1)
|
||||
return AML;
|
||||
}
|
||||
@ -80,13 +87,18 @@ enum platform platform_check(char* name) {
|
||||
if (init == INIT_VDPAU)
|
||||
return X11_VDPAU;
|
||||
#endif
|
||||
#ifdef HAVE_SDL
|
||||
return SDL;
|
||||
#else
|
||||
return X11;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_SDL
|
||||
if (std || strcmp(name, "sdl") == 0)
|
||||
return SDL;
|
||||
#endif
|
||||
|
||||
if (strcmp(name, "fake") == 0)
|
||||
return FAKE;
|
||||
|
||||
@ -97,13 +109,14 @@ void platform_start(enum platform system) {
|
||||
switch (system) {
|
||||
#ifdef HAVE_AML
|
||||
case AML:
|
||||
blank_fb("/sys/class/graphics/fb0/blank", true);
|
||||
blank_fb("/sys/class/graphics/fb1/blank", true);
|
||||
write_bool("/sys/class/graphics/fb0/blank", true);
|
||||
write_bool("/sys/class/graphics/fb1/blank", true);
|
||||
write_bool("/sys/class/video/disable_video", false);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_PI
|
||||
#if defined(HAVE_PI) || defined(HAVE_MMAL)
|
||||
case PI:
|
||||
blank_fb("/sys/class/graphics/fb0/blank", true);
|
||||
write_bool("/sys/class/graphics/fb0/blank", true);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
@ -113,13 +126,13 @@ void platform_stop(enum platform system) {
|
||||
switch (system) {
|
||||
#ifdef HAVE_AML
|
||||
case AML:
|
||||
blank_fb("/sys/class/graphics/fb0/blank", false);
|
||||
blank_fb("/sys/class/graphics/fb1/blank", false);
|
||||
write_bool("/sys/class/graphics/fb0/blank", false);
|
||||
write_bool("/sys/class/graphics/fb1/blank", false);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_PI
|
||||
#if defined(HAVE_PI) || defined(HAVE_MMAL)
|
||||
case PI:
|
||||
blank_fb("/sys/class/graphics/fb0/blank", false);
|
||||
write_bool("/sys/class/graphics/fb0/blank", false);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
@ -151,6 +164,10 @@ DECODER_RENDERER_CALLBACKS* platform_get_video(enum platform system) {
|
||||
case PI:
|
||||
return (PDECODER_RENDERER_CALLBACKS) dlsym(RTLD_DEFAULT, "decoder_callbacks_pi");
|
||||
#endif
|
||||
#ifdef HAVE_MMAL
|
||||
case MMAL:
|
||||
return (PDECODER_RENDERER_CALLBACKS) dlsym(RTLD_DEFAULT, "decoder_callbacks_mmal");
|
||||
#endif
|
||||
#ifdef HAVE_AML
|
||||
case AML:
|
||||
return (PDECODER_RENDERER_CALLBACKS) dlsym(RTLD_DEFAULT, "decoder_callbacks_aml");
|
||||
@ -165,6 +182,8 @@ DECODER_RENDERER_CALLBACKS* platform_get_video(enum platform system) {
|
||||
|
||||
AUDIO_RENDERER_CALLBACKS* platform_get_audio(enum platform system, char* audio_device) {
|
||||
switch (system) {
|
||||
case FAKE:
|
||||
return NULL;
|
||||
#ifdef HAVE_SDL
|
||||
case SDL:
|
||||
return &audio_callbacks_sdl;
|
||||
@ -173,6 +192,7 @@ AUDIO_RENDERER_CALLBACKS* platform_get_audio(enum platform system, char* audio_d
|
||||
case PI:
|
||||
if (audio_device == NULL || strcmp(audio_device, "local") == 0 || strcmp(audio_device, "hdmi") == 0)
|
||||
return (PAUDIO_RENDERER_CALLBACKS) dlsym(RTLD_DEFAULT, "audio_callbacks_omx");
|
||||
// fall-through
|
||||
#endif
|
||||
default:
|
||||
#ifdef HAVE_PULSE
|
||||
@ -182,15 +202,29 @@ AUDIO_RENDERER_CALLBACKS* platform_get_audio(enum platform system, char* audio_d
|
||||
#ifdef HAVE_ALSA
|
||||
return &audio_callbacks_alsa;
|
||||
#endif
|
||||
#ifdef __FreeBSD__
|
||||
return &audio_callbacks_oss;
|
||||
#endif
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool platform_supports_hevc(enum platform system) {
|
||||
switch (system) {
|
||||
case AML:
|
||||
case RK:
|
||||
bool platform_prefers_codec(enum platform system, enum codecs codec) {
|
||||
switch (codec) {
|
||||
case CODEC_H264:
|
||||
// H.264 is always supported
|
||||
return true;
|
||||
case CODEC_HEVC:
|
||||
switch (system) {
|
||||
case AML:
|
||||
case RK:
|
||||
case X11_VAAPI:
|
||||
case X11_VDPAU:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case CODEC_AV1:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -199,6 +233,8 @@ char* platform_name(enum platform system) {
|
||||
switch(system) {
|
||||
case PI:
|
||||
return "Raspberry Pi (Broadcom)";
|
||||
case MMAL:
|
||||
return "Raspberry Pi (Broadcom) MMAL";
|
||||
case IMX:
|
||||
return "i.MX6 (MXC Vivante)";
|
||||
case AML:
|
||||
|
@ -26,12 +26,13 @@
|
||||
|
||||
#define IS_EMBEDDED(SYSTEM) SYSTEM != SDL
|
||||
|
||||
enum platform { NONE, SDL, X11, X11_VDPAU, X11_VAAPI, PI, IMX, AML, RK, FAKE };
|
||||
enum platform { NONE, SDL, X11, X11_VDPAU, X11_VAAPI, PI, MMAL, IMX, AML, RK, FAKE };
|
||||
enum codecs { CODEC_UNSPECIFIED, CODEC_H264, CODEC_HEVC, CODEC_AV1 };
|
||||
|
||||
enum platform platform_check(char*);
|
||||
PDECODER_RENDERER_CALLBACKS platform_get_video(enum platform system);
|
||||
PAUDIO_RENDERER_CALLBACKS platform_get_audio(enum platform system, char* audio_device);
|
||||
bool platform_supports_hevc(enum platform system);
|
||||
bool platform_prefers_codec(enum platform system, enum codecs codec);
|
||||
char* platform_name(enum platform system);
|
||||
|
||||
void platform_start(enum platform system);
|
||||
|
@ -50,7 +50,6 @@ void sdl_init(int width, int height, bool fullscreen) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
||||
if (!renderer) {
|
||||
printf("SDL_CreateRenderer failed: %s\n", SDL_GetError());
|
||||
@ -72,19 +71,25 @@ void sdl_init(int width, int height, bool fullscreen) {
|
||||
|
||||
void sdl_loop() {
|
||||
SDL_Event event;
|
||||
|
||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||
|
||||
while(!done && SDL_WaitEvent(&event)) {
|
||||
switch (sdlinput_handle_event(&event)) {
|
||||
switch (sdlinput_handle_event(window, &event)) {
|
||||
case SDL_QUIT_APPLICATION:
|
||||
done = true;
|
||||
break;
|
||||
case SDL_TOGGLE_FULLSCREEN:
|
||||
fullscreen_flags ^= SDL_WINDOW_FULLSCREEN;
|
||||
SDL_SetWindowFullscreen(window, fullscreen_flags);
|
||||
break;
|
||||
case SDL_MOUSE_GRAB:
|
||||
SDL_ShowCursor(SDL_ENABLE);
|
||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||
break;
|
||||
case SDL_MOUSE_UNGRAB:
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
break;
|
||||
default:
|
||||
if (event.type == SDL_QUIT)
|
||||
|
@ -36,7 +36,7 @@
|
||||
void sdl_init(int width, int height, bool fullscreen);
|
||||
void sdl_loop();
|
||||
|
||||
SDL_mutex *mutex;
|
||||
int sdlCurrentFrame, sdlNextFrame;
|
||||
extern SDL_mutex *mutex;
|
||||
extern int sdlCurrentFrame, sdlNextFrame;
|
||||
|
||||
#endif /* HAVE_SDL */
|
||||
|
32
src/util.c
32
src/util.c
@ -24,17 +24,43 @@
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int blank_fb(char *path, bool clear) {
|
||||
int write_bool(char *path, bool val) {
|
||||
int fd = open(path, O_RDWR);
|
||||
|
||||
if(fd >= 0) {
|
||||
int ret = write(fd, clear ? "1" : "0", 1);
|
||||
int ret = write(fd, val ? "1" : "0", 1);
|
||||
if (ret < 0)
|
||||
fprintf(stderr, "Failed to clear framebuffer %s: %d\n", path, ret);
|
||||
fprintf(stderr, "Failed to write %d to %s: %d\n", val ? 1 : 0, path, ret);
|
||||
|
||||
close(fd);
|
||||
return 0;
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
int read_file(char *path, char* output, int output_len) {
|
||||
int fd = open(path, O_RDONLY);
|
||||
|
||||
if(fd >= 0) {
|
||||
output_len = read(fd, output, output_len);
|
||||
close(fd);
|
||||
return output_len;
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool ensure_buf_size(void **buf, size_t *buf_size, size_t required_size) {
|
||||
if (*buf_size >= required_size)
|
||||
return false;
|
||||
|
||||
*buf_size = required_size;
|
||||
*buf = realloc(*buf, *buf_size);
|
||||
if (!*buf) {
|
||||
fprintf(stderr, "Failed to allocate %zu bytes\n", *buf_size);
|
||||
abort();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -18,5 +18,9 @@
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
int blank_fb(char *path, bool clear);
|
||||
int write_bool(char *path, bool val);
|
||||
int read_file(char *path, char *output, int output_len);
|
||||
bool ensure_buf_size(void **buf, size_t *buf_size, size_t required_size);
|
||||
bool has_fast_aes(void);
|
||||
|
183
src/video/aml.c
183
src/video/aml.c
@ -24,48 +24,101 @@
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <codec.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../util.h"
|
||||
#include "video.h"
|
||||
|
||||
#define SYNC_OUTSIDE 0x02
|
||||
#define UCODE_IP_ONLY_PARAM 0x08
|
||||
#define MAX_WRITE_ATTEMPTS 5
|
||||
#define EAGAIN_SLEEP_TIME 2 * 1000
|
||||
|
||||
static codec_para_t codecParam = { 0 };
|
||||
static pthread_t displayThread;
|
||||
static int videoFd = -1;
|
||||
static volatile bool done = false;
|
||||
void *pkt_buf = NULL;
|
||||
size_t pkt_buf_size = 0;
|
||||
|
||||
void* aml_display_thread(void* unused) {
|
||||
while (!done) {
|
||||
struct v4l2_buffer vbuf = { 0 };
|
||||
vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
if (ioctl(videoFd, VIDIOC_DQBUF, &vbuf) < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
usleep(500);
|
||||
continue;
|
||||
}
|
||||
fprintf(stderr, "VIDIOC_DQBUF failed: %d\n", errno);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ioctl(videoFd, VIDIOC_QBUF, &vbuf) < 0) {
|
||||
fprintf(stderr, "VIDIOC_QBUF failed: %d\n", errno);
|
||||
break;
|
||||
}
|
||||
}
|
||||
printf("Display thread terminated\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int aml_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
codecParam.stream_type = STREAM_TYPE_ES_VIDEO;
|
||||
codecParam.has_video = 1;
|
||||
codecParam.noblock = 0;
|
||||
codecParam.am_sysinfo.param = 0;
|
||||
|
||||
switch (videoFormat) {
|
||||
case VIDEO_FORMAT_H264:
|
||||
if (width > 1920 || height > 1080) {
|
||||
codecParam.video_type = VFORMAT_H264_4K2K;
|
||||
codecParam.am_sysinfo.format = VIDEO_DEC_FORMAT_H264_4K2K;
|
||||
} else {
|
||||
codecParam.video_type = VFORMAT_H264;
|
||||
codecParam.am_sysinfo.format = VIDEO_DEC_FORMAT_H264;
|
||||
codecParam.handle = -1;
|
||||
codecParam.cntl_handle = -1;
|
||||
codecParam.audio_utils_handle = -1;
|
||||
codecParam.sub_handle = -1;
|
||||
codecParam.has_video = 1;
|
||||
codecParam.noblock = 0;
|
||||
codecParam.stream_type = STREAM_TYPE_ES_VIDEO;
|
||||
codecParam.am_sysinfo.param = 0;
|
||||
|
||||
// Workaround for decoding special case of C1, 1080p, H264
|
||||
int major, minor;
|
||||
struct utsname name;
|
||||
uname(&name);
|
||||
int ret = sscanf(name.release, "%d.%d", &major, &minor);
|
||||
if (!(major > 3 || (major == 3 && minor >= 14)) && width == 1920 && height == 1080)
|
||||
codecParam.am_sysinfo.param = (void*) UCODE_IP_ONLY_PARAM;
|
||||
}
|
||||
break;
|
||||
case VIDEO_FORMAT_H265:
|
||||
codecParam.video_type = VFORMAT_HEVC;
|
||||
codecParam.am_sysinfo.format = VIDEO_DEC_FORMAT_HEVC;
|
||||
break;
|
||||
default:
|
||||
printf("Video format not supported\n");
|
||||
return -1;
|
||||
#ifdef STREAM_TYPE_FRAME
|
||||
codecParam.dec_mode = STREAM_TYPE_FRAME;
|
||||
#endif
|
||||
|
||||
#ifdef FRAME_BASE_PATH_AMLVIDEO_AMVIDEO
|
||||
codecParam.video_path = FRAME_BASE_PATH_AMLVIDEO_AMVIDEO;
|
||||
#endif
|
||||
|
||||
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||
if (width > 1920 || height > 1080) {
|
||||
codecParam.video_type = VFORMAT_H264_4K2K;
|
||||
codecParam.am_sysinfo.format = VIDEO_DEC_FORMAT_H264_4K2K;
|
||||
} else {
|
||||
codecParam.video_type = VFORMAT_H264;
|
||||
codecParam.am_sysinfo.format = VIDEO_DEC_FORMAT_H264;
|
||||
|
||||
// Workaround for decoding special case of C1, 1080p, H264
|
||||
int major, minor;
|
||||
struct utsname name;
|
||||
uname(&name);
|
||||
int ret = sscanf(name.release, "%d.%d", &major, &minor);
|
||||
if (ret == 2 && !(major > 3 || (major == 3 && minor >= 14)) && width == 1920 && height == 1080)
|
||||
codecParam.am_sysinfo.param = (void*) UCODE_IP_ONLY_PARAM;
|
||||
}
|
||||
} else if (videoFormat & VIDEO_FORMAT_MASK_H265) {
|
||||
codecParam.video_type = VFORMAT_HEVC;
|
||||
codecParam.am_sysinfo.format = VIDEO_DEC_FORMAT_HEVC;
|
||||
#ifdef CODEC_TAG_AV1
|
||||
} else if (videoFormat & VIDEO_FORMAT_MASK_AV1) {
|
||||
codecParam.video_type = VFORMAT_AV1;
|
||||
codecParam.am_sysinfo.format = VIDEO_DEC_FORMAT_AV1;
|
||||
#endif
|
||||
} else {
|
||||
printf("Video format not supported\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
codecParam.am_sysinfo.width = width;
|
||||
@ -84,33 +137,83 @@ int aml_setup(int videoFormat, int width, int height, int redrawRate, void* cont
|
||||
return -2;
|
||||
}
|
||||
|
||||
char vfm_map[2048] = {};
|
||||
char* eol;
|
||||
if (read_file("/sys/class/vfm/map", vfm_map, sizeof(vfm_map) - 1) > 0 && (eol = strchr(vfm_map, '\n'))) {
|
||||
*eol = 0;
|
||||
|
||||
// If amlvideo is in the pipeline, we must spawn a display thread
|
||||
printf("VFM map: %s\n", vfm_map);
|
||||
if (strstr(vfm_map, "amlvideo")) {
|
||||
printf("Using display thread for amlvideo pipeline\n");
|
||||
|
||||
videoFd = open("/dev/video10", O_RDONLY | O_NONBLOCK);
|
||||
if (videoFd < 0) {
|
||||
fprintf(stderr, "Failed to open video device: %d\n", errno);
|
||||
return -3;
|
||||
}
|
||||
|
||||
pthread_create(&displayThread, NULL, aml_display_thread, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
ensure_buf_size(&pkt_buf, &pkt_buf_size, INITIAL_DECODER_BUFFER_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void aml_cleanup() {
|
||||
if (videoFd >= 0) {
|
||||
done = true;
|
||||
pthread_join(displayThread, NULL);
|
||||
close(videoFd);
|
||||
}
|
||||
|
||||
codec_close(&codecParam);
|
||||
free(pkt_buf);
|
||||
}
|
||||
|
||||
int aml_submit_decode_unit(PDECODE_UNIT decodeUnit) {
|
||||
int result = DR_OK;
|
||||
PLENTRY entry = decodeUnit->bufferList;
|
||||
while (entry != NULL) {
|
||||
int api = codec_write(&codecParam, entry->data, entry->length);
|
||||
if (api != entry->length) {
|
||||
fprintf(stderr, "codec_write error: %x\n", api);
|
||||
codec_reset(&codecParam);
|
||||
result = DR_NEED_IDR;
|
||||
break;
|
||||
}
|
||||
|
||||
ensure_buf_size(&pkt_buf, &pkt_buf_size, decodeUnit->fullLength);
|
||||
|
||||
int written = 0, length = 0, errCounter = 0, api;
|
||||
PLENTRY entry = decodeUnit->bufferList;
|
||||
do {
|
||||
memcpy(pkt_buf+length, entry->data, entry->length);
|
||||
length += entry->length;
|
||||
entry = entry->next;
|
||||
} while (entry != NULL);
|
||||
|
||||
codec_checkin_pts(&codecParam, decodeUnit->presentationTimeMs);
|
||||
while (length > 0) {
|
||||
api = codec_write(&codecParam, pkt_buf+written, length);
|
||||
if (api < 0) {
|
||||
if (errno != EAGAIN) {
|
||||
fprintf(stderr, "codec_write() error: %x %d\n", errno, api);
|
||||
codec_reset(&codecParam);
|
||||
break;
|
||||
} else {
|
||||
if (++errCounter == MAX_WRITE_ATTEMPTS) {
|
||||
fprintf(stderr, "codec_write() timeout\n");
|
||||
break;
|
||||
}
|
||||
usleep(EAGAIN_SLEEP_TIME);
|
||||
}
|
||||
} else {
|
||||
written += api;
|
||||
length -= api;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
return length ? DR_NEED_IDR : DR_OK;
|
||||
}
|
||||
|
||||
DECODER_RENDERER_CALLBACKS decoder_callbacks_aml = {
|
||||
.setup = aml_setup,
|
||||
.cleanup = aml_cleanup,
|
||||
.submitDecodeUnit = aml_submit_decode_unit,
|
||||
.capabilities = CAPABILITY_DIRECT_SUBMIT | CAPABILITY_SLICES_PER_FRAME(8),
|
||||
|
||||
// We may delay in aml_submit_decode_unit() for a while, so we can't set CAPABILITY_DIRECT_SUBMIT
|
||||
.capabilities = CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC,
|
||||
};
|
||||
|
@ -139,11 +139,10 @@ void egl_init(EGLNativeDisplayType native_display, NativeWindowType native_windo
|
||||
glGenBuffers(1, &ebo);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STATIC_DRAW);
|
||||
|
||||
|
||||
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
|
||||
glShaderSource(vertex_shader, 1, &vertex_source, NULL);
|
||||
glCompileShader(vertex_shader);
|
||||
GLint maxLength = 0;
|
||||
|
||||
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
glShaderSource(fragment_shader, 1, &fragment_source, NULL);
|
||||
|
@ -32,8 +32,8 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
// General decoder and renderer state
|
||||
static AVPacket pkt;
|
||||
static AVCodec* decoder;
|
||||
static AVPacket* pkt;
|
||||
static const AVCodec* decoder;
|
||||
static AVCodecContext* decoder_ctx;
|
||||
static AVFrame** dec_frames;
|
||||
|
||||
@ -53,16 +53,79 @@ int ffmpeg_init(int videoFormat, int width, int height, int perf_lvl, int buffer
|
||||
avcodec_register_all();
|
||||
#endif
|
||||
|
||||
av_init_packet(&pkt);
|
||||
pkt = av_packet_alloc();
|
||||
if (pkt == NULL) {
|
||||
printf("Couldn't allocate packet\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ffmpeg_decoder = perf_lvl & VAAPI_ACCELERATION ? VAAPI : SOFTWARE;
|
||||
switch (videoFormat) {
|
||||
case VIDEO_FORMAT_H264:
|
||||
decoder = avcodec_find_decoder_by_name("h264");
|
||||
break;
|
||||
case VIDEO_FORMAT_H265:
|
||||
decoder = avcodec_find_decoder_by_name("hevc");
|
||||
break;
|
||||
|
||||
for (int try = 0; try < 6; try++) {
|
||||
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||
if (ffmpeg_decoder == SOFTWARE) {
|
||||
if (try == 0) decoder = avcodec_find_decoder_by_name("h264_nvv4l2"); // Tegra
|
||||
if (try == 1) decoder = avcodec_find_decoder_by_name("h264_nvmpi"); // Tegra
|
||||
if (try == 2) decoder = avcodec_find_decoder_by_name("h264_omx"); // VisionFive
|
||||
if (try == 3) decoder = avcodec_find_decoder_by_name("h264_v4l2m2m"); // Stateful V4L2
|
||||
}
|
||||
if (try == 4) decoder = avcodec_find_decoder_by_name("h264"); // Software and hwaccel
|
||||
} else if (videoFormat & VIDEO_FORMAT_MASK_H265) {
|
||||
if (ffmpeg_decoder == SOFTWARE) {
|
||||
if (try == 0) decoder = avcodec_find_decoder_by_name("hevc_nvv4l2"); // Tegra
|
||||
if (try == 1) decoder = avcodec_find_decoder_by_name("hevc_nvmpi"); // Tegra
|
||||
if (try == 2) decoder = avcodec_find_decoder_by_name("hevc_omx"); // VisionFive
|
||||
if (try == 3) decoder = avcodec_find_decoder_by_name("hevc_v4l2m2m"); // Stateful V4L2
|
||||
}
|
||||
if (try == 4) decoder = avcodec_find_decoder_by_name("hevc"); // Software and hwaccel
|
||||
} else if (videoFormat & VIDEO_FORMAT_MASK_AV1) {
|
||||
if (ffmpeg_decoder == SOFTWARE) {
|
||||
if (try == 0) decoder = avcodec_find_decoder_by_name("libdav1d");
|
||||
}
|
||||
if (try == 1) decoder = avcodec_find_decoder_by_name("av1"); // Hwaccel
|
||||
} else {
|
||||
printf("Video format not supported\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Skip this decoder if it isn't compiled into FFmpeg
|
||||
if (!decoder) {
|
||||
continue;
|
||||
}
|
||||
|
||||
decoder_ctx = avcodec_alloc_context3(decoder);
|
||||
if (decoder_ctx == NULL) {
|
||||
printf("Couldn't allocate context\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Use low delay decoding
|
||||
decoder_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
||||
|
||||
// Allow display of corrupt frames and frames missing references
|
||||
decoder_ctx->flags |= AV_CODEC_FLAG_OUTPUT_CORRUPT;
|
||||
decoder_ctx->flags2 |= AV_CODEC_FLAG2_SHOW_ALL;
|
||||
|
||||
// Report decoding errors to allow us to request a key frame
|
||||
decoder_ctx->err_recognition = AV_EF_EXPLODE;
|
||||
|
||||
if (perf_lvl & SLICE_THREADING) {
|
||||
decoder_ctx->thread_type = FF_THREAD_SLICE;
|
||||
decoder_ctx->thread_count = thread_count;
|
||||
} else {
|
||||
decoder_ctx->thread_count = 1;
|
||||
}
|
||||
|
||||
decoder_ctx->width = width;
|
||||
decoder_ctx->height = height;
|
||||
decoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
|
||||
int err = avcodec_open2(decoder_ctx, decoder, NULL);
|
||||
if (err < 0) {
|
||||
printf("Couldn't open codec: %s\n", decoder->name);
|
||||
avcodec_free_context(&decoder_ctx);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (decoder == NULL) {
|
||||
@ -70,36 +133,7 @@ int ffmpeg_init(int videoFormat, int width, int height, int perf_lvl, int buffer
|
||||
return -1;
|
||||
}
|
||||
|
||||
decoder_ctx = avcodec_alloc_context3(decoder);
|
||||
if (decoder_ctx == NULL) {
|
||||
printf("Couldn't allocate context");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (perf_lvl & DISABLE_LOOP_FILTER)
|
||||
// Skip the loop filter for performance reasons
|
||||
decoder_ctx->skip_loop_filter = AVDISCARD_ALL;
|
||||
|
||||
if (perf_lvl & LOW_LATENCY_DECODE)
|
||||
// Use low delay single threaded encoding
|
||||
decoder_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
||||
|
||||
if (perf_lvl & SLICE_THREADING)
|
||||
decoder_ctx->thread_type = FF_THREAD_SLICE;
|
||||
else
|
||||
decoder_ctx->thread_type = FF_THREAD_FRAME;
|
||||
|
||||
decoder_ctx->thread_count = thread_count;
|
||||
|
||||
decoder_ctx->width = width;
|
||||
decoder_ctx->height = height;
|
||||
decoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
|
||||
int err = avcodec_open2(decoder_ctx, decoder, NULL);
|
||||
if (err < 0) {
|
||||
printf("Couldn't open codec");
|
||||
return err;
|
||||
}
|
||||
printf("Using FFmpeg decoder: %s\n", decoder->name);
|
||||
|
||||
dec_frames_cnt = buffer_count;
|
||||
dec_frames = malloc(buffer_count * sizeof(AVFrame*));
|
||||
@ -127,10 +161,9 @@ int ffmpeg_init(int videoFormat, int width, int height, int perf_lvl, int buffer
|
||||
// This function must be called after
|
||||
// decoding is finished
|
||||
void ffmpeg_destroy(void) {
|
||||
av_packet_free(&pkt);
|
||||
if (decoder_ctx) {
|
||||
avcodec_close(decoder_ctx);
|
||||
av_free(decoder_ctx);
|
||||
decoder_ctx = NULL;
|
||||
avcodec_free_context(&decoder_ctx);
|
||||
}
|
||||
if (dec_frames) {
|
||||
for (int i = 0; i < dec_frames_cnt; i++) {
|
||||
@ -161,15 +194,15 @@ AVFrame* ffmpeg_get_frame(bool native_frame) {
|
||||
int ffmpeg_decode(unsigned char* indata, int inlen) {
|
||||
int err;
|
||||
|
||||
pkt.data = indata;
|
||||
pkt.size = inlen;
|
||||
pkt->data = indata;
|
||||
pkt->size = inlen;
|
||||
|
||||
err = avcodec_send_packet(decoder_ctx, &pkt);
|
||||
err = avcodec_send_packet(decoder_ctx, pkt);
|
||||
if (err < 0) {
|
||||
char errorstring[512];
|
||||
av_strerror(err, errorstring, sizeof(errorstring));
|
||||
fprintf(stderr, "Decode failed - %s\n", errorstring);
|
||||
}
|
||||
|
||||
|
||||
return err < 0 ? err : 0;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of Moonlight Embedded.
|
||||
*
|
||||
*
|
||||
* Based on Moonlight Pc implementation
|
||||
*
|
||||
* Moonlight is free software; you can redistribute it and/or modify
|
||||
@ -21,18 +21,8 @@
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
// Disables the deblocking filter at the cost of image quality
|
||||
#define DISABLE_LOOP_FILTER 0x1
|
||||
// Uses the low latency decode flag (disables multithreading)
|
||||
#define LOW_LATENCY_DECODE 0x2
|
||||
// Threads process each slice, rather than each frame
|
||||
// Enable multi-threaded decoding
|
||||
#define SLICE_THREADING 0x4
|
||||
// Uses nonstandard speedup tricks
|
||||
#define FAST_DECODE 0x8
|
||||
// Uses bilinear filtering instead of bicubic
|
||||
#define BILINEAR_FILTERING 0x10
|
||||
// Uses a faster bilinear filtering with lower image quality
|
||||
#define FAST_BILINEAR_FILTERING 0x20
|
||||
// Uses hardware acceleration
|
||||
#define VDPAU_ACCELERATION 0x40
|
||||
#define VAAPI_ACCELERATION 0x80
|
||||
|
@ -46,7 +46,7 @@ static enum AVPixelFormat va_get_format(AVCodecContext* context, const enum AVPi
|
||||
fprintf(stderr, "Failed to initialize VAAPI frame context");
|
||||
return AV_PIX_FMT_NONE;
|
||||
}
|
||||
|
||||
|
||||
context->pix_fmt = AV_PIX_FMT_VAAPI;
|
||||
context->hw_device_ctx = device_ref;
|
||||
context->hw_frames_ctx = hw_ctx;
|
||||
@ -65,6 +65,7 @@ int vaapi_init_lib() {
|
||||
int vaapi_init(AVCodecContext* decoder_ctx) {
|
||||
decoder_ctx->get_format = va_get_format;
|
||||
decoder_ctx->get_buffer2 = va_get_buffer;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void vaapi_queue(AVFrame* dec_frame, Window win, int width, int height) {
|
||||
|
@ -42,7 +42,7 @@
|
||||
#include <linux/v4l2-controls.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#define MIN_FRAME_BUFFER_COUNT 18;
|
||||
#define MIN_FRAME_BUFFER_COUNT 18
|
||||
|
||||
#define THRESHOLD 2
|
||||
|
||||
@ -139,11 +139,11 @@ static int decoder_renderer_setup(int videoFormat, int width, int height, int re
|
||||
}
|
||||
|
||||
close(fd_fb);
|
||||
|
||||
|
||||
int regfbcount = MIN_FRAME_BUFFER_COUNT + 2;
|
||||
int picWidth = ((width + 15) & ~15);
|
||||
int picHeight = ((height + 15) & ~15);
|
||||
|
||||
|
||||
char v4l_device[16];
|
||||
sprintf(v4l_device, "/dev/video%d", 17);
|
||||
fd = open(v4l_device, O_RDWR, 0);
|
||||
@ -192,7 +192,7 @@ static int decoder_renderer_setup(int videoFormat, int width, int height, int re
|
||||
fprintf(stderr, "Not enough video buffers\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < regfbcount; i++) {
|
||||
struct v4l2_buffer buffer = {0};
|
||||
struct vpu_buf *buf;
|
||||
@ -233,7 +233,7 @@ static int decoder_renderer_setup(int videoFormat, int width, int height, int re
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
vpu_setup(buffers, regfbcount, width, height);
|
||||
|
||||
if (pipe(pipefd) == -1 || pipe(clearpipefd) == -1) {
|
||||
|
@ -143,7 +143,7 @@ void vpu_setup(struct vpu_buf* buffers[], int bufferCount, int width, int height
|
||||
}
|
||||
fb[i].bufMvCol = mvcol_md[i].phy_addr;
|
||||
}
|
||||
|
||||
|
||||
bufinfo.avcSliceBufInfo.bufferBase = slice_mem_desc.phy_addr;
|
||||
bufinfo.avcSliceBufInfo.bufferSize = phy_slicebuf_size;
|
||||
|
||||
@ -210,7 +210,7 @@ bool vpu_decode(PDECODE_UNIT decodeUnit) {
|
||||
while (vpu_IsBusy()) {
|
||||
if (loop_id > 50) {
|
||||
vpu_SWReset(handle, 0);
|
||||
fprintf(stderr, "VPU is too long busy\n");
|
||||
fprintf(stderr, "VPU busy timeout expired\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
vpu_WaitForInt(100);
|
||||
@ -237,7 +237,7 @@ bool vpu_decode(PDECODE_UNIT decodeUnit) {
|
||||
fprintf(stderr, "Failed to decode frame\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
|
||||
currentFrame = outinfo.indexFrameDisplay;
|
||||
return true;
|
||||
}
|
||||
@ -249,7 +249,7 @@ void vpu_clear(int disp_clr_index) {
|
||||
void vpu_cleanup() {
|
||||
IOFreePhyMem(&ps_mem_desc);
|
||||
IOFreePhyMem(&slice_mem_desc);
|
||||
|
||||
|
||||
IOFreeVirtMem(&mem_desc);
|
||||
IOFreePhyMem(&mem_desc);
|
||||
vpu_UnInit();
|
||||
|
293
src/video/mmal.c
Normal file
293
src/video/mmal.c
Normal file
@ -0,0 +1,293 @@
|
||||
|
||||
/*
|
||||
Copyright (c) 2012, Broadcom Europe Ltd
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the copyright holder nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
// Video decode on Raspberry Pi using MMAL
|
||||
// Based upon example code from the Raspberry Pi
|
||||
|
||||
#include "video.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <sps.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <bcm_host.h>
|
||||
#include <interface/mmal/mmal.h>
|
||||
#include <interface/mmal/util/mmal_default_components.h>
|
||||
#include <interface/mmal/util/mmal_util_params.h>
|
||||
#include <interface/mmal/util/mmal_util.h>
|
||||
#include <interface/mmal/vc/mmal_vc_api.h>
|
||||
#include <interface/vcos/vcos.h>
|
||||
|
||||
#define ALIGN(x, a) (((x)+(a)-1)&~((a)-1))
|
||||
|
||||
static VCOS_SEMAPHORE_T semaphore;
|
||||
static MMAL_COMPONENT_T *decoder = NULL, *renderer = NULL;
|
||||
static MMAL_POOL_T *pool_in = NULL, *pool_out = NULL;
|
||||
|
||||
static void input_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) {
|
||||
mmal_buffer_header_release(buf);
|
||||
|
||||
if (port == decoder->input[0])
|
||||
vcos_semaphore_post(&semaphore);
|
||||
}
|
||||
|
||||
static void control_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) {
|
||||
if (buf->cmd == MMAL_EVENT_ERROR) {
|
||||
MMAL_STATUS_T status = *(uint32_t *) buf->data;
|
||||
fprintf(stderr, "Video decode error MMAL_EVENT_ERROR:%d\n", status);
|
||||
}
|
||||
|
||||
mmal_buffer_header_release(buf);
|
||||
}
|
||||
|
||||
static void output_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) {
|
||||
if (mmal_port_send_buffer(renderer->input[0], buf) != MMAL_SUCCESS) {
|
||||
fprintf(stderr, "Can't display decoded frame\n");
|
||||
mmal_buffer_header_release(buf);
|
||||
}
|
||||
}
|
||||
|
||||
static int decoder_renderer_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
if (videoFormat != VIDEO_FORMAT_H264) {
|
||||
fprintf(stderr, "Video format not supported\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
bcm_host_init();
|
||||
mmal_vc_init();
|
||||
gs_sps_init(width, height);
|
||||
|
||||
vcos_semaphore_create(&semaphore, "video_decoder", 1);
|
||||
if (mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, &decoder) != MMAL_SUCCESS) {
|
||||
fprintf(stderr, "Can't create decoder\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
MMAL_ES_FORMAT_T *format_in = decoder->input[0]->format;
|
||||
format_in->type = MMAL_ES_TYPE_VIDEO;
|
||||
format_in->encoding = MMAL_ENCODING_H264;
|
||||
format_in->es->video.width = ALIGN(width, 32);
|
||||
format_in->es->video.height = ALIGN(height, 16);
|
||||
format_in->es->video.crop.width = width;
|
||||
format_in->es->video.crop.height = height;
|
||||
format_in->es->video.frame_rate.num = redrawRate;
|
||||
format_in->es->video.frame_rate.den = 1;
|
||||
format_in->es->video.par.num = 1;
|
||||
format_in->es->video.par.den = 1;
|
||||
format_in->flags = MMAL_ES_FORMAT_FLAG_FRAMED;
|
||||
|
||||
if (mmal_port_format_commit(decoder->input[0]) != MMAL_SUCCESS) {
|
||||
fprintf(stderr, "Can't commit input format to decoder\n");
|
||||
return -3;
|
||||
}
|
||||
|
||||
decoder->input[0]->buffer_num = 5;
|
||||
decoder->input[0]->buffer_size = INITIAL_DECODER_BUFFER_SIZE;
|
||||
pool_in = mmal_port_pool_create(decoder->input[0], decoder->input[0]->buffer_num, decoder->output[0]->buffer_size);
|
||||
|
||||
MMAL_ES_FORMAT_T *format_out = decoder->output[0]->format;
|
||||
format_out->encoding = MMAL_ENCODING_OPAQUE;
|
||||
if (mmal_port_format_commit(decoder->output[0]) != MMAL_SUCCESS) {
|
||||
fprintf(stderr, "Can't commit output format to decoder\n");
|
||||
return -3;
|
||||
}
|
||||
|
||||
decoder->output[0]->buffer_num = 3;
|
||||
decoder->output[0]->buffer_size = decoder->output[0]->buffer_size_recommended;
|
||||
pool_out = mmal_port_pool_create(decoder->output[0], decoder->output[0]->buffer_num, decoder->output[0]->buffer_size);
|
||||
|
||||
if (mmal_port_enable(decoder->control, control_callback) != MMAL_SUCCESS) {
|
||||
fprintf(stderr, "Can't enable control port\n");
|
||||
return -4;
|
||||
}
|
||||
|
||||
if (mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &renderer) != MMAL_SUCCESS) {
|
||||
fprintf(stderr, "Can't create renderer\n");
|
||||
return -5;
|
||||
}
|
||||
|
||||
format_in = renderer->input[0]->format;
|
||||
format_in->encoding = MMAL_ENCODING_OPAQUE;
|
||||
format_in->es->video.width = width;
|
||||
format_in->es->video.height = height;
|
||||
format_in->es->video.crop.x = 0;
|
||||
format_in->es->video.crop.y = 0;
|
||||
format_in->es->video.crop.width = width;
|
||||
format_in->es->video.crop.height = height;
|
||||
if (mmal_port_format_commit(renderer->input[0]) != MMAL_SUCCESS) {
|
||||
fprintf(stderr, "Can't set output format\n");
|
||||
return -6;
|
||||
}
|
||||
|
||||
MMAL_DISPLAYREGION_T param;
|
||||
param.hdr.id = MMAL_PARAMETER_DISPLAYREGION;
|
||||
param.hdr.size = sizeof(MMAL_DISPLAYREGION_T);
|
||||
param.set = MMAL_DISPLAY_SET_LAYER | MMAL_DISPLAY_SET_NUM | MMAL_DISPLAY_SET_FULLSCREEN | MMAL_DISPLAY_SET_TRANSFORM;
|
||||
param.layer = 128;
|
||||
param.display_num = 0;
|
||||
param.fullscreen = true;
|
||||
int displayRotation = drFlags & DISPLAY_ROTATE_MASK;
|
||||
switch (displayRotation) {
|
||||
case DISPLAY_ROTATE_90:
|
||||
param.transform = MMAL_DISPLAY_ROT90;
|
||||
break;
|
||||
case DISPLAY_ROTATE_180:
|
||||
param.transform = MMAL_DISPLAY_ROT180;
|
||||
break;
|
||||
case DISPLAY_ROTATE_270:
|
||||
param.transform = MMAL_DISPLAY_ROT270;
|
||||
break;
|
||||
default:
|
||||
param.transform = MMAL_DISPLAY_ROT0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (mmal_port_parameter_set(renderer->input[0], ¶m.hdr) != MMAL_SUCCESS) {
|
||||
fprintf(stderr, "Can't set parameters\n");
|
||||
return -7;
|
||||
}
|
||||
|
||||
if (mmal_port_enable(renderer->control, control_callback) != MMAL_SUCCESS) {
|
||||
fprintf(stderr, "Can't enable control port\n");
|
||||
return -8;
|
||||
}
|
||||
|
||||
if (mmal_component_enable(renderer) != MMAL_SUCCESS) {
|
||||
fprintf(stderr, "Can't enable renderer\n");
|
||||
return -9;
|
||||
}
|
||||
|
||||
if (mmal_port_enable(renderer->input[0], input_callback) != MMAL_SUCCESS) {
|
||||
fprintf(stderr, "Can't enable renderer input port\n");
|
||||
return -10;
|
||||
}
|
||||
|
||||
if (mmal_port_enable(decoder->input[0], input_callback) != MMAL_SUCCESS) {
|
||||
fprintf(stderr, "Can't enable decoder input port\n");
|
||||
return -11;
|
||||
}
|
||||
|
||||
if (mmal_port_enable(decoder->output[0], output_callback) != MMAL_SUCCESS) {
|
||||
fprintf(stderr, "Can't enable decoder output port\n");
|
||||
return -12;
|
||||
}
|
||||
|
||||
if (mmal_component_enable(decoder) != MMAL_SUCCESS) {
|
||||
fprintf(stderr, "Can't enable decoder\n");
|
||||
return -13;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void decoder_renderer_cleanup() {
|
||||
if (decoder)
|
||||
mmal_component_destroy(decoder);
|
||||
|
||||
if (renderer)
|
||||
mmal_component_destroy(renderer);
|
||||
|
||||
if (pool_in)
|
||||
mmal_pool_destroy(pool_in);
|
||||
|
||||
if (pool_out)
|
||||
mmal_pool_destroy(pool_out);
|
||||
|
||||
vcos_semaphore_delete(&semaphore);
|
||||
}
|
||||
|
||||
static int decoder_renderer_submit_decode_unit(PDECODE_UNIT decodeUnit) {
|
||||
MMAL_STATUS_T status;
|
||||
MMAL_BUFFER_HEADER_T *buf = NULL;
|
||||
PLENTRY entry = decodeUnit->bufferList;
|
||||
bool first_entry = false;
|
||||
|
||||
while (entry != NULL) {
|
||||
if (buf == NULL) {
|
||||
vcos_semaphore_wait(&semaphore);
|
||||
if ((buf = mmal_queue_get(pool_in->queue)) != NULL) {
|
||||
buf->flags = 0;
|
||||
buf->offset = 0;
|
||||
buf->pts = buf->dts = MMAL_TIME_UNKNOWN;
|
||||
} else {
|
||||
fprintf(stderr, "Video buffer full\n");
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
}
|
||||
|
||||
if (entry->bufferType != BUFFER_TYPE_PICDATA)
|
||||
buf->flags |= MMAL_BUFFER_HEADER_FLAG_CONFIG;
|
||||
else if (!first_entry) {
|
||||
buf->flags |= MMAL_BUFFER_HEADER_FLAG_FRAME_START;
|
||||
first_entry = true;
|
||||
}
|
||||
|
||||
if (entry->bufferType == BUFFER_TYPE_SPS)
|
||||
gs_sps_fix(entry, GS_SPS_BITSTREAM_FIXUP, buf->data, &buf->length);
|
||||
else {
|
||||
if (entry->length + buf->length > buf->alloc_size) {
|
||||
fprintf(stderr, "Video decoder buffer too small\n");
|
||||
mmal_buffer_header_release(buf);
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
memcpy(buf->data + buf->length, entry->data, entry->length);
|
||||
buf->length += entry->length;
|
||||
}
|
||||
|
||||
if (entry->bufferType != BUFFER_TYPE_PICDATA || entry->next == NULL || entry->next->bufferType != BUFFER_TYPE_PICDATA) {
|
||||
buf->flags |= MMAL_BUFFER_HEADER_FLAG_FRAME_END;
|
||||
if ((status = mmal_port_send_buffer(decoder->input[0], buf)) != MMAL_SUCCESS) {
|
||||
mmal_buffer_header_release(buf);
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
buf = NULL;
|
||||
}
|
||||
|
||||
entry = entry->next;
|
||||
}
|
||||
|
||||
//Send available output buffers to decoder
|
||||
while ((buf = mmal_queue_get(pool_out->queue))) {
|
||||
if ((status = mmal_port_send_buffer(decoder->output[0], buf)) != MMAL_SUCCESS)
|
||||
mmal_buffer_header_release(buf);
|
||||
}
|
||||
|
||||
return DR_OK;
|
||||
}
|
||||
|
||||
DECODER_RENDERER_CALLBACKS decoder_callbacks_mmal = {
|
||||
.setup = decoder_renderer_setup,
|
||||
.cleanup = decoder_renderer_cleanup,
|
||||
.submitDecodeUnit = decoder_renderer_submit_decode_unit,
|
||||
.capabilities = CAPABILITY_DIRECT_SUBMIT,
|
||||
};
|
@ -28,6 +28,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
// Video decode on Raspberry Pi using OpenMAX IL though the ilcient helper library
|
||||
// Based upon video decode example from the Raspberry Pi firmware
|
||||
|
||||
#include "video.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <sps.h>
|
||||
@ -39,13 +41,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#include <ilclient.h>
|
||||
#include <bcm_host.h>
|
||||
|
||||
#define MAX_DECODE_UNIT_SIZE 262144
|
||||
|
||||
static TUNNEL_T tunnel[2];
|
||||
static COMPONENT_T *list[3];
|
||||
static ILCLIENT_T *client;
|
||||
|
||||
static COMPONENT_T *video_decode = NULL, *video_scheduler = NULL, *video_render = NULL;
|
||||
static COMPONENT_T *video_decode = NULL, *video_render = NULL;
|
||||
|
||||
static int port_settings_changed;
|
||||
static int first_packet;
|
||||
@ -60,8 +60,6 @@ static int decoder_renderer_setup(int videoFormat, int width, int height, int re
|
||||
gs_sps_init(width, height);
|
||||
|
||||
OMX_VIDEO_PARAM_PORTFORMATTYPE format;
|
||||
OMX_TIME_CONFIG_CLOCKSTATETYPE cstate;
|
||||
COMPONENT_T *clock = NULL;
|
||||
|
||||
memset(list, 0, sizeof(list));
|
||||
memset(tunnel, 0, sizeof(tunnel));
|
||||
@ -130,18 +128,34 @@ static int decoder_renderer_setup(int videoFormat, int width, int height, int re
|
||||
latencyTarget.nInterFactor = 500;
|
||||
latencyTarget.nAdjCap = 20;
|
||||
|
||||
OMX_CONFIG_DISPLAYREGIONTYPE displayRegion;
|
||||
displayRegion.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE);
|
||||
displayRegion.nVersion.nVersion = OMX_VERSION;
|
||||
displayRegion.nPortIndex = 90;
|
||||
displayRegion.fullscreen = OMX_TRUE;
|
||||
displayRegion.mode = OMX_DISPLAY_SET_FULLSCREEN;
|
||||
|
||||
if(OMX_SetParameter(ILC_GET_HANDLE(video_render), OMX_IndexConfigLatencyTarget, &latencyTarget) != OMX_ErrorNone) {
|
||||
fprintf(stderr, "Failed to set video render parameters\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
OMX_CONFIG_ROTATIONTYPE rotationType;
|
||||
memset(&rotationType, 0, sizeof(OMX_CONFIG_ROTATIONTYPE));
|
||||
rotationType.nSize = sizeof(OMX_CONFIG_ROTATIONTYPE);
|
||||
rotationType.nVersion.nVersion = OMX_VERSION;
|
||||
rotationType.nPortIndex = 90;
|
||||
int displayRotation = drFlags & DISPLAY_ROTATE_MASK;
|
||||
switch (displayRotation) {
|
||||
case DISPLAY_ROTATE_90:
|
||||
rotationType.nRotation = 90;
|
||||
break;
|
||||
case DISPLAY_ROTATE_180:
|
||||
rotationType.nRotation = 180;
|
||||
break;
|
||||
case DISPLAY_ROTATE_270:
|
||||
rotationType.nRotation = 270;
|
||||
break;
|
||||
}
|
||||
|
||||
if(OMX_SetParameter(ILC_GET_HANDLE(video_render), OMX_IndexConfigCommonRotate, &rotationType) != OMX_ErrorNone) {
|
||||
fprintf(stderr, "Failed to set video rotation\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
OMX_PARAM_PORTDEFINITIONTYPE port;
|
||||
|
||||
memset(&port, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
|
||||
@ -154,7 +168,7 @@ static int decoder_renderer_setup(int videoFormat, int width, int height, int re
|
||||
}
|
||||
|
||||
// Increase the buffer size to fit the largest possible frame
|
||||
port.nBufferSize = MAX_DECODE_UNIT_SIZE;
|
||||
port.nBufferSize = INITIAL_DECODER_BUFFER_SIZE;
|
||||
|
||||
if(OMX_SetParameter(ILC_GET_HANDLE(video_decode), OMX_IndexParamPortDefinition, &port) == OMX_ErrorNone &&
|
||||
ilclient_enable_port_buffers(video_decode, 130, NULL, NULL, NULL) == 0) {
|
||||
@ -172,8 +186,6 @@ static int decoder_renderer_setup(int videoFormat, int width, int height, int re
|
||||
}
|
||||
|
||||
static void decoder_renderer_cleanup() {
|
||||
int status = 0;
|
||||
|
||||
OMX_BUFFERHEADERTYPE *buf;
|
||||
if((buf = ilclient_get_input_buffer(video_decode, 130, 1)) == NULL){
|
||||
fprintf(stderr, "Can't get video buffer\n");
|
||||
|
513
src/video/rk.c
513
src/video/rk.c
@ -18,7 +18,8 @@
|
||||
* along with Moonlight; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Limelight.h>
|
||||
#include "video.h"
|
||||
#include "../util.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -27,7 +28,6 @@
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
@ -35,24 +35,80 @@
|
||||
#include <xf86drm.h>
|
||||
#include <xf86drmMode.h>
|
||||
#include <libdrm/drm_fourcc.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include <rockchip/rk_mpi.h>
|
||||
|
||||
#define READ_BUF_SIZE (SZ_1M)
|
||||
#define MAX_FRAMES 16
|
||||
#define RK_H264 7
|
||||
#define RK_H265 16777220
|
||||
#define MAX_FRAMES 3
|
||||
#define RK_H264 0x7
|
||||
#define RK_H265 0x1000004
|
||||
#define RK_AV1 0x1000008
|
||||
|
||||
// Vendor-defined 10-bit format code used prior to 5.10
|
||||
#ifndef DRM_FORMAT_NA12
|
||||
#define DRM_FORMAT_NA12 fourcc_code('N', 'A', '1', '2')
|
||||
#endif
|
||||
|
||||
// Upstreamed 10-bit format code used on 5.10+ kernels
|
||||
#ifndef DRM_FORMAT_NV15
|
||||
#define DRM_FORMAT_NV15 fourcc_code('N', 'V', '1', '5')
|
||||
#endif
|
||||
|
||||
// Values for "Colorspace" connector property
|
||||
#ifndef DRM_MODE_COLORIMETRY_DEFAULT
|
||||
#define DRM_MODE_COLORIMETRY_DEFAULT 0
|
||||
#endif
|
||||
#ifndef DRM_MODE_COLORIMETRY_BT2020_RGB
|
||||
#define DRM_MODE_COLORIMETRY_BT2020_RGB 9
|
||||
#endif
|
||||
|
||||
// HDR structs copied from linux include/linux/hdmi.h for older libdrm versions
|
||||
struct rk_hdr_metadata_infoframe
|
||||
{
|
||||
uint8_t eotf;
|
||||
uint8_t metadata_type;
|
||||
|
||||
struct
|
||||
{
|
||||
uint16_t x, y;
|
||||
} display_primaries[3];
|
||||
|
||||
struct
|
||||
{
|
||||
uint16_t x, y;
|
||||
} white_point;
|
||||
|
||||
uint16_t max_display_mastering_luminance;
|
||||
uint16_t min_display_mastering_luminance;
|
||||
|
||||
uint16_t max_cll;
|
||||
uint16_t max_fall;
|
||||
};
|
||||
|
||||
struct rk_hdr_output_metadata
|
||||
{
|
||||
uint32_t metadata_type;
|
||||
|
||||
union {
|
||||
struct rk_hdr_metadata_infoframe hdmi_metadata_type1;
|
||||
};
|
||||
};
|
||||
|
||||
void *pkt_buf = NULL;
|
||||
size_t pkt_buf_size = 0;
|
||||
int fd;
|
||||
int fb_id;
|
||||
uint32_t plane_id, crtc_id;
|
||||
uint32_t plane_id, crtc_id, conn_id, hdr_metadata_blob_id, pixel_format;
|
||||
int frm_eos;
|
||||
int crtc_width;
|
||||
int crtc_height;
|
||||
RK_U32 frm_width;
|
||||
RK_U32 frm_height;
|
||||
int fb_x, fb_y, fb_width, fb_height;
|
||||
bool atomic;
|
||||
|
||||
uint8_t last_colorspace = 0xFF;
|
||||
bool last_hdr_state = false;
|
||||
|
||||
pthread_t tid_frame, tid_display;
|
||||
pthread_mutex_t mutex;
|
||||
@ -65,6 +121,12 @@ drmModeRes *resources = NULL;
|
||||
drmModePlaneRes *plane_resources = NULL;
|
||||
drmModeCrtcPtr crtc = {0};
|
||||
|
||||
drmModePropertyPtr hdr_metadata_prop = NULL;
|
||||
|
||||
drmModeAtomicReqPtr drm_request = NULL;
|
||||
drmModePropertyPtr plane_props[32];
|
||||
drmModePropertyPtr conn_props[32];
|
||||
|
||||
MppCtx mpi_ctx;
|
||||
MppApi *mpi_api;
|
||||
MppPacket mpi_packet;
|
||||
@ -76,36 +138,57 @@ struct {
|
||||
uint32_t handle;
|
||||
} frame_to_drm[MAX_FRAMES];
|
||||
|
||||
int set_property(uint32_t id, uint32_t type, drmModePropertyPtr *props, char *name, uint64_t value) {
|
||||
while (*props) {
|
||||
if (!strcasecmp(name, (*props)->name)) {
|
||||
if (atomic)
|
||||
return drmModeAtomicAddProperty(drm_request, id, (*props)->prop_id, value);
|
||||
else
|
||||
return drmModeObjectSetProperty(fd, id, type, (*props)->prop_id, value);
|
||||
}
|
||||
props++;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Property '%s' not found\n", name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
void *display_thread(void *param) {
|
||||
|
||||
int ret;
|
||||
|
||||
while (!frm_eos) {
|
||||
int _fb_id;
|
||||
|
||||
ret = pthread_mutex_lock(&mutex);
|
||||
assert(!ret);
|
||||
pthread_mutex_lock(&mutex);
|
||||
while (fb_id == 0) {
|
||||
pthread_cond_wait(&cond, &mutex);
|
||||
assert(!ret);
|
||||
if (fb_id == 0 && frm_eos) {
|
||||
ret = pthread_mutex_unlock(&mutex);
|
||||
assert(!ret);
|
||||
pthread_mutex_unlock(&mutex);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
_fb_id = fb_id;
|
||||
|
||||
fb_id = 0;
|
||||
ret = pthread_mutex_unlock(&mutex);
|
||||
assert(!ret);
|
||||
pthread_mutex_unlock(&mutex);
|
||||
|
||||
// show DRM FB in overlay plane (auto vsynced/atomic !)
|
||||
ret = drmModeSetPlane(fd, plane_id, crtc_id, _fb_id, 0,
|
||||
fb_x, fb_y, fb_width, fb_height,
|
||||
0, 0, frm_width << 16, frm_height << 16);
|
||||
assert(!ret);
|
||||
if (atomic) {
|
||||
// We may need to modeset to apply colorspace changes when toggling HDR
|
||||
set_property(plane_id, DRM_MODE_OBJECT_PLANE, plane_props, "FB_ID", _fb_id);
|
||||
ret = drmModeAtomicCommit(fd, drm_request, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
|
||||
if (ret) {
|
||||
perror("drmModeAtomicCommit");
|
||||
}
|
||||
} else {
|
||||
ret = drmModeSetPlane(fd, plane_id, crtc_id, _fb_id, 0,
|
||||
fb_x, fb_y, fb_width, fb_height,
|
||||
0, 0, frm_width << 16, frm_height << 16);
|
||||
if (ret) {
|
||||
perror("drmModeSetPlane");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *frame_thread(void *param) {
|
||||
@ -162,25 +245,27 @@ void *frame_thread(void *param) {
|
||||
|
||||
// new DRM buffer
|
||||
struct drm_mode_create_dumb dmcd = {0};
|
||||
dmcd.bpp = fmt == MPP_FMT_YUV420SP ? 8:10;
|
||||
dmcd.bpp = 8; // hor_stride is already adjusted for 10 vs 8 bit
|
||||
dmcd.width = hor_stride;
|
||||
dmcd.height = ver_stride * 2; // documentation say not v*2/3 but v*2 (additional info included)
|
||||
do {
|
||||
ret = ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &dmcd);
|
||||
} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
|
||||
assert(!ret);
|
||||
assert(dmcd.pitch == (fmt == MPP_FMT_YUV420SP?hor_stride:hor_stride * 10 / 8));
|
||||
assert(dmcd.size == (fmt == MPP_FMT_YUV420SP?hor_stride:hor_stride * 10 / 8) * ver_stride * 2);
|
||||
ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &dmcd);
|
||||
if (ret) {
|
||||
perror("drmIoctl(DRM_IOCTL_MODE_CREATE_DUMB)");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
assert(dmcd.pitch == dmcd.width);
|
||||
assert(dmcd.size == dmcd.pitch * dmcd.height);
|
||||
frame_to_drm[i].handle = dmcd.handle;
|
||||
|
||||
// commit DRM buffer to frame group
|
||||
struct drm_prime_handle dph = {0};
|
||||
dph.handle = dmcd.handle;
|
||||
dph.fd = -1;
|
||||
do {
|
||||
ret = ioctl(fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &dph);
|
||||
} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
|
||||
assert(!ret);
|
||||
ret = drmIoctl(fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &dph);
|
||||
if (ret) {
|
||||
perror("drmIoctl(DRM_IOCTL_PRIME_HANDLE_TO_FD)");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
MppBufferInfo info = {0};
|
||||
info.type = MPP_BUFFER_TYPE_DRM;
|
||||
info.size = dmcd.width * dmcd.height;
|
||||
@ -193,17 +278,37 @@ void *frame_thread(void *param) {
|
||||
uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0};
|
||||
handles[0] = frame_to_drm[i].handle;
|
||||
offsets[0] = 0;
|
||||
pitches[0] = hor_stride;
|
||||
pitches[0] = dmcd.pitch;
|
||||
handles[1] = frame_to_drm[i].handle;
|
||||
offsets[1] = hor_stride * ver_stride;
|
||||
pitches[1] = hor_stride;
|
||||
ret = drmModeAddFB2(fd, frm_width, frm_height, fmt == MPP_FMT_YUV420SP ? DRM_FORMAT_NV12:DRM_FORMAT_NV12_10, handles, pitches, offsets, &frame_to_drm[i].fb_id, 0);
|
||||
assert(!ret);
|
||||
offsets[1] = pitches[0] * ver_stride;
|
||||
pitches[1] = dmcd.pitch;
|
||||
ret = drmModeAddFB2(fd, frm_width, frm_height, pixel_format, handles, pitches, offsets, &frame_to_drm[i].fb_id, 0);
|
||||
if (ret) {
|
||||
perror("drmModeAddFB2");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
// register external frame group
|
||||
ret = mpi_api->control(mpi_ctx, MPP_DEC_SET_EXT_BUF_GROUP, mpi_frm_grp);
|
||||
ret = mpi_api->control(mpi_ctx, MPP_DEC_SET_INFO_CHANGE_READY, NULL);
|
||||
|
||||
// Set atomic properties for the plane prior to the first commit
|
||||
if (atomic) {
|
||||
set_property(plane_id, DRM_MODE_OBJECT_PLANE, plane_props, "CRTC_ID", crtc_id);
|
||||
set_property(plane_id, DRM_MODE_OBJECT_PLANE, plane_props, "SRC_X", 0 << 16);
|
||||
set_property(plane_id, DRM_MODE_OBJECT_PLANE, plane_props, "SRC_Y", 0 << 16);
|
||||
set_property(plane_id, DRM_MODE_OBJECT_PLANE, plane_props, "SRC_W", frm_width << 16);
|
||||
set_property(plane_id, DRM_MODE_OBJECT_PLANE, plane_props, "SRC_H", frm_height << 16);
|
||||
set_property(plane_id, DRM_MODE_OBJECT_PLANE, plane_props, "CRTC_X", fb_x);
|
||||
set_property(plane_id, DRM_MODE_OBJECT_PLANE, plane_props, "CRTC_Y", fb_y);
|
||||
set_property(plane_id, DRM_MODE_OBJECT_PLANE, plane_props, "CRTC_W", fb_width);
|
||||
set_property(plane_id, DRM_MODE_OBJECT_PLANE, plane_props, "CRTC_H", fb_height);
|
||||
set_property(plane_id, DRM_MODE_OBJECT_PLANE, plane_props, "ZPOS", 0);
|
||||
}
|
||||
|
||||
// Set properties on the connector
|
||||
set_property(conn_id, DRM_MODE_OBJECT_CONNECTOR, conn_props, "allm_enable", 1); // HDMI ALLM (Game Mode)
|
||||
set_property(conn_id, DRM_MODE_OBJECT_CONNECTOR, conn_props, "Colorspace", last_hdr_state ? DRM_MODE_COLORIMETRY_BT2020_RGB : DRM_MODE_COLORIMETRY_DEFAULT);
|
||||
} else {
|
||||
// regular frame received
|
||||
|
||||
@ -218,14 +323,10 @@ void *frame_thread(void *param) {
|
||||
}
|
||||
assert(i != MAX_FRAMES);
|
||||
// send DRM FB to display thread
|
||||
ret = pthread_mutex_lock(&mutex);
|
||||
assert(!ret);
|
||||
pthread_mutex_lock(&mutex);
|
||||
fb_id = frame_to_drm[i].fb_id;
|
||||
ret = pthread_cond_signal(&cond);
|
||||
assert(!ret);
|
||||
ret = pthread_mutex_unlock(&mutex);
|
||||
assert(!ret);
|
||||
|
||||
pthread_cond_signal(&cond);
|
||||
pthread_mutex_unlock(&mutex);
|
||||
} else {
|
||||
fprintf(stderr, "Frame no buff\n");
|
||||
}
|
||||
@ -250,29 +351,42 @@ int rk_setup(int videoFormat, int width, int height, int redrawRate, void* conte
|
||||
int ret;
|
||||
int i;
|
||||
int j;
|
||||
int format = 0;
|
||||
int format;
|
||||
|
||||
switch (videoFormat) {
|
||||
case VIDEO_FORMAT_H264:
|
||||
format = RK_H264;
|
||||
break;
|
||||
case VIDEO_FORMAT_H265:
|
||||
format = RK_H265;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Video format not supported\n");
|
||||
return -1;
|
||||
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||
format = RK_H264;
|
||||
} else if (videoFormat & VIDEO_FORMAT_MASK_H265) {
|
||||
format = RK_H265;
|
||||
} else if (videoFormat & VIDEO_FORMAT_MASK_AV1) {
|
||||
format = RK_AV1;
|
||||
} else {
|
||||
fprintf(stderr, "Video format not supported\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We need atomic plane properties for HDR, but atomic seems to perform quite bad
|
||||
// on RK3588 for some reason. The performance of commits seems to go down the longer
|
||||
// the stream runs. We'll use the legacy API for non-HDR streams as a workaround.
|
||||
atomic = !!(videoFormat & VIDEO_FORMAT_MASK_10BIT);
|
||||
|
||||
MppCodingType mpp_type = (MppCodingType)format;
|
||||
ret = mpp_check_support_format(MPP_CTX_DEC, mpp_type);
|
||||
assert(!ret);
|
||||
if (ret) {
|
||||
fprintf(stderr, "Selected video format is not supported\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
|
||||
assert(fd >= 0);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "Unable to open card0: %d\n", errno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
resources = drmModeGetResources(fd);
|
||||
assert(resources);
|
||||
if (!resources) {
|
||||
perror("drmModeGetResources");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// find active monitor
|
||||
for (i = 0; i < resources->count_connectors; ++i) {
|
||||
@ -287,6 +401,24 @@ int rk_setup(int videoFormat, int width, int height, int redrawRate, void* conte
|
||||
}
|
||||
assert(i < resources->count_connectors);
|
||||
|
||||
conn_id = connector->connector_id;
|
||||
|
||||
{
|
||||
drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(fd, conn_id, DRM_MODE_OBJECT_CONNECTOR);
|
||||
assert(props->count_props < sizeof(conn_props) / sizeof(conn_props[0]));
|
||||
for (j = 0; j < props->count_props; j++) {
|
||||
drmModePropertyPtr prop = drmModeGetProperty(fd, props->props[j]);
|
||||
if (!prop) {
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(prop->name, "HDR_OUTPUT_METADATA")) {
|
||||
hdr_metadata_prop = prop;
|
||||
}
|
||||
conn_props[j] = prop;
|
||||
}
|
||||
drmModeFreeObjectProperties(props);
|
||||
}
|
||||
|
||||
for (i = 0; i < resources->count_encoders; ++i) {
|
||||
encoder = drmModeGetEncoder(fd, resources->encoders[i]);
|
||||
if (!encoder) {
|
||||
@ -302,7 +434,10 @@ int rk_setup(int videoFormat, int width, int height, int redrawRate, void* conte
|
||||
for (i = 0; i < resources->count_crtcs; ++i) {
|
||||
if (resources->crtcs[i] == encoder->crtc_id) {
|
||||
crtc = drmModeGetCrtc(fd, resources->crtcs[i]);
|
||||
assert(crtc);
|
||||
if (!crtc) {
|
||||
perror("drmModeGetCrtc");
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -313,9 +448,25 @@ int rk_setup(int videoFormat, int width, int height, int redrawRate, void* conte
|
||||
uint32_t crtc_bit = (1 << i);
|
||||
|
||||
ret = drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
|
||||
assert(!ret);
|
||||
if (ret) {
|
||||
perror("drmSetClientCap(DRM_CLIENT_CAP_UNIVERSAL_PLANES)");
|
||||
}
|
||||
if (atomic) {
|
||||
ret = drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
|
||||
if (ret) {
|
||||
perror("drmSetClientCap(DRM_CLIENT_CAP_ATOMIC)");
|
||||
atomic = false;
|
||||
}
|
||||
else {
|
||||
drm_request = drmModeAtomicAlloc();
|
||||
assert(drm_request);
|
||||
}
|
||||
}
|
||||
plane_resources = drmModeGetPlaneResources(fd);
|
||||
assert(plane_resources);
|
||||
if (!plane_resources) {
|
||||
perror("drmModeGetPlaneResources");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// search for OVERLAY (for active connector, unused, NV12 support)
|
||||
for (i = 0; i < plane_resources->count_planes; i++) {
|
||||
@ -324,11 +475,19 @@ int rk_setup(int videoFormat, int width, int height, int redrawRate, void* conte
|
||||
continue;
|
||||
}
|
||||
for (j = 0; j < ovr->count_formats; j++) {
|
||||
if (ovr->formats[j] == DRM_FORMAT_NV12) {
|
||||
if (videoFormat & VIDEO_FORMAT_MASK_10BIT) {
|
||||
// 10-bit formats use NA12 (vendor-defined) or NV15 (upstreamed in 5.10+)
|
||||
if (ovr->formats[j] == DRM_FORMAT_NA12 || ovr->formats[j] == DRM_FORMAT_NV15) {
|
||||
break;
|
||||
}
|
||||
} else if (ovr->formats[j] == DRM_FORMAT_NV12) {
|
||||
// 8-bit formats always use NV12
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j == ovr->count_formats) {
|
||||
if (j < ovr->count_formats) {
|
||||
pixel_format = ovr->formats[j];
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if ((ovr->possible_crtcs & crtc_bit) && !ovr->crtc_id) {
|
||||
@ -337,37 +496,70 @@ int rk_setup(int videoFormat, int width, int height, int redrawRate, void* conte
|
||||
continue;
|
||||
}
|
||||
|
||||
for (j = 0; j < props->count_props && !plane_id; j++) {
|
||||
assert(props->count_props < sizeof(plane_props) / sizeof(plane_props[0]));
|
||||
for (j = 0; j < props->count_props; j++) {
|
||||
drmModePropertyPtr prop = drmModeGetProperty(fd, props->props[j]);
|
||||
if (!prop) {
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(prop->name, "type") && props->prop_values[j] == DRM_PLANE_TYPE_OVERLAY) {
|
||||
plane_props[j] = prop;
|
||||
if (!strcmp(prop->name, "type") && (props->prop_values[j] == DRM_PLANE_TYPE_OVERLAY ||
|
||||
props->prop_values[j] == DRM_PLANE_TYPE_CURSOR ||
|
||||
props->prop_values[j] == DRM_PLANE_TYPE_PRIMARY)) {
|
||||
plane_id = ovr->plane_id;
|
||||
}
|
||||
drmModeFreeProperty(prop);
|
||||
}
|
||||
drmModeFreeObjectProperties(props);
|
||||
if (plane_id) {
|
||||
break;
|
||||
drmModeFreeObjectProperties(props);
|
||||
break;
|
||||
} else {
|
||||
for (j = 0; j < props->count_props; j++) {
|
||||
drmModeFreeProperty(plane_props[j]);
|
||||
plane_props[j] = NULL;
|
||||
}
|
||||
drmModeFreeObjectProperties(props);
|
||||
}
|
||||
}
|
||||
drmModeFreePlane(ovr);
|
||||
}
|
||||
assert(plane_id);
|
||||
|
||||
if (!plane_id) {
|
||||
fprintf(stderr, "Unable to find suitable plane\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// DRM defines rotation in degrees counter-clockwise while we define
|
||||
// rotation in degrees clockwise, so we swap the 90 and 270 cases
|
||||
int displayRotation = drFlags & DISPLAY_ROTATE_MASK;
|
||||
switch (displayRotation) {
|
||||
case DISPLAY_ROTATE_90:
|
||||
set_property(plane_id, DRM_MODE_OBJECT_PLANE, plane_props, "rotation", DRM_MODE_ROTATE_270);
|
||||
break;
|
||||
case DISPLAY_ROTATE_180:
|
||||
set_property(plane_id, DRM_MODE_OBJECT_PLANE, plane_props, "rotation", DRM_MODE_ROTATE_180);
|
||||
break;
|
||||
case DISPLAY_ROTATE_270:
|
||||
set_property(plane_id, DRM_MODE_OBJECT_PLANE, plane_props, "rotation", DRM_MODE_ROTATE_90);
|
||||
break;
|
||||
default:
|
||||
set_property(plane_id, DRM_MODE_OBJECT_PLANE, plane_props, "rotation", DRM_MODE_ROTATE_0);
|
||||
break;
|
||||
}
|
||||
|
||||
// hide cursor by move in left lower corner
|
||||
drmModeMoveCursor(fd, crtc_id, 0, crtc_height);
|
||||
|
||||
// MPI SETUP
|
||||
|
||||
pkt_buf = malloc(READ_BUF_SIZE);
|
||||
assert(pkt_buf);
|
||||
ret = mpp_packet_init(&mpi_packet, pkt_buf, READ_BUF_SIZE);
|
||||
ensure_buf_size(&pkt_buf, &pkt_buf_size, INITIAL_DECODER_BUFFER_SIZE);
|
||||
ret = mpp_packet_init(&mpi_packet, pkt_buf, pkt_buf_size);
|
||||
assert(!ret);
|
||||
|
||||
ret = mpp_create(&mpi_ctx, &mpi_api);
|
||||
assert(!ret);
|
||||
if (ret) {
|
||||
fprintf(stderr, "mpp_create() failed: %d\n", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// decoder split mode (multi-data-input) need to be set before init
|
||||
int param = 1;
|
||||
@ -375,22 +567,21 @@ int rk_setup(int videoFormat, int width, int height, int redrawRate, void* conte
|
||||
assert(!ret);
|
||||
|
||||
ret = mpp_init(mpi_ctx, MPP_CTX_DEC, mpp_type);
|
||||
assert(!ret);
|
||||
if (ret) {
|
||||
fprintf(stderr, "mpp_init() failed: %d\n", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// set blocked read on Frame Thread
|
||||
param = MPP_POLL_BLOCK;
|
||||
ret = mpi_api->control(mpi_ctx, MPP_SET_OUTPUT_BLOCK, ¶m);
|
||||
assert(!ret);
|
||||
|
||||
ret = pthread_mutex_init(&mutex, NULL);
|
||||
assert(!ret);
|
||||
ret = pthread_cond_init(&cond, NULL);
|
||||
assert(!ret);
|
||||
pthread_mutex_init(&mutex, NULL);
|
||||
pthread_cond_init(&cond, NULL);
|
||||
|
||||
ret = pthread_create(&tid_frame, NULL, frame_thread, NULL);
|
||||
assert(!ret);
|
||||
ret = pthread_create(&tid_display, NULL, display_thread, NULL);
|
||||
assert(!ret);
|
||||
pthread_create(&tid_frame, NULL, frame_thread, NULL);
|
||||
pthread_create(&tid_display, NULL, display_thread, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -401,26 +592,19 @@ void rk_cleanup() {
|
||||
int ret;
|
||||
|
||||
frm_eos = 1;
|
||||
ret = pthread_mutex_lock(&mutex);
|
||||
assert(!ret);
|
||||
ret = pthread_cond_signal(&cond);
|
||||
assert(!ret);
|
||||
ret = pthread_mutex_unlock(&mutex);
|
||||
assert(!ret);
|
||||
pthread_mutex_lock(&mutex);
|
||||
pthread_cond_signal(&cond);
|
||||
pthread_mutex_unlock(&mutex);
|
||||
|
||||
ret = pthread_join(tid_display, NULL);
|
||||
assert(!ret);
|
||||
pthread_join(tid_display, NULL);
|
||||
|
||||
ret = pthread_cond_destroy(&cond);
|
||||
assert(!ret);
|
||||
ret = pthread_mutex_destroy(&mutex);
|
||||
assert(!ret);
|
||||
pthread_cond_destroy(&cond);
|
||||
pthread_mutex_destroy(&mutex);
|
||||
|
||||
ret = mpi_api->reset(mpi_ctx);
|
||||
assert(!ret);
|
||||
|
||||
ret = pthread_join(tid_frame, NULL);
|
||||
assert(!ret);
|
||||
pthread_join(tid_frame, NULL);
|
||||
|
||||
if (mpi_frm_grp) {
|
||||
ret = mpp_buffer_group_put(mpi_frm_grp);
|
||||
@ -428,13 +612,15 @@ void rk_cleanup() {
|
||||
mpi_frm_grp = NULL;
|
||||
for (i = 0; i < MAX_FRAMES; i++) {
|
||||
ret = drmModeRmFB(fd, frame_to_drm[i].fb_id);
|
||||
assert(!ret);
|
||||
if (ret) {
|
||||
perror("drmModeRmFB");
|
||||
}
|
||||
struct drm_mode_destroy_dumb dmdd = {0};
|
||||
dmdd.handle = frame_to_drm[i].handle;
|
||||
do {
|
||||
ret = ioctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dmdd);
|
||||
} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
|
||||
assert(!ret);
|
||||
ret = drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dmdd);
|
||||
if (ret) {
|
||||
perror("drmIoctl(DRM_IOCTL_MODE_DESTROY_DUMB)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -442,37 +628,130 @@ void rk_cleanup() {
|
||||
mpp_destroy(mpi_ctx);
|
||||
free(pkt_buf);
|
||||
|
||||
// Undo the connector-wide changes we performed
|
||||
if (atomic)
|
||||
drmModeAtomicSetCursor(drm_request, 0);
|
||||
set_property(conn_id, DRM_MODE_OBJECT_CONNECTOR, conn_props, "HDR_OUTPUT_METADATA", 0);
|
||||
set_property(conn_id, DRM_MODE_OBJECT_CONNECTOR, conn_props, "allm_enable", 0);
|
||||
set_property(conn_id, DRM_MODE_OBJECT_CONNECTOR, conn_props, "Colorspace", DRM_MODE_COLORIMETRY_DEFAULT);
|
||||
if (atomic)
|
||||
drmModeAtomicCommit(fd, drm_request, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
|
||||
|
||||
if (hdr_metadata_blob_id) {
|
||||
drmModeDestroyPropertyBlob(fd, hdr_metadata_blob_id);
|
||||
hdr_metadata_blob_id = 0;
|
||||
}
|
||||
|
||||
if (atomic)
|
||||
drmModeAtomicFree(drm_request);
|
||||
drmModeFreePlane(ovr);
|
||||
drmModeFreePlaneResources(plane_resources);
|
||||
drmModeFreeEncoder(encoder);
|
||||
drmModeFreeConnector(connector);
|
||||
drmModeFreeCrtc(crtc);
|
||||
drmModeFreeResources(resources);
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
int rk_submit_decode_unit(PDECODE_UNIT decodeUnit) {
|
||||
|
||||
int result = DR_OK;
|
||||
PLENTRY entry = decodeUnit->bufferList;
|
||||
int length = 0;
|
||||
|
||||
if (decodeUnit->fullLength < READ_BUF_SIZE) {
|
||||
PLENTRY entry = decodeUnit->bufferList;
|
||||
int length = 0;
|
||||
while (entry != NULL) {
|
||||
memcpy(pkt_buf+length, entry->data, entry->length);
|
||||
length += entry->length;
|
||||
entry = entry->next;
|
||||
}
|
||||
if (length) {
|
||||
mpp_packet_set_pos(mpi_packet, pkt_buf);
|
||||
mpp_packet_set_length(mpi_packet, length);
|
||||
|
||||
while (MPP_OK != mpi_api->decode_put_packet(mpi_ctx, mpi_packet))
|
||||
;
|
||||
|
||||
}
|
||||
if (ensure_buf_size(&pkt_buf, &pkt_buf_size, decodeUnit->fullLength)) {
|
||||
// Buffer was reallocated, so update the mpp_packet accordingly
|
||||
mpp_packet_set_data(mpi_packet, pkt_buf);
|
||||
mpp_packet_set_size(mpi_packet, pkt_buf_size);
|
||||
}
|
||||
|
||||
while (entry != NULL) {
|
||||
memcpy(pkt_buf+length, entry->data, entry->length);
|
||||
length += entry->length;
|
||||
entry = entry->next;
|
||||
}
|
||||
|
||||
mpp_packet_set_pos(mpi_packet, pkt_buf);
|
||||
mpp_packet_set_length(mpi_packet, length);
|
||||
|
||||
if (last_hdr_state != decodeUnit->hdrActive) {
|
||||
if (hdr_metadata_prop != NULL) {
|
||||
int err;
|
||||
|
||||
if (hdr_metadata_blob_id) {
|
||||
drmModeDestroyPropertyBlob(fd, hdr_metadata_blob_id);
|
||||
hdr_metadata_blob_id = 0;
|
||||
}
|
||||
|
||||
if (decodeUnit->hdrActive) {
|
||||
struct rk_hdr_output_metadata outputMetadata;
|
||||
SS_HDR_METADATA sunshineHdrMetadata;
|
||||
|
||||
// Sunshine will have HDR metadata but GFE will not
|
||||
if (!LiGetHdrMetadata(&sunshineHdrMetadata)) {
|
||||
memset(&sunshineHdrMetadata, 0, sizeof(sunshineHdrMetadata));
|
||||
}
|
||||
|
||||
outputMetadata.metadata_type = 0; // HDMI_STATIC_METADATA_TYPE1
|
||||
outputMetadata.hdmi_metadata_type1.eotf = 2; // SMPTE ST 2084
|
||||
outputMetadata.hdmi_metadata_type1.metadata_type = 0; // Static Metadata Type 1
|
||||
for (int i = 0; i < 3; i++) {
|
||||
outputMetadata.hdmi_metadata_type1.display_primaries[i].x = sunshineHdrMetadata.displayPrimaries[i].x;
|
||||
outputMetadata.hdmi_metadata_type1.display_primaries[i].y = sunshineHdrMetadata.displayPrimaries[i].y;
|
||||
}
|
||||
outputMetadata.hdmi_metadata_type1.white_point.x = sunshineHdrMetadata.whitePoint.x;
|
||||
outputMetadata.hdmi_metadata_type1.white_point.y = sunshineHdrMetadata.whitePoint.y;
|
||||
outputMetadata.hdmi_metadata_type1.max_display_mastering_luminance = sunshineHdrMetadata.maxDisplayLuminance;
|
||||
outputMetadata.hdmi_metadata_type1.min_display_mastering_luminance = sunshineHdrMetadata.minDisplayLuminance;
|
||||
outputMetadata.hdmi_metadata_type1.max_cll = sunshineHdrMetadata.maxContentLightLevel;
|
||||
outputMetadata.hdmi_metadata_type1.max_fall = sunshineHdrMetadata.maxFrameAverageLightLevel;
|
||||
|
||||
err = drmModeCreatePropertyBlob(fd, &outputMetadata, sizeof(outputMetadata), &hdr_metadata_blob_id);
|
||||
if (err < 0) {
|
||||
hdr_metadata_blob_id = 0;
|
||||
fprintf(stderr, "Failed to create HDR metadata blob: %d\n", errno);
|
||||
}
|
||||
}
|
||||
|
||||
err = drmModeObjectSetProperty(fd, conn_id, DRM_MODE_OBJECT_CONNECTOR, hdr_metadata_prop->prop_id, hdr_metadata_blob_id);
|
||||
if (err < 0) {
|
||||
fprintf(stderr, "Failed to set HDR metadata: %d\n", errno);
|
||||
} else {
|
||||
printf("Set display HDR mode: %s\n", decodeUnit->hdrActive ? "active" : "inactive");
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "HDR_OUTPUT_METADATA property is not supported by your display/kernel. Do you have an HDR display connected?\n");
|
||||
}
|
||||
|
||||
// Adjust plane EOTF property
|
||||
set_property(plane_id, DRM_MODE_OBJECT_PLANE, plane_props, "EOTF", decodeUnit->hdrActive ? 2 : 0); // PQ or SDR
|
||||
}
|
||||
|
||||
if (last_colorspace != decodeUnit->colorspace) {
|
||||
uint32_t v4l2_colorspace;
|
||||
switch (decodeUnit->colorspace) {
|
||||
default:
|
||||
fprintf(stderr, "Unknown frame colorspace: %d\n", decodeUnit->colorspace);
|
||||
/* fall-through */
|
||||
case COLORSPACE_REC_601:
|
||||
v4l2_colorspace = V4L2_COLORSPACE_SMPTE170M;
|
||||
break;
|
||||
case COLORSPACE_REC_709:
|
||||
v4l2_colorspace = V4L2_COLORSPACE_REC709;
|
||||
break;
|
||||
case COLORSPACE_REC_2020:
|
||||
v4l2_colorspace = V4L2_COLORSPACE_BT2020;
|
||||
break;
|
||||
}
|
||||
set_property(plane_id, DRM_MODE_OBJECT_PLANE, plane_props, "COLOR_SPACE", v4l2_colorspace);
|
||||
}
|
||||
|
||||
last_colorspace = decodeUnit->colorspace;
|
||||
last_hdr_state = decodeUnit->hdrActive;
|
||||
|
||||
while (MPP_OK != mpi_api->decode_put_packet(mpi_ctx, mpi_packet));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "ffmpeg.h"
|
||||
|
||||
#include "../sdl.h"
|
||||
#include "../util.h"
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL_thread.h>
|
||||
@ -28,24 +29,18 @@
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define DECODER_BUFFER_SIZE 92*1024
|
||||
#define SLICES_PER_FRAME 4
|
||||
|
||||
static char* ffmpeg_buffer;
|
||||
static void* ffmpeg_buffer;
|
||||
static size_t ffmpeg_buffer_size;
|
||||
|
||||
static int sdl_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
int avc_flags = SLICE_THREADING;
|
||||
|
||||
if (ffmpeg_init(videoFormat, width, height, avc_flags, SDL_BUFFER_FRAMES, sysconf(_SC_NPROCESSORS_ONLN)) < 0) {
|
||||
if (ffmpeg_init(videoFormat, width, height, SLICE_THREADING, SDL_BUFFER_FRAMES, SLICES_PER_FRAME) < 0) {
|
||||
fprintf(stderr, "Couldn't initialize video decoding\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ffmpeg_buffer = malloc(DECODER_BUFFER_SIZE + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (ffmpeg_buffer == NULL) {
|
||||
fprintf(stderr, "Not enough memory\n");
|
||||
ffmpeg_destroy();
|
||||
return -1;
|
||||
}
|
||||
|
||||
ensure_buf_size(&ffmpeg_buffer, &ffmpeg_buffer_size, INITIAL_DECODER_BUFFER_SIZE + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -55,36 +50,31 @@ static void sdl_cleanup() {
|
||||
}
|
||||
|
||||
static int sdl_submit_decode_unit(PDECODE_UNIT decodeUnit) {
|
||||
if (decodeUnit->fullLength < DECODER_BUFFER_SIZE) {
|
||||
PLENTRY entry = decodeUnit->bufferList;
|
||||
int length = 0;
|
||||
while (entry != NULL) {
|
||||
memcpy(ffmpeg_buffer+length, entry->data, entry->length);
|
||||
length += entry->length;
|
||||
entry = entry->next;
|
||||
}
|
||||
ffmpeg_decode(ffmpeg_buffer, length);
|
||||
PLENTRY entry = decodeUnit->bufferList;
|
||||
int length = 0;
|
||||
|
||||
if (SDL_LockMutex(mutex) == 0) {
|
||||
AVFrame* frame = ffmpeg_get_frame(false);
|
||||
if (frame != NULL) {
|
||||
sdlNextFrame++;
|
||||
ensure_buf_size(&ffmpeg_buffer, &ffmpeg_buffer_size, decodeUnit->fullLength + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
|
||||
SDL_Event event;
|
||||
event.type = SDL_USEREVENT;
|
||||
event.user.code = SDL_CODE_FRAME;
|
||||
event.user.data1 = &frame->data;
|
||||
event.user.data2 = &frame->linesize;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
|
||||
SDL_UnlockMutex(mutex);
|
||||
} else
|
||||
fprintf(stderr, "Couldn't lock mutex\n");
|
||||
} else {
|
||||
fprintf(stderr, "Video decode buffer too small");
|
||||
exit(1);
|
||||
while (entry != NULL) {
|
||||
memcpy(ffmpeg_buffer+length, entry->data, entry->length);
|
||||
length += entry->length;
|
||||
entry = entry->next;
|
||||
}
|
||||
ffmpeg_decode(ffmpeg_buffer, length);
|
||||
|
||||
SDL_LockMutex(mutex);
|
||||
AVFrame* frame = ffmpeg_get_frame(false);
|
||||
if (frame != NULL) {
|
||||
sdlNextFrame++;
|
||||
|
||||
SDL_Event event;
|
||||
event.type = SDL_USEREVENT;
|
||||
event.user.code = SDL_CODE_FRAME;
|
||||
event.user.data1 = &frame->data;
|
||||
event.user.data2 = &frame->linesize;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
SDL_UnlockMutex(mutex);
|
||||
|
||||
return DR_OK;
|
||||
}
|
||||
@ -93,5 +83,5 @@ DECODER_RENDERER_CALLBACKS decoder_callbacks_sdl = {
|
||||
.setup = sdl_setup,
|
||||
.cleanup = sdl_cleanup,
|
||||
.submitDecodeUnit = sdl_submit_decode_unit,
|
||||
.capabilities = CAPABILITY_SLICES_PER_FRAME(4) | CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC | CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC | CAPABILITY_DIRECT_SUBMIT,
|
||||
.capabilities = CAPABILITY_SLICES_PER_FRAME(SLICES_PER_FRAME) | CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC | CAPABILITY_DIRECT_SUBMIT,
|
||||
};
|
||||
|
@ -24,11 +24,17 @@
|
||||
#define DISPLAY_FULLSCREEN 1
|
||||
#define ENABLE_HARDWARE_ACCELERATION_1 2
|
||||
#define ENABLE_HARDWARE_ACCELERATION_2 4
|
||||
#define DISPLAY_ROTATE_MASK 24
|
||||
#define DISPLAY_ROTATE_90 8
|
||||
#define DISPLAY_ROTATE_180 16
|
||||
#define DISPLAY_ROTATE_270 24
|
||||
|
||||
#define INIT_EGL 1
|
||||
#define INIT_VDPAU 2
|
||||
#define INIT_VAAPI 3
|
||||
|
||||
#define INITIAL_DECODER_BUFFER_SIZE (256*1024)
|
||||
|
||||
#ifdef HAVE_X11
|
||||
int x11_init(bool vdpau, bool vaapi);
|
||||
extern DECODER_RENDERER_CALLBACKS decoder_callbacks_x11;
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
#include "../input/x11.h"
|
||||
#include "../loop.h"
|
||||
#include "../util.h"
|
||||
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xutil.h>
|
||||
@ -37,11 +38,12 @@
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
|
||||
#define DECODER_BUFFER_SIZE 92*1024
|
||||
#define X11_VDPAU_ACCELERATION ENABLE_HARDWARE_ACCELERATION_1
|
||||
#define X11_VAAPI_ACCELERATION ENABLE_HARDWARE_ACCELERATION_2
|
||||
#define SLICES_PER_FRAME 4
|
||||
|
||||
static char* ffmpeg_buffer = NULL;
|
||||
static void* ffmpeg_buffer = NULL;
|
||||
static size_t ffmpeg_buffer_size = 0;
|
||||
|
||||
static Display *display = NULL;
|
||||
static Window window;
|
||||
@ -57,7 +59,7 @@ static int frame_handle(int pipefd) {
|
||||
if (frame) {
|
||||
if (ffmpeg_decoder == SOFTWARE)
|
||||
egl_draw(frame->data);
|
||||
#ifdef HAVE_VAAPI
|
||||
#ifdef HAVE_VAAPI
|
||||
else if (ffmpeg_decoder == VAAPI)
|
||||
vaapi_queue(frame, window, display_width, display_height);
|
||||
#endif
|
||||
@ -81,11 +83,7 @@ int x11_init(bool vdpau, bool vaapi) {
|
||||
}
|
||||
|
||||
int x11_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
ffmpeg_buffer = malloc(DECODER_BUFFER_SIZE + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (ffmpeg_buffer == NULL) {
|
||||
fprintf(stderr, "Not enough memory\n");
|
||||
return -1;
|
||||
}
|
||||
ensure_buf_size(&ffmpeg_buffer, &ffmpeg_buffer_size, INITIAL_DECODER_BUFFER_SIZE + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
|
||||
if (!display) {
|
||||
fprintf(stderr, "Error: failed to open X display.\n");
|
||||
@ -124,13 +122,15 @@ int x11_setup(int videoFormat, int width, int height, int redrawRate, void* cont
|
||||
}
|
||||
XFlush(display);
|
||||
|
||||
int avc_flags = SLICE_THREADING;
|
||||
int avc_flags;
|
||||
if (drFlags & X11_VDPAU_ACCELERATION)
|
||||
avc_flags |= VDPAU_ACCELERATION;
|
||||
avc_flags = VDPAU_ACCELERATION;
|
||||
else if (drFlags & X11_VAAPI_ACCELERATION)
|
||||
avc_flags |= VAAPI_ACCELERATION;
|
||||
avc_flags = VAAPI_ACCELERATION;
|
||||
else
|
||||
avc_flags = SLICE_THREADING;
|
||||
|
||||
if (ffmpeg_init(videoFormat, width, height, avc_flags, 2, 2) < 0) {
|
||||
if (ffmpeg_init(videoFormat, width, height, avc_flags, 2, SLICES_PER_FRAME) < 0) {
|
||||
fprintf(stderr, "Couldn't initialize video decoding\n");
|
||||
return -1;
|
||||
}
|
||||
@ -164,20 +164,23 @@ void x11_cleanup() {
|
||||
}
|
||||
|
||||
int x11_submit_decode_unit(PDECODE_UNIT decodeUnit) {
|
||||
if (decodeUnit->fullLength < DECODER_BUFFER_SIZE) {
|
||||
PLENTRY entry = decodeUnit->bufferList;
|
||||
int length = 0;
|
||||
while (entry != NULL) {
|
||||
memcpy(ffmpeg_buffer+length, entry->data, entry->length);
|
||||
length += entry->length;
|
||||
entry = entry->next;
|
||||
}
|
||||
ffmpeg_decode(ffmpeg_buffer, length);
|
||||
AVFrame* frame = ffmpeg_get_frame(true);
|
||||
if (frame != NULL)
|
||||
write(pipefd[1], &frame, sizeof(void*));
|
||||
PLENTRY entry = decodeUnit->bufferList;
|
||||
int length = 0;
|
||||
|
||||
ensure_buf_size(&ffmpeg_buffer, &ffmpeg_buffer_size, decodeUnit->fullLength + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
|
||||
while (entry != NULL) {
|
||||
memcpy(ffmpeg_buffer+length, entry->data, entry->length);
|
||||
length += entry->length;
|
||||
entry = entry->next;
|
||||
}
|
||||
|
||||
ffmpeg_decode(ffmpeg_buffer, length);
|
||||
|
||||
AVFrame* frame = ffmpeg_get_frame(true);
|
||||
if (frame != NULL)
|
||||
write(pipefd[1], &frame, sizeof(void*));
|
||||
|
||||
return DR_OK;
|
||||
}
|
||||
|
||||
@ -185,7 +188,7 @@ DECODER_RENDERER_CALLBACKS decoder_callbacks_x11 = {
|
||||
.setup = x11_setup,
|
||||
.cleanup = x11_cleanup,
|
||||
.submitDecodeUnit = x11_submit_decode_unit,
|
||||
.capabilities = CAPABILITY_SLICES_PER_FRAME(4) | CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC | CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC | CAPABILITY_DIRECT_SUBMIT,
|
||||
.capabilities = CAPABILITY_SLICES_PER_FRAME(SLICES_PER_FRAME) | CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC | CAPABILITY_DIRECT_SUBMIT,
|
||||
};
|
||||
|
||||
DECODER_RENDERER_CALLBACKS decoder_callbacks_x11_vdpau = {
|
||||
@ -199,5 +202,5 @@ DECODER_RENDERER_CALLBACKS decoder_callbacks_x11_vaapi = {
|
||||
.setup = x11_setup_vaapi,
|
||||
.cleanup = x11_cleanup,
|
||||
.submitDecodeUnit = x11_submit_decode_unit,
|
||||
.capabilities = CAPABILITY_SLICES_PER_FRAME(4) | CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC | CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC | CAPABILITY_DIRECT_SUBMIT,
|
||||
.capabilities = CAPABILITY_DIRECT_SUBMIT,
|
||||
};
|
||||
|
1
third_party/SDL_GameControllerDB
vendored
Submodule
1
third_party/SDL_GameControllerDB
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit ae51c9942f0d985b2161d6b22986f041fc98ce5c
|
896
third_party/h264bitstream/h264_stream.c
vendored
896
third_party/h264bitstream/h264_stream.c
vendored
@ -24,16 +24,11 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "bs.h"
|
||||
#include "h264_stream.h"
|
||||
#include "h264_sei.h"
|
||||
|
||||
FILE* h264_dbgfile = NULL;
|
||||
|
||||
#define printf(...) fprintf((h264_dbgfile == NULL ? stdout : h264_dbgfile), __VA_ARGS__)
|
||||
|
||||
/**
|
||||
Calculate the log base 2 of the argument, rounded up.
|
||||
Zero or negative arguments return zero
|
||||
@ -554,7 +549,7 @@ void read_pic_parameter_set_rbsp(h264_stream_t* h, bs_t* b)
|
||||
if( 1 ) { have_more_data = more_rbsp_data(h, b); }
|
||||
if( 0 )
|
||||
{
|
||||
have_more_data = pps->transform_8x8_mode_flag | pps->pic_scaling_matrix_present_flag | pps->second_chroma_qp_index_offset != 0;
|
||||
have_more_data = pps->transform_8x8_mode_flag | pps->pic_scaling_matrix_present_flag | (pps->second_chroma_qp_index_offset != 0);
|
||||
}
|
||||
|
||||
if( have_more_data )
|
||||
@ -1441,7 +1436,7 @@ void write_pic_parameter_set_rbsp(h264_stream_t* h, bs_t* b)
|
||||
if( 0 ) { have_more_data = more_rbsp_data(h, b); }
|
||||
if( 1 )
|
||||
{
|
||||
have_more_data = pps->transform_8x8_mode_flag | pps->pic_scaling_matrix_present_flag | pps->second_chroma_qp_index_offset != 0;
|
||||
have_more_data = pps->transform_8x8_mode_flag | pps->pic_scaling_matrix_present_flag | (pps->second_chroma_qp_index_offset != 0);
|
||||
}
|
||||
|
||||
if( have_more_data )
|
||||
@ -1899,890 +1894,3 @@ void write_dec_ref_pic_marking(h264_stream_t* h, bs_t* b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void read_debug_seq_parameter_set_rbsp(h264_stream_t* h, bs_t* b);
|
||||
void read_debug_scaling_list(bs_t* b, int* scalingList, int sizeOfScalingList, int* useDefaultScalingMatrixFlag );
|
||||
void read_debug_vui_parameters(h264_stream_t* h, bs_t* b);
|
||||
void read_debug_hrd_parameters(h264_stream_t* h, bs_t* b);
|
||||
void read_debug_pic_parameter_set_rbsp(h264_stream_t* h, bs_t* b);
|
||||
void read_debug_sei_rbsp(h264_stream_t* h, bs_t* b);
|
||||
void read_debug_sei_message(h264_stream_t* h, bs_t* b);
|
||||
void read_debug_access_unit_delimiter_rbsp(h264_stream_t* h, bs_t* b);
|
||||
void read_debug_end_of_seq_rbsp(h264_stream_t* h, bs_t* b);
|
||||
void read_debug_end_of_stream_rbsp(h264_stream_t* h, bs_t* b);
|
||||
void read_debug_filler_data_rbsp(h264_stream_t* h, bs_t* b);
|
||||
void read_debug_slice_layer_rbsp(h264_stream_t* h, bs_t* b);
|
||||
void read_debug_rbsp_slice_trailing_bits(h264_stream_t* h, bs_t* b);
|
||||
void read_debug_rbsp_trailing_bits(h264_stream_t* h, bs_t* b);
|
||||
void read_debug_slice_header(h264_stream_t* h, bs_t* b);
|
||||
void read_debug_ref_pic_list_reordering(h264_stream_t* h, bs_t* b);
|
||||
void read_debug_pred_weight_table(h264_stream_t* h, bs_t* b);
|
||||
void read_debug_dec_ref_pic_marking(h264_stream_t* h, bs_t* b);
|
||||
|
||||
|
||||
|
||||
//7.3.1 NAL unit syntax
|
||||
int read_debug_nal_unit(h264_stream_t* h, uint8_t* buf, int size)
|
||||
{
|
||||
nal_t* nal = h->nal;
|
||||
|
||||
int nal_size = size;
|
||||
int rbsp_size = size;
|
||||
uint8_t* rbsp_buf = (uint8_t*)calloc(1, rbsp_size);
|
||||
|
||||
if( 1 )
|
||||
{
|
||||
int rc = nal_to_rbsp(buf, &nal_size, rbsp_buf, &rbsp_size);
|
||||
|
||||
if (rc < 0) { free(rbsp_buf); return -1; } // handle conversion error
|
||||
}
|
||||
|
||||
if( 0 )
|
||||
{
|
||||
rbsp_size = size*3/4; // NOTE this may have to be slightly smaller (3/4 smaller, worst case) in order to be guaranteed to fit
|
||||
}
|
||||
|
||||
bs_t* b = bs_new(rbsp_buf, rbsp_size);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); int forbidden_zero_bit = bs_read_u(b, 1); printf("forbidden_zero_bit: %d \n", forbidden_zero_bit);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); nal->nal_ref_idc = bs_read_u(b, 2); printf("nal->nal_ref_idc: %d \n", nal->nal_ref_idc);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); nal->nal_unit_type = bs_read_u(b, 5); printf("nal->nal_unit_type: %d \n", nal->nal_unit_type);
|
||||
|
||||
switch ( nal->nal_unit_type )
|
||||
{
|
||||
case NAL_UNIT_TYPE_CODED_SLICE_IDR:
|
||||
case NAL_UNIT_TYPE_CODED_SLICE_NON_IDR:
|
||||
case NAL_UNIT_TYPE_CODED_SLICE_AUX:
|
||||
read_debug_slice_layer_rbsp(h, b);
|
||||
break;
|
||||
|
||||
#ifdef HAVE_SEI
|
||||
case NAL_UNIT_TYPE_SEI:
|
||||
read_debug_sei_rbsp(h, b);
|
||||
break;
|
||||
#endif
|
||||
|
||||
case NAL_UNIT_TYPE_SPS:
|
||||
read_debug_seq_parameter_set_rbsp(h, b);
|
||||
break;
|
||||
|
||||
case NAL_UNIT_TYPE_PPS:
|
||||
read_debug_pic_parameter_set_rbsp(h, b);
|
||||
break;
|
||||
|
||||
case NAL_UNIT_TYPE_AUD:
|
||||
read_debug_access_unit_delimiter_rbsp(h, b);
|
||||
break;
|
||||
|
||||
case NAL_UNIT_TYPE_END_OF_SEQUENCE:
|
||||
read_debug_end_of_seq_rbsp(h, b);
|
||||
break;
|
||||
|
||||
case NAL_UNIT_TYPE_END_OF_STREAM:
|
||||
read_debug_end_of_stream_rbsp(h, b);
|
||||
break;
|
||||
|
||||
case NAL_UNIT_TYPE_FILLER:
|
||||
case NAL_UNIT_TYPE_SPS_EXT:
|
||||
case NAL_UNIT_TYPE_UNSPECIFIED:
|
||||
case NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_A:
|
||||
case NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_B:
|
||||
case NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_C:
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (bs_overrun(b)) { bs_free(b); free(rbsp_buf); return -1; }
|
||||
|
||||
if( 0 )
|
||||
{
|
||||
// now get the actual size used
|
||||
rbsp_size = bs_pos(b);
|
||||
|
||||
int rc = rbsp_to_nal(rbsp_buf, &rbsp_size, buf, &nal_size);
|
||||
if (rc < 0) { bs_free(b); free(rbsp_buf); return -1; }
|
||||
}
|
||||
|
||||
bs_free(b);
|
||||
free(rbsp_buf);
|
||||
|
||||
return nal_size;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//7.3.2.1 Sequence parameter set RBSP syntax
|
||||
void read_debug_seq_parameter_set_rbsp(h264_stream_t* h, bs_t* b)
|
||||
{
|
||||
int i;
|
||||
|
||||
sps_t* sps = h->sps;
|
||||
if( 1 )
|
||||
{
|
||||
memset(sps, 0, sizeof(sps_t));
|
||||
sps->chroma_format_idc = 1;
|
||||
}
|
||||
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->profile_idc = bs_read_u8(b); printf("sps->profile_idc: %d \n", sps->profile_idc);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->constraint_set0_flag = bs_read_u1(b); printf("sps->constraint_set0_flag: %d \n", sps->constraint_set0_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->constraint_set1_flag = bs_read_u1(b); printf("sps->constraint_set1_flag: %d \n", sps->constraint_set1_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->constraint_set2_flag = bs_read_u1(b); printf("sps->constraint_set2_flag: %d \n", sps->constraint_set2_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->constraint_set3_flag = bs_read_u1(b); printf("sps->constraint_set3_flag: %d \n", sps->constraint_set3_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->constraint_set4_flag = bs_read_u1(b); printf("sps->constraint_set4_flag: %d \n", sps->constraint_set4_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->constraint_set5_flag = bs_read_u1(b); printf("sps->constraint_set5_flag: %d \n", sps->constraint_set5_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); int reserved_zero_2bits = bs_read_u(b, 2); printf("reserved_zero_2bits: %d \n", reserved_zero_2bits);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->level_idc = bs_read_u8(b); printf("sps->level_idc: %d \n", sps->level_idc);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->seq_parameter_set_id = bs_read_ue(b); printf("sps->seq_parameter_set_id: %d \n", sps->seq_parameter_set_id);
|
||||
|
||||
if( sps->profile_idc == 100 || sps->profile_idc == 110 ||
|
||||
sps->profile_idc == 122 || sps->profile_idc == 144 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->chroma_format_idc = bs_read_ue(b); printf("sps->chroma_format_idc: %d \n", sps->chroma_format_idc);
|
||||
if( sps->chroma_format_idc == 3 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->residual_colour_transform_flag = bs_read_u1(b); printf("sps->residual_colour_transform_flag: %d \n", sps->residual_colour_transform_flag);
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->bit_depth_luma_minus8 = bs_read_ue(b); printf("sps->bit_depth_luma_minus8: %d \n", sps->bit_depth_luma_minus8);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->bit_depth_chroma_minus8 = bs_read_ue(b); printf("sps->bit_depth_chroma_minus8: %d \n", sps->bit_depth_chroma_minus8);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->qpprime_y_zero_transform_bypass_flag = bs_read_u1(b); printf("sps->qpprime_y_zero_transform_bypass_flag: %d \n", sps->qpprime_y_zero_transform_bypass_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->seq_scaling_matrix_present_flag = bs_read_u1(b); printf("sps->seq_scaling_matrix_present_flag: %d \n", sps->seq_scaling_matrix_present_flag);
|
||||
if( sps->seq_scaling_matrix_present_flag )
|
||||
{
|
||||
for( i = 0; i < 8; i++ )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->seq_scaling_list_present_flag[ i ] = bs_read_u1(b); printf("sps->seq_scaling_list_present_flag[ i ]: %d \n", sps->seq_scaling_list_present_flag[ i ]);
|
||||
if( sps->seq_scaling_list_present_flag[ i ] )
|
||||
{
|
||||
if( i < 6 )
|
||||
{
|
||||
read_debug_scaling_list( b, sps->ScalingList4x4[ i ], 16,
|
||||
&( sps->UseDefaultScalingMatrix4x4Flag[ i ] ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
read_debug_scaling_list( b, sps->ScalingList8x8[ i - 6 ], 64,
|
||||
&( sps->UseDefaultScalingMatrix8x8Flag[ i - 6 ] ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->log2_max_frame_num_minus4 = bs_read_ue(b); printf("sps->log2_max_frame_num_minus4: %d \n", sps->log2_max_frame_num_minus4);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->pic_order_cnt_type = bs_read_ue(b); printf("sps->pic_order_cnt_type: %d \n", sps->pic_order_cnt_type);
|
||||
if( sps->pic_order_cnt_type == 0 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->log2_max_pic_order_cnt_lsb_minus4 = bs_read_ue(b); printf("sps->log2_max_pic_order_cnt_lsb_minus4: %d \n", sps->log2_max_pic_order_cnt_lsb_minus4);
|
||||
}
|
||||
else if( sps->pic_order_cnt_type == 1 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->delta_pic_order_always_zero_flag = bs_read_u1(b); printf("sps->delta_pic_order_always_zero_flag: %d \n", sps->delta_pic_order_always_zero_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->offset_for_non_ref_pic = bs_read_se(b); printf("sps->offset_for_non_ref_pic: %d \n", sps->offset_for_non_ref_pic);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->offset_for_top_to_bottom_field = bs_read_se(b); printf("sps->offset_for_top_to_bottom_field: %d \n", sps->offset_for_top_to_bottom_field);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->num_ref_frames_in_pic_order_cnt_cycle = bs_read_ue(b); printf("sps->num_ref_frames_in_pic_order_cnt_cycle: %d \n", sps->num_ref_frames_in_pic_order_cnt_cycle);
|
||||
for( i = 0; i < sps->num_ref_frames_in_pic_order_cnt_cycle; i++ )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->offset_for_ref_frame[ i ] = bs_read_se(b); printf("sps->offset_for_ref_frame[ i ]: %d \n", sps->offset_for_ref_frame[ i ]);
|
||||
}
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->num_ref_frames = bs_read_ue(b); printf("sps->num_ref_frames: %d \n", sps->num_ref_frames);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->gaps_in_frame_num_value_allowed_flag = bs_read_u1(b); printf("sps->gaps_in_frame_num_value_allowed_flag: %d \n", sps->gaps_in_frame_num_value_allowed_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->pic_width_in_mbs_minus1 = bs_read_ue(b); printf("sps->pic_width_in_mbs_minus1: %d \n", sps->pic_width_in_mbs_minus1);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->pic_height_in_map_units_minus1 = bs_read_ue(b); printf("sps->pic_height_in_map_units_minus1: %d \n", sps->pic_height_in_map_units_minus1);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->frame_mbs_only_flag = bs_read_u1(b); printf("sps->frame_mbs_only_flag: %d \n", sps->frame_mbs_only_flag);
|
||||
if( !sps->frame_mbs_only_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->mb_adaptive_frame_field_flag = bs_read_u1(b); printf("sps->mb_adaptive_frame_field_flag: %d \n", sps->mb_adaptive_frame_field_flag);
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->direct_8x8_inference_flag = bs_read_u1(b); printf("sps->direct_8x8_inference_flag: %d \n", sps->direct_8x8_inference_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->frame_cropping_flag = bs_read_u1(b); printf("sps->frame_cropping_flag: %d \n", sps->frame_cropping_flag);
|
||||
if( sps->frame_cropping_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->frame_crop_left_offset = bs_read_ue(b); printf("sps->frame_crop_left_offset: %d \n", sps->frame_crop_left_offset);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->frame_crop_right_offset = bs_read_ue(b); printf("sps->frame_crop_right_offset: %d \n", sps->frame_crop_right_offset);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->frame_crop_top_offset = bs_read_ue(b); printf("sps->frame_crop_top_offset: %d \n", sps->frame_crop_top_offset);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->frame_crop_bottom_offset = bs_read_ue(b); printf("sps->frame_crop_bottom_offset: %d \n", sps->frame_crop_bottom_offset);
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui_parameters_present_flag = bs_read_u1(b); printf("sps->vui_parameters_present_flag: %d \n", sps->vui_parameters_present_flag);
|
||||
if( sps->vui_parameters_present_flag )
|
||||
{
|
||||
read_debug_vui_parameters(h, b);
|
||||
}
|
||||
read_debug_rbsp_trailing_bits(h, b);
|
||||
|
||||
if( 1 )
|
||||
{
|
||||
memcpy(h->sps_table[sps->seq_parameter_set_id], h->sps, sizeof(sps_t));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//7.3.2.1.1 Scaling list syntax
|
||||
void read_debug_scaling_list(bs_t* b, int* scalingList, int sizeOfScalingList, int* useDefaultScalingMatrixFlag )
|
||||
{
|
||||
// NOTE need to be able to set useDefaultScalingMatrixFlag when reading, hence passing as pointer
|
||||
int lastScale = 8;
|
||||
int nextScale = 8;
|
||||
int delta_scale;
|
||||
for( int j = 0; j < sizeOfScalingList; j++ )
|
||||
{
|
||||
if( nextScale != 0 )
|
||||
{
|
||||
if( 0 )
|
||||
{
|
||||
nextScale = scalingList[ j ];
|
||||
if (useDefaultScalingMatrixFlag[0]) { nextScale = 0; }
|
||||
delta_scale = (nextScale - lastScale) % 256 ;
|
||||
}
|
||||
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); delta_scale = bs_read_se(b); printf("delta_scale: %d \n", delta_scale);
|
||||
|
||||
if( 1 )
|
||||
{
|
||||
nextScale = ( lastScale + delta_scale + 256 ) % 256;
|
||||
useDefaultScalingMatrixFlag[0] = ( j == 0 && nextScale == 0 );
|
||||
}
|
||||
}
|
||||
if( 1 )
|
||||
{
|
||||
scalingList[ j ] = ( nextScale == 0 ) ? lastScale : nextScale;
|
||||
}
|
||||
lastScale = scalingList[ j ];
|
||||
}
|
||||
}
|
||||
|
||||
//Appendix E.1.1 VUI parameters syntax
|
||||
void read_debug_vui_parameters(h264_stream_t* h, bs_t* b)
|
||||
{
|
||||
sps_t* sps = h->sps;
|
||||
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.aspect_ratio_info_present_flag = bs_read_u1(b); printf("sps->vui.aspect_ratio_info_present_flag: %d \n", sps->vui.aspect_ratio_info_present_flag);
|
||||
if( sps->vui.aspect_ratio_info_present_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.aspect_ratio_idc = bs_read_u8(b); printf("sps->vui.aspect_ratio_idc: %d \n", sps->vui.aspect_ratio_idc);
|
||||
if( sps->vui.aspect_ratio_idc == SAR_Extended )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.sar_width = bs_read_u(b, 16); printf("sps->vui.sar_width: %d \n", sps->vui.sar_width);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.sar_height = bs_read_u(b, 16); printf("sps->vui.sar_height: %d \n", sps->vui.sar_height);
|
||||
}
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.overscan_info_present_flag = bs_read_u1(b); printf("sps->vui.overscan_info_present_flag: %d \n", sps->vui.overscan_info_present_flag);
|
||||
if( sps->vui.overscan_info_present_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.overscan_appropriate_flag = bs_read_u1(b); printf("sps->vui.overscan_appropriate_flag: %d \n", sps->vui.overscan_appropriate_flag);
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.video_signal_type_present_flag = bs_read_u1(b); printf("sps->vui.video_signal_type_present_flag: %d \n", sps->vui.video_signal_type_present_flag);
|
||||
if( sps->vui.video_signal_type_present_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.video_format = bs_read_u(b, 3); printf("sps->vui.video_format: %d \n", sps->vui.video_format);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.video_full_range_flag = bs_read_u1(b); printf("sps->vui.video_full_range_flag: %d \n", sps->vui.video_full_range_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.colour_description_present_flag = bs_read_u1(b); printf("sps->vui.colour_description_present_flag: %d \n", sps->vui.colour_description_present_flag);
|
||||
if( sps->vui.colour_description_present_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.colour_primaries = bs_read_u8(b); printf("sps->vui.colour_primaries: %d \n", sps->vui.colour_primaries);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.transfer_characteristics = bs_read_u8(b); printf("sps->vui.transfer_characteristics: %d \n", sps->vui.transfer_characteristics);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.matrix_coefficients = bs_read_u8(b); printf("sps->vui.matrix_coefficients: %d \n", sps->vui.matrix_coefficients);
|
||||
}
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.chroma_loc_info_present_flag = bs_read_u1(b); printf("sps->vui.chroma_loc_info_present_flag: %d \n", sps->vui.chroma_loc_info_present_flag);
|
||||
if( sps->vui.chroma_loc_info_present_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.chroma_sample_loc_type_top_field = bs_read_ue(b); printf("sps->vui.chroma_sample_loc_type_top_field: %d \n", sps->vui.chroma_sample_loc_type_top_field);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.chroma_sample_loc_type_bottom_field = bs_read_ue(b); printf("sps->vui.chroma_sample_loc_type_bottom_field: %d \n", sps->vui.chroma_sample_loc_type_bottom_field);
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.timing_info_present_flag = bs_read_u1(b); printf("sps->vui.timing_info_present_flag: %d \n", sps->vui.timing_info_present_flag);
|
||||
if( sps->vui.timing_info_present_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.num_units_in_tick = bs_read_u(b, 32); printf("sps->vui.num_units_in_tick: %d \n", sps->vui.num_units_in_tick);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.time_scale = bs_read_u(b, 32); printf("sps->vui.time_scale: %d \n", sps->vui.time_scale);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.fixed_frame_rate_flag = bs_read_u1(b); printf("sps->vui.fixed_frame_rate_flag: %d \n", sps->vui.fixed_frame_rate_flag);
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.nal_hrd_parameters_present_flag = bs_read_u1(b); printf("sps->vui.nal_hrd_parameters_present_flag: %d \n", sps->vui.nal_hrd_parameters_present_flag);
|
||||
if( sps->vui.nal_hrd_parameters_present_flag )
|
||||
{
|
||||
read_debug_hrd_parameters(h, b);
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.vcl_hrd_parameters_present_flag = bs_read_u1(b); printf("sps->vui.vcl_hrd_parameters_present_flag: %d \n", sps->vui.vcl_hrd_parameters_present_flag);
|
||||
if( sps->vui.vcl_hrd_parameters_present_flag )
|
||||
{
|
||||
read_debug_hrd_parameters(h, b);
|
||||
}
|
||||
if( sps->vui.nal_hrd_parameters_present_flag || sps->vui.vcl_hrd_parameters_present_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.low_delay_hrd_flag = bs_read_u1(b); printf("sps->vui.low_delay_hrd_flag: %d \n", sps->vui.low_delay_hrd_flag);
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.pic_struct_present_flag = bs_read_u1(b); printf("sps->vui.pic_struct_present_flag: %d \n", sps->vui.pic_struct_present_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.bitstream_restriction_flag = bs_read_u1(b); printf("sps->vui.bitstream_restriction_flag: %d \n", sps->vui.bitstream_restriction_flag);
|
||||
if( sps->vui.bitstream_restriction_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.motion_vectors_over_pic_boundaries_flag = bs_read_u1(b); printf("sps->vui.motion_vectors_over_pic_boundaries_flag: %d \n", sps->vui.motion_vectors_over_pic_boundaries_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.max_bytes_per_pic_denom = bs_read_ue(b); printf("sps->vui.max_bytes_per_pic_denom: %d \n", sps->vui.max_bytes_per_pic_denom);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.max_bits_per_mb_denom = bs_read_ue(b); printf("sps->vui.max_bits_per_mb_denom: %d \n", sps->vui.max_bits_per_mb_denom);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.log2_max_mv_length_horizontal = bs_read_ue(b); printf("sps->vui.log2_max_mv_length_horizontal: %d \n", sps->vui.log2_max_mv_length_horizontal);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.log2_max_mv_length_vertical = bs_read_ue(b); printf("sps->vui.log2_max_mv_length_vertical: %d \n", sps->vui.log2_max_mv_length_vertical);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.num_reorder_frames = bs_read_ue(b); printf("sps->vui.num_reorder_frames: %d \n", sps->vui.num_reorder_frames);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->vui.max_dec_frame_buffering = bs_read_ue(b); printf("sps->vui.max_dec_frame_buffering: %d \n", sps->vui.max_dec_frame_buffering);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Appendix E.1.2 HRD parameters syntax
|
||||
void read_debug_hrd_parameters(h264_stream_t* h, bs_t* b)
|
||||
{
|
||||
sps_t* sps = h->sps;
|
||||
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->hrd.cpb_cnt_minus1 = bs_read_ue(b); printf("sps->hrd.cpb_cnt_minus1: %d \n", sps->hrd.cpb_cnt_minus1);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->hrd.bit_rate_scale = bs_read_u(b, 4); printf("sps->hrd.bit_rate_scale: %d \n", sps->hrd.bit_rate_scale);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->hrd.cpb_size_scale = bs_read_u(b, 4); printf("sps->hrd.cpb_size_scale: %d \n", sps->hrd.cpb_size_scale);
|
||||
for( int SchedSelIdx = 0; SchedSelIdx <= sps->hrd.cpb_cnt_minus1; SchedSelIdx++ )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->hrd.bit_rate_value_minus1[ SchedSelIdx ] = bs_read_ue(b); printf("sps->hrd.bit_rate_value_minus1[ SchedSelIdx ]: %d \n", sps->hrd.bit_rate_value_minus1[ SchedSelIdx ]);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->hrd.cpb_size_value_minus1[ SchedSelIdx ] = bs_read_ue(b); printf("sps->hrd.cpb_size_value_minus1[ SchedSelIdx ]: %d \n", sps->hrd.cpb_size_value_minus1[ SchedSelIdx ]);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->hrd.cbr_flag[ SchedSelIdx ] = bs_read_u1(b); printf("sps->hrd.cbr_flag[ SchedSelIdx ]: %d \n", sps->hrd.cbr_flag[ SchedSelIdx ]);
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->hrd.initial_cpb_removal_delay_length_minus1 = bs_read_u(b, 5); printf("sps->hrd.initial_cpb_removal_delay_length_minus1: %d \n", sps->hrd.initial_cpb_removal_delay_length_minus1);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->hrd.cpb_removal_delay_length_minus1 = bs_read_u(b, 5); printf("sps->hrd.cpb_removal_delay_length_minus1: %d \n", sps->hrd.cpb_removal_delay_length_minus1);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->hrd.dpb_output_delay_length_minus1 = bs_read_u(b, 5); printf("sps->hrd.dpb_output_delay_length_minus1: %d \n", sps->hrd.dpb_output_delay_length_minus1);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sps->hrd.time_offset_length = bs_read_u(b, 5); printf("sps->hrd.time_offset_length: %d \n", sps->hrd.time_offset_length);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
UNIMPLEMENTED
|
||||
//7.3.2.1.2 Sequence parameter set extension RBSP syntax
|
||||
int read_debug_seq_parameter_set_extension_rbsp(bs_t* b, sps_ext_t* sps_ext) {
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); seq_parameter_set_id = bs_read_ue(b); printf("seq_parameter_set_id: %d \n", seq_parameter_set_id);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); aux_format_idc = bs_read_ue(b); printf("aux_format_idc: %d \n", aux_format_idc);
|
||||
if( aux_format_idc != 0 ) {
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); bit_depth_aux_minus8 = bs_read_ue(b); printf("bit_depth_aux_minus8: %d \n", bit_depth_aux_minus8);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); alpha_incr_flag = bs_read_u1(b); printf("alpha_incr_flag: %d \n", alpha_incr_flag);
|
||||
alpha_opaque_value = bs_read_debug_u(v);
|
||||
alpha_transparent_value = bs_read_debug_u(v);
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); additional_extension_flag = bs_read_u1(b); printf("additional_extension_flag: %d \n", additional_extension_flag);
|
||||
read_debug_rbsp_trailing_bits();
|
||||
}
|
||||
*/
|
||||
|
||||
//7.3.2.2 Picture parameter set RBSP syntax
|
||||
void read_debug_pic_parameter_set_rbsp(h264_stream_t* h, bs_t* b)
|
||||
{
|
||||
pps_t* pps = h->pps;
|
||||
if( 1 )
|
||||
{
|
||||
memset(pps, 0, sizeof(pps_t));
|
||||
}
|
||||
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->pic_parameter_set_id = bs_read_ue(b); printf("pps->pic_parameter_set_id: %d \n", pps->pic_parameter_set_id);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->seq_parameter_set_id = bs_read_ue(b); printf("pps->seq_parameter_set_id: %d \n", pps->seq_parameter_set_id);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->entropy_coding_mode_flag = bs_read_u1(b); printf("pps->entropy_coding_mode_flag: %d \n", pps->entropy_coding_mode_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->pic_order_present_flag = bs_read_u1(b); printf("pps->pic_order_present_flag: %d \n", pps->pic_order_present_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->num_slice_groups_minus1 = bs_read_ue(b); printf("pps->num_slice_groups_minus1: %d \n", pps->num_slice_groups_minus1);
|
||||
|
||||
if( pps->num_slice_groups_minus1 > 0 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->slice_group_map_type = bs_read_ue(b); printf("pps->slice_group_map_type: %d \n", pps->slice_group_map_type);
|
||||
if( pps->slice_group_map_type == 0 )
|
||||
{
|
||||
for( int i_group = 0; i_group <= pps->num_slice_groups_minus1; i_group++ )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->run_length_minus1[ i_group ] = bs_read_ue(b); printf("pps->run_length_minus1[ i_group ]: %d \n", pps->run_length_minus1[ i_group ]);
|
||||
}
|
||||
}
|
||||
else if( pps->slice_group_map_type == 2 )
|
||||
{
|
||||
for( int i_group = 0; i_group < pps->num_slice_groups_minus1; i_group++ )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->top_left[ i_group ] = bs_read_ue(b); printf("pps->top_left[ i_group ]: %d \n", pps->top_left[ i_group ]);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->bottom_right[ i_group ] = bs_read_ue(b); printf("pps->bottom_right[ i_group ]: %d \n", pps->bottom_right[ i_group ]);
|
||||
}
|
||||
}
|
||||
else if( pps->slice_group_map_type == 3 ||
|
||||
pps->slice_group_map_type == 4 ||
|
||||
pps->slice_group_map_type == 5 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->slice_group_change_direction_flag = bs_read_u1(b); printf("pps->slice_group_change_direction_flag: %d \n", pps->slice_group_change_direction_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->slice_group_change_rate_minus1 = bs_read_ue(b); printf("pps->slice_group_change_rate_minus1: %d \n", pps->slice_group_change_rate_minus1);
|
||||
}
|
||||
else if( pps->slice_group_map_type == 6 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->pic_size_in_map_units_minus1 = bs_read_ue(b); printf("pps->pic_size_in_map_units_minus1: %d \n", pps->pic_size_in_map_units_minus1);
|
||||
for( int i = 0; i <= pps->pic_size_in_map_units_minus1; i++ )
|
||||
{
|
||||
int v = intlog2( pps->num_slice_groups_minus1 + 1 );
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->slice_group_id[ i ] = bs_read_u(b, v); printf("pps->slice_group_id[ i ]: %d \n", pps->slice_group_id[ i ]);
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->num_ref_idx_l0_active_minus1 = bs_read_ue(b); printf("pps->num_ref_idx_l0_active_minus1: %d \n", pps->num_ref_idx_l0_active_minus1);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->num_ref_idx_l1_active_minus1 = bs_read_ue(b); printf("pps->num_ref_idx_l1_active_minus1: %d \n", pps->num_ref_idx_l1_active_minus1);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->weighted_pred_flag = bs_read_u1(b); printf("pps->weighted_pred_flag: %d \n", pps->weighted_pred_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->weighted_bipred_idc = bs_read_u(b, 2); printf("pps->weighted_bipred_idc: %d \n", pps->weighted_bipred_idc);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->pic_init_qp_minus26 = bs_read_se(b); printf("pps->pic_init_qp_minus26: %d \n", pps->pic_init_qp_minus26);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->pic_init_qs_minus26 = bs_read_se(b); printf("pps->pic_init_qs_minus26: %d \n", pps->pic_init_qs_minus26);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->chroma_qp_index_offset = bs_read_se(b); printf("pps->chroma_qp_index_offset: %d \n", pps->chroma_qp_index_offset);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->deblocking_filter_control_present_flag = bs_read_u1(b); printf("pps->deblocking_filter_control_present_flag: %d \n", pps->deblocking_filter_control_present_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->constrained_intra_pred_flag = bs_read_u1(b); printf("pps->constrained_intra_pred_flag: %d \n", pps->constrained_intra_pred_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->redundant_pic_cnt_present_flag = bs_read_u1(b); printf("pps->redundant_pic_cnt_present_flag: %d \n", pps->redundant_pic_cnt_present_flag);
|
||||
|
||||
int have_more_data = 0;
|
||||
if( 1 ) { have_more_data = more_rbsp_data(h, b); }
|
||||
if( 0 )
|
||||
{
|
||||
have_more_data = pps->transform_8x8_mode_flag | pps->pic_scaling_matrix_present_flag | pps->second_chroma_qp_index_offset != 0;
|
||||
}
|
||||
|
||||
if( have_more_data )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->transform_8x8_mode_flag = bs_read_u1(b); printf("pps->transform_8x8_mode_flag: %d \n", pps->transform_8x8_mode_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->pic_scaling_matrix_present_flag = bs_read_u1(b); printf("pps->pic_scaling_matrix_present_flag: %d \n", pps->pic_scaling_matrix_present_flag);
|
||||
if( pps->pic_scaling_matrix_present_flag )
|
||||
{
|
||||
for( int i = 0; i < 6 + 2* pps->transform_8x8_mode_flag; i++ )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->pic_scaling_list_present_flag[ i ] = bs_read_u1(b); printf("pps->pic_scaling_list_present_flag[ i ]: %d \n", pps->pic_scaling_list_present_flag[ i ]);
|
||||
if( pps->pic_scaling_list_present_flag[ i ] )
|
||||
{
|
||||
if( i < 6 )
|
||||
{
|
||||
read_debug_scaling_list( b, pps->ScalingList4x4[ i ], 16,
|
||||
&( pps->UseDefaultScalingMatrix4x4Flag[ i ] ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
read_debug_scaling_list( b, pps->ScalingList8x8[ i - 6 ], 64,
|
||||
&( pps->UseDefaultScalingMatrix8x8Flag[ i - 6 ] ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); pps->second_chroma_qp_index_offset = bs_read_se(b); printf("pps->second_chroma_qp_index_offset: %d \n", pps->second_chroma_qp_index_offset);
|
||||
}
|
||||
read_debug_rbsp_trailing_bits(h, b);
|
||||
|
||||
if( 1 )
|
||||
{
|
||||
memcpy(h->pps, h->pps_table[pps->pic_parameter_set_id], sizeof(pps_t));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_SEI
|
||||
//7.3.2.3 Supplemental enhancement information RBSP syntax
|
||||
void read_debug_sei_rbsp(h264_stream_t* h, bs_t* b)
|
||||
{
|
||||
if( 1 )
|
||||
{
|
||||
for( int i = 0; i < h->num_seis; i++ )
|
||||
{
|
||||
sei_free(h->seis[i]);
|
||||
}
|
||||
|
||||
h->num_seis = 0;
|
||||
do {
|
||||
h->num_seis++;
|
||||
h->seis = (sei_t**)realloc(h->seis, h->num_seis * sizeof(sei_t*));
|
||||
h->seis[h->num_seis - 1] = sei_new();
|
||||
h->sei = h->seis[h->num_seis - 1];
|
||||
read_debug_sei_message(h, b);
|
||||
} while( more_rbsp_data(h, b) );
|
||||
|
||||
}
|
||||
|
||||
if( 0 )
|
||||
{
|
||||
for (int i = 0; i < h->num_seis; i++)
|
||||
{
|
||||
h->sei = h->seis[i];
|
||||
read_debug_sei_message(h, b);
|
||||
}
|
||||
h->sei = NULL;
|
||||
}
|
||||
|
||||
read_debug_rbsp_trailing_bits(h, b);
|
||||
}
|
||||
|
||||
//7.3.2.3.1 Supplemental enhancement information message syntax
|
||||
void read_debug_sei_message(h264_stream_t* h, bs_t* b)
|
||||
{
|
||||
if( 0 )
|
||||
{
|
||||
_write_ff_coded_number(b, h->sei->payloadType);
|
||||
_write_ff_coded_number(b, h->sei->payloadSize);
|
||||
}
|
||||
if( 1 )
|
||||
{
|
||||
h->sei->payloadType = _read_ff_coded_number(b);
|
||||
h->sei->payloadSize = _read_ff_coded_number(b);
|
||||
}
|
||||
read_debug_sei_payload( h, b, h->sei->payloadType, h->sei->payloadSize );
|
||||
}
|
||||
#endif
|
||||
|
||||
//7.3.2.4 Access unit delimiter RBSP syntax
|
||||
void read_debug_access_unit_delimiter_rbsp(h264_stream_t* h, bs_t* b)
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); h->aud->primary_pic_type = bs_read_u(b, 3); printf("h->aud->primary_pic_type: %d \n", h->aud->primary_pic_type);
|
||||
read_debug_rbsp_trailing_bits(h, b);
|
||||
}
|
||||
|
||||
//7.3.2.5 End of sequence RBSP syntax
|
||||
void read_debug_end_of_seq_rbsp(h264_stream_t* h, bs_t* b)
|
||||
{
|
||||
}
|
||||
|
||||
//7.3.2.6 End of stream RBSP syntax
|
||||
void read_debug_end_of_stream_rbsp(h264_stream_t* h, bs_t* b)
|
||||
{
|
||||
}
|
||||
|
||||
//7.3.2.7 Filler data RBSP syntax
|
||||
void read_debug_filler_data_rbsp(h264_stream_t* h, bs_t* b)
|
||||
{
|
||||
while( bs_next_bits(b, 8) == 0xFF )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); int ff_byte = bs_read_u(b, 8); printf("ff_byte: %d \n", ff_byte);
|
||||
}
|
||||
read_debug_rbsp_trailing_bits(h, b);
|
||||
}
|
||||
|
||||
//7.3.2.8 Slice layer without partitioning RBSP syntax
|
||||
void read_debug_slice_layer_rbsp(h264_stream_t* h, bs_t* b)
|
||||
{
|
||||
read_debug_slice_header(h, b);
|
||||
slice_data_rbsp_t* slice_data = h->slice_data;
|
||||
|
||||
if ( slice_data != NULL )
|
||||
{
|
||||
if ( slice_data->rbsp_buf != NULL ) free( slice_data->rbsp_buf );
|
||||
uint8_t *sptr = b->p + (!!b->bits_left); // CABAC-specific: skip alignment bits, if there are any
|
||||
slice_data->rbsp_size = b->end - sptr;
|
||||
|
||||
slice_data->rbsp_buf = (uint8_t*)malloc(slice_data->rbsp_size);
|
||||
memcpy( slice_data->rbsp_buf, sptr, slice_data->rbsp_size );
|
||||
// ugly hack: since next NALU starts at byte border, we are going to be padded by trailing_bits;
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME should read or skip data
|
||||
//slice_data( ); /* all categories of slice_data( ) syntax */
|
||||
read_debug_rbsp_slice_trailing_bits(h, b);
|
||||
}
|
||||
|
||||
/*
|
||||
// UNIMPLEMENTED
|
||||
//7.3.2.9.1 Slice data partition A RBSP syntax
|
||||
slice_data_partition_a_layer_rbsp( ) {
|
||||
read_debug_slice_header( ); // only category 2
|
||||
slice_id = bs_read_debug_ue(b)
|
||||
read_debug_slice_data( ); // only category 2
|
||||
read_debug_rbsp_slice_trailing_bits( ); // only category 2
|
||||
}
|
||||
|
||||
//7.3.2.9.2 Slice data partition B RBSP syntax
|
||||
slice_data_partition_b_layer_rbsp( ) {
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); slice_id = bs_read_ue(b); printf("slice_id: %d \n", slice_id); // only category 3
|
||||
if( redundant_pic_cnt_present_flag )
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); redundant_pic_cnt = bs_read_ue(b); printf("redundant_pic_cnt: %d \n", redundant_pic_cnt);
|
||||
read_debug_slice_data( ); // only category 3
|
||||
read_debug_rbsp_slice_trailing_bits( ); // only category 3
|
||||
}
|
||||
|
||||
//7.3.2.9.3 Slice data partition C RBSP syntax
|
||||
slice_data_partition_c_layer_rbsp( ) {
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); slice_id = bs_read_ue(b); printf("slice_id: %d \n", slice_id); // only category 4
|
||||
if( redundant_pic_cnt_present_flag )
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); redundant_pic_cnt = bs_read_ue(b); printf("redundant_pic_cnt: %d \n", redundant_pic_cnt);
|
||||
read_debug_slice_data( ); // only category 4
|
||||
rbsp_slice_trailing_bits( ); // only category 4
|
||||
}
|
||||
*/
|
||||
|
||||
//7.3.2.10 RBSP slice trailing bits syntax
|
||||
void read_debug_rbsp_slice_trailing_bits(h264_stream_t* h, bs_t* b)
|
||||
{
|
||||
read_debug_rbsp_trailing_bits(h, b);
|
||||
if( h->pps->entropy_coding_mode_flag )
|
||||
{
|
||||
while( more_rbsp_trailing_data(h, b) )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); int cabac_zero_word = bs_read_u(b, 16); printf("cabac_zero_word: %d \n", cabac_zero_word);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//7.3.2.11 RBSP trailing bits syntax
|
||||
void read_debug_rbsp_trailing_bits(h264_stream_t* h, bs_t* b)
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); int rbsp_stop_one_bit = bs_read_u(b, 1); printf("rbsp_stop_one_bit: %d \n", rbsp_stop_one_bit);
|
||||
|
||||
while( !bs_byte_aligned(b) )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); int rbsp_alignment_zero_bit = bs_read_u(b, 1); printf("rbsp_alignment_zero_bit: %d \n", rbsp_alignment_zero_bit);
|
||||
}
|
||||
}
|
||||
|
||||
//7.3.3 Slice header syntax
|
||||
void read_debug_slice_header(h264_stream_t* h, bs_t* b)
|
||||
{
|
||||
slice_header_t* sh = h->sh;
|
||||
if( 1 )
|
||||
{
|
||||
memset(sh, 0, sizeof(slice_header_t));
|
||||
}
|
||||
|
||||
nal_t* nal = h->nal;
|
||||
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->first_mb_in_slice = bs_read_ue(b); printf("sh->first_mb_in_slice: %d \n", sh->first_mb_in_slice);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->slice_type = bs_read_ue(b); printf("sh->slice_type: %d \n", sh->slice_type);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->pic_parameter_set_id = bs_read_ue(b); printf("sh->pic_parameter_set_id: %d \n", sh->pic_parameter_set_id);
|
||||
|
||||
// TODO check existence, otherwise fail
|
||||
pps_t* pps = h->pps;
|
||||
sps_t* sps = h->sps;
|
||||
memcpy(h->pps_table[sh->pic_parameter_set_id], h->pps, sizeof(pps_t));
|
||||
memcpy(h->sps_table[pps->seq_parameter_set_id], h->sps, sizeof(sps_t));
|
||||
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->frame_num = bs_read_u(b, sps->log2_max_frame_num_minus4 + 4 ); printf("sh->frame_num: %d \n", sh->frame_num); // was u(v)
|
||||
if( !sps->frame_mbs_only_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->field_pic_flag = bs_read_u1(b); printf("sh->field_pic_flag: %d \n", sh->field_pic_flag);
|
||||
if( sh->field_pic_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->bottom_field_flag = bs_read_u1(b); printf("sh->bottom_field_flag: %d \n", sh->bottom_field_flag);
|
||||
}
|
||||
}
|
||||
if( nal->nal_unit_type == 5 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->idr_pic_id = bs_read_ue(b); printf("sh->idr_pic_id: %d \n", sh->idr_pic_id);
|
||||
}
|
||||
if( sps->pic_order_cnt_type == 0 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->pic_order_cnt_lsb = bs_read_u(b, sps->log2_max_pic_order_cnt_lsb_minus4 + 4 ); printf("sh->pic_order_cnt_lsb: %d \n", sh->pic_order_cnt_lsb); // was u(v)
|
||||
if( pps->pic_order_present_flag && !sh->field_pic_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->delta_pic_order_cnt_bottom = bs_read_se(b); printf("sh->delta_pic_order_cnt_bottom: %d \n", sh->delta_pic_order_cnt_bottom);
|
||||
}
|
||||
}
|
||||
if( sps->pic_order_cnt_type == 1 && !sps->delta_pic_order_always_zero_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->delta_pic_order_cnt[ 0 ] = bs_read_se(b); printf("sh->delta_pic_order_cnt[ 0 ]: %d \n", sh->delta_pic_order_cnt[ 0 ]);
|
||||
if( pps->pic_order_present_flag && !sh->field_pic_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->delta_pic_order_cnt[ 1 ] = bs_read_se(b); printf("sh->delta_pic_order_cnt[ 1 ]: %d \n", sh->delta_pic_order_cnt[ 1 ]);
|
||||
}
|
||||
}
|
||||
if( pps->redundant_pic_cnt_present_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->redundant_pic_cnt = bs_read_ue(b); printf("sh->redundant_pic_cnt: %d \n", sh->redundant_pic_cnt);
|
||||
}
|
||||
if( is_slice_type( sh->slice_type, SH_SLICE_TYPE_B ) )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->direct_spatial_mv_pred_flag = bs_read_u1(b); printf("sh->direct_spatial_mv_pred_flag: %d \n", sh->direct_spatial_mv_pred_flag);
|
||||
}
|
||||
if( is_slice_type( sh->slice_type, SH_SLICE_TYPE_P ) || is_slice_type( sh->slice_type, SH_SLICE_TYPE_SP ) || is_slice_type( sh->slice_type, SH_SLICE_TYPE_B ) )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->num_ref_idx_active_override_flag = bs_read_u1(b); printf("sh->num_ref_idx_active_override_flag: %d \n", sh->num_ref_idx_active_override_flag);
|
||||
if( sh->num_ref_idx_active_override_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->num_ref_idx_l0_active_minus1 = bs_read_ue(b); printf("sh->num_ref_idx_l0_active_minus1: %d \n", sh->num_ref_idx_l0_active_minus1); // FIXME does this modify the pps?
|
||||
if( is_slice_type( sh->slice_type, SH_SLICE_TYPE_B ) )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->num_ref_idx_l1_active_minus1 = bs_read_ue(b); printf("sh->num_ref_idx_l1_active_minus1: %d \n", sh->num_ref_idx_l1_active_minus1);
|
||||
}
|
||||
}
|
||||
}
|
||||
read_debug_ref_pic_list_reordering(h, b);
|
||||
if( ( pps->weighted_pred_flag && ( is_slice_type( sh->slice_type, SH_SLICE_TYPE_P ) || is_slice_type( sh->slice_type, SH_SLICE_TYPE_SP ) ) ) ||
|
||||
( pps->weighted_bipred_idc == 1 && is_slice_type( sh->slice_type, SH_SLICE_TYPE_B ) ) )
|
||||
{
|
||||
read_debug_pred_weight_table(h, b);
|
||||
}
|
||||
if( nal->nal_ref_idc != 0 )
|
||||
{
|
||||
read_debug_dec_ref_pic_marking(h, b);
|
||||
}
|
||||
if( pps->entropy_coding_mode_flag && ! is_slice_type( sh->slice_type, SH_SLICE_TYPE_I ) && ! is_slice_type( sh->slice_type, SH_SLICE_TYPE_SI ) )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->cabac_init_idc = bs_read_ue(b); printf("sh->cabac_init_idc: %d \n", sh->cabac_init_idc);
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->slice_qp_delta = bs_read_se(b); printf("sh->slice_qp_delta: %d \n", sh->slice_qp_delta);
|
||||
if( is_slice_type( sh->slice_type, SH_SLICE_TYPE_SP ) || is_slice_type( sh->slice_type, SH_SLICE_TYPE_SI ) )
|
||||
{
|
||||
if( is_slice_type( sh->slice_type, SH_SLICE_TYPE_SP ) )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->sp_for_switch_flag = bs_read_u1(b); printf("sh->sp_for_switch_flag: %d \n", sh->sp_for_switch_flag);
|
||||
}
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->slice_qs_delta = bs_read_se(b); printf("sh->slice_qs_delta: %d \n", sh->slice_qs_delta);
|
||||
}
|
||||
if( pps->deblocking_filter_control_present_flag )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->disable_deblocking_filter_idc = bs_read_ue(b); printf("sh->disable_deblocking_filter_idc: %d \n", sh->disable_deblocking_filter_idc);
|
||||
if( sh->disable_deblocking_filter_idc != 1 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->slice_alpha_c0_offset_div2 = bs_read_se(b); printf("sh->slice_alpha_c0_offset_div2: %d \n", sh->slice_alpha_c0_offset_div2);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->slice_beta_offset_div2 = bs_read_se(b); printf("sh->slice_beta_offset_div2: %d \n", sh->slice_beta_offset_div2);
|
||||
}
|
||||
}
|
||||
if( pps->num_slice_groups_minus1 > 0 &&
|
||||
pps->slice_group_map_type >= 3 && pps->slice_group_map_type <= 5)
|
||||
{
|
||||
int v = intlog2( pps->pic_size_in_map_units_minus1 + pps->slice_group_change_rate_minus1 + 1 );
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->slice_group_change_cycle = bs_read_u(b, v); printf("sh->slice_group_change_cycle: %d \n", sh->slice_group_change_cycle); // FIXME add 2?
|
||||
}
|
||||
}
|
||||
|
||||
//7.3.3.1 Reference picture list reordering syntax
|
||||
void read_debug_ref_pic_list_reordering(h264_stream_t* h, bs_t* b)
|
||||
{
|
||||
slice_header_t* sh = h->sh;
|
||||
// FIXME should be an array
|
||||
|
||||
if( ! is_slice_type( sh->slice_type, SH_SLICE_TYPE_I ) && ! is_slice_type( sh->slice_type, SH_SLICE_TYPE_SI ) )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->rplr.ref_pic_list_reordering_flag_l0 = bs_read_u1(b); printf("sh->rplr.ref_pic_list_reordering_flag_l0: %d \n", sh->rplr.ref_pic_list_reordering_flag_l0);
|
||||
if( sh->rplr.ref_pic_list_reordering_flag_l0 )
|
||||
{
|
||||
int n = -1;
|
||||
do
|
||||
{
|
||||
n++;
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->rplr.reorder_l0.reordering_of_pic_nums_idc[ n ] = bs_read_ue(b); printf("sh->rplr.reorder_l0.reordering_of_pic_nums_idc[ n ]: %d \n", sh->rplr.reorder_l0.reordering_of_pic_nums_idc[ n ]);
|
||||
if( sh->rplr.reorder_l0.reordering_of_pic_nums_idc[ n ] == 0 ||
|
||||
sh->rplr.reorder_l0.reordering_of_pic_nums_idc[ n ] == 1 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->rplr.reorder_l0.abs_diff_pic_num_minus1[ n ] = bs_read_ue(b); printf("sh->rplr.reorder_l0.abs_diff_pic_num_minus1[ n ]: %d \n", sh->rplr.reorder_l0.abs_diff_pic_num_minus1[ n ]);
|
||||
}
|
||||
else if( sh->rplr.reorder_l0.reordering_of_pic_nums_idc[ n ] == 2 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->rplr.reorder_l0.long_term_pic_num[ n ] = bs_read_ue(b); printf("sh->rplr.reorder_l0.long_term_pic_num[ n ]: %d \n", sh->rplr.reorder_l0.long_term_pic_num[ n ]);
|
||||
}
|
||||
} while( sh->rplr.reorder_l0.reordering_of_pic_nums_idc[ n ] != 3 && ! bs_eof(b) );
|
||||
}
|
||||
}
|
||||
if( is_slice_type( sh->slice_type, SH_SLICE_TYPE_B ) )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->rplr.ref_pic_list_reordering_flag_l1 = bs_read_u1(b); printf("sh->rplr.ref_pic_list_reordering_flag_l1: %d \n", sh->rplr.ref_pic_list_reordering_flag_l1);
|
||||
if( sh->rplr.ref_pic_list_reordering_flag_l1 )
|
||||
{
|
||||
int n = -1;
|
||||
do
|
||||
{
|
||||
n++;
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->rplr.reorder_l1.reordering_of_pic_nums_idc[ n ] = bs_read_ue(b); printf("sh->rplr.reorder_l1.reordering_of_pic_nums_idc[ n ]: %d \n", sh->rplr.reorder_l1.reordering_of_pic_nums_idc[ n ]);
|
||||
if( sh->rplr.reorder_l1.reordering_of_pic_nums_idc[ n ] == 0 ||
|
||||
sh->rplr.reorder_l1.reordering_of_pic_nums_idc[ n ] == 1 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->rplr.reorder_l1.abs_diff_pic_num_minus1[ n ] = bs_read_ue(b); printf("sh->rplr.reorder_l1.abs_diff_pic_num_minus1[ n ]: %d \n", sh->rplr.reorder_l1.abs_diff_pic_num_minus1[ n ]);
|
||||
}
|
||||
else if( sh->rplr.reorder_l1.reordering_of_pic_nums_idc[ n ] == 2 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->rplr.reorder_l1.long_term_pic_num[ n ] = bs_read_ue(b); printf("sh->rplr.reorder_l1.long_term_pic_num[ n ]: %d \n", sh->rplr.reorder_l1.long_term_pic_num[ n ]);
|
||||
}
|
||||
} while( sh->rplr.reorder_l1.reordering_of_pic_nums_idc[ n ] != 3 && ! bs_eof(b) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//7.3.3.2 Prediction weight table syntax
|
||||
void read_debug_pred_weight_table(h264_stream_t* h, bs_t* b)
|
||||
{
|
||||
slice_header_t* sh = h->sh;
|
||||
sps_t* sps = h->sps;
|
||||
pps_t* pps = h->pps;
|
||||
|
||||
int i, j;
|
||||
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->pwt.luma_log2_weight_denom = bs_read_ue(b); printf("sh->pwt.luma_log2_weight_denom: %d \n", sh->pwt.luma_log2_weight_denom);
|
||||
if( sps->chroma_format_idc != 0 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->pwt.chroma_log2_weight_denom = bs_read_ue(b); printf("sh->pwt.chroma_log2_weight_denom: %d \n", sh->pwt.chroma_log2_weight_denom);
|
||||
}
|
||||
for( i = 0; i <= pps->num_ref_idx_l0_active_minus1; i++ )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->pwt.luma_weight_l0_flag[i] = bs_read_u1(b); printf("sh->pwt.luma_weight_l0_flag[i]: %d \n", sh->pwt.luma_weight_l0_flag[i]);
|
||||
if( sh->pwt.luma_weight_l0_flag[i] )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->pwt.luma_weight_l0[ i ] = bs_read_se(b); printf("sh->pwt.luma_weight_l0[ i ]: %d \n", sh->pwt.luma_weight_l0[ i ]);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->pwt.luma_offset_l0[ i ] = bs_read_se(b); printf("sh->pwt.luma_offset_l0[ i ]: %d \n", sh->pwt.luma_offset_l0[ i ]);
|
||||
}
|
||||
if ( sps->chroma_format_idc != 0 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->pwt.chroma_weight_l0_flag[i] = bs_read_u1(b); printf("sh->pwt.chroma_weight_l0_flag[i]: %d \n", sh->pwt.chroma_weight_l0_flag[i]);
|
||||
if( sh->pwt.chroma_weight_l0_flag[i] )
|
||||
{
|
||||
for( j =0; j < 2; j++ )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->pwt.chroma_weight_l0[ i ][ j ] = bs_read_se(b); printf("sh->pwt.chroma_weight_l0[ i ][ j ]: %d \n", sh->pwt.chroma_weight_l0[ i ][ j ]);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->pwt.chroma_offset_l0[ i ][ j ] = bs_read_se(b); printf("sh->pwt.chroma_offset_l0[ i ][ j ]: %d \n", sh->pwt.chroma_offset_l0[ i ][ j ]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if( is_slice_type( sh->slice_type, SH_SLICE_TYPE_B ) )
|
||||
{
|
||||
for( i = 0; i <= pps->num_ref_idx_l1_active_minus1; i++ )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->pwt.luma_weight_l1_flag[i] = bs_read_u1(b); printf("sh->pwt.luma_weight_l1_flag[i]: %d \n", sh->pwt.luma_weight_l1_flag[i]);
|
||||
if( sh->pwt.luma_weight_l1_flag[i] )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->pwt.luma_weight_l1[ i ] = bs_read_se(b); printf("sh->pwt.luma_weight_l1[ i ]: %d \n", sh->pwt.luma_weight_l1[ i ]);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->pwt.luma_offset_l1[ i ] = bs_read_se(b); printf("sh->pwt.luma_offset_l1[ i ]: %d \n", sh->pwt.luma_offset_l1[ i ]);
|
||||
}
|
||||
if( sps->chroma_format_idc != 0 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->pwt.chroma_weight_l1_flag[i] = bs_read_u1(b); printf("sh->pwt.chroma_weight_l1_flag[i]: %d \n", sh->pwt.chroma_weight_l1_flag[i]);
|
||||
if( sh->pwt.chroma_weight_l1_flag[i] )
|
||||
{
|
||||
for( j = 0; j < 2; j++ )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->pwt.chroma_weight_l1[ i ][ j ] = bs_read_se(b); printf("sh->pwt.chroma_weight_l1[ i ][ j ]: %d \n", sh->pwt.chroma_weight_l1[ i ][ j ]);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->pwt.chroma_offset_l1[ i ][ j ] = bs_read_se(b); printf("sh->pwt.chroma_offset_l1[ i ][ j ]: %d \n", sh->pwt.chroma_offset_l1[ i ][ j ]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//7.3.3.3 Decoded reference picture marking syntax
|
||||
void read_debug_dec_ref_pic_marking(h264_stream_t* h, bs_t* b)
|
||||
{
|
||||
slice_header_t* sh = h->sh;
|
||||
// FIXME should be an array
|
||||
|
||||
if( h->nal->nal_unit_type == 5 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->drpm.no_output_of_prior_pics_flag = bs_read_u1(b); printf("sh->drpm.no_output_of_prior_pics_flag: %d \n", sh->drpm.no_output_of_prior_pics_flag);
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->drpm.long_term_reference_flag = bs_read_u1(b); printf("sh->drpm.long_term_reference_flag: %d \n", sh->drpm.long_term_reference_flag);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->drpm.adaptive_ref_pic_marking_mode_flag = bs_read_u1(b); printf("sh->drpm.adaptive_ref_pic_marking_mode_flag: %d \n", sh->drpm.adaptive_ref_pic_marking_mode_flag);
|
||||
if( sh->drpm.adaptive_ref_pic_marking_mode_flag )
|
||||
{
|
||||
int n = -1;
|
||||
do
|
||||
{
|
||||
n++;
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->drpm.memory_management_control_operation[ n ] = bs_read_ue(b); printf("sh->drpm.memory_management_control_operation[ n ]: %d \n", sh->drpm.memory_management_control_operation[ n ]);
|
||||
if( sh->drpm.memory_management_control_operation[ n ] == 1 ||
|
||||
sh->drpm.memory_management_control_operation[ n ] == 3 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->drpm.difference_of_pic_nums_minus1[ n ] = bs_read_ue(b); printf("sh->drpm.difference_of_pic_nums_minus1[ n ]: %d \n", sh->drpm.difference_of_pic_nums_minus1[ n ]);
|
||||
}
|
||||
if(sh->drpm.memory_management_control_operation[ n ] == 2 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->drpm.long_term_pic_num[ n ] = bs_read_ue(b); printf("sh->drpm.long_term_pic_num[ n ]: %d \n", sh->drpm.long_term_pic_num[ n ]);
|
||||
}
|
||||
if( sh->drpm.memory_management_control_operation[ n ] == 3 ||
|
||||
sh->drpm.memory_management_control_operation[ n ] == 6 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->drpm.long_term_frame_idx[ n ] = bs_read_ue(b); printf("sh->drpm.long_term_frame_idx[ n ]: %d \n", sh->drpm.long_term_frame_idx[ n ]);
|
||||
}
|
||||
if( sh->drpm.memory_management_control_operation[ n ] == 4 )
|
||||
{
|
||||
printf("%d.%d: ", b->p - b->start, b->bits_left); sh->drpm.max_long_term_frame_idx_plus1[ n ] = bs_read_ue(b); printf("sh->drpm.max_long_term_frame_idx_plus1[ n ]: %d \n", sh->drpm.max_long_term_frame_idx_plus1[ n ]);
|
||||
}
|
||||
} while( sh->drpm.memory_management_control_operation[ n ] != 0 && ! bs_eof(b) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2
third_party/moonlight-common-c
vendored
2
third_party/moonlight-common-c
vendored
@ -1 +1 @@
|
||||
Subproject commit b70f072090f734cc35fd163ce9d39e2d22ceba57
|
||||
Subproject commit 8af4562af672dd6b9ed28553ead172984fd9a683
|
Loading…
x
Reference in New Issue
Block a user