Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
821d4ac9dd | ||
|
|
f4408473e6 | ||
|
|
1f759577ce | ||
|
|
04376ca047 | ||
|
|
7ac108f495 | ||
|
|
a2eece0f5c | ||
|
|
a03279df3b | ||
|
|
bef6c2b665 | ||
|
|
8002c65693 | ||
|
|
5c7247b8b5 | ||
|
|
1a18e68441 | ||
|
|
72afa55897 | ||
|
|
eed9fca10c | ||
|
|
25d97e187a | ||
|
|
7ca2ca8045 | ||
|
|
54885c3e5f | ||
|
|
b8ffb6e53d | ||
|
|
7bc43c226d | ||
|
|
da4697794d | ||
|
|
879c524658 | ||
|
|
e3a8f010c3 | ||
|
|
f2f8f92172 | ||
|
|
265ec96c6f | ||
|
|
6e06174db6 | ||
|
|
9677569293 | ||
|
|
c8e00195ed | ||
|
|
64bc189010 | ||
|
|
12496e4432 | ||
|
|
9e2fd67487 | ||
|
|
af17d56cbd | ||
|
|
6e0a657b76 | ||
|
|
b0a9fc7085 | ||
|
|
5e47b8a835 | ||
|
|
0b94ad1fef | ||
|
|
9dcd856099 | ||
|
|
cfbcc4093c | ||
|
|
b8169ae08b | ||
|
|
103625fbdd | ||
|
|
4712080a1b | ||
|
|
93c683be3a | ||
|
|
c9a7c15f98 | ||
|
|
64a08f0533 | ||
|
|
52c6455ca5 | ||
|
|
238681d72f | ||
|
|
dbcc6feb3f | ||
|
|
300b53f28c | ||
|
|
3dabb1c891 | ||
|
|
4381f7a973 | ||
|
|
77b767ae5b | ||
|
|
2c4f78133f | ||
|
|
ec2c181f4e | ||
|
|
fae98eb13a | ||
|
|
f1d117d5d7 | ||
|
|
529ac8041a | ||
|
|
391e84e0f4 | ||
|
|
6a2b717ff1 | ||
|
|
475a39cb73 | ||
|
|
4dba74e2f9 | ||
|
|
587d783955 | ||
|
|
3bfe87d516 | ||
|
|
d3e43012fb | ||
|
|
5da156b433 | ||
|
|
b2eeb63b6f | ||
|
|
a7517dace3 | ||
|
|
bd9f2d2009 | ||
|
|
78730f7235 | ||
|
|
8a2491d955 | ||
|
|
0d68faf40c | ||
|
|
957a2b2169 | ||
|
|
856318f947 | ||
|
|
4df9d6b20a | ||
|
|
8519d09141 | ||
|
|
1b4fe83739 | ||
|
|
8fc825b2eb | ||
|
|
6d0fc22ab5 | ||
|
|
9a71b4c237 | ||
|
|
9aa487418f | ||
|
|
203b2fcaca | ||
|
|
dae21f2638 | ||
|
|
af6e99cbac | ||
|
|
6d6acf4308 | ||
|
|
7dd4815edf | ||
|
|
2c068a99a3 | ||
|
|
fa93364ddd | ||
|
|
9b1eef3f63 | ||
|
|
ed6c07fd4b | ||
|
|
5ad96f5287 | ||
|
|
fa8845c7e2 | ||
|
|
b96ace7f04 | ||
|
|
234a8dc3d5 | ||
|
|
0467c2f893 | ||
|
|
762fe92933 | ||
|
|
16d105d462 | ||
|
|
8a8b624f47 | ||
|
|
9470615c63 | ||
|
|
8f0ea700a5 | ||
|
|
b7cbec0b1c | ||
|
|
9be9934b8c | ||
|
|
55f0e1e1d5 | ||
|
|
bbd784fc85 | ||
|
|
408f93732b | ||
|
|
fb53c95207 | ||
|
|
960e083152 | ||
|
|
7e560aa09e | ||
|
|
7d9478b774 | ||
|
|
0fa0ab43c9 | ||
|
|
f9cdd49d2e | ||
|
|
7661f5a3d7 | ||
|
|
395f337bbb | ||
|
|
9e9fe2a2cf | ||
|
|
5bf88cfe96 | ||
|
|
f7d3c10c9d | ||
|
|
6b395c816f | ||
|
|
d6e7173af0 | ||
|
|
1a60484abc | ||
|
|
62f765b2b4 | ||
|
|
57f2fb07f0 | ||
|
|
1b8c3aae73 | ||
|
|
845e84adb7 | ||
|
|
91c0429e2c | ||
|
|
30f673efe1 | ||
|
|
c1b1719914 | ||
|
|
9fbdd794b9 | ||
|
|
38ff2bf5cb | ||
|
|
345e800abd | ||
|
|
404eaa44e4 | ||
|
|
8e257ab241 | ||
|
|
fabbad5370 | ||
|
|
7d61acb2a8 | ||
|
|
f6a451d6e7 | ||
|
|
e68a15c825 | ||
|
|
f929cffce7 | ||
|
|
f171588616 | ||
|
|
e467ce7ce8 | ||
|
|
1fda7550da | ||
|
|
f714a5d0cb | ||
|
|
2a3d5a27a8 | ||
|
|
c9bf5ae79d | ||
|
|
cf2d8f71d6 | ||
|
|
611d9ac1ba | ||
|
|
f3309087ab | ||
|
|
860d317330 | ||
|
|
bab11a731e | ||
|
|
df9e7bead0 | ||
|
|
962dc847e7 | ||
|
|
566181e8d7 | ||
|
|
5f5f835b2e | ||
|
|
c0e697eb58 | ||
|
|
d3c2f50b20 | ||
|
|
f1ee1eeb63 | ||
|
|
bb18519e44 | ||
|
|
8e2ad133ea | ||
|
|
5e82e8413f | ||
|
|
917f6e5ee9 | ||
|
|
e1ca6e644e | ||
|
|
f1e89242cb | ||
|
|
91145cde0f | ||
|
|
8ae01fae3f | ||
|
|
4e60ccb2ff | ||
|
|
186052f21d | ||
|
|
0fb3bb0727 | ||
|
|
8f61a11452 | ||
|
|
4b1d3b0125 | ||
|
|
3f81d055af | ||
|
|
c61182bcb2 | ||
|
|
2fdfb3e998 | ||
|
|
9beb51ecac | ||
|
|
7be167856e | ||
|
|
e1d68ef097 | ||
|
|
5ec256a734 | ||
|
|
e4d7d190e2 | ||
|
|
b6763bb9f0 | ||
|
|
6d9c0c96c9 | ||
|
|
73dd5630df | ||
|
|
b76645e26b | ||
|
|
9cc20c27ab | ||
|
|
36b42f6e50 | ||
|
|
45ebf2ca7d | ||
|
|
2140c94cd1 | ||
|
|
470769e910 | ||
|
|
493165f57f | ||
|
|
1dcf55515b | ||
|
|
b04bc5117d | ||
|
|
5cbb38091b | ||
|
|
b076744f00 | ||
|
|
723e815546 | ||
|
|
ca72cbb135 | ||
|
|
1e8c2b179a | ||
|
|
f2ed3a8502 | ||
|
|
2bcc15e1d2 | ||
|
|
4935baa72a | ||
|
|
10cfaa2a5f | ||
|
|
3117e360f4 | ||
|
|
48a770edec | ||
|
|
fca402138f | ||
|
|
2853e498b0 | ||
|
|
509dba2264 | ||
|
|
b8dd131af8 | ||
|
|
0c18bcdd5e | ||
|
|
624578f286 | ||
|
|
c18e99c9ec | ||
|
|
979ba2dab7 | ||
|
|
56277af5d1 | ||
|
|
608db6a3e5 | ||
|
|
be52272e5c | ||
|
|
32f305347f | ||
|
|
18dd6b09bb | ||
|
|
3ed5f9edf7 | ||
|
|
017362a5d1 | ||
|
|
e250e08242 | ||
|
|
48d5a7fb51 | ||
|
|
141ee11e2f | ||
|
|
4e82ea298e | ||
|
|
22be912788 | ||
|
|
02242b9b34 | ||
|
|
e9c1f883da | ||
|
|
141f6669e8 | ||
|
|
6300d242cc | ||
|
|
186579266c | ||
|
|
13490af90c | ||
|
|
23a9c2cd9e | ||
|
|
88631e4a63 | ||
|
|
9e744ce169 | ||
|
|
77a68db8c1 | ||
|
|
27247f253c | ||
|
|
ada7f92c75 | ||
|
|
87db4e88a5 | ||
|
|
277594139b | ||
|
|
79d5e35306 | ||
|
|
cda80b319e | ||
|
|
ab4b024df4 | ||
|
|
7f9cbac232 | ||
|
|
8470ffcb0d | ||
|
|
92c897762b | ||
|
|
36014cfcfd | ||
|
|
df26f731eb | ||
|
|
e39b5a6a72 | ||
|
|
c7138fe36c | ||
|
|
b5deab772d | ||
|
|
4bfeccec74 |
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us address broken functionality
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Steps to reproduce**
|
||||
Any special steps that are required for the bug to appear.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem. If the issue is related to video glitching or poor quality, please include screenshots.
|
||||
|
||||
**Moonlight settings (please complete the following information)**
|
||||
- Have any settings been adjusted from defaults?
|
||||
- If so, which settings have been changed?
|
||||
- Does the problem still occur after reverting settings back to default?
|
||||
|
||||
**Client PC details (please complete the following information)**
|
||||
- OS: [e.g. Windows 10 1803]
|
||||
- Moonlight Version: [e.g. v0.2.0]
|
||||
|
||||
**Logs (please attach)**
|
||||
- On Windows, `Moonlight-###.log` files can be found in `%TEMP%`. Simply type that into the File Explorer path field to navigate there.
|
||||
- On macOS, `Moonlight-###.log` files can be found in `/tmp`. In Finder, press Cmd+Shift+G, then type `/tmp` to navigate there.
|
||||
- On Linux, logs print to the terminal when running via `flatpak run com.moonlight_stream.Moonlight`
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,8 @@
|
||||
# ProBot No Response (https://probot.github.io/apps/no-response/)
|
||||
|
||||
daysUntilClose: 7
|
||||
responseRequiredLabel: 'need more info'
|
||||
closeComment: >
|
||||
This issue has been automatically closed because there was no response to a
|
||||
request for more information from the issue opener. Please leave a comment or
|
||||
open a new issue if you have additional information related to this issue.
|
||||
@@ -0,0 +1,14 @@
|
||||
# ProBot Stale (https://probot.github.io/apps/stale/)
|
||||
|
||||
daysUntilStale: 90
|
||||
daysUntilClose: 7
|
||||
exemptLabels:
|
||||
- accepted
|
||||
- bug
|
||||
- enhancement
|
||||
- meta
|
||||
staleLabel: stale
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs.
|
||||
closeComment: false
|
||||
@@ -1,9 +1,6 @@
|
||||
[submodule "moonlight-common-c/moonlight-common-c"]
|
||||
path = moonlight-common-c/moonlight-common-c
|
||||
url = https://github.com/moonlight-stream/moonlight-common-c.git
|
||||
[submodule "opus/opus"]
|
||||
path = opus/opus
|
||||
url = https://github.com/xiph/opus.git
|
||||
[submodule "qmdnsengine/qmdnsengine"]
|
||||
path = qmdnsengine/qmdnsengine
|
||||
url = https://github.com/cgutman/qmdnsengine.git
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
language: cpp
|
||||
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
compiler: clang
|
||||
- os: linux
|
||||
sudo: required
|
||||
dist: xenial
|
||||
compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: 'ppa:beineri/opt-qt596-xenial'
|
||||
- sourceline: 'ppa:jonathonf/ffmpeg-4'
|
||||
packages:
|
||||
- qt59base
|
||||
- qt59quickcontrols2
|
||||
- qt59svg
|
||||
- qt59multimedia
|
||||
- libesd0-dev
|
||||
- libgl-dev
|
||||
- libegl1-mesa-dev
|
||||
- libxkbcommon-dev
|
||||
- wayland-protocols
|
||||
- libopus-dev
|
||||
- libavcodec-dev
|
||||
- libavutil-dev
|
||||
- libva-dev
|
||||
- libvdpau-dev
|
||||
|
||||
install:
|
||||
- '[ "$TRAVIS_OS_NAME" != osx ] || brew install qt5'
|
||||
- '[ "$TRAVIS_OS_NAME" != osx ] || brew link --force qt5'
|
||||
- if [[ "$TRAVIS_OS_NAME" = "linux" ]]; then export SDL2_VER=2.0.8 && wget https://www.libsdl.org/release/SDL2-$SDL2_VER.tar.gz && tar -xvf SDL2-$SDL2_VER.tar.gz && cd SDL2-$SDL2_VER && ./configure && make && sudo make install && cd ..; fi
|
||||
|
||||
before_script:
|
||||
- '[ "$TRAVIS_OS_NAME" != "linux" ] || source /opt/qt59/bin/qt59-env.sh'
|
||||
- qmake -v
|
||||
- '[[ "$TRAVIS_OS_NAME" != linux || "$CXX" != clang++ ]] || export QMAKESPEC=linux-clang'
|
||||
- '[[ "$TRAVIS_OS_NAME" != linux || "$CXX" != g++ ]] || export QMAKESPEC=linux-g++'
|
||||
- '[ "$TRAVIS_OS_NAME" != osx ] || export QMAKESPEC=macx-clang'
|
||||
|
||||
script:
|
||||
- qmake moonlight-qt.pro -spec $QMAKESPEC
|
||||
- make debug
|
||||
- make release
|
||||
@@ -1,11 +1,30 @@
|
||||
# Moonlight PC
|
||||
|
||||
[Moonlight PC](http://moonlight-stream.com) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield, but built to run on Windows, Mac, and Linux.
|
||||
[Moonlight PC](http://moonlight-stream.com) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield, but built to run on Windows, Mac, and Linux. This client is the successor to [Moonlight Chrome](https://github.com/moonlight-stream/moonlight-chrome) for streaming on PC.
|
||||
|
||||
Moonlight also has mobile versions for [Android](https://github.com/moonlight-stream/moonlight-android) and [iOS](https://github.com/moonlight-stream/moonlight-ios).
|
||||
|
||||
See the [releases page](https://github.com/moonlight-stream/moonlight-qt/releases) for download links for Windows and Mac. For Linux, we're on [Flathub](https://flathub.org/apps/details/com.moonlight_stream.Moonlight).
|
||||
|
||||
You can follow development on our [Discord server](https://discord.gg/6ERtzFY).
|
||||
|
||||
[](https://ci.appveyor.com/project/cgutman/moonlight-qt/branch/master)
|
||||
[](https://travis-ci.org/moonlight-stream/moonlight-qt)
|
||||
|
||||
## Features
|
||||
- Hardware accelerated video decoding on Windows, Mac, and Linux
|
||||
- Supports streaming at up to 120 FPS (high refresh rate monitor recommended)
|
||||
- Supports streaming at 720p, 1080p, 1440p, 4K, and the client PC's native screen resolution
|
||||
- 5.1 surround sound audio
|
||||
- HEVC support for better image quality at reduced bandwidth
|
||||
- Keyboard and mouse support
|
||||
- Gamepad support with SDL gamepad mappings
|
||||
|
||||
## Building
|
||||
1. Install the latest Qt SDK (and optionally, the Qt Creator IDE) from https://www.qt.io/download (select MSVC toolchain on Windows)
|
||||
1. Install the latest Qt SDK (and optionally, the Qt Creator IDE) from https://www.qt.io/download
|
||||
- On Windows, install Visual Studio 2017 and select MSVC 2017 toolchain during Qt installation
|
||||
- On Mac, install the latest version of XCode
|
||||
- On Linux, install your distro equivalents of: `openssl-devel qt5-devel SDL2-devel ffmpeg-devel qt5-qtquickcontrols2-devel libva-devel libvdpau-devel`
|
||||
2. Run `git submodule update --init --recursive` from within `moonlight-qt/`
|
||||
3. Open the project in Qt Creator or build from qmake on the command line
|
||||
|
||||
|
||||
+12
-6
@@ -5,22 +5,28 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>Moonlight</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>Created by Qt/QMake</string>
|
||||
<string>Stream games from your NVIDIA GameStream-enabled PC</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>macos</string>
|
||||
<string>moonlight</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.yourcompany.moonlight-qt</string>
|
||||
<string>com.moonlight-stream.Moonlight</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.10</string>
|
||||
<key>NOTE</key>
|
||||
<string>This file was generated by Qt/QMake.</string>
|
||||
<string>10.11</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.5.1</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.5.1</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Moonlight</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<description>Moonlight Game Streaming</description>
|
||||
<asmv3:application>
|
||||
<asmv3:windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitor</dpiAwareness>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<!-- Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<!-- Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<!-- Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
<!-- Windows Vista -->
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
+102
-35
@@ -1,4 +1,4 @@
|
||||
QT += core quick network quickcontrols2 svg
|
||||
QT += core quick network quickcontrols2 svg multimedia
|
||||
CONFIG += c++11
|
||||
|
||||
unix:!macx {
|
||||
@@ -8,6 +8,12 @@ unix:!macx {
|
||||
TARGET = Moonlight
|
||||
}
|
||||
|
||||
# Support debug and release builds from command line for CI
|
||||
CONFIG += debug_and_release
|
||||
|
||||
# Ensure symbols are always generated
|
||||
CONFIG += force_debug_info
|
||||
|
||||
TEMPLATE = app
|
||||
|
||||
# The following define makes your compiler emit warnings if you use
|
||||
@@ -31,33 +37,42 @@ win32 {
|
||||
LIBS += -L$$PWD/../libs/windows/lib/x64
|
||||
}
|
||||
|
||||
LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib
|
||||
LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib gdi32.lib user32.lib d3d9.lib dwmapi.lib dbghelp.lib
|
||||
}
|
||||
macx {
|
||||
INCLUDEPATH += $$PWD/../libs/mac/include
|
||||
LIBS += -L$$PWD/../libs/mac/lib
|
||||
INCLUDEPATH += $$PWD/../libs/mac/include $$PWD/../libs/mac/Frameworks/SDL2.framework/Versions/A/Headers
|
||||
LIBS += -L$$PWD/../libs/mac/lib -F$$PWD/../libs/mac/Frameworks
|
||||
}
|
||||
|
||||
unix:!macx {
|
||||
CONFIG += link_pkgconfig
|
||||
PKGCONFIG += openssl sdl2
|
||||
LIBS += -ldl
|
||||
PKGCONFIG += openssl sdl2 opus
|
||||
|
||||
packagesExist(libavcodec) {
|
||||
PKGCONFIG += libavcodec libavutil
|
||||
CONFIG += ffmpeg
|
||||
|
||||
packagesExist(libva) {
|
||||
packagesExist(libva-x11) {
|
||||
CONFIG += libva-x11
|
||||
}
|
||||
packagesExist(libva-wayland) {
|
||||
CONFIG += libva-wayland
|
||||
}
|
||||
CONFIG += libva
|
||||
}
|
||||
|
||||
packagesExist(vdpau) {
|
||||
CONFIG += libvdpau
|
||||
}
|
||||
}
|
||||
}
|
||||
win32 {
|
||||
LIBS += -llibssl -llibcrypto -lSDL2 -lavcodec -lavutil
|
||||
LIBS += -llibssl -llibcrypto -lSDL2 -lavcodec -lavutil -lopus
|
||||
CONFIG += ffmpeg
|
||||
}
|
||||
macx {
|
||||
LIBS += -lssl -lcrypto -lSDL2 -lavcodec.58 -lavutil.56
|
||||
LIBS += -lssl -lcrypto -lavcodec.58 -lavutil.56 -lopus -framework SDL2
|
||||
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit
|
||||
CONFIG += ffmpeg
|
||||
}
|
||||
@@ -72,9 +87,14 @@ SOURCES += \
|
||||
settings/streamingpreferences.cpp \
|
||||
streaming/input.cpp \
|
||||
streaming/session.cpp \
|
||||
streaming/audio.cpp \
|
||||
streaming/audio/audio.cpp \
|
||||
streaming/audio/renderers/sdlaud.cpp \
|
||||
streaming/audio/renderers/qtaud.cpp \
|
||||
gui/computermodel.cpp \
|
||||
gui/appmodel.cpp
|
||||
gui/appmodel.cpp \
|
||||
streaming/streamutils.cpp \
|
||||
backend/autoupdatechecker.cpp \
|
||||
path.cpp
|
||||
|
||||
HEADERS += \
|
||||
utils.h \
|
||||
@@ -86,9 +106,15 @@ HEADERS += \
|
||||
settings/streamingpreferences.h \
|
||||
streaming/input.hpp \
|
||||
streaming/session.hpp \
|
||||
streaming/audio/renderers/renderer.h \
|
||||
streaming/audio/renderers/sdl.h \
|
||||
streaming/audio/renderers/qtaud.h \
|
||||
gui/computermodel.h \
|
||||
gui/appmodel.h \
|
||||
streaming/video/decoder.h
|
||||
streaming/video/decoder.h \
|
||||
streaming/streamutils.h \
|
||||
backend/autoupdatechecker.h \
|
||||
path.h
|
||||
|
||||
# Platform-specific renderers and decoders
|
||||
ffmpeg {
|
||||
@@ -97,18 +123,44 @@ ffmpeg {
|
||||
DEFINES += HAVE_FFMPEG
|
||||
SOURCES += \
|
||||
streaming/video/ffmpeg.cpp \
|
||||
streaming/video/ffmpeg-renderers/sdl.cpp
|
||||
streaming/video/ffmpeg-renderers/sdlvid.cpp \
|
||||
streaming/video/ffmpeg-renderers/pacer/pacer.cpp \
|
||||
streaming/video/ffmpeg-renderers/pacer/nullthreadedvsyncsource.cpp
|
||||
|
||||
|
||||
HEADERS += \
|
||||
streaming/video/ffmpeg.h \
|
||||
streaming/video/ffmpeg-renderers/renderer.h
|
||||
streaming/video/ffmpeg-renderers/renderer.h \
|
||||
streaming/video/ffmpeg-renderers/pacer/pacer.h \
|
||||
streaming/video/ffmpeg-renderers/pacer/nullthreadedvsyncsource.h
|
||||
}
|
||||
libva {
|
||||
message(VAAPI renderer selected)
|
||||
|
||||
PKGCONFIG += libva
|
||||
DEFINES += HAVE_LIBVA
|
||||
SOURCES += streaming/video/ffmpeg-renderers/vaapi.cpp
|
||||
HEADERS += streaming/video/ffmpeg-renderers/vaapi.h
|
||||
}
|
||||
libva-x11 {
|
||||
message(VAAPI X11 support enabled)
|
||||
|
||||
PKGCONFIG += libva-x11
|
||||
DEFINES += HAVE_LIBVA_X11
|
||||
}
|
||||
libva-wayland {
|
||||
message(VAAPI Wayland support enabled)
|
||||
|
||||
PKGCONFIG += libva-wayland
|
||||
DEFINES += HAVE_LIBVA_WAYLAND
|
||||
}
|
||||
libvdpau {
|
||||
message(VDPAU renderer selected)
|
||||
|
||||
DEFINES += HAVE_LIBVDPAU
|
||||
SOURCES += streaming/video/ffmpeg-renderers/vdpau.cpp
|
||||
HEADERS += streaming/video/ffmpeg-renderers/vdpau.h
|
||||
}
|
||||
config_SLVideo {
|
||||
message(SLVideo decoder/renderer selected)
|
||||
|
||||
@@ -120,14 +172,24 @@ config_SLVideo {
|
||||
win32 {
|
||||
message(DXVA2 renderer selected)
|
||||
|
||||
SOURCES += streaming/video/ffmpeg-renderers/dxva2.cpp
|
||||
HEADERS += streaming/video/ffmpeg-renderers/dxva2.h
|
||||
SOURCES += \
|
||||
streaming/video/ffmpeg-renderers/dxva2.cpp \
|
||||
streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp
|
||||
|
||||
HEADERS += \
|
||||
streaming/video/ffmpeg-renderers/dxva2.h \
|
||||
streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h
|
||||
}
|
||||
macx {
|
||||
message(VideoToolbox renderer selected)
|
||||
|
||||
SOURCES += streaming/video/ffmpeg-renderers/vt.mm
|
||||
HEADERS += streaming/video/ffmpeg-renderers/vt.h
|
||||
SOURCES += \
|
||||
streaming/video/ffmpeg-renderers/vt.mm \
|
||||
streaming/video/ffmpeg-renderers/pacer/displaylinkvsyncsource.mm
|
||||
|
||||
HEADERS += \
|
||||
streaming/video/ffmpeg-renderers/vt.h \
|
||||
streaming/video/ffmpeg-renderers/pacer/displaylinkvsyncsource.h
|
||||
}
|
||||
|
||||
RESOURCES += \
|
||||
@@ -147,13 +209,6 @@ else:unix: LIBS += -L$$OUT_PWD/../moonlight-common-c/ -lmoonlight-common-c
|
||||
INCLUDEPATH += $$PWD/../moonlight-common-c/moonlight-common-c/src
|
||||
DEPENDPATH += $$PWD/../moonlight-common-c/moonlight-common-c/src
|
||||
|
||||
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../opus/release/ -lopus
|
||||
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../opus/debug/ -lopus
|
||||
else:unix: LIBS += -L$$OUT_PWD/../opus/ -lopus
|
||||
|
||||
INCLUDEPATH += $$PWD/../opus/opus/include
|
||||
DEPENDPATH += $$PWD/../opus/opus/include
|
||||
|
||||
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../qmdnsengine/release/ -lqmdnsengine
|
||||
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../qmdnsengine/debug/ -lqmdnsengine
|
||||
else:unix: LIBS += -L$$OUT_PWD/../qmdnsengine/ -lqmdnsengine
|
||||
@@ -168,30 +223,42 @@ unix:!macx: {
|
||||
isEmpty(BINDIR) {
|
||||
BINDIR = bin
|
||||
}
|
||||
isEmpty(DATADIR) {
|
||||
DATADIR = share
|
||||
}
|
||||
|
||||
target.path = $$PREFIX/$$BINDIR/
|
||||
|
||||
desktop.files = deploy/linux/com.moonlight_stream.Moonlight.desktop
|
||||
desktop.path = $$PREFIX/share/applications/
|
||||
desktop.path = $$PREFIX/$$DATADIR/applications/
|
||||
|
||||
icons.files = res/moonlight.svg
|
||||
icons.path = $$PREFIX/share/icons/hicolor/scalable/apps/
|
||||
icons.path = $$PREFIX/$$DATADIR/icons/hicolor/scalable/apps/
|
||||
|
||||
appdata.files = deploy/linux/com.moonlight_stream.Moonlight.appdata.xml
|
||||
appdata.path = $$PREFIX/share/metainfo/
|
||||
appstream.files = deploy/linux/com.moonlight_stream.Moonlight.appdata.xml
|
||||
appstream.path = $$PREFIX/$$DATADIR/metainfo/
|
||||
|
||||
INSTALLS += target desktop icons appdata
|
||||
appdata.files = gamecontrollerdb.txt
|
||||
appdata.path = "$$PREFIX/$$DATADIR/Moonlight Game Streaming Project/Moonlight/"
|
||||
|
||||
INSTALLS += target appdata desktop icons appstream
|
||||
}
|
||||
|
||||
win32 {
|
||||
RC_ICONS = moonlight.ico
|
||||
}
|
||||
QMAKE_TARGET_COMPANY = Moonlight Game Streaming Project
|
||||
QMAKE_TARGET_DESCRIPTION = Moonlight Game Streaming Client
|
||||
QMAKE_TARGET_PRODUCT = Moonlight
|
||||
|
||||
CONFIG -= embed_manifest_exe
|
||||
QMAKE_LFLAGS += /MANIFEST:embed /MANIFESTINPUT:$${PWD}/Moonlight.exe.manifest
|
||||
}
|
||||
macx {
|
||||
QMAKE_INFO_PLIST = $$PWD/Info.plist
|
||||
APP_QML_FILES.files = res/macos.icns
|
||||
APP_QML_FILES.path = Contents/Resources
|
||||
QMAKE_BUNDLE_DATA += APP_QML_FILES
|
||||
|
||||
APP_BUNDLE_RESOURCES.files = moonlight.icns gamecontrollerdb.txt
|
||||
APP_BUNDLE_RESOURCES.path = Contents/Resources
|
||||
QMAKE_BUNDLE_DATA += APP_BUNDLE_RESOURCES
|
||||
}
|
||||
|
||||
VERSION = 0.0.3
|
||||
VERSION = 0.5.1
|
||||
DEFINES += VERSION_STR=\\\"0.5.1\\\"
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
#include "autoupdatechecker.h"
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
AutoUpdateChecker::AutoUpdateChecker(QObject *parent) :
|
||||
QObject(parent),
|
||||
m_Nam(this)
|
||||
{
|
||||
// Never communicate over HTTP
|
||||
m_Nam.setStrictTransportSecurityEnabled(true);
|
||||
|
||||
connect(&m_Nam, SIGNAL(finished(QNetworkReply*)),
|
||||
this, SLOT(handleUpdateCheckRequestFinished(QNetworkReply*)));
|
||||
|
||||
QString currentVersion(VERSION_STR);
|
||||
qDebug() << "Current Moonlight version:" << currentVersion;
|
||||
parseStringToVersionQuad(currentVersion, m_CurrentVersionQuad);
|
||||
|
||||
// Should at least have a 1.0-style version number
|
||||
Q_ASSERT(m_CurrentVersionQuad.count() > 1);
|
||||
}
|
||||
|
||||
void AutoUpdateChecker::start()
|
||||
{
|
||||
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN) // Only run update checker on platforms without auto-update
|
||||
// We'll get a callback when this is finished
|
||||
QUrl url("https://moonlight-stream.com/updates/qt.json");
|
||||
m_Nam.get(QNetworkRequest(url));
|
||||
#endif
|
||||
}
|
||||
|
||||
void AutoUpdateChecker::parseStringToVersionQuad(QString& string, QVector<int>& version)
|
||||
{
|
||||
QStringList list = string.split('.');
|
||||
for (const QString& component : list) {
|
||||
version.append(component.toInt());
|
||||
}
|
||||
}
|
||||
|
||||
void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
|
||||
{
|
||||
Q_ASSERT(reply->isFinished());
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QTextStream stream(reply);
|
||||
stream.setCodec("UTF-8");
|
||||
|
||||
// Read all data and queue the reply for deletion
|
||||
QString jsonString = stream.readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonString.toUtf8(), &error);
|
||||
if (jsonDoc.isNull()) {
|
||||
qWarning() << "Update manifest malformed:" << error.errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray array = jsonDoc.array();
|
||||
if (array.isEmpty()) {
|
||||
qWarning() << "Update manifest doesn't contain an array";
|
||||
return;
|
||||
}
|
||||
|
||||
for (QJsonValueRef updateEntry : array) {
|
||||
if (updateEntry.isObject()) {
|
||||
QJsonObject updateObj = updateEntry.toObject();
|
||||
if (!updateObj.contains("platform") ||
|
||||
!updateObj.contains("arch") ||
|
||||
!updateObj.contains("version") ||
|
||||
!updateObj.contains("browser_url")) {
|
||||
qWarning() << "Update manifest entry missing vital field";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!updateObj["platform"].isString() ||
|
||||
!updateObj["arch"].isString() ||
|
||||
!updateObj["version"].isString() ||
|
||||
!updateObj["browser_url"].isString()) {
|
||||
qWarning() << "Update manifest entry has unexpected vital field type";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (updateObj["arch"] == QSysInfo::buildCpuArchitecture() &&
|
||||
updateObj["platform"] == QSysInfo::productType()) {
|
||||
qDebug() << "Found update manifest match for current platform";
|
||||
|
||||
QString latestVersion = updateObj["version"].toString();
|
||||
qDebug() << "Latest version of Moonlight for this platform is:" << latestVersion;
|
||||
|
||||
QVector<int> latestVersionQuad;
|
||||
|
||||
parseStringToVersionQuad(latestVersion, latestVersionQuad);
|
||||
|
||||
for (int i = 0;; i++) {
|
||||
int latestVer = 0;
|
||||
int currentVer = 0;
|
||||
|
||||
// Treat missing decimal places as 0
|
||||
if (i < latestVersionQuad.count()) {
|
||||
latestVer = latestVersionQuad[i];
|
||||
}
|
||||
if (i < m_CurrentVersionQuad.count()) {
|
||||
currentVer = m_CurrentVersionQuad[i];
|
||||
}
|
||||
if (i >= latestVersionQuad.count() && i >= m_CurrentVersionQuad.count()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (currentVer < latestVer) {
|
||||
qDebug() << "Update available";
|
||||
|
||||
emit onUpdateAvailable(updateObj["browser_url"].toString());
|
||||
return;
|
||||
}
|
||||
else if (currentVer > latestVer) {
|
||||
qDebug() << "Update manifest version lower than current version";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Update manifest version equal to current version";
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
qWarning() << "Update manifest contained unrecognized entry:" << updateEntry.toString();
|
||||
}
|
||||
}
|
||||
|
||||
qWarning() << "No entry in update manifest found for current platform: "
|
||||
<< QSysInfo::buildCpuArchitecture() << QSysInfo::productType();
|
||||
}
|
||||
else {
|
||||
qWarning() << "Update checking failed with error: " << reply->error();
|
||||
reply->deleteLater();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
class AutoUpdateChecker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AutoUpdateChecker(QObject *parent = nullptr);
|
||||
|
||||
Q_INVOKABLE void start();
|
||||
|
||||
signals:
|
||||
void onUpdateAvailable(QString url);
|
||||
|
||||
private slots:
|
||||
void handleUpdateCheckRequestFinished(QNetworkReply* reply);
|
||||
|
||||
private:
|
||||
void parseStringToVersionQuad(QString& string, QVector<int>& version);
|
||||
|
||||
QVector<int> m_CurrentVersionQuad;
|
||||
QNetworkAccessManager m_Nam;
|
||||
};
|
||||
@@ -1,14 +1,19 @@
|
||||
#include "boxartmanager.h"
|
||||
#include "../path.h"
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QImageReader>
|
||||
#include <QImageWriter>
|
||||
|
||||
BoxArtManager::BoxArtManager(QObject *parent) :
|
||||
QObject(parent),
|
||||
m_BoxArtDir(
|
||||
QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/boxart")
|
||||
m_BoxArtDir(Path::getBoxArtCacheDir()),
|
||||
m_ThreadPool(this)
|
||||
{
|
||||
// 4 is a good balance between fast loading for large
|
||||
// app grids and not crushing GFE with tons of requests
|
||||
// and causing UI jank from constantly stalling to decode
|
||||
// new images.
|
||||
m_ThreadPool.setMaxThreadCount(4);
|
||||
if (!m_BoxArtDir.exists()) {
|
||||
m_BoxArtDir.mkpath(".");
|
||||
}
|
||||
@@ -42,7 +47,7 @@ QUrl BoxArtManager::loadBoxArt(NvComputer* computer, NvApp& app)
|
||||
// If we get here, we need to fetch asynchronously.
|
||||
// Kick off a worker on our thread pool to do just that.
|
||||
NetworkBoxArtLoadTask* netLoadTask = new NetworkBoxArtLoadTask(this, computer, app);
|
||||
QThreadPool::globalInstance()->start(netLoadTask);
|
||||
m_ThreadPool.start(netLoadTask);
|
||||
|
||||
// Return the placeholder then we can notify the caller
|
||||
// later when the real image is ready.
|
||||
|
||||
@@ -36,6 +36,7 @@ private:
|
||||
getFilePathForBoxArt(NvComputer* computer, int appId);
|
||||
|
||||
QDir m_BoxArtDir;
|
||||
QThreadPool m_ThreadPool;
|
||||
};
|
||||
|
||||
class NetworkBoxArtLoadTask : public QObject, public QRunnable
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "computermanager.h"
|
||||
#include "nvhttp.h"
|
||||
#include "settings/streamingpreferences.h"
|
||||
|
||||
#include <QThread>
|
||||
#include <QUdpSocket>
|
||||
@@ -51,6 +52,8 @@ NvComputer::NvComputer(QSettings& settings)
|
||||
this->appVersion = nullptr;
|
||||
this->maxLumaPixelsHEVC = 0;
|
||||
this->serverCodecModeSupport = 0;
|
||||
this->pendingQuit = false;
|
||||
this->gpuModel = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -90,7 +93,7 @@ void NvComputer::sortAppList()
|
||||
NvComputer::NvComputer(QString address, QString serverInfo)
|
||||
{
|
||||
this->name = NvHTTP::getXmlString(serverInfo, "hostname");
|
||||
if (this->name.isNull()) {
|
||||
if (this->name.isEmpty()) {
|
||||
this->name = "UNKNOWN";
|
||||
}
|
||||
|
||||
@@ -104,7 +107,7 @@ NvComputer::NvComputer(QString address, QString serverInfo)
|
||||
}
|
||||
|
||||
QString codecSupport = NvHTTP::getXmlString(serverInfo, "ServerCodecModeSupport");
|
||||
if (!codecSupport.isNull()) {
|
||||
if (!codecSupport.isEmpty()) {
|
||||
this->serverCodecModeSupport = codecSupport.toInt();
|
||||
}
|
||||
else {
|
||||
@@ -112,7 +115,7 @@ NvComputer::NvComputer(QString address, QString serverInfo)
|
||||
}
|
||||
|
||||
QString maxLumaPixelsHEVC = NvHTTP::getXmlString(serverInfo, "MaxLumaPixelsHEVC");
|
||||
if (!maxLumaPixelsHEVC.isNull()) {
|
||||
if (!maxLumaPixelsHEVC.isEmpty()) {
|
||||
this->maxLumaPixelsHEVC = maxLumaPixelsHEVC.toInt();
|
||||
}
|
||||
else {
|
||||
@@ -133,8 +136,10 @@ NvComputer::NvComputer(QString address, QString serverInfo)
|
||||
this->currentGameId = NvHTTP::getCurrentGame(serverInfo);
|
||||
this->appVersion = NvHTTP::getXmlString(serverInfo, "appversion");
|
||||
this->gfeVersion = NvHTTP::getXmlString(serverInfo, "GfeVersion");
|
||||
this->gpuModel = NvHTTP::getXmlString(serverInfo, "gputype");
|
||||
this->activeAddress = address;
|
||||
this->state = NvComputer::CS_ONLINE;
|
||||
this->pendingQuit = false;
|
||||
}
|
||||
|
||||
bool NvComputer::wake()
|
||||
@@ -144,7 +149,7 @@ bool NvComputer::wake()
|
||||
return true;
|
||||
}
|
||||
|
||||
if (macAddress.isNull()) {
|
||||
if (macAddress.isEmpty()) {
|
||||
qWarning() << name << "has no MAC address stored";
|
||||
return false;
|
||||
}
|
||||
@@ -173,6 +178,11 @@ bool NvComputer::wake()
|
||||
for (QString& addressString : addressList) {
|
||||
QHostInfo hostInfo = QHostInfo::fromName(addressString);
|
||||
|
||||
if (hostInfo.error() != QHostInfo::NoError) {
|
||||
qWarning() << "Error resolving" << addressString << ":" << hostInfo.errorString();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try all IP addresses that this string resolves to
|
||||
for (QHostAddress& address : hostInfo.addresses()) {
|
||||
QUdpSocket sock;
|
||||
@@ -207,7 +217,7 @@ QVector<QString> NvComputer::uniqueAddresses()
|
||||
|
||||
// Prune duplicates (always giving precedence to the first)
|
||||
for (int i = 0; i < uniqueAddressList.count(); i++) {
|
||||
if (uniqueAddressList[i].isEmpty() || uniqueAddressList[i].isNull()) {
|
||||
if (uniqueAddressList[i].isEmpty()) {
|
||||
uniqueAddressList.remove(i);
|
||||
i--;
|
||||
continue;
|
||||
@@ -264,6 +274,7 @@ bool NvComputer::update(NvComputer& that)
|
||||
ASSIGN_IF_CHANGED(gfeVersion);
|
||||
ASSIGN_IF_CHANGED(appVersion);
|
||||
ASSIGN_IF_CHANGED(maxLumaPixelsHEVC);
|
||||
ASSIGN_IF_CHANGED(gpuModel);
|
||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(appList);
|
||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(displayModes);
|
||||
return changed;
|
||||
@@ -358,17 +369,24 @@ void ComputerManager::startPolling()
|
||||
return;
|
||||
}
|
||||
|
||||
// Start an MDNS query for GameStream hosts
|
||||
m_MdnsBrowser = new QMdnsEngine::Browser(&m_MdnsServer, "_nvstream._tcp.local.", &m_MdnsCache);
|
||||
connect(m_MdnsBrowser, &QMdnsEngine::Browser::serviceAdded,
|
||||
this, [this](const QMdnsEngine::Service& service) {
|
||||
qInfo() << "Discovered mDNS host:" << service.hostname();
|
||||
StreamingPreferences prefs;
|
||||
|
||||
MdnsPendingComputer* pendingComputer = new MdnsPendingComputer(&m_MdnsServer, &m_MdnsCache, service);
|
||||
connect(pendingComputer, SIGNAL(resolvedv4(MdnsPendingComputer*,QHostAddress)),
|
||||
this, SLOT(handleMdnsServiceResolved(MdnsPendingComputer*,QHostAddress)));
|
||||
m_PendingResolution.append(pendingComputer);
|
||||
});
|
||||
if (prefs.enableMdns) {
|
||||
// Start an MDNS query for GameStream hosts
|
||||
m_MdnsBrowser = new QMdnsEngine::Browser(&m_MdnsServer, "_nvstream._tcp.local.", &m_MdnsCache);
|
||||
connect(m_MdnsBrowser, &QMdnsEngine::Browser::serviceAdded,
|
||||
this, [this](const QMdnsEngine::Service& service) {
|
||||
qInfo() << "Discovered mDNS host:" << service.hostname();
|
||||
|
||||
MdnsPendingComputer* pendingComputer = new MdnsPendingComputer(&m_MdnsServer, &m_MdnsCache, service);
|
||||
connect(pendingComputer, SIGNAL(resolvedv4(MdnsPendingComputer*,QHostAddress)),
|
||||
this, SLOT(handleMdnsServiceResolved(MdnsPendingComputer*,QHostAddress)));
|
||||
m_PendingResolution.append(pendingComputer);
|
||||
});
|
||||
}
|
||||
else {
|
||||
qWarning() << "mDNS is disabled by user preference";
|
||||
}
|
||||
|
||||
// Start polling threads for each known host
|
||||
QMapIterator<QString, NvComputer*> i(m_KnownHosts);
|
||||
@@ -446,6 +464,15 @@ void ComputerManager::pairHost(NvComputer* computer, QString pin)
|
||||
QThreadPool::globalInstance()->start(pairing);
|
||||
}
|
||||
|
||||
void ComputerManager::quitRunningApp(NvComputer* computer)
|
||||
{
|
||||
QWriteLocker lock(&computer->lock);
|
||||
computer->pendingQuit = true;
|
||||
|
||||
PendingQuitTask* quit = new PendingQuitTask(this, computer);
|
||||
QThreadPool::globalInstance()->start(quit);
|
||||
}
|
||||
|
||||
void ComputerManager::stopPollingAsync()
|
||||
{
|
||||
QWriteLocker lock(&m_Lock);
|
||||
@@ -497,6 +524,11 @@ ComputerManager::handleComputerStateChanged(NvComputer* computer)
|
||||
{
|
||||
emit computerStateChanged(computer);
|
||||
|
||||
if (computer->pendingQuit && computer->currentGameId == 0) {
|
||||
computer->pendingQuit = false;
|
||||
emit quitAppCompleted(QVariant());
|
||||
}
|
||||
|
||||
// Save updated hosts to QSettings
|
||||
saveHosts();
|
||||
}
|
||||
|
||||
@@ -16,10 +16,14 @@
|
||||
class NvComputer
|
||||
{
|
||||
friend class PcMonitorThread;
|
||||
friend class ComputerManager;
|
||||
friend class PendingQuitTask;
|
||||
|
||||
private:
|
||||
void sortAppList();
|
||||
|
||||
bool pendingQuit;
|
||||
|
||||
public:
|
||||
explicit NvComputer(QString address, QString serverInfo);
|
||||
|
||||
@@ -61,6 +65,7 @@ public:
|
||||
QVector<NvDisplayMode> displayModes;
|
||||
int maxLumaPixelsHEVC;
|
||||
int serverCodecModeSupport;
|
||||
QString gpuModel;
|
||||
|
||||
// Persisted traits
|
||||
QString localAddress;
|
||||
@@ -98,7 +103,7 @@ private:
|
||||
|
||||
QString serverInfo;
|
||||
try {
|
||||
serverInfo = http.getServerInfo();
|
||||
serverInfo = http.getServerInfo(NvHTTP::NvLogLevel::NONE);
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
@@ -148,31 +153,31 @@ private:
|
||||
int pollsSinceLastAppListFetch = POLLS_PER_APPLIST_FETCH;
|
||||
while (!isInterruptionRequested()) {
|
||||
bool stateChanged = false;
|
||||
for (int i = 0; i < TRIES_BEFORE_OFFLINING; i++) {
|
||||
bool online = false;
|
||||
bool wasOnline = m_Computer->state == NvComputer::CS_ONLINE;
|
||||
for (int i = 0; i < TRIES_BEFORE_OFFLINING && !online; i++) {
|
||||
for (auto& address : m_Computer->uniqueAddresses()) {
|
||||
if (isInterruptionRequested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tryPollComputer(address, stateChanged)) {
|
||||
if (!wasOnline) {
|
||||
qInfo() << m_Computer->name << "is now online at" << m_Computer->activeAddress;
|
||||
}
|
||||
online = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// No need to continue retrying if we're online
|
||||
if (m_Computer->state == NvComputer::CS_ONLINE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we failed after all retry attempts
|
||||
// Note: we don't need to acquire the read lock here,
|
||||
// because we're on the writing thread.
|
||||
if (m_Computer->state != NvComputer::CS_ONLINE) {
|
||||
if (m_Computer->state != NvComputer::CS_OFFLINE) {
|
||||
m_Computer->state = NvComputer::CS_OFFLINE;
|
||||
stateChanged = true;
|
||||
}
|
||||
if (!online && m_Computer->state != NvComputer::CS_OFFLINE) {
|
||||
qInfo() << m_Computer->name << "is now offline";
|
||||
m_Computer->state = NvComputer::CS_OFFLINE;
|
||||
stateChanged = true;
|
||||
}
|
||||
|
||||
// Grab the applist if it's empty or it's been long enough that we need to refresh
|
||||
@@ -180,6 +185,13 @@ private:
|
||||
if (m_Computer->state == NvComputer::CS_ONLINE &&
|
||||
m_Computer->pairState == NvComputer::PS_PAIRED &&
|
||||
(m_Computer->appList.isEmpty() || pollsSinceLastAppListFetch >= POLLS_PER_APPLIST_FETCH)) {
|
||||
// Notify prior to the app list poll since it may take a while, and we don't
|
||||
// want to delay onlining of a machine, especially if we already have a cached list.
|
||||
if (stateChanged) {
|
||||
emit computerStateChanged(m_Computer);
|
||||
stateChanged = false;
|
||||
}
|
||||
|
||||
if (updateAppList(stateChanged)) {
|
||||
pollsSinceLastAppListFetch = 0;
|
||||
}
|
||||
@@ -259,6 +271,8 @@ public:
|
||||
|
||||
void pairHost(NvComputer* computer, QString pin);
|
||||
|
||||
void quitRunningApp(NvComputer* computer);
|
||||
|
||||
QVector<NvComputer*> getComputers();
|
||||
|
||||
// computer is deleted inside this call
|
||||
@@ -271,6 +285,8 @@ signals:
|
||||
|
||||
void computerAddCompleted(QVariant success);
|
||||
|
||||
void quitAppCompleted(QVariant error);
|
||||
|
||||
private slots:
|
||||
void handleComputerStateChanged(NvComputer* computer);
|
||||
|
||||
@@ -338,6 +354,49 @@ private:
|
||||
QString m_Pin;
|
||||
};
|
||||
|
||||
class PendingQuitTask : public QObject, public QRunnable
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PendingQuitTask(ComputerManager* computerManager, NvComputer* computer)
|
||||
: m_Computer(computer)
|
||||
{
|
||||
connect(this, &PendingQuitTask::quitAppFailed,
|
||||
computerManager, &ComputerManager::quitAppCompleted);
|
||||
}
|
||||
|
||||
signals:
|
||||
void quitAppFailed(QString error);
|
||||
|
||||
private:
|
||||
void run()
|
||||
{
|
||||
NvHTTP http(m_Computer->activeAddress);
|
||||
|
||||
try {
|
||||
if (m_Computer->currentGameId != 0) {
|
||||
http.quitApp();
|
||||
}
|
||||
} catch (const GfeHttpResponseException& e) {
|
||||
{
|
||||
QWriteLocker lock(&m_Computer->lock);
|
||||
m_Computer->pendingQuit = false;
|
||||
}
|
||||
if (e.getStatusCode() == 599) {
|
||||
// 599 is a special code we make a custom message for
|
||||
emit quitAppFailed("The running game wasn't started by this PC. "
|
||||
"You must quit the game on the host PC manually or use the device that originally started the game.");
|
||||
}
|
||||
else {
|
||||
emit quitAppFailed(e.toQString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NvComputer* m_Computer;
|
||||
};
|
||||
|
||||
class PendingAddTask : public QObject, public QRunnable
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -364,9 +423,11 @@ private:
|
||||
{
|
||||
NvHTTP http(m_Address);
|
||||
|
||||
qInfo() << "Processing new PC at" << m_Address << "from" << (m_Mdns ? "mDNS" : "user");
|
||||
|
||||
QString serverInfo;
|
||||
try {
|
||||
serverInfo = http.getServerInfo();
|
||||
serverInfo = http.getServerInfo(NvHTTP::NvLogLevel::VERBOSE);
|
||||
} catch (...) {
|
||||
if (!m_Mdns) {
|
||||
emit computerAddCompleted(false);
|
||||
@@ -402,6 +463,7 @@ private:
|
||||
|
||||
// Tell our client if something changed
|
||||
if (changed) {
|
||||
qInfo() << existingComputer->name << "is now at" << existingComputer->activeAddress;
|
||||
emit computerStateChanged(existingComputer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,12 +185,12 @@ IdentityManager::getSslConfig()
|
||||
QString
|
||||
IdentityManager::getUniqueId()
|
||||
{
|
||||
if (m_CachedUniqueId.isNull()) {
|
||||
if (m_CachedUniqueId.isEmpty()) {
|
||||
QSettings settings;
|
||||
|
||||
// Load the unique ID from settings
|
||||
m_CachedUniqueId = settings.value(SER_UNIQUEID).toString();
|
||||
if (!m_CachedUniqueId.isEmpty() && !m_CachedUniqueId.isNull()) {
|
||||
if (!m_CachedUniqueId.isEmpty()) {
|
||||
qInfo() << "Loaded unique ID from settings:" << m_CachedUniqueId;
|
||||
}
|
||||
else {
|
||||
|
||||
+33
-13
@@ -16,6 +16,8 @@
|
||||
NvHTTP::NvHTTP(QString address) :
|
||||
m_Address(address)
|
||||
{
|
||||
Q_ASSERT(!address.isEmpty());
|
||||
|
||||
m_BaseUrlHttp.setScheme("http");
|
||||
m_BaseUrlHttps.setScheme("https");
|
||||
m_BaseUrlHttp.setHost(address);
|
||||
@@ -27,9 +29,15 @@ NvHTTP::NvHTTP(QString address) :
|
||||
QVector<int>
|
||||
NvHTTP::parseQuad(QString quad)
|
||||
{
|
||||
QStringList parts = quad.split(".");
|
||||
QVector<int> ret;
|
||||
|
||||
// Return an empty vector for old GFE versions
|
||||
// that were missing GfeVersion.
|
||||
if (quad.isEmpty()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
QStringList parts = quad.split(".");
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
ret.append(parts.at(i).toInt());
|
||||
@@ -56,7 +64,7 @@ NvHTTP::getCurrentGame(QString serverInfo)
|
||||
}
|
||||
|
||||
QString
|
||||
NvHTTP::getServerInfo()
|
||||
NvHTTP::getServerInfo(NvLogLevel logLevel)
|
||||
{
|
||||
QString serverInfo;
|
||||
|
||||
@@ -67,7 +75,8 @@ NvHTTP::getServerInfo()
|
||||
serverInfo = openConnectionToString(m_BaseUrlHttps,
|
||||
"serverinfo",
|
||||
nullptr,
|
||||
true);
|
||||
true,
|
||||
logLevel);
|
||||
// Throws if the request failed
|
||||
verifyResponseStatus(serverInfo);
|
||||
}
|
||||
@@ -79,7 +88,8 @@ NvHTTP::getServerInfo()
|
||||
serverInfo = openConnectionToString(m_BaseUrlHttp,
|
||||
"serverinfo",
|
||||
nullptr,
|
||||
true);
|
||||
true,
|
||||
logLevel);
|
||||
verifyResponseStatus(serverInfo);
|
||||
}
|
||||
else
|
||||
@@ -185,7 +195,7 @@ NvHTTP::quitApp()
|
||||
|
||||
// Newer GFE versions will just return success even if quitting fails
|
||||
// if we're not the original requestor.
|
||||
if (getCurrentGame(getServerInfo()) != 0) {
|
||||
if (getCurrentGame(getServerInfo(NvHTTP::ERROR)) != 0) {
|
||||
// Generate a synthetic GfeResponseException letting the caller know
|
||||
// that they can't kill someone else's stream.
|
||||
throw GfeHttpResponseException(599, "");
|
||||
@@ -225,7 +235,8 @@ NvHTTP::getAppList()
|
||||
QString appxml = openConnectionToString(m_BaseUrlHttps,
|
||||
"applist",
|
||||
nullptr,
|
||||
true);
|
||||
true,
|
||||
NvLogLevel::ERROR);
|
||||
verifyResponseStatus(appxml);
|
||||
|
||||
QXmlStreamReader xmlReader(appxml);
|
||||
@@ -292,7 +303,8 @@ NvHTTP::getBoxArt(int appId)
|
||||
"appasset",
|
||||
"appid="+QString::number(appId)+
|
||||
"&AssetType=2&AssetIdx=0",
|
||||
true);
|
||||
true,
|
||||
NvLogLevel::VERBOSE);
|
||||
QImage image = QImageReader(reply).read();
|
||||
delete reply;
|
||||
|
||||
@@ -338,9 +350,10 @@ QString
|
||||
NvHTTP::openConnectionToString(QUrl baseUrl,
|
||||
QString command,
|
||||
QString arguments,
|
||||
bool enableTimeout)
|
||||
bool enableTimeout,
|
||||
NvLogLevel logLevel)
|
||||
{
|
||||
QNetworkReply* reply = openConnection(baseUrl, command, arguments, enableTimeout);
|
||||
QNetworkReply* reply = openConnection(baseUrl, command, arguments, enableTimeout, logLevel);
|
||||
QString ret;
|
||||
|
||||
QTextStream stream(reply);
|
||||
@@ -355,7 +368,8 @@ QNetworkReply*
|
||||
NvHTTP::openConnection(QUrl baseUrl,
|
||||
QString command,
|
||||
QString arguments,
|
||||
bool enableTimeout)
|
||||
bool enableTimeout,
|
||||
NvLogLevel logLevel)
|
||||
{
|
||||
// Build a URL for the request
|
||||
QUrl url(baseUrl);
|
||||
@@ -381,13 +395,17 @@ NvHTTP::openConnection(QUrl baseUrl,
|
||||
{
|
||||
QTimer::singleShot(REQUEST_TIMEOUT_MS, &loop, SLOT(quit()));
|
||||
}
|
||||
qInfo() << "Executing request:" << url.toString();
|
||||
if (logLevel >= NvLogLevel::VERBOSE) {
|
||||
qInfo() << "Executing request:" << url.toString();
|
||||
}
|
||||
loop.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
// Abort the request if it timed out
|
||||
if (!reply->isFinished())
|
||||
{
|
||||
qWarning() << "Aborting timed out request for" << url.toString();
|
||||
if (logLevel >= NvLogLevel::ERROR) {
|
||||
qWarning() << "Aborting timed out request for" << url.toString();
|
||||
}
|
||||
reply->abort();
|
||||
}
|
||||
|
||||
@@ -398,7 +416,9 @@ NvHTTP::openConnection(QUrl baseUrl,
|
||||
// Handle error
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
qWarning() << command << " request failed with error " << reply->error();
|
||||
if (logLevel >= NvLogLevel::ERROR) {
|
||||
qWarning() << command << " request failed with error " << reply->error();
|
||||
}
|
||||
GfeHttpResponseException exception(reply->error(), reply->errorString());
|
||||
delete reply;
|
||||
throw exception;
|
||||
|
||||
+12
-4
@@ -17,7 +17,7 @@ public:
|
||||
|
||||
bool isInitialized()
|
||||
{
|
||||
return id != 0 && !name.isNull();
|
||||
return id != 0 && !name.isEmpty();
|
||||
}
|
||||
|
||||
int id;
|
||||
@@ -80,6 +80,12 @@ private:
|
||||
class NvHTTP
|
||||
{
|
||||
public:
|
||||
enum NvLogLevel {
|
||||
NONE,
|
||||
ERROR,
|
||||
VERBOSE
|
||||
};
|
||||
|
||||
explicit NvHTTP(QString address);
|
||||
|
||||
static
|
||||
@@ -87,7 +93,7 @@ public:
|
||||
getCurrentGame(QString serverInfo);
|
||||
|
||||
QString
|
||||
getServerInfo();
|
||||
getServerInfo(NvLogLevel logLevel);
|
||||
|
||||
static
|
||||
void
|
||||
@@ -107,7 +113,8 @@ public:
|
||||
openConnectionToString(QUrl baseUrl,
|
||||
QString command,
|
||||
QString arguments,
|
||||
bool enableTimeout);
|
||||
bool enableTimeout,
|
||||
NvLogLevel logLevel = NvLogLevel::VERBOSE);
|
||||
|
||||
static
|
||||
QVector<int>
|
||||
@@ -143,7 +150,8 @@ private:
|
||||
openConnection(QUrl baseUrl,
|
||||
QString command,
|
||||
QString arguments,
|
||||
bool enableTimeout);
|
||||
bool enableTimeout,
|
||||
NvLogLevel logLevel);
|
||||
|
||||
QString m_Address;
|
||||
QNetworkAccessManager m_Nam;
|
||||
|
||||
@@ -1,20 +1,136 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop">
|
||||
<id>com.moonlight_stream.Moonlight.desktop</id>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop">
|
||||
<id>com.moonlight_stream.Moonlight</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-3.0+</project_license>
|
||||
<name>Moonlight</name>
|
||||
<summary>Stream games from your NVIDIA GameStream-enabled PC</summary>
|
||||
|
||||
<description>
|
||||
<p>Moonlight can stream games or other applications from a PC with an NVIDIA GeForce GTX 600-series or higher GPU and GeForce Experience installed.</p>
|
||||
<p>GameStream utilizes the NVENC hardware encoder present on NVIDIA GPUs for low-latency streaming at up 4K 60 FPS. Moonlight can utilize VAAPI hardware acceleration for high-performance H.264 and HEVC video decoding with low CPU usage.</p>
|
||||
<p>Moonlight can stream games and other applications from a PC with an NVIDIA GeForce GTX 600-series or higher GPU and GeForce Experience installed.</p>
|
||||
<p>Features include:</p>
|
||||
<ul>
|
||||
<li>Streaming at up to 4K resolution</li>
|
||||
<li>Support for up to 120 FPS streaming (high refresh rate monitor recommended)</li>
|
||||
<li>Hardware accelerated video decoding with VAAPI and VDPAU support</li>
|
||||
<li>5.1 surround sound audio support</li>
|
||||
<li>HEVC support for better video compression efficiency</li>
|
||||
<li>Gamepad support with SDL mapping compatibility</li>
|
||||
</ul>
|
||||
</description>
|
||||
|
||||
<url type="homepage">https://moonlight-stream.com</url>
|
||||
<url type="bugtracker">https://github.com/moonlight-stream/moonlight-qt/issues</url>
|
||||
<url type="help">https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide</url>
|
||||
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://moonlight-stream.com/appstream_resources/moonlight_screenshot_1.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://moonlight-stream.com/appstream_resources/moonlight_screenshot_2.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="0.5.0" date="2018-09-15">
|
||||
<description>
|
||||
<p>New features:</p>
|
||||
<ul>
|
||||
<li>Added option to enable mouse acceleration for remote desktop usage</li>
|
||||
<li>Added option to disable automatic PC discovery</li>
|
||||
<li>Added gamepad mappings from gabomdq's SDL_GameControllerDB</li>
|
||||
<li>Added help links to error dialogs</li>
|
||||
<li>Added quit shortcut tip to stream loading page</li>
|
||||
<li>Restored the autodetect audio configuration option</li>
|
||||
</ul>
|
||||
<p>Bugfixes:</p>
|
||||
<ul>
|
||||
<li>Fixed quit app dialog starting the wrong app</li>
|
||||
<li>Fixed very high input lag on GFE 3.14.0 and earlier</li>
|
||||
<li>Fixed sending errant mouse click when capturing the mouse</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.4.0" date="2018-09-08">
|
||||
<description>
|
||||
<p>New features:</p>
|
||||
<ul>
|
||||
<li>Added option to enable (unsupported) 90/120 FPS streaming on 60 Hz monitors</li>
|
||||
<li>Added refresh rate matching for better frame pacing on high refresh rate monitors</li>
|
||||
<li>The streaming window now appears on the same monitor the UI was displayed on</li>
|
||||
<li>The streaming window is now centered on the target monitor</li>
|
||||
<li>Display a dialog when the connection is terminated by the remote PC</li>
|
||||
<li>Added option to select between full-screen exclusive and borderless windowed mode</li>
|
||||
</ul>
|
||||
<p>Bugfixes:</p>
|
||||
<ul>
|
||||
<li>Fixed streaming with no audio device</li>
|
||||
<li>Fixed mouse lag with some high polling rate mice</li>
|
||||
<li>Improved UI performance on the app grid page</li>
|
||||
<li>1440p and 4K 120 FPS performance is improved to avoid encoder bottlenecks</li>
|
||||
<li>Autodetect audio configuration option removed because it was broken</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.3.0" date="2018-08-21">
|
||||
<description>
|
||||
<p>Bugfixes:</p>
|
||||
<ul>
|
||||
<li>Reduced power consumption when the app is minimized</li>
|
||||
<li>Fixed malformed asset requests being sent when the first host in the grid is offline</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.2.0" date="2018-08-12">
|
||||
<description>
|
||||
<p>Bugfixes:</p>
|
||||
<ul>
|
||||
<li>Fixed functionality of '=', 'Numpad Dot', and 'Numpad Enter' keys</li>
|
||||
<li>Fixed uninitialized variable causing strange gamepad behavior</li>
|
||||
<li>Added a warning for incompatible configs and decoder failures</li>
|
||||
<li>Added a warning for Wayland performance issues</li>
|
||||
<li>Stopped capturing the mouse by default for windowed streams</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.1.0" date="2018-08-05">
|
||||
<description>
|
||||
<p>New features:</p>
|
||||
<ul>
|
||||
<li>Allow streaming at the client's native resolution</li>
|
||||
<li>VDPAU hardware decoding support for NVIDIA GPUs</li>
|
||||
<li>Updated UI to replace status text with icons</li>
|
||||
<li>Stream window is now resizable</li>
|
||||
<li>Display warnings during game launch for configuration issues</li>
|
||||
</ul>
|
||||
<p>Bugfixes:</p>
|
||||
<ul>
|
||||
<li>Gamepad input now works even if the app doesn't have focus</li>
|
||||
<li>Fixed streaming black screen if the host supported HEVC but the client didn't</li>
|
||||
<li>Fixed broken video scaling causing extra stretching and off-center video</li>
|
||||
<li>Fixed gamepad input issue causing some Y axis inputs to be reversed</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.5" date="2018-08-01">
|
||||
<description>
|
||||
<p>Changes from 0.0.4:</p>
|
||||
<ul>
|
||||
<li>Fixed a bug causing increased frame drops and input lag</li>
|
||||
<li>Added support for quitting running games</li>
|
||||
<li>Added support for waking sleeping PCs with Wake-on-LAN</li>
|
||||
<li>Increased the FPS limit to 120 FPS</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.0.4a" date="2018-07-30">
|
||||
<description>
|
||||
<p>Initial pre-alpha release for Linux. Please file bugs!</p>
|
||||
</description>
|
||||
</release>
|
||||
</releases>
|
||||
|
||||
<categories>
|
||||
<category>Game</category>
|
||||
<category>Qt</category>
|
||||
@@ -29,33 +145,5 @@
|
||||
<binary>moonlight</binary>
|
||||
</provides>
|
||||
|
||||
<content_rating type="oars-1.1">
|
||||
<content_attribute id="violence-cartoon">none</content_attribute>
|
||||
<content_attribute id="violence-fantasy">none</content_attribute>
|
||||
<content_attribute id="violence-realistic">none</content_attribute>
|
||||
<content_attribute id="violence-bloodshed">none</content_attribute>
|
||||
<content_attribute id="violence-sexual">none</content_attribute>
|
||||
<content_attribute id="violence-desecration">none</content_attribute>
|
||||
<content_attribute id="violence-slavery">none</content_attribute>
|
||||
<content_attribute id="violence-worship">none</content_attribute>
|
||||
<content_attribute id="drugs-alcohol">none</content_attribute>
|
||||
<content_attribute id="drugs-narcotics">none</content_attribute>
|
||||
<content_attribute id="drugs-tobacco">none</content_attribute>
|
||||
<content_attribute id="sex-nudity">none</content_attribute>
|
||||
<content_attribute id="sex-themes">none</content_attribute>
|
||||
<content_attribute id="sex-homosexuality">none</content_attribute>
|
||||
<content_attribute id="sex-prostitution">none</content_attribute>
|
||||
<content_attribute id="sex-adultery">none</content_attribute>
|
||||
<content_attribute id="sex-appearance">none</content_attribute>
|
||||
<content_attribute id="language-profanity">none</content_attribute>
|
||||
<content_attribute id="language-humor">none</content_attribute>
|
||||
<content_attribute id="language-discrimination">none</content_attribute>
|
||||
<content_attribute id="social-chat">none</content_attribute>
|
||||
<content_attribute id="social-info">moderate</content_attribute>
|
||||
<content_attribute id="social-audio">none</content_attribute>
|
||||
<content_attribute id="social-location">none</content_attribute>
|
||||
<content_attribute id="social-contacts">none</content_attribute>
|
||||
<content_attribute id="money-purchasing">none</content_attribute>
|
||||
<content_attribute id="money-gambling">none</content_attribute>
|
||||
</content_rating>
|
||||
<content_rating type="oars-1.0" />
|
||||
</component>
|
||||
|
||||
@@ -0,0 +1,438 @@
|
||||
# Game Controller DB for SDL in 2.0.6 format
|
||||
# Source: https://github.com/gabomdq/SDL_GameControllerDB
|
||||
|
||||
# Windows
|
||||
03000000fa2d00000100000000000000,3DRUDDER,leftx:a0,lefty:a1,rightx:a5,righty:a2,platform:Windows,
|
||||
03000000022000000090000000000000,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:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
|
||||
03000000203800000900000000000000,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:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
|
||||
03000000c82d00000060000000000000,8Bitdo SF30 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:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
|
||||
03000000c82d00000061000000000000,8Bitdo SF30 Pro Wireless,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:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
|
||||
03000000102800000900000000000000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,
|
||||
03000000a00500003232000000000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows,
|
||||
030000008f0e00001200000000000000,Acme,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,platform:Windows,
|
||||
03000000341a00003608000000000000,Afterglow 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,platform:Windows,
|
||||
03000000c01100001352000000000000,Battalife Joystick,a:b6,b:b7,back:b2,leftshoulder:b0,leftx:a0,lefty:a1,rightshoulder:b1,start:b3,x:b4,y:b5,platform:Windows,
|
||||
030000006b1400000055000000000000,bigben ps3padstreetnew,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,platform:Windows,
|
||||
0300000066f700000500000000000000,BrutalLegendTest,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:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000d81d00000b00000000000000,BUFFALO BSGP1601 Series ,a:b5,b:b3,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b4,y:b2,platform:Windows,
|
||||
03000000e82000006058000000000000,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,platform:Windows,
|
||||
030000005e0400008e02000000000000,Controller (XBOX 360 For Windows),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
|
||||
03000000260900008888000000000000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a4,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Windows,
|
||||
03000000a306000022f6000000000000,Cyborg V.3 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:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000791d00000103000000000000,Dual Box WII,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
030000004f04000023b3000000000000,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,platform:Windows,
|
||||
03000000341a00000108000000000000,EXEQ RF USB Gamepad 8206,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
|
||||
030000000d0f00008500000000000000,Fighting Commander 2016 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,platform:Windows,
|
||||
030000000d0f00008400000000000000,Fighting Commander 5,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,platform:Windows,
|
||||
030000000d0f00008700000000000000,Fighting Stick mini 4,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,platform:Windows,
|
||||
030000000d0f00008800000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00002700000000000000,FIGHTING STICK 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,platform:Windows,
|
||||
78696e70757403000000000000000000,Fightstick TES,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Windows,
|
||||
03000000790000000600000000000000,G-Shark GS-GP702,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:a4,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000260900002625000000000000,Gamecube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,lefttrigger:a4,leftx:a0,lefty:a1,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Windows,
|
||||
030000008f0e00000d31000000000000,GAMEPAD 3 TURBO,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,platform:Windows,
|
||||
03000000280400000140000000000000,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,platform:Windows,
|
||||
03000000ffff00000000000000000000,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,platform:Windows,
|
||||
03000000451300000010000000000000,Generic 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:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
|
||||
03000000341a00000302000000000000,Hama Scorpad,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,platform:Windows,
|
||||
030000000d0f00004900000000000000,Hatsune Miku Sho 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,platform:Windows,
|
||||
03000000d81400000862000000000000,HitBox Edition Cthulhu+,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00005f00000000000000,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,platform:Windows,
|
||||
030000000d0f00005e00000000000000,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,platform:Windows,
|
||||
030000000d0f00004000000000000000,Hori Fighting Stick Mini 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00006e00000000000000,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,platform:Windows,
|
||||
030000000d0f00006600000000000000,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,platform:Windows,
|
||||
030000000d0f0000ee00000000000000,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,platform:Windows,
|
||||
030000000d0f00004d00000000000000,HORIPAD3 A,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,platform:Windows,
|
||||
03000000250900000017000000000000,HRAP2 on PS/SS/N64 Joypad to USB BOX,a:b2,b:b1,back:b9,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b8,x:b3,y:b0,platform:Windows,
|
||||
030000008f0e00001330000000000000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000d81d00000f00000000000000,iBUFFALO BSGP1204 Series,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:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000d81d00001000000000000000,iBUFFALO BSGP1204P Series,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:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000830500006020000000000000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Windows,
|
||||
03000000b50700001403000000000000,IMPACT BLACK,a:b2,b:b3,back:b8,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,platform:Windows,
|
||||
030000006f0e00002401000000000000,INJUSTICE FightStick for 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,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000491900000204000000000000,Ipega PG-9023,a:b0,b:b1,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:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
|
||||
030000006d04000011c2000000000000,Logitech Cordless Wingman,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b2,righttrigger:b7,rightx:a3,righty:a4,x:b4,platform:Windows,
|
||||
030000006d04000016c2000000000000,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,platform:Windows,
|
||||
030000006d04000018c2000000000000,Logitech F510 Gamepad,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,platform:Windows,
|
||||
030000006d04000019c2000000000000,Logitech F710 Gamepad,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,platform:Windows,
|
||||
03000000380700005032000000000000,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,platform:Windows,
|
||||
03000000380700005082000000000000,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,platform:Windows,
|
||||
03000000380700008433000000000000,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,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700008483000000000000,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:b6,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700008134000000000000,Mad Catz FightStick TE2+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700008184000000000000,Mad Catz FightStick TE2+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,leftstick:b10,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700008034000000000000,Mad Catz TE2 PS3 Fightstick,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,platform:Windows,
|
||||
03000000380700008084000000000000,Mad Catz TE2 PS4 Fightstick,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,platform:Windows,
|
||||
03000000380700008532000000000000,Madcatz Arcade 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,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700003888000000000000,Madcatz Arcade 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,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700001888000000000000,MadCatz SFIV FightStick PS3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b6,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
|
||||
03000000380700008081000000000000,MADCATZ SFV Arcade FightStick Alpha PS4,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:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000008305000031b0000000000000,MaxfireBlaze3,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,platform:Windows,
|
||||
03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,platform:Windows,
|
||||
03000000790000004418000000000000,Mayflash GameCube Controller,a:b1,b:b2,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:b0,y:b3,platform:Windows,
|
||||
03000000790000004318000000000000,Mayflash GameCube Controller Adapter,a:b1,b:b2,back:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b0,leftshoulder:b4,leftstick:b0,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b0,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000008f0e00001030000000000000,Mayflash USB Adapter for original Sega Saturn controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,rightshoulder:b2,righttrigger:b7,start:b9,x:b3,y:b4,platform:Windows,
|
||||
0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows,
|
||||
03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (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,platform:Windows,
|
||||
03000000250900006688000000000000,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,platform:Windows,
|
||||
030000001008000001e5000000000000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000bd12000015d0000000000000,Nintendo Retrolink USB Super SNES Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows,
|
||||
030000007e0500000920000000000000,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,platform:Windows,
|
||||
030000004b120000014d000000000000,NYKO AIRFLO,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a3,leftstick:a0,lefttrigger:b6,rightshoulder:b5,rightstick:a2,righttrigger:b7,start:b9,x:b2,y:b3,platform:Windows,
|
||||
03000000362800000100000000000000,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:b13,rightx:a3,righty:a4,x:b1,y:b2,platform:Windows,
|
||||
03000000120c0000f60e000000000000,P4 Wired Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000008f0e00000300000000000000,Piranha xtreme,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,platform:Windows,
|
||||
03000000d62000006dca000000000000,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,platform:Windows,
|
||||
030000008f0e00007530000000000000,PS (R) Gamepad,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:b1,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000e30500009605000000000000,PS to USB convert cable,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,platform:Windows,
|
||||
03000000100800000100000000000000,PS1 USB,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,platform:Windows,
|
||||
03000000100800000300000000000000,PS2 USB,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:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
|
||||
030000004c0500006802000000000000,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,platform:Windows,
|
||||
03000000888800000803000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows,
|
||||
03000000250900000500000000000000,PS3 DualShock,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,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:b0,y:b3,platform:Windows,
|
||||
03000000100000008200000000000000,PS360+ v1.66,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:h0.4,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000004c050000a00b000000000000,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,platform:Windows,
|
||||
030000004c050000c405000000000000,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,platform:Windows,
|
||||
030000004c050000cc09000000000000,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,platform:Windows,
|
||||
03000000300f00000011000000000000,QanBa Arcade JoyStick 1008,a:b1,b:b2,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,start:b10,x:b0,y:b3,platform:Windows,
|
||||
03000000300f00001611000000000000,QanBa Arcade JoyStick 4018,a:b1,b:b2,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,
|
||||
03000000222c00000020000000000000,QANBA DRONE ARCADE JOYSTICK,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000300f00001210000000000000,QanBa Joystick Plus,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows,
|
||||
03000000341a00000104000000000000,QanBa Joystick Q4RAF,a:b5,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b1,y:b2,platform:Windows,
|
||||
03000000222c00000223000000000000,Qanba Obsidian Arcade Joystick PS3 Mode,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,platform:Windows,
|
||||
03000000222c00000023000000000000,Qanba Obsidian Arcade Joystick PS4 Mode,a:b1,b:b2,back:b13,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,platform:Windows,
|
||||
03000000321500000003000000000000,Razer Hydra,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
|
||||
030000000d0f00001100000000000000,REAL ARCADE PRO.3,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,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00006a00000000000000,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,platform:Windows,
|
||||
030000000d0f00006b00000000000000,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,platform:Windows,
|
||||
030000000d0f00008a00000000000000,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,platform:Windows,
|
||||
030000000d0f00008b00000000000000,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,platform:Windows,
|
||||
030000000d0f00007000000000000000,REAL ARCADE PRO.4 VLX,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,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00002200000000000000,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,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00005b00000000000000,Real Arcade Pro.V4,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,platform:Windows,
|
||||
030000000d0f00005c00000000000000,Real Arcade Pro.V4,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,platform:Windows,
|
||||
03000000790000001100000000000000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows,
|
||||
0300000000f000000300000000000000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows,
|
||||
0300000000f00000f100000000000000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows,
|
||||
030000006b140000010d000000000000,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,platform:Windows,
|
||||
030000006f0e00001e01000000000000,Rock Candy Gamepad for 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,platform:Windows,
|
||||
030000004f04000003d0000000000000,run'n'drive,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:a3,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:a4,rightstick:b11,righttrigger:b5,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000a30600001af5000000000000,Saitek Cyborg,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:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000a306000023f6000000000000,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,platform:Windows,
|
||||
03000000300f00001201000000000000,Saitek Dual Analog 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,platform:Windows,
|
||||
03000000a30600000cff000000000000,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:a2,righty:a3,x:b0,y:b1,platform:Windows,
|
||||
03000000a30600000c04000000000000,Saitek P2900,a:b1,b:b2,back:b12,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:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000300f00001001000000000000,Saitek P480 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,platform:Windows,
|
||||
03000000a30600000b04000000000000,Saitek P990,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:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
|
||||
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,platform:Windows,
|
||||
03000000300f00001101000000000000,saitek rumble pad,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,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,platform:Windows,
|
||||
0300000000050000289b000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000009b2800000500000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000341a00000208000000000000,SL-6555-SBK,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:-a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a3,righty:a2,start:b7,x:b2,y:b3,platform:Windows,
|
||||
030000008f0e00000800000000000000,SpeedLink Strike FX Wireless,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,platform:Windows,
|
||||
03000000ff1100003133000000000000,SVEN X-PAD,a:b2,b:b3,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a4,start:b5,x:b0,y:b1,platform:Windows,
|
||||
03000000fa1900000706000000000000,Team 5,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,platform:Windows,
|
||||
03000000b50700001203000000000000,Techmobility X6-38V,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,platform:Windows,
|
||||
030000004f04000015b3000000000000,Thrustmaster Dual Analog 2,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,platform:Windows,
|
||||
030000004f04000000b3000000000000,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,platform:Windows,
|
||||
030000004f04000004b3000000000000,Thrustmaster Firestorm Dual Power 3,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,platform:Windows,
|
||||
03000000666600000488000000000000,TigerGame PS/PS2 Game Controller Adapter,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,platform:Windows,
|
||||
03000000d90400000200000000000000,TwinShock PS2,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,platform:Windows,
|
||||
03000000380700006652000000000000,UnKnown,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:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000632500002305000000000000,USB Vibration Joystick (BM),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,platform:Windows,
|
||||
03000000790000001b18000000000000,Venom Arcade Joystick,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,platform:Windows,
|
||||
03000000450c00002043000000000000,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,platform:Windows,
|
||||
03000000172700004431000000000000,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:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
|
||||
03000000786901006e70000000000000,XInput 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,platform:Windows,
|
||||
xinput,XInput 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,platform:Windows,
|
||||
|
||||
# Mac OS X
|
||||
03000000022000000090000001000000,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,platform:Mac OS X,
|
||||
03000000203800000900000000010000,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,platform:Mac OS X,
|
||||
03000000102800000900000000000000,8Bitdo SFC30 GamePad Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,
|
||||
03000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X,
|
||||
03000000a00500003232000009010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X,
|
||||
030000008305000031b0000000000000,Cideko AK08b,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,platform:Mac OS X,
|
||||
03000000260900008888000088020000,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,platform:Mac OS X,
|
||||
03000000a306000022f6000001030000,Cyborg V.3 Rumble 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:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
03000000790000000600000000000000,G-Shark GP-702,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,platform:Mac OS X,
|
||||
03000000ad1b000001f9000000000000,Gamestop BB-070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
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,platform:Mac OS X,
|
||||
030000000d0f00005f00000000010000,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,platform:Mac OS X,
|
||||
030000000d0f00005e00000000010000,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,platform:Mac OS X,
|
||||
030000000d0f00005f00000000000000,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,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000000d0f00005e00000000000000,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,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000000d0f00004d00000000000000,HORI Gem Pad 3,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,platform:Mac OS X,
|
||||
030000000d0f00006e00000000010000,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,platform:Mac OS X,
|
||||
030000000d0f00006600000000010000,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,platform:Mac OS X,
|
||||
030000000d0f00006600000000000000,HORIPAD FPS PLUS 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:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000008f0e00001330000011010000,HuiJia SNES Controller,a:b4,b:b2,back:b16,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b12,rightshoulder:b14,start:b18,x:b6,y:b0,platform:Mac OS X,
|
||||
03000000830500006020000000010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X,
|
||||
03000000830500006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X,
|
||||
030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
030000006d04000016c2000000020000,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,platform:Mac OS X,
|
||||
030000006d04000016c2000000030000,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,platform:Mac OS X,
|
||||
030000006d04000016c2000014040000,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,platform:Mac OS X,
|
||||
030000006d04000016c2000000000000,Logitech F310 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,platform:Mac OS X,
|
||||
030000006d04000018c2000000000000,Logitech F510 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,platform:Mac OS X,
|
||||
030000006d0400001fc2000000000000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
030000006d04000019c2000000000000,Logitech Wireless 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,platform:Mac OS X,
|
||||
03000000380700005032000000010000,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,platform:Mac OS X,
|
||||
03000000380700005082000000010000,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,platform:Mac OS X,
|
||||
03000000790000004418000000010000,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,platform:Mac OS X,
|
||||
0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Mac OS X,
|
||||
03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,platform:Mac OS X,
|
||||
03000000d8140000cecf000000000000,MC Cthulhu,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,platform:Mac OS X,
|
||||
030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Mac OS X,
|
||||
030000007e0500000920000000000000,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,platform:Mac OS X,
|
||||
030000008f0e00000300000000000000,Piranha xtreme,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,platform:Mac OS X,
|
||||
03000000d62000006dca000000010000,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,platform:Mac OS X,
|
||||
030000004c0500006802000000000000,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,platform:Mac OS X,
|
||||
030000004c0500006802000000010000,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,platform:Mac OS X,
|
||||
030000004c050000a00b000000010000,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,platform:Mac OS X,
|
||||
030000004c050000c405000000000000,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,platform:Mac OS X,
|
||||
030000004c050000c405000000010000,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,platform:Mac OS X,
|
||||
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,platform:Mac OS X,
|
||||
030000008916000000fd000000000000,Razer Onza TE,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000321500000010000000010000,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,platform:Mac OS X,
|
||||
0300000032150000030a000000000000,Razer Wildcat,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000790000001100000000000000,Retrolink Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a3,lefty:a4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,
|
||||
03000000790000001100000006010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,
|
||||
030000006b140000010d000000010000,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,platform:Mac OS X,
|
||||
03000000c6240000fefa000000000000,Rock Candy Gamepad for PS3,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000811700007e05000000000000,Sega Saturn,a:b2,b:b4,dpdown:b16,dpleft:b15,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,leftx:a0,lefty:a2,rightshoulder:b9,righttrigger:a4,start:b13,x:b0,y:b6,platform:Mac OS X,
|
||||
03000000b40400000a01000000000000,Sega Saturn USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Mac OS X,
|
||||
030000003512000021ab000000000000,SFC30 Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,
|
||||
030000004c050000cc09000000000000,Sony DualShock 4 V2,a:b1,b:b2,back:b13,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,platform:Mac OS X,
|
||||
030000004c050000a00b000000000000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,back:b13,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,platform:Mac OS X,
|
||||
030000005e0400008e02000001000000,Steam Virtual GamePad,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000110100002014000000000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b12,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000110100002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000381000002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000110100001714000000000000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000110100001714000020010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,
|
||||
030000004f04000015b3000000000000,Thrustmaster Dual Analog 3.2,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,platform:Mac OS X,
|
||||
030000004f04000000b3000000000000,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,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Mac OS X,
|
||||
03000000bd12000015d0000000000000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,
|
||||
03000000bd12000015d0000000010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,
|
||||
03000000100800000100000000000000,Twin USB Joystick,a:b4,b:b2,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b12,leftstick:b20,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b14,rightstick:b22,righttrigger:b10,rightx:a6,righty:a4,start:b18,x:b6,y:b0,platform:Mac OS X,
|
||||
050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,back:b7,dpdown:b3,dpleft:b0,dpright:b1,dpup:b2,guide:b8,leftshoulder:b11,lefttrigger:b12,leftx:a0,lefty:a1,start:b6,x:b10,y:b9,platform:Mac OS X,
|
||||
050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b8,leftshoulder:b19,leftstick:b23,lefttrigger:b21,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b24,righttrigger:b22,rightx:a2,righty:a3,start:b6,x:b18,y:b17,platform:Mac OS X,
|
||||
030000005e0400008e02000000000000,X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000c6240000045d000000000000,Xbox 360 Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
030000005e040000d102000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
030000005e040000dd02000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
030000005e040000e302000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
030000005e040000e002000000000000,Xbox 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,platform:Mac OS X,
|
||||
030000005e040000e002000003090000,Xbox 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,platform:Mac OS X,
|
||||
030000005e040000ea02000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
030000005e040000fd02000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,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,platform:Mac OS X,
|
||||
03000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Mac OS X,
|
||||
03000000120c0000100e000000010000,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,platform:Mac OS X,
|
||||
|
||||
# Linux
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
05000000102800000900000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,
|
||||
05000000c82d00003028000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,
|
||||
05000000a00500003232000001000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,
|
||||
05000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,
|
||||
030000006f0e00003901000000430000,Afterglow Prismatic Wired 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,platform:Linux,
|
||||
030000006f0e00003901000020060000,Afterglow Wired 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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
03000000a306000022f6000011010000,Cyborg V.3 Rumble 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:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
030000006f0e00001f01000000010000,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:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
030000008f0e00000300000010010000,GreenAsia Inc. USB Joystick,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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
030000008f0e00001330000010010000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
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,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
050000004c0500006802000000800000,PS3 Controller (Bluetooth),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,platform:Linux,
|
||||
05000000504c415953544154494f4e00,PS3 Controller (Bluetooth),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,platform:Linux,
|
||||
060000004c0500006802000000010000,PS3 Controller (Bluetooth),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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
030000008916000000fd000024010000,Razer Onza Tournament,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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
03000000790000001100000010010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,
|
||||
0300000000f000000300000000010000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,
|
||||
0300000000f00000f100000000010000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,
|
||||
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,platform:Linux,
|
||||
030000006f0e00001e01000011010000,Rock Candy Gamepad for 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,platform:Linux,
|
||||
030000006f0e00004601000001010000,Rock Candy Wired Controller for Xbox One,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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
03000000de280000fc11000001000000,Steam 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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
03000000bd12000015d0000010010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
03000000790000001100000000010000,USB Gamepad1,a:b2,b:b1,back:b8,dpdown:a0,dpleft:a1,dpright:a2,dpup:a4,start:b9,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
xinput,XInput 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,platform:Linux,
|
||||
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,platform:Linux,
|
||||
|
||||
# Android
|
||||
64633436313965656664373634323364,Microsoft X-Box 360 pad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android,
|
||||
37336435666338653565313731303834,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
4e564944494120436f72706f72617469,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
61363931656135336130663561616264,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
35643031303033326130316330353564,PS4 Controller,a:b1,b:b17,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android,
|
||||
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,platform:Android,
|
||||
5477696e20555342204a6f7973746963,Twin USB Joystick,a:b22,b:b21,back:b28,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b26,leftstick:b30,lefttrigger:b24,leftx:a0,lefty:a1,rightshoulder:b27,rightstick:b31,righttrigger:b25,rightx:a3,righty:a2,start:b29,x:b23,y:b20,platform:Android,
|
||||
34356136633366613530316338376136,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,x:b17,y:b2,platform:Android,
|
||||
|
||||
# iOS
|
||||
4d466947616d65706164010000000000,MFi Extended Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:iOS,
|
||||
4d466947616d65706164020000000000,MFi Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b6,x:b2,y:b3,platform:iOS,
|
||||
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,platform:iOS,
|
||||
+101
-26
@@ -1,4 +1,5 @@
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Controls 2.2
|
||||
|
||||
import AppModel 1.0
|
||||
@@ -15,26 +16,25 @@ GridView {
|
||||
anchors.topMargin: 20
|
||||
anchors.rightMargin: 5
|
||||
anchors.bottomMargin: 5
|
||||
cellWidth: 225; cellHeight: 350;
|
||||
focus: true
|
||||
|
||||
// Cache delegates for 1000px in both directions to improve
|
||||
// scrolling and resizing performance
|
||||
cacheBuffer: 1000
|
||||
cellWidth: 225; cellHeight: 385;
|
||||
|
||||
// The StackView will trigger a visibility change when
|
||||
// we're pushed onto it, causing our onVisibleChanged
|
||||
// routine to run, but only if we start as invisible
|
||||
visible: false
|
||||
|
||||
function computerLost()
|
||||
{
|
||||
// Go back to the PC view on PC loss
|
||||
stackView.pop()
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
// Start polling when this view is shown
|
||||
ComputerManager.startPolling()
|
||||
appModel.computerLost.connect(computerLost)
|
||||
}
|
||||
else {
|
||||
// Stop polling when this view is not on top
|
||||
ComputerManager.stopPollingAsync()
|
||||
appModel.computerLost.disconnect(computerLost)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,18 +47,13 @@ GridView {
|
||||
|
||||
model: appModel
|
||||
|
||||
delegate: Item {
|
||||
width: 200; height: 300;
|
||||
|
||||
Component.onCompleted: {
|
||||
if (model.running) {
|
||||
appNameText.text = "<font color=\"green\">Running</font><font color=\"white\"> - </font>" + appNameText.text
|
||||
}
|
||||
}
|
||||
delegate: ItemDelegate {
|
||||
width: 200; height: 335;
|
||||
|
||||
Image {
|
||||
id: appIcon
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
y: 20
|
||||
source: model.boxart
|
||||
sourceSize {
|
||||
width: 150
|
||||
@@ -66,11 +61,21 @@ GridView {
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: runningIcon
|
||||
anchors.centerIn: appIcon
|
||||
visible: model.running
|
||||
source: "qrc:/res/baseline-play_circle_filled_white-48px.svg"
|
||||
sourceSize {
|
||||
width: 75
|
||||
height: 75
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: appNameText
|
||||
text: model.name
|
||||
color: "white"
|
||||
|
||||
width: parent.width
|
||||
height: 125
|
||||
anchors.top: appIcon.bottom
|
||||
@@ -80,18 +85,88 @@ GridView {
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
function launchOrResumeSelectedApp()
|
||||
{
|
||||
var runningIndex = appModel.getRunningAppIndex()
|
||||
if (runningIndex >= 0 && runningIndex !== index) {
|
||||
quitAppDialog.appName = appModel.getRunningAppName()
|
||||
quitAppDialog.segueToStream = true
|
||||
quitAppDialog.nextAppName = model.name
|
||||
quitAppDialog.nextAppIndex = index
|
||||
quitAppDialog.open()
|
||||
return
|
||||
}
|
||||
|
||||
var component = Qt.createComponent("StreamSegue.qml")
|
||||
var segue = component.createObject(stackView, {"appName": model.name, "session": appModel.createSessionForApp(index)})
|
||||
stackView.push(segue)
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
// Nothing is running or this app is running
|
||||
launchOrResumeSelectedApp()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: {
|
||||
// TODO: Check if a different game is running
|
||||
|
||||
var component = Qt.createComponent("StreamSegue.qml")
|
||||
var segue = component.createObject(stackView)
|
||||
segue.appname = model.name
|
||||
segue.session = appModel.createSessionForApp(index)
|
||||
stackView.push(segue)
|
||||
// Right click
|
||||
appContextMenu.open()
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: appContextMenu
|
||||
MenuItem {
|
||||
text: model.running ? "Resume Game" : "Launch Game"
|
||||
onTriggered: {
|
||||
appContextMenu.close()
|
||||
launchOrResumeSelectedApp()
|
||||
}
|
||||
height: visible ? implicitHeight : 0
|
||||
}
|
||||
MenuItem {
|
||||
text: "Quit Game"
|
||||
onTriggered: {
|
||||
quitAppDialog.appName = appModel.getRunningAppName()
|
||||
quitAppDialog.segueToStream = false
|
||||
quitAppDialog.open()
|
||||
}
|
||||
visible: model.running
|
||||
height: visible ? implicitHeight : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: quitAppDialog
|
||||
modality:Qt.WindowModal
|
||||
property string appName : ""
|
||||
property bool segueToStream : false
|
||||
property string nextAppName: ""
|
||||
property int nextAppIndex: 0
|
||||
text:"Are you sure you want to quit " + appName +"? Any unsaved progress will be lost."
|
||||
standardButtons: StandardButton.Yes | StandardButton.No
|
||||
onYes: {
|
||||
var component = Qt.createComponent("QuitSegue.qml")
|
||||
var params = {"appName": appName}
|
||||
if (segueToStream) {
|
||||
// Store the session and app name if we're going to stream after
|
||||
// successfully quitting the old app.
|
||||
params.nextAppName = nextAppName
|
||||
params.nextSession = appModel.createSessionForApp(nextAppIndex)
|
||||
}
|
||||
else {
|
||||
params.nextAppName = null
|
||||
params.nextSession = null
|
||||
}
|
||||
|
||||
stackView.push(component.createObject(stackView, params))
|
||||
|
||||
// Trigger the quit after pushing the quit segue on screen
|
||||
appModel.quitRunningApp()
|
||||
}
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
|
||||
+115
-84
@@ -1,7 +1,8 @@
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Dialogs 1.3
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import ComputerModel 1.0
|
||||
|
||||
@@ -18,7 +19,6 @@ GridView {
|
||||
anchors.rightMargin: 5
|
||||
anchors.bottomMargin: 5
|
||||
cellWidth: 350; cellHeight: 350;
|
||||
focus: true
|
||||
objectName: "Computers"
|
||||
|
||||
// The StackView will trigger a visibility change when
|
||||
@@ -34,32 +34,21 @@ GridView {
|
||||
// Setup signals on CM
|
||||
ComputerManager.computerAddCompleted.connect(addComplete)
|
||||
|
||||
if (!prefs.hasAnyHardwareAcceleration()) {
|
||||
if (prefs.isRunningWayland()) {
|
||||
waylandDialog.open()
|
||||
}
|
||||
else if (!prefs.hasAnyHardwareAcceleration()) {
|
||||
noHwDecoderDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
// Start polling when this view is shown
|
||||
ComputerManager.startPolling()
|
||||
}
|
||||
else {
|
||||
// Stop polling when this view is not top-most
|
||||
ComputerManager.stopPollingAsync()
|
||||
}
|
||||
}
|
||||
|
||||
function pairingComplete(error)
|
||||
{
|
||||
// Close the PIN dialog
|
||||
pairDialog.close()
|
||||
|
||||
// Start polling again
|
||||
ComputerManager.startPolling()
|
||||
|
||||
// Display a failed dialog if we got an error
|
||||
if (error !== null) {
|
||||
if (error !== undefined) {
|
||||
errorDialog.text = error
|
||||
errorDialog.open()
|
||||
}
|
||||
@@ -68,7 +57,7 @@ GridView {
|
||||
function addComplete(success)
|
||||
{
|
||||
if (!success) {
|
||||
errorDialog.text = "Unable to connect to the specified PC. Ensure GameStream is enabled in GeForce Experience."
|
||||
errorDialog.text = "Unable to connect to the specified PC. Click the Help button for possible solutions."
|
||||
errorDialog.open()
|
||||
}
|
||||
}
|
||||
@@ -81,14 +70,14 @@ GridView {
|
||||
return model
|
||||
}
|
||||
|
||||
model: createModel()
|
||||
model: computerModel
|
||||
|
||||
delegate: Item {
|
||||
delegate: ItemDelegate {
|
||||
width: 300; height: 300;
|
||||
|
||||
Image {
|
||||
id: pcIcon
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
source: {
|
||||
model.addPc ? "qrc:/res/ic_add_to_queue_white_48px.svg" : "qrc:/res/ic_tv_white_48px.svg"
|
||||
}
|
||||
@@ -98,6 +87,30 @@ GridView {
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
// TODO: Tooltip
|
||||
id: stateIcon
|
||||
anchors.horizontalCenter: pcIcon.horizontalCenter
|
||||
anchors.verticalCenter: pcIcon.verticalCenter
|
||||
anchors.verticalCenterOffset: -10
|
||||
visible: !model.addPc && !model.statusUnknown && (!model.online || !model.paired)
|
||||
source: !model.online ? "qrc:/res/baseline-warning-24px.svg" : "qrc:/res/baseline-lock-24px.svg"
|
||||
sourceSize {
|
||||
width: 75
|
||||
height: 75
|
||||
}
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
id: statusUnknownSpinner
|
||||
anchors.horizontalCenter: pcIcon.horizontalCenter
|
||||
anchors.verticalCenter: pcIcon.verticalCenter
|
||||
anchors.verticalCenterOffset: -10
|
||||
width: 75
|
||||
height: 75
|
||||
visible: !model.addPc && model.statusUnknown
|
||||
}
|
||||
|
||||
Text {
|
||||
id: pcNameText
|
||||
text: model.name
|
||||
@@ -110,76 +123,64 @@ GridView {
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Text {
|
||||
function getStatusText(model)
|
||||
{
|
||||
if (model.online) {
|
||||
var text = "<font color=\"green\">Online</font>"
|
||||
text += "<font color=\"white\"> - </font>"
|
||||
if (model.paired) {
|
||||
text += "<font color=\"skyblue\">Paired</font>"
|
||||
}
|
||||
else if (model.busy) {
|
||||
text += "<font color=\"red\">Busy</font>"
|
||||
}
|
||||
else {
|
||||
text += "<font color=\"red\">Not Paired</font>"
|
||||
}
|
||||
return text
|
||||
}
|
||||
else {
|
||||
return "<font color=\"red\">Offline</font>";
|
||||
Menu {
|
||||
id: pcContextMenu
|
||||
MenuItem {
|
||||
text: "Wake PC"
|
||||
onTriggered: computerModel.wakeComputer(index)
|
||||
visible: !model.addPc && !model.online && model.wakeable
|
||||
height: visible ? implicitHeight : 0
|
||||
}
|
||||
MenuItem {
|
||||
text: "Delete PC"
|
||||
onTriggered: {
|
||||
deletePcDialog.pcIndex = index
|
||||
// get confirmation first, actual closing is called from the dialog
|
||||
deletePcDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
id: pcPairedText
|
||||
text: getStatusText(model)
|
||||
visible: !model.addPc
|
||||
onClicked: {
|
||||
if (model.addPc) {
|
||||
addPcDialog.open()
|
||||
}
|
||||
else if (model.online) {
|
||||
if (model.paired) {
|
||||
// go to game view
|
||||
var component = Qt.createComponent("AppView.qml")
|
||||
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name})
|
||||
stackView.push(appView)
|
||||
}
|
||||
else {
|
||||
if (!model.busy) {
|
||||
var pin = ("0000" + Math.floor(Math.random() * 10000)).slice(-4)
|
||||
|
||||
width: parent.width
|
||||
anchors.top: pcNameText.bottom
|
||||
font.pointSize: 24
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
// Kick off pairing in the background
|
||||
computerModel.pairComputer(index, pin)
|
||||
|
||||
// Display the pairing dialog
|
||||
pairDialog.pin = pin
|
||||
pairDialog.open()
|
||||
}
|
||||
else {
|
||||
// cannot pair while something is streaming or attempting to pair
|
||||
errorDialog.text = "This PC is currently busy. Make sure to quit any running games and try again."
|
||||
errorDialog.open()
|
||||
}
|
||||
}
|
||||
} else if (!model.online) {
|
||||
pcContextMenu.open()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton;
|
||||
onClicked: {
|
||||
if (model.addPc) {
|
||||
addPcDialog.open()
|
||||
}
|
||||
else if (model.online) {
|
||||
if (model.paired) {
|
||||
// go to game view
|
||||
var component = Qt.createComponent("AppView.qml")
|
||||
var appView = component.createObject(stackView)
|
||||
appView.computerIndex = index
|
||||
appView.objectName = model.name
|
||||
stackView.push(appView)
|
||||
}
|
||||
else {
|
||||
if (!model.busy) {
|
||||
var pin = ("0000" + Math.floor(Math.random() * 10000)).slice(-4)
|
||||
|
||||
// Stop polling, since pairing may make GFE unresponsive
|
||||
ComputerManager.stopPollingAsync()
|
||||
|
||||
// Kick off pairing in the background
|
||||
computerModel.pairComputer(index, pin)
|
||||
|
||||
// Display the pairing dialog
|
||||
pairDialog.pin = pin
|
||||
pairDialog.open()
|
||||
}
|
||||
else {
|
||||
// cannot pair while something is streaming or attempting to pair
|
||||
errorDialog.text = "This PC is currently busy. Make sure to quit any running games and try again."
|
||||
errorDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO: Wake on LAN and delete PC options
|
||||
// right click
|
||||
if (!model.addPc) { // but only for actual PCs, not the add-pc option
|
||||
pcContextMenu.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,7 +191,12 @@ GridView {
|
||||
// don't allow edits to the rest of the window while open
|
||||
modality:Qt.WindowModal
|
||||
icon: StandardIcon.Critical
|
||||
standardButtons: StandardButton.Ok
|
||||
standardButtons: StandardButton.Ok | StandardButton.Help
|
||||
onHelp: {
|
||||
// Using Setup-Guide here instead of Troubleshooting because it's likely that users
|
||||
// will arrive here by forgetting to enable GameStream or not forwarding ports.
|
||||
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide");
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
@@ -206,6 +212,18 @@ GridView {
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: waylandDialog
|
||||
modality:Qt.WindowModal
|
||||
icon: StandardIcon.Warning
|
||||
standardButtons: StandardButton.Ok | StandardButton.Help
|
||||
text: "Moonlight does not support hardware acceleration on Wayland. Continuing on Wayland may result in poor streaming performance. " +
|
||||
"Please switch to an X session for optimal performance."
|
||||
onHelp: {
|
||||
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Fixing-Hardware-Decoding-Problems");
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: pairDialog
|
||||
// don't allow edits to the rest of the window while open
|
||||
@@ -218,6 +236,19 @@ GridView {
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: deletePcDialog
|
||||
// don't allow edits to the rest of the window while open
|
||||
modality:Qt.WindowModal
|
||||
property int pcIndex : -1;
|
||||
text:"Are you sure you want to remove this PC?"
|
||||
standardButtons: StandardButton.Yes | StandardButton.No
|
||||
onYes: {
|
||||
console.log("deleting PC pairing for PC at index: " + pcIndex)
|
||||
computerModel.deleteComputer(pcIndex);
|
||||
}
|
||||
}
|
||||
|
||||
Dialog {
|
||||
id: addPcDialog
|
||||
property string label: "Enter the IP address of your GameStream PC"
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Dialogs 1.2
|
||||
|
||||
import ComputerManager 1.0
|
||||
import Session 1.0
|
||||
|
||||
Item {
|
||||
property string appName
|
||||
property Session nextSession : null
|
||||
property string nextAppName : ""
|
||||
|
||||
property string stageText : "Quitting " + appName + "..."
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
// The StackView will trigger a visibility change when
|
||||
// we're pushed onto it, causing our onVisibleChanged
|
||||
// routine to run, but only if we start as invisible
|
||||
visible: false
|
||||
|
||||
function quitAppCompleted(error)
|
||||
{
|
||||
// Display a failed dialog if we got an error
|
||||
if (error !== undefined) {
|
||||
errorDialog.text = error
|
||||
errorDialog.open()
|
||||
}
|
||||
|
||||
// Exit this view
|
||||
stackView.pop()
|
||||
|
||||
// If we're supposed to launch another game after this, do so now
|
||||
if (error === undefined && nextSession !== null) {
|
||||
var component = Qt.createComponent("StreamSegue.qml")
|
||||
var segue = component.createObject(stackView, {"appName": nextAppName, "session": nextSession})
|
||||
stackView.push(segue)
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
// Connect the quit completion signal
|
||||
ComputerManager.quitAppCompleted.connect(quitAppCompleted)
|
||||
}
|
||||
else {
|
||||
// Disconnect the signal
|
||||
ComputerManager.quitAppCompleted.disconnect(quitAppCompleted)
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 5
|
||||
|
||||
BusyIndicator {
|
||||
id: stageSpinner
|
||||
}
|
||||
|
||||
Label {
|
||||
id: stageLabel
|
||||
height: stageSpinner.height
|
||||
text: stageText
|
||||
font.pointSize: 20
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: errorDialog
|
||||
modality:Qt.WindowModal
|
||||
icon: StandardIcon.Critical
|
||||
standardButtons: StandardButton.Ok
|
||||
}
|
||||
}
|
||||
+243
-52
@@ -2,6 +2,7 @@ import QtQuick 2.9
|
||||
import QtQuick.Controls 2.2
|
||||
|
||||
import StreamingPreferences 1.0
|
||||
import ComputerManager 1.0
|
||||
|
||||
ScrollView {
|
||||
id: settingsPage
|
||||
@@ -43,7 +44,7 @@ ScrollView {
|
||||
Label {
|
||||
width: parent.width
|
||||
id: resFPSdesc
|
||||
text: qsTr("Setting values too high for your PC may cause lag, stuttering, or errors")
|
||||
text: qsTr("Setting values too high for your PC may cause lag, stuttering, or errors.")
|
||||
font.pointSize: 9
|
||||
wrapMode: Text.Wrap
|
||||
color: "white"
|
||||
@@ -55,25 +56,81 @@ ScrollView {
|
||||
ComboBox {
|
||||
// ignore setting the index at first, and actually set it when the component is loaded
|
||||
Component.onCompleted: {
|
||||
// Add native resolutions for all attached displays
|
||||
var done = false
|
||||
for (var displayIndex = 0; !done; displayIndex++) {
|
||||
for (var displayResIndex = 0; displayResIndex < 2; displayResIndex++) {
|
||||
var screenRect;
|
||||
|
||||
// Some platforms have different desktop resolutions
|
||||
// and native resolutions (like macOS with Retina displays)
|
||||
if (displayResIndex == 0) {
|
||||
screenRect = prefs.getDesktopResolution(displayIndex)
|
||||
}
|
||||
else {
|
||||
screenRect = prefs.getNativeResolution(displayIndex)
|
||||
}
|
||||
|
||||
if (screenRect.width === 0) {
|
||||
// Exceeded max count of displays
|
||||
done = true
|
||||
break
|
||||
}
|
||||
|
||||
var indexToAdd = 0
|
||||
for (var j = 0; j < resolutionComboBox.count; j++) {
|
||||
var existing_width = parseInt(resolutionListModel.get(j).video_width);
|
||||
var existing_height = parseInt(resolutionListModel.get(j).video_height);
|
||||
|
||||
if (screenRect.width === existing_width && screenRect.height === existing_height) {
|
||||
// Duplicate entry, skip
|
||||
indexToAdd = -1
|
||||
break
|
||||
}
|
||||
else if (screenRect.width * screenRect.height > existing_width * existing_height) {
|
||||
// Candidate entrypoint after this entry
|
||||
indexToAdd = j + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Insert this display's resolution if it's not a duplicate
|
||||
if (indexToAdd >= 0) {
|
||||
resolutionListModel.insert(indexToAdd,
|
||||
{
|
||||
"text": "Native ("+screenRect.width+"x"+screenRect.height+")",
|
||||
"video_width": ""+screenRect.width,
|
||||
"video_height": ""+screenRect.height
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// load the saved width/height, and iterate through the ComboBox until a match is found
|
||||
// and set it to that index.
|
||||
var saved_width = prefs.width
|
||||
var saved_height = prefs.height
|
||||
currentIndex = 0
|
||||
for (var i = 0; i < resolutionComboBox.count; i++) {
|
||||
for (var i = 0; i < resolutionListModel.count; i++) {
|
||||
var el_width = parseInt(resolutionListModel.get(i).video_width);
|
||||
var el_height = parseInt(resolutionListModel.get(i).video_height);
|
||||
if (saved_width === el_width && saved_height === el_height) {
|
||||
|
||||
// Pick the highest value lesser or equal to the saved resolution
|
||||
if (saved_width * saved_height >= el_width * el_height) {
|
||||
currentIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
// Persist the selected value
|
||||
activated(currentIndex)
|
||||
}
|
||||
|
||||
id: resolutionComboBox
|
||||
font.pointSize: 9
|
||||
width: 200
|
||||
textRole: "text"
|
||||
model: ListModel {
|
||||
id: resolutionListModel
|
||||
// Other elements may be added at runtime
|
||||
// based on attached display resolution
|
||||
ListElement {
|
||||
text: "720p"
|
||||
video_width: "1280"
|
||||
@@ -97,58 +154,97 @@ ScrollView {
|
||||
}
|
||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
||||
onActivated : {
|
||||
prefs.width = parseInt(resolutionListModel.get(currentIndex).video_width)
|
||||
prefs.height = parseInt(resolutionListModel.get(currentIndex).video_height)
|
||||
var selectedWidth = parseInt(resolutionListModel.get(currentIndex).video_width)
|
||||
var selectedHeight = parseInt(resolutionListModel.get(currentIndex).video_height)
|
||||
|
||||
prefs.bitrateKbps = prefs.getDefaultBitrate(prefs.width, prefs.height, prefs.fps);
|
||||
slider.value = prefs.bitrateKbps
|
||||
// Only modify the bitrate if the values actually changed
|
||||
if (prefs.width !== selectedWidth || prefs.height !== selectedHeight) {
|
||||
prefs.width = selectedWidth
|
||||
prefs.height = selectedHeight
|
||||
|
||||
prefs.bitrateKbps = prefs.getDefaultBitrate(prefs.width, prefs.height, prefs.fps);
|
||||
slider.value = prefs.bitrateKbps
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
// ignore setting the index at first, and actually set it when the component is loaded
|
||||
Component.onCompleted: {
|
||||
function createModel() {
|
||||
var fpsListModel = Qt.createQmlObject('import QtQuick 2.0; ListModel {}', parent, '')
|
||||
|
||||
// Get the max supported FPS on this system
|
||||
var max_fps = prefs.getMaximumStreamingFrameRate();
|
||||
|
||||
// Default entries
|
||||
fpsListModel.append({"text": "30 FPS", "video_fps": "30"})
|
||||
fpsListModel.append({"text": "60 FPS", "video_fps": "60"})
|
||||
|
||||
// Add unsupported FPS values that come before the display max FPS
|
||||
if (prefs.unsupportedFps) {
|
||||
if (max_fps > 90) {
|
||||
fpsListModel.append({"text": "90 FPS (Unsupported)", "video_fps": "90"})
|
||||
}
|
||||
if (max_fps > 120) {
|
||||
fpsListModel.append({"text": "120 FPS (Unsupported)", "video_fps": "120"})
|
||||
}
|
||||
}
|
||||
|
||||
// Use 64 as the cutoff for adding a separate option to
|
||||
// handle wonky displays that report just over 60 Hz.
|
||||
if (max_fps > 64) {
|
||||
fpsListModel.append({"text": max_fps+" FPS", "video_fps": ""+max_fps})
|
||||
}
|
||||
|
||||
// Add unsupported FPS values that come after the display max FPS
|
||||
if (prefs.unsupportedFps) {
|
||||
if (max_fps < 90) {
|
||||
fpsListModel.append({"text": "90 FPS (Unsupported)", "video_fps": "90"})
|
||||
}
|
||||
if (max_fps < 120) {
|
||||
fpsListModel.append({"text": "120 FPS (Unsupported)", "video_fps": "120"})
|
||||
}
|
||||
}
|
||||
|
||||
return fpsListModel
|
||||
}
|
||||
|
||||
function reinitialize() {
|
||||
model = createModel()
|
||||
|
||||
var saved_fps = prefs.fps
|
||||
currentIndex = 0
|
||||
for (var i = 0; i < fpsComboBox.count; i++) {
|
||||
var el_fps = parseInt(fpsListModel.get(i).video_fps);
|
||||
if (el_fps === saved_fps) {
|
||||
for (var i = 0; i < model.count; i++) {
|
||||
var el_fps = parseInt(model.get(i).video_fps);
|
||||
|
||||
// Pick the highest value lesser or equal to the saved FPS
|
||||
if (saved_fps >= el_fps) {
|
||||
currentIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
// Persist the selected value
|
||||
activated(currentIndex)
|
||||
}
|
||||
|
||||
// ignore setting the index at first, and actually set it when the component is loaded
|
||||
Component.onCompleted: {
|
||||
reinitialize()
|
||||
}
|
||||
|
||||
id: fpsComboBox
|
||||
font.pointSize: 9
|
||||
textRole: "text"
|
||||
model: ListModel {
|
||||
id: fpsListModel
|
||||
ListElement {
|
||||
text: "30 FPS"
|
||||
video_fps: "30"
|
||||
}
|
||||
ListElement {
|
||||
text: "60 FPS"
|
||||
video_fps: "60"
|
||||
}
|
||||
// A higher value may be added at runtime
|
||||
// based on the attached display refresh rate
|
||||
}
|
||||
width: 250
|
||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
||||
onActivated : {
|
||||
prefs.fps = parseInt(fpsListModel.get(currentIndex).video_fps)
|
||||
var selectedFps = parseInt(model.get(currentIndex).video_fps)
|
||||
|
||||
prefs.bitrateKbps = prefs.getDefaultBitrate(prefs.width, prefs.height, prefs.fps);
|
||||
slider.value = prefs.bitrateKbps
|
||||
// Only modify the bitrate if the values actually changed
|
||||
if (prefs.fps !== selectedFps) {
|
||||
prefs.fps = selectedFps
|
||||
|
||||
prefs.bitrateKbps = prefs.getDefaultBitrate(prefs.width, prefs.height, prefs.fps);
|
||||
slider.value = prefs.bitrateKbps
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,13 +286,60 @@ ScrollView {
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
width: parent.width
|
||||
id: windowModeTitle
|
||||
text: qsTr("Display mode")
|
||||
font.pointSize: 12
|
||||
wrapMode: Text.Wrap
|
||||
color: "white"
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
// ignore setting the index at first, and actually set it when the component is loaded
|
||||
Component.onCompleted: {
|
||||
var savedWm = prefs.windowMode
|
||||
currentIndex = 0
|
||||
for (var i = 0; i < windowModeListModel.count; i++) {
|
||||
var thisWm = windowModeListModel.get(i).val;
|
||||
if (savedWm === thisWm) {
|
||||
currentIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
activated(currentIndex)
|
||||
}
|
||||
|
||||
id: windowModeComboBox
|
||||
width: Math.min(bitrateDesc.implicitWidth, parent.width)
|
||||
textRole: "text"
|
||||
model: ListModel {
|
||||
id: windowModeListModel
|
||||
ListElement {
|
||||
text: "Full-screen (Recommended)"
|
||||
val: StreamingPreferences.WM_FULLSCREEN
|
||||
}
|
||||
ListElement {
|
||||
text: "Borderless windowed"
|
||||
val: StreamingPreferences.WM_FULLSCREEN_DESKTOP
|
||||
}
|
||||
ListElement {
|
||||
text: "Windowed"
|
||||
val: StreamingPreferences.WM_WINDOWED
|
||||
}
|
||||
}
|
||||
onActivated: {
|
||||
prefs.windowMode = windowModeListModel.get(currentIndex).val
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: fullScreenCheck
|
||||
text: "<font color=\"white\">Full-screen</font>"
|
||||
id: vsyncCheck
|
||||
text: "<font color=\"white\">Enable V-Sync</font>"
|
||||
font.pointSize: 12
|
||||
checked: prefs.fullScreen
|
||||
checked: prefs.enableVsync
|
||||
onCheckedChanged: {
|
||||
prefs.fullScreen = checked
|
||||
prefs.enableVsync = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,17 +371,18 @@ ScrollView {
|
||||
Component.onCompleted: {
|
||||
var saved_audio = prefs.audioConfig
|
||||
currentIndex = 0
|
||||
for(var i = 0; i < audioListModel.count; i++) {
|
||||
var el_audio= audioListModel.get(i).val;
|
||||
if(saved_audio === el_audio) {
|
||||
currentIndex = i
|
||||
}
|
||||
for (var i = 0; i < audioListModel.count; i++) {
|
||||
var el_audio = audioListModel.get(i).val;
|
||||
if (saved_audio === el_audio) {
|
||||
currentIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
activated(currentIndex)
|
||||
}
|
||||
|
||||
id: audioComboBox
|
||||
width: Math.min(bitrateDesc.implicitWidth, parent.width)
|
||||
font.pointSize: 9
|
||||
textRole: "text"
|
||||
model: ListModel {
|
||||
id: audioListModel
|
||||
@@ -275,10 +419,10 @@ ScrollView {
|
||||
id: gamepadSettingsGroupBox
|
||||
width: (parent.width - parent.padding)
|
||||
padding: 12
|
||||
title: "<font color=\"skyblue\">Gamepad Settings</font>"
|
||||
title: "<font color=\"skyblue\">Input Settings</font>"
|
||||
font.pointSize: 12
|
||||
|
||||
Column {
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
|
||||
@@ -291,6 +435,16 @@ ScrollView {
|
||||
prefs.multiController = checked
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: mouseAccelerationCheck
|
||||
text: "<font color=\"white\">Enable mouse acceleration</font>"
|
||||
font.pointSize: 12
|
||||
checked: prefs.mouseAcceleration
|
||||
onCheckedChanged: {
|
||||
prefs.mouseAcceleration = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,13 +455,15 @@ ScrollView {
|
||||
title: "<font color=\"skyblue\">Host Settings</font>"
|
||||
font.pointSize: 12
|
||||
|
||||
Column {
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
|
||||
CheckBox {
|
||||
id: optimizeGameSettingsCheck
|
||||
text: "<font color=\"white\">Optimize game settings</font>"
|
||||
// HACK: Match width of the other checkbox to make the UI not look bad
|
||||
width: multiControllerCheck.width
|
||||
font.pointSize: 12
|
||||
checked: prefs.gameOptimizations
|
||||
onCheckedChanged: {
|
||||
@@ -352,17 +508,18 @@ ScrollView {
|
||||
Component.onCompleted: {
|
||||
var saved_vds = prefs.videoDecoderSelection
|
||||
currentIndex = 0
|
||||
for(var i = 0; i < decoderListModel.count; i++) {
|
||||
for (var i = 0; i < decoderListModel.count; i++) {
|
||||
var el_vds = decoderListModel.get(i).val;
|
||||
if(saved_vds === el_vds) {
|
||||
currentIndex = i
|
||||
}
|
||||
if (saved_vds === el_vds) {
|
||||
currentIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
activated(currentIndex)
|
||||
}
|
||||
|
||||
id: decoderComboBox
|
||||
width: Math.min(bitrateDesc.implicitWidth, parent.width)
|
||||
font.pointSize: 9
|
||||
textRole: "text"
|
||||
model: ListModel {
|
||||
id: decoderListModel
|
||||
@@ -401,15 +558,16 @@ ScrollView {
|
||||
currentIndex = 0
|
||||
for(var i = 0; i < codecListModel.count; i++) {
|
||||
var el_vcc = codecListModel.get(i).val;
|
||||
if(saved_vcc === el_vcc) {
|
||||
currentIndex = i
|
||||
}
|
||||
if (saved_vcc === el_vcc) {
|
||||
currentIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
activated(currentIndex)
|
||||
}
|
||||
|
||||
id: codecComboBox
|
||||
width: Math.min(bitrateDesc.implicitWidth, parent.width)
|
||||
font.pointSize: 9
|
||||
textRole: "text"
|
||||
model: ListModel {
|
||||
id: codecListModel
|
||||
@@ -439,6 +597,39 @@ ScrollView {
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: unlockUnsupportedFps
|
||||
text: "<font color=\"white\">Unlock unsupported FPS options</font>"
|
||||
font.pointSize: 12
|
||||
checked: prefs.unsupportedFps
|
||||
onCheckedChanged: {
|
||||
prefs.unsupportedFps = checked
|
||||
|
||||
// The selectable FPS values depend on whether
|
||||
// this option is enabled or not
|
||||
fpsComboBox.reinitialize()
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: enableMdns
|
||||
text: "<font color=\"white\">Automatically find PCs on the local network (Recommended)</font>"
|
||||
font.pointSize: 12
|
||||
checked: prefs.enableMdns
|
||||
onCheckedChanged: {
|
||||
prefs.enableMdns = checked
|
||||
|
||||
// We must save the updated preference to ensure
|
||||
// ComputerManager can observe the change internally.
|
||||
prefs.save()
|
||||
|
||||
// Restart polling so the mDNS change takes effect
|
||||
if (window.pollingActive) {
|
||||
ComputerManager.stopPollingAsync()
|
||||
ComputerManager.startPolling()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+28
-6
@@ -1,13 +1,14 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Dialogs 1.3
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import Session 1.0
|
||||
|
||||
Item {
|
||||
property Session session
|
||||
property string appname
|
||||
property string stageText : "Starting " + appname + "..."
|
||||
property string appName
|
||||
property string stageText : "Starting " + appName + "..."
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -47,7 +48,13 @@ Item {
|
||||
|
||||
function displayLaunchWarning(text)
|
||||
{
|
||||
// TODO: toast
|
||||
// This toast appears for 3 seconds, just shorter than how long
|
||||
// Session will wait for it to be displayed. This gives it time
|
||||
// to transition to invisible before continuing.
|
||||
var toast = Qt.createQmlObject('import QtQuick.Controls 2.3; ToolTip {}', parent, '')
|
||||
toast.text = text
|
||||
toast.timeout = 3000
|
||||
toast.visible = true
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
@@ -63,7 +70,7 @@ Item {
|
||||
session.displayLaunchWarning.connect(displayLaunchWarning)
|
||||
|
||||
// Run the streaming session to completion
|
||||
session.exec()
|
||||
session.exec(Screen.virtualX, Screen.virtualY)
|
||||
|
||||
// Show the Qt window again after streaming
|
||||
window.visible = true
|
||||
@@ -97,10 +104,25 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "Tip: Press Ctrl+Alt+Shift+Q to disconnect your session"
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 50
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
font.pointSize: 18
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
color: "white"
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: errorDialog
|
||||
modality:Qt.WindowModal
|
||||
icon: StandardIcon.Critical
|
||||
standardButtons: StandardButton.Ok
|
||||
standardButtons: StandardButton.Ok | StandardButton.Help
|
||||
onHelp: {
|
||||
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,32 @@ void AppModel::initialize(ComputerManager* computerManager, int computerIndex)
|
||||
m_CurrentGameId = m_Computer->currentGameId;
|
||||
}
|
||||
|
||||
int AppModel::getRunningAppIndex()
|
||||
{
|
||||
if (m_CurrentGameId != 0) {
|
||||
for (int i = 0; i < m_Apps.count(); i++) {
|
||||
if (m_Apps[i].id == m_CurrentGameId) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString AppModel::getRunningAppName()
|
||||
{
|
||||
if (m_CurrentGameId != 0) {
|
||||
for (int i = 0; i < m_Apps.count(); i++) {
|
||||
if (m_Apps[i].id == m_CurrentGameId) {
|
||||
return m_Apps[i].name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Session* AppModel::createSessionForApp(int appIndex)
|
||||
{
|
||||
Q_ASSERT(appIndex < m_Apps.count());
|
||||
@@ -70,6 +96,11 @@ QHash<int, QByteArray> AppModel::roleNames() const
|
||||
return names;
|
||||
}
|
||||
|
||||
void AppModel::quitRunningApp()
|
||||
{
|
||||
m_ComputerManager->quitRunningApp(m_Computer);
|
||||
}
|
||||
|
||||
void AppModel::handleComputerStateChanged(NvComputer* computer)
|
||||
{
|
||||
// Ignore updates for computers that aren't ours
|
||||
@@ -77,6 +108,14 @@ void AppModel::handleComputerStateChanged(NvComputer* computer)
|
||||
return;
|
||||
}
|
||||
|
||||
// If the computer has gone offline or we've been unpaired,
|
||||
// signal the UI so we can go back to the PC view.
|
||||
if (m_Computer->state == NvComputer::CS_OFFLINE ||
|
||||
m_Computer->pairState == NvComputer::PS_NOT_PAIRED) {
|
||||
emit computerLost();
|
||||
return;
|
||||
}
|
||||
|
||||
// First, process additions/removals from the app list. This
|
||||
// is required because the new game may now be running, so
|
||||
// we can't check that first.
|
||||
|
||||
@@ -25,6 +25,12 @@ public:
|
||||
|
||||
Q_INVOKABLE Session* createSessionForApp(int appIndex);
|
||||
|
||||
Q_INVOKABLE int getRunningAppIndex();
|
||||
|
||||
Q_INVOKABLE QString getRunningAppName();
|
||||
|
||||
Q_INVOKABLE void quitRunningApp();
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
@@ -36,6 +42,9 @@ private slots:
|
||||
|
||||
void handleBoxArtLoaded(NvComputer* computer, NvApp app, QUrl image);
|
||||
|
||||
signals:
|
||||
void computerLost();
|
||||
|
||||
private:
|
||||
NvComputer* m_Computer;
|
||||
BoxArtManager m_BoxArtManager;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "computermodel.h"
|
||||
|
||||
#include <QThreadPool>
|
||||
|
||||
ComputerModel::ComputerModel(QObject* object)
|
||||
: QAbstractListModel(object) {}
|
||||
|
||||
@@ -46,8 +48,12 @@ QVariant ComputerModel::data(const QModelIndex& index, int role) const
|
||||
return computer->pairState == NvComputer::PS_PAIRED;
|
||||
case BusyRole:
|
||||
return computer->currentGameId != 0;
|
||||
case WakeableRole:
|
||||
return !computer->macAddress.isEmpty();
|
||||
case AddPcRole:
|
||||
return false;
|
||||
case StatusUnknownRole:
|
||||
return computer->state == NvComputer::CS_UNKNOWN;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
@@ -74,6 +80,8 @@ QHash<int, QByteArray> ComputerModel::roleNames() const
|
||||
names[PairedRole] = "paired";
|
||||
names[BusyRole] = "busy";
|
||||
names[AddPcRole] = "addPc";
|
||||
names[WakeableRole] = "wakeable";
|
||||
names[StatusUnknownRole] = "statusUnknown";
|
||||
|
||||
return names;
|
||||
}
|
||||
@@ -113,11 +121,27 @@ void ComputerModel::deleteComputer(int computerIndex)
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
bool ComputerModel::wakeComputer(int computerIndex)
|
||||
class DeferredWakeHostTask : public QRunnable
|
||||
{
|
||||
public:
|
||||
DeferredWakeHostTask(NvComputer* computer)
|
||||
: m_Computer(computer) {}
|
||||
|
||||
void run()
|
||||
{
|
||||
m_Computer->wake();
|
||||
}
|
||||
|
||||
private:
|
||||
NvComputer* m_Computer;
|
||||
};
|
||||
|
||||
void ComputerModel::wakeComputer(int computerIndex)
|
||||
{
|
||||
Q_ASSERT(computerIndex < m_Computers.count());
|
||||
|
||||
return m_Computers[computerIndex]->wake();
|
||||
DeferredWakeHostTask* wakeTask = new DeferredWakeHostTask(m_Computers[computerIndex]);
|
||||
QThreadPool::globalInstance()->start(wakeTask);
|
||||
}
|
||||
|
||||
void ComputerModel::pairComputer(int computerIndex, QString pin)
|
||||
@@ -129,7 +153,7 @@ void ComputerModel::pairComputer(int computerIndex, QString pin)
|
||||
|
||||
void ComputerModel::handlePairingCompleted(NvComputer*, QString error)
|
||||
{
|
||||
emit pairingCompleted(error.isNull() ? QVariant() : error);
|
||||
emit pairingCompleted(error.isEmpty() ? QVariant() : error);
|
||||
}
|
||||
|
||||
void ComputerModel::handleComputerStateChanged(NvComputer* computer)
|
||||
|
||||
@@ -13,6 +13,8 @@ class ComputerModel : public QAbstractListModel
|
||||
OnlineRole,
|
||||
PairedRole,
|
||||
BusyRole,
|
||||
WakeableRole,
|
||||
StatusUnknownRole,
|
||||
AddPcRole
|
||||
};
|
||||
|
||||
@@ -32,7 +34,7 @@ public:
|
||||
|
||||
Q_INVOKABLE void pairComputer(int computerIndex, QString pin);
|
||||
|
||||
Q_INVOKABLE bool wakeComputer(int computerIndex);
|
||||
Q_INVOKABLE void wakeComputer(int computerIndex);
|
||||
|
||||
Q_INVOKABLE Session* createSessionForCurrentGame(int computerIndex);
|
||||
|
||||
|
||||
+115
-26
@@ -1,10 +1,16 @@
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import QtQuick.Controls.Material 2.1
|
||||
|
||||
import ComputerManager 1.0
|
||||
import AutoUpdateChecker 1.0
|
||||
|
||||
ApplicationWindow {
|
||||
property bool pollingActive: false
|
||||
|
||||
id: window
|
||||
visible: true
|
||||
width: 1280
|
||||
@@ -19,6 +25,24 @@ ApplicationWindow {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
onVisibilityChanged: {
|
||||
// We don't want to just use 'active' here because that will stop polling if
|
||||
// we lose focus, which might be overzealous for users with multiple screens
|
||||
// where we may be clearly visible on the other display. Ideally we'll poll
|
||||
// only if the window is visible to the user (not if obscured by other windows),
|
||||
// but it seems difficult to do this portably.
|
||||
var shouldPoll = visibility !== Window.Minimized && visibility !== Window.Hidden
|
||||
|
||||
if (shouldPoll && !pollingActive) {
|
||||
ComputerManager.startPolling()
|
||||
pollingActive = true
|
||||
}
|
||||
else if (!shouldPoll && pollingActive) {
|
||||
ComputerManager.stopPollingAsync()
|
||||
pollingActive = false
|
||||
}
|
||||
}
|
||||
|
||||
function navigateTo(url, objectName)
|
||||
{
|
||||
var existingItem = stackView.find(function(item, index) {
|
||||
@@ -37,6 +61,8 @@ ApplicationWindow {
|
||||
|
||||
header: ToolBar {
|
||||
id: toolBar
|
||||
anchors.topMargin: 5
|
||||
anchors.bottomMargin: 5
|
||||
|
||||
RowLayout {
|
||||
spacing: 20
|
||||
@@ -46,14 +72,19 @@ ApplicationWindow {
|
||||
// Only make the button visible if the user has navigated somewhere.
|
||||
visible: stackView.depth > 1
|
||||
|
||||
icon.source: "qrc:/res/arrow_left.svg"
|
||||
onClicked: stackView.pop()
|
||||
|
||||
Menu {
|
||||
id: backButton
|
||||
x: parent.width - width
|
||||
transformOrigin: Menu.TopRight
|
||||
// FIXME: We're using an Image here rather than icon.source because
|
||||
// icons don't work on Qt 5.9 LTS.
|
||||
Image {
|
||||
source: "qrc:/res/arrow_left.svg"
|
||||
anchors.centerIn: parent
|
||||
sourceSize {
|
||||
// The icon should be square so use the height as the width too
|
||||
width: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
height: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: stackView.pop()
|
||||
}
|
||||
|
||||
Label {
|
||||
@@ -67,41 +98,99 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
icon.source: "qrc:/res/question_mark.svg"
|
||||
property string browserUrl: ""
|
||||
|
||||
id: updateButton
|
||||
|
||||
Image {
|
||||
source: "qrc:/res/update.svg"
|
||||
anchors.centerIn: parent
|
||||
sourceSize {
|
||||
width: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
height: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
}
|
||||
}
|
||||
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 3000
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: "Update available for Moonlight"
|
||||
|
||||
// Invisible until we get a callback notifying us that
|
||||
// an update is available
|
||||
visible: false
|
||||
|
||||
onClicked: Qt.openUrlExternally(browserUrl);
|
||||
|
||||
|
||||
function updateAvailable(url)
|
||||
{
|
||||
updateButton.browserUrl = url
|
||||
updateButton.visible = true
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
AutoUpdateChecker.onUpdateAvailable.connect(updateAvailable)
|
||||
AutoUpdateChecker.start()
|
||||
}
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
Image {
|
||||
source: "qrc:/res/question_mark.svg"
|
||||
anchors.centerIn: parent
|
||||
sourceSize {
|
||||
width: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
height: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
}
|
||||
}
|
||||
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 3000
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: "Help"
|
||||
|
||||
// TODO need to make sure browser is brought to foreground.
|
||||
onClicked: Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide");
|
||||
|
||||
Menu {
|
||||
id: helpButton
|
||||
x: parent.width
|
||||
transformOrigin: Menu.TopRight
|
||||
}
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
// TODO: Implement gamepad mapping then unhide this button
|
||||
visible: false
|
||||
|
||||
icon.source: "qrc:/res/ic_videogame_asset_white_48px.svg"
|
||||
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", "Gamepad Mapping")
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 3000
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: "Gamepad Mapper"
|
||||
|
||||
Menu {
|
||||
id: gamepadMappingMenu
|
||||
x: parent.width
|
||||
transformOrigin: Menu.TopRight
|
||||
Image {
|
||||
source: "qrc:/res/ic_videogame_asset_white_48px.svg"
|
||||
anchors.centerIn: parent
|
||||
sourceSize {
|
||||
width: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
height: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", "Gamepad Mapping")
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
icon.source: "qrc:/res/settings.svg"
|
||||
Image {
|
||||
source: "qrc:/res/settings.svg"
|
||||
anchors.centerIn: parent
|
||||
sourceSize {
|
||||
width: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
height: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: navigateTo("qrc:/gui/SettingsView.qml", "Settings")
|
||||
|
||||
Menu {
|
||||
id: optionsMenu
|
||||
x: parent.width
|
||||
transformOrigin: Menu.TopRight
|
||||
}
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 3000
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: "Settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+134
-8
@@ -11,8 +11,14 @@
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include <SDL.h>
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
#include "streaming/video/ffmpeg.h"
|
||||
#endif
|
||||
|
||||
#include "path.h"
|
||||
#include "gui/computermodel.h"
|
||||
#include "gui/appmodel.h"
|
||||
#include "backend/autoupdatechecker.h"
|
||||
#include "streaming/session.hpp"
|
||||
#include "settings/streamingpreferences.h"
|
||||
|
||||
@@ -60,7 +66,8 @@ void logToLoggerStream(QString& message)
|
||||
}
|
||||
#endif
|
||||
|
||||
s_LoggerStream << message << endl;
|
||||
s_LoggerStream << message;
|
||||
s_LoggerStream.flush();
|
||||
}
|
||||
|
||||
void sdlLogToDiskHandler(void*, int category, SDL_LogPriority priority, const char* message)
|
||||
@@ -92,7 +99,7 @@ void sdlLogToDiskHandler(void*, int category, SDL_LogPriority priority, const ch
|
||||
}
|
||||
|
||||
QTime logTime = QTime::fromMSecsSinceStartOfDay(s_LoggerTime.elapsed());
|
||||
QString txt = QString("%1 - SDL %2 (%3): %4").arg(logTime.toString()).arg(priorityTxt).arg(category).arg(message);
|
||||
QString txt = QString("%1 - SDL %2 (%3): %4\n").arg(logTime.toString()).arg(priorityTxt).arg(category).arg(message);
|
||||
|
||||
logToLoggerStream(txt);
|
||||
}
|
||||
@@ -120,18 +127,116 @@ void qtLogToDiskHandler(QtMsgType type, const QMessageLogContext&, const QString
|
||||
}
|
||||
|
||||
QTime logTime = QTime::fromMSecsSinceStartOfDay(s_LoggerTime.elapsed());
|
||||
QString txt = QString("%1 - Qt %2: %3").arg(logTime.toString()).arg(typeTxt).arg(msg);
|
||||
QString txt = QString("%1 - Qt %2: %3\n").arg(logTime.toString()).arg(typeTxt).arg(msg);
|
||||
|
||||
logToLoggerStream(txt);
|
||||
}
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
|
||||
void ffmpegLogToDiskHandler(void* ptr, int level, const char* fmt, va_list vl)
|
||||
{
|
||||
char lineBuffer[1024];
|
||||
static int printPrefix = 1;
|
||||
|
||||
if ((level & 0xFF) > av_log_get_level()) {
|
||||
return;
|
||||
}
|
||||
|
||||
av_log_format_line(ptr, level, fmt, vl, lineBuffer, sizeof(lineBuffer), &printPrefix);
|
||||
|
||||
QTime logTime = QTime::fromMSecsSinceStartOfDay(s_LoggerTime.elapsed());
|
||||
QString txt = QString("%1 - FFmpeg: %2").arg(logTime.toString()).arg(lineBuffer);
|
||||
|
||||
logToLoggerStream(txt);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <DbgHelp.h>
|
||||
|
||||
static UINT s_HitUnhandledException = 0;
|
||||
|
||||
LONG WINAPI UnhandledExceptionHandler(struct _EXCEPTION_POINTERS *ExceptionInfo)
|
||||
{
|
||||
// Only write a dump for the first unhandled exception
|
||||
if (InterlockedCompareExchange(&s_HitUnhandledException, 1, 0) != 0) {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
WCHAR dmpFileName[MAX_PATH];
|
||||
swprintf_s(dmpFileName, L"%ls\\Moonlight-%I64u.dmp",
|
||||
(PWCHAR)QDir::toNativeSeparators(Path::getLogDir()).utf16(), QDateTime::currentSecsSinceEpoch());
|
||||
QString qDmpFileName = QString::fromUtf16((unsigned short*)dmpFileName);
|
||||
HANDLE dumpHandle = CreateFileW(dmpFileName, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (dumpHandle != INVALID_HANDLE_VALUE) {
|
||||
MINIDUMP_EXCEPTION_INFORMATION info;
|
||||
|
||||
info.ThreadId = GetCurrentThreadId();
|
||||
info.ExceptionPointers = ExceptionInfo;
|
||||
info.ClientPointers = FALSE;
|
||||
|
||||
DWORD typeFlags = MiniDumpWithIndirectlyReferencedMemory |
|
||||
MiniDumpIgnoreInaccessibleMemory |
|
||||
MiniDumpWithUnloadedModules |
|
||||
MiniDumpWithThreadInfo;
|
||||
|
||||
if (MiniDumpWriteDump(GetCurrentProcess(),
|
||||
GetCurrentProcessId(),
|
||||
dumpHandle,
|
||||
(MINIDUMP_TYPE)typeFlags,
|
||||
&info,
|
||||
nullptr,
|
||||
nullptr)) {
|
||||
qCritical() << "Unhandled exception! Minidump written to:" << qDmpFileName;
|
||||
}
|
||||
else {
|
||||
qCritical() << "Unhandled exception! Failed to write dump:" << GetLastError();
|
||||
}
|
||||
|
||||
CloseHandle(dumpHandle);
|
||||
}
|
||||
else {
|
||||
qCritical() << "Unhandled exception! Failed to open dump file:" << qDmpFileName << "with error" << GetLastError();
|
||||
}
|
||||
|
||||
// Let the program crash and WER collect a dump
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// Set these here to allow us to use the default QSettings constructor.
|
||||
// These also ensure that our cache directory is named correctly. As such,
|
||||
// it is critical that these be called before Path::initialize().
|
||||
QCoreApplication::setOrganizationName("Moonlight Game Streaming Project");
|
||||
QCoreApplication::setOrganizationDomain("moonlight-stream.com");
|
||||
QCoreApplication::setApplicationName("Moonlight");
|
||||
|
||||
if (QFile(QDir::currentPath() + "/portable.dat").exists()) {
|
||||
qInfo() << "Running in portable mode from:" << QDir::currentPath();
|
||||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
||||
QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, QDir::currentPath());
|
||||
QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, QDir::currentPath());
|
||||
|
||||
// Initialize paths for portable mode
|
||||
Path::initialize(true);
|
||||
}
|
||||
else {
|
||||
// Initialize paths for standard installation
|
||||
Path::initialize(false);
|
||||
}
|
||||
|
||||
#ifdef USE_CUSTOM_LOGGER
|
||||
#ifdef LOG_TO_FILE
|
||||
QDir tempDir(QDir::tempPath());
|
||||
QDir tempDir(Path::getLogDir());
|
||||
s_LoggerFile = new QFile(tempDir.filePath(QString("Moonlight-%1.log").arg(QDateTime::currentSecsSinceEpoch())));
|
||||
if (s_LoggerFile->open(QIODevice::WriteOnly)) {
|
||||
qInfo() << "Redirecting log output to " << s_LoggerFile->fileName();
|
||||
@@ -142,6 +247,16 @@ int main(int argc, char *argv[])
|
||||
s_LoggerTime.start();
|
||||
qInstallMessageHandler(qtLogToDiskHandler);
|
||||
SDL_LogSetOutputFunction(sdlLogToDiskHandler, nullptr);
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
av_log_set_callback(ffmpegLogToDiskHandler);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// Create a crash dump when we crash on Windows
|
||||
SetUnhandledExceptionFilter(UnhandledExceptionHandler);
|
||||
#endif
|
||||
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
@@ -150,10 +265,11 @@ int main(int argc, char *argv[])
|
||||
// password prompts on macOS.
|
||||
qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", QByteArray("1"));
|
||||
|
||||
// Set these here to allow us to use the default QSettings constructor
|
||||
QCoreApplication::setOrganizationName("Moonlight Game Streaming Project");
|
||||
QCoreApplication::setOrganizationDomain("moonlight-stream.com");
|
||||
QCoreApplication::setApplicationName("Moonlight");
|
||||
#ifdef Q_OS_WIN32
|
||||
// On Windows, use ANGLE so we don't have to load both DX and OGL
|
||||
// user-mode drivers into our app.
|
||||
qputenv("QT_OPENGL", "angle");
|
||||
#endif
|
||||
|
||||
// Register custom metatypes for use in signals
|
||||
qRegisterMetaType<NvApp>("NvApp");
|
||||
@@ -172,6 +288,11 @@ int main(int argc, char *argv[])
|
||||
[](QQmlEngine*, QJSEngine*) -> QObject* {
|
||||
return new ComputerManager();
|
||||
});
|
||||
qmlRegisterSingletonType<AutoUpdateChecker>("AutoUpdateChecker", 1, 0,
|
||||
"AutoUpdateChecker",
|
||||
[](QQmlEngine*, QJSEngine*) -> QObject* {
|
||||
return new AutoUpdateChecker();
|
||||
});
|
||||
|
||||
QQuickStyle::setStyle("Material");
|
||||
|
||||
@@ -188,6 +309,11 @@ int main(int argc, char *argv[])
|
||||
SDL_GetError());
|
||||
}
|
||||
|
||||
// Avoid the default behavior of changing the timer resolution to 1 ms.
|
||||
// We don't want this all the time that Moonlight is open. We will set
|
||||
// it manually when we start streaming.
|
||||
SDL_SetHint(SDL_HINT_TIMER_RESOLUTION, "0");
|
||||
|
||||
int err = app.exec();
|
||||
|
||||
SDL_Quit();
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,77 @@
|
||||
#include "path.h"
|
||||
|
||||
#include <QtDebug>
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
#include <QSettings>
|
||||
#include <QCoreApplication>
|
||||
|
||||
QString Path::s_LogDir;
|
||||
QString Path::s_BoxArtCacheDir;
|
||||
const QString Path::k_GameControllerMappingFile("gamecontrollerdb.txt");
|
||||
|
||||
QString Path::getLogDir()
|
||||
{
|
||||
Q_ASSERT(!s_LogDir.isEmpty());
|
||||
return s_LogDir;
|
||||
}
|
||||
|
||||
QString Path::getBoxArtCacheDir()
|
||||
{
|
||||
Q_ASSERT(!s_BoxArtCacheDir.isEmpty());
|
||||
return s_BoxArtCacheDir;
|
||||
}
|
||||
|
||||
QString Path::getGamepadMappingFile()
|
||||
{
|
||||
QString candidatePath;
|
||||
|
||||
// Check the current directory first
|
||||
candidatePath = QDir(QDir::currentPath()).absoluteFilePath(k_GameControllerMappingFile);
|
||||
if (QFile::exists(candidatePath)) {
|
||||
return candidatePath;
|
||||
}
|
||||
|
||||
// Now check the data directories (for Linux, in particular)
|
||||
candidatePath = QStandardPaths::locate(QStandardPaths::DataLocation, k_GameControllerMappingFile);
|
||||
if (!candidatePath.isEmpty() && QFile::exists(candidatePath)) {
|
||||
return candidatePath;
|
||||
}
|
||||
|
||||
// Now try the directory of our app installation (for Windows, if current dir doesn't find it)
|
||||
candidatePath = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(k_GameControllerMappingFile);
|
||||
if (QFile::exists(candidatePath)) {
|
||||
return candidatePath;
|
||||
}
|
||||
|
||||
// Now try the Resources folder in our app bundle for macOS
|
||||
QDir dir = QDir(QCoreApplication::applicationDirPath());
|
||||
dir.cdUp();
|
||||
dir.cd("Resources");
|
||||
dir.filePath(k_GameControllerMappingFile);
|
||||
candidatePath = dir.absoluteFilePath(k_GameControllerMappingFile);
|
||||
if (QFile::exists(candidatePath)) {
|
||||
return candidatePath;
|
||||
}
|
||||
|
||||
// Couldn't find it
|
||||
return QString();
|
||||
}
|
||||
|
||||
void Path::initialize(bool portable)
|
||||
{
|
||||
if (portable) {
|
||||
s_LogDir = QDir::currentPath();
|
||||
s_BoxArtCacheDir = QDir::currentPath() + "/boxart";
|
||||
}
|
||||
else {
|
||||
#ifdef Q_OS_DARWIN
|
||||
// On macOS, $TMPDIR is some random folder under /var/folders/ that nobody can
|
||||
// easily find, so use the system's global tmp directory instead.
|
||||
s_LogDir = "/tmp";
|
||||
#else
|
||||
s_LogDir = QDir::tempPath();
|
||||
#endif
|
||||
s_BoxArtCacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/boxart";
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
class Path
|
||||
{
|
||||
public:
|
||||
static QString getLogDir();
|
||||
|
||||
static QString getBoxArtCacheDir();
|
||||
|
||||
static QString getGamepadMappingFile();
|
||||
|
||||
static void initialize(bool portable);
|
||||
|
||||
private:
|
||||
static QString s_LogDir;
|
||||
static QString s_BoxArtCacheDir;
|
||||
static const QString k_GameControllerMappingFile;
|
||||
};
|
||||
@@ -6,5 +6,6 @@
|
||||
<file>gui/SettingsView.qml</file>
|
||||
<file>gui/StreamSegue.qml</file>
|
||||
<file>gui/GamepadMapper.qml</file>
|
||||
<file>gui/QuitSegue.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#FFFFFF" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 390 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="#fff">
|
||||
<path d="M0 0h48v48H0z" fill="none"/>
|
||||
<path d="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm-4 29V15l12 9-12 9z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 250 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#FFFFFF" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 215 B |
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="#0BF200">
|
||||
<path d="M21,10.12H14.22L16.96,7.3C14.23,4.6 9.81,4.5 7.08,7.2C4.35,9.91 4.35,14.28 7.08,17C9.81,19.7 14.23,19.7 16.96,17C18.32,15.65 19,14.08 19,12.1H21C21,14.08 20.12,16.65 18.36,18.39C14.85,21.87 9.15,21.87 5.64,18.39C2.14,14.92 2.11,9.28 5.62,5.81C9.13,2.34 14.76,2.34 18.27,5.81L21,3V10.12M12.5,8V12.25L16,14.33L15.28,15.54L11,13V8H12.5Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 499 B |
@@ -5,10 +5,14 @@
|
||||
<file>res/ic_videogame_asset_white_48px.svg</file>
|
||||
<file>res/ic_tv_white_48px.svg</file>
|
||||
<file>res/ic_add_to_queue_white_48px.svg</file>
|
||||
<file>res/baseline-lock-24px.svg</file>
|
||||
<file>res/baseline-play_circle_filled_white-48px.svg</file>
|
||||
<file>res/baseline-warning-24px.svg</file>
|
||||
<file>res/no_app_image.png</file>
|
||||
<file>res/settings.svg</file>
|
||||
<file>res/arrow_left.svg</file>
|
||||
<file>res/question_mark.svg</file>
|
||||
<file>res/moonlight.svg</file>
|
||||
<file>res/update.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "streamingpreferences.h"
|
||||
#include "streaming/session.hpp"
|
||||
#include "streaming/streamutils.h"
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
@@ -9,12 +10,17 @@
|
||||
#define SER_FPS "fps"
|
||||
#define SER_BITRATE "bitrate"
|
||||
#define SER_FULLSCREEN "fullscreen"
|
||||
#define SER_VSYNC "vsync"
|
||||
#define SER_GAMEOPTS "gameopts"
|
||||
#define SER_HOSTAUDIO "hostaudio"
|
||||
#define SER_MULTICONT "multicontroller"
|
||||
#define SER_AUDIOCFG "audiocfg"
|
||||
#define SER_VIDEOCFG "videocfg"
|
||||
#define SER_VIDEODEC "videodec"
|
||||
#define SER_WINDOWMODE "windowmode"
|
||||
#define SER_UNSUPPORTEDFPS "unsupportedfps"
|
||||
#define SER_MDNS "mdns"
|
||||
#define SER_MOUSEACCELERATION "mouseacceleration"
|
||||
|
||||
StreamingPreferences::StreamingPreferences()
|
||||
{
|
||||
@@ -29,16 +35,23 @@ void StreamingPreferences::reload()
|
||||
height = settings.value(SER_HEIGHT, 720).toInt();
|
||||
fps = settings.value(SER_FPS, 60).toInt();
|
||||
bitrateKbps = settings.value(SER_BITRATE, getDefaultBitrate(width, height, fps)).toInt();
|
||||
fullScreen = settings.value(SER_FULLSCREEN, true).toBool();
|
||||
enableVsync = settings.value(SER_VSYNC, true).toBool();
|
||||
gameOptimizations = settings.value(SER_GAMEOPTS, true).toBool();
|
||||
playAudioOnHost = settings.value(SER_HOSTAUDIO, false).toBool();
|
||||
multiController = settings.value(SER_MULTICONT, true).toBool();
|
||||
unsupportedFps = settings.value(SER_UNSUPPORTEDFPS, false).toBool();
|
||||
enableMdns = settings.value(SER_MDNS, true).toBool();
|
||||
mouseAcceleration = settings.value(SER_MOUSEACCELERATION, false).toBool();
|
||||
audioConfig = static_cast<AudioConfig>(settings.value(SER_AUDIOCFG,
|
||||
static_cast<int>(AudioConfig::AC_AUTO)).toInt());
|
||||
videoCodecConfig = static_cast<VideoCodecConfig>(settings.value(SER_VIDEOCFG,
|
||||
static_cast<int>(VideoCodecConfig::VCC_AUTO)).toInt());
|
||||
videoDecoderSelection = static_cast<VideoDecoderSelection>(settings.value(SER_VIDEODEC,
|
||||
static_cast<int>(VideoDecoderSelection::VDS_AUTO)).toInt());
|
||||
windowMode = static_cast<WindowMode>(settings.value(SER_WINDOWMODE,
|
||||
// Try to load from the old preference value too
|
||||
static_cast<int>(settings.value(SER_FULLSCREEN, true).toBool() ?
|
||||
WindowMode::WM_FULLSCREEN : WindowMode::WM_WINDOWED)).toInt());
|
||||
}
|
||||
|
||||
void StreamingPreferences::save()
|
||||
@@ -49,13 +62,17 @@ void StreamingPreferences::save()
|
||||
settings.setValue(SER_HEIGHT, height);
|
||||
settings.setValue(SER_FPS, fps);
|
||||
settings.setValue(SER_BITRATE, bitrateKbps);
|
||||
settings.setValue(SER_FULLSCREEN, fullScreen);
|
||||
settings.setValue(SER_VSYNC, enableVsync);
|
||||
settings.setValue(SER_GAMEOPTS, gameOptimizations);
|
||||
settings.setValue(SER_HOSTAUDIO, playAudioOnHost);
|
||||
settings.setValue(SER_MULTICONT, multiController);
|
||||
settings.setValue(SER_UNSUPPORTEDFPS, unsupportedFps);
|
||||
settings.setValue(SER_MDNS, enableMdns);
|
||||
settings.setValue(SER_MOUSEACCELERATION, mouseAcceleration);
|
||||
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
|
||||
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
|
||||
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));
|
||||
settings.setValue(SER_WINDOWMODE, static_cast<int>(windowMode));
|
||||
}
|
||||
|
||||
bool StreamingPreferences::hasAnyHardwareAcceleration()
|
||||
@@ -67,6 +84,11 @@ bool StreamingPreferences::hasAnyHardwareAcceleration()
|
||||
1920, 1080, 60);
|
||||
}
|
||||
|
||||
bool StreamingPreferences::isRunningWayland()
|
||||
{
|
||||
return qgetenv("XDG_SESSION_TYPE") == QByteArray("wayland");
|
||||
}
|
||||
|
||||
int StreamingPreferences::getMaximumStreamingFrameRate()
|
||||
{
|
||||
// Never let the maximum drop below 60 FPS
|
||||
@@ -79,12 +101,26 @@ int StreamingPreferences::getMaximumStreamingFrameRate()
|
||||
return maxFrameRate;
|
||||
}
|
||||
|
||||
for (int i = 0; i < SDL_GetNumVideoDisplays(); i++) {
|
||||
SDL_DisplayMode mode;
|
||||
if (SDL_GetCurrentDisplayMode(i, &mode) == 0) {
|
||||
// Cap the frame rate at 90 FPS, since I can't seem to get
|
||||
// much more out of GFE even with the game rendering at > 300 FPS.
|
||||
maxFrameRate = qMax(maxFrameRate, qMin(90, mode.refresh_rate));
|
||||
SDL_DisplayMode mode, bestMode, desktopMode;
|
||||
for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) {
|
||||
// Get the native desktop resolution
|
||||
if (StreamUtils::getRealDesktopMode(displayIndex, &desktopMode)) {
|
||||
|
||||
// Start at desktop mode and work our way up
|
||||
bestMode = desktopMode;
|
||||
for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) {
|
||||
if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0) {
|
||||
if (mode.w == desktopMode.w && mode.h == desktopMode.h) {
|
||||
if (mode.refresh_rate > bestMode.refresh_rate) {
|
||||
bestMode = mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cap the frame rate at 120 FPS. Past this, the encoders start
|
||||
// to max out and drop frames.
|
||||
maxFrameRate = qMax(maxFrameRate, qMin(120, bestMode.refresh_rate));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,15 +129,76 @@ int StreamingPreferences::getMaximumStreamingFrameRate()
|
||||
return maxFrameRate;
|
||||
}
|
||||
|
||||
QRect StreamingPreferences::getDesktopResolution(int displayIndex)
|
||||
{
|
||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
||||
SDL_GetError());
|
||||
return QRect();
|
||||
}
|
||||
|
||||
if (displayIndex >= SDL_GetNumVideoDisplays()) {
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return QRect();
|
||||
}
|
||||
|
||||
SDL_DisplayMode mode;
|
||||
int err = SDL_GetDesktopDisplayMode(displayIndex, &mode);
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
|
||||
if (err == 0) {
|
||||
return QRect(0, 0, mode.w, mode.h);
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetDesktopDisplayMode() failed: %s",
|
||||
SDL_GetError());
|
||||
return QRect();
|
||||
}
|
||||
}
|
||||
|
||||
QRect StreamingPreferences::getNativeResolution(int displayIndex)
|
||||
{
|
||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
||||
SDL_GetError());
|
||||
return QRect();
|
||||
}
|
||||
|
||||
if (displayIndex >= SDL_GetNumVideoDisplays()) {
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return QRect();
|
||||
}
|
||||
|
||||
SDL_DisplayMode mode;
|
||||
bool success = StreamUtils::getRealDesktopMode(displayIndex, &mode);
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
|
||||
if (success) {
|
||||
return QRect(0, 0, mode.w, mode.h);
|
||||
}
|
||||
else {
|
||||
return QRect();
|
||||
}
|
||||
}
|
||||
|
||||
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
|
||||
{
|
||||
if (width * height <= 1280 * 720) {
|
||||
// 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.
|
||||
|
||||
// This covers 1280x720 and 1280x800 too
|
||||
if (width * height <= 1366 * 768) {
|
||||
return static_cast<int>(5000 * (fps / 30.0));
|
||||
}
|
||||
else if (width * height <= 1920 * 1080) {
|
||||
else if (width * height <= 1920 * 1200) {
|
||||
return static_cast<int>(10000 * (fps / 30.0));
|
||||
}
|
||||
else if (width * height <= 2560 * 1440) {
|
||||
else if (width * height <= 2560 * 1600) {
|
||||
return static_cast<int>(20000 * (fps / 30.0));
|
||||
}
|
||||
else /* if (width * height <= 3840 * 2160) */ {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QRect>
|
||||
|
||||
class StreamingPreferences : public QObject
|
||||
{
|
||||
@@ -16,8 +17,14 @@ public:
|
||||
|
||||
Q_INVOKABLE static bool hasAnyHardwareAcceleration();
|
||||
|
||||
Q_INVOKABLE static bool isRunningWayland();
|
||||
|
||||
Q_INVOKABLE static int getMaximumStreamingFrameRate();
|
||||
|
||||
Q_INVOKABLE QRect getDesktopResolution(int displayIndex);
|
||||
|
||||
Q_INVOKABLE QRect getNativeResolution(int displayIndex);
|
||||
|
||||
void reload();
|
||||
|
||||
enum AudioConfig
|
||||
@@ -45,40 +52,60 @@ public:
|
||||
};
|
||||
Q_ENUM(VideoDecoderSelection)
|
||||
|
||||
enum WindowMode
|
||||
{
|
||||
WM_FULLSCREEN,
|
||||
WM_FULLSCREEN_DESKTOP,
|
||||
WM_WINDOWED
|
||||
};
|
||||
Q_ENUM(WindowMode)
|
||||
|
||||
Q_PROPERTY(int width MEMBER width NOTIFY displayModeChanged)
|
||||
Q_PROPERTY(int height MEMBER height NOTIFY displayModeChanged)
|
||||
Q_PROPERTY(int fps MEMBER fps NOTIFY displayModeChanged)
|
||||
Q_PROPERTY(int bitrateKbps MEMBER bitrateKbps NOTIFY bitrateChanged)
|
||||
Q_PROPERTY(bool fullScreen MEMBER fullScreen NOTIFY fullScreenChanged)
|
||||
Q_PROPERTY(bool enableVsync MEMBER enableVsync NOTIFY enableVsyncChanged)
|
||||
Q_PROPERTY(bool gameOptimizations MEMBER gameOptimizations NOTIFY gameOptimizationsChanged)
|
||||
Q_PROPERTY(bool playAudioOnHost MEMBER playAudioOnHost NOTIFY playAudioOnHostChanged)
|
||||
Q_PROPERTY(bool multiController MEMBER multiController NOTIFY multiControllerChanged)
|
||||
Q_PROPERTY(bool unsupportedFps MEMBER unsupportedFps NOTIFY unsupportedFpsChanged)
|
||||
Q_PROPERTY(bool enableMdns MEMBER enableMdns NOTIFY enableMdnsChanged)
|
||||
Q_PROPERTY(bool mouseAcceleration MEMBER mouseAcceleration NOTIFY mouseAccelerationChanged)
|
||||
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
|
||||
Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
|
||||
Q_PROPERTY(VideoDecoderSelection videoDecoderSelection MEMBER videoDecoderSelection NOTIFY videoDecoderSelectionChanged)
|
||||
Q_PROPERTY(WindowMode windowMode MEMBER windowMode NOTIFY windowModeChanged)
|
||||
|
||||
// Directly accessible members for preferences
|
||||
int width;
|
||||
int height;
|
||||
int fps;
|
||||
int bitrateKbps;
|
||||
bool fullScreen;
|
||||
bool enableVsync;
|
||||
bool gameOptimizations;
|
||||
bool playAudioOnHost;
|
||||
bool multiController;
|
||||
bool unsupportedFps;
|
||||
bool enableMdns;
|
||||
bool mouseAcceleration;
|
||||
AudioConfig audioConfig;
|
||||
VideoCodecConfig videoCodecConfig;
|
||||
VideoDecoderSelection videoDecoderSelection;
|
||||
WindowMode windowMode;
|
||||
|
||||
signals:
|
||||
void displayModeChanged();
|
||||
void bitrateChanged();
|
||||
void fullScreenChanged();
|
||||
void enableVsyncChanged();
|
||||
void gameOptimizationsChanged();
|
||||
void playAudioOnHostChanged();
|
||||
void multiControllerChanged();
|
||||
void unsupportedFpsChanged();
|
||||
void enableMdnsChanged();
|
||||
void mouseAccelerationChanged();
|
||||
void audioConfigChanged();
|
||||
void videoCodecConfigChanged();
|
||||
void videoDecoderSelectionChanged();
|
||||
void windowModeChanged();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
#include "session.hpp"
|
||||
|
||||
#include <Limelight.h>
|
||||
#include <SDL.h>
|
||||
|
||||
#define MAX_CHANNELS 6
|
||||
#define SAMPLES_PER_FRAME 240
|
||||
#define MIN_QUEUED_FRAMES 2
|
||||
#define MAX_QUEUED_FRAMES 4
|
||||
#define STOP_THE_WORLD_LIMIT 20
|
||||
#define DROP_RATIO_DENOM 32
|
||||
|
||||
SDL_AudioDeviceID Session::s_AudioDevice;
|
||||
OpusMSDecoder* Session::s_OpusDecoder;
|
||||
short Session::s_OpusDecodeBuffer[MAX_CHANNELS * SAMPLES_PER_FRAME];
|
||||
int Session::s_ChannelCount;
|
||||
int Session::s_PendingDrops;
|
||||
int Session::s_PendingHardDrops;
|
||||
unsigned int Session::s_SampleIndex;
|
||||
Uint32 Session::s_BaselinePendingData;
|
||||
|
||||
int Session::sdlDetermineAudioConfiguration()
|
||||
{
|
||||
SDL_AudioSpec want, have;
|
||||
SDL_AudioDeviceID dev;
|
||||
int ret;
|
||||
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_InitSubSystem(SDL_INIT_AUDIO) failed: %s",
|
||||
SDL_GetError());
|
||||
return AUDIO_CONFIGURATION_STEREO;
|
||||
}
|
||||
|
||||
SDL_zero(want);
|
||||
want.freq = 48000;
|
||||
want.format = AUDIO_S16;
|
||||
want.channels = 6;
|
||||
want.samples = 1024;
|
||||
|
||||
// Try to open for 5.1 surround sound, but allow SDL to tell us that's
|
||||
// not available.
|
||||
dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, SDL_AUDIO_ALLOW_CHANNELS_CHANGE);
|
||||
if (dev == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to open audio device");
|
||||
// We'll probably have issues during audio stream init, but we'll
|
||||
// try anyway
|
||||
ret = AUDIO_CONFIGURATION_STEREO;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
SDL_CloseAudioDevice(dev);
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio device has %d channels", have.channels);
|
||||
|
||||
if (have.channels > 2) {
|
||||
// We don't support quadraphonic or 7.1 surround, but SDL
|
||||
// should be able to downmix or upmix better from 5.1 than
|
||||
// from stereo, so use 5.1 in non-stereo cases.
|
||||
ret = AUDIO_CONFIGURATION_51_SURROUND;
|
||||
}
|
||||
else {
|
||||
ret = AUDIO_CONFIGURATION_STEREO;
|
||||
}
|
||||
|
||||
Exit:
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int Session::sdlAudioInit(int /* audioConfiguration */,
|
||||
POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
||||
void* /* arContext */, int /* arFlags */)
|
||||
{
|
||||
SDL_AudioSpec want, have;
|
||||
int error;
|
||||
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_InitSubSystem(SDL_INIT_AUDIO) failed: %s",
|
||||
SDL_GetError());
|
||||
return AUDIO_CONFIGURATION_STEREO;
|
||||
}
|
||||
|
||||
SDL_zero(want);
|
||||
want.freq = opusConfig->sampleRate;
|
||||
want.format = AUDIO_S16;
|
||||
want.channels = opusConfig->channelCount;
|
||||
|
||||
// This is supposed to be a power of 2, but our
|
||||
// frames contain a non-power of 2 number of samples,
|
||||
// so the slop would require buffering another full frame.
|
||||
// Specifying non-Po2 seems to work for our supported platforms.
|
||||
want.samples = SAMPLES_PER_FRAME;
|
||||
|
||||
s_AudioDevice = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
||||
if (s_AudioDevice == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to open audio device: %s",
|
||||
SDL_GetError());
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
return -1;
|
||||
}
|
||||
|
||||
s_OpusDecoder = opus_multistream_decoder_create(opusConfig->sampleRate,
|
||||
opusConfig->channelCount,
|
||||
opusConfig->streams,
|
||||
opusConfig->coupledStreams,
|
||||
opusConfig->mapping,
|
||||
&error);
|
||||
if (s_OpusDecoder == NULL) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create decoder: %d",
|
||||
error);
|
||||
SDL_CloseAudioDevice(s_AudioDevice);
|
||||
s_AudioDevice = 0;
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
return -2;
|
||||
}
|
||||
|
||||
// SDL counts pending samples in the queued
|
||||
// audio size using the WASAPI backend. This
|
||||
// includes silence, which can throw off our
|
||||
// pending data count. Get a baseline so we
|
||||
// can exclude that data.
|
||||
s_BaselinePendingData = 0;
|
||||
#ifdef Q_OS_WIN32
|
||||
for (int i = 0; i < 100; i++) {
|
||||
s_BaselinePendingData = qMax(s_BaselinePendingData, SDL_GetQueuedAudioSize(s_AudioDevice));
|
||||
SDL_Delay(10);
|
||||
}
|
||||
#endif
|
||||
s_BaselinePendingData *= 2;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Baseline pending audio data: %d bytes",
|
||||
s_BaselinePendingData);
|
||||
|
||||
s_ChannelCount = opusConfig->channelCount;
|
||||
s_SampleIndex = 0;
|
||||
s_PendingDrops = s_PendingHardDrops = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Session::sdlAudioStart()
|
||||
{
|
||||
// Unpause the audio device
|
||||
SDL_PauseAudioDevice(s_AudioDevice, 0);
|
||||
}
|
||||
|
||||
void Session::sdlAudioStop()
|
||||
{
|
||||
// Pause the audio device
|
||||
SDL_PauseAudioDevice(s_AudioDevice, 1);
|
||||
}
|
||||
|
||||
void Session::sdlAudioCleanup()
|
||||
{
|
||||
SDL_CloseAudioDevice(s_AudioDevice);
|
||||
s_AudioDevice = 0;
|
||||
|
||||
opus_multistream_decoder_destroy(s_OpusDecoder);
|
||||
s_OpusDecoder = nullptr;
|
||||
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
||||
}
|
||||
|
||||
void Session::sdlAudioDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||
{
|
||||
int samplesDecoded;
|
||||
|
||||
s_SampleIndex++;
|
||||
|
||||
Uint32 queuedAudio = qMax((int)SDL_GetQueuedAudioSize(s_AudioDevice) - (int)s_BaselinePendingData, 0);
|
||||
Uint32 framesQueued = queuedAudio / (SAMPLES_PER_FRAME * s_ChannelCount * sizeof(short));
|
||||
|
||||
// Pend enough drops to get us back to MIN_QUEUED_FRAMES
|
||||
if (framesQueued - s_PendingHardDrops > STOP_THE_WORLD_LIMIT) {
|
||||
s_PendingHardDrops = framesQueued - MIN_QUEUED_FRAMES;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Pending hard drop of %u audio frames",
|
||||
s_PendingHardDrops);
|
||||
}
|
||||
else if (framesQueued - s_PendingHardDrops - s_PendingDrops > MAX_QUEUED_FRAMES) {
|
||||
s_PendingDrops = framesQueued - MIN_QUEUED_FRAMES;
|
||||
}
|
||||
|
||||
// Determine if this frame should be dropped
|
||||
if (framesQueued <= MIN_QUEUED_FRAMES) {
|
||||
s_PendingDrops = s_PendingHardDrops = 0;
|
||||
}
|
||||
else if (s_PendingHardDrops != 0) {
|
||||
// Hard drops happen all at once to forcefully
|
||||
// resync with the source.
|
||||
s_PendingHardDrops--;
|
||||
return;
|
||||
}
|
||||
else if (s_PendingDrops != 0 && s_SampleIndex % DROP_RATIO_DENOM == 0) {
|
||||
// Normal drops are interspersed with the audio data
|
||||
// to hide the glitches.
|
||||
s_PendingDrops--;
|
||||
return;
|
||||
}
|
||||
|
||||
samplesDecoded = opus_multistream_decode(s_OpusDecoder,
|
||||
(unsigned char*)sampleData,
|
||||
sampleLength,
|
||||
s_OpusDecodeBuffer,
|
||||
SAMPLES_PER_FRAME,
|
||||
0);
|
||||
if (samplesDecoded > 0) {
|
||||
if (SDL_QueueAudio(s_AudioDevice,
|
||||
s_OpusDecodeBuffer,
|
||||
sizeof(short) * samplesDecoded * s_ChannelCount) < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to queue audio sample: %s",
|
||||
SDL_GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
#include "../session.hpp"
|
||||
#include "renderers/renderer.h"
|
||||
#include "renderers/sdl.h"
|
||||
#include "renderers/qtaud.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
IAudioRenderer* Session::createAudioRenderer()
|
||||
{
|
||||
return new SdlAudioRenderer();
|
||||
}
|
||||
|
||||
bool Session::testAudio(int audioConfiguration)
|
||||
{
|
||||
IAudioRenderer* audioRenderer;
|
||||
|
||||
audioRenderer = createAudioRenderer();
|
||||
if (audioRenderer == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = audioRenderer->testAudio(audioConfiguration);
|
||||
|
||||
delete audioRenderer;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int Session::detectAudioConfiguration()
|
||||
{
|
||||
IAudioRenderer* audioRenderer;
|
||||
|
||||
audioRenderer = createAudioRenderer();
|
||||
if (audioRenderer == nullptr) {
|
||||
// Hope for the best
|
||||
return AUDIO_CONFIGURATION_STEREO;
|
||||
}
|
||||
|
||||
int audioConfig = audioRenderer->detectAudioConfiguration();
|
||||
|
||||
delete audioRenderer;
|
||||
|
||||
return audioConfig;
|
||||
}
|
||||
|
||||
int Session::arInit(int /* audioConfiguration */,
|
||||
const POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
||||
void* /* arContext */, int /* arFlags */)
|
||||
{
|
||||
int error;
|
||||
|
||||
SDL_memcpy(&s_ActiveSession->m_AudioConfig, opusConfig, sizeof(*opusConfig));
|
||||
|
||||
s_ActiveSession->m_OpusDecoder =
|
||||
opus_multistream_decoder_create(opusConfig->sampleRate,
|
||||
opusConfig->channelCount,
|
||||
opusConfig->streams,
|
||||
opusConfig->coupledStreams,
|
||||
opusConfig->mapping,
|
||||
&error);
|
||||
if (s_ActiveSession->m_OpusDecoder == NULL) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create decoder: %d",
|
||||
error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer();
|
||||
if (s_ActiveSession->m_AudioRenderer == nullptr) {
|
||||
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (!s_ActiveSession->m_AudioRenderer->prepareForPlayback(opusConfig)) {
|
||||
delete s_ActiveSession->m_AudioRenderer;
|
||||
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
|
||||
return -3;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio stream has %d channels",
|
||||
opusConfig->channelCount);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Session::arCleanup()
|
||||
{
|
||||
// m_AudioRenderer is deleted in cleanupAudioRenderer()
|
||||
|
||||
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
|
||||
s_ActiveSession->m_OpusDecoder = nullptr;
|
||||
}
|
||||
|
||||
// This is called on the main thread
|
||||
void Session::cleanupAudioRendererOnMainThread()
|
||||
{
|
||||
SDL_AtomicLock(&m_AudioRendererLock);
|
||||
delete m_AudioRenderer;
|
||||
m_AudioRenderer = nullptr;
|
||||
SDL_AtomicUnlock(&m_AudioRendererLock);
|
||||
}
|
||||
|
||||
void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||
{
|
||||
int samplesDecoded;
|
||||
|
||||
samplesDecoded = opus_multistream_decode(s_ActiveSession->m_OpusDecoder,
|
||||
(unsigned char*)sampleData,
|
||||
sampleLength,
|
||||
s_ActiveSession->m_OpusDecodeBuffer,
|
||||
SAMPLES_PER_FRAME,
|
||||
0);
|
||||
if (samplesDecoded > 0) {
|
||||
SDL_AtomicLock(&s_ActiveSession->m_AudioRendererLock);
|
||||
if (s_ActiveSession->m_AudioRenderer != nullptr) {
|
||||
s_ActiveSession->m_AudioRenderer->submitAudio(s_ActiveSession->m_OpusDecodeBuffer,
|
||||
static_cast<int>(sizeof(short) *
|
||||
samplesDecoded *
|
||||
s_ActiveSession->m_AudioConfig.channelCount));
|
||||
}
|
||||
SDL_AtomicUnlock(&s_ActiveSession->m_AudioRendererLock);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
#include "qtaud.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
QtAudioRenderer::QtAudioRenderer()
|
||||
: m_AudioOutput(nullptr),
|
||||
m_OutputDevice(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QtAudioRenderer::~QtAudioRenderer()
|
||||
{
|
||||
delete m_AudioOutput;
|
||||
}
|
||||
|
||||
bool QtAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig)
|
||||
{
|
||||
QAudioFormat format;
|
||||
|
||||
// testAudio() assumes this sample rate
|
||||
Q_ASSERT(opusConfig->sampleRate == 48000);
|
||||
|
||||
format.setSampleRate(opusConfig->sampleRate);
|
||||
format.setChannelCount(opusConfig->channelCount);
|
||||
format.setSampleSize(16);
|
||||
format.setSampleType(QAudioFormat::SignedInt);
|
||||
format.setCodec("audio/pcm");
|
||||
|
||||
m_AudioOutput = new QAudioOutput(format);
|
||||
|
||||
// Buffer 40 ms of audio data
|
||||
m_AudioOutput->setBufferSize(format.bytesForDuration(40000));
|
||||
|
||||
m_OutputDevice = m_AudioOutput->start();
|
||||
if (m_OutputDevice == nullptr) {
|
||||
qWarning() << "Audio start failed:" << m_AudioOutput->error();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check what buffer size was really used after starting
|
||||
qInfo() << "Audio stream buffer:" << m_AudioOutput->bufferSize() << "bytes";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void QtAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
||||
{
|
||||
m_OutputDevice->write((const char *)audioBuffer, audioSize);
|
||||
}
|
||||
|
||||
bool QtAudioRenderer::testAudio(int audioConfiguration)
|
||||
{
|
||||
QAudioFormat format;
|
||||
|
||||
format.setSampleRate(48000);
|
||||
format.setSampleSize(16);
|
||||
format.setSampleType(QAudioFormat::SignedInt);
|
||||
format.setCodec("audio/pcm");
|
||||
|
||||
switch (audioConfiguration) {
|
||||
case AUDIO_CONFIGURATION_STEREO:
|
||||
format.setChannelCount(2);
|
||||
break;
|
||||
case AUDIO_CONFIGURATION_51_SURROUND:
|
||||
format.setChannelCount(6);
|
||||
break;
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (QAudioDeviceInfo::defaultOutputDevice().isFormatSupported(format)) {
|
||||
qInfo() << "Audio test successful with" << format.channelCount() << "channels";
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
qWarning() << "Audio test failed with" << format.channelCount() << "channels";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int QtAudioRenderer::detectAudioConfiguration()
|
||||
{
|
||||
int preferredChannelCount = QAudioDeviceInfo::defaultOutputDevice().preferredFormat().channelCount();
|
||||
|
||||
qInfo() << "Audio output device prefers" << preferredChannelCount << "channel configuration";
|
||||
|
||||
// We can better downmix 5.1 to quad than we can upmix stereo
|
||||
if (preferredChannelCount > 2) {
|
||||
return AUDIO_CONFIGURATION_51_SURROUND;
|
||||
}
|
||||
else {
|
||||
return AUDIO_CONFIGURATION_STEREO;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "renderer.h"
|
||||
#include <QAudioOutput>
|
||||
|
||||
class QtAudioRenderer : public IAudioRenderer
|
||||
{
|
||||
public:
|
||||
QtAudioRenderer();
|
||||
|
||||
virtual ~QtAudioRenderer();
|
||||
|
||||
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
||||
|
||||
virtual void submitAudio(short* audioBuffer, int audioSize);
|
||||
|
||||
virtual bool testAudio(int audioConfiguration);
|
||||
|
||||
virtual int detectAudioConfiguration();
|
||||
|
||||
private:
|
||||
QAudioOutput* m_AudioOutput;
|
||||
QIODevice* m_OutputDevice;
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
#define MAX_CHANNELS 6
|
||||
#define SAMPLES_PER_FRAME 240
|
||||
|
||||
class IAudioRenderer
|
||||
{
|
||||
public:
|
||||
virtual ~IAudioRenderer() {}
|
||||
|
||||
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig) = 0;
|
||||
|
||||
virtual void submitAudio(short* audioBuffer, int audioSize) = 0;
|
||||
|
||||
virtual bool testAudio(int audioConfiguration) = 0;
|
||||
|
||||
virtual int detectAudioConfiguration() = 0;
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "renderer.h"
|
||||
#include <SDL.h>
|
||||
|
||||
class SdlAudioRenderer : public IAudioRenderer
|
||||
{
|
||||
public:
|
||||
SdlAudioRenderer();
|
||||
|
||||
virtual ~SdlAudioRenderer();
|
||||
|
||||
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
||||
|
||||
virtual void submitAudio(short* audioBuffer, int audioSize);
|
||||
|
||||
virtual bool testAudio(int audioConfiguration);
|
||||
|
||||
virtual int detectAudioConfiguration();
|
||||
|
||||
private:
|
||||
SDL_AudioDeviceID m_AudioDevice;
|
||||
Uint32 m_ChannelCount;
|
||||
Uint32 m_PendingDrops;
|
||||
Uint32 m_PendingHardDrops;
|
||||
Uint32 m_SampleIndex;
|
||||
Uint32 m_BaselinePendingData;
|
||||
};
|
||||
@@ -0,0 +1,218 @@
|
||||
#include "sdl.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
#include <SDL.h>
|
||||
|
||||
#include <QAudioDeviceInfo>
|
||||
#include <QtGlobal>
|
||||
|
||||
#define MIN_QUEUED_FRAMES 2
|
||||
#define MAX_QUEUED_FRAMES 4
|
||||
#define STOP_THE_WORLD_LIMIT 20
|
||||
#define DROP_RATIO_DENOM 32
|
||||
|
||||
// Detecting this with SDL is quite problematic, so we'll use Qt's
|
||||
// multimedia framework to do so. It appears to be actually
|
||||
// accurate on Linux and macOS, unlike using SDL and relying
|
||||
// on a channel change in the format received.
|
||||
int SdlAudioRenderer::detectAudioConfiguration()
|
||||
{
|
||||
int preferredChannelCount = QAudioDeviceInfo::defaultOutputDevice().preferredFormat().channelCount();
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio output device prefers %d channel configuration",
|
||||
preferredChannelCount);
|
||||
|
||||
// We can better downmix 5.1 to quad than we can upmix stereo
|
||||
if (preferredChannelCount > 2) {
|
||||
return AUDIO_CONFIGURATION_51_SURROUND;
|
||||
}
|
||||
else {
|
||||
return AUDIO_CONFIGURATION_STEREO;
|
||||
}
|
||||
}
|
||||
|
||||
bool SdlAudioRenderer::testAudio(int audioConfiguration)
|
||||
{
|
||||
SDL_AudioSpec want, have;
|
||||
SDL_AudioDeviceID dev;
|
||||
|
||||
SDL_zero(want);
|
||||
want.freq = 48000;
|
||||
want.format = AUDIO_S16;
|
||||
want.samples = SAMPLES_PER_FRAME;
|
||||
|
||||
switch (audioConfiguration) {
|
||||
case AUDIO_CONFIGURATION_STEREO:
|
||||
want.channels = 2;
|
||||
break;
|
||||
case AUDIO_CONFIGURATION_51_SURROUND:
|
||||
want.channels = 6;
|
||||
break;
|
||||
default:
|
||||
SDL_assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Test audio device for functionality
|
||||
dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
||||
if (dev == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio test - Failed to open audio device: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_CloseAudioDevice(dev);
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio test - Successful with %d channels",
|
||||
want.channels);
|
||||
return true;
|
||||
}
|
||||
|
||||
SdlAudioRenderer::SdlAudioRenderer()
|
||||
: m_AudioDevice(0),
|
||||
m_ChannelCount(0),
|
||||
m_PendingDrops(0),
|
||||
m_PendingHardDrops(0),
|
||||
m_SampleIndex(0),
|
||||
m_BaselinePendingData(0)
|
||||
{
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_InitSubSystem(SDL_INIT_AUDIO) failed: %s",
|
||||
SDL_GetError());
|
||||
SDL_assert(SDL_WasInit(SDL_INIT_AUDIO));
|
||||
}
|
||||
}
|
||||
|
||||
bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig)
|
||||
{
|
||||
SDL_AudioSpec want, have;
|
||||
|
||||
SDL_zero(want);
|
||||
want.freq = opusConfig->sampleRate;
|
||||
want.format = AUDIO_S16;
|
||||
want.channels = opusConfig->channelCount;
|
||||
|
||||
// This is supposed to be a power of 2, but our
|
||||
// frames contain a non-power of 2 number of samples,
|
||||
// so the slop would require buffering another full frame.
|
||||
// Specifying non-Po2 seems to work for our supported platforms.
|
||||
want.samples = SAMPLES_PER_FRAME;
|
||||
|
||||
m_AudioDevice = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
||||
if (m_AudioDevice == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to open audio device: %s",
|
||||
SDL_GetError());
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Desired audio buffer: %u samples (%lu bytes)",
|
||||
want.samples,
|
||||
want.samples * sizeof(short) * want.channels);
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Obtained audio buffer: %u samples (%u bytes)",
|
||||
have.samples,
|
||||
have.size);
|
||||
|
||||
// SDL counts pending samples in the queued
|
||||
// audio size using the WASAPI backend. This
|
||||
// includes silence, which can throw off our
|
||||
// pending data count. Get a baseline so we
|
||||
// can exclude that data.
|
||||
m_BaselinePendingData = 0;
|
||||
#ifdef Q_OS_WIN32
|
||||
for (int i = 0; i < 100; i++) {
|
||||
m_BaselinePendingData = qMax(m_BaselinePendingData, SDL_GetQueuedAudioSize(m_AudioDevice));
|
||||
SDL_Delay(10);
|
||||
}
|
||||
#endif
|
||||
m_BaselinePendingData *= 2;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Baseline pending audio data: %d bytes",
|
||||
m_BaselinePendingData);
|
||||
|
||||
m_ChannelCount = opusConfig->channelCount;
|
||||
m_SampleIndex = 0;
|
||||
m_PendingDrops = m_PendingHardDrops = 0;
|
||||
|
||||
// Start playback
|
||||
SDL_PauseAudioDevice(m_AudioDevice, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SdlAudioRenderer::~SdlAudioRenderer()
|
||||
{
|
||||
if (m_AudioDevice != 0) {
|
||||
// Stop playback
|
||||
SDL_PauseAudioDevice(m_AudioDevice, 1);
|
||||
SDL_CloseAudioDevice(m_AudioDevice);
|
||||
}
|
||||
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
||||
}
|
||||
|
||||
void SdlAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
||||
{
|
||||
m_SampleIndex++;
|
||||
|
||||
Uint32 queuedAudio = SDL_GetQueuedAudioSize(m_AudioDevice);
|
||||
if (queuedAudio > m_BaselinePendingData) {
|
||||
queuedAudio -= m_BaselinePendingData;
|
||||
}
|
||||
else {
|
||||
queuedAudio = 0;
|
||||
}
|
||||
|
||||
Uint32 framesQueued = queuedAudio / (SAMPLES_PER_FRAME * m_ChannelCount * sizeof(short));
|
||||
|
||||
// We must check this prior to the below checks to ensure we don't
|
||||
// underflow if framesQueued - m_PendingHardDrops < 0.
|
||||
if (framesQueued <= MIN_QUEUED_FRAMES) {
|
||||
m_PendingDrops = m_PendingHardDrops = 0;
|
||||
}
|
||||
// Pend enough drops to get us back to MIN_QUEUED_FRAMES, checking first
|
||||
// to ensure we don't underflow.
|
||||
else if (framesQueued > m_PendingHardDrops &&
|
||||
framesQueued - m_PendingHardDrops > STOP_THE_WORLD_LIMIT) {
|
||||
m_PendingHardDrops = framesQueued - MIN_QUEUED_FRAMES;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Pending hard drop of %u audio frames",
|
||||
m_PendingHardDrops);
|
||||
}
|
||||
// If we're under the stop the world limit, we can drop samples
|
||||
// gracefully over the next little while.
|
||||
else if (framesQueued > m_PendingHardDrops + m_PendingDrops &&
|
||||
framesQueued - m_PendingHardDrops - m_PendingDrops > MAX_QUEUED_FRAMES) {
|
||||
m_PendingDrops = framesQueued - MIN_QUEUED_FRAMES;
|
||||
}
|
||||
|
||||
// Determine if this frame should be dropped
|
||||
if (m_PendingHardDrops != 0) {
|
||||
// Hard drops happen all at once to forcefully
|
||||
// resync with the source.
|
||||
m_PendingHardDrops--;
|
||||
return;
|
||||
}
|
||||
else if (m_PendingDrops != 0 && m_SampleIndex % DROP_RATIO_DENOM == 0) {
|
||||
// Normal drops are interspersed with the audio data
|
||||
// to hide the glitches.
|
||||
m_PendingDrops--;
|
||||
return;
|
||||
}
|
||||
|
||||
if (SDL_QueueAudio(m_AudioDevice, audioBuffer, audioSize) < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to queue audio sample: %s",
|
||||
SDL_GetError());
|
||||
}
|
||||
}
|
||||
+113
-10
@@ -1,6 +1,10 @@
|
||||
#include <Limelight.h>
|
||||
#include <SDL.h>
|
||||
#include "streaming/session.hpp"
|
||||
#include "path.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QDir>
|
||||
|
||||
#define VK_0 0x30
|
||||
#define VK_A 0x41
|
||||
@@ -20,9 +24,21 @@ const int SdlInputHandler::k_ButtonMap[] = {
|
||||
UP_FLAG, DOWN_FLAG, LEFT_FLAG, RIGHT_FLAG
|
||||
};
|
||||
|
||||
SdlInputHandler::SdlInputHandler(bool multiController)
|
||||
: m_MultiController(multiController)
|
||||
SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer* computer)
|
||||
: m_LastMouseMotionTime(0),
|
||||
m_MultiController(prefs.multiController),
|
||||
m_NeedsInputDelay(false)
|
||||
{
|
||||
// Allow gamepad input when the app doesn't have focus
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||
|
||||
// If mouse acceleration is enabled, use relative mode warp (which
|
||||
// is via normal motion events that are influenced by mouse acceleration).
|
||||
// Otherwise, we'll use raw input capture which is straight from the device
|
||||
// without modification by the OS.
|
||||
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_WARP,
|
||||
prefs.mouseAcceleration ? "1" : "0");
|
||||
|
||||
// We need to reinit this each time, since you only get
|
||||
// an initial set of gamepad arrival events once per init.
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_GAMECONTROLLER));
|
||||
@@ -32,10 +48,52 @@ SdlInputHandler::SdlInputHandler(bool multiController)
|
||||
SDL_GetError());
|
||||
}
|
||||
|
||||
QString mappingFile = Path::getGamepadMappingFile();
|
||||
if (!mappingFile.isEmpty()) {
|
||||
std::string mappingFileNative = QDir::toNativeSeparators(mappingFile).toStdString();
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Loading gamepad mappings from: %s",
|
||||
mappingFileNative.c_str());
|
||||
|
||||
int newMappings = SDL_GameControllerAddMappingsFromFile(mappingFileNative.c_str());
|
||||
if (newMappings < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error loading gamepad mappings: %s",
|
||||
SDL_GetError());
|
||||
}
|
||||
else {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Loaded %d new gamepad mappings",
|
||||
newMappings);
|
||||
}
|
||||
}
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"No gamepad mapping file found");
|
||||
}
|
||||
|
||||
if (!m_MultiController) {
|
||||
// Player 1 is always present in non-MC mode
|
||||
m_GamepadMask = 0x1;
|
||||
}
|
||||
else {
|
||||
// Otherwise, detect gamepads on the fly
|
||||
m_GamepadMask = 0;
|
||||
}
|
||||
|
||||
// Prior to GFE 3.14.1, sending too many mouse motion events can cause
|
||||
// GFE to choke and input latency to increase significantly. We will
|
||||
// artificially throttle them to avoid this situation.
|
||||
QVector<int> gfeVersion = NvHTTP::parseQuad(computer->gfeVersion);
|
||||
if (gfeVersion.isEmpty() || // Very old versions don't have GfeVersion at all
|
||||
gfeVersion[0] < 3 ||
|
||||
(gfeVersion[0] == 3 && gfeVersion[1] < 14) ||
|
||||
(gfeVersion[0] == 3 && gfeVersion[1] == 14 && gfeVersion[2] < 1)) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"This older version of GFE requires input delay hack");
|
||||
m_NeedsInputDelay = true;
|
||||
}
|
||||
|
||||
SDL_zero(m_GamepadState);
|
||||
}
|
||||
@@ -153,6 +211,7 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
||||
case SDL_SCANCODE_CLEAR:
|
||||
keyCode = 0x0C;
|
||||
break;
|
||||
case SDL_SCANCODE_KP_ENTER: // FIXME: Is this correct?
|
||||
case SDL_SCANCODE_RETURN:
|
||||
keyCode = 0x0D;
|
||||
break;
|
||||
@@ -230,7 +289,7 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
||||
case SDL_SCANCODE_KP_MINUS:
|
||||
keyCode = 0x6D;
|
||||
break;
|
||||
case SDL_SCANCODE_KP_DECIMAL:
|
||||
case SDL_SCANCODE_KP_PERIOD:
|
||||
keyCode = 0x6E;
|
||||
break;
|
||||
case SDL_SCANCODE_KP_DIVIDE:
|
||||
@@ -284,6 +343,9 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
||||
case SDL_SCANCODE_SEMICOLON:
|
||||
keyCode = 0xBA;
|
||||
break;
|
||||
case SDL_SCANCODE_EQUALS:
|
||||
keyCode = 0xBB;
|
||||
break;
|
||||
case SDL_SCANCODE_COMMA:
|
||||
keyCode = 0xBC;
|
||||
break;
|
||||
@@ -311,6 +373,9 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
||||
case SDL_SCANCODE_APOSTROPHE:
|
||||
keyCode = 0xDE;
|
||||
break;
|
||||
case SDL_SCANCODE_NONUSBACKSLASH:
|
||||
keyCode = 0xE2;
|
||||
break;
|
||||
default:
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unhandled button event: %d",
|
||||
@@ -329,9 +394,13 @@ void SdlInputHandler::handleMouseButtonEvent(SDL_MouseButtonEvent* event)
|
||||
{
|
||||
int button;
|
||||
|
||||
// Capture the mouse again if clicked when unbound
|
||||
// Capture the mouse again if clicked when unbound.
|
||||
// We start capture on left button released instead of
|
||||
// pressed to avoid sending an errant mouse button released
|
||||
// event to the host when clicking into our window (since
|
||||
// the pressed event was consumed by this code).
|
||||
if (event->button == SDL_BUTTON_LEFT &&
|
||||
event->state == SDL_PRESSED &&
|
||||
event->state == SDL_RELEASED &&
|
||||
!SDL_GetRelativeMouseMode()) {
|
||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||
return;
|
||||
@@ -375,10 +444,39 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
||||
// Not capturing
|
||||
return;
|
||||
}
|
||||
short xdelta = (short)event->xrel;
|
||||
short ydelta = (short)event->yrel;
|
||||
|
||||
if (event->xrel != 0 || event->yrel != 0) {
|
||||
LiSendMouseMoveEvent((unsigned short)event->xrel,
|
||||
(unsigned short)event->yrel);
|
||||
// If we're sending more than one motion event per millisecond,
|
||||
// delay for 1 ms to allow batching of mouse move events. On older
|
||||
// versions of GFE, we will unconditionally wait this 1 ms to
|
||||
// work around an input processing issue that causes massive mouse latency.
|
||||
Uint32 currentTime = SDL_GetTicks();
|
||||
if (m_NeedsInputDelay || !SDL_TICKS_PASSED(currentTime, m_LastMouseMotionTime + 1)) {
|
||||
SDL_Delay(1);
|
||||
currentTime = SDL_GetTicks();
|
||||
}
|
||||
m_LastMouseMotionTime = currentTime;
|
||||
|
||||
// Pump even if we didn't delay since we might get some extra events
|
||||
SDL_PumpEvents();
|
||||
|
||||
// Batch all of the pending mouse motion events
|
||||
SDL_Event nextEvent;
|
||||
while (SDL_PeepEvents(&nextEvent,
|
||||
1,
|
||||
SDL_GETEVENT,
|
||||
SDL_MOUSEMOTION,
|
||||
SDL_MOUSEMOTION) == 1) {
|
||||
// In theory, these can overflow but in practice
|
||||
// it should be highly unlikely since it would require
|
||||
// moving 64K pixels in 1 ms.
|
||||
xdelta += nextEvent.motion.xrel;
|
||||
ydelta += nextEvent.motion.yrel;
|
||||
}
|
||||
|
||||
if (xdelta != 0 || ydelta != 0) {
|
||||
LiSendMouseMoveEvent(xdelta, ydelta);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,13 +536,18 @@ void SdlInputHandler::handleControllerAxisEvent(SDL_ControllerAxisEvent* event)
|
||||
state->lsX = event->value;
|
||||
break;
|
||||
case SDL_CONTROLLER_AXIS_LEFTY:
|
||||
state->lsY = -event->value;
|
||||
// Signed values have one more negative value than
|
||||
// positive value, so inverting the sign on -32768
|
||||
// could actually cause the value to overflow and
|
||||
// wrap around to be negative again. Avoid that by
|
||||
// capping the value at 32767.
|
||||
state->lsY = -qMax(event->value, (short)-32767);
|
||||
break;
|
||||
case SDL_CONTROLLER_AXIS_RIGHTX:
|
||||
state->rsX = event->value;
|
||||
break;
|
||||
case SDL_CONTROLLER_AXIS_RIGHTY:
|
||||
state->rsY = -event->value;
|
||||
state->rsY = -qMax(event->value, (short)-32767);
|
||||
break;
|
||||
case SDL_CONTROLLER_AXIS_TRIGGERLEFT:
|
||||
state->lt = (unsigned char)(event->value * 255UL / 32767);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "settings/streamingpreferences.h"
|
||||
#include "backend/computermanager.h"
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
struct GamepadState {
|
||||
@@ -18,7 +21,7 @@ struct GamepadState {
|
||||
class SdlInputHandler
|
||||
{
|
||||
public:
|
||||
explicit SdlInputHandler(bool multiController);
|
||||
explicit SdlInputHandler(StreamingPreferences& prefs, NvComputer* computer);
|
||||
|
||||
~SdlInputHandler();
|
||||
|
||||
@@ -44,7 +47,9 @@ private:
|
||||
|
||||
void sendGamepadState(GamepadState* state);
|
||||
|
||||
Uint32 m_LastMouseMotionTime;
|
||||
bool m_MultiController;
|
||||
bool m_NeedsInputDelay;
|
||||
int m_GamepadMask;
|
||||
GamepadState m_GamepadState[MAX_GAMEPADS];
|
||||
|
||||
|
||||
+509
-142
@@ -1,10 +1,17 @@
|
||||
#include "session.hpp"
|
||||
#include "settings/streamingpreferences.h"
|
||||
#include "streaming/streamutils.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
#include <SDL.h>
|
||||
#include "utils.h"
|
||||
|
||||
// HACK: Need to call SetThreadExecutionState() because SDL doesn't
|
||||
#ifdef Q_OS_WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
#include "video/ffmpeg.h"
|
||||
#endif
|
||||
@@ -13,15 +20,6 @@
|
||||
#include "video/sl.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// Using full-screen desktop allows us to avoid needing to enable V-sync
|
||||
// and it also avoids some strange flickering issues on my Win7 test machine
|
||||
// with Intel HD 5500 graphics.
|
||||
#define SDL_OS_FULLSCREEN_FLAG SDL_WINDOW_FULLSCREEN_DESKTOP
|
||||
#else
|
||||
#define SDL_OS_FULLSCREEN_FLAG SDL_WINDOW_FULLSCREEN
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// Scaling the icon down on Win32 looks dreadful, so render at lower res
|
||||
#define ICON_SIZE 32
|
||||
@@ -50,11 +48,11 @@ CONNECTION_LISTENER_CALLBACKS Session::k_ConnCallbacks = {
|
||||
};
|
||||
|
||||
AUDIO_RENDERER_CALLBACKS Session::k_AudioCallbacks = {
|
||||
Session::sdlAudioInit,
|
||||
Session::sdlAudioStart,
|
||||
Session::sdlAudioStop,
|
||||
Session::sdlAudioCleanup,
|
||||
Session::sdlAudioDecodeAndPlaySample,
|
||||
Session::arInit,
|
||||
nullptr,
|
||||
nullptr,
|
||||
Session::arCleanup,
|
||||
Session::arDecodeAndPlaySample,
|
||||
CAPABILITY_DIRECT_SUBMIT
|
||||
};
|
||||
|
||||
@@ -81,6 +79,8 @@ void Session::clStageFailed(int stage, long errorCode)
|
||||
|
||||
void Session::clConnectionTerminated(long errorCode)
|
||||
{
|
||||
emit s_ActiveSession->displayLaunchError("Connection terminated");
|
||||
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Connection terminated: %ld",
|
||||
errorCode);
|
||||
@@ -104,13 +104,15 @@ void Session::clLogMessage(const char* format, ...)
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
#define CALL_INITIALIZE(dec) (dec)->initialize(vds, window, videoFormat, width, height, frameRate, enableVsync)
|
||||
|
||||
bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window* window, int videoFormat, int width, int height,
|
||||
int frameRate, IVideoDecoder*& chosenDecoder)
|
||||
int frameRate, bool enableVsync, IVideoDecoder*& chosenDecoder)
|
||||
{
|
||||
#ifdef HAVE_SLVIDEO
|
||||
chosenDecoder = new SLVideoDecoder();
|
||||
if (chosenDecoder->initialize(vds, window, videoFormat, width, height, frameRate)) {
|
||||
if (CALL_INITIALIZE(chosenDecoder)) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SLVideo video decoder chosen");
|
||||
return true;
|
||||
@@ -125,7 +127,7 @@ bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
chosenDecoder = new FFmpegVideoDecoder();
|
||||
if (chosenDecoder->initialize(vds, window, videoFormat, width, height, frameRate)) {
|
||||
if (CALL_INITIALIZE(chosenDecoder)) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"FFmpeg-based video decoder chosen");
|
||||
return true;
|
||||
@@ -138,6 +140,10 @@ bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(HAVE_FFMPEG) && !defined(HAVE_SLVIDEO)
|
||||
#error No video decoding libraries available!
|
||||
#endif
|
||||
|
||||
// If we reach this, we didn't initialize any decoders successfully
|
||||
return false;
|
||||
}
|
||||
@@ -153,6 +159,9 @@ int Session::drSetup(int videoFormat, int width, int height, int frameRate, void
|
||||
// don't have to hide and show the SDL window (which seems to
|
||||
// cause pointer hiding to break on Windows).
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Video stream is %dx%dx%d (format 0x%x)",
|
||||
width, height, frameRate, videoFormat);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -205,7 +214,7 @@ bool Session::isHardwareDecodeAvailable(StreamingPreferences::VideoDecoderSelect
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, decoder)) {
|
||||
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, true, decoder)) {
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return false;
|
||||
@@ -221,14 +230,66 @@ bool Session::isHardwareDecodeAvailable(StreamingPreferences::VideoDecoderSelect
|
||||
return ret;
|
||||
}
|
||||
|
||||
int Session::getDecoderCapabilities(StreamingPreferences::VideoDecoderSelection vds,
|
||||
int videoFormat, int width, int height, int frameRate)
|
||||
{
|
||||
IVideoDecoder* decoder;
|
||||
|
||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_Window* window = SDL_CreateWindow("", 0, 0, width, height, SDL_WINDOW_HIDDEN);
|
||||
if (!window) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create window for hardware decode test: %s",
|
||||
SDL_GetError());
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, true, decoder)) {
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_DestroyWindow(window);
|
||||
|
||||
int caps = decoder->getDecoderCapabilities();
|
||||
|
||||
delete decoder;
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
Session::Session(NvComputer* computer, NvApp& app)
|
||||
: m_Computer(computer),
|
||||
m_App(app),
|
||||
m_Window(nullptr),
|
||||
m_VideoDecoder(nullptr),
|
||||
m_DecoderLock(0),
|
||||
m_NeedsIdr(false)
|
||||
m_NeedsIdr(false),
|
||||
m_AudioDisabled(false),
|
||||
m_DisplayOriginX(0),
|
||||
m_DisplayOriginY(0),
|
||||
m_PendingWindowedTransition(false),
|
||||
m_OpusDecoder(nullptr),
|
||||
m_AudioRenderer(nullptr),
|
||||
m_AudioRendererLock(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Session::initialize()
|
||||
{
|
||||
qInfo() << "Server GPU:" << m_Computer->gpuModel;
|
||||
qInfo() << "Server GFE version:" << m_Computer->gfeVersion;
|
||||
|
||||
LiInitializeVideoCallbacks(&m_VideoCallbacks);
|
||||
m_VideoCallbacks.setup = drSetup;
|
||||
m_VideoCallbacks.submitDecodeUnit = drSubmitDecodeUnit;
|
||||
@@ -237,7 +298,11 @@ Session::Session(NvComputer* computer, NvApp& app)
|
||||
m_VideoCallbacks.capabilities |= CAPABILITY_DIRECT_SUBMIT;
|
||||
|
||||
// Slice up to 4 times for parallel decode, once slice per core
|
||||
m_VideoCallbacks.capabilities |= CAPABILITY_SLICES_PER_FRAME(qMin(MAX_SLICES, SDL_GetCPUCount()));
|
||||
int slices = qMin(MAX_SLICES, SDL_GetCPUCount());
|
||||
m_VideoCallbacks.capabilities |= CAPABILITY_SLICES_PER_FRAME(slices);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Encoder configured for %d slices per frame",
|
||||
slices);
|
||||
|
||||
LiInitializeStreamConfiguration(&m_StreamConfig);
|
||||
m_StreamConfig.width = m_Preferences.width;
|
||||
@@ -245,6 +310,11 @@ Session::Session(NvComputer* computer, NvApp& app)
|
||||
m_StreamConfig.fps = m_Preferences.fps;
|
||||
m_StreamConfig.bitrate = m_Preferences.bitrateKbps;
|
||||
m_StreamConfig.hevcBitratePercentageMultiplier = 75;
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Video bitrate: %d kbps",
|
||||
m_StreamConfig.bitrate);
|
||||
|
||||
RAND_bytes(reinterpret_cast<unsigned char*>(m_StreamConfig.remoteInputAesKey),
|
||||
sizeof(m_StreamConfig.remoteInputAesKey));
|
||||
|
||||
@@ -254,7 +324,8 @@ Session::Session(NvComputer* computer, NvApp& app)
|
||||
switch (m_Preferences.audioConfig)
|
||||
{
|
||||
case StreamingPreferences::AC_AUTO:
|
||||
m_StreamConfig.audioConfiguration = sdlDetermineAudioConfiguration();
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Autodetecting audio configuration");
|
||||
m_StreamConfig.audioConfiguration = detectAudioConfiguration();
|
||||
break;
|
||||
case StreamingPreferences::AC_FORCE_STEREO:
|
||||
m_StreamConfig.audioConfiguration = AUDIO_CONFIGURATION_STEREO;
|
||||
@@ -264,6 +335,10 @@ Session::Session(NvComputer* computer, NvApp& app)
|
||||
break;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio configuration: %d",
|
||||
m_StreamConfig.audioConfiguration);
|
||||
|
||||
switch (m_Preferences.videoCodecConfig)
|
||||
{
|
||||
case StreamingPreferences::VCC_AUTO:
|
||||
@@ -274,6 +349,16 @@ Session::Session(NvComputer* computer, NvApp& app)
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height,
|
||||
m_StreamConfig.fps);
|
||||
|
||||
// Oddly enough, the H.264 encoder on my GTX 1080 is actually more
|
||||
// performant than the HEVC encoder (especially because split frame encode
|
||||
// is disabled for HEVC by moonlight-common-c). Use H.264 for 1440p 100 FPS and above.
|
||||
if (m_StreamConfig.width * m_StreamConfig.height * m_StreamConfig.fps >= 2560 * 1440 * 100) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Preferring H.264 for higher performance");
|
||||
m_StreamConfig.supportsHevc = false;
|
||||
}
|
||||
|
||||
m_StreamConfig.enableHdr = false;
|
||||
break;
|
||||
case StreamingPreferences::VCC_FORCE_H264:
|
||||
@@ -290,25 +375,64 @@ Session::Session(NvComputer* computer, NvApp& app)
|
||||
break;
|
||||
}
|
||||
|
||||
if (computer->activeAddress == computer->remoteAddress) {
|
||||
if (m_Computer->activeAddress == m_Computer->remoteAddress) {
|
||||
m_StreamConfig.streamingRemotely = 1;
|
||||
}
|
||||
else {
|
||||
m_StreamConfig.streamingRemotely = 0;
|
||||
}
|
||||
|
||||
if (computer->activeAddress == computer->localAddress) {
|
||||
if (m_Computer->activeAddress == m_Computer->localAddress) {
|
||||
m_StreamConfig.packetSize = 1392;
|
||||
}
|
||||
else {
|
||||
m_StreamConfig.packetSize = 1024;
|
||||
}
|
||||
|
||||
switch (m_Preferences.windowMode)
|
||||
{
|
||||
case StreamingPreferences::WM_FULLSCREEN_DESKTOP:
|
||||
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
break;
|
||||
case StreamingPreferences::WM_FULLSCREEN:
|
||||
default:
|
||||
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Session::emitLaunchWarning(QString text)
|
||||
{
|
||||
// Emit the warning to the UI
|
||||
emit displayLaunchWarning(text);
|
||||
|
||||
// Wait a little bit so the user can actually read what we just said.
|
||||
// This wait is a little longer than the actual toast timeout (3 seconds)
|
||||
// to allow it to transition off the screen before continuing.
|
||||
uint32_t start = SDL_GetTicks();
|
||||
while (!SDL_TICKS_PASSED(SDL_GetTicks(), start + 3500)) {
|
||||
// Pump the UI loop while we wait
|
||||
SDL_Delay(5);
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
}
|
||||
|
||||
bool Session::validateLaunch()
|
||||
{
|
||||
QStringList warningList;
|
||||
|
||||
if (m_Preferences.videoDecoderSelection == StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
||||
emitLaunchWarning("Your settings selection to force software decoding may cause poor streaming performance.");
|
||||
}
|
||||
|
||||
if (m_Preferences.unsupportedFps && m_StreamConfig.fps > 60) {
|
||||
emitLaunchWarning("Using unsupported FPS options may cause stuttering or lag.");
|
||||
|
||||
if (m_Preferences.enableVsync) {
|
||||
emitLaunchWarning("V-sync will be disabled when streaming at a higher frame rate than the display.");
|
||||
}
|
||||
}
|
||||
|
||||
if (m_StreamConfig.supportsHevc) {
|
||||
bool hevcForced = m_Preferences.videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC ||
|
||||
m_Preferences.videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC_HDR;
|
||||
@@ -317,22 +441,26 @@ bool Session::validateLaunch()
|
||||
VIDEO_FORMAT_H265,
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height,
|
||||
m_StreamConfig.fps)) {
|
||||
// NOTE: HEVC currently uses only 1 slice regardless of what
|
||||
// we provide in CAPABILITY_SLICES_PER_FRAME(), so we should
|
||||
// never use it for software decoding (unless common-c starts
|
||||
// respecting it for HEVC).
|
||||
m_StreamConfig.supportsHevc = false;
|
||||
|
||||
m_StreamConfig.fps) &&
|
||||
m_Preferences.videoDecoderSelection == StreamingPreferences::VDS_AUTO) {
|
||||
if (hevcForced) {
|
||||
emit displayLaunchWarning("This PC's GPU doesn't support HEVC decoding.");
|
||||
emitLaunchWarning("Using software decoding due to your selection to force HEVC without GPU support. This may cause poor streaming performance.");
|
||||
}
|
||||
else {
|
||||
emitLaunchWarning("This PC's GPU doesn't support HEVC decoding.");
|
||||
m_StreamConfig.supportsHevc = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (hevcForced) {
|
||||
if (m_Computer->maxLumaPixelsHEVC == 0) {
|
||||
emit displayLaunchWarning("Your host PC GPU doesn't support HEVC. "
|
||||
"A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming.");
|
||||
emitLaunchWarning("Your host PC GPU doesn't support HEVC. "
|
||||
"A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming.");
|
||||
|
||||
// Moonlight-common-c will handle this case already, but we want
|
||||
// to set this explicitly here so we can do our hardware acceleration
|
||||
// check below.
|
||||
m_StreamConfig.supportsHevc = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,19 +471,19 @@ bool Session::validateLaunch()
|
||||
|
||||
// Check that the app supports HDR
|
||||
if (!m_App.hdrSupported) {
|
||||
emit displayLaunchWarning(m_App.name + " doesn't support HDR10.");
|
||||
emitLaunchWarning(m_App.name + " doesn't support HDR10.");
|
||||
}
|
||||
// Check that the server GPU supports HDR
|
||||
else if (!(m_Computer->serverCodecModeSupport & 0x200)) {
|
||||
emit displayLaunchWarning("Your host PC GPU doesn't support HDR streaming. "
|
||||
"A GeForce GTX 1000-series (Pascal) or later GPU is required for HDR streaming.");
|
||||
emitLaunchWarning("Your host PC GPU doesn't support HDR streaming. "
|
||||
"A GeForce GTX 1000-series (Pascal) or later GPU is required for HDR streaming.");
|
||||
}
|
||||
else if (!isHardwareDecodeAvailable(m_Preferences.videoDecoderSelection,
|
||||
VIDEO_FORMAT_H265_MAIN10,
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height,
|
||||
m_StreamConfig.fps)) {
|
||||
emit displayLaunchWarning("This PC's GPU doesn't support HEVC Main10 decoding for HDR streaming.");
|
||||
emitLaunchWarning("This PC's GPU doesn't support HEVC Main10 decoding for HDR streaming.");
|
||||
}
|
||||
else {
|
||||
// TODO: Also validate display capabilites
|
||||
@@ -367,8 +495,8 @@ bool Session::validateLaunch()
|
||||
|
||||
if (m_StreamConfig.width >= 3840) {
|
||||
// Only allow 4K on GFE 3.x+
|
||||
if (m_Computer->gfeVersion.isNull() || m_Computer->gfeVersion.startsWith("2.")) {
|
||||
emit displayLaunchWarning("GeForce Experience 3.0 or higher is required for 4K streaming.");
|
||||
if (m_Computer->gfeVersion.isEmpty() || m_Computer->gfeVersion.startsWith("2.")) {
|
||||
emitLaunchWarning("GeForce Experience 3.0 or higher is required for 4K streaming.");
|
||||
|
||||
m_StreamConfig.width = 1920;
|
||||
m_StreamConfig.height = 1080;
|
||||
@@ -376,15 +504,44 @@ bool Session::validateLaunch()
|
||||
// This list is sorted from least to greatest
|
||||
else if (m_Computer->displayModes.last().width < 3840 ||
|
||||
(m_Computer->displayModes.last().refreshRate < 60 && m_StreamConfig.fps >= 60)) {
|
||||
emit displayLaunchWarning("Your host PC GPU doesn't support 4K streaming. "
|
||||
"A GeForce GTX 900-series (Maxwell) or later GPU is required for 4K streaming.");
|
||||
emitLaunchWarning("Your host PC GPU doesn't support 4K streaming. "
|
||||
"A GeForce GTX 900-series (Maxwell) or later GPU is required for 4K streaming.");
|
||||
|
||||
m_StreamConfig.width = 1920;
|
||||
m_StreamConfig.height = 1080;
|
||||
}
|
||||
}
|
||||
|
||||
// Always allow the launch to proceed for now
|
||||
// Test that audio hardware is functional
|
||||
m_AudioDisabled = !testAudio(m_StreamConfig.audioConfiguration);
|
||||
if (m_AudioDisabled) {
|
||||
emitLaunchWarning("Failed to open audio device. Audio will be unavailable during this session.");
|
||||
}
|
||||
|
||||
if (m_Preferences.videoDecoderSelection == StreamingPreferences::VDS_FORCE_HARDWARE &&
|
||||
!isHardwareDecodeAvailable(m_Preferences.videoDecoderSelection,
|
||||
m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264,
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height,
|
||||
m_StreamConfig.fps)) {
|
||||
if (m_Preferences.videoCodecConfig == StreamingPreferences::VCC_AUTO) {
|
||||
emit displayLaunchError("Your selection to force hardware decoding cannot be satisfied due to missing hardware decoding support on this PC's GPU.");
|
||||
}
|
||||
else {
|
||||
emit displayLaunchError("Your codec selection and force hardware decoding setting are not compatible. This PC's GPU lacks support for decoding your chosen codec.");
|
||||
}
|
||||
|
||||
// Fail the launch, because we won't manage to get a decoder for the actual stream
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the capability flags from the chosen decoder/renderer
|
||||
m_VideoCallbacks.capabilities |= getDecoderCapabilities(m_Preferences.videoDecoderSelection,
|
||||
m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264,
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height,
|
||||
m_StreamConfig.fps);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -402,113 +559,176 @@ class DeferredSessionCleanupTask : public QRunnable
|
||||
}
|
||||
};
|
||||
|
||||
void Session::getWindowDimensions(bool fullScreen,
|
||||
int& x, int& y,
|
||||
void Session::getWindowDimensions(int& x, int& y,
|
||||
int& width, int& height)
|
||||
{
|
||||
int displayIndex = 0;
|
||||
|
||||
if (m_Window != nullptr) {
|
||||
displayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
||||
if (displayIndex < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetWindowDisplayIndex() failed: %s",
|
||||
SDL_GetError());
|
||||
|
||||
// Assume display 0
|
||||
displayIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (fullScreen) {
|
||||
SDL_DisplayMode currentMode;
|
||||
|
||||
if (SDL_GetCurrentDisplayMode(displayIndex, ¤tMode) == 0) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Using current display mode: %dx%dx%d",
|
||||
currentMode.w, currentMode.h, currentMode.refresh_rate);
|
||||
width = currentMode.w;
|
||||
height = currentMode.h;
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to get current display mode: %s",
|
||||
SDL_GetError());
|
||||
width = m_StreamConfig.width;
|
||||
height = m_StreamConfig.height;
|
||||
}
|
||||
|
||||
// Full-screen modes always start at the origin
|
||||
x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex);
|
||||
SDL_assert(displayIndex >= 0);
|
||||
}
|
||||
// Create our window on the same display that Qt's UI
|
||||
// was being displayed on.
|
||||
else {
|
||||
SDL_Rect usableBounds;
|
||||
if (SDL_GetDisplayUsableBounds(displayIndex, &usableBounds) == 0) {
|
||||
width = usableBounds.w;
|
||||
height = usableBounds.h;
|
||||
x = usableBounds.x;
|
||||
y = usableBounds.y;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Qt UI screen is at (%d,%d)",
|
||||
m_DisplayOriginX, m_DisplayOriginY);
|
||||
for (int i = 0; i < SDL_GetNumVideoDisplays(); i++) {
|
||||
SDL_Rect displayBounds;
|
||||
|
||||
if (m_Window != nullptr) {
|
||||
int top, left, bottom, right;
|
||||
|
||||
if (SDL_GetWindowBordersSize(m_Window, &top, &left, &bottom, &right) < 0) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to get window border size");
|
||||
return;
|
||||
if (SDL_GetDisplayBounds(i, &displayBounds) == 0) {
|
||||
if (displayBounds.x == m_DisplayOriginX &&
|
||||
displayBounds.y == m_DisplayOriginY) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL found matching display %d",
|
||||
i);
|
||||
displayIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetDisplayBounds(%d) failed: %s",
|
||||
i, SDL_GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
x += left;
|
||||
y += top;
|
||||
SDL_Rect usableBounds;
|
||||
if (SDL_GetDisplayUsableBounds(displayIndex, &usableBounds) == 0) {
|
||||
width = usableBounds.w;
|
||||
height = usableBounds.h;
|
||||
|
||||
if (m_Window != nullptr) {
|
||||
int top, left, bottom, right;
|
||||
|
||||
if (SDL_GetWindowBordersSize(m_Window, &top, &left, &bottom, &right) == 0) {
|
||||
width -= left + right;
|
||||
height -= top + bottom;
|
||||
}
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetDisplayUsableBounds() failed: %s",
|
||||
SDL_GetError());
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to get window border size: %s",
|
||||
SDL_GetError());
|
||||
}
|
||||
|
||||
width = m_StreamConfig.width;
|
||||
height = m_StreamConfig.height;
|
||||
x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex);
|
||||
// If the stream window can fit within the usable drawing area with 1:1
|
||||
// scaling, do that rather than filling the screen.
|
||||
if (m_StreamConfig.width < width && m_StreamConfig.height < height) {
|
||||
width = m_StreamConfig.width;
|
||||
height = m_StreamConfig.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetDisplayUsableBounds() failed: %s",
|
||||
SDL_GetError());
|
||||
|
||||
width = m_StreamConfig.width;
|
||||
height = m_StreamConfig.height;
|
||||
}
|
||||
|
||||
x = y = SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex);
|
||||
}
|
||||
|
||||
void Session::updateOptimalWindowDisplayMode()
|
||||
{
|
||||
SDL_DisplayMode desktopMode, bestMode, mode;
|
||||
int displayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
||||
|
||||
// Try the current display mode first. On macOS, this will be the normal
|
||||
// scaled desktop resolution setting.
|
||||
if (SDL_GetDesktopDisplayMode(displayIndex, &desktopMode) == 0) {
|
||||
// If this doesn't fit the selected resolution, use the native
|
||||
// resolution of the panel (unscaled).
|
||||
if (desktopMode.w < m_ActiveVideoWidth || desktopMode.h < m_ActiveVideoHeight) {
|
||||
if (!StreamUtils::getRealDesktopMode(displayIndex, &desktopMode)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetDesktopDisplayMode() failed: %s",
|
||||
SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
// Start with the native desktop resolution and try to find
|
||||
// the highest refresh rate that our stream FPS evenly divides.
|
||||
bestMode = desktopMode;
|
||||
bestMode.refresh_rate = 0;
|
||||
for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) {
|
||||
if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0) {
|
||||
if (mode.w == desktopMode.w && mode.h == desktopMode.h &&
|
||||
mode.refresh_rate % m_StreamConfig.fps == 0) {
|
||||
if (mode.refresh_rate > bestMode.refresh_rate) {
|
||||
bestMode = mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMode.refresh_rate == 0) {
|
||||
// We may find no match if the user has moved a 120 FPS
|
||||
// stream onto a 60 Hz monitor (since no refresh rate can
|
||||
// divide our FPS setting). We'll stick to the default in
|
||||
// this case.
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"No matching refresh rate found; using desktop mode");
|
||||
bestMode = desktopMode;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Chosen best display mode: %dx%dx%d",
|
||||
bestMode.w, bestMode.h, bestMode.refresh_rate);
|
||||
|
||||
SDL_SetWindowDisplayMode(m_Window, &bestMode);
|
||||
}
|
||||
|
||||
void Session::toggleFullscreen()
|
||||
{
|
||||
bool fullScreen = !(SDL_GetWindowFlags(m_Window) & SDL_OS_FULLSCREEN_FLAG);
|
||||
|
||||
int x, y, width, height;
|
||||
|
||||
// If we're leaving full screen, toggle out before setting our size and position
|
||||
// that way we have accurate display size metrics (not the size our stream changed it to).
|
||||
if (!fullScreen) {
|
||||
SDL_SetWindowFullscreen(m_Window, 0);
|
||||
}
|
||||
|
||||
getWindowDimensions(fullScreen, x, y, width, height);
|
||||
|
||||
SDL_SetWindowPosition(m_Window, x, y);
|
||||
SDL_SetWindowSize(m_Window, width, height);
|
||||
bool fullScreen = !(SDL_GetWindowFlags(m_Window) & m_FullScreenFlag);
|
||||
|
||||
if (fullScreen) {
|
||||
SDL_SetWindowFullscreen(m_Window, SDL_OS_FULLSCREEN_FLAG);
|
||||
SDL_SetWindowResizable(m_Window, SDL_FALSE);
|
||||
SDL_SetWindowFullscreen(m_Window, m_FullScreenFlag);
|
||||
}
|
||||
else {
|
||||
SDL_SetWindowFullscreen(m_Window, 0);
|
||||
SDL_SetWindowResizable(m_Window, SDL_TRUE);
|
||||
|
||||
// Reposition the window when the resize is complete
|
||||
m_PendingWindowedTransition = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Session::exec()
|
||||
void Session::exec(int displayOriginX, int displayOriginY)
|
||||
{
|
||||
m_DisplayOriginX = displayOriginX;
|
||||
m_DisplayOriginY = displayOriginY;
|
||||
|
||||
// Complete initialization in this deferred context to avoid
|
||||
// calling expensive functions in the constructor (during the
|
||||
// process of loading the StreamSegue).
|
||||
initialize();
|
||||
|
||||
// Check for validation errors/warnings and emit
|
||||
// signals for them, if appropriate
|
||||
if (!validateLaunch()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Manually pump the UI thread for the view
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
// Wait 1.5 seconds before connecting to let the user
|
||||
// have time to read any messages present on the segue
|
||||
uint32_t start = SDL_GetTicks();
|
||||
while (!SDL_TICKS_PASSED(SDL_GetTicks(), start + 1500)) {
|
||||
// Pump the UI loop while we wait
|
||||
SDL_Delay(5);
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
|
||||
// Wait for any old session to finish cleanup
|
||||
s_ActiveSessionSemaphore.acquire();
|
||||
@@ -518,13 +738,25 @@ void Session::exec()
|
||||
|
||||
// Initialize the gamepad code with our preferences
|
||||
StreamingPreferences prefs;
|
||||
SdlInputHandler inputHandler(prefs.multiController);
|
||||
SdlInputHandler inputHandler(prefs, m_Computer);
|
||||
|
||||
// The UI should have ensured the old game was already quit
|
||||
// if we decide to stream a different game.
|
||||
Q_ASSERT(m_Computer->currentGameId == 0 ||
|
||||
m_Computer->currentGameId == m_App.id);
|
||||
|
||||
bool enableGameOptimizations = false;
|
||||
for (const NvDisplayMode &mode : m_Computer->displayModes) {
|
||||
if (mode.width == m_StreamConfig.width &&
|
||||
mode.height == m_StreamConfig.height) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Found host supported resolution: %dx%d",
|
||||
mode.width, mode.height);
|
||||
enableGameOptimizations = prefs.gameOptimizations;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
NvHTTP http(m_Computer->activeAddress);
|
||||
if (m_Computer->currentGameId != 0) {
|
||||
@@ -532,7 +764,7 @@ void Session::exec()
|
||||
}
|
||||
else {
|
||||
http.launchApp(m_App.id, &m_StreamConfig,
|
||||
prefs.gameOptimizations,
|
||||
enableGameOptimizations,
|
||||
prefs.playAudioOnHost,
|
||||
inputHandler.getAttachedGamepadMask());
|
||||
}
|
||||
@@ -561,21 +793,22 @@ void Session::exec()
|
||||
|
||||
// Older GFE versions didn't have this field
|
||||
QByteArray siGfeVersion;
|
||||
if (!m_Computer->gfeVersion.isNull()) {
|
||||
if (!m_Computer->gfeVersion.isEmpty()) {
|
||||
siGfeVersion = m_Computer->gfeVersion.toLatin1();
|
||||
}
|
||||
if (!siGfeVersion.isNull()) {
|
||||
if (!siGfeVersion.isEmpty()) {
|
||||
hostInfo.serverInfoGfeVersion = siGfeVersion.data();
|
||||
}
|
||||
|
||||
int err = LiStartConnection(&hostInfo, &m_StreamConfig, &k_ConnCallbacks,
|
||||
&m_VideoCallbacks, &k_AudioCallbacks,
|
||||
&m_VideoCallbacks,
|
||||
m_AudioDisabled ? nullptr : &k_AudioCallbacks,
|
||||
NULL, 0, NULL, 0);
|
||||
if (err != 0) {
|
||||
// We already displayed an error dialog in the stage failure
|
||||
// listener.
|
||||
s_ActiveSessionSemaphore.release();
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
s_ActiveSessionSemaphore.release();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -584,33 +817,44 @@ void Session::exec()
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
int x, y, width, height;
|
||||
getWindowDimensions(m_Preferences.fullScreen,
|
||||
x, y, width, height);
|
||||
getWindowDimensions(x, y, width, height);
|
||||
|
||||
m_Window = SDL_CreateWindow("Moonlight",
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
m_Preferences.fullScreen ?
|
||||
SDL_OS_FULLSCREEN_FLAG : 0);
|
||||
SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
if (!m_Window) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_CreateWindow() failed: %s",
|
||||
SDL_GetError());
|
||||
s_ActiveSessionSemaphore.release();
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
s_ActiveSessionSemaphore.release();
|
||||
return;
|
||||
}
|
||||
|
||||
// For non-full screen windows, call getWindowDimensions()
|
||||
// again after creating a window to allow it to account
|
||||
// for window chrome size.
|
||||
if (!m_Preferences.fullScreen) {
|
||||
getWindowDimensions(false, x, y, width, height);
|
||||
if (m_Preferences.windowMode == StreamingPreferences::WM_WINDOWED) {
|
||||
getWindowDimensions(x, y, width, height);
|
||||
|
||||
SDL_SetWindowPosition(m_Window, x, y);
|
||||
// We must set the size before the position because centering
|
||||
// won't work unless it knows the final size of the window.
|
||||
SDL_SetWindowSize(m_Window, width, height);
|
||||
SDL_SetWindowPosition(m_Window, x, y);
|
||||
|
||||
// Passing SDL_WINDOW_RESIZABLE to set this during window
|
||||
// creation causes our window to be full screen for some reason
|
||||
SDL_SetWindowResizable(m_Window, SDL_TRUE);
|
||||
}
|
||||
else {
|
||||
// Update the window display mode based on our current monitor
|
||||
updateOptimalWindowDisplayMode();
|
||||
|
||||
// Enter full screen
|
||||
SDL_SetWindowFullscreen(m_Window, m_FullScreenFlag);
|
||||
}
|
||||
|
||||
QSvgRenderer svgIconRenderer(QString(":/res/moonlight.svg"));
|
||||
@@ -636,12 +880,31 @@ void Session::exec()
|
||||
// Capture the mouse by default on release builds only.
|
||||
// This prevents the mouse from becoming trapped inside
|
||||
// Moonlight when it's halted at a debug break.
|
||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||
if (m_Preferences.windowMode != StreamingPreferences::WM_WINDOWED) {
|
||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Stop text input. SDL enables it by default
|
||||
// when we initialize the video subsystem, but this
|
||||
// causes an IME popup when certain keys are held down
|
||||
// on macOS.
|
||||
SDL_StopTextInput();
|
||||
|
||||
// Disable the screen saver
|
||||
SDL_DisableScreenSaver();
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// HACK: SDL doesn't call this, so we must do so to disable the
|
||||
// screensaver when we're in windowed mode. DirectX will disable
|
||||
// it for us when we're in full-screen exclusive mode.
|
||||
SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED);
|
||||
#endif
|
||||
|
||||
// Set timer resolution to 1 ms on Windows for greater
|
||||
// sleep precision and more accurate callback timing.
|
||||
SDL_SetHint(SDL_HINT_TIMER_RESOLUTION, "1");
|
||||
|
||||
// Raise the priority of the main thread, since it handles
|
||||
// time-sensitive video rendering
|
||||
if (SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH) < 0) {
|
||||
@@ -650,10 +913,20 @@ void Session::exec()
|
||||
SDL_GetError());
|
||||
}
|
||||
|
||||
int currentDisplayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
||||
|
||||
// Hijack this thread to be the SDL main thread. We have to do this
|
||||
// because we want to suspend all Qt processing until the stream is over.
|
||||
SDL_Event event;
|
||||
while (SDL_WaitEvent(&event)) {
|
||||
for (;;) {
|
||||
// We explicitly use SDL_PollEvent() and SDL_Delay() because
|
||||
// SDL_WaitEvent() has an internal SDL_Delay(10) inside which
|
||||
// blocks this thread too long for high polling rate mice and high
|
||||
// refresh rate displays.
|
||||
if (!SDL_PollEvent(&event)) {
|
||||
SDL_Delay(1);
|
||||
continue;
|
||||
}
|
||||
switch (event.type) {
|
||||
case SDL_QUIT:
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -680,31 +953,116 @@ void Session::exec()
|
||||
}
|
||||
|
||||
case SDL_WINDOWEVENT:
|
||||
// Capture mouse cursor when user actives the window by clicking on
|
||||
// window's client area (borders and title bar excluded).
|
||||
// Without this you would have to click the window twice (once to
|
||||
// activate it, second time to enable capture). With this you need to
|
||||
// click it only once.
|
||||
// On Linux, the button press event is delivered after the focus gain
|
||||
// so this is not neccessary (and leads to a click sent to the host
|
||||
// when focusing the window by clicking).
|
||||
// By excluding window's borders and title bar out, lets user still
|
||||
// interact with them without mouse capture kicking in.
|
||||
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN)
|
||||
if (event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) {
|
||||
int mouseX, mouseY;
|
||||
Uint32 mouseState = SDL_GetGlobalMouseState(&mouseX, &mouseY);
|
||||
if (mouseState & SDL_BUTTON(SDL_BUTTON_LEFT)) {
|
||||
int x, y, width, height;
|
||||
SDL_GetWindowPosition(m_Window, &x, &y);
|
||||
SDL_GetWindowSize(m_Window, &width, &height);
|
||||
if (mouseX > x && mouseX < x+width && mouseY > y && mouseY < y+height) {
|
||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Release mouse cursor when another window is activated (e.g. by using ALT+TAB).
|
||||
// This lets user to interact with our window's title bar and with the buttons in it.
|
||||
// Doing this while the window is full-screen breaks the transition out of FS
|
||||
// (desktop and exclusive), so we must check for that before releasing mouse capture.
|
||||
if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST &&
|
||||
!(SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN)) {
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
}
|
||||
|
||||
// We want to recreate the decoder for resizes (full-screen toggles) and the initial shown event.
|
||||
// We use SDL_WINDOWEVENT_SIZE_CHANGED rather than SDL_WINDOWEVENT_RESIZED because the latter doesn't
|
||||
// seem to fire when switching from windowed to full-screen on X11.
|
||||
if (event.window.event != SDL_WINDOWEVENT_SIZE_CHANGED && event.window.event != SDL_WINDOWEVENT_SHOWN) {
|
||||
break;
|
||||
// Check that the window display hasn't changed. If it has, we want
|
||||
// to recreate the decoder to allow it to adapt to the new display.
|
||||
// This will allow Pacer to pull the new display refresh rate.
|
||||
if (SDL_GetWindowDisplayIndex(m_Window) == currentDisplayIndex) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Complete any repositioning that was deferred until
|
||||
// the resize from full-screen to windowed had completed.
|
||||
// If we try to do this immediately, the resize won't take effect
|
||||
// properly on Windows.
|
||||
if (m_PendingWindowedTransition) {
|
||||
m_PendingWindowedTransition = false;
|
||||
|
||||
int x, y, width, height;
|
||||
getWindowDimensions(x, y, width, height);
|
||||
|
||||
SDL_SetWindowSize(m_Window, width, height);
|
||||
SDL_SetWindowPosition(m_Window, x, y);
|
||||
}
|
||||
|
||||
// Fall through
|
||||
case SDL_RENDER_DEVICE_RESET:
|
||||
case SDL_RENDER_TARGETS_RESET:
|
||||
|
||||
SDL_AtomicLock(&m_DecoderLock);
|
||||
|
||||
// Destroy the old decoder
|
||||
delete m_VideoDecoder;
|
||||
|
||||
// Chose a new decoder (hopefully the same one, but possibly
|
||||
// not if a GPU was removed or something).
|
||||
if (!chooseDecoder(m_Preferences.videoDecoderSelection,
|
||||
m_Window, m_ActiveVideoFormat, m_ActiveVideoWidth,
|
||||
m_ActiveVideoHeight, m_ActiveVideoFrameRate,
|
||||
s_ActiveSession->m_VideoDecoder)) {
|
||||
SDL_AtomicUnlock(&m_DecoderLock);
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to recreate decoder after reset");
|
||||
goto DispatchDeferredCleanup;
|
||||
// Flush any other pending window events that could
|
||||
// send us back here immediately
|
||||
SDL_PumpEvents();
|
||||
SDL_FlushEvent(SDL_WINDOWEVENT);
|
||||
|
||||
// Update the window display mode based on our current monitor
|
||||
currentDisplayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
||||
updateOptimalWindowDisplayMode();
|
||||
|
||||
// Now that the old decoder is dead, flush any events it may
|
||||
// have queued to reset itself (if this reset was the result
|
||||
// of state loss).
|
||||
SDL_PumpEvents();
|
||||
SDL_FlushEvent(SDL_RENDER_DEVICE_RESET);
|
||||
SDL_FlushEvent(SDL_RENDER_TARGETS_RESET);
|
||||
|
||||
{
|
||||
// If the stream exceeds the display refresh rate (plus some slack),
|
||||
// forcefully disable V-sync to allow the stream to render faster
|
||||
// than the display.
|
||||
int displayHz = StreamUtils::getDisplayRefreshRate(m_Window);
|
||||
bool enableVsync = m_Preferences.enableVsync;
|
||||
if (displayHz + 5 < m_StreamConfig.fps) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Disabling V-sync because refresh rate limit exceeded");
|
||||
enableVsync = false;
|
||||
}
|
||||
|
||||
// Choose a new decoder (hopefully the same one, but possibly
|
||||
// not if a GPU was removed or something).
|
||||
if (!chooseDecoder(m_Preferences.videoDecoderSelection,
|
||||
m_Window, m_ActiveVideoFormat, m_ActiveVideoWidth,
|
||||
m_ActiveVideoHeight, m_ActiveVideoFrameRate,
|
||||
enableVsync,
|
||||
s_ActiveSession->m_VideoDecoder)) {
|
||||
SDL_AtomicUnlock(&m_DecoderLock);
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to recreate decoder after reset");
|
||||
emit displayLaunchError("Unable to initialize video decoder. Please check your streaming settings and try again.");
|
||||
goto DispatchDeferredCleanup;
|
||||
}
|
||||
}
|
||||
|
||||
// Request an IDR frame to complete the reset
|
||||
@@ -746,10 +1104,16 @@ void Session::exec()
|
||||
SDL_GetError());
|
||||
|
||||
DispatchDeferredCleanup:
|
||||
#ifdef Q_OS_WIN32
|
||||
// HACK: See comment above
|
||||
SetThreadExecutionState(ES_CONTINUOUS);
|
||||
#endif
|
||||
|
||||
// Uncapture the mouse and hide the window immediately,
|
||||
// so we can return to the Qt GUI ASAP.
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
SDL_EnableScreenSaver();
|
||||
SDL_SetHint(SDL_HINT_TIMER_RESOLUTION, "0");
|
||||
|
||||
// Destroy the decoder, since this must be done on the main thread
|
||||
SDL_AtomicLock(&m_DecoderLock);
|
||||
@@ -757,6 +1121,9 @@ DispatchDeferredCleanup:
|
||||
m_VideoDecoder = nullptr;
|
||||
SDL_AtomicUnlock(&m_DecoderLock);
|
||||
|
||||
// Destroy the audio renderer which must also be done on the main thread
|
||||
cleanupAudioRendererOnMainThread();
|
||||
|
||||
SDL_DestroyWindow(m_Window);
|
||||
if (iconSurface != nullptr) {
|
||||
SDL_FreeSurface(iconSurface);
|
||||
|
||||
+42
-30
@@ -8,6 +8,7 @@
|
||||
#include "settings/streamingpreferences.h"
|
||||
#include "input.hpp"
|
||||
#include "video/decoder.h"
|
||||
#include "audio/renderers/renderer.h"
|
||||
|
||||
class Session : public QObject
|
||||
{
|
||||
@@ -19,12 +20,16 @@ class Session : public QObject
|
||||
public:
|
||||
explicit Session(NvComputer* computer, NvApp& app);
|
||||
|
||||
Q_INVOKABLE void exec();
|
||||
Q_INVOKABLE void exec(int displayOriginX, int displayOriginY);
|
||||
|
||||
static
|
||||
bool isHardwareDecodeAvailable(StreamingPreferences::VideoDecoderSelection vds,
|
||||
int videoFormat, int width, int height, int frameRate);
|
||||
|
||||
static
|
||||
int getDecoderCapabilities(StreamingPreferences::VideoDecoderSelection vds,
|
||||
int videoFormat, int width, int height, int frameRate);
|
||||
|
||||
signals:
|
||||
void stageStarting(QString stage);
|
||||
|
||||
@@ -37,22 +42,33 @@ signals:
|
||||
void displayLaunchWarning(QString text);
|
||||
|
||||
private:
|
||||
void initialize();
|
||||
|
||||
bool validateLaunch();
|
||||
|
||||
void emitLaunchWarning(QString text);
|
||||
|
||||
int getDecoderCapabilities();
|
||||
|
||||
int sdlDetermineAudioConfiguration();
|
||||
IAudioRenderer* createAudioRenderer();
|
||||
|
||||
void getWindowDimensions(bool fullScreen,
|
||||
int& x, int& y,
|
||||
int detectAudioConfiguration();
|
||||
|
||||
void cleanupAudioRendererOnMainThread();
|
||||
|
||||
bool testAudio(int audioConfiguration);
|
||||
|
||||
void getWindowDimensions(int& x, int& y,
|
||||
int& width, int& height);
|
||||
|
||||
void toggleFullscreen();
|
||||
|
||||
void updateOptimalWindowDisplayMode();
|
||||
|
||||
static
|
||||
bool chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window* window, int videoFormat, int width, int height,
|
||||
int frameRate, IVideoDecoder*& chosenDecoder);
|
||||
int frameRate, bool enableVsync, IVideoDecoder*& chosenDecoder);
|
||||
|
||||
static
|
||||
void clStageStarting(int stage);
|
||||
@@ -66,6 +82,17 @@ private:
|
||||
static
|
||||
void clLogMessage(const char* format, ...);
|
||||
|
||||
static
|
||||
int arInit(int audioConfiguration,
|
||||
const POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
||||
void* arContext, int arFlags);
|
||||
|
||||
static
|
||||
void arCleanup();
|
||||
|
||||
static
|
||||
void arDecodeAndPlaySample(char* sampleData, int sampleLength);
|
||||
|
||||
static
|
||||
int drSetup(int videoFormat, int width, int height, int frameRate, void*, int);
|
||||
|
||||
@@ -75,23 +102,6 @@ private:
|
||||
static
|
||||
int drSubmitDecodeUnit(PDECODE_UNIT du);
|
||||
|
||||
static
|
||||
int sdlAudioInit(int audioConfiguration,
|
||||
POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
||||
void* arContext, int arFlags);
|
||||
|
||||
static
|
||||
void sdlAudioStart();
|
||||
|
||||
static
|
||||
void sdlAudioStop();
|
||||
|
||||
static
|
||||
void sdlAudioCleanup();
|
||||
|
||||
static
|
||||
void sdlAudioDecodeAndPlaySample(char* sampleData, int sampleLength);
|
||||
|
||||
StreamingPreferences m_Preferences;
|
||||
STREAM_CONFIGURATION m_StreamConfig;
|
||||
DECODER_RENDERER_CALLBACKS m_VideoCallbacks;
|
||||
@@ -101,20 +111,22 @@ private:
|
||||
IVideoDecoder* m_VideoDecoder;
|
||||
SDL_SpinLock m_DecoderLock;
|
||||
bool m_NeedsIdr;
|
||||
bool m_AudioDisabled;
|
||||
Uint32 m_FullScreenFlag;
|
||||
int m_DisplayOriginX;
|
||||
int m_DisplayOriginY;
|
||||
bool m_PendingWindowedTransition;
|
||||
|
||||
int m_ActiveVideoFormat;
|
||||
int m_ActiveVideoWidth;
|
||||
int m_ActiveVideoHeight;
|
||||
int m_ActiveVideoFrameRate;
|
||||
|
||||
static SDL_AudioDeviceID s_AudioDevice;
|
||||
static OpusMSDecoder* s_OpusDecoder;
|
||||
static short s_OpusDecodeBuffer[];
|
||||
static int s_ChannelCount;
|
||||
static int s_PendingDrops;
|
||||
static int s_PendingHardDrops;
|
||||
static unsigned int s_SampleIndex;
|
||||
static Uint32 s_BaselinePendingData;
|
||||
OpusMSDecoder* m_OpusDecoder;
|
||||
short m_OpusDecodeBuffer[MAX_CHANNELS * SAMPLES_PER_FRAME];
|
||||
IAudioRenderer* m_AudioRenderer;
|
||||
OPUS_MULTISTREAM_CONFIGURATION m_AudioConfig;
|
||||
SDL_SpinLock m_AudioRendererLock;
|
||||
|
||||
static AUDIO_RENDERER_CALLBACKS k_AudioCallbacks;
|
||||
static CONNECTION_LISTENER_CALLBACKS k_ConnCallbacks;
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
#include "streamutils.h"
|
||||
|
||||
#include <Qt>
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
#endif
|
||||
|
||||
void StreamUtils::scaleSourceToDestinationSurface(SDL_Rect* src, SDL_Rect* dst)
|
||||
{
|
||||
int dstH = dst->w * src->h / src->w;
|
||||
int dstW = dst->h * src->w / src->h;
|
||||
|
||||
if (dstH > dst->h) {
|
||||
dst->y = 0;
|
||||
dst->x = (dst->w - dstW) / 2;
|
||||
dst->w = dstW;
|
||||
SDL_assert(dst->w * src->h / src->w <= dst->h);
|
||||
}
|
||||
else {
|
||||
dst->x = 0;
|
||||
dst->y = (dst->h - dstH) / 2;
|
||||
dst->h = dstH;
|
||||
SDL_assert(dst->h * src->w / src->h <= dst->w);
|
||||
}
|
||||
}
|
||||
|
||||
int StreamUtils::getDisplayRefreshRate(SDL_Window* window)
|
||||
{
|
||||
int displayIndex = SDL_GetWindowDisplayIndex(window);
|
||||
if (displayIndex < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to get current display: %s",
|
||||
SDL_GetError());
|
||||
|
||||
// Assume display 0 if it fails
|
||||
displayIndex = 0;
|
||||
}
|
||||
|
||||
SDL_DisplayMode mode;
|
||||
if ((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
|
||||
// Use the window display mode for full-screen exclusive mode
|
||||
if (SDL_GetWindowDisplayMode(window, &mode) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetWindowDisplayMode() failed: %s",
|
||||
SDL_GetError());
|
||||
|
||||
// Assume 60 Hz
|
||||
return 60;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Use the current display mode for windowed and borderless
|
||||
if (SDL_GetCurrentDisplayMode(displayIndex, &mode) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetCurrentDisplayMode() failed: %s",
|
||||
SDL_GetError());
|
||||
|
||||
// Assume 60 Hz
|
||||
return 60;
|
||||
}
|
||||
}
|
||||
|
||||
// May be zero if undefined
|
||||
if (mode.refresh_rate == 0) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Refresh rate unknown; assuming 60 Hz");
|
||||
mode.refresh_rate = 60;
|
||||
}
|
||||
|
||||
return mode.refresh_rate;
|
||||
}
|
||||
|
||||
bool StreamUtils::getRealDesktopMode(int displayIndex, SDL_DisplayMode* mode)
|
||||
{
|
||||
#ifdef Q_OS_DARWIN
|
||||
#define MAX_DISPLAYS 16
|
||||
CGDirectDisplayID displayIds[MAX_DISPLAYS];
|
||||
uint32_t displayCount = 0;
|
||||
CGGetActiveDisplayList(MAX_DISPLAYS, displayIds, &displayCount);
|
||||
if (displayIndex >= displayCount) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Too many displays: %d vs %d",
|
||||
displayIndex, displayCount);
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_zerop(mode);
|
||||
|
||||
// Retina displays have non-native resolutions both below and above (!) their
|
||||
// native resolution, so it's impossible for us to figure out what's actually
|
||||
// native on macOS using the SDL API alone. We'll talk to CoreGraphics to
|
||||
// find the correct resolution and match it in our SDL list.
|
||||
CFArrayRef modeList = CGDisplayCopyAllDisplayModes(displayIds[displayIndex], nullptr);
|
||||
CFIndex count = CFArrayGetCount(modeList);
|
||||
for (CFIndex i = 0; i < count; i++) {
|
||||
auto cgMode = (CGDisplayModeRef)(CFArrayGetValueAtIndex(modeList, i));
|
||||
if ((CGDisplayModeGetIOFlags(cgMode) & kDisplayModeNativeFlag) != 0) {
|
||||
mode->w = static_cast<int>(CGDisplayModeGetWidth(cgMode));
|
||||
mode->h = static_cast<int>(CGDisplayModeGetHeight(cgMode));
|
||||
break;
|
||||
}
|
||||
}
|
||||
CFRelease(modeList);
|
||||
|
||||
// Now find the SDL mode that matches the CG native mode
|
||||
for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) {
|
||||
SDL_DisplayMode thisMode;
|
||||
if (SDL_GetDisplayMode(displayIndex, i, &thisMode) == 0) {
|
||||
if (thisMode.w == mode->w && thisMode.h == mode->h &&
|
||||
thisMode.refresh_rate >= mode->refresh_rate) {
|
||||
*mode = thisMode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (SDL_GetDesktopDisplayMode(displayIndex, mode) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetDesktopDisplayMode() failed: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
class StreamUtils
|
||||
{
|
||||
public:
|
||||
static
|
||||
void scaleSourceToDestinationSurface(SDL_Rect* src, SDL_Rect* dst);
|
||||
|
||||
static
|
||||
bool getRealDesktopMode(int displayIndex, SDL_DisplayMode* mode);
|
||||
|
||||
static
|
||||
int getDisplayRefreshRate(SDL_Window* window);
|
||||
};
|
||||
@@ -16,8 +16,10 @@ public:
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height,
|
||||
int frameRate) = 0;
|
||||
int frameRate,
|
||||
bool enableVsync) = 0;
|
||||
virtual bool isHardwareAccelerated() = 0;
|
||||
virtual int getDecoderCapabilities() = 0;
|
||||
virtual int submitDecodeUnit(PDECODE_UNIT du) = 0;
|
||||
virtual void renderFrame(SDL_UserEvent* event) = 0;
|
||||
virtual void dropFrame(SDL_UserEvent* event) = 0;
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
#include <initguid.h>
|
||||
#include "dxva2.h"
|
||||
#include "../ffmpeg.h"
|
||||
#include <streaming/streamutils.h>
|
||||
|
||||
#include <SDL_syswm.h>
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <VersionHelpers.h>
|
||||
#include <dwmapi.h>
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
@@ -8,7 +17,6 @@ DEFINE_GUID(DXVADDI_Intel_ModeH264_E, 0x604F8E68,0x4951,0x4C54,0x88,0xFE,0xAB,0x
|
||||
#define SAFE_COM_RELEASE(x) if (x) { (x)->Release(); }
|
||||
|
||||
DXVA2Renderer::DXVA2Renderer() :
|
||||
m_SdlRenderer(nullptr),
|
||||
m_DecService(nullptr),
|
||||
m_Decoder(nullptr),
|
||||
m_SurfacesUsed(0),
|
||||
@@ -21,10 +29,15 @@ DXVA2Renderer::DXVA2Renderer() :
|
||||
{
|
||||
RtlZeroMemory(m_DecSurfaces, sizeof(m_DecSurfaces));
|
||||
RtlZeroMemory(&m_DXVAContext, sizeof(m_DXVAContext));
|
||||
|
||||
// Use MMCSS scheduling for lower scheduling latency while we're streaming
|
||||
DwmEnableMMCSS(TRUE);
|
||||
}
|
||||
|
||||
DXVA2Renderer::~DXVA2Renderer()
|
||||
{
|
||||
DwmEnableMMCSS(FALSE);
|
||||
|
||||
SAFE_COM_RELEASE(m_DecService);
|
||||
SAFE_COM_RELEASE(m_Decoder);
|
||||
SAFE_COM_RELEASE(m_Device);
|
||||
@@ -39,10 +52,6 @@ DXVA2Renderer::~DXVA2Renderer()
|
||||
if (m_Pool != nullptr) {
|
||||
av_buffer_pool_uninit(&m_Pool);
|
||||
}
|
||||
|
||||
if (m_SdlRenderer != nullptr) {
|
||||
SDL_DestroyRenderer(m_SdlRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
void DXVA2Renderer::ffPoolDummyDelete(void*, uint8_t*)
|
||||
@@ -75,11 +84,8 @@ bool DXVA2Renderer::prepareDecoderContext(AVCodecContext* context)
|
||||
|
||||
context->hwaccel_context = &m_DXVAContext;
|
||||
|
||||
context->get_format = ffGetFormat;
|
||||
context->get_buffer2 = ffGetBuffer2;
|
||||
|
||||
context->opaque = this;
|
||||
|
||||
m_Pool = av_buffer_pool_init2(ARRAYSIZE(m_DecSurfaces), this, ffPoolAlloc, nullptr);
|
||||
if (!m_Pool) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -87,12 +93,15 @@ bool DXVA2Renderer::prepareDecoderContext(AVCodecContext* context)
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Using DXVA2 accelerated renderer");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int DXVA2Renderer::ffGetBuffer2(AVCodecContext* context, AVFrame* frame, int)
|
||||
{
|
||||
DXVA2Renderer* me = reinterpret_cast<DXVA2Renderer*>(context->opaque);
|
||||
DXVA2Renderer* me = (DXVA2Renderer*)((FFmpegVideoDecoder*)context->opaque)->getRenderer();
|
||||
|
||||
frame->buf[0] = av_buffer_pool_get(me->m_Pool);
|
||||
if (!frame->buf[0]) {
|
||||
@@ -107,25 +116,14 @@ int DXVA2Renderer::ffGetBuffer2(AVCodecContext* context, AVFrame* frame, int)
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum AVPixelFormat DXVA2Renderer::ffGetFormat(AVCodecContext*,
|
||||
const enum AVPixelFormat* pixFmts)
|
||||
{
|
||||
const enum AVPixelFormat *p;
|
||||
|
||||
for (p = pixFmts; *p != -1; p++) {
|
||||
if (*p == AV_PIX_FMT_DXVA2_VLD)
|
||||
return *p;
|
||||
}
|
||||
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to find DXVA2 HW surface format");
|
||||
return AV_PIX_FMT_NONE;
|
||||
}
|
||||
|
||||
bool DXVA2Renderer::initializeDecoder()
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
if (isDecoderBlacklisted()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = DXVA2CreateVideoService(m_Device, IID_IDirectXVideoDecoderService,
|
||||
reinterpret_cast<void**>(&m_DecService));
|
||||
if (FAILED(hr)) {
|
||||
@@ -208,18 +206,11 @@ bool DXVA2Renderer::initializeDecoder()
|
||||
return false;
|
||||
}
|
||||
|
||||
int alignment;
|
||||
|
||||
// HEVC using DXVA requires 128B alignment
|
||||
if (m_VideoFormat & VIDEO_FORMAT_MASK_H265) {
|
||||
alignment = 128;
|
||||
}
|
||||
else {
|
||||
alignment = 16;
|
||||
}
|
||||
|
||||
hr = m_DecService->CreateSurface(FFALIGN(m_VideoWidth, alignment),
|
||||
FFALIGN(m_VideoHeight, alignment),
|
||||
// Alignment was already taken care of
|
||||
SDL_assert(m_Desc.SampleWidth % 16 == 0);
|
||||
SDL_assert(m_Desc.SampleHeight % 16 == 0);
|
||||
hr = m_DecService->CreateSurface(m_Desc.SampleWidth,
|
||||
m_Desc.SampleHeight,
|
||||
ARRAYSIZE(m_DecSurfaces) - 1,
|
||||
m_Desc.Format,
|
||||
D3DPOOL_DEFAULT,
|
||||
@@ -349,37 +340,289 @@ bool DXVA2Renderer::initializeRenderer()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, int height)
|
||||
bool DXVA2Renderer::isDecoderBlacklisted()
|
||||
{
|
||||
IDirect3D9* d3d9;
|
||||
HRESULT hr;
|
||||
bool result = false;
|
||||
|
||||
// TODO: Update for HEVC Main10
|
||||
SDL_assert(m_VideoFormat != VIDEO_FORMAT_H265_MAIN10);
|
||||
|
||||
hr = m_Device->GetDirect3D(&d3d9);
|
||||
if (SUCCEEDED(hr)) {
|
||||
D3DCAPS9 caps;
|
||||
|
||||
hr = m_Device->GetDeviceCaps(&caps);
|
||||
if (SUCCEEDED(hr)) {
|
||||
D3DADAPTER_IDENTIFIER9 id;
|
||||
|
||||
hr = d3d9->GetAdapterIdentifier(caps.AdapterOrdinal, 0, &id);
|
||||
if (SUCCEEDED(hr)) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Detected GPU: %s (%x:%x)",
|
||||
id.Description,
|
||||
id.VendorId,
|
||||
id.DeviceId);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"GPU driver: %s %d.%d.%d.%d",
|
||||
id.Driver,
|
||||
HIWORD(id.DriverVersion.HighPart),
|
||||
LOWORD(id.DriverVersion.HighPart),
|
||||
HIWORD(id.DriverVersion.LowPart),
|
||||
LOWORD(id.DriverVersion.LowPart));
|
||||
|
||||
if (id.VendorId == 0x8086) {
|
||||
// Intel seems to encode the series in the high byte of
|
||||
// the device ID. We want to avoid the "Partial" acceleration
|
||||
// support explicitly. Those will claim to have HW acceleration
|
||||
// but perform badly.
|
||||
// https://en.wikipedia.org/wiki/Intel_Graphics_Technology#Capabilities_(GPU_video_acceleration)
|
||||
// https://raw.githubusercontent.com/GameTechDev/gpudetect/master/IntelGfx.cfg
|
||||
switch (id.DeviceId & 0xFF00) {
|
||||
case 0x0400: // Haswell
|
||||
case 0x0A00: // Haswell
|
||||
case 0x0D00: // Haswell
|
||||
case 0x1600: // Broadwell
|
||||
case 0x2200: // Cherry Trail and Braswell
|
||||
// Blacklist these for HEVC to avoid hybrid decode
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"GPU blacklisted for HEVC due to hybrid decode");
|
||||
result = (m_VideoFormat & VIDEO_FORMAT_MASK_H265) != 0;
|
||||
break;
|
||||
default:
|
||||
// Intel drivers from before late-2017 had a bug that caused some strange artifacts
|
||||
// when decoding HEVC. Avoid HEVC on drivers prior to build 4836 which I confirmed
|
||||
// is not affected on my Intel HD 515.
|
||||
// https://github.com/moonlight-stream/moonlight-qt/issues/32
|
||||
// https://www.intel.com/content/www/us/en/support/articles/000005654/graphics-drivers.html
|
||||
if (LOWORD(id.DriverVersion.LowPart) < 4836) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Intel driver version blacklisted for HEVC");
|
||||
result = (m_VideoFormat & VIDEO_FORMAT_MASK_H265) != 0;
|
||||
}
|
||||
else {
|
||||
// Everything else is fine with whatever it says it supports
|
||||
result = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (id.VendorId == 0x10DE) {
|
||||
// For NVIDIA, we wait to avoid those GPUs with Feature Set E
|
||||
// for HEVC decoding, since that's hybrid. It appears that Kepler GPUs
|
||||
// also had some hybrid decode support (per DXVA2 Checker) so we'll
|
||||
// blacklist those too.
|
||||
// https://en.wikipedia.org/wiki/Nvidia_PureVideo
|
||||
// https://bluesky23.yukishigure.com/en/dxvac/deviceInfo/decoder.html
|
||||
// http://envytools.readthedocs.io/en/latest/hw/pciid.html (missing GM200)
|
||||
if ((id.DeviceId >= 0x1180 && id.DeviceId <= 0x11BF) || // GK104
|
||||
(id.DeviceId >= 0x11C0 && id.DeviceId <= 0x11FF) || // GK106
|
||||
(id.DeviceId >= 0x0FC0 && id.DeviceId <= 0x0FFF) || // GK107
|
||||
(id.DeviceId >= 0x1000 && id.DeviceId <= 0x103F) || // GK110/GK110B
|
||||
(id.DeviceId >= 0x1280 && id.DeviceId <= 0x12BF) || // GK208
|
||||
(id.DeviceId >= 0x1340 && id.DeviceId <= 0x137F) || // GM108
|
||||
(id.DeviceId >= 0x1380 && id.DeviceId <= 0x13BF) || // GM107
|
||||
(id.DeviceId >= 0x13C0 && id.DeviceId <= 0x13FF) || // GM204
|
||||
(id.DeviceId >= 0x1617 && id.DeviceId <= 0x161A) || // GM204
|
||||
(id.DeviceId == 0x1667) || // GM204
|
||||
(id.DeviceId >= 0x17C0 && id.DeviceId <= 0x17FF)) { // GM200
|
||||
// Avoid HEVC on Feature Set E GPUs
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"GPU blacklisted for HEVC due to hybrid decode");
|
||||
result = (m_VideoFormat & VIDEO_FORMAT_MASK_H265) != 0;
|
||||
}
|
||||
else {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
else if (id.VendorId == 0x1002) {
|
||||
// AMD doesn't seem to do hybrid acceleration?
|
||||
result = false;
|
||||
}
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unrecognized vendor ID: %x",
|
||||
id.VendorId);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"GetDeviceCaps() failed: %x", hr);
|
||||
}
|
||||
|
||||
d3d9->Release();
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"GetDirect3D() failed: %x", hr);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"GPU blacklisted for format %x",
|
||||
m_VideoFormat);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
|
||||
{
|
||||
SDL_SysWMinfo info;
|
||||
|
||||
SDL_VERSION(&info.version);
|
||||
SDL_GetWindowWMInfo(window, &info);
|
||||
|
||||
IDirect3D9Ex* d3d9ex;
|
||||
HRESULT hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &d3d9ex);
|
||||
if (FAILED(hr)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Direct3DCreate9Ex() failed: %x",
|
||||
hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
int adapterIndex = SDL_Direct3D9GetAdapterIndex(SDL_GetWindowDisplayIndex(window));
|
||||
Uint32 windowFlags = SDL_GetWindowFlags(window);
|
||||
|
||||
D3DCAPS9 deviceCaps;
|
||||
d3d9ex->GetDeviceCaps(adapterIndex, D3DDEVTYPE_HAL, &deviceCaps);
|
||||
|
||||
D3DDISPLAYMODEEX currentMode;
|
||||
currentMode.Size = sizeof(currentMode);
|
||||
d3d9ex->GetAdapterDisplayModeEx(adapterIndex, ¤tMode, nullptr);
|
||||
|
||||
D3DPRESENT_PARAMETERS d3dpp = {};
|
||||
d3dpp.hDeviceWindow = info.info.win.window;
|
||||
d3dpp.Flags = D3DPRESENTFLAG_VIDEO;
|
||||
|
||||
if ((windowFlags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
|
||||
d3dpp.Windowed = false;
|
||||
d3dpp.BackBufferWidth = currentMode.Width;
|
||||
d3dpp.BackBufferHeight = currentMode.Height;
|
||||
d3dpp.FullScreen_RefreshRateInHz = currentMode.RefreshRate;
|
||||
d3dpp.BackBufferFormat = currentMode.Format;
|
||||
}
|
||||
else {
|
||||
d3dpp.Windowed = true;
|
||||
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
|
||||
|
||||
SDL_GetWindowSize(window, (int*)&d3dpp.BackBufferWidth, (int*)&d3dpp.BackBufferHeight);
|
||||
}
|
||||
|
||||
BOOL dwmEnabled;
|
||||
DwmIsCompositionEnabled(&dwmEnabled);
|
||||
if (d3dpp.Windowed && dwmEnabled) {
|
||||
// If composition enabled, disable v-sync and let DWM manage things
|
||||
// to reduce latency by avoiding double v-syncing.
|
||||
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
|
||||
|
||||
// If V-sync is enabled (not rendering faster than display),
|
||||
// we can use FlipEx for more efficient swapping.
|
||||
if (enableVsync) {
|
||||
// D3DSWAPEFFECT_FLIPEX requires at least 2 back buffers to allow us to
|
||||
// continue while DWM is waiting to render the surface to the display.
|
||||
d3dpp.SwapEffect = D3DSWAPEFFECT_FLIPEX;
|
||||
d3dpp.BackBufferCount = 2;
|
||||
}
|
||||
else {
|
||||
// With V-sync off, we won't use FlipEx because that will block while
|
||||
// DWM is waiting to render our surface (effectively behaving like V-Sync).
|
||||
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||
d3dpp.BackBufferCount = 1;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Windowed mode with DWM running");
|
||||
}
|
||||
else if (enableVsync) {
|
||||
// Uncomposited desktop or full-screen exclusive mode with V-sync enabled
|
||||
// We will enable V-sync in this scenario to avoid tearing.
|
||||
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
|
||||
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||
d3dpp.BackBufferCount = 1;
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"V-Sync enabled");
|
||||
}
|
||||
else {
|
||||
// Uncomposited desktop or full-screen exclusive mode with V-sync disabled
|
||||
// We will allowing tearing for lowest latency.
|
||||
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
|
||||
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||
d3dpp.BackBufferCount = 1;
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"V-Sync disabled in tearing mode");
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Windowed: %d | Present Interval: %x",
|
||||
d3dpp.Windowed, d3dpp.PresentationInterval);
|
||||
|
||||
// FFmpeg requires this attribute for doing asynchronous decoding
|
||||
// in a separate thread with this device.
|
||||
int deviceFlags = D3DCREATE_MULTITHREADED;
|
||||
|
||||
if (deviceCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) {
|
||||
deviceFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
|
||||
}
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"No hardware vertex processing support!");
|
||||
deviceFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
|
||||
}
|
||||
|
||||
hr = d3d9ex->CreateDeviceEx(adapterIndex,
|
||||
D3DDEVTYPE_HAL,
|
||||
d3dpp.hDeviceWindow,
|
||||
deviceFlags,
|
||||
&d3dpp,
|
||||
d3dpp.Windowed ? nullptr : ¤tMode,
|
||||
&m_Device);
|
||||
|
||||
d3d9ex->Release();
|
||||
|
||||
if (FAILED(hr)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"CreateDeviceEx() failed: %x",
|
||||
hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = m_Device->SetMaximumFrameLatency(1);
|
||||
if (FAILED(hr)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SetMaximumFrameLatency() failed: %x",
|
||||
hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, int height, int, bool enableVsync)
|
||||
{
|
||||
m_VideoFormat = videoFormat;
|
||||
m_VideoWidth = width;
|
||||
m_VideoHeight = height;
|
||||
|
||||
// FFmpeg will be decoding on different threads than the main thread that we're
|
||||
// currently running on right now. We must set this hint so SDL will pass
|
||||
// D3DCREATE_MULTITHREADED to IDirect3D9::CreateDevice().
|
||||
SDL_SetHint(SDL_HINT_RENDER_DIRECT3D_THREADSAFE, "1");
|
||||
RtlZeroMemory(&m_Desc, sizeof(m_Desc));
|
||||
|
||||
// We require full-screen desktop mode to avoid having to enable V-sync
|
||||
// to synchronize frame delivery (which has a much higher latency penalty).
|
||||
m_SdlRenderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||
if (!m_SdlRenderer) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_CreateRenderer() failed: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
int alignment;
|
||||
|
||||
// HEVC using DXVA requires 128B alignment
|
||||
if (m_VideoFormat & VIDEO_FORMAT_MASK_H265) {
|
||||
alignment = 128;
|
||||
}
|
||||
else {
|
||||
alignment = 16;
|
||||
}
|
||||
|
||||
m_Device = SDL_RenderGetD3D9Device(m_SdlRenderer);
|
||||
|
||||
// Draw a black frame until the video stream starts rendering
|
||||
SDL_SetRenderDrawColor(m_SdlRenderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
|
||||
SDL_RenderClear(m_SdlRenderer);
|
||||
SDL_RenderPresent(m_SdlRenderer);
|
||||
|
||||
RtlZeroMemory(&m_Desc, sizeof(m_Desc));
|
||||
m_Desc.SampleWidth = m_VideoWidth;
|
||||
m_Desc.SampleHeight = m_VideoHeight;
|
||||
m_Desc.SampleWidth = FFALIGN(m_VideoWidth, alignment);
|
||||
m_Desc.SampleHeight = FFALIGN(m_VideoHeight, alignment);
|
||||
m_Desc.SampleFormat.VideoChromaSubsampling = DXVA2_VideoChromaSubsampling_Unknown;
|
||||
m_Desc.SampleFormat.NominalRange = DXVA2_NominalRange_Unknown;
|
||||
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_Unknown;
|
||||
@@ -389,6 +632,10 @@ bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, i
|
||||
m_Desc.SampleFormat.SampleFormat = DXVA2_SampleProgressiveFrame;
|
||||
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('N','V','1','2');
|
||||
|
||||
if (!initializeDevice(window, enableVsync)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!initializeDecoder()) {
|
||||
return false;
|
||||
}
|
||||
@@ -397,10 +644,34 @@ bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, i
|
||||
return false;
|
||||
}
|
||||
|
||||
// For some reason, using Direct3D9Ex breaks this with multi-monitor setups.
|
||||
// When focus is lost, the window is minimized then immediately restored without
|
||||
// input focus. This glitches out the renderer and a bunch of other stuff.
|
||||
// Direct3D9Ex itself seems to have this minimize on focus loss behavior on its
|
||||
// own, so just disable SDL's handling of the focus loss event.
|
||||
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DXVA2Renderer::renderFrame(AVFrame* frame)
|
||||
bool DXVA2Renderer::needsTestFrame()
|
||||
{
|
||||
// We validate the DXVA2 profiles are supported
|
||||
// in initialize() so no test frame is required
|
||||
return false;
|
||||
}
|
||||
|
||||
int DXVA2Renderer::getDecoderCapabilities()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
IFFmpegRenderer::VSyncConstraint DXVA2Renderer::getVsyncConstraint()
|
||||
{
|
||||
return VSYNC_ANY;
|
||||
}
|
||||
|
||||
void DXVA2Renderer::renderFrameAtVsync(AVFrame *frame)
|
||||
{
|
||||
IDirect3DSurface9* surface = reinterpret_cast<IDirect3DSurface9*>(frame->data[3]);
|
||||
HRESULT hr;
|
||||
@@ -500,28 +771,26 @@ void DXVA2Renderer::renderFrame(AVFrame* frame)
|
||||
sample.Start = m_FrameIndex;
|
||||
sample.End = m_FrameIndex + 1;
|
||||
sample.SrcSurface = surface;
|
||||
sample.SrcRect.right = m_Desc.SampleWidth;
|
||||
sample.SrcRect.bottom = m_Desc.SampleHeight;
|
||||
sample.SrcRect.right = m_VideoWidth;
|
||||
sample.SrcRect.bottom = m_VideoHeight;
|
||||
sample.SampleFormat = m_Desc.SampleFormat;
|
||||
sample.PlanarAlpha = DXVA2_Fixed32OpaqueAlpha();
|
||||
|
||||
// Center in frame and preserve aspect ratio
|
||||
double srcAspectRatio = (double)m_Desc.SampleWidth / (double)m_Desc.SampleHeight;
|
||||
double dstAspectRatio = (double)m_DisplayWidth / (double)m_DisplayHeight;
|
||||
if (dstAspectRatio < srcAspectRatio) {
|
||||
// Greater height per width
|
||||
int drawHeight = (int)(m_DisplayWidth / srcAspectRatio);
|
||||
sample.DstRect.top = (m_DisplayHeight - drawHeight) / 2;
|
||||
sample.DstRect.bottom = sample.DstRect.bottom + drawHeight;
|
||||
sample.DstRect.right = m_DisplayWidth;
|
||||
}
|
||||
else {
|
||||
// Greater width per height
|
||||
int drawWidth = (int)(m_DisplayHeight * srcAspectRatio);
|
||||
sample.DstRect.bottom = m_DisplayHeight;
|
||||
sample.DstRect.left = (m_DisplayWidth - drawWidth) / 2;
|
||||
sample.DstRect.right = sample.DstRect.left + drawWidth;
|
||||
}
|
||||
SDL_Rect src, dst;
|
||||
src.x = src.y = 0;
|
||||
src.w = m_VideoWidth;
|
||||
src.h = m_VideoHeight;
|
||||
dst.x = dst.y = 0;
|
||||
dst.w = m_DisplayWidth;
|
||||
dst.h = m_DisplayHeight;
|
||||
|
||||
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
||||
|
||||
sample.DstRect.left = dst.x;
|
||||
sample.DstRect.right = dst.x + dst.w;
|
||||
sample.DstRect.top = dst.y;
|
||||
sample.DstRect.bottom = dst.y + dst.h;
|
||||
|
||||
DXVA2_VideoProcessBltParams bltParams = {};
|
||||
|
||||
@@ -538,33 +807,27 @@ void DXVA2Renderer::renderFrame(AVFrame* frame)
|
||||
|
||||
bltParams.Alpha = DXVA2_Fixed32OpaqueAlpha();
|
||||
|
||||
if (SDL_RenderClear(m_SdlRenderer) != 0) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_RenderClear() failed: %s",
|
||||
SDL_GetError());
|
||||
av_frame_free(&frame);
|
||||
|
||||
// We're going to cheat a little bit here. It seems SDL's
|
||||
// renderer may flake out in scenarios like moving the window
|
||||
// between monitors, so generate a synthetic reset event for
|
||||
// the main loop to consume.
|
||||
SDL_Event event;
|
||||
event.type = SDL_RENDER_TARGETS_RESET;
|
||||
SDL_PushEvent(&event);
|
||||
|
||||
return;
|
||||
}
|
||||
m_Device->Clear(0, nullptr, D3DCLEAR_TARGET, D3DCOLOR_ARGB(255, 0, 0, 0), 0.0f, 0);
|
||||
|
||||
hr = m_Processor->VideoProcessBlt(m_RenderTarget, &bltParams, &sample, 1, nullptr);
|
||||
av_frame_free(&frame);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VideoProcessBlt() failed: %x",
|
||||
hr);
|
||||
SDL_Event event;
|
||||
event.type = SDL_RENDER_TARGETS_RESET;
|
||||
SDL_PushEvent(&event);
|
||||
return;
|
||||
}
|
||||
|
||||
// We must try to present to trigger SDL's logic to recover the render target,
|
||||
// even if VideoProcessBlt() fails.
|
||||
SDL_RenderPresent(m_SdlRenderer);
|
||||
hr = m_Device->PresentEx(nullptr, nullptr, nullptr, nullptr, 0);
|
||||
if (FAILED(hr)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"PresentEx() failed: %x",
|
||||
hr);
|
||||
SDL_Event event;
|
||||
event.type = SDL_RENDER_TARGETS_RESET;
|
||||
SDL_PushEvent(&event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "renderer.h"
|
||||
#include "pacer/pacer.h"
|
||||
|
||||
#include <d3d9.h>
|
||||
#include <dxva2api.h>
|
||||
@@ -17,13 +18,20 @@ public:
|
||||
virtual bool initialize(SDL_Window* window,
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height);
|
||||
int height,
|
||||
int maxFps,
|
||||
bool enableVsync);
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context);
|
||||
virtual void renderFrame(AVFrame* frame);
|
||||
virtual void renderFrameAtVsync(AVFrame* frame);
|
||||
virtual bool needsTestFrame();
|
||||
virtual int getDecoderCapabilities();
|
||||
virtual VSyncConstraint getVsyncConstraint();
|
||||
|
||||
private:
|
||||
bool initializeDecoder();
|
||||
bool initializeRenderer();
|
||||
bool initializeDevice(SDL_Window* window, bool enableVsync);
|
||||
bool isDecoderBlacklisted();
|
||||
|
||||
static
|
||||
AVBufferRef* ffPoolAlloc(void* opaque, int size);
|
||||
@@ -34,10 +42,6 @@ private:
|
||||
static
|
||||
int ffGetBuffer2(AVCodecContext* context, AVFrame* frame, int flags);
|
||||
|
||||
static
|
||||
enum AVPixelFormat ffGetFormat(AVCodecContext*,
|
||||
const enum AVPixelFormat* pixFmts);
|
||||
|
||||
int m_VideoFormat;
|
||||
int m_VideoWidth;
|
||||
int m_VideoHeight;
|
||||
@@ -45,8 +49,6 @@ private:
|
||||
int m_DisplayWidth;
|
||||
int m_DisplayHeight;
|
||||
|
||||
SDL_Renderer* m_SdlRenderer;
|
||||
|
||||
struct dxva_context m_DXVAContext;
|
||||
IDirect3DSurface9* m_DecSurfaces[19];
|
||||
DXVA2_ConfigPictureDecode m_Config;
|
||||
@@ -55,7 +57,7 @@ private:
|
||||
int m_SurfacesUsed;
|
||||
AVBufferPool* m_Pool;
|
||||
|
||||
IDirect3DDevice9* m_Device;
|
||||
IDirect3DDevice9Ex* m_Device;
|
||||
IDirect3DSurface9* m_RenderTarget;
|
||||
IDirectXVideoProcessorService* m_ProcService;
|
||||
IDirectXVideoProcessor* m_Processor;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "pacer.h"
|
||||
|
||||
class DisplayLinkVsyncSourceFactory
|
||||
{
|
||||
public:
|
||||
static
|
||||
IVsyncSource* createVsyncSource(Pacer* pacer);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
#include "displaylinkvsyncsource.h"
|
||||
|
||||
#include <SDL_syswm.h>
|
||||
|
||||
#include <CoreVideo/CoreVideo.h>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
class DisplayLinkVsyncSource : public IVsyncSource
|
||||
{
|
||||
public:
|
||||
DisplayLinkVsyncSource(Pacer* pacer)
|
||||
: m_Pacer(pacer),
|
||||
m_DisplayLink(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
virtual ~DisplayLinkVsyncSource() override
|
||||
{
|
||||
if (m_DisplayLink != nullptr) {
|
||||
CVDisplayLinkStop(m_DisplayLink);
|
||||
CVDisplayLinkRelease(m_DisplayLink);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
CGDirectDisplayID
|
||||
getDisplayID(NSScreen* screen)
|
||||
{
|
||||
NSNumber* screenNumber = [screen deviceDescription][@"NSScreenNumber"];
|
||||
return [screenNumber unsignedIntValue];
|
||||
}
|
||||
|
||||
static
|
||||
CVReturn
|
||||
displayLinkOutputCallback(
|
||||
CVDisplayLinkRef displayLink,
|
||||
const CVTimeStamp* /* now */,
|
||||
const CVTimeStamp* /* vsyncTime */,
|
||||
CVOptionFlags,
|
||||
CVOptionFlags*,
|
||||
void *displayLinkContext)
|
||||
{
|
||||
auto me = reinterpret_cast<DisplayLinkVsyncSource*>(displayLinkContext);
|
||||
|
||||
SDL_assert(displayLink == me->m_DisplayLink);
|
||||
|
||||
// In my testing on macOS 10.13, this callback is invoked about 24 ms
|
||||
// prior to the specified v-sync time (now - vsyncTime). Since this is
|
||||
// greater than the standard v-sync interval (16 ms = 60 FPS), we will
|
||||
// draw using the current host time, rather than the actual v-sync target
|
||||
// time. Because the CVDisplayLink is in sync with the actual v-sync
|
||||
// interval, even if many ms prior, we can safely use the current host time
|
||||
// and get a consistent callback for each v-sync. This reduces video latency
|
||||
// by at least 1 frame vs. rendering with the actual vsyncTime.
|
||||
me->m_Pacer->vsyncCallback(500 / me->m_DisplayFps);
|
||||
|
||||
return kCVReturnSuccess;
|
||||
}
|
||||
|
||||
virtual bool initialize(SDL_Window* window, int displayFps) override
|
||||
{
|
||||
SDL_SysWMinfo info;
|
||||
|
||||
SDL_VERSION(&info.version);
|
||||
|
||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetWindowWMInfo() failed: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_assert(info.subsystem == SDL_SYSWM_COCOA);
|
||||
|
||||
m_DisplayFps = displayFps;
|
||||
|
||||
NSScreen* screen = [info.info.cocoa.window screen];
|
||||
CVReturn status;
|
||||
if (screen == nullptr) {
|
||||
// Window not visible on any display, so use a
|
||||
// CVDisplayLink that can work with all active displays.
|
||||
// When we become visible, we'll recreate ourselves
|
||||
// and associate with the new screen.
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"NSWindow is not visible on any display");
|
||||
status = CVDisplayLinkCreateWithActiveCGDisplays(&m_DisplayLink);
|
||||
}
|
||||
else {
|
||||
CGDirectDisplayID displayId;
|
||||
displayId = getDisplayID(screen);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"NSWindow on display: %x",
|
||||
displayId);
|
||||
status = CVDisplayLinkCreateWithCGDisplay(displayId, &m_DisplayLink);
|
||||
}
|
||||
if (status != kCVReturnSuccess) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create CVDisplayLink: %d",
|
||||
status);
|
||||
return false;
|
||||
}
|
||||
|
||||
status = CVDisplayLinkSetOutputCallback(m_DisplayLink, displayLinkOutputCallback, this);
|
||||
if (status != kCVReturnSuccess) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"CVDisplayLinkSetOutputCallback() failed: %d",
|
||||
status);
|
||||
return false;
|
||||
}
|
||||
|
||||
status = CVDisplayLinkStart(m_DisplayLink);
|
||||
if (status != kCVReturnSuccess) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"CVDisplayLinkStart() failed: %d",
|
||||
status);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
Pacer* m_Pacer;
|
||||
CVDisplayLinkRef m_DisplayLink;
|
||||
int m_DisplayFps;
|
||||
};
|
||||
|
||||
IVsyncSource* DisplayLinkVsyncSourceFactory::createVsyncSource(Pacer* pacer) {
|
||||
return new DisplayLinkVsyncSource(pacer);
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
#include "dxvsyncsource.h"
|
||||
|
||||
// Useful references:
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=467617
|
||||
// https://chromium.googlesource.com/chromium/src.git/+/c564f2fe339b2b2abb0c8773c90c83215670ea71/gpu/ipc/service/gpu_vsync_provider_win.cc
|
||||
|
||||
DxVsyncSource::DxVsyncSource(Pacer* pacer) :
|
||||
m_Pacer(pacer),
|
||||
m_Thread(nullptr),
|
||||
m_Gdi32Handle(nullptr)
|
||||
{
|
||||
SDL_AtomicSet(&m_Stopping, 0);
|
||||
}
|
||||
|
||||
DxVsyncSource::~DxVsyncSource()
|
||||
{
|
||||
if (m_Thread != nullptr) {
|
||||
SDL_AtomicSet(&m_Stopping, 1);
|
||||
SDL_WaitThread(m_Thread, nullptr);
|
||||
}
|
||||
|
||||
if (m_Gdi32Handle != nullptr) {
|
||||
FreeLibrary(m_Gdi32Handle);
|
||||
}
|
||||
}
|
||||
|
||||
bool DxVsyncSource::initialize(SDL_Window* window, int displayFps)
|
||||
{
|
||||
m_DisplayFps = displayFps;
|
||||
|
||||
m_Gdi32Handle = LoadLibraryA("gdi32.dll");
|
||||
if (m_Gdi32Handle == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to load gdi32.dll: %d",
|
||||
GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_D3DKMTOpenAdapterFromHdc = (PFND3DKMTOPENADAPTERFROMHDC)GetProcAddress(m_Gdi32Handle, "D3DKMTOpenAdapterFromHdc");
|
||||
m_D3DKMTCloseAdapter = (PFND3DKMTCLOSEADAPTER)GetProcAddress(m_Gdi32Handle, "D3DKMTCloseAdapter");
|
||||
m_D3DKMTWaitForVerticalBlankEvent = (PFND3DKMTWAITFORVERTICALBLANKEVENT)GetProcAddress(m_Gdi32Handle, "D3DKMTWaitForVerticalBlankEvent");
|
||||
|
||||
if (m_D3DKMTOpenAdapterFromHdc == nullptr ||
|
||||
m_D3DKMTCloseAdapter == nullptr ||
|
||||
m_D3DKMTWaitForVerticalBlankEvent == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Missing required function in gdi32.dll");
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_SysWMinfo info;
|
||||
|
||||
SDL_VERSION(&info.version);
|
||||
|
||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetWindowWMInfo() failed: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Window = info.info.win.window;
|
||||
|
||||
m_Thread = SDL_CreateThread(vsyncThread, "DX Vsync Thread", this);
|
||||
if (m_Thread == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to create DX V-sync thread: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int DxVsyncSource::vsyncThread(void* context)
|
||||
{
|
||||
DxVsyncSource* me = reinterpret_cast<DxVsyncSource*>(context);
|
||||
|
||||
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
||||
|
||||
D3DKMT_OPENADAPTERFROMHDC openAdapterParams = {};
|
||||
HMONITOR lastMonitor = nullptr;
|
||||
DEVMODEA monitorMode;
|
||||
monitorMode.dmSize = sizeof(monitorMode);
|
||||
|
||||
while (SDL_AtomicGet(&me->m_Stopping) == 0) {
|
||||
D3DKMT_WAITFORVERTICALBLANKEVENT waitForVblankEventParams;
|
||||
NTSTATUS status;
|
||||
|
||||
// If the monitor has changed from last time, open the new adapter
|
||||
HMONITOR currentMonitor = MonitorFromWindow(me->m_Window, MONITOR_DEFAULTTONEAREST);
|
||||
if (currentMonitor != lastMonitor) {
|
||||
MONITORINFOEXA monitorInfo = {};
|
||||
monitorInfo.cbSize = sizeof(monitorInfo);
|
||||
if (!GetMonitorInfoA(currentMonitor, &monitorInfo)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"GetMonitorInfo() failed: %d",
|
||||
GetLastError());
|
||||
SDL_Delay(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!EnumDisplaySettingsA(monitorInfo.szDevice, ENUM_CURRENT_SETTINGS, &monitorMode)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"EnumDisplaySettings() failed: %d",
|
||||
GetLastError());
|
||||
SDL_Delay(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Monitor changed: %s %d Hz",
|
||||
monitorInfo.szDevice,
|
||||
monitorMode.dmDisplayFrequency);
|
||||
|
||||
if (openAdapterParams.hAdapter != 0) {
|
||||
D3DKMT_CLOSEADAPTER closeAdapterParams = {};
|
||||
closeAdapterParams.hAdapter = openAdapterParams.hAdapter;
|
||||
me->m_D3DKMTCloseAdapter(&closeAdapterParams);
|
||||
}
|
||||
|
||||
openAdapterParams.hDc = CreateDCA(nullptr, monitorInfo.szDevice, nullptr, nullptr);
|
||||
if (!openAdapterParams.hDc) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"CreateDC() failed: %d",
|
||||
GetLastError());
|
||||
SDL_Delay(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
status = me->m_D3DKMTOpenAdapterFromHdc(&openAdapterParams);
|
||||
DeleteDC(openAdapterParams.hDc);
|
||||
|
||||
if (status != STATUS_SUCCESS) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"D3DKMTOpenAdapterFromHdc() failed: %x",
|
||||
status);
|
||||
SDL_Delay(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
lastMonitor = currentMonitor;
|
||||
}
|
||||
|
||||
waitForVblankEventParams.hAdapter = openAdapterParams.hAdapter;
|
||||
waitForVblankEventParams.hDevice = 0;
|
||||
waitForVblankEventParams.VidPnSourceId = openAdapterParams.VidPnSourceId;
|
||||
|
||||
status = me->m_D3DKMTWaitForVerticalBlankEvent(&waitForVblankEventParams);
|
||||
if (status != STATUS_SUCCESS) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"D3DKMTWaitForVerticalBlankEvent() failed: %x",
|
||||
status);
|
||||
SDL_Delay(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
me->m_Pacer->vsyncCallback(1000 / me->m_DisplayFps);
|
||||
}
|
||||
|
||||
if (openAdapterParams.hAdapter != 0) {
|
||||
D3DKMT_CLOSEADAPTER closeAdapterParams = {};
|
||||
closeAdapterParams.hAdapter = openAdapterParams.hAdapter;
|
||||
me->m_D3DKMTCloseAdapter(&closeAdapterParams);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "pacer.h"
|
||||
|
||||
#include <SDL_syswm.h>
|
||||
|
||||
// from <D3dkmthk.h>
|
||||
typedef LONG NTSTATUS;
|
||||
typedef UINT D3DKMT_HANDLE;
|
||||
typedef UINT D3DDDI_VIDEO_PRESENT_SOURCE_ID;
|
||||
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
|
||||
#define STATUS_GRAPHICS_PRESENT_OCCLUDED ((NTSTATUS)0xC01E0006L)
|
||||
typedef struct _D3DKMT_OPENADAPTERFROMHDC {
|
||||
HDC hDc;
|
||||
D3DKMT_HANDLE hAdapter;
|
||||
LUID AdapterLuid;
|
||||
D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId;
|
||||
} D3DKMT_OPENADAPTERFROMHDC;
|
||||
typedef struct _D3DKMT_CLOSEADAPTER {
|
||||
D3DKMT_HANDLE hAdapter;
|
||||
} D3DKMT_CLOSEADAPTER;
|
||||
typedef struct _D3DKMT_WAITFORVERTICALBLANKEVENT {
|
||||
D3DKMT_HANDLE hAdapter;
|
||||
D3DKMT_HANDLE hDevice;
|
||||
D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId;
|
||||
} D3DKMT_WAITFORVERTICALBLANKEVENT;
|
||||
typedef NTSTATUS(APIENTRY* PFND3DKMTOPENADAPTERFROMHDC)(D3DKMT_OPENADAPTERFROMHDC*);
|
||||
typedef NTSTATUS(APIENTRY* PFND3DKMTCLOSEADAPTER)(D3DKMT_CLOSEADAPTER*);
|
||||
typedef NTSTATUS(APIENTRY* PFND3DKMTWAITFORVERTICALBLANKEVENT)(D3DKMT_WAITFORVERTICALBLANKEVENT*);
|
||||
|
||||
class DxVsyncSource : public IVsyncSource
|
||||
{
|
||||
public:
|
||||
DxVsyncSource(Pacer* pacer);
|
||||
|
||||
virtual ~DxVsyncSource();
|
||||
|
||||
virtual bool initialize(SDL_Window* window, int displayFps);
|
||||
|
||||
private:
|
||||
static int vsyncThread(void* context);
|
||||
|
||||
Pacer* m_Pacer;
|
||||
SDL_Thread* m_Thread;
|
||||
SDL_atomic_t m_Stopping;
|
||||
HMODULE m_Gdi32Handle;
|
||||
HWND m_Window;
|
||||
int m_DisplayFps;
|
||||
|
||||
PFND3DKMTOPENADAPTERFROMHDC m_D3DKMTOpenAdapterFromHdc;
|
||||
PFND3DKMTCLOSEADAPTER m_D3DKMTCloseAdapter;
|
||||
PFND3DKMTWAITFORVERTICALBLANKEVENT m_D3DKMTWaitForVerticalBlankEvent;
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "nullthreadedvsyncsource.h"
|
||||
|
||||
NullThreadedVsyncSource::NullThreadedVsyncSource(Pacer* pacer) :
|
||||
m_Pacer(pacer),
|
||||
m_Thread(nullptr)
|
||||
{
|
||||
SDL_AtomicSet(&m_Stopping, 0);
|
||||
}
|
||||
|
||||
NullThreadedVsyncSource::~NullThreadedVsyncSource()
|
||||
{
|
||||
if (m_Thread != nullptr) {
|
||||
SDL_AtomicSet(&m_Stopping, 1);
|
||||
SDL_WaitThread(m_Thread, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool NullThreadedVsyncSource::initialize(SDL_Window*, int displayFps)
|
||||
{
|
||||
m_DisplayFps = displayFps;
|
||||
m_Thread = SDL_CreateThread(vsyncThread, "Null Vsync Thread", this);
|
||||
if (m_Thread == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to create DX V-sync thread: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int NullThreadedVsyncSource::vsyncThread(void* context)
|
||||
{
|
||||
NullThreadedVsyncSource* me = reinterpret_cast<NullThreadedVsyncSource*>(context);
|
||||
|
||||
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
||||
|
||||
while (SDL_AtomicGet(&me->m_Stopping) == 0) {
|
||||
me->m_Pacer->vsyncCallback(1000 / me->m_DisplayFps);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "pacer.h"
|
||||
|
||||
class NullThreadedVsyncSource : public IVsyncSource
|
||||
{
|
||||
public:
|
||||
NullThreadedVsyncSource(Pacer* pacer);
|
||||
|
||||
virtual ~NullThreadedVsyncSource();
|
||||
|
||||
virtual bool initialize(SDL_Window* window, int displayFps);
|
||||
|
||||
private:
|
||||
static int vsyncThread(void* context);
|
||||
|
||||
Pacer* m_Pacer;
|
||||
SDL_Thread* m_Thread;
|
||||
SDL_atomic_t m_Stopping;
|
||||
int m_DisplayFps;
|
||||
};
|
||||
@@ -0,0 +1,175 @@
|
||||
#include "pacer.h"
|
||||
#include "streaming/streamutils.h"
|
||||
|
||||
#include "nullthreadedvsyncsource.h"
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
#include "displaylinkvsyncsource.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#include "dxvsyncsource.h"
|
||||
#endif
|
||||
|
||||
#define FRAME_HISTORY_ENTRIES 8
|
||||
|
||||
// We may be woken up slightly late so don't go all the way
|
||||
// up to the next V-sync since we may accidentally step into
|
||||
// the next V-sync period. It also takes some amount of time
|
||||
// to do the render itself, so we can't render right before
|
||||
// V-sync happens.
|
||||
#define TIMER_SLACK_MS 3
|
||||
|
||||
Pacer::Pacer(IFFmpegRenderer* renderer) :
|
||||
m_FrameQueueLock(0),
|
||||
m_VsyncSource(nullptr),
|
||||
m_VsyncRenderer(renderer),
|
||||
m_MaxVideoFps(0),
|
||||
m_DisplayFps(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Pacer::~Pacer()
|
||||
{
|
||||
// Stop V-sync callbacks
|
||||
delete m_VsyncSource;
|
||||
m_VsyncSource = nullptr;
|
||||
|
||||
while (!m_FrameQueue.isEmpty()) {
|
||||
AVFrame* frame = m_FrameQueue.dequeue();
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
}
|
||||
|
||||
// Called in an arbitrary thread by the IVsyncSource on V-sync
|
||||
// or an event synchronized with V-sync
|
||||
void Pacer::vsyncCallback(int timeUntilNextVsyncMillis)
|
||||
{
|
||||
// Make sure initialize() has been called
|
||||
SDL_assert(m_MaxVideoFps != 0);
|
||||
|
||||
SDL_assert(timeUntilNextVsyncMillis >= TIMER_SLACK_MS);
|
||||
|
||||
Uint32 vsyncCallbackStartTime = SDL_GetTicks();
|
||||
|
||||
SDL_AtomicLock(&m_FrameQueueLock);
|
||||
|
||||
// If the queue length history entries are large, be strict
|
||||
// about dropping excess frames.
|
||||
int frameDropTarget = 1;
|
||||
|
||||
// If we may get more frames per second than we can display, use
|
||||
// frame history to drop frames only if consistently above the
|
||||
// one queued frame mark.
|
||||
if (m_MaxVideoFps >= m_DisplayFps) {
|
||||
for (int i = 0; i < m_FrameQueueHistory.count(); i++) {
|
||||
if (m_FrameQueueHistory[i] <= 1) {
|
||||
// Be lenient as long as the queue length
|
||||
// resolves before the end of frame history
|
||||
frameDropTarget = 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_FrameQueueHistory.count() == FRAME_HISTORY_ENTRIES) {
|
||||
m_FrameQueueHistory.dequeue();
|
||||
}
|
||||
|
||||
m_FrameQueueHistory.enqueue(m_FrameQueue.count());
|
||||
}
|
||||
|
||||
// Catch up if we're several frames ahead
|
||||
while (m_FrameQueue.count() > frameDropTarget) {
|
||||
AVFrame* frame = m_FrameQueue.dequeue();
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
|
||||
if (m_FrameQueue.isEmpty()) {
|
||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
||||
|
||||
while (!SDL_TICKS_PASSED(SDL_GetTicks(),
|
||||
vsyncCallbackStartTime + timeUntilNextVsyncMillis - TIMER_SLACK_MS)) {
|
||||
SDL_Delay(1);
|
||||
|
||||
SDL_AtomicLock(&m_FrameQueueLock);
|
||||
if (!m_FrameQueue.isEmpty()) {
|
||||
// Don't release the lock
|
||||
goto RenderNextFrame;
|
||||
}
|
||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
||||
}
|
||||
|
||||
// Nothing to render at this time
|
||||
return;
|
||||
}
|
||||
|
||||
RenderNextFrame:
|
||||
// Grab the first frame
|
||||
AVFrame* frame = m_FrameQueue.dequeue();
|
||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
||||
|
||||
// Render it
|
||||
m_VsyncRenderer->renderFrameAtVsync(frame);
|
||||
|
||||
// Free the frame
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enableVsync)
|
||||
{
|
||||
m_MaxVideoFps = maxVideoFps;
|
||||
m_EnableVsync = enableVsync;
|
||||
m_DisplayFps = StreamUtils::getDisplayRefreshRate(window);
|
||||
|
||||
if (m_EnableVsync) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Frame pacing in tear-free mode: target %d Hz with %d FPS stream",
|
||||
m_DisplayFps, m_MaxVideoFps);
|
||||
|
||||
#if defined(Q_OS_DARWIN)
|
||||
m_VsyncSource = DisplayLinkVsyncSourceFactory::createVsyncSource(this);
|
||||
#elif defined(Q_OS_WIN32)
|
||||
m_VsyncSource = new DxVsyncSource(this);
|
||||
#else
|
||||
// Platforms without a VsyncSource will just render frames
|
||||
// immediately like they used to.
|
||||
#endif
|
||||
|
||||
if (m_VsyncSource != nullptr && !m_VsyncSource->initialize(window, m_DisplayFps)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Minimal latency tearing mode: target %d Hz with %d FPS stream",
|
||||
m_DisplayFps, m_MaxVideoFps);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Pacer::submitFrame(AVFrame* frame)
|
||||
{
|
||||
// Make sure initialize() has been called
|
||||
SDL_assert(m_MaxVideoFps != 0);
|
||||
|
||||
// Queue the frame until the V-sync callback if
|
||||
// we have a V-sync source, otherwise deliver it
|
||||
// immediately and hope for the best.
|
||||
if (isUsingFrameQueue()) {
|
||||
SDL_AtomicLock(&m_FrameQueueLock);
|
||||
m_FrameQueue.enqueue(frame);
|
||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
||||
}
|
||||
else {
|
||||
m_VsyncRenderer->renderFrameAtVsync(frame);
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
}
|
||||
|
||||
bool Pacer::isUsingFrameQueue()
|
||||
{
|
||||
return m_VsyncSource != nullptr;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "../renderer.h"
|
||||
|
||||
#include <QQueue>
|
||||
|
||||
class IVsyncSource {
|
||||
public:
|
||||
virtual ~IVsyncSource() {}
|
||||
virtual bool initialize(SDL_Window* window, int displayFps) = 0;
|
||||
};
|
||||
|
||||
class Pacer
|
||||
{
|
||||
public:
|
||||
Pacer(IFFmpegRenderer* renderer);
|
||||
|
||||
~Pacer();
|
||||
|
||||
void submitFrame(AVFrame* frame);
|
||||
|
||||
bool initialize(SDL_Window* window, int maxVideoFps, bool enableVsync);
|
||||
|
||||
void vsyncCallback(int timeUntilNextVsyncMillis);
|
||||
|
||||
bool isUsingFrameQueue();
|
||||
|
||||
private:
|
||||
QQueue<AVFrame*> m_FrameQueue;
|
||||
QQueue<int> m_FrameQueueHistory;
|
||||
SDL_SpinLock m_FrameQueueLock;
|
||||
|
||||
IVsyncSource* m_VsyncSource;
|
||||
IFFmpegRenderer* m_VsyncRenderer;
|
||||
int m_MaxVideoFps;
|
||||
int m_DisplayFps;
|
||||
bool m_EnableVsync;
|
||||
};
|
||||
@@ -8,13 +8,24 @@ extern "C" {
|
||||
|
||||
class IFFmpegRenderer {
|
||||
public:
|
||||
enum VSyncConstraint {
|
||||
VSYNC_FORCE_OFF,
|
||||
VSYNC_FORCE_ON,
|
||||
VSYNC_ANY
|
||||
};
|
||||
|
||||
virtual ~IFFmpegRenderer() {}
|
||||
virtual bool initialize(SDL_Window* window,
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height) = 0;
|
||||
int height,
|
||||
int maxFps,
|
||||
bool enableVsync) = 0;
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context) = 0;
|
||||
virtual void renderFrame(AVFrame* frame) = 0;
|
||||
virtual void renderFrameAtVsync(AVFrame* frame) = 0;
|
||||
virtual bool needsTestFrame() = 0;
|
||||
virtual int getDecoderCapabilities() = 0;
|
||||
virtual VSyncConstraint getVsyncConstraint() = 0;
|
||||
};
|
||||
|
||||
class SdlRenderer : public IFFmpegRenderer {
|
||||
@@ -24,9 +35,14 @@ public:
|
||||
virtual bool initialize(SDL_Window* window,
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height);
|
||||
int height,
|
||||
int maxFps,
|
||||
bool enableVsync);
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context);
|
||||
virtual void renderFrame(AVFrame* frame);
|
||||
virtual void renderFrameAtVsync(AVFrame* frame);
|
||||
virtual bool needsTestFrame();
|
||||
virtual int getDecoderCapabilities();
|
||||
virtual VSyncConstraint getVsyncConstraint();
|
||||
|
||||
private:
|
||||
SDL_Renderer* m_Renderer;
|
||||
|
||||
+39
-15
@@ -23,23 +23,50 @@ SdlRenderer::~SdlRenderer()
|
||||
bool SdlRenderer::prepareDecoderContext(AVCodecContext*)
|
||||
{
|
||||
/* Nothing to do */
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Using SDL software renderer");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SdlRenderer::initialize(SDL_Window* window,
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height)
|
||||
bool SdlRenderer::needsTestFrame()
|
||||
{
|
||||
// NOTE: HEVC currently uses only 1 slice regardless of what
|
||||
// we provide in CAPABILITY_SLICES_PER_FRAME(), so we should
|
||||
// never use it for software decoding (unless common-c starts
|
||||
// respecting it for HEVC).
|
||||
if (videoFormat != VIDEO_FORMAT_H264) {
|
||||
return false;
|
||||
// This renderer should always work
|
||||
return false;
|
||||
}
|
||||
|
||||
int SdlRenderer::getDecoderCapabilities()
|
||||
{
|
||||
// The FFmpeg CPU decoder can handle reference frame invalidation,
|
||||
// but only for H.264.
|
||||
return CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC;
|
||||
}
|
||||
|
||||
IFFmpegRenderer::VSyncConstraint SdlRenderer::getVsyncConstraint()
|
||||
{
|
||||
return VSYNC_ANY;
|
||||
}
|
||||
|
||||
bool SdlRenderer::initialize(SDL_Window* window,
|
||||
int,
|
||||
int width,
|
||||
int height,
|
||||
int,
|
||||
bool enableVsync)
|
||||
{
|
||||
Uint32 rendererFlags = SDL_RENDERER_ACCELERATED;
|
||||
|
||||
if ((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
|
||||
// In full-screen exclusive mode, we enable V-sync if requested. For other modes, Windows and Mac
|
||||
// have compositors that make rendering tear-free. Linux compositor varies by distro and user
|
||||
// configuration but doesn't seem feasible to detect here.
|
||||
if (enableVsync) {
|
||||
rendererFlags |= SDL_RENDERER_PRESENTVSYNC;
|
||||
}
|
||||
}
|
||||
|
||||
m_Renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||
m_Renderer = SDL_CreateRenderer(window, -1, rendererFlags);
|
||||
if (!m_Renderer) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_CreateRenderer() failed: %s",
|
||||
@@ -71,7 +98,7 @@ bool SdlRenderer::initialize(SDL_Window* window,
|
||||
return true;
|
||||
}
|
||||
|
||||
void SdlRenderer::renderFrame(AVFrame* frame)
|
||||
void SdlRenderer::renderFrameAtVsync(AVFrame* frame)
|
||||
{
|
||||
SDL_UpdateYUVTexture(m_Texture, nullptr,
|
||||
frame->data[0],
|
||||
@@ -81,9 +108,6 @@ void SdlRenderer::renderFrame(AVFrame* frame)
|
||||
frame->data[2],
|
||||
frame->linesize[2]);
|
||||
|
||||
// Done with the frame now
|
||||
av_frame_free(&frame);
|
||||
|
||||
SDL_RenderClear(m_Renderer);
|
||||
SDL_RenderCopy(m_Renderer, m_Texture, nullptr, nullptr);
|
||||
SDL_RenderPresent(m_Renderer);
|
||||
@@ -1,13 +1,10 @@
|
||||
#include "vaapi.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <streaming/streamutils.h>
|
||||
|
||||
#include <SDL_syswm.h>
|
||||
|
||||
VAAPIRenderer::VAAPIRenderer()
|
||||
: m_HwContext(nullptr),
|
||||
m_X11VaLibHandle(nullptr),
|
||||
m_vaPutSurface(nullptr)
|
||||
: m_HwContext(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -15,16 +12,22 @@ VAAPIRenderer::VAAPIRenderer()
|
||||
VAAPIRenderer::~VAAPIRenderer()
|
||||
{
|
||||
if (m_HwContext != nullptr) {
|
||||
av_buffer_unref(&m_HwContext);
|
||||
}
|
||||
AVHWDeviceContext* deviceContext = (AVHWDeviceContext*)m_HwContext->data;
|
||||
AVVAAPIDeviceContext* vaDeviceContext = (AVVAAPIDeviceContext*)deviceContext->hwctx;
|
||||
|
||||
if (m_X11VaLibHandle != nullptr) {
|
||||
dlclose(m_X11VaLibHandle);
|
||||
// Hold onto this VADisplay since we'll need it to uninitialize VAAPI
|
||||
VADisplay display = vaDeviceContext->display;
|
||||
|
||||
av_buffer_unref(&m_HwContext);
|
||||
|
||||
if (display) {
|
||||
vaTerminate(display);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
VAAPIRenderer::initialize(SDL_Window* window, int, int width, int height)
|
||||
VAAPIRenderer::initialize(SDL_Window* window, int, int width, int height, int, bool)
|
||||
{
|
||||
int err;
|
||||
SDL_SysWMinfo info;
|
||||
@@ -37,31 +40,53 @@ VAAPIRenderer::initialize(SDL_Window* window, int, int width, int height)
|
||||
SDL_VERSION(&info.version);
|
||||
|
||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetWindowWMInfo() failed: %s",
|
||||
SDL_GetError());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetWindowWMInfo() failed: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_assert(info.subsystem == SDL_SYSWM_X11);
|
||||
m_HwContext = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VAAPI);
|
||||
if (!m_HwContext) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to allocate VAAPI context");
|
||||
return false;
|
||||
}
|
||||
|
||||
AVHWDeviceContext* deviceContext = (AVHWDeviceContext*)m_HwContext->data;
|
||||
AVVAAPIDeviceContext* vaDeviceContext = (AVVAAPIDeviceContext*)deviceContext->hwctx;
|
||||
|
||||
m_WindowSystem = info.subsystem;
|
||||
if (info.subsystem == SDL_SYSWM_X11) {
|
||||
#ifdef HAVE_LIBVA_X11
|
||||
m_XWindow = info.info.x11.window;
|
||||
|
||||
m_X11VaLibHandle = dlopen("libva-x11.so", RTLD_LAZY);
|
||||
if (!m_X11VaLibHandle) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"dlopen(libva.so) failed: %s",
|
||||
dlerror());
|
||||
vaDeviceContext->display = vaGetDisplay(info.info.x11.display);
|
||||
if (!vaDeviceContext->display) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to open X11 display for VAAPI");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_vaPutSurface = (vaPutSurface_t)dlsym(m_X11VaLibHandle, "vaPutSurface");
|
||||
#else
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Moonlight not compiled with VAAPI X11 support!");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
else if (info.subsystem == SDL_SYSWM_WAYLAND) {
|
||||
#ifdef HAVE_LIBVA_WAYLAND
|
||||
m_WaylandSurface = info.info.wl.surface;
|
||||
m_WaylandDisplay = info.info.wl.display;
|
||||
vaDeviceContext->display = vaGetDisplayWl(info.info.wl.display);
|
||||
if (!vaDeviceContext->display) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to open Wayland display for VAAPI");
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VAAPI backend does not currently support Wayland");
|
||||
"Moonlight not compiled with VAAPI Wayland support!");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -70,17 +95,37 @@ VAAPIRenderer::initialize(SDL_Window* window, int, int width, int height)
|
||||
return false;
|
||||
}
|
||||
|
||||
int major, minor;
|
||||
VAStatus status;
|
||||
status = vaInitialize(vaDeviceContext->display, &major, &minor);
|
||||
if (status != VA_STATUS_SUCCESS) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to initialize VAAPI: %d",
|
||||
status);
|
||||
return false;
|
||||
}
|
||||
|
||||
err = av_hwdevice_ctx_create(&m_HwContext,
|
||||
AV_HWDEVICE_TYPE_VAAPI,
|
||||
nullptr, nullptr, 0);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Initialized VAAPI %d.%d",
|
||||
major, minor);
|
||||
|
||||
// This will populate the driver_quirks
|
||||
err = av_hwdevice_ctx_init(m_HwContext);
|
||||
if (err < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create VAAPI context: %d",
|
||||
"Failed to initialize VAAPI context: %d",
|
||||
err);
|
||||
return false;
|
||||
}
|
||||
|
||||
// This quirk is set for the VDPAU wrapper which doesn't work with our VAAPI renderer
|
||||
if (vaDeviceContext->driver_quirks & AV_VAAPI_DRIVER_QUIRK_SURFACE_ATTRIBUTES) {
|
||||
// Fail and let our VDPAU renderer pick this up
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Avoiding VDPAU wrapper for VAAPI decoding");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -88,45 +133,87 @@ bool
|
||||
VAAPIRenderer::prepareDecoderContext(AVCodecContext* context)
|
||||
{
|
||||
context->hw_device_ctx = av_buffer_ref(m_HwContext);
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Using VAAPI accelerated renderer on %s",
|
||||
m_WindowSystem == SDL_SYSWM_X11 ? "X11" : "Wayland");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
VAAPIRenderer::needsTestFrame()
|
||||
{
|
||||
// We need a test frame to see if this VAAPI driver
|
||||
// supports the profile used for streaming
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
VAAPIRenderer::getDecoderCapabilities()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
IFFmpegRenderer::VSyncConstraint VAAPIRenderer::getVsyncConstraint()
|
||||
{
|
||||
return VSYNC_ANY;
|
||||
}
|
||||
|
||||
void
|
||||
VAAPIRenderer::renderFrame(AVFrame* frame)
|
||||
VAAPIRenderer::renderFrameAtVsync(AVFrame* frame)
|
||||
{
|
||||
VASurfaceID surface = (VASurfaceID)(uintptr_t)frame->data[3];
|
||||
AVHWDeviceContext* deviceContext = (AVHWDeviceContext*)m_HwContext->data;
|
||||
AVVAAPIDeviceContext* vaDeviceContext = (AVVAAPIDeviceContext*)deviceContext->hwctx;
|
||||
|
||||
// Center in frame and preserve aspect ratio
|
||||
int x, y, width, height;
|
||||
double srcAspectRatio = (double)m_VideoWidth / (double)m_VideoHeight;
|
||||
double dstAspectRatio = (double)m_DisplayWidth / (double)m_DisplayHeight;
|
||||
if (dstAspectRatio < srcAspectRatio) {
|
||||
// Greater height per width
|
||||
int drawHeight = (int)(m_DisplayWidth / srcAspectRatio);
|
||||
y = (m_DisplayHeight - drawHeight) / 2;
|
||||
height = drawHeight;
|
||||
x = 0;
|
||||
width = m_DisplayWidth;
|
||||
SDL_Rect src, dst;
|
||||
src.x = src.y = 0;
|
||||
src.w = m_VideoWidth;
|
||||
src.h = m_VideoHeight;
|
||||
dst.x = dst.y = 0;
|
||||
dst.w = m_DisplayWidth;
|
||||
dst.h = m_DisplayHeight;
|
||||
|
||||
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
||||
|
||||
if (m_WindowSystem == SDL_SYSWM_X11) {
|
||||
#ifdef HAVE_LIBVA_X11
|
||||
vaPutSurface(vaDeviceContext->display,
|
||||
surface,
|
||||
m_XWindow,
|
||||
0, 0,
|
||||
m_VideoWidth, m_VideoHeight,
|
||||
dst.x, dst.y,
|
||||
dst.w, dst.h,
|
||||
NULL, 0, 0);
|
||||
#endif
|
||||
}
|
||||
else if (m_WindowSystem == SDL_SYSWM_WAYLAND) {
|
||||
#ifdef HAVE_LIBVA_WAYLAND
|
||||
struct wl_buffer* buffer;
|
||||
VAStatus status;
|
||||
|
||||
status = vaGetSurfaceBufferWl(vaDeviceContext->display,
|
||||
surface,
|
||||
VA_FRAME_PICTURE,
|
||||
&buffer);
|
||||
if (status == VA_STATUS_SUCCESS) {
|
||||
wl_surface_attach(m_WaylandSurface, buffer, 0, 0);
|
||||
wl_surface_damage(m_WaylandSurface, dst.x, dst.y, dst.w, dst.h);
|
||||
|
||||
wl_display_flush(m_WaylandDisplay);
|
||||
wl_surface_commit(m_WaylandSurface);
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"vaGetSurfaceBufferWl failed(): %d",
|
||||
status);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
// Greater width per height
|
||||
int drawWidth = (int)(m_DisplayHeight * srcAspectRatio);
|
||||
y = 0;
|
||||
height = m_DisplayHeight;
|
||||
x = (m_DisplayWidth - drawWidth) / 2;
|
||||
width = drawWidth;
|
||||
// We don't accept anything else in initialize().
|
||||
SDL_assert(false);
|
||||
}
|
||||
|
||||
m_vaPutSurface(vaDeviceContext->display,
|
||||
surface,
|
||||
m_XWindow,
|
||||
0, 0,
|
||||
m_VideoWidth, m_VideoHeight,
|
||||
x, y,
|
||||
width, height,
|
||||
NULL, 0, 0);
|
||||
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
@@ -2,49 +2,61 @@
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
// Avoid X11 if SDL was built without it
|
||||
#ifndef SDL_VIDEO_DRIVER_X11
|
||||
#warning Unable to use libva-x11 without SDL support
|
||||
#undef HAVE_LIBVA_X11
|
||||
#endif
|
||||
|
||||
// Avoid Wayland if SDL was built without it
|
||||
#ifndef SDL_VIDEO_DRIVER_WAYLAND
|
||||
#warning Unable to use libva-wayland without SDL support
|
||||
#undef HAVE_LIBVA_WAYLAND
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
#include <va/va.h>
|
||||
#ifdef HAVE_LIBVA_X11
|
||||
#include <va/va_x11.h>
|
||||
#endif
|
||||
#ifdef HAVE_LIBVA_WAYLAND
|
||||
#include <va/va_wayland.h>
|
||||
#endif
|
||||
#include <libavutil/hwcontext_vaapi.h>
|
||||
}
|
||||
|
||||
class VAAPIRenderer : public IFFmpegRenderer
|
||||
{
|
||||
typedef VAStatus (*vaPutSurface_t)(
|
||||
VADisplay dpy,
|
||||
VASurfaceID surface,
|
||||
Drawable draw, /* X Drawable */
|
||||
short srcx,
|
||||
short srcy,
|
||||
unsigned short srcw,
|
||||
unsigned short srch,
|
||||
short destx,
|
||||
short desty,
|
||||
unsigned short destw,
|
||||
unsigned short desth,
|
||||
VARectangle *cliprects,
|
||||
unsigned int number_cliprects,
|
||||
unsigned int flags
|
||||
);
|
||||
|
||||
public:
|
||||
VAAPIRenderer();
|
||||
virtual ~VAAPIRenderer();
|
||||
virtual bool initialize(SDL_Window* window,
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height);
|
||||
int height,
|
||||
int maxFps,
|
||||
bool enableVsync);
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context);
|
||||
virtual void renderFrame(AVFrame* frame);
|
||||
virtual void renderFrameAtVsync(AVFrame* frame);
|
||||
virtual bool needsTestFrame();
|
||||
virtual int getDecoderCapabilities();
|
||||
virtual VSyncConstraint getVsyncConstraint();
|
||||
|
||||
private:
|
||||
Window m_XWindow;
|
||||
int m_WindowSystem;
|
||||
AVBufferRef* m_HwContext;
|
||||
void* m_X11VaLibHandle;
|
||||
vaPutSurface_t m_vaPutSurface;
|
||||
|
||||
#ifdef HAVE_LIBVA_X11
|
||||
Window m_XWindow;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBVA_WAYLAND
|
||||
struct wl_surface* m_WaylandSurface;
|
||||
struct wl_display* m_WaylandDisplay;
|
||||
#endif
|
||||
|
||||
int m_VideoWidth;
|
||||
int m_VideoHeight;
|
||||
int m_DisplayWidth;
|
||||
int m_DisplayHeight;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
#include "vdpau.h"
|
||||
#include <streaming/streamutils.h>
|
||||
|
||||
#include <SDL_syswm.h>
|
||||
|
||||
#define BAIL_ON_FAIL(status, something) if ((status) != VDP_STATUS_OK) { \
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, \
|
||||
#something " failed: %d", (status)); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
#define GET_PROC_ADDRESS(id, func) status = vdpauCtx->get_proc_address(m_Device, id, (void**)func); \
|
||||
BAIL_ON_FAIL(status, id)
|
||||
|
||||
const VdpRGBAFormat VDPAURenderer::k_OutputFormats[] = {
|
||||
VDP_RGBA_FORMAT_B8G8R8A8,
|
||||
VDP_RGBA_FORMAT_R8G8B8A8
|
||||
};
|
||||
|
||||
VDPAURenderer::VDPAURenderer()
|
||||
: m_HwContext(nullptr),
|
||||
m_PresentationQueueTarget(0),
|
||||
m_PresentationQueue(0),
|
||||
m_VideoMixer(0),
|
||||
m_NextSurfaceIndex(0)
|
||||
{
|
||||
SDL_zero(m_OutputSurface);
|
||||
}
|
||||
|
||||
VDPAURenderer::~VDPAURenderer()
|
||||
{
|
||||
if (m_PresentationQueue != 0) {
|
||||
m_VdpPresentationQueueDestroy(m_PresentationQueue);
|
||||
}
|
||||
|
||||
if (m_VideoMixer != 0) {
|
||||
m_VdpVideoMixerDestroy(m_VideoMixer);
|
||||
}
|
||||
|
||||
if (m_PresentationQueueTarget != 0) {
|
||||
m_VdpPresentationQueueTargetDestroy(m_PresentationQueueTarget);
|
||||
}
|
||||
|
||||
for (int i = 0; i < OUTPUT_SURFACE_COUNT; i++) {
|
||||
if (m_OutputSurface[i] != 0) {
|
||||
m_VdpOutputSurfaceDestroy(m_OutputSurface[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// This must be done last as it frees VDPAU context required to call
|
||||
// the functions above.
|
||||
if (m_HwContext != nullptr) {
|
||||
av_buffer_unref(&m_HwContext);
|
||||
}
|
||||
}
|
||||
|
||||
bool VDPAURenderer::initialize(SDL_Window* window, int, int width, int height, int, bool)
|
||||
{
|
||||
int err;
|
||||
VdpStatus status;
|
||||
SDL_SysWMinfo info;
|
||||
|
||||
m_VideoWidth = width;
|
||||
m_VideoHeight = height;
|
||||
|
||||
err = av_hwdevice_ctx_create(&m_HwContext,
|
||||
AV_HWDEVICE_TYPE_VDPAU,
|
||||
nullptr, nullptr, 0);
|
||||
if (err < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create VDPAU context: %d",
|
||||
err);
|
||||
return false;
|
||||
}
|
||||
|
||||
AVHWDeviceContext* devCtx = (AVHWDeviceContext*)m_HwContext->data;
|
||||
AVVDPAUDeviceContext* vdpauCtx = (AVVDPAUDeviceContext*)devCtx->hwctx;
|
||||
m_Device = vdpauCtx->device;
|
||||
|
||||
GET_PROC_ADDRESS(VDP_FUNC_ID_GET_ERROR_STRING, &m_VdpGetErrorString);
|
||||
GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_TARGET_DESTROY, &m_VdpPresentationQueueTargetDestroy);
|
||||
GET_PROC_ADDRESS(VDP_FUNC_ID_VIDEO_MIXER_CREATE, &m_VdpVideoMixerCreate);
|
||||
GET_PROC_ADDRESS(VDP_FUNC_ID_VIDEO_MIXER_DESTROY, &m_VdpVideoMixerDestroy);
|
||||
GET_PROC_ADDRESS(VDP_FUNC_ID_VIDEO_MIXER_RENDER, &m_VdpVideoMixerRender);
|
||||
GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_CREATE, &m_VdpPresentationQueueCreate);
|
||||
GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_DESTROY, &m_VdpPresentationQueueDestroy);
|
||||
GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_DISPLAY, &m_VdpPresentationQueueDisplay);
|
||||
GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_SET_BACKGROUND_COLOR, &m_VdpPresentationQueueSetBackgroundColor);
|
||||
GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_BLOCK_UNTIL_SURFACE_IDLE, &m_VdpPresentationQueueBlockUntilSurfaceIdle);
|
||||
GET_PROC_ADDRESS(VDP_FUNC_ID_OUTPUT_SURFACE_CREATE, &m_VdpOutputSurfaceCreate);
|
||||
GET_PROC_ADDRESS(VDP_FUNC_ID_OUTPUT_SURFACE_DESTROY, &m_VdpOutputSurfaceDestroy);
|
||||
GET_PROC_ADDRESS(VDP_FUNC_ID_OUTPUT_SURFACE_QUERY_CAPABILITIES, &m_VdpOutputSurfaceQueryCapabilities);
|
||||
GET_PROC_ADDRESS(VDP_FUNC_ID_VIDEO_SURFACE_GET_PARAMETERS, &m_VdpVideoSurfaceGetParameters);
|
||||
|
||||
SDL_GetWindowSize(window, (int*)&m_DisplayWidth, (int*)&m_DisplayHeight);
|
||||
|
||||
SDL_VERSION(&info.version);
|
||||
|
||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetWindowWMInfo() failed: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_assert(info.subsystem == SDL_SYSWM_X11);
|
||||
|
||||
if (info.subsystem == SDL_SYSWM_X11) {
|
||||
GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_TARGET_CREATE_X11,
|
||||
&m_VdpPresentationQueueTargetCreateX11);
|
||||
status = m_VdpPresentationQueueTargetCreateX11(m_Device,
|
||||
info.info.x11.window,
|
||||
&m_PresentationQueueTarget);
|
||||
if (status != VDP_STATUS_OK) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VdpPresentationQueueTargetCreateX11() failed: %s",
|
||||
m_VdpGetErrorString(status));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (info.subsystem == SDL_SYSWM_WAYLAND) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VDPAU backend does not currently support Wayland");
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unsupported VDPAU rendering subsystem: %d",
|
||||
info.subsystem);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try our available output formats to find something the GPU supports
|
||||
bool foundFormat = false;
|
||||
for (int i = 0; i < OUTPUT_SURFACE_FORMAT_COUNT; i++) {
|
||||
VdpBool supported;
|
||||
uint32_t maxWidth, maxHeight;
|
||||
status = m_VdpOutputSurfaceQueryCapabilities(m_Device, k_OutputFormats[i],
|
||||
&supported, &maxWidth, &maxHeight);
|
||||
if (status != VDP_STATUS_OK) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VdpOutputSurfaceQueryCapabilities() failed: %s",
|
||||
m_VdpGetErrorString(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (supported) {
|
||||
if (m_DisplayWidth <= maxWidth && m_DisplayHeight <= maxHeight) {
|
||||
m_OutputSurfaceFormat = k_OutputFormats[i];
|
||||
foundFormat = true;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Display size not within capabilities %dx%d vs %dx%d",
|
||||
m_DisplayWidth, m_DisplayWidth,
|
||||
maxWidth, maxHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundFormat) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"No compatible output surface format found!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the output surfaces
|
||||
for (int i = 0; i < OUTPUT_SURFACE_COUNT; i++) {
|
||||
// It seems there's some lazy freeing going on or something in VDPAU
|
||||
// because we can get VDP_STATUS_RESOURCES, then wait a bit and it'll
|
||||
// complete without a problem.
|
||||
int tries = 1;
|
||||
do {
|
||||
status = m_VdpOutputSurfaceCreate(m_Device, m_OutputSurfaceFormat,
|
||||
m_DisplayWidth, m_DisplayHeight,
|
||||
&m_OutputSurface[i]);
|
||||
if (status != VDP_STATUS_OK) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VdpOutputSurfaceCreate() try #%d: %s",
|
||||
tries,
|
||||
m_VdpGetErrorString(status));
|
||||
SDL_Delay(250);
|
||||
}
|
||||
} while (status == VDP_STATUS_RESOURCES && ++tries <= 10);
|
||||
|
||||
if (status != VDP_STATUS_OK) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VdpOutputSurfaceCreate() failed: %s",
|
||||
m_VdpGetErrorString(status));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
status = m_VdpPresentationQueueCreate(m_Device, m_PresentationQueueTarget,
|
||||
&m_PresentationQueue);
|
||||
if (status != VDP_STATUS_OK) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VdpPresentationQueueCreate() failed: %s",
|
||||
m_VdpGetErrorString(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the background to opaque black
|
||||
VdpColor color = {0.0, 0.0, 0.0, 1.0};
|
||||
m_VdpPresentationQueueSetBackgroundColor(m_PresentationQueue, &color);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VDPAURenderer::prepareDecoderContext(AVCodecContext* context)
|
||||
{
|
||||
context->hw_device_ctx = av_buffer_ref(m_HwContext);
|
||||
|
||||
// Allow HEVC usage on VDPAU. This was disabled by FFmpeg due to
|
||||
// GL interop issues, but we use VDPAU for rendering so it's no issue.
|
||||
// https://github.com/FFmpeg/FFmpeg/commit/64ecb78b7179cab2dbdf835463104679dbb7c895
|
||||
context->hwaccel_flags |= AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH;
|
||||
|
||||
// This flag is recommended due to hardware underreporting supported levels
|
||||
context->hwaccel_flags |= AV_HWACCEL_FLAG_IGNORE_LEVEL;
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Using VDPAU accelerated renderer");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VDPAURenderer::needsTestFrame()
|
||||
{
|
||||
// We need a test frame to see if this VDPAU driver
|
||||
// supports the profile used for streaming
|
||||
return true;
|
||||
}
|
||||
|
||||
int VDPAURenderer::getDecoderCapabilities()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
IFFmpegRenderer::VSyncConstraint VDPAURenderer::getVsyncConstraint()
|
||||
{
|
||||
return VSYNC_ANY;
|
||||
}
|
||||
|
||||
void VDPAURenderer::renderFrameAtVsync(AVFrame* frame)
|
||||
{
|
||||
VdpStatus status;
|
||||
VdpVideoSurface videoSurface = (VdpVideoSurface)(uintptr_t)frame->data[3];
|
||||
|
||||
// This is safe without locking because this is always called on the main thread
|
||||
VdpOutputSurface chosenSurface = m_OutputSurface[m_NextSurfaceIndex];
|
||||
m_NextSurfaceIndex = (m_NextSurfaceIndex + 1) % OUTPUT_SURFACE_COUNT;
|
||||
|
||||
// We need to create the mixer on the fly, because we don't know the dimensions
|
||||
// of our video surfaces in advance of decoding
|
||||
if (m_VideoMixer == 0) {
|
||||
VdpChromaType videoSurfaceChroma;
|
||||
uint32_t videoSurfaceWidth, videoSurfaceHeight;
|
||||
status = m_VdpVideoSurfaceGetParameters(videoSurface, &videoSurfaceChroma,
|
||||
&videoSurfaceWidth, &videoSurfaceHeight);
|
||||
if (status != VDP_STATUS_OK) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VdpVideoSurfaceGetParameters() failed: %s",
|
||||
m_VdpGetErrorString(status));
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VDPAU surface size: %dx%d",
|
||||
videoSurfaceWidth, videoSurfaceHeight);
|
||||
|
||||
#define PARAM_COUNT 3
|
||||
const VdpVideoMixerParameter params[PARAM_COUNT] = {
|
||||
VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH,
|
||||
VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT,
|
||||
VDP_VIDEO_MIXER_PARAMETER_CHROMA_TYPE,
|
||||
};
|
||||
const void* const paramValues[PARAM_COUNT] = {
|
||||
&videoSurfaceWidth,
|
||||
&videoSurfaceHeight,
|
||||
&videoSurfaceChroma,
|
||||
};
|
||||
|
||||
status = m_VdpVideoMixerCreate(m_Device, 0, nullptr,
|
||||
PARAM_COUNT, params, paramValues,
|
||||
&m_VideoMixer);
|
||||
if (status != VDP_STATUS_OK) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VdpVideoMixerCreate() failed: %s",
|
||||
m_VdpGetErrorString(status));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for this frame to be off the screen
|
||||
VdpTime pts;
|
||||
m_VdpPresentationQueueBlockUntilSurfaceIdle(m_PresentationQueue, chosenSurface, &pts);
|
||||
|
||||
VdpRect sourceRect, outputRect;
|
||||
|
||||
SDL_Rect src, dst;
|
||||
src.x = src.y = 0;
|
||||
src.w = m_VideoWidth;
|
||||
src.h = m_VideoHeight;
|
||||
dst.x = dst.y = 0;
|
||||
dst.w = m_DisplayWidth;
|
||||
dst.h = m_DisplayHeight;
|
||||
|
||||
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
||||
|
||||
outputRect.x0 = dst.x;
|
||||
outputRect.x1 = dst.x + dst.w;
|
||||
outputRect.y0 = dst.y;
|
||||
outputRect.y1 = dst.y + dst.h;
|
||||
|
||||
sourceRect.x0 = sourceRect.y0 = 0;
|
||||
sourceRect.x1 = m_VideoWidth;
|
||||
sourceRect.y1 = m_VideoHeight;
|
||||
|
||||
// Render the next frame into the output surface
|
||||
status = m_VdpVideoMixerRender(m_VideoMixer,
|
||||
VDP_INVALID_HANDLE, nullptr,
|
||||
VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME,
|
||||
0, nullptr,
|
||||
videoSurface,
|
||||
0, nullptr,
|
||||
&sourceRect,
|
||||
chosenSurface,
|
||||
&outputRect,
|
||||
nullptr,
|
||||
0,
|
||||
nullptr);
|
||||
if (status != VDP_STATUS_OK) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VdpVideoMixerRender() failed: %s",
|
||||
m_VdpGetErrorString(status));
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue the frame for display immediately
|
||||
status = m_VdpPresentationQueueDisplay(m_PresentationQueue, chosenSurface, 0, 0, 0);
|
||||
if (status != VDP_STATUS_OK) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VdpPresentationQueueDisplay() failed: %s",
|
||||
m_VdpGetErrorString(status));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
extern "C" {
|
||||
#include <vdpau/vdpau.h>
|
||||
#include <vdpau/vdpau_x11.h>
|
||||
#include <libavutil/hwcontext_vdpau.h>
|
||||
}
|
||||
|
||||
class VDPAURenderer : public IFFmpegRenderer
|
||||
{
|
||||
public:
|
||||
VDPAURenderer();
|
||||
virtual ~VDPAURenderer();
|
||||
virtual bool initialize(SDL_Window* window,
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height,
|
||||
int maxFps,
|
||||
bool enableVsync);
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context);
|
||||
virtual void renderFrameAtVsync(AVFrame* frame);
|
||||
virtual bool needsTestFrame();
|
||||
virtual int getDecoderCapabilities();
|
||||
virtual VSyncConstraint getVsyncConstraint();
|
||||
|
||||
private:
|
||||
uint32_t m_VideoWidth, m_VideoHeight;
|
||||
uint32_t m_DisplayWidth, m_DisplayHeight;
|
||||
AVBufferRef* m_HwContext;
|
||||
VdpPresentationQueueTarget m_PresentationQueueTarget;
|
||||
VdpPresentationQueue m_PresentationQueue;
|
||||
VdpVideoMixer m_VideoMixer;
|
||||
VdpRGBAFormat m_OutputSurfaceFormat;
|
||||
VdpDevice m_Device;
|
||||
|
||||
#define OUTPUT_SURFACE_COUNT 3
|
||||
VdpOutputSurface m_OutputSurface[OUTPUT_SURFACE_COUNT];
|
||||
int m_NextSurfaceIndex;
|
||||
|
||||
#define OUTPUT_SURFACE_FORMAT_COUNT 2
|
||||
static const VdpRGBAFormat k_OutputFormats[OUTPUT_SURFACE_FORMAT_COUNT];
|
||||
|
||||
VdpGetErrorString* m_VdpGetErrorString;
|
||||
VdpPresentationQueueTargetDestroy* m_VdpPresentationQueueTargetDestroy;
|
||||
VdpVideoMixerCreate* m_VdpVideoMixerCreate;
|
||||
VdpVideoMixerDestroy* m_VdpVideoMixerDestroy;
|
||||
VdpVideoMixerRender* m_VdpVideoMixerRender;
|
||||
VdpPresentationQueueCreate* m_VdpPresentationQueueCreate;
|
||||
VdpPresentationQueueDestroy* m_VdpPresentationQueueDestroy;
|
||||
VdpPresentationQueueDisplay* m_VdpPresentationQueueDisplay;
|
||||
VdpPresentationQueueSetBackgroundColor* m_VdpPresentationQueueSetBackgroundColor;
|
||||
VdpPresentationQueueBlockUntilSurfaceIdle* m_VdpPresentationQueueBlockUntilSurfaceIdle;
|
||||
VdpOutputSurfaceCreate* m_VdpOutputSurfaceCreate;
|
||||
VdpOutputSurfaceDestroy* m_VdpOutputSurfaceDestroy;
|
||||
VdpOutputSurfaceQueryCapabilities* m_VdpOutputSurfaceQueryCapabilities;
|
||||
VdpVideoSurfaceGetParameters* m_VdpVideoSurfaceGetParameters;
|
||||
|
||||
// X11 stuff
|
||||
VdpPresentationQueueTargetCreateX11* m_VdpPresentationQueueTargetCreateX11;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,19 +2,16 @@
|
||||
// libavutil both defining AVMediaType
|
||||
#define AVMediaType AVMediaType_FFmpeg
|
||||
#include "vt.h"
|
||||
#include "pacer/pacer.h"
|
||||
#undef AVMediaType
|
||||
|
||||
#include <SDL_syswm.h>
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <QQueue>
|
||||
|
||||
#include <mach/mach_time.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <VideoToolbox/VideoToolbox.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <CoreVideo/CoreVideo.h>
|
||||
|
||||
#define FRAME_HISTORY_ENTRIES 8
|
||||
|
||||
class VTRenderer : public IFFmpegRenderer
|
||||
{
|
||||
@@ -23,9 +20,7 @@ public:
|
||||
: m_HwContext(nullptr),
|
||||
m_DisplayLayer(nullptr),
|
||||
m_FormatDesc(nullptr),
|
||||
m_View(nullptr),
|
||||
m_DisplayLink(nullptr),
|
||||
m_FrameQueueLock(0)
|
||||
m_View(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -39,63 +34,28 @@ public:
|
||||
CFRelease(m_FormatDesc);
|
||||
}
|
||||
|
||||
if (m_DisplayLink != nullptr) {
|
||||
CVDisplayLinkStop(m_DisplayLink);
|
||||
CVDisplayLinkRelease(m_DisplayLink);
|
||||
}
|
||||
|
||||
while (!m_FrameQueue.isEmpty()) {
|
||||
AVFrame* frame = m_FrameQueue.dequeue();
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
if (m_View != nullptr) {
|
||||
[m_View removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
void drawFrame(uint64_t vsyncTime)
|
||||
// Caller frees frame after we return
|
||||
virtual void renderFrameAtVsync(AVFrame* frame) override
|
||||
{
|
||||
OSStatus status;
|
||||
CVPixelBufferRef pixBuf = reinterpret_cast<CVPixelBufferRef>(frame->data[3]);
|
||||
|
||||
SDL_AtomicLock(&m_FrameQueueLock);
|
||||
if (m_DisplayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Resetting failed AVSampleBufferDisplay layer");
|
||||
|
||||
int frameDropTarget;
|
||||
|
||||
// If the queue length history entries are large, be strict
|
||||
// about dropping excess frames.
|
||||
frameDropTarget = 1;
|
||||
for (int i = 0; i < m_FrameQueueHistory.count(); i++) {
|
||||
if (m_FrameQueueHistory[i] <= 1) {
|
||||
// Be lenient as long as the queue length
|
||||
// resolves before the end of frame history
|
||||
frameDropTarget = 3;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_FrameQueueHistory.count() == FRAME_HISTORY_ENTRIES) {
|
||||
m_FrameQueueHistory.dequeue();
|
||||
}
|
||||
|
||||
m_FrameQueueHistory.enqueue(m_FrameQueue.count());
|
||||
|
||||
// Catch up if we're several frames ahead
|
||||
while (m_FrameQueue.count() > frameDropTarget) {
|
||||
AVFrame* frame = m_FrameQueue.dequeue();
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
if (m_FrameQueue.isEmpty()) {
|
||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
||||
// Trigger the main thread to recreate the decoder
|
||||
SDL_Event event;
|
||||
event.type = SDL_RENDER_TARGETS_RESET;
|
||||
SDL_PushEvent(&event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab the first frame
|
||||
AVFrame* frame = m_FrameQueue.dequeue();
|
||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
||||
|
||||
CVPixelBufferRef pixBuf = reinterpret_cast<CVPixelBufferRef>(frame->data[3]);
|
||||
|
||||
// If the format has changed or doesn't exist yet, construct it with the
|
||||
// pixel buffer data
|
||||
if (!m_FormatDesc || !CMVideoFormatDescriptionMatchesImageBuffer(m_FormatDesc, pixBuf)) {
|
||||
@@ -108,7 +68,6 @@ public:
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"CMVideoFormatDescriptionCreateForImageBuffer() failed: %d",
|
||||
status);
|
||||
av_frame_free(&frame);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -117,7 +76,7 @@ public:
|
||||
CMSampleTimingInfo timingInfo = {
|
||||
.duration = kCMTimeInvalid,
|
||||
.decodeTimeStamp = kCMTimeInvalid,
|
||||
.presentationTimeStamp = CMTimeMake(vsyncTime, 1000 * 1000 * 1000)
|
||||
.presentationTimeStamp = CMTimeMake(mach_absolute_time(), 1000 * 1000 * 1000)
|
||||
};
|
||||
|
||||
CMSampleBufferRef sampleBuffer;
|
||||
@@ -130,45 +89,20 @@ public:
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"CMSampleBufferCreateReadyWithImageBuffer() failed: %d",
|
||||
status);
|
||||
av_frame_free(&frame);
|
||||
return;
|
||||
}
|
||||
|
||||
[m_DisplayLayer enqueueSampleBuffer:sampleBuffer];
|
||||
|
||||
CFRelease(sampleBuffer);
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
static
|
||||
CVReturn
|
||||
displayLinkOutputCallback(
|
||||
CVDisplayLinkRef,
|
||||
const CVTimeStamp* now,
|
||||
const CVTimeStamp* /* vsyncTime */,
|
||||
CVOptionFlags,
|
||||
CVOptionFlags*,
|
||||
void *displayLinkContext)
|
||||
{
|
||||
VTRenderer* me = reinterpret_cast<VTRenderer*>(displayLinkContext);
|
||||
|
||||
// In my testing on macOS 10.13, this callback is invoked about 24 ms
|
||||
// prior to the specified v-sync time (now - vsyncTime). Since this is
|
||||
// greater than the standard v-sync interval (16 ms = 60 FPS), we will
|
||||
// draw using the current host time, rather than the actual v-sync target
|
||||
// time. Because the CVDisplayLink is in sync with the actual v-sync
|
||||
// interval, even if many ms prior, we can safely use the current host time
|
||||
// and get a consistent callback for each v-sync. This reduces video latency
|
||||
// by at least 1 frame vs. rendering with the actual vsyncTime.
|
||||
me->drawFrame(now->hostTime);
|
||||
|
||||
return kCVReturnSuccess;
|
||||
}
|
||||
|
||||
virtual bool initialize(SDL_Window* window,
|
||||
int videoFormat,
|
||||
int,
|
||||
int) override
|
||||
int,
|
||||
int,
|
||||
bool) override
|
||||
{
|
||||
int err;
|
||||
|
||||
@@ -243,30 +177,36 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
CVDisplayLinkCreateWithActiveCGDisplays(&m_DisplayLink);
|
||||
CVDisplayLinkSetOutputCallback(m_DisplayLink, displayLinkOutputCallback, this);
|
||||
CVDisplayLinkStart(m_DisplayLink);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context) override
|
||||
{
|
||||
context->hw_device_ctx = av_buffer_ref(m_HwContext);
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Using VideoToolbox accelerated renderer");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void renderFrame(AVFrame* frame) override
|
||||
virtual bool needsTestFrame() override
|
||||
{
|
||||
if (m_DisplayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Resetting failed AVSampleBufferDisplay layer");
|
||||
setupDisplayLayer();
|
||||
}
|
||||
// We query VT to determine whether the codec is supported
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_AtomicLock(&m_FrameQueueLock);
|
||||
m_FrameQueue.enqueue(frame);
|
||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
||||
virtual int getDecoderCapabilities() override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual IFFmpegRenderer::VSyncConstraint getVsyncConstraint() override
|
||||
{
|
||||
// This renderer is inherently tied to V-sync due how we're
|
||||
// rendering with AVSampleBufferDisplay layer. Running without
|
||||
// the V-Sync source leads to massive stuttering.
|
||||
return VSYNC_FORCE_ON;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -292,10 +232,6 @@ private:
|
||||
AVSampleBufferDisplayLayer* m_DisplayLayer;
|
||||
CMVideoFormatDescriptionRef m_FormatDesc;
|
||||
NSView* m_View;
|
||||
CVDisplayLinkRef m_DisplayLink;
|
||||
QQueue<AVFrame*> m_FrameQueue;
|
||||
QQueue<int> m_FrameQueueHistory;
|
||||
SDL_SpinLock m_FrameQueueLock;
|
||||
};
|
||||
|
||||
IFFmpegRenderer* VTRendererFactory::createRenderer() {
|
||||
|
||||
+276
-97
@@ -1,5 +1,6 @@
|
||||
#include <Limelight.h>
|
||||
#include "ffmpeg.h"
|
||||
#include "streaming/streamutils.h"
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#include "ffmpeg-renderers/dxva2.h"
|
||||
@@ -13,98 +14,53 @@
|
||||
#include "ffmpeg-renderers/vaapi.h"
|
||||
#endif
|
||||
|
||||
bool FFmpegVideoDecoder::chooseDecoder(
|
||||
StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window* window,
|
||||
int videoFormat,
|
||||
int width, int height,
|
||||
AVCodec*& chosenDecoder,
|
||||
const AVCodecHWConfig*& chosenHwConfig,
|
||||
IFFmpegRenderer*& newRenderer)
|
||||
{
|
||||
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||
chosenDecoder = avcodec_find_decoder(AV_CODEC_ID_H264);
|
||||
}
|
||||
else if (videoFormat & VIDEO_FORMAT_MASK_H265) {
|
||||
chosenDecoder = avcodec_find_decoder(AV_CODEC_ID_HEVC);
|
||||
}
|
||||
else {
|
||||
Q_ASSERT(false);
|
||||
chosenDecoder = nullptr;
|
||||
}
|
||||
|
||||
if (!chosenDecoder) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to find decoder for format: %x",
|
||||
videoFormat);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0;; i++) {
|
||||
const AVCodecHWConfig *config = avcodec_get_hw_config(chosenDecoder, i);
|
||||
if (!config || vds == StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
||||
// No matching hardware acceleration support.
|
||||
// This is not an error.
|
||||
chosenHwConfig = nullptr;
|
||||
newRenderer = new SdlRenderer();
|
||||
if (vds != StreamingPreferences::VDS_FORCE_HARDWARE &&
|
||||
newRenderer->initialize(window, videoFormat, width, height)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
delete newRenderer;
|
||||
newRenderer = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for acceleration types we support
|
||||
switch (config->device_type) {
|
||||
#ifdef Q_OS_WIN32
|
||||
case AV_HWDEVICE_TYPE_DXVA2:
|
||||
newRenderer = new DXVA2Renderer();
|
||||
break;
|
||||
#ifdef HAVE_LIBVDPAU
|
||||
#include "ffmpeg-renderers/vdpau.h"
|
||||
#endif
|
||||
#ifdef Q_OS_DARWIN
|
||||
case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
|
||||
newRenderer = VTRendererFactory::createRenderer();
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_LIBVA
|
||||
case AV_HWDEVICE_TYPE_VAAPI:
|
||||
newRenderer = new VAAPIRenderer();
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
if (newRenderer->initialize(window, videoFormat, width, height)) {
|
||||
chosenHwConfig = config;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// Failed to initialize
|
||||
delete newRenderer;
|
||||
newRenderer = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
// This is gross but it allows us to use sizeof()
|
||||
#include "ffmpeg_videosamples.cpp"
|
||||
|
||||
#define FAILED_DECODES_RESET_THRESHOLD 20
|
||||
|
||||
bool FFmpegVideoDecoder::isHardwareAccelerated()
|
||||
{
|
||||
return m_HwDecodeCfg != nullptr;
|
||||
}
|
||||
|
||||
int FFmpegVideoDecoder::getDecoderCapabilities()
|
||||
{
|
||||
return m_Renderer->getDecoderCapabilities();
|
||||
}
|
||||
|
||||
enum AVPixelFormat FFmpegVideoDecoder::ffGetFormat(AVCodecContext* context,
|
||||
const enum AVPixelFormat* pixFmts)
|
||||
{
|
||||
FFmpegVideoDecoder* decoder = (FFmpegVideoDecoder*)context->opaque;
|
||||
const enum AVPixelFormat *p;
|
||||
|
||||
for (p = pixFmts; *p != -1; p++) {
|
||||
// Only match our hardware decoding codec or SW pixel
|
||||
// format (if not using hardware decoding). It's crucial
|
||||
// to override the default get_format() which will try
|
||||
// to gracefully fall back to software decode and break us.
|
||||
if (*p == (decoder->m_HwDecodeCfg ?
|
||||
decoder->m_HwDecodeCfg->pix_fmt :
|
||||
context->pix_fmt)) {
|
||||
return *p;
|
||||
}
|
||||
}
|
||||
|
||||
return AV_PIX_FMT_NONE;
|
||||
}
|
||||
|
||||
FFmpegVideoDecoder::FFmpegVideoDecoder()
|
||||
: m_VideoDecoderCtx(nullptr),
|
||||
m_DecodeBuffer(1024 * 1024, 0),
|
||||
m_HwDecodeCfg(nullptr),
|
||||
m_Renderer(nullptr)
|
||||
m_Renderer(nullptr),
|
||||
m_ConsecutiveFailedDecodes(0),
|
||||
m_Pacer(nullptr)
|
||||
{
|
||||
av_init_packet(&m_Pkt);
|
||||
SDL_AtomicSet(&m_QueuedFrames, 0);
|
||||
@@ -114,6 +70,16 @@ FFmpegVideoDecoder::FFmpegVideoDecoder()
|
||||
}
|
||||
|
||||
FFmpegVideoDecoder::~FFmpegVideoDecoder()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
IFFmpegRenderer* FFmpegVideoDecoder::getRenderer()
|
||||
{
|
||||
return m_Renderer;
|
||||
}
|
||||
|
||||
void FFmpegVideoDecoder::reset()
|
||||
{
|
||||
// Drop any frames still queued to ensure
|
||||
// they are properly freed.
|
||||
@@ -132,29 +98,47 @@ FFmpegVideoDecoder::~FFmpegVideoDecoder()
|
||||
dropFrame(&event.user);
|
||||
}
|
||||
else {
|
||||
SDL_Delay(100);
|
||||
SDL_Delay(10);
|
||||
SDL_PumpEvents();
|
||||
}
|
||||
}
|
||||
|
||||
delete m_Pacer;
|
||||
m_Pacer = nullptr;
|
||||
|
||||
// This must be called after deleting Pacer because it
|
||||
// may be holding AVFrames to free in its destructor.
|
||||
// However, it must be called before deleting the IFFmpegRenderer
|
||||
// since the codec context may be referencing objects that we
|
||||
// need to delete in the renderer destructor.
|
||||
avcodec_free_context(&m_VideoDecoderCtx);
|
||||
|
||||
delete m_Renderer;
|
||||
m_Renderer = nullptr;
|
||||
}
|
||||
|
||||
bool FFmpegVideoDecoder::initialize(
|
||||
StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window* window,
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height,
|
||||
int)
|
||||
bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, SDL_Window* window,
|
||||
int videoFormat, int width, int height,
|
||||
int maxFps, bool enableVsync, bool testOnly)
|
||||
{
|
||||
AVCodec* decoder;
|
||||
auto vsyncConstraint = m_Renderer->getVsyncConstraint();
|
||||
if (vsyncConstraint == IFFmpegRenderer::VSYNC_FORCE_OFF && enableVsync) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"V-sync is forcefully disabled by the active renderer");
|
||||
enableVsync = false;
|
||||
}
|
||||
else if (vsyncConstraint == IFFmpegRenderer::VSYNC_FORCE_ON && !enableVsync) {
|
||||
// FIXME: This duplicates logic in Session.cpp
|
||||
int displayHz = StreamUtils::getDisplayRefreshRate(window);
|
||||
if (displayHz + 5 >= maxFps) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"V-sync is forcefully enabled by the active renderer");
|
||||
enableVsync = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!chooseDecoder(vds, window, videoFormat, width, height,
|
||||
decoder, m_HwDecodeCfg, m_Renderer)) {
|
||||
// Error logged in chooseDecoder()
|
||||
m_Pacer = new Pacer(m_Renderer);
|
||||
if (!m_Pacer->initialize(window, maxFps, enableVsync)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -168,6 +152,10 @@ bool FFmpegVideoDecoder::initialize(
|
||||
// Always request low delay decoding
|
||||
m_VideoDecoderCtx->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
||||
|
||||
// Allow display of corrupt frames and frames missing references
|
||||
m_VideoDecoderCtx->flags |= AV_CODEC_FLAG_OUTPUT_CORRUPT;
|
||||
m_VideoDecoderCtx->flags2 |= AV_CODEC_FLAG2_SHOW_ALL;
|
||||
|
||||
// Enable slice multi-threading for software decoding
|
||||
if (!m_HwDecodeCfg) {
|
||||
m_VideoDecoderCtx->thread_type = FF_THREAD_SLICE;
|
||||
@@ -182,12 +170,20 @@ bool FFmpegVideoDecoder::initialize(
|
||||
m_VideoDecoderCtx->width = width;
|
||||
m_VideoDecoderCtx->height = height;
|
||||
m_VideoDecoderCtx->pix_fmt = AV_PIX_FMT_YUV420P; // FIXME: HDR
|
||||
m_VideoDecoderCtx->get_format = ffGetFormat;
|
||||
|
||||
// Allow the renderer to attach data to this decoder
|
||||
if (!m_Renderer->prepareDecoderContext(m_VideoDecoderCtx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Nobody must override our ffGetFormat
|
||||
SDL_assert(m_VideoDecoderCtx->get_format == ffGetFormat);
|
||||
|
||||
// Stash a pointer to this object in the context
|
||||
SDL_assert(m_VideoDecoderCtx->opaque == nullptr);
|
||||
m_VideoDecoderCtx->opaque = this;
|
||||
|
||||
int err = avcodec_open2(m_VideoDecoderCtx, decoder, nullptr);
|
||||
if (err < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -196,9 +192,155 @@ bool FFmpegVideoDecoder::initialize(
|
||||
return false;
|
||||
}
|
||||
|
||||
// FFMpeg doesn't completely initialize the codec until the codec
|
||||
// config data comes in. This would be too late for us to change
|
||||
// our minds on the selected video codec, so we'll do a trial run
|
||||
// now to see if things will actually work when the video stream
|
||||
// comes in.
|
||||
if (testOnly) {
|
||||
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||
m_Pkt.data = (uint8_t*)k_H264TestFrame;
|
||||
m_Pkt.size = sizeof(k_H264TestFrame);
|
||||
}
|
||||
else {
|
||||
m_Pkt.data = (uint8_t*)k_HEVCTestFrame;
|
||||
m_Pkt.size = sizeof(k_HEVCTestFrame);
|
||||
}
|
||||
|
||||
err = avcodec_send_packet(m_VideoDecoderCtx, &m_Pkt);
|
||||
if (err < 0) {
|
||||
char errorstring[512];
|
||||
av_strerror(err, errorstring, sizeof(errorstring));
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Test decode failed: %s", errorstring);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
// Restore default log level before streaming
|
||||
av_log_set_level(AV_LOG_INFO);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IFFmpegRenderer* FFmpegVideoDecoder::createAcceleratedRenderer(const AVCodecHWConfig* hwDecodeCfg)
|
||||
{
|
||||
if (!(hwDecodeCfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Look for acceleration types we support
|
||||
switch (hwDecodeCfg->device_type) {
|
||||
#ifdef Q_OS_WIN32
|
||||
case AV_HWDEVICE_TYPE_DXVA2:
|
||||
return new DXVA2Renderer();
|
||||
#endif
|
||||
#ifdef Q_OS_DARWIN
|
||||
case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
|
||||
return VTRendererFactory::createRenderer();
|
||||
#endif
|
||||
#ifdef HAVE_LIBVA
|
||||
case AV_HWDEVICE_TYPE_VAAPI:
|
||||
return new VAAPIRenderer();
|
||||
#endif
|
||||
#ifdef HAVE_LIBVDPAU
|
||||
case AV_HWDEVICE_TYPE_VDPAU:
|
||||
return new VDPAURenderer();
|
||||
#endif
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool FFmpegVideoDecoder::initialize(
|
||||
StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window* window,
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height,
|
||||
int maxFps,
|
||||
bool enableVsync)
|
||||
{
|
||||
AVCodec* decoder;
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
// Increase log level during initialization
|
||||
av_log_set_level(AV_LOG_DEBUG);
|
||||
#endif
|
||||
|
||||
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||
decoder = avcodec_find_decoder(AV_CODEC_ID_H264);
|
||||
}
|
||||
else if (videoFormat & VIDEO_FORMAT_MASK_H265) {
|
||||
decoder = avcodec_find_decoder(AV_CODEC_ID_HEVC);
|
||||
}
|
||||
else {
|
||||
Q_ASSERT(false);
|
||||
decoder = nullptr;
|
||||
}
|
||||
|
||||
if (!decoder) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to find decoder for format: %x",
|
||||
videoFormat);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0;; i++) {
|
||||
const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
|
||||
if (!config || vds == StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
||||
// No matching hardware acceleration support.
|
||||
// This is not an error.
|
||||
m_HwDecodeCfg = nullptr;
|
||||
m_Renderer = new SdlRenderer();
|
||||
if (vds != StreamingPreferences::VDS_FORCE_HARDWARE &&
|
||||
m_Renderer->initialize(window, videoFormat, width, height, maxFps, enableVsync) &&
|
||||
completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableVsync, false)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_Renderer = createAcceleratedRenderer(config);
|
||||
if (!m_Renderer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_HwDecodeCfg = config;
|
||||
// Initialize the hardware codec and submit a test frame if the renderer needs it
|
||||
if (m_Renderer->initialize(window, videoFormat, width, height, maxFps, enableVsync) &&
|
||||
completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableVsync, m_Renderer->needsTestFrame())) {
|
||||
if (m_Renderer->needsTestFrame()) {
|
||||
// The test worked, so now let's initialize it for real
|
||||
reset();
|
||||
if ((m_Renderer = createAcceleratedRenderer(config)) != nullptr &&
|
||||
m_Renderer->initialize(window, videoFormat, width, height, maxFps, enableVsync) &&
|
||||
completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableVsync, false)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Decoder failed to initialize after successful test");
|
||||
reset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No test required. Good to go now.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Failed to initialize or test frame failed, so keep looking
|
||||
reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
||||
{
|
||||
PLENTRY entry = du->bufferList;
|
||||
@@ -224,12 +366,23 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
||||
|
||||
err = avcodec_send_packet(m_VideoDecoderCtx, &m_Pkt);
|
||||
if (err < 0) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Decoding failed: %d", err);
|
||||
char errorstring[512];
|
||||
av_strerror(err, errorstring, sizeof(errorstring));
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Decoding failed: %s", errorstring);
|
||||
"avcodec_send_packet() failed: %s", errorstring);
|
||||
|
||||
// If we've failed a bunch of decodes in a row, the decoder/renderer is
|
||||
// clearly unhealthy, so let's generate a synthetic reset event to trigger
|
||||
// the event loop to destroy and recreate the decoder.
|
||||
if (++m_ConsecutiveFailedDecodes == FAILED_DECODES_RESET_THRESHOLD) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Resetting decoder due to consistent failure");
|
||||
|
||||
SDL_Event event;
|
||||
event.type = SDL_RENDER_DEVICE_RESET;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
|
||||
@@ -244,12 +397,29 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
||||
|
||||
err = avcodec_receive_frame(m_VideoDecoderCtx, frame);
|
||||
if (err == 0) {
|
||||
// Reset failed decodes count if we reached this far
|
||||
m_ConsecutiveFailedDecodes = 0;
|
||||
|
||||
// Queue the frame for rendering from the main thread
|
||||
SDL_AtomicIncRef(&m_QueuedFrames);
|
||||
queueFrame(frame);
|
||||
}
|
||||
else {
|
||||
av_frame_free(&frame);
|
||||
|
||||
char errorstring[512];
|
||||
av_strerror(err, errorstring, sizeof(errorstring));
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"avcodec_receive_frame() failed: %s", errorstring);
|
||||
|
||||
if (++m_ConsecutiveFailedDecodes == FAILED_DECODES_RESET_THRESHOLD) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Resetting decoder due to consistent failure");
|
||||
|
||||
SDL_Event event;
|
||||
event.type = SDL_RENDER_DEVICE_RESET;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
}
|
||||
|
||||
return DR_OK;
|
||||
@@ -259,7 +429,7 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
||||
void FFmpegVideoDecoder::renderFrame(SDL_UserEvent* event)
|
||||
{
|
||||
AVFrame* frame = reinterpret_cast<AVFrame*>(event->data1);
|
||||
m_Renderer->renderFrame(frame);
|
||||
m_Pacer->submitFrame(frame);
|
||||
SDL_AtomicDecRef(&m_QueuedFrames);
|
||||
}
|
||||
|
||||
@@ -267,6 +437,15 @@ void FFmpegVideoDecoder::renderFrame(SDL_UserEvent* event)
|
||||
void FFmpegVideoDecoder::dropFrame(SDL_UserEvent* event)
|
||||
{
|
||||
AVFrame* frame = reinterpret_cast<AVFrame*>(event->data1);
|
||||
av_frame_free(&frame);
|
||||
// If Pacer is using a frame queue, we can just queue the
|
||||
// frame and let it decide whether to drop or not. If Pacer
|
||||
// is submitting directly for rendering, we drop the frame
|
||||
// ourselves.
|
||||
if (m_Pacer->isUsingFrameQueue()) {
|
||||
m_Pacer->submitFrame(frame);
|
||||
}
|
||||
else {
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
SDL_AtomicDecRef(&m_QueuedFrames);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "decoder.h"
|
||||
#include "ffmpeg-renderers/renderer.h"
|
||||
#include "ffmpeg-renderers/pacer/pacer.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
@@ -16,22 +17,28 @@ public:
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height,
|
||||
int frameRate) override;
|
||||
int maxFps,
|
||||
bool enableVsync) override;
|
||||
virtual bool isHardwareAccelerated() override;
|
||||
virtual int getDecoderCapabilities() override;
|
||||
virtual int submitDecodeUnit(PDECODE_UNIT du) override;
|
||||
virtual void renderFrame(SDL_UserEvent* event) override;
|
||||
virtual void dropFrame(SDL_UserEvent* event) override;
|
||||
|
||||
virtual IFFmpegRenderer* getRenderer();
|
||||
|
||||
private:
|
||||
bool
|
||||
chooseDecoder(
|
||||
StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window* window,
|
||||
int videoFormat,
|
||||
int width, int height,
|
||||
AVCodec*& chosenDecoder,
|
||||
const AVCodecHWConfig*& chosenHwConfig,
|
||||
IFFmpegRenderer*& newRenderer);
|
||||
bool completeInitialization(AVCodec* decoder, SDL_Window* window,
|
||||
int videoFormat, int width, int height,
|
||||
int maxFps, bool enableVsync, bool testOnly);
|
||||
|
||||
IFFmpegRenderer* createAcceleratedRenderer(const AVCodecHWConfig* hwDecodeCfg);
|
||||
|
||||
void reset();
|
||||
|
||||
static
|
||||
enum AVPixelFormat ffGetFormat(AVCodecContext* context,
|
||||
const enum AVPixelFormat* pixFmts);
|
||||
|
||||
AVPacket m_Pkt;
|
||||
AVCodecContext* m_VideoDecoderCtx;
|
||||
@@ -39,4 +46,9 @@ private:
|
||||
const AVCodecHWConfig* m_HwDecodeCfg;
|
||||
IFFmpegRenderer* m_Renderer;
|
||||
SDL_atomic_t m_QueuedFrames;
|
||||
int m_ConsecutiveFailedDecodes;
|
||||
Pacer* m_Pacer;
|
||||
|
||||
static const uint8_t k_H264TestFrame[];
|
||||
static const uint8_t k_HEVCTestFrame[];
|
||||
};
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "ffmpeg.h"
|
||||
|
||||
// 720p 60 FPS H.264 with 1 reference frame
|
||||
const uint8_t FFmpegVideoDecoder::k_H264TestFrame[] = {
|
||||
0x00, 0x00, 0x00, 0x01, 0x67, 0x64, 0x00, 0x20, 0xac, 0x2b, 0x40, 0x28, 0x02, 0xdd, 0x80, 0xb5, 0x06, 0x06, 0x06, 0xa5, 0x00, 0x00, 0x03, 0x03, 0xe8, 0x00, 0x01, 0xd4, 0xc0, 0x8f, 0x4a, 0xa0,
|
||||
0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x3c, 0xb0,
|
||||
0x00, 0x00, 0x00, 0x01, 0x65, 0xb8, 0x02, 0x01, 0x67, 0x25, 0x1b, 0xf4, 0xfa, 0x7d, 0x40, 0x1a, 0x78, 0xe5, 0x10, 0x52, 0xc2, 0xee, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0xc6, 0xef, 0xbb, 0x81, 0x85, 0x2d, 0x47, 0xda, 0xca, 0x4c, 0x00, 0x00, 0x03, 0x00, 0x02, 0x7b, 0xcf, 0x80, 0x00, 0x45, 0x40, 0x01, 0x8d, 0xa6, 0x00, 0x01, 0x64, 0x00, 0x0e, 0x03, 0xc8, 0x00, 0x0e, 0x10, 0x00, 0xbd, 0xc5, 0x00, 0x01, 0x11, 0x00, 0x0e, 0xa3, 0x80, 0x00, 0x38, 0xa0,
|
||||
0x00, 0x00, 0x01, 0x65, 0x00, 0x6e, 0x2e, 0x00, 0x83, 0x7f, 0xb4, 0x8e, 0x79, 0xa5, 0xff, 0x84, 0x3f, 0x7f, 0x34, 0x3f, 0x97, 0x1b, 0xaf, 0x31, 0x5f, 0x6e, 0xaa, 0xb6, 0xac, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x78, 0x36, 0x9d, 0xa4, 0x65, 0xf9, 0x1e, 0x5b, 0x3a, 0xb0, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x41, 0x80, 0x00, 0xc5, 0xc8, 0x00, 0x00, 0xfa, 0x00, 0x03, 0x24, 0x00, 0x0e, 0x20, 0x00, 0x3f, 0x80, 0x01, 0x32, 0x00, 0x08, 0x68, 0x00, 0x3e, 0xc0, 0x03, 0x8a,
|
||||
0x00, 0x00, 0x01, 0x65, 0x00, 0x37, 0x0b, 0x80, 0x21, 0x7f, 0xe3, 0x85, 0x1c, 0xd9, 0xff, 0xe1, 0x1b, 0x01, 0xfa, 0xc0, 0x3e, 0x11, 0x7e, 0xfe, 0x45, 0x5c, 0x43, 0x69, 0x31, 0x4b, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x02, 0x24, 0xae, 0x1a, 0xbb, 0xae, 0x75, 0x9e, 0x35, 0xae, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x03, 0x64, 0x00, 0x09, 0x98, 0x00, 0x1e, 0xc0, 0x00, 0x64, 0x80, 0x01, 0xc4, 0x00, 0x07, 0xf0, 0x00, 0x42, 0xf0, 0x00, 0x00, 0xe1, 0x98, 0x00, 0x05, 0x4b, 0x28, 0x00, 0x06, 0x04,
|
||||
0x00, 0x00, 0x01, 0x65, 0x00, 0x14, 0xa2, 0xe0, 0x08, 0x5f, 0xe3, 0x85, 0x1c, 0xd9, 0xff, 0xe1, 0x1b, 0x01, 0xfa, 0xc0, 0x3e, 0x11, 0x7e, 0xfe, 0x45, 0x5c, 0x43, 0x69, 0x31, 0x4b, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x02, 0x24, 0xb9, 0x7d, 0xb4, 0x70, 0x4d, 0x15, 0xc5, 0x0a, 0x4e, 0x60, 0x00, 0x00, 0x03, 0x00, 0x00, 0x26, 0xa8, 0xb0, 0x00, 0x13, 0xcd, 0xcc, 0x00, 0x07, 0x48, 0x88, 0x00, 0x06, 0x29, 0x69, 0x00, 0x01, 0x16, 0xac, 0x80, 0x00, 0x9e, 0x4e, 0x80, 0x00, 0x3f, 0x84, 0x20, 0x00, 0x6f, 0x41, 0xa0, 0x00, 0x20, 0x00, 0x02, 0x16, 0xb8, 0x00, 0x08, 0x08
|
||||
};
|
||||
|
||||
// 720p 60 FPS HEVC with 1 reference frame
|
||||
const uint8_t FFmpegVideoDecoder::k_HEVCTestFrame[] = {
|
||||
0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xac, 0x09,
|
||||
0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xa0, 0x02, 0x80, 0x80, 0x2e, 0x1f, 0x13, 0x96, 0xb4, 0xa4, 0x25, 0x92, 0xe3, 0x01, 0x6a, 0x0c, 0x0c, 0x0d, 0x48, 0x20, 0x00, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x07, 0x85, 0xf1, 0xa2, 0xd0,
|
||||
0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc0, 0xf7, 0xc0, 0xcc, 0x90,
|
||||
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0xac, 0x0c, 0xf0, 0xe6, 0xf6, 0x5e, 0xff, 0xd9, 0x97, 0xeb, 0x44, 0x7d, 0x1f, 0xa4, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x03, 0x52, 0x7c, 0x4e, 0x5b, 0xfe, 0xbe, 0xc2, 0x87, 0x20, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x2b, 0x60,
|
||||
};
|
||||
@@ -25,10 +25,16 @@ SLVideoDecoder::isHardwareAccelerated()
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
SLVideoDecoder::getDecoderCapabilities()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
SLVideoDecoder::initialize(StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window*,
|
||||
int videoFormat, int, int, int frameRate)
|
||||
int videoFormat, int, int, int frameRate, bool)
|
||||
{
|
||||
// SLVideo only supports hardware decoding
|
||||
if (vds == StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
||||
|
||||
@@ -14,8 +14,10 @@ public:
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height,
|
||||
int frameRate);
|
||||
int frameRate,
|
||||
bool enableVsync);
|
||||
virtual bool isHardwareAccelerated();
|
||||
virtual int getDecoderCapabilities();
|
||||
virtual int submitDecodeUnit(PDECODE_UNIT du);
|
||||
|
||||
// Unused since rendering is done directly from the decode thread
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Versions/Current/Headers
|
||||
@@ -0,0 +1 @@
|
||||
Versions/Current/Resources
|
||||
+1
@@ -0,0 +1 @@
|
||||
Versions/Current/SDL2
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>17C88</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>SDL2</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>http://www.libsdl.org</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.libsdl.SDL2</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Simple DirectMedia Layer</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.0.8</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>SDLX</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2.0.8</string>
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>9C40b</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>GM</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>17C76</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>macosx10.13</string>
|
||||
<key>DTXcode</key>
|
||||
<string>0920</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>9C40b</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,792 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>files</key>
|
||||
<dict>
|
||||
<key>Resources/Info.plist</key>
|
||||
<data>
|
||||
dts8ape5zBg3+8u8BQ5kWqp9cC8=
|
||||
</data>
|
||||
</dict>
|
||||
<key>files2</key>
|
||||
<dict>
|
||||
<key>Headers/SDL.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
wiqwnVgtbgjGPAOFejxz8yw7geU=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
LLwzMAHn2OsCr2SVR7M71KeQf+TiJpHk/KKojzQdRlI=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_assert.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
zWoE6/P7ZWAYr4J+EVgDAK+kot8=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
tuS18C7A11akqH9MWT0WWCb6q7kcF97Ui5OFwNtSF/k=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_atomic.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
/q15zvR2djPgTRkySENYqzisn64=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
n9rGA8zXFxasUnN0ytgLLWmW2CDCtKBeGJZcfB+nfRc=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_audio.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
9uJv81fH9Op/f3sF7/GVuNYQNOc=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
KDokTSV3kZuHCQFFrRum3VoTP1QSW2xAghjceoIArXY=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_bits.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
yp8Jj93ZE224v1+u++cOexlp9RY=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
kQTBxFYnOjKSlti4H7pAYZWdkH0BpQ9Eq54E/r/nlVY=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_blendmode.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
kgTnzOrp0kqeI8lzDgQyT/gCGiM=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
d/8B0iLh4aFeoaPEFqQ4C6x4ISlDJCKrybaKuHCJHL4=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_clipboard.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
qq1r82AS7fHG8G94+jvL41iKEyA=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
+r0LOXHGRf2Q9+FkqRU6i++1zEzsE5sWXxxMGrsSyKk=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_config.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
nJnfliCMDJUfOG8t4QYt5U9tFBY=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
qkVvxnI+XRMTIdbzQ9tV+ERRc75r/EzWu0qRTgt5K5c=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_config_macosx.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
D0LXp0FibaaLSE6877hbyqf2Jb8=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
pCQLXwJ6HaGZiG5vr9njJtgNcIV9p0mxcSJtYoj147A=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_copying.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
0DKOkPvRlLo00X7iE2fL4i8XBww=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
nHJ8cj/FeSfLoRm5gRiDk73eyYlKn7u1BfbqkB2ofLg=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_cpuinfo.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
2wleDfJFmCBrEssrE9rcuaiqOLQ=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
MEpF0mFQFLhuTV+5uSB++q8tT2z5heESxyP1HuzuKxc=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_endian.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
WmJrxu+b1MkjtE6ttCymwH4EVC0=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
77hv0QIqlra/8gcQF81aAwP21/ZAANKiIaJRV1QK8mk=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_error.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
HMprigJ3u4PqQ7B+gD1UUbTSrJ8=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
errosoVYCD45UvQVzL4Lmfq4eCQg92a6xUZ062DGEL4=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_events.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
ptgRA0IBsCg5za/lwhTOPGNmekY=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
lwISPqzee6nV4HCHaMzZCVJJAtFZmoi8cYlJkY41wKA=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_filesystem.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
9rIKoCtsAONdgfT/4QKm3a56hV0=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
l5nBvMglAIFEWf357KMyQ+E7cr4GqO9XNwevA04+VmQ=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_gamecontroller.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
sUaSBwOprX7A696W6u2oOtIne+Y=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
VZDstHk/V+tZik3q3xnDCAG3AfA+BoHk9lB406mKtxU=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_gesture.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
d80fR6I92r+G26xckkQzTRj/tj4=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
/5+tOQWQ9raopqBCExlH9ClAA/fVAfEBIrd/WlpaqIE=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_haptic.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
95rNclZzbsuV6pyeG8cbIETgS84=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
O4RUmC9HMqsjAf22Ru6z5j9HaYTsc7SxOnzOXPR3kz0=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_hints.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
UjEgXfZiLUmQTh9G+Bkx8Y1Ov6k=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
DqFPmBXNg9BovdIAlipnBKZo5mcQJCrLmhyIM/O+tvU=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_joystick.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
97SJSCx+xjRHUQ6ilndhP8TZ110=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
xYWL+BS5f9NGr7eeP+vL0kD5K6O99PXgOWLT2Bx2dRQ=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_keyboard.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
RsaE9DMLq9lFsYokvdaQ8nhIkfY=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
tpZGEHU3TKCozcYf4MA9gA5bXNPSxx9rqqBaPttgr+4=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_keycode.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
9nIqgM67nCJUdjV3iiiviT8DFhk=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
SpMclG+epmPQibi3fNsQe1lBUxZPJOOu4DfQjpTZYgw=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_loadso.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
m+OEcfHbykTuEo6P3/y/kuV1xjk=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
Yg3OrZpF17uC69TwprPyrpn3+su3YAaaA1hTNXAHxLw=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_log.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
HcUUwymYqVSfgz2LdtuUqZSwjUY=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
j4F34jvgNTEcHQQoPbBBugi4RKQLhGM+x4ndXdtDc0E=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_main.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
7I0GPDiLKlavuXuoepYsl2mS6CI=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
qwXROmpXppMPodetUGE0s/7HHmwfu3taLUeiiu+APk0=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_messagebox.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
MEB/oCw7dyinbrBmQQqh46/uRVk=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
DSuDS/nAzfEi2kO06DEpxRLj5V1++p2DYYA9Oou8ewQ=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_mouse.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
I2UksqoB5v6RX64Ddwea9dW0sO0=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
gFMH6bBzvJCff8F7FMWlP6o6TydFmiXEu0daWp166wk=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_mutex.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
H6Q1bWPDKu2RsALoLzTBzPTrPkk=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
WKU8g7bVD3KpQEgvXuUGrPcG5hgg+LwQBgzLQCeSVTg=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_name.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
doLASBj4oiSiP6MW9wQblnyPN1E=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
M0b8pslMMsWZp1KDVUdqX7ml3DKUVcwiTcfwGbhopvA=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_opengl.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
ZN9vVzyxx542ZCZ4nmASJKJ7qFM=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
dazK6AC8BOZXW9m9761qsuoqe3eJozPIbSc1YEY3UVE=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_opengl_glext.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
nqPX1ObCVYyVzW3VbD3C/1vaTRE=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
IULBL/m+rBc3UQPqu68w6FDM/Mda58oybFnlyn3OkjE=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_opengles.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
aVEa5N2CHg93ZxdDuKfDtFnCbU8=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
3CmcA5B4ydtkVin3NvNmYVHfgnhJJAVYNVw1YKM0Mko=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_opengles2.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
yQ5nSugeN94hMSNhsIOmlAstJhY=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
8H2lt37zXZs68UfBlqDGqSJTQ2+Slu7qywggJ2mIosI=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_opengles2_gl2.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
g4y04zPFy1H/qujSNGRd0vB7ClQ=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
9GAwK0cg8YPrZHWVqMGpyVwKFz3Ay9VHiyz70jzWltA=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_opengles2_gl2ext.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
bTlnL+42kbi+n/gH//X2p0pqeuM=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
5404JOHXH4pGO60gR/uLhz9zW6fKy9jvdMw5i1WTcpo=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_opengles2_gl2platform.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
ByFMXJFtjcRglS/e0+DujyjC3dM=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
wlKRTsmTMrnniYqSvQklJfb9VF07J0MDpqzYOcfiDDY=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_opengles2_khrplatform.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
NLpJq9uBqjOpWhXISOQHPnTp/XQ=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
djHeDyzmR0e/2JNIbqpBqgi56MXlxlAbgcBFxuvwSyo=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_pixels.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
q2Uas2PGwUNo30Wiqomvrb03mp4=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
fpczDM4z7VLJW6FMvRTygnypORPdk2q89S1kCbS2fpM=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_platform.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
aM0ptLXVxYx4yXLsNYOssy/jx9Y=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
R6MDdJ2ZUd9FGKReGdJE6YyHwqczb99jK9/IDW59AmI=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_power.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
NqkQCWzmodWGdUFUZmzR9B/TApc=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
VkJkbp+5Wd4WYDemJiu8R/emdH4PZSNPweoPmzRkfxM=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_quit.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
eOEJzybg3UBfe83rKNfky9XAVGM=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
mN/T7PjHVtVce+/m7tIWIH8p8lylLWPuJAkCsbEvjhg=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_rect.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
B7oWq3VOxrWbVn0YV8xpvXG6dic=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
dCsZcEfdmcr1RuOycWAtHnnQs5BWk8lw0Ubnv/aUwG4=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_render.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
ATenWUmKg6AEbv2SbCy9JfT8jCw=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
EYiKPX5wRIcWkCyROlT59wuiDupZ6yp0+YsDWOrhtnQ=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_revision.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
XBn2NI8Qm53MeZGaXVB3fQrs0uc=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
FHG6Lqi1wMmjow+JjxKYy/LAoqP+iDH8QYuJS1zr6T4=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_rwops.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
ULSy/1QoNQhBu3kk75qXo30Wcvs=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
KewlZ14FJ6CfLjd7EDc1KlVSiUhIt2P9d3p2VEJuUUA=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_scancode.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
FAcbPIsuPOYbsldZL4m0AFUeoW4=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
5VaZC9IfMuNgz3h6OJn9Q/IGB/+cpymxIrjkluCWzOo=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_shape.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
S5Ty+gKktVyifkoYT0K7E48JqdI=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
LVwSnfZBuKL1bW2zy4rt0vRPmH2/ZxfmP0xB9pLVEOE=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_stdinc.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
qBTB0Yqh+34vD3n3dN9ry9GwqJM=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
TDMriI0PPYF73lvh6suExzpMldCmX1SkLgg+wkBqt7A=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_surface.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
bssTKTIF4Yw/3a6fyF7GhaVrdU0=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
zRF/b8bCqjClZnbPorqjhlWw9aujM9RwnVemHyiC2yM=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_system.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
n2WodCV+WpbfpFSKUUKpB/i5ZPw=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
3W0Z/Fdwbc7dTyR+v4g6eVE58XcEYLTzDGO+RMwcmJc=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_syswm.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
QoebSqgb1XslKPBwMiUOM6daVKo=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
rCqmdvdibhT5MBOnvKCT0gK7Howb5uLewC321yLYr5U=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_thread.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
fXCWQD4MIniqvdB6pWVwlxNS9jk=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
18iJ1yFzOTeiM30JwK3GMlbgUqzNDIQiw3ZLLr3M+XQ=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_timer.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
qfzyWhIh36SK6BAcdd50D/MfdGo=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
5Qe92nVkUnHbxgbGwxkyglvVKajhfgVhxPei6dhr4c0=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_touch.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
yyJt6piMAcoFWhFqsHk9mo3MtaI=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
9mQ2V937DAO6OjcQFMboFrnJEiqdk+2uHBzdvMYUnd0=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_types.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
oVg7CzABdDKtxCWINHaUvBRbRZ0=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
8uZym5ly46CZ4VQZCz/Y/cH4THoDS8kGqrvB+Ux/254=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_version.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
nKLee2vSNZZK9p94Up9o87OoiDE=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
F3dpo9XlvebIS2NLJ1ovH+W7fG3bzb01F8OKUeqd+U0=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_video.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
UFZPU1U3CDog3RBKdd5wu9LFQZk=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
6/OCq92BIQnGHBLmlGX4nUNnfLM7LzPoMp+lPlYY4ew=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_vulkan.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
eLdIezOR2pV1ai5rLBrtiR3NAok=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
iCB8avj2P0+ZkjVGfTchu5BAd7BJ+LeC1fJ9BU+80IE=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/begin_code.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
6RX1yuMtg6N9G2iCtCjjKzPCkuU=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
JQ71zTwAp3EzI22aBP3w80YYEcxNBRF8/0DkqO39oDo=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/close_code.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
IDKa63p9RAIkSpPXA0mU/dzgBWQ=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
oF0I0dcQDvLcuEIgqx9wEQiuqHR5KLoq1zhL/AK5geg=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Resources/Info.plist</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
dts8ape5zBg3+8u8BQ5kWqp9cC8=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
ZCgx1szeVwObsPiq6VoZh8Q9ym7l+7WXzv0HTSoFT0U=
|
||||
</data>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>rules</key>
|
||||
<dict>
|
||||
<key>^Resources/</key>
|
||||
<true/>
|
||||
<key>^Resources/.*\.lproj/</key>
|
||||
<dict>
|
||||
<key>optional</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1000</real>
|
||||
</dict>
|
||||
<key>^Resources/.*\.lproj/locversion.plist$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1100</real>
|
||||
</dict>
|
||||
<key>^Resources/Base\.lproj/</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>1010</real>
|
||||
</dict>
|
||||
<key>^version.plist$</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>rules2</key>
|
||||
<dict>
|
||||
<key>.*\.dSYM($|/)</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>11</real>
|
||||
</dict>
|
||||
<key>^(.*/)?\.DS_Store$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>2000</real>
|
||||
</dict>
|
||||
<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
|
||||
<dict>
|
||||
<key>nested</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>10</real>
|
||||
</dict>
|
||||
<key>^.*</key>
|
||||
<true/>
|
||||
<key>^Info\.plist$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^PkgInfo$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^Resources/</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^Resources/.*\.lproj/</key>
|
||||
<dict>
|
||||
<key>optional</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1000</real>
|
||||
</dict>
|
||||
<key>^Resources/.*\.lproj/locversion.plist$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1100</real>
|
||||
</dict>
|
||||
<key>^Resources/Base\.lproj/</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>1010</real>
|
||||
</dict>
|
||||
<key>^[^/]+$</key>
|
||||
<dict>
|
||||
<key>nested</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>10</real>
|
||||
</dict>
|
||||
<key>^embedded\.provisionprofile$</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^version\.plist$</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1 @@
|
||||
A
|
||||
@@ -0,0 +1,981 @@
|
||||
/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited
|
||||
Written by Jean-Marc Valin and Koen Vos */
|
||||
/*
|
||||
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.
|
||||
|
||||
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 OWNER
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file opus.h
|
||||
* @brief Opus reference implementation API
|
||||
*/
|
||||
|
||||
#ifndef OPUS_H
|
||||
#define OPUS_H
|
||||
|
||||
#include "opus_types.h"
|
||||
#include "opus_defines.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @mainpage Opus
|
||||
*
|
||||
* The Opus codec is designed for interactive speech and audio transmission over the Internet.
|
||||
* It is designed by the IETF Codec Working Group and incorporates technology from
|
||||
* Skype's SILK codec and Xiph.Org's CELT codec.
|
||||
*
|
||||
* The Opus codec is designed to handle a wide range of interactive audio applications,
|
||||
* including Voice over IP, videoconferencing, in-game chat, and even remote live music
|
||||
* performances. It can scale from low bit-rate narrowband speech to very high quality
|
||||
* stereo music. Its main features are:
|
||||
|
||||
* @li Sampling rates from 8 to 48 kHz
|
||||
* @li Bit-rates from 6 kb/s to 510 kb/s
|
||||
* @li Support for both constant bit-rate (CBR) and variable bit-rate (VBR)
|
||||
* @li Audio bandwidth from narrowband to full-band
|
||||
* @li Support for speech and music
|
||||
* @li Support for mono and stereo
|
||||
* @li Support for multichannel (up to 255 channels)
|
||||
* @li Frame sizes from 2.5 ms to 60 ms
|
||||
* @li Good loss robustness and packet loss concealment (PLC)
|
||||
* @li Floating point and fixed-point implementation
|
||||
*
|
||||
* Documentation sections:
|
||||
* @li @ref opus_encoder
|
||||
* @li @ref opus_decoder
|
||||
* @li @ref opus_repacketizer
|
||||
* @li @ref opus_multistream
|
||||
* @li @ref opus_libinfo
|
||||
* @li @ref opus_custom
|
||||
*/
|
||||
|
||||
/** @defgroup opus_encoder Opus Encoder
|
||||
* @{
|
||||
*
|
||||
* @brief This page describes the process and functions used to encode Opus.
|
||||
*
|
||||
* Since Opus is a stateful codec, the encoding process starts with creating an encoder
|
||||
* state. This can be done with:
|
||||
*
|
||||
* @code
|
||||
* int error;
|
||||
* OpusEncoder *enc;
|
||||
* enc = opus_encoder_create(Fs, channels, application, &error);
|
||||
* @endcode
|
||||
*
|
||||
* From this point, @c enc can be used for encoding an audio stream. An encoder state
|
||||
* @b must @b not be used for more than one stream at the same time. Similarly, the encoder
|
||||
* state @b must @b not be re-initialized for each frame.
|
||||
*
|
||||
* While opus_encoder_create() allocates memory for the state, it's also possible
|
||||
* to initialize pre-allocated memory:
|
||||
*
|
||||
* @code
|
||||
* int size;
|
||||
* int error;
|
||||
* OpusEncoder *enc;
|
||||
* size = opus_encoder_get_size(channels);
|
||||
* enc = malloc(size);
|
||||
* error = opus_encoder_init(enc, Fs, channels, application);
|
||||
* @endcode
|
||||
*
|
||||
* where opus_encoder_get_size() returns the required size for the encoder state. Note that
|
||||
* future versions of this code may change the size, so no assuptions should be made about it.
|
||||
*
|
||||
* The encoder state is always continuous in memory and only a shallow copy is sufficient
|
||||
* to copy it (e.g. memcpy())
|
||||
*
|
||||
* It is possible to change some of the encoder's settings using the opus_encoder_ctl()
|
||||
* interface. All these settings already default to the recommended value, so they should
|
||||
* only be changed when necessary. The most common settings one may want to change are:
|
||||
*
|
||||
* @code
|
||||
* opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate));
|
||||
* opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity));
|
||||
* opus_encoder_ctl(enc, OPUS_SET_SIGNAL(signal_type));
|
||||
* @endcode
|
||||
*
|
||||
* where
|
||||
*
|
||||
* @arg bitrate is in bits per second (b/s)
|
||||
* @arg complexity is a value from 1 to 10, where 1 is the lowest complexity and 10 is the highest
|
||||
* @arg signal_type is either OPUS_AUTO (default), OPUS_SIGNAL_VOICE, or OPUS_SIGNAL_MUSIC
|
||||
*
|
||||
* See @ref opus_encoderctls and @ref opus_genericctls for a complete list of parameters that can be set or queried. Most parameters can be set or changed at any time during a stream.
|
||||
*
|
||||
* To encode a frame, opus_encode() or opus_encode_float() must be called with exactly one frame (2.5, 5, 10, 20, 40 or 60 ms) of audio data:
|
||||
* @code
|
||||
* len = opus_encode(enc, audio_frame, frame_size, packet, max_packet);
|
||||
* @endcode
|
||||
*
|
||||
* where
|
||||
* <ul>
|
||||
* <li>audio_frame is the audio data in opus_int16 (or float for opus_encode_float())</li>
|
||||
* <li>frame_size is the duration of the frame in samples (per channel)</li>
|
||||
* <li>packet is the byte array to which the compressed data is written</li>
|
||||
* <li>max_packet is the maximum number of bytes that can be written in the packet (4000 bytes is recommended).
|
||||
* Do not use max_packet to control VBR target bitrate, instead use the #OPUS_SET_BITRATE CTL.</li>
|
||||
* </ul>
|
||||
*
|
||||
* opus_encode() and opus_encode_float() return the number of bytes actually written to the packet.
|
||||
* The return value <b>can be negative</b>, which indicates that an error has occurred. If the return value
|
||||
* is 2 bytes or less, then the packet does not need to be transmitted (DTX).
|
||||
*
|
||||
* Once the encoder state if no longer needed, it can be destroyed with
|
||||
*
|
||||
* @code
|
||||
* opus_encoder_destroy(enc);
|
||||
* @endcode
|
||||
*
|
||||
* If the encoder was created with opus_encoder_init() rather than opus_encoder_create(),
|
||||
* then no action is required aside from potentially freeing the memory that was manually
|
||||
* allocated for it (calling free(enc) for the example above)
|
||||
*
|
||||
*/
|
||||
|
||||
/** Opus encoder state.
|
||||
* This contains the complete state of an Opus encoder.
|
||||
* It is position independent and can be freely copied.
|
||||
* @see opus_encoder_create,opus_encoder_init
|
||||
*/
|
||||
typedef struct OpusEncoder OpusEncoder;
|
||||
|
||||
/** Gets the size of an <code>OpusEncoder</code> structure.
|
||||
* @param[in] channels <tt>int</tt>: Number of channels.
|
||||
* This must be 1 or 2.
|
||||
* @returns The size in bytes.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_encoder_get_size(int channels);
|
||||
|
||||
/**
|
||||
*/
|
||||
|
||||
/** Allocates and initializes an encoder state.
|
||||
* There are three coding modes:
|
||||
*
|
||||
* @ref OPUS_APPLICATION_VOIP gives best quality at a given bitrate for voice
|
||||
* signals. It enhances the input signal by high-pass filtering and
|
||||
* emphasizing formants and harmonics. Optionally it includes in-band
|
||||
* forward error correction to protect against packet loss. Use this
|
||||
* mode for typical VoIP applications. Because of the enhancement,
|
||||
* even at high bitrates the output may sound different from the input.
|
||||
*
|
||||
* @ref OPUS_APPLICATION_AUDIO gives best quality at a given bitrate for most
|
||||
* non-voice signals like music. Use this mode for music and mixed
|
||||
* (music/voice) content, broadcast, and applications requiring less
|
||||
* than 15 ms of coding delay.
|
||||
*
|
||||
* @ref OPUS_APPLICATION_RESTRICTED_LOWDELAY configures low-delay mode that
|
||||
* disables the speech-optimized mode in exchange for slightly reduced delay.
|
||||
* This mode can only be set on an newly initialized or freshly reset encoder
|
||||
* because it changes the codec delay.
|
||||
*
|
||||
* This is useful when the caller knows that the speech-optimized modes will not be needed (use with caution).
|
||||
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate of input signal (Hz)
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param [in] channels <tt>int</tt>: Number of channels (1 or 2) in input signal
|
||||
* @param [in] application <tt>int</tt>: Coding mode (@ref OPUS_APPLICATION_VOIP/@ref OPUS_APPLICATION_AUDIO/@ref OPUS_APPLICATION_RESTRICTED_LOWDELAY)
|
||||
* @param [out] error <tt>int*</tt>: @ref opus_errorcodes
|
||||
* @note Regardless of the sampling rate and number channels selected, the Opus encoder
|
||||
* can switch to a lower audio bandwidth or number of channels if the bitrate
|
||||
* selected is too low. This also means that it is safe to always use 48 kHz stereo input
|
||||
* and let the encoder optimize the encoding.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusEncoder *opus_encoder_create(
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int application,
|
||||
int *error
|
||||
);
|
||||
|
||||
/** Initializes a previously allocated encoder state
|
||||
* The memory pointed to by st must be at least the size returned by opus_encoder_get_size().
|
||||
* This is intended for applications which use their own allocator instead of malloc.
|
||||
* @see opus_encoder_create(),opus_encoder_get_size()
|
||||
* To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
|
||||
* @param [in] st <tt>OpusEncoder*</tt>: Encoder state
|
||||
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate of input signal (Hz)
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param [in] channels <tt>int</tt>: Number of channels (1 or 2) in input signal
|
||||
* @param [in] application <tt>int</tt>: Coding mode (OPUS_APPLICATION_VOIP/OPUS_APPLICATION_AUDIO/OPUS_APPLICATION_RESTRICTED_LOWDELAY)
|
||||
* @retval #OPUS_OK Success or @ref opus_errorcodes
|
||||
*/
|
||||
OPUS_EXPORT int opus_encoder_init(
|
||||
OpusEncoder *st,
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int application
|
||||
) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Encodes an Opus frame.
|
||||
* @param [in] st <tt>OpusEncoder*</tt>: Encoder state
|
||||
* @param [in] pcm <tt>opus_int16*</tt>: Input signal (interleaved if 2 channels). length is frame_size*channels*sizeof(opus_int16)
|
||||
* @param [in] frame_size <tt>int</tt>: Number of samples per channel in the
|
||||
* input signal.
|
||||
* This must be an Opus frame size for
|
||||
* the encoder's sampling rate.
|
||||
* For example, at 48 kHz the permitted
|
||||
* values are 120, 240, 480, 960, 1920,
|
||||
* and 2880.
|
||||
* Passing in a duration of less than
|
||||
* 10 ms (480 samples at 48 kHz) will
|
||||
* prevent the encoder from using the LPC
|
||||
* or hybrid modes.
|
||||
* @param [out] data <tt>unsigned char*</tt>: Output payload.
|
||||
* This must contain storage for at
|
||||
* least \a max_data_bytes.
|
||||
* @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
|
||||
* memory for the output
|
||||
* payload. This may be
|
||||
* used to impose an upper limit on
|
||||
* the instant bitrate, but should
|
||||
* not be used as the only bitrate
|
||||
* control. Use #OPUS_SET_BITRATE to
|
||||
* control the bitrate.
|
||||
* @returns The length of the encoded packet (in bytes) on success or a
|
||||
* negative error code (see @ref opus_errorcodes) on failure.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode(
|
||||
OpusEncoder *st,
|
||||
const opus_int16 *pcm,
|
||||
int frame_size,
|
||||
unsigned char *data,
|
||||
opus_int32 max_data_bytes
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Encodes an Opus frame from floating point input.
|
||||
* @param [in] st <tt>OpusEncoder*</tt>: Encoder state
|
||||
* @param [in] pcm <tt>float*</tt>: Input in float format (interleaved if 2 channels), with a normal range of +/-1.0.
|
||||
* Samples with a range beyond +/-1.0 are supported but will
|
||||
* be clipped by decoders using the integer API and should
|
||||
* only be used if it is known that the far end supports
|
||||
* extended dynamic range.
|
||||
* length is frame_size*channels*sizeof(float)
|
||||
* @param [in] frame_size <tt>int</tt>: Number of samples per channel in the
|
||||
* input signal.
|
||||
* This must be an Opus frame size for
|
||||
* the encoder's sampling rate.
|
||||
* For example, at 48 kHz the permitted
|
||||
* values are 120, 240, 480, 960, 1920,
|
||||
* and 2880.
|
||||
* Passing in a duration of less than
|
||||
* 10 ms (480 samples at 48 kHz) will
|
||||
* prevent the encoder from using the LPC
|
||||
* or hybrid modes.
|
||||
* @param [out] data <tt>unsigned char*</tt>: Output payload.
|
||||
* This must contain storage for at
|
||||
* least \a max_data_bytes.
|
||||
* @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
|
||||
* memory for the output
|
||||
* payload. This may be
|
||||
* used to impose an upper limit on
|
||||
* the instant bitrate, but should
|
||||
* not be used as the only bitrate
|
||||
* control. Use #OPUS_SET_BITRATE to
|
||||
* control the bitrate.
|
||||
* @returns The length of the encoded packet (in bytes) on success or a
|
||||
* negative error code (see @ref opus_errorcodes) on failure.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode_float(
|
||||
OpusEncoder *st,
|
||||
const float *pcm,
|
||||
int frame_size,
|
||||
unsigned char *data,
|
||||
opus_int32 max_data_bytes
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Frees an <code>OpusEncoder</code> allocated by opus_encoder_create().
|
||||
* @param[in] st <tt>OpusEncoder*</tt>: State to be freed.
|
||||
*/
|
||||
OPUS_EXPORT void opus_encoder_destroy(OpusEncoder *st);
|
||||
|
||||
/** Perform a CTL function on an Opus encoder.
|
||||
*
|
||||
* Generally the request and subsequent arguments are generated
|
||||
* by a convenience macro.
|
||||
* @param st <tt>OpusEncoder*</tt>: Encoder state.
|
||||
* @param request This and all remaining parameters should be replaced by one
|
||||
* of the convenience macros in @ref opus_genericctls or
|
||||
* @ref opus_encoderctls.
|
||||
* @see opus_genericctls
|
||||
* @see opus_encoderctls
|
||||
*/
|
||||
OPUS_EXPORT int opus_encoder_ctl(OpusEncoder *st, int request, ...) OPUS_ARG_NONNULL(1);
|
||||
/**@}*/
|
||||
|
||||
/** @defgroup opus_decoder Opus Decoder
|
||||
* @{
|
||||
*
|
||||
* @brief This page describes the process and functions used to decode Opus.
|
||||
*
|
||||
* The decoding process also starts with creating a decoder
|
||||
* state. This can be done with:
|
||||
* @code
|
||||
* int error;
|
||||
* OpusDecoder *dec;
|
||||
* dec = opus_decoder_create(Fs, channels, &error);
|
||||
* @endcode
|
||||
* where
|
||||
* @li Fs is the sampling rate and must be 8000, 12000, 16000, 24000, or 48000
|
||||
* @li channels is the number of channels (1 or 2)
|
||||
* @li error will hold the error code in case of failure (or #OPUS_OK on success)
|
||||
* @li the return value is a newly created decoder state to be used for decoding
|
||||
*
|
||||
* While opus_decoder_create() allocates memory for the state, it's also possible
|
||||
* to initialize pre-allocated memory:
|
||||
* @code
|
||||
* int size;
|
||||
* int error;
|
||||
* OpusDecoder *dec;
|
||||
* size = opus_decoder_get_size(channels);
|
||||
* dec = malloc(size);
|
||||
* error = opus_decoder_init(dec, Fs, channels);
|
||||
* @endcode
|
||||
* where opus_decoder_get_size() returns the required size for the decoder state. Note that
|
||||
* future versions of this code may change the size, so no assuptions should be made about it.
|
||||
*
|
||||
* The decoder state is always continuous in memory and only a shallow copy is sufficient
|
||||
* to copy it (e.g. memcpy())
|
||||
*
|
||||
* To decode a frame, opus_decode() or opus_decode_float() must be called with a packet of compressed audio data:
|
||||
* @code
|
||||
* frame_size = opus_decode(dec, packet, len, decoded, max_size, 0);
|
||||
* @endcode
|
||||
* where
|
||||
*
|
||||
* @li packet is the byte array containing the compressed data
|
||||
* @li len is the exact number of bytes contained in the packet
|
||||
* @li decoded is the decoded audio data in opus_int16 (or float for opus_decode_float())
|
||||
* @li max_size is the max duration of the frame in samples (per channel) that can fit into the decoded_frame array
|
||||
*
|
||||
* opus_decode() and opus_decode_float() return the number of samples (per channel) decoded from the packet.
|
||||
* If that value is negative, then an error has occurred. This can occur if the packet is corrupted or if the audio
|
||||
* buffer is too small to hold the decoded audio.
|
||||
*
|
||||
* Opus is a stateful codec with overlapping blocks and as a result Opus
|
||||
* packets are not coded independently of each other. Packets must be
|
||||
* passed into the decoder serially and in the correct order for a correct
|
||||
* decode. Lost packets can be replaced with loss concealment by calling
|
||||
* the decoder with a null pointer and zero length for the missing packet.
|
||||
*
|
||||
* A single codec state may only be accessed from a single thread at
|
||||
* a time and any required locking must be performed by the caller. Separate
|
||||
* streams must be decoded with separate decoder states and can be decoded
|
||||
* in parallel unless the library was compiled with NONTHREADSAFE_PSEUDOSTACK
|
||||
* defined.
|
||||
*
|
||||
*/
|
||||
|
||||
/** Opus decoder state.
|
||||
* This contains the complete state of an Opus decoder.
|
||||
* It is position independent and can be freely copied.
|
||||
* @see opus_decoder_create,opus_decoder_init
|
||||
*/
|
||||
typedef struct OpusDecoder OpusDecoder;
|
||||
|
||||
/** Gets the size of an <code>OpusDecoder</code> structure.
|
||||
* @param [in] channels <tt>int</tt>: Number of channels.
|
||||
* This must be 1 or 2.
|
||||
* @returns The size in bytes.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_size(int channels);
|
||||
|
||||
/** Allocates and initializes a decoder state.
|
||||
* @param [in] Fs <tt>opus_int32</tt>: Sample rate to decode at (Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param [in] channels <tt>int</tt>: Number of channels (1 or 2) to decode
|
||||
* @param [out] error <tt>int*</tt>: #OPUS_OK Success or @ref opus_errorcodes
|
||||
*
|
||||
* Internally Opus stores data at 48000 Hz, so that should be the default
|
||||
* value for Fs. However, the decoder can efficiently decode to buffers
|
||||
* at 8, 12, 16, and 24 kHz so if for some reason the caller cannot use
|
||||
* data at the full sample rate, or knows the compressed data doesn't
|
||||
* use the full frequency range, it can request decoding at a reduced
|
||||
* rate. Likewise, the decoder is capable of filling in either mono or
|
||||
* interleaved stereo pcm buffers, at the caller's request.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusDecoder *opus_decoder_create(
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int *error
|
||||
);
|
||||
|
||||
/** Initializes a previously allocated decoder state.
|
||||
* The state must be at least the size returned by opus_decoder_get_size().
|
||||
* This is intended for applications which use their own allocator instead of malloc. @see opus_decoder_create,opus_decoder_get_size
|
||||
* To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
|
||||
* @param [in] st <tt>OpusDecoder*</tt>: Decoder state.
|
||||
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate to decode to (Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param [in] channels <tt>int</tt>: Number of channels (1 or 2) to decode
|
||||
* @retval #OPUS_OK Success or @ref opus_errorcodes
|
||||
*/
|
||||
OPUS_EXPORT int opus_decoder_init(
|
||||
OpusDecoder *st,
|
||||
opus_int32 Fs,
|
||||
int channels
|
||||
) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Decode an Opus packet.
|
||||
* @param [in] st <tt>OpusDecoder*</tt>: Decoder state
|
||||
* @param [in] data <tt>char*</tt>: Input payload. Use a NULL pointer to indicate packet loss
|
||||
* @param [in] len <tt>opus_int32</tt>: Number of bytes in payload*
|
||||
* @param [out] pcm <tt>opus_int16*</tt>: Output signal (interleaved if 2 channels). length
|
||||
* is frame_size*channels*sizeof(opus_int16)
|
||||
* @param [in] frame_size Number of samples per channel of available space in \a pcm.
|
||||
* If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will
|
||||
* not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1),
|
||||
* then frame_size needs to be exactly the duration of audio that is missing, otherwise the
|
||||
* decoder will not be in the optimal state to decode the next incoming packet. For the PLC and
|
||||
* FEC cases, frame_size <b>must</b> be a multiple of 2.5 ms.
|
||||
* @param [in] decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band forward error correction data be
|
||||
* decoded. If no such data is available, the frame is decoded as if it were lost.
|
||||
* @returns Number of decoded samples or @ref opus_errorcodes
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode(
|
||||
OpusDecoder *st,
|
||||
const unsigned char *data,
|
||||
opus_int32 len,
|
||||
opus_int16 *pcm,
|
||||
int frame_size,
|
||||
int decode_fec
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Decode an Opus packet with floating point output.
|
||||
* @param [in] st <tt>OpusDecoder*</tt>: Decoder state
|
||||
* @param [in] data <tt>char*</tt>: Input payload. Use a NULL pointer to indicate packet loss
|
||||
* @param [in] len <tt>opus_int32</tt>: Number of bytes in payload
|
||||
* @param [out] pcm <tt>float*</tt>: Output signal (interleaved if 2 channels). length
|
||||
* is frame_size*channels*sizeof(float)
|
||||
* @param [in] frame_size Number of samples per channel of available space in \a pcm.
|
||||
* If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will
|
||||
* not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1),
|
||||
* then frame_size needs to be exactly the duration of audio that is missing, otherwise the
|
||||
* decoder will not be in the optimal state to decode the next incoming packet. For the PLC and
|
||||
* FEC cases, frame_size <b>must</b> be a multiple of 2.5 ms.
|
||||
* @param [in] decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band forward error correction data be
|
||||
* decoded. If no such data is available the frame is decoded as if it were lost.
|
||||
* @returns Number of decoded samples or @ref opus_errorcodes
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode_float(
|
||||
OpusDecoder *st,
|
||||
const unsigned char *data,
|
||||
opus_int32 len,
|
||||
float *pcm,
|
||||
int frame_size,
|
||||
int decode_fec
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Perform a CTL function on an Opus decoder.
|
||||
*
|
||||
* Generally the request and subsequent arguments are generated
|
||||
* by a convenience macro.
|
||||
* @param st <tt>OpusDecoder*</tt>: Decoder state.
|
||||
* @param request This and all remaining parameters should be replaced by one
|
||||
* of the convenience macros in @ref opus_genericctls or
|
||||
* @ref opus_decoderctls.
|
||||
* @see opus_genericctls
|
||||
* @see opus_decoderctls
|
||||
*/
|
||||
OPUS_EXPORT int opus_decoder_ctl(OpusDecoder *st, int request, ...) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Frees an <code>OpusDecoder</code> allocated by opus_decoder_create().
|
||||
* @param[in] st <tt>OpusDecoder*</tt>: State to be freed.
|
||||
*/
|
||||
OPUS_EXPORT void opus_decoder_destroy(OpusDecoder *st);
|
||||
|
||||
/** Parse an opus packet into one or more frames.
|
||||
* Opus_decode will perform this operation internally so most applications do
|
||||
* not need to use this function.
|
||||
* This function does not copy the frames, the returned pointers are pointers into
|
||||
* the input packet.
|
||||
* @param [in] data <tt>char*</tt>: Opus packet to be parsed
|
||||
* @param [in] len <tt>opus_int32</tt>: size of data
|
||||
* @param [out] out_toc <tt>char*</tt>: TOC pointer
|
||||
* @param [out] frames <tt>char*[48]</tt> encapsulated frames
|
||||
* @param [out] size <tt>opus_int16[48]</tt> sizes of the encapsulated frames
|
||||
* @param [out] payload_offset <tt>int*</tt>: returns the position of the payload within the packet (in bytes)
|
||||
* @returns number of frames
|
||||
*/
|
||||
OPUS_EXPORT int opus_packet_parse(
|
||||
const unsigned char *data,
|
||||
opus_int32 len,
|
||||
unsigned char *out_toc,
|
||||
const unsigned char *frames[48],
|
||||
opus_int16 size[48],
|
||||
int *payload_offset
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Gets the bandwidth of an Opus packet.
|
||||
* @param [in] data <tt>char*</tt>: Opus packet
|
||||
* @retval OPUS_BANDWIDTH_NARROWBAND Narrowband (4kHz bandpass)
|
||||
* @retval OPUS_BANDWIDTH_MEDIUMBAND Mediumband (6kHz bandpass)
|
||||
* @retval OPUS_BANDWIDTH_WIDEBAND Wideband (8kHz bandpass)
|
||||
* @retval OPUS_BANDWIDTH_SUPERWIDEBAND Superwideband (12kHz bandpass)
|
||||
* @retval OPUS_BANDWIDTH_FULLBAND Fullband (20kHz bandpass)
|
||||
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_bandwidth(const unsigned char *data) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Gets the number of samples per frame from an Opus packet.
|
||||
* @param [in] data <tt>char*</tt>: Opus packet.
|
||||
* This must contain at least one byte of
|
||||
* data.
|
||||
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate in Hz.
|
||||
* This must be a multiple of 400, or
|
||||
* inaccurate results will be returned.
|
||||
* @returns Number of samples per frame.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_samples_per_frame(const unsigned char *data, opus_int32 Fs) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Gets the number of channels from an Opus packet.
|
||||
* @param [in] data <tt>char*</tt>: Opus packet
|
||||
* @returns Number of channels
|
||||
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_channels(const unsigned char *data) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Gets the number of frames in an Opus packet.
|
||||
* @param [in] packet <tt>char*</tt>: Opus packet
|
||||
* @param [in] len <tt>opus_int32</tt>: Length of packet
|
||||
* @returns Number of frames
|
||||
* @retval OPUS_BAD_ARG Insufficient data was passed to the function
|
||||
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_frames(const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Gets the number of samples of an Opus packet.
|
||||
* @param [in] packet <tt>char*</tt>: Opus packet
|
||||
* @param [in] len <tt>opus_int32</tt>: Length of packet
|
||||
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate in Hz.
|
||||
* This must be a multiple of 400, or
|
||||
* inaccurate results will be returned.
|
||||
* @returns Number of samples
|
||||
* @retval OPUS_BAD_ARG Insufficient data was passed to the function
|
||||
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_samples(const unsigned char packet[], opus_int32 len, opus_int32 Fs) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Gets the number of samples of an Opus packet.
|
||||
* @param [in] dec <tt>OpusDecoder*</tt>: Decoder state
|
||||
* @param [in] packet <tt>char*</tt>: Opus packet
|
||||
* @param [in] len <tt>opus_int32</tt>: Length of packet
|
||||
* @returns Number of samples
|
||||
* @retval OPUS_BAD_ARG Insufficient data was passed to the function
|
||||
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_nb_samples(const OpusDecoder *dec, const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2);
|
||||
|
||||
/** Applies soft-clipping to bring a float signal within the [-1,1] range. If
|
||||
* the signal is already in that range, nothing is done. If there are values
|
||||
* outside of [-1,1], then the signal is clipped as smoothly as possible to
|
||||
* both fit in the range and avoid creating excessive distortion in the
|
||||
* process.
|
||||
* @param [in,out] pcm <tt>float*</tt>: Input PCM and modified PCM
|
||||
* @param [in] frame_size <tt>int</tt> Number of samples per channel to process
|
||||
* @param [in] channels <tt>int</tt>: Number of channels
|
||||
* @param [in,out] softclip_mem <tt>float*</tt>: State memory for the soft clipping process (one float per channel, initialized to zero)
|
||||
*/
|
||||
OPUS_EXPORT void opus_pcm_soft_clip(float *pcm, int frame_size, int channels, float *softclip_mem);
|
||||
|
||||
|
||||
/**@}*/
|
||||
|
||||
/** @defgroup opus_repacketizer Repacketizer
|
||||
* @{
|
||||
*
|
||||
* The repacketizer can be used to merge multiple Opus packets into a single
|
||||
* packet or alternatively to split Opus packets that have previously been
|
||||
* merged. Splitting valid Opus packets is always guaranteed to succeed,
|
||||
* whereas merging valid packets only succeeds if all frames have the same
|
||||
* mode, bandwidth, and frame size, and when the total duration of the merged
|
||||
* packet is no more than 120 ms. The 120 ms limit comes from the
|
||||
* specification and limits decoder memory requirements at a point where
|
||||
* framing overhead becomes negligible.
|
||||
*
|
||||
* The repacketizer currently only operates on elementary Opus
|
||||
* streams. It will not manipualte multistream packets successfully, except in
|
||||
* the degenerate case where they consist of data from a single stream.
|
||||
*
|
||||
* The repacketizing process starts with creating a repacketizer state, either
|
||||
* by calling opus_repacketizer_create() or by allocating the memory yourself,
|
||||
* e.g.,
|
||||
* @code
|
||||
* OpusRepacketizer *rp;
|
||||
* rp = (OpusRepacketizer*)malloc(opus_repacketizer_get_size());
|
||||
* if (rp != NULL)
|
||||
* opus_repacketizer_init(rp);
|
||||
* @endcode
|
||||
*
|
||||
* Then the application should submit packets with opus_repacketizer_cat(),
|
||||
* extract new packets with opus_repacketizer_out() or
|
||||
* opus_repacketizer_out_range(), and then reset the state for the next set of
|
||||
* input packets via opus_repacketizer_init().
|
||||
*
|
||||
* For example, to split a sequence of packets into individual frames:
|
||||
* @code
|
||||
* unsigned char *data;
|
||||
* int len;
|
||||
* while (get_next_packet(&data, &len))
|
||||
* {
|
||||
* unsigned char out[1276];
|
||||
* opus_int32 out_len;
|
||||
* int nb_frames;
|
||||
* int err;
|
||||
* int i;
|
||||
* err = opus_repacketizer_cat(rp, data, len);
|
||||
* if (err != OPUS_OK)
|
||||
* {
|
||||
* release_packet(data);
|
||||
* return err;
|
||||
* }
|
||||
* nb_frames = opus_repacketizer_get_nb_frames(rp);
|
||||
* for (i = 0; i < nb_frames; i++)
|
||||
* {
|
||||
* out_len = opus_repacketizer_out_range(rp, i, i+1, out, sizeof(out));
|
||||
* if (out_len < 0)
|
||||
* {
|
||||
* release_packet(data);
|
||||
* return (int)out_len;
|
||||
* }
|
||||
* output_next_packet(out, out_len);
|
||||
* }
|
||||
* opus_repacketizer_init(rp);
|
||||
* release_packet(data);
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Alternatively, to combine a sequence of frames into packets that each
|
||||
* contain up to <code>TARGET_DURATION_MS</code> milliseconds of data:
|
||||
* @code
|
||||
* // The maximum number of packets with duration TARGET_DURATION_MS occurs
|
||||
* // when the frame size is 2.5 ms, for a total of (TARGET_DURATION_MS*2/5)
|
||||
* // packets.
|
||||
* unsigned char *data[(TARGET_DURATION_MS*2/5)+1];
|
||||
* opus_int32 len[(TARGET_DURATION_MS*2/5)+1];
|
||||
* int nb_packets;
|
||||
* unsigned char out[1277*(TARGET_DURATION_MS*2/2)];
|
||||
* opus_int32 out_len;
|
||||
* int prev_toc;
|
||||
* nb_packets = 0;
|
||||
* while (get_next_packet(data+nb_packets, len+nb_packets))
|
||||
* {
|
||||
* int nb_frames;
|
||||
* int err;
|
||||
* nb_frames = opus_packet_get_nb_frames(data[nb_packets], len[nb_packets]);
|
||||
* if (nb_frames < 1)
|
||||
* {
|
||||
* release_packets(data, nb_packets+1);
|
||||
* return nb_frames;
|
||||
* }
|
||||
* nb_frames += opus_repacketizer_get_nb_frames(rp);
|
||||
* // If adding the next packet would exceed our target, or it has an
|
||||
* // incompatible TOC sequence, output the packets we already have before
|
||||
* // submitting it.
|
||||
* // N.B., The nb_packets > 0 check ensures we've submitted at least one
|
||||
* // packet since the last call to opus_repacketizer_init(). Otherwise a
|
||||
* // single packet longer than TARGET_DURATION_MS would cause us to try to
|
||||
* // output an (invalid) empty packet. It also ensures that prev_toc has
|
||||
* // been set to a valid value. Additionally, len[nb_packets] > 0 is
|
||||
* // guaranteed by the call to opus_packet_get_nb_frames() above, so the
|
||||
* // reference to data[nb_packets][0] should be valid.
|
||||
* if (nb_packets > 0 && (
|
||||
* ((prev_toc & 0xFC) != (data[nb_packets][0] & 0xFC)) ||
|
||||
* opus_packet_get_samples_per_frame(data[nb_packets], 48000)*nb_frames >
|
||||
* TARGET_DURATION_MS*48))
|
||||
* {
|
||||
* out_len = opus_repacketizer_out(rp, out, sizeof(out));
|
||||
* if (out_len < 0)
|
||||
* {
|
||||
* release_packets(data, nb_packets+1);
|
||||
* return (int)out_len;
|
||||
* }
|
||||
* output_next_packet(out, out_len);
|
||||
* opus_repacketizer_init(rp);
|
||||
* release_packets(data, nb_packets);
|
||||
* data[0] = data[nb_packets];
|
||||
* len[0] = len[nb_packets];
|
||||
* nb_packets = 0;
|
||||
* }
|
||||
* err = opus_repacketizer_cat(rp, data[nb_packets], len[nb_packets]);
|
||||
* if (err != OPUS_OK)
|
||||
* {
|
||||
* release_packets(data, nb_packets+1);
|
||||
* return err;
|
||||
* }
|
||||
* prev_toc = data[nb_packets][0];
|
||||
* nb_packets++;
|
||||
* }
|
||||
* // Output the final, partial packet.
|
||||
* if (nb_packets > 0)
|
||||
* {
|
||||
* out_len = opus_repacketizer_out(rp, out, sizeof(out));
|
||||
* release_packets(data, nb_packets);
|
||||
* if (out_len < 0)
|
||||
* return (int)out_len;
|
||||
* output_next_packet(out, out_len);
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* An alternate way of merging packets is to simply call opus_repacketizer_cat()
|
||||
* unconditionally until it fails. At that point, the merged packet can be
|
||||
* obtained with opus_repacketizer_out() and the input packet for which
|
||||
* opus_repacketizer_cat() needs to be re-added to a newly reinitialized
|
||||
* repacketizer state.
|
||||
*/
|
||||
|
||||
typedef struct OpusRepacketizer OpusRepacketizer;
|
||||
|
||||
/** Gets the size of an <code>OpusRepacketizer</code> structure.
|
||||
* @returns The size in bytes.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_size(void);
|
||||
|
||||
/** (Re)initializes a previously allocated repacketizer state.
|
||||
* The state must be at least the size returned by opus_repacketizer_get_size().
|
||||
* This can be used for applications which use their own allocator instead of
|
||||
* malloc().
|
||||
* It must also be called to reset the queue of packets waiting to be
|
||||
* repacketized, which is necessary if the maximum packet duration of 120 ms
|
||||
* is reached or if you wish to submit packets with a different Opus
|
||||
* configuration (coding mode, audio bandwidth, frame size, or channel count).
|
||||
* Failure to do so will prevent a new packet from being added with
|
||||
* opus_repacketizer_cat().
|
||||
* @see opus_repacketizer_create
|
||||
* @see opus_repacketizer_get_size
|
||||
* @see opus_repacketizer_cat
|
||||
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state to
|
||||
* (re)initialize.
|
||||
* @returns A pointer to the same repacketizer state that was passed in.
|
||||
*/
|
||||
OPUS_EXPORT OpusRepacketizer *opus_repacketizer_init(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Allocates memory and initializes the new repacketizer with
|
||||
* opus_repacketizer_init().
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusRepacketizer *opus_repacketizer_create(void);
|
||||
|
||||
/** Frees an <code>OpusRepacketizer</code> allocated by
|
||||
* opus_repacketizer_create().
|
||||
* @param[in] rp <tt>OpusRepacketizer*</tt>: State to be freed.
|
||||
*/
|
||||
OPUS_EXPORT void opus_repacketizer_destroy(OpusRepacketizer *rp);
|
||||
|
||||
/** Add a packet to the current repacketizer state.
|
||||
* This packet must match the configuration of any packets already submitted
|
||||
* for repacketization since the last call to opus_repacketizer_init().
|
||||
* This means that it must have the same coding mode, audio bandwidth, frame
|
||||
* size, and channel count.
|
||||
* This can be checked in advance by examining the top 6 bits of the first
|
||||
* byte of the packet, and ensuring they match the top 6 bits of the first
|
||||
* byte of any previously submitted packet.
|
||||
* The total duration of audio in the repacketizer state also must not exceed
|
||||
* 120 ms, the maximum duration of a single packet, after adding this packet.
|
||||
*
|
||||
* The contents of the current repacketizer state can be extracted into new
|
||||
* packets using opus_repacketizer_out() or opus_repacketizer_out_range().
|
||||
*
|
||||
* In order to add a packet with a different configuration or to add more
|
||||
* audio beyond 120 ms, you must clear the repacketizer state by calling
|
||||
* opus_repacketizer_init().
|
||||
* If a packet is too large to add to the current repacketizer state, no part
|
||||
* of it is added, even if it contains multiple frames, some of which might
|
||||
* fit.
|
||||
* If you wish to be able to add parts of such packets, you should first use
|
||||
* another repacketizer to split the packet into pieces and add them
|
||||
* individually.
|
||||
* @see opus_repacketizer_out_range
|
||||
* @see opus_repacketizer_out
|
||||
* @see opus_repacketizer_init
|
||||
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state to which to
|
||||
* add the packet.
|
||||
* @param[in] data <tt>const unsigned char*</tt>: The packet data.
|
||||
* The application must ensure
|
||||
* this pointer remains valid
|
||||
* until the next call to
|
||||
* opus_repacketizer_init() or
|
||||
* opus_repacketizer_destroy().
|
||||
* @param len <tt>opus_int32</tt>: The number of bytes in the packet data.
|
||||
* @returns An error code indicating whether or not the operation succeeded.
|
||||
* @retval #OPUS_OK The packet's contents have been added to the repacketizer
|
||||
* state.
|
||||
* @retval #OPUS_INVALID_PACKET The packet did not have a valid TOC sequence,
|
||||
* the packet's TOC sequence was not compatible
|
||||
* with previously submitted packets (because
|
||||
* the coding mode, audio bandwidth, frame size,
|
||||
* or channel count did not match), or adding
|
||||
* this packet would increase the total amount of
|
||||
* audio stored in the repacketizer state to more
|
||||
* than 120 ms.
|
||||
*/
|
||||
OPUS_EXPORT int opus_repacketizer_cat(OpusRepacketizer *rp, const unsigned char *data, opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2);
|
||||
|
||||
|
||||
/** Construct a new packet from data previously submitted to the repacketizer
|
||||
* state via opus_repacketizer_cat().
|
||||
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state from which to
|
||||
* construct the new packet.
|
||||
* @param begin <tt>int</tt>: The index of the first frame in the current
|
||||
* repacketizer state to include in the output.
|
||||
* @param end <tt>int</tt>: One past the index of the last frame in the
|
||||
* current repacketizer state to include in the
|
||||
* output.
|
||||
* @param[out] data <tt>const unsigned char*</tt>: The buffer in which to
|
||||
* store the output packet.
|
||||
* @param maxlen <tt>opus_int32</tt>: The maximum number of bytes to store in
|
||||
* the output buffer. In order to guarantee
|
||||
* success, this should be at least
|
||||
* <code>1276</code> for a single frame,
|
||||
* or for multiple frames,
|
||||
* <code>1277*(end-begin)</code>.
|
||||
* However, <code>1*(end-begin)</code> plus
|
||||
* the size of all packet data submitted to
|
||||
* the repacketizer since the last call to
|
||||
* opus_repacketizer_init() or
|
||||
* opus_repacketizer_create() is also
|
||||
* sufficient, and possibly much smaller.
|
||||
* @returns The total size of the output packet on success, or an error code
|
||||
* on failure.
|
||||
* @retval #OPUS_BAD_ARG <code>[begin,end)</code> was an invalid range of
|
||||
* frames (begin < 0, begin >= end, or end >
|
||||
* opus_repacketizer_get_nb_frames()).
|
||||
* @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the
|
||||
* complete output packet.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out_range(OpusRepacketizer *rp, int begin, int end, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Return the total number of frames contained in packet data submitted to
|
||||
* the repacketizer state so far via opus_repacketizer_cat() since the last
|
||||
* call to opus_repacketizer_init() or opus_repacketizer_create().
|
||||
* This defines the valid range of packets that can be extracted with
|
||||
* opus_repacketizer_out_range() or opus_repacketizer_out().
|
||||
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state containing the
|
||||
* frames.
|
||||
* @returns The total number of frames contained in the packet data submitted
|
||||
* to the repacketizer state.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_nb_frames(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Construct a new packet from data previously submitted to the repacketizer
|
||||
* state via opus_repacketizer_cat().
|
||||
* This is a convenience routine that returns all the data submitted so far
|
||||
* in a single packet.
|
||||
* It is equivalent to calling
|
||||
* @code
|
||||
* opus_repacketizer_out_range(rp, 0, opus_repacketizer_get_nb_frames(rp),
|
||||
* data, maxlen)
|
||||
* @endcode
|
||||
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state from which to
|
||||
* construct the new packet.
|
||||
* @param[out] data <tt>const unsigned char*</tt>: The buffer in which to
|
||||
* store the output packet.
|
||||
* @param maxlen <tt>opus_int32</tt>: The maximum number of bytes to store in
|
||||
* the output buffer. In order to guarantee
|
||||
* success, this should be at least
|
||||
* <code>1277*opus_repacketizer_get_nb_frames(rp)</code>.
|
||||
* However,
|
||||
* <code>1*opus_repacketizer_get_nb_frames(rp)</code>
|
||||
* plus the size of all packet data
|
||||
* submitted to the repacketizer since the
|
||||
* last call to opus_repacketizer_init() or
|
||||
* opus_repacketizer_create() is also
|
||||
* sufficient, and possibly much smaller.
|
||||
* @returns The total size of the output packet on success, or an error code
|
||||
* on failure.
|
||||
* @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the
|
||||
* complete output packet.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out(OpusRepacketizer *rp, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Pads a given Opus packet to a larger size (possibly changing the TOC sequence).
|
||||
* @param[in,out] data <tt>const unsigned char*</tt>: The buffer containing the
|
||||
* packet to pad.
|
||||
* @param len <tt>opus_int32</tt>: The size of the packet.
|
||||
* This must be at least 1.
|
||||
* @param new_len <tt>opus_int32</tt>: The desired size of the packet after padding.
|
||||
* This must be at least as large as len.
|
||||
* @returns an error code
|
||||
* @retval #OPUS_OK \a on success.
|
||||
* @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len.
|
||||
* @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet.
|
||||
*/
|
||||
OPUS_EXPORT int opus_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len);
|
||||
|
||||
/** Remove all padding from a given Opus packet and rewrite the TOC sequence to
|
||||
* minimize space usage.
|
||||
* @param[in,out] data <tt>const unsigned char*</tt>: The buffer containing the
|
||||
* packet to strip.
|
||||
* @param len <tt>opus_int32</tt>: The size of the packet.
|
||||
* This must be at least 1.
|
||||
* @returns The new size of the output packet on success, or an error code
|
||||
* on failure.
|
||||
* @retval #OPUS_BAD_ARG \a len was less than 1.
|
||||
* @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_packet_unpad(unsigned char *data, opus_int32 len);
|
||||
|
||||
/** Pads a given Opus multi-stream packet to a larger size (possibly changing the TOC sequence).
|
||||
* @param[in,out] data <tt>const unsigned char*</tt>: The buffer containing the
|
||||
* packet to pad.
|
||||
* @param len <tt>opus_int32</tt>: The size of the packet.
|
||||
* This must be at least 1.
|
||||
* @param new_len <tt>opus_int32</tt>: The desired size of the packet after padding.
|
||||
* This must be at least 1.
|
||||
* @param nb_streams <tt>opus_int32</tt>: The number of streams (not channels) in the packet.
|
||||
* This must be at least as large as len.
|
||||
* @returns an error code
|
||||
* @retval #OPUS_OK \a on success.
|
||||
* @retval #OPUS_BAD_ARG \a len was less than 1.
|
||||
* @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet.
|
||||
*/
|
||||
OPUS_EXPORT int opus_multistream_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len, int nb_streams);
|
||||
|
||||
/** Remove all padding from a given Opus multi-stream packet and rewrite the TOC sequence to
|
||||
* minimize space usage.
|
||||
* @param[in,out] data <tt>const unsigned char*</tt>: The buffer containing the
|
||||
* packet to strip.
|
||||
* @param len <tt>opus_int32</tt>: The size of the packet.
|
||||
* This must be at least 1.
|
||||
* @param nb_streams <tt>opus_int32</tt>: The number of streams (not channels) in the packet.
|
||||
* This must be at least 1.
|
||||
* @returns The new size of the output packet on success, or an error code
|
||||
* on failure.
|
||||
* @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len.
|
||||
* @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_packet_unpad(unsigned char *data, opus_int32 len, int nb_streams);
|
||||
|
||||
/**@}*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPUS_H */
|
||||
@@ -0,0 +1,342 @@
|
||||
/* Copyright (c) 2007-2008 CSIRO
|
||||
Copyright (c) 2007-2009 Xiph.Org Foundation
|
||||
Copyright (c) 2008-2012 Gregory Maxwell
|
||||
Written by Jean-Marc Valin and Gregory Maxwell */
|
||||
/*
|
||||
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.
|
||||
|
||||
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 OWNER
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
@file opus_custom.h
|
||||
@brief Opus-Custom reference implementation API
|
||||
*/
|
||||
|
||||
#ifndef OPUS_CUSTOM_H
|
||||
#define OPUS_CUSTOM_H
|
||||
|
||||
#include "opus_defines.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef CUSTOM_MODES
|
||||
# define OPUS_CUSTOM_EXPORT OPUS_EXPORT
|
||||
# define OPUS_CUSTOM_EXPORT_STATIC OPUS_EXPORT
|
||||
#else
|
||||
# define OPUS_CUSTOM_EXPORT
|
||||
# ifdef OPUS_BUILD
|
||||
# define OPUS_CUSTOM_EXPORT_STATIC static OPUS_INLINE
|
||||
# else
|
||||
# define OPUS_CUSTOM_EXPORT_STATIC
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/** @defgroup opus_custom Opus Custom
|
||||
* @{
|
||||
* Opus Custom is an optional part of the Opus specification and
|
||||
* reference implementation which uses a distinct API from the regular
|
||||
* API and supports frame sizes that are not normally supported.\ Use
|
||||
* of Opus Custom is discouraged for all but very special applications
|
||||
* for which a frame size different from 2.5, 5, 10, or 20 ms is needed
|
||||
* (for either complexity or latency reasons) and where interoperability
|
||||
* is less important.
|
||||
*
|
||||
* In addition to the interoperability limitations the use of Opus custom
|
||||
* disables a substantial chunk of the codec and generally lowers the
|
||||
* quality available at a given bitrate. Normally when an application needs
|
||||
* a different frame size from the codec it should buffer to match the
|
||||
* sizes but this adds a small amount of delay which may be important
|
||||
* in some very low latency applications. Some transports (especially
|
||||
* constant rate RF transports) may also work best with frames of
|
||||
* particular durations.
|
||||
*
|
||||
* Libopus only supports custom modes if they are enabled at compile time.
|
||||
*
|
||||
* The Opus Custom API is similar to the regular API but the
|
||||
* @ref opus_encoder_create and @ref opus_decoder_create calls take
|
||||
* an additional mode parameter which is a structure produced by
|
||||
* a call to @ref opus_custom_mode_create. Both the encoder and decoder
|
||||
* must create a mode using the same sample rate (fs) and frame size
|
||||
* (frame size) so these parameters must either be signaled out of band
|
||||
* or fixed in a particular implementation.
|
||||
*
|
||||
* Similar to regular Opus the custom modes support on the fly frame size
|
||||
* switching, but the sizes available depend on the particular frame size in
|
||||
* use. For some initial frame sizes on a single on the fly size is available.
|
||||
*/
|
||||
|
||||
/** Contains the state of an encoder. One encoder state is needed
|
||||
for each stream. It is initialized once at the beginning of the
|
||||
stream. Do *not* re-initialize the state for every frame.
|
||||
@brief Encoder state
|
||||
*/
|
||||
typedef struct OpusCustomEncoder OpusCustomEncoder;
|
||||
|
||||
/** State of the decoder. One decoder state is needed for each stream.
|
||||
It is initialized once at the beginning of the stream. Do *not*
|
||||
re-initialize the state for every frame.
|
||||
@brief Decoder state
|
||||
*/
|
||||
typedef struct OpusCustomDecoder OpusCustomDecoder;
|
||||
|
||||
/** The mode contains all the information necessary to create an
|
||||
encoder. Both the encoder and decoder need to be initialized
|
||||
with exactly the same mode, otherwise the output will be
|
||||
corrupted.
|
||||
@brief Mode configuration
|
||||
*/
|
||||
typedef struct OpusCustomMode OpusCustomMode;
|
||||
|
||||
/** Creates a new mode struct. This will be passed to an encoder or
|
||||
* decoder. The mode MUST NOT BE DESTROYED until the encoders and
|
||||
* decoders that use it are destroyed as well.
|
||||
* @param [in] Fs <tt>int</tt>: Sampling rate (8000 to 96000 Hz)
|
||||
* @param [in] frame_size <tt>int</tt>: Number of samples (per channel) to encode in each
|
||||
* packet (64 - 1024, prime factorization must contain zero or more 2s, 3s, or 5s and no other primes)
|
||||
* @param [out] error <tt>int*</tt>: Returned error code (if NULL, no error will be returned)
|
||||
* @return A newly created mode
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomMode *opus_custom_mode_create(opus_int32 Fs, int frame_size, int *error);
|
||||
|
||||
/** Destroys a mode struct. Only call this after all encoders and
|
||||
* decoders using this mode are destroyed as well.
|
||||
* @param [in] mode <tt>OpusCustomMode*</tt>: Mode to be freed.
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT void opus_custom_mode_destroy(OpusCustomMode *mode);
|
||||
|
||||
|
||||
#if !defined(OPUS_BUILD) || defined(CELT_ENCODER_C)
|
||||
|
||||
/* Encoder */
|
||||
/** Gets the size of an OpusCustomEncoder structure.
|
||||
* @param [in] mode <tt>OpusCustomMode *</tt>: Mode configuration
|
||||
* @param [in] channels <tt>int</tt>: Number of channels
|
||||
* @returns size
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT_STATIC OPUS_WARN_UNUSED_RESULT int opus_custom_encoder_get_size(
|
||||
const OpusCustomMode *mode,
|
||||
int channels
|
||||
) OPUS_ARG_NONNULL(1);
|
||||
|
||||
# ifdef CUSTOM_MODES
|
||||
/** Initializes a previously allocated encoder state
|
||||
* The memory pointed to by st must be the size returned by opus_custom_encoder_get_size.
|
||||
* This is intended for applications which use their own allocator instead of malloc.
|
||||
* @see opus_custom_encoder_create(),opus_custom_encoder_get_size()
|
||||
* To reset a previously initialized state use the OPUS_RESET_STATE CTL.
|
||||
* @param [in] st <tt>OpusCustomEncoder*</tt>: Encoder state
|
||||
* @param [in] mode <tt>OpusCustomMode *</tt>: Contains all the information about the characteristics of
|
||||
* the stream (must be the same characteristics as used for the
|
||||
* decoder)
|
||||
* @param [in] channels <tt>int</tt>: Number of channels
|
||||
* @return OPUS_OK Success or @ref opus_errorcodes
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT int opus_custom_encoder_init(
|
||||
OpusCustomEncoder *st,
|
||||
const OpusCustomMode *mode,
|
||||
int channels
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2);
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
/** Creates a new encoder state. Each stream needs its own encoder
|
||||
* state (can't be shared across simultaneous streams).
|
||||
* @param [in] mode <tt>OpusCustomMode*</tt>: Contains all the information about the characteristics of
|
||||
* the stream (must be the same characteristics as used for the
|
||||
* decoder)
|
||||
* @param [in] channels <tt>int</tt>: Number of channels
|
||||
* @param [out] error <tt>int*</tt>: Returns an error code
|
||||
* @return Newly created encoder state.
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomEncoder *opus_custom_encoder_create(
|
||||
const OpusCustomMode *mode,
|
||||
int channels,
|
||||
int *error
|
||||
) OPUS_ARG_NONNULL(1);
|
||||
|
||||
|
||||
/** Destroys a an encoder state.
|
||||
* @param[in] st <tt>OpusCustomEncoder*</tt>: State to be freed.
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT void opus_custom_encoder_destroy(OpusCustomEncoder *st);
|
||||
|
||||
/** Encodes a frame of audio.
|
||||
* @param [in] st <tt>OpusCustomEncoder*</tt>: Encoder state
|
||||
* @param [in] pcm <tt>float*</tt>: PCM audio in float format, with a normal range of +/-1.0.
|
||||
* Samples with a range beyond +/-1.0 are supported but will
|
||||
* be clipped by decoders using the integer API and should
|
||||
* only be used if it is known that the far end supports
|
||||
* extended dynamic range. There must be exactly
|
||||
* frame_size samples per channel.
|
||||
* @param [in] frame_size <tt>int</tt>: Number of samples per frame of input signal
|
||||
* @param [out] compressed <tt>char *</tt>: The compressed data is written here. This may not alias pcm and must be at least maxCompressedBytes long.
|
||||
* @param [in] maxCompressedBytes <tt>int</tt>: Maximum number of bytes to use for compressing the frame
|
||||
* (can change from one frame to another)
|
||||
* @return Number of bytes written to "compressed".
|
||||
* If negative, an error has occurred (see error codes). It is IMPORTANT that
|
||||
* the length returned be somehow transmitted to the decoder. Otherwise, no
|
||||
* decoding is possible.
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_encode_float(
|
||||
OpusCustomEncoder *st,
|
||||
const float *pcm,
|
||||
int frame_size,
|
||||
unsigned char *compressed,
|
||||
int maxCompressedBytes
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Encodes a frame of audio.
|
||||
* @param [in] st <tt>OpusCustomEncoder*</tt>: Encoder state
|
||||
* @param [in] pcm <tt>opus_int16*</tt>: PCM audio in signed 16-bit format (native endian).
|
||||
* There must be exactly frame_size samples per channel.
|
||||
* @param [in] frame_size <tt>int</tt>: Number of samples per frame of input signal
|
||||
* @param [out] compressed <tt>char *</tt>: The compressed data is written here. This may not alias pcm and must be at least maxCompressedBytes long.
|
||||
* @param [in] maxCompressedBytes <tt>int</tt>: Maximum number of bytes to use for compressing the frame
|
||||
* (can change from one frame to another)
|
||||
* @return Number of bytes written to "compressed".
|
||||
* If negative, an error has occurred (see error codes). It is IMPORTANT that
|
||||
* the length returned be somehow transmitted to the decoder. Otherwise, no
|
||||
* decoding is possible.
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_encode(
|
||||
OpusCustomEncoder *st,
|
||||
const opus_int16 *pcm,
|
||||
int frame_size,
|
||||
unsigned char *compressed,
|
||||
int maxCompressedBytes
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Perform a CTL function on an Opus custom encoder.
|
||||
*
|
||||
* Generally the request and subsequent arguments are generated
|
||||
* by a convenience macro.
|
||||
* @see opus_encoderctls
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT int opus_custom_encoder_ctl(OpusCustomEncoder * OPUS_RESTRICT st, int request, ...) OPUS_ARG_NONNULL(1);
|
||||
|
||||
|
||||
#if !defined(OPUS_BUILD) || defined(CELT_DECODER_C)
|
||||
/* Decoder */
|
||||
|
||||
/** Gets the size of an OpusCustomDecoder structure.
|
||||
* @param [in] mode <tt>OpusCustomMode *</tt>: Mode configuration
|
||||
* @param [in] channels <tt>int</tt>: Number of channels
|
||||
* @returns size
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT_STATIC OPUS_WARN_UNUSED_RESULT int opus_custom_decoder_get_size(
|
||||
const OpusCustomMode *mode,
|
||||
int channels
|
||||
) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Initializes a previously allocated decoder state
|
||||
* The memory pointed to by st must be the size returned by opus_custom_decoder_get_size.
|
||||
* This is intended for applications which use their own allocator instead of malloc.
|
||||
* @see opus_custom_decoder_create(),opus_custom_decoder_get_size()
|
||||
* To reset a previously initialized state use the OPUS_RESET_STATE CTL.
|
||||
* @param [in] st <tt>OpusCustomDecoder*</tt>: Decoder state
|
||||
* @param [in] mode <tt>OpusCustomMode *</tt>: Contains all the information about the characteristics of
|
||||
* the stream (must be the same characteristics as used for the
|
||||
* encoder)
|
||||
* @param [in] channels <tt>int</tt>: Number of channels
|
||||
* @return OPUS_OK Success or @ref opus_errorcodes
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT_STATIC int opus_custom_decoder_init(
|
||||
OpusCustomDecoder *st,
|
||||
const OpusCustomMode *mode,
|
||||
int channels
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/** Creates a new decoder state. Each stream needs its own decoder state (can't
|
||||
* be shared across simultaneous streams).
|
||||
* @param [in] mode <tt>OpusCustomMode</tt>: Contains all the information about the characteristics of the
|
||||
* stream (must be the same characteristics as used for the encoder)
|
||||
* @param [in] channels <tt>int</tt>: Number of channels
|
||||
* @param [out] error <tt>int*</tt>: Returns an error code
|
||||
* @return Newly created decoder state.
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomDecoder *opus_custom_decoder_create(
|
||||
const OpusCustomMode *mode,
|
||||
int channels,
|
||||
int *error
|
||||
) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Destroys a an decoder state.
|
||||
* @param[in] st <tt>OpusCustomDecoder*</tt>: State to be freed.
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT void opus_custom_decoder_destroy(OpusCustomDecoder *st);
|
||||
|
||||
/** Decode an opus custom frame with floating point output
|
||||
* @param [in] st <tt>OpusCustomDecoder*</tt>: Decoder state
|
||||
* @param [in] data <tt>char*</tt>: Input payload. Use a NULL pointer to indicate packet loss
|
||||
* @param [in] len <tt>int</tt>: Number of bytes in payload
|
||||
* @param [out] pcm <tt>float*</tt>: Output signal (interleaved if 2 channels). length
|
||||
* is frame_size*channels*sizeof(float)
|
||||
* @param [in] frame_size Number of samples per channel of available space in *pcm.
|
||||
* @returns Number of decoded samples or @ref opus_errorcodes
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_decode_float(
|
||||
OpusCustomDecoder *st,
|
||||
const unsigned char *data,
|
||||
int len,
|
||||
float *pcm,
|
||||
int frame_size
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Decode an opus custom frame
|
||||
* @param [in] st <tt>OpusCustomDecoder*</tt>: Decoder state
|
||||
* @param [in] data <tt>char*</tt>: Input payload. Use a NULL pointer to indicate packet loss
|
||||
* @param [in] len <tt>int</tt>: Number of bytes in payload
|
||||
* @param [out] pcm <tt>opus_int16*</tt>: Output signal (interleaved if 2 channels). length
|
||||
* is frame_size*channels*sizeof(opus_int16)
|
||||
* @param [in] frame_size Number of samples per channel of available space in *pcm.
|
||||
* @returns Number of decoded samples or @ref opus_errorcodes
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_decode(
|
||||
OpusCustomDecoder *st,
|
||||
const unsigned char *data,
|
||||
int len,
|
||||
opus_int16 *pcm,
|
||||
int frame_size
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Perform a CTL function on an Opus custom decoder.
|
||||
*
|
||||
* Generally the request and subsequent arguments are generated
|
||||
* by a convenience macro.
|
||||
* @see opus_genericctls
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT int opus_custom_decoder_ctl(OpusCustomDecoder * OPUS_RESTRICT st, int request, ...) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/**@}*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPUS_CUSTOM_H */
|
||||
@@ -0,0 +1,785 @@
|
||||
/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited
|
||||
Written by Jean-Marc Valin and Koen Vos */
|
||||
/*
|
||||
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.
|
||||
|
||||
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 OWNER
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file opus_defines.h
|
||||
* @brief Opus reference implementation constants
|
||||
*/
|
||||
|
||||
#ifndef OPUS_DEFINES_H
|
||||
#define OPUS_DEFINES_H
|
||||
|
||||
#include "opus_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @defgroup opus_errorcodes Error codes
|
||||
* @{
|
||||
*/
|
||||
/** No error @hideinitializer*/
|
||||
#define OPUS_OK 0
|
||||
/** One or more invalid/out of range arguments @hideinitializer*/
|
||||
#define OPUS_BAD_ARG -1
|
||||
/** Not enough bytes allocated in the buffer @hideinitializer*/
|
||||
#define OPUS_BUFFER_TOO_SMALL -2
|
||||
/** An internal error was detected @hideinitializer*/
|
||||
#define OPUS_INTERNAL_ERROR -3
|
||||
/** The compressed data passed is corrupted @hideinitializer*/
|
||||
#define OPUS_INVALID_PACKET -4
|
||||
/** Invalid/unsupported request number @hideinitializer*/
|
||||
#define OPUS_UNIMPLEMENTED -5
|
||||
/** An encoder or decoder structure is invalid or already freed @hideinitializer*/
|
||||
#define OPUS_INVALID_STATE -6
|
||||
/** Memory allocation has failed @hideinitializer*/
|
||||
#define OPUS_ALLOC_FAIL -7
|
||||
/**@}*/
|
||||
|
||||
/** @cond OPUS_INTERNAL_DOC */
|
||||
/**Export control for opus functions */
|
||||
|
||||
#ifndef OPUS_EXPORT
|
||||
# if defined(WIN32)
|
||||
# if defined(OPUS_BUILD) && defined(DLL_EXPORT)
|
||||
# define OPUS_EXPORT __declspec(dllexport)
|
||||
# else
|
||||
# define OPUS_EXPORT
|
||||
# endif
|
||||
# elif defined(__GNUC__) && defined(OPUS_BUILD)
|
||||
# define OPUS_EXPORT __attribute__ ((visibility ("default")))
|
||||
# else
|
||||
# define OPUS_EXPORT
|
||||
# endif
|
||||
#endif
|
||||
|
||||
# if !defined(OPUS_GNUC_PREREQ)
|
||||
# if defined(__GNUC__)&&defined(__GNUC_MINOR__)
|
||||
# define OPUS_GNUC_PREREQ(_maj,_min) \
|
||||
((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min))
|
||||
# else
|
||||
# define OPUS_GNUC_PREREQ(_maj,_min) 0
|
||||
# endif
|
||||
# endif
|
||||
|
||||
#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) )
|
||||
# if OPUS_GNUC_PREREQ(3,0)
|
||||
# define OPUS_RESTRICT __restrict__
|
||||
# elif (defined(_MSC_VER) && _MSC_VER >= 1400)
|
||||
# define OPUS_RESTRICT __restrict
|
||||
# else
|
||||
# define OPUS_RESTRICT
|
||||
# endif
|
||||
#else
|
||||
# define OPUS_RESTRICT restrict
|
||||
#endif
|
||||
|
||||
#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) )
|
||||
# if OPUS_GNUC_PREREQ(2,7)
|
||||
# define OPUS_INLINE __inline__
|
||||
# elif (defined(_MSC_VER))
|
||||
# define OPUS_INLINE __inline
|
||||
# else
|
||||
# define OPUS_INLINE
|
||||
# endif
|
||||
#else
|
||||
# define OPUS_INLINE inline
|
||||
#endif
|
||||
|
||||
/**Warning attributes for opus functions
|
||||
* NONNULL is not used in OPUS_BUILD to avoid the compiler optimizing out
|
||||
* some paranoid null checks. */
|
||||
#if defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4)
|
||||
# define OPUS_WARN_UNUSED_RESULT __attribute__ ((__warn_unused_result__))
|
||||
#else
|
||||
# define OPUS_WARN_UNUSED_RESULT
|
||||
#endif
|
||||
#if !defined(OPUS_BUILD) && defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4)
|
||||
# define OPUS_ARG_NONNULL(_x) __attribute__ ((__nonnull__(_x)))
|
||||
#else
|
||||
# define OPUS_ARG_NONNULL(_x)
|
||||
#endif
|
||||
|
||||
/** These are the actual Encoder CTL ID numbers.
|
||||
* They should not be used directly by applications.
|
||||
* In general, SETs should be even and GETs should be odd.*/
|
||||
#define OPUS_SET_APPLICATION_REQUEST 4000
|
||||
#define OPUS_GET_APPLICATION_REQUEST 4001
|
||||
#define OPUS_SET_BITRATE_REQUEST 4002
|
||||
#define OPUS_GET_BITRATE_REQUEST 4003
|
||||
#define OPUS_SET_MAX_BANDWIDTH_REQUEST 4004
|
||||
#define OPUS_GET_MAX_BANDWIDTH_REQUEST 4005
|
||||
#define OPUS_SET_VBR_REQUEST 4006
|
||||
#define OPUS_GET_VBR_REQUEST 4007
|
||||
#define OPUS_SET_BANDWIDTH_REQUEST 4008
|
||||
#define OPUS_GET_BANDWIDTH_REQUEST 4009
|
||||
#define OPUS_SET_COMPLEXITY_REQUEST 4010
|
||||
#define OPUS_GET_COMPLEXITY_REQUEST 4011
|
||||
#define OPUS_SET_INBAND_FEC_REQUEST 4012
|
||||
#define OPUS_GET_INBAND_FEC_REQUEST 4013
|
||||
#define OPUS_SET_PACKET_LOSS_PERC_REQUEST 4014
|
||||
#define OPUS_GET_PACKET_LOSS_PERC_REQUEST 4015
|
||||
#define OPUS_SET_DTX_REQUEST 4016
|
||||
#define OPUS_GET_DTX_REQUEST 4017
|
||||
#define OPUS_SET_VBR_CONSTRAINT_REQUEST 4020
|
||||
#define OPUS_GET_VBR_CONSTRAINT_REQUEST 4021
|
||||
#define OPUS_SET_FORCE_CHANNELS_REQUEST 4022
|
||||
#define OPUS_GET_FORCE_CHANNELS_REQUEST 4023
|
||||
#define OPUS_SET_SIGNAL_REQUEST 4024
|
||||
#define OPUS_GET_SIGNAL_REQUEST 4025
|
||||
#define OPUS_GET_LOOKAHEAD_REQUEST 4027
|
||||
/* #define OPUS_RESET_STATE 4028 */
|
||||
#define OPUS_GET_SAMPLE_RATE_REQUEST 4029
|
||||
#define OPUS_GET_FINAL_RANGE_REQUEST 4031
|
||||
#define OPUS_GET_PITCH_REQUEST 4033
|
||||
#define OPUS_SET_GAIN_REQUEST 4034
|
||||
#define OPUS_GET_GAIN_REQUEST 4045 /* Should have been 4035 */
|
||||
#define OPUS_SET_LSB_DEPTH_REQUEST 4036
|
||||
#define OPUS_GET_LSB_DEPTH_REQUEST 4037
|
||||
#define OPUS_GET_LAST_PACKET_DURATION_REQUEST 4039
|
||||
#define OPUS_SET_EXPERT_FRAME_DURATION_REQUEST 4040
|
||||
#define OPUS_GET_EXPERT_FRAME_DURATION_REQUEST 4041
|
||||
#define OPUS_SET_PREDICTION_DISABLED_REQUEST 4042
|
||||
#define OPUS_GET_PREDICTION_DISABLED_REQUEST 4043
|
||||
/* Don't use 4045, it's already taken by OPUS_GET_GAIN_REQUEST */
|
||||
#define OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST 4046
|
||||
#define OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST 4047
|
||||
|
||||
/* Macros to trigger compilation errors when the wrong types are provided to a CTL */
|
||||
#define __opus_check_int(x) (((void)((x) == (opus_int32)0)), (opus_int32)(x))
|
||||
#define __opus_check_int_ptr(ptr) ((ptr) + ((ptr) - (opus_int32*)(ptr)))
|
||||
#define __opus_check_uint_ptr(ptr) ((ptr) + ((ptr) - (opus_uint32*)(ptr)))
|
||||
#define __opus_check_val16_ptr(ptr) ((ptr) + ((ptr) - (opus_val16*)(ptr)))
|
||||
/** @endcond */
|
||||
|
||||
/** @defgroup opus_ctlvalues Pre-defined values for CTL interface
|
||||
* @see opus_genericctls, opus_encoderctls
|
||||
* @{
|
||||
*/
|
||||
/* Values for the various encoder CTLs */
|
||||
#define OPUS_AUTO -1000 /**<Auto/default setting @hideinitializer*/
|
||||
#define OPUS_BITRATE_MAX -1 /**<Maximum bitrate @hideinitializer*/
|
||||
|
||||
/** Best for most VoIP/videoconference applications where listening quality and intelligibility matter most
|
||||
* @hideinitializer */
|
||||
#define OPUS_APPLICATION_VOIP 2048
|
||||
/** Best for broadcast/high-fidelity application where the decoded audio should be as close as possible to the input
|
||||
* @hideinitializer */
|
||||
#define OPUS_APPLICATION_AUDIO 2049
|
||||
/** Only use when lowest-achievable latency is what matters most. Voice-optimized modes cannot be used.
|
||||
* @hideinitializer */
|
||||
#define OPUS_APPLICATION_RESTRICTED_LOWDELAY 2051
|
||||
|
||||
#define OPUS_SIGNAL_VOICE 3001 /**< Signal being encoded is voice */
|
||||
#define OPUS_SIGNAL_MUSIC 3002 /**< Signal being encoded is music */
|
||||
#define OPUS_BANDWIDTH_NARROWBAND 1101 /**< 4 kHz bandpass @hideinitializer*/
|
||||
#define OPUS_BANDWIDTH_MEDIUMBAND 1102 /**< 6 kHz bandpass @hideinitializer*/
|
||||
#define OPUS_BANDWIDTH_WIDEBAND 1103 /**< 8 kHz bandpass @hideinitializer*/
|
||||
#define OPUS_BANDWIDTH_SUPERWIDEBAND 1104 /**<12 kHz bandpass @hideinitializer*/
|
||||
#define OPUS_BANDWIDTH_FULLBAND 1105 /**<20 kHz bandpass @hideinitializer*/
|
||||
|
||||
#define OPUS_FRAMESIZE_ARG 5000 /**< Select frame size from the argument (default) */
|
||||
#define OPUS_FRAMESIZE_2_5_MS 5001 /**< Use 2.5 ms frames */
|
||||
#define OPUS_FRAMESIZE_5_MS 5002 /**< Use 5 ms frames */
|
||||
#define OPUS_FRAMESIZE_10_MS 5003 /**< Use 10 ms frames */
|
||||
#define OPUS_FRAMESIZE_20_MS 5004 /**< Use 20 ms frames */
|
||||
#define OPUS_FRAMESIZE_40_MS 5005 /**< Use 40 ms frames */
|
||||
#define OPUS_FRAMESIZE_60_MS 5006 /**< Use 60 ms frames */
|
||||
#define OPUS_FRAMESIZE_80_MS 5007 /**< Use 80 ms frames */
|
||||
#define OPUS_FRAMESIZE_100_MS 5008 /**< Use 100 ms frames */
|
||||
#define OPUS_FRAMESIZE_120_MS 5009 /**< Use 120 ms frames */
|
||||
|
||||
/**@}*/
|
||||
|
||||
|
||||
/** @defgroup opus_encoderctls Encoder related CTLs
|
||||
*
|
||||
* These are convenience macros for use with the \c opus_encode_ctl
|
||||
* interface. They are used to generate the appropriate series of
|
||||
* arguments for that call, passing the correct type, size and so
|
||||
* on as expected for each particular request.
|
||||
*
|
||||
* Some usage examples:
|
||||
*
|
||||
* @code
|
||||
* int ret;
|
||||
* ret = opus_encoder_ctl(enc_ctx, OPUS_SET_BANDWIDTH(OPUS_AUTO));
|
||||
* if (ret != OPUS_OK) return ret;
|
||||
*
|
||||
* opus_int32 rate;
|
||||
* opus_encoder_ctl(enc_ctx, OPUS_GET_BANDWIDTH(&rate));
|
||||
*
|
||||
* opus_encoder_ctl(enc_ctx, OPUS_RESET_STATE);
|
||||
* @endcode
|
||||
*
|
||||
* @see opus_genericctls, opus_encoder
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Configures the encoder's computational complexity.
|
||||
* The supported range is 0-10 inclusive with 10 representing the highest complexity.
|
||||
* @see OPUS_GET_COMPLEXITY
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values: 0-10, inclusive.
|
||||
*
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_COMPLEXITY(x) OPUS_SET_COMPLEXITY_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's complexity configuration.
|
||||
* @see OPUS_SET_COMPLEXITY
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns a value in the range 0-10,
|
||||
* inclusive.
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_COMPLEXITY(x) OPUS_GET_COMPLEXITY_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Configures the bitrate in the encoder.
|
||||
* Rates from 500 to 512000 bits per second are meaningful, as well as the
|
||||
* special values #OPUS_AUTO and #OPUS_BITRATE_MAX.
|
||||
* The value #OPUS_BITRATE_MAX can be used to cause the codec to use as much
|
||||
* rate as it can, which is useful for controlling the rate by adjusting the
|
||||
* output buffer size.
|
||||
* @see OPUS_GET_BITRATE
|
||||
* @param[in] x <tt>opus_int32</tt>: Bitrate in bits per second. The default
|
||||
* is determined based on the number of
|
||||
* channels and the input sampling rate.
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_BITRATE(x) OPUS_SET_BITRATE_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's bitrate configuration.
|
||||
* @see OPUS_SET_BITRATE
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns the bitrate in bits per second.
|
||||
* The default is determined based on the
|
||||
* number of channels and the input
|
||||
* sampling rate.
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_BITRATE(x) OPUS_GET_BITRATE_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Enables or disables variable bitrate (VBR) in the encoder.
|
||||
* The configured bitrate may not be met exactly because frames must
|
||||
* be an integer number of bytes in length.
|
||||
* @see OPUS_GET_VBR
|
||||
* @see OPUS_SET_VBR_CONSTRAINT
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Hard CBR. For LPC/hybrid modes at very low bit-rate, this can
|
||||
* cause noticeable quality degradation.</dd>
|
||||
* <dt>1</dt><dd>VBR (default). The exact type of VBR is controlled by
|
||||
* #OPUS_SET_VBR_CONSTRAINT.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_VBR(x) OPUS_SET_VBR_REQUEST, __opus_check_int(x)
|
||||
/** Determine if variable bitrate (VBR) is enabled in the encoder.
|
||||
* @see OPUS_SET_VBR
|
||||
* @see OPUS_GET_VBR_CONSTRAINT
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Hard CBR.</dd>
|
||||
* <dt>1</dt><dd>VBR (default). The exact type of VBR may be retrieved via
|
||||
* #OPUS_GET_VBR_CONSTRAINT.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_VBR(x) OPUS_GET_VBR_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Enables or disables constrained VBR in the encoder.
|
||||
* This setting is ignored when the encoder is in CBR mode.
|
||||
* @warning Only the MDCT mode of Opus currently heeds the constraint.
|
||||
* Speech mode ignores it completely, hybrid mode may fail to obey it
|
||||
* if the LPC layer uses more bitrate than the constraint would have
|
||||
* permitted.
|
||||
* @see OPUS_GET_VBR_CONSTRAINT
|
||||
* @see OPUS_SET_VBR
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Unconstrained VBR.</dd>
|
||||
* <dt>1</dt><dd>Constrained VBR (default). This creates a maximum of one
|
||||
* frame of buffering delay assuming a transport with a
|
||||
* serialization speed of the nominal bitrate.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_VBR_CONSTRAINT(x) OPUS_SET_VBR_CONSTRAINT_REQUEST, __opus_check_int(x)
|
||||
/** Determine if constrained VBR is enabled in the encoder.
|
||||
* @see OPUS_SET_VBR_CONSTRAINT
|
||||
* @see OPUS_GET_VBR
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Unconstrained VBR.</dd>
|
||||
* <dt>1</dt><dd>Constrained VBR (default).</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_VBR_CONSTRAINT(x) OPUS_GET_VBR_CONSTRAINT_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Configures mono/stereo forcing in the encoder.
|
||||
* This can force the encoder to produce packets encoded as either mono or
|
||||
* stereo, regardless of the format of the input audio. This is useful when
|
||||
* the caller knows that the input signal is currently a mono source embedded
|
||||
* in a stereo stream.
|
||||
* @see OPUS_GET_FORCE_CHANNELS
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt><dd>Not forced (default)</dd>
|
||||
* <dt>1</dt> <dd>Forced mono</dd>
|
||||
* <dt>2</dt> <dd>Forced stereo</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_FORCE_CHANNELS(x) OPUS_SET_FORCE_CHANNELS_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's forced channel configuration.
|
||||
* @see OPUS_SET_FORCE_CHANNELS
|
||||
* @param[out] x <tt>opus_int32 *</tt>:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt><dd>Not forced (default)</dd>
|
||||
* <dt>1</dt> <dd>Forced mono</dd>
|
||||
* <dt>2</dt> <dd>Forced stereo</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_FORCE_CHANNELS(x) OPUS_GET_FORCE_CHANNELS_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Configures the maximum bandpass that the encoder will select automatically.
|
||||
* Applications should normally use this instead of #OPUS_SET_BANDWIDTH
|
||||
* (leaving that set to the default, #OPUS_AUTO). This allows the
|
||||
* application to set an upper bound based on the type of input it is
|
||||
* providing, but still gives the encoder the freedom to reduce the bandpass
|
||||
* when the bitrate becomes too low, for better overall quality.
|
||||
* @see OPUS_GET_MAX_BANDWIDTH
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
|
||||
* <dt>OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
|
||||
* <dt>OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
|
||||
* <dt>OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
|
||||
* <dt>OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband (default)</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_MAX_BANDWIDTH(x) OPUS_SET_MAX_BANDWIDTH_REQUEST, __opus_check_int(x)
|
||||
|
||||
/** Gets the encoder's configured maximum allowed bandpass.
|
||||
* @see OPUS_SET_MAX_BANDWIDTH
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband (default)</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_MAX_BANDWIDTH(x) OPUS_GET_MAX_BANDWIDTH_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Sets the encoder's bandpass to a specific value.
|
||||
* This prevents the encoder from automatically selecting the bandpass based
|
||||
* on the available bitrate. If an application knows the bandpass of the input
|
||||
* audio it is providing, it should normally use #OPUS_SET_MAX_BANDWIDTH
|
||||
* instead, which still gives the encoder the freedom to reduce the bandpass
|
||||
* when the bitrate becomes too low, for better overall quality.
|
||||
* @see OPUS_GET_BANDWIDTH
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_BANDWIDTH(x) OPUS_SET_BANDWIDTH_REQUEST, __opus_check_int(x)
|
||||
|
||||
/** Configures the type of signal being encoded.
|
||||
* This is a hint which helps the encoder's mode selection.
|
||||
* @see OPUS_GET_SIGNAL
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
|
||||
* <dt>#OPUS_SIGNAL_VOICE</dt><dd>Bias thresholds towards choosing LPC or Hybrid modes.</dd>
|
||||
* <dt>#OPUS_SIGNAL_MUSIC</dt><dd>Bias thresholds towards choosing MDCT modes.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_SIGNAL(x) OPUS_SET_SIGNAL_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's configured signal type.
|
||||
* @see OPUS_SET_SIGNAL
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
|
||||
* <dt>#OPUS_SIGNAL_VOICE</dt><dd>Bias thresholds towards choosing LPC or Hybrid modes.</dd>
|
||||
* <dt>#OPUS_SIGNAL_MUSIC</dt><dd>Bias thresholds towards choosing MDCT modes.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_SIGNAL(x) OPUS_GET_SIGNAL_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
|
||||
/** Configures the encoder's intended application.
|
||||
* The initial value is a mandatory argument to the encoder_create function.
|
||||
* @see OPUS_GET_APPLICATION
|
||||
* @param[in] x <tt>opus_int32</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_APPLICATION_VOIP</dt>
|
||||
* <dd>Process signal for improved speech intelligibility.</dd>
|
||||
* <dt>#OPUS_APPLICATION_AUDIO</dt>
|
||||
* <dd>Favor faithfulness to the original input.</dd>
|
||||
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
|
||||
* <dd>Configure the minimum possible coding delay by disabling certain modes
|
||||
* of operation.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_APPLICATION(x) OPUS_SET_APPLICATION_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's configured application.
|
||||
* @see OPUS_SET_APPLICATION
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_APPLICATION_VOIP</dt>
|
||||
* <dd>Process signal for improved speech intelligibility.</dd>
|
||||
* <dt>#OPUS_APPLICATION_AUDIO</dt>
|
||||
* <dd>Favor faithfulness to the original input.</dd>
|
||||
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
|
||||
* <dd>Configure the minimum possible coding delay by disabling certain modes
|
||||
* of operation.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_APPLICATION(x) OPUS_GET_APPLICATION_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Gets the total samples of delay added by the entire codec.
|
||||
* This can be queried by the encoder and then the provided number of samples can be
|
||||
* skipped on from the start of the decoder's output to provide time aligned input
|
||||
* and output. From the perspective of a decoding application the real data begins this many
|
||||
* samples late.
|
||||
*
|
||||
* The decoder contribution to this delay is identical for all decoders, but the
|
||||
* encoder portion of the delay may vary from implementation to implementation,
|
||||
* version to version, or even depend on the encoder's initial configuration.
|
||||
* Applications needing delay compensation should call this CTL rather than
|
||||
* hard-coding a value.
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Number of lookahead samples
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_LOOKAHEAD(x) OPUS_GET_LOOKAHEAD_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Configures the encoder's use of inband forward error correction (FEC).
|
||||
* @note This is only applicable to the LPC layer
|
||||
* @see OPUS_GET_INBAND_FEC
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Disable inband FEC (default).</dd>
|
||||
* <dt>1</dt><dd>Enable inband FEC.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_INBAND_FEC(x) OPUS_SET_INBAND_FEC_REQUEST, __opus_check_int(x)
|
||||
/** Gets encoder's configured use of inband forward error correction.
|
||||
* @see OPUS_SET_INBAND_FEC
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Inband FEC disabled (default).</dd>
|
||||
* <dt>1</dt><dd>Inband FEC enabled.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_INBAND_FEC(x) OPUS_GET_INBAND_FEC_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Configures the encoder's expected packet loss percentage.
|
||||
* Higher values trigger progressively more loss resistant behavior in the encoder
|
||||
* at the expense of quality at a given bitrate in the absence of packet loss, but
|
||||
* greater quality under loss.
|
||||
* @see OPUS_GET_PACKET_LOSS_PERC
|
||||
* @param[in] x <tt>opus_int32</tt>: Loss percentage in the range 0-100, inclusive (default: 0).
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_PACKET_LOSS_PERC(x) OPUS_SET_PACKET_LOSS_PERC_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's configured packet loss percentage.
|
||||
* @see OPUS_SET_PACKET_LOSS_PERC
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns the configured loss percentage
|
||||
* in the range 0-100, inclusive (default: 0).
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_PACKET_LOSS_PERC(x) OPUS_GET_PACKET_LOSS_PERC_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Configures the encoder's use of discontinuous transmission (DTX).
|
||||
* @note This is only applicable to the LPC layer
|
||||
* @see OPUS_GET_DTX
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Disable DTX (default).</dd>
|
||||
* <dt>1</dt><dd>Enabled DTX.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_DTX(x) OPUS_SET_DTX_REQUEST, __opus_check_int(x)
|
||||
/** Gets encoder's configured use of discontinuous transmission.
|
||||
* @see OPUS_SET_DTX
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>DTX disabled (default).</dd>
|
||||
* <dt>1</dt><dd>DTX enabled.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_DTX(x) OPUS_GET_DTX_REQUEST, __opus_check_int_ptr(x)
|
||||
/** Configures the depth of signal being encoded.
|
||||
*
|
||||
* This is a hint which helps the encoder identify silence and near-silence.
|
||||
* It represents the number of significant bits of linear intensity below
|
||||
* which the signal contains ignorable quantization or other noise.
|
||||
*
|
||||
* For example, OPUS_SET_LSB_DEPTH(14) would be an appropriate setting
|
||||
* for G.711 u-law input. OPUS_SET_LSB_DEPTH(16) would be appropriate
|
||||
* for 16-bit linear pcm input with opus_encode_float().
|
||||
*
|
||||
* When using opus_encode() instead of opus_encode_float(), or when libopus
|
||||
* is compiled for fixed-point, the encoder uses the minimum of the value
|
||||
* set here and the value 16.
|
||||
*
|
||||
* @see OPUS_GET_LSB_DEPTH
|
||||
* @param[in] x <tt>opus_int32</tt>: Input precision in bits, between 8 and 24
|
||||
* (default: 24).
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_LSB_DEPTH(x) OPUS_SET_LSB_DEPTH_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's configured signal depth.
|
||||
* @see OPUS_SET_LSB_DEPTH
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Input precision in bits, between 8 and
|
||||
* 24 (default: 24).
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_LSB_DEPTH(x) OPUS_GET_LSB_DEPTH_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Configures the encoder's use of variable duration frames.
|
||||
* When variable duration is enabled, the encoder is free to use a shorter frame
|
||||
* size than the one requested in the opus_encode*() call.
|
||||
* It is then the user's responsibility
|
||||
* to verify how much audio was encoded by checking the ToC byte of the encoded
|
||||
* packet. The part of the audio that was not encoded needs to be resent to the
|
||||
* encoder for the next call. Do not use this option unless you <b>really</b>
|
||||
* know what you are doing.
|
||||
* @see OPUS_GET_EXPERT_FRAME_DURATION
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>OPUS_FRAMESIZE_ARG</dt><dd>Select frame size from the argument (default).</dd>
|
||||
* <dt>OPUS_FRAMESIZE_2_5_MS</dt><dd>Use 2.5 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_5_MS</dt><dd>Use 5 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_10_MS</dt><dd>Use 10 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_20_MS</dt><dd>Use 20 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_40_MS</dt><dd>Use 40 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_60_MS</dt><dd>Use 60 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_80_MS</dt><dd>Use 80 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_100_MS</dt><dd>Use 100 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_120_MS</dt><dd>Use 120 ms frames.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_EXPERT_FRAME_DURATION(x) OPUS_SET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's configured use of variable duration frames.
|
||||
* @see OPUS_SET_EXPERT_FRAME_DURATION
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>OPUS_FRAMESIZE_ARG</dt><dd>Select frame size from the argument (default).</dd>
|
||||
* <dt>OPUS_FRAMESIZE_2_5_MS</dt><dd>Use 2.5 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_5_MS</dt><dd>Use 5 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_10_MS</dt><dd>Use 10 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_20_MS</dt><dd>Use 20 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_40_MS</dt><dd>Use 40 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_60_MS</dt><dd>Use 60 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_80_MS</dt><dd>Use 80 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_100_MS</dt><dd>Use 100 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_120_MS</dt><dd>Use 120 ms frames.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_EXPERT_FRAME_DURATION(x) OPUS_GET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** If set to 1, disables almost all use of prediction, making frames almost
|
||||
* completely independent. This reduces quality.
|
||||
* @see OPUS_GET_PREDICTION_DISABLED
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Enable prediction (default).</dd>
|
||||
* <dt>1</dt><dd>Disable prediction.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_PREDICTION_DISABLED(x) OPUS_SET_PREDICTION_DISABLED_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's configured prediction status.
|
||||
* @see OPUS_SET_PREDICTION_DISABLED
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Prediction enabled (default).</dd>
|
||||
* <dt>1</dt><dd>Prediction disabled.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_PREDICTION_DISABLED(x) OPUS_GET_PREDICTION_DISABLED_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/**@}*/
|
||||
|
||||
/** @defgroup opus_genericctls Generic CTLs
|
||||
*
|
||||
* These macros are used with the \c opus_decoder_ctl and
|
||||
* \c opus_encoder_ctl calls to generate a particular
|
||||
* request.
|
||||
*
|
||||
* When called on an \c OpusDecoder they apply to that
|
||||
* particular decoder instance. When called on an
|
||||
* \c OpusEncoder they apply to the corresponding setting
|
||||
* on that encoder instance, if present.
|
||||
*
|
||||
* Some usage examples:
|
||||
*
|
||||
* @code
|
||||
* int ret;
|
||||
* opus_int32 pitch;
|
||||
* ret = opus_decoder_ctl(dec_ctx, OPUS_GET_PITCH(&pitch));
|
||||
* if (ret == OPUS_OK) return ret;
|
||||
*
|
||||
* opus_encoder_ctl(enc_ctx, OPUS_RESET_STATE);
|
||||
* opus_decoder_ctl(dec_ctx, OPUS_RESET_STATE);
|
||||
*
|
||||
* opus_int32 enc_bw, dec_bw;
|
||||
* opus_encoder_ctl(enc_ctx, OPUS_GET_BANDWIDTH(&enc_bw));
|
||||
* opus_decoder_ctl(dec_ctx, OPUS_GET_BANDWIDTH(&dec_bw));
|
||||
* if (enc_bw != dec_bw) {
|
||||
* printf("packet bandwidth mismatch!\n");
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @see opus_encoder, opus_decoder_ctl, opus_encoder_ctl, opus_decoderctls, opus_encoderctls
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Resets the codec state to be equivalent to a freshly initialized state.
|
||||
* This should be called when switching streams in order to prevent
|
||||
* the back to back decoding from giving different results from
|
||||
* one at a time decoding.
|
||||
* @hideinitializer */
|
||||
#define OPUS_RESET_STATE 4028
|
||||
|
||||
/** Gets the final state of the codec's entropy coder.
|
||||
* This is used for testing purposes,
|
||||
* The encoder and decoder state should be identical after coding a payload
|
||||
* (assuming no data corruption or software bugs)
|
||||
*
|
||||
* @param[out] x <tt>opus_uint32 *</tt>: Entropy coder state
|
||||
*
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_FINAL_RANGE(x) OPUS_GET_FINAL_RANGE_REQUEST, __opus_check_uint_ptr(x)
|
||||
|
||||
/** Gets the encoder's configured bandpass or the decoder's last bandpass.
|
||||
* @see OPUS_SET_BANDWIDTH
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_BANDWIDTH(x) OPUS_GET_BANDWIDTH_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Gets the sampling rate the encoder or decoder was initialized with.
|
||||
* This simply returns the <code>Fs</code> value passed to opus_encoder_init()
|
||||
* or opus_decoder_init().
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Sampling rate of encoder or decoder.
|
||||
* @hideinitializer
|
||||
*/
|
||||
#define OPUS_GET_SAMPLE_RATE(x) OPUS_GET_SAMPLE_RATE_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** If set to 1, disables the use of phase inversion for intensity stereo,
|
||||
* improving the quality of mono downmixes, but slightly reducing normal
|
||||
* stereo quality. Disabling phase inversion in the decoder does not comply
|
||||
* with RFC 6716, although it does not cause any interoperability issue and
|
||||
* is expected to become part of the Opus standard once RFC 6716 is updated
|
||||
* by draft-ietf-codec-opus-update.
|
||||
* @see OPUS_GET_PHASE_INVERSION_DISABLED
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Enable phase inversion (default).</dd>
|
||||
* <dt>1</dt><dd>Disable phase inversion.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_PHASE_INVERSION_DISABLED(x) OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's configured phase inversion status.
|
||||
* @see OPUS_SET_PHASE_INVERSION_DISABLED
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Stereo phase inversion enabled (default).</dd>
|
||||
* <dt>1</dt><dd>Stereo phase inversion disabled.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_PHASE_INVERSION_DISABLED(x) OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/**@}*/
|
||||
|
||||
/** @defgroup opus_decoderctls Decoder related CTLs
|
||||
* @see opus_genericctls, opus_encoderctls, opus_decoder
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Configures decoder gain adjustment.
|
||||
* Scales the decoded output by a factor specified in Q8 dB units.
|
||||
* This has a maximum range of -32768 to 32767 inclusive, and returns
|
||||
* OPUS_BAD_ARG otherwise. The default is zero indicating no adjustment.
|
||||
* This setting survives decoder reset.
|
||||
*
|
||||
* gain = pow(10, x/(20.0*256))
|
||||
*
|
||||
* @param[in] x <tt>opus_int32</tt>: Amount to scale PCM signal by in Q8 dB units.
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_GAIN(x) OPUS_SET_GAIN_REQUEST, __opus_check_int(x)
|
||||
/** Gets the decoder's configured gain adjustment. @see OPUS_SET_GAIN
|
||||
*
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Amount to scale PCM signal by in Q8 dB units.
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_GAIN(x) OPUS_GET_GAIN_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Gets the duration (in samples) of the last packet successfully decoded or concealed.
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Number of samples (at current sampling rate).
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_LAST_PACKET_DURATION(x) OPUS_GET_LAST_PACKET_DURATION_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Gets the pitch of the last decoded frame, if available.
|
||||
* This can be used for any post-processing algorithm requiring the use of pitch,
|
||||
* e.g. time stretching/shortening. If the last frame was not voiced, or if the
|
||||
* pitch was not coded in the frame, then zero is returned.
|
||||
*
|
||||
* This CTL is only implemented for decoder instances.
|
||||
*
|
||||
* @param[out] x <tt>opus_int32 *</tt>: pitch period at 48 kHz (or 0 if not available)
|
||||
*
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_PITCH(x) OPUS_GET_PITCH_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/**@}*/
|
||||
|
||||
/** @defgroup opus_libinfo Opus library information functions
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Converts an opus error code into a human readable string.
|
||||
*
|
||||
* @param[in] error <tt>int</tt>: Error number
|
||||
* @returns Error string
|
||||
*/
|
||||
OPUS_EXPORT const char *opus_strerror(int error);
|
||||
|
||||
/** Gets the libopus version string.
|
||||
*
|
||||
* Applications may look for the substring "-fixed" in the version string to
|
||||
* determine whether they have a fixed-point or floating-point build at
|
||||
* runtime.
|
||||
*
|
||||
* @returns Version string
|
||||
*/
|
||||
OPUS_EXPORT const char *opus_get_version_string(void);
|
||||
/**@}*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPUS_DEFINES_H */
|
||||
@@ -0,0 +1,660 @@
|
||||
/* Copyright (c) 2011 Xiph.Org Foundation
|
||||
Written by Jean-Marc Valin */
|
||||
/*
|
||||
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.
|
||||
|
||||
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 OWNER
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file opus_multistream.h
|
||||
* @brief Opus reference implementation multistream API
|
||||
*/
|
||||
|
||||
#ifndef OPUS_MULTISTREAM_H
|
||||
#define OPUS_MULTISTREAM_H
|
||||
|
||||
#include "opus.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @cond OPUS_INTERNAL_DOC */
|
||||
|
||||
/** Macros to trigger compilation errors when the wrong types are provided to a
|
||||
* CTL. */
|
||||
/**@{*/
|
||||
#define __opus_check_encstate_ptr(ptr) ((ptr) + ((ptr) - (OpusEncoder**)(ptr)))
|
||||
#define __opus_check_decstate_ptr(ptr) ((ptr) + ((ptr) - (OpusDecoder**)(ptr)))
|
||||
/**@}*/
|
||||
|
||||
/** These are the actual encoder and decoder CTL ID numbers.
|
||||
* They should not be used directly by applications.
|
||||
* In general, SETs should be even and GETs should be odd.*/
|
||||
/**@{*/
|
||||
#define OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST 5120
|
||||
#define OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST 5122
|
||||
/**@}*/
|
||||
|
||||
/** @endcond */
|
||||
|
||||
/** @defgroup opus_multistream_ctls Multistream specific encoder and decoder CTLs
|
||||
*
|
||||
* These are convenience macros that are specific to the
|
||||
* opus_multistream_encoder_ctl() and opus_multistream_decoder_ctl()
|
||||
* interface.
|
||||
* The CTLs from @ref opus_genericctls, @ref opus_encoderctls, and
|
||||
* @ref opus_decoderctls may be applied to a multistream encoder or decoder as
|
||||
* well.
|
||||
* In addition, you may retrieve the encoder or decoder state for an specific
|
||||
* stream via #OPUS_MULTISTREAM_GET_ENCODER_STATE or
|
||||
* #OPUS_MULTISTREAM_GET_DECODER_STATE and apply CTLs to it individually.
|
||||
*/
|
||||
/**@{*/
|
||||
|
||||
/** Gets the encoder state for an individual stream of a multistream encoder.
|
||||
* @param[in] x <tt>opus_int32</tt>: The index of the stream whose encoder you
|
||||
* wish to retrieve.
|
||||
* This must be non-negative and less than
|
||||
* the <code>streams</code> parameter used
|
||||
* to initialize the encoder.
|
||||
* @param[out] y <tt>OpusEncoder**</tt>: Returns a pointer to the given
|
||||
* encoder state.
|
||||
* @retval OPUS_BAD_ARG The index of the requested stream was out of range.
|
||||
* @hideinitializer
|
||||
*/
|
||||
#define OPUS_MULTISTREAM_GET_ENCODER_STATE(x,y) OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST, __opus_check_int(x), __opus_check_encstate_ptr(y)
|
||||
|
||||
/** Gets the decoder state for an individual stream of a multistream decoder.
|
||||
* @param[in] x <tt>opus_int32</tt>: The index of the stream whose decoder you
|
||||
* wish to retrieve.
|
||||
* This must be non-negative and less than
|
||||
* the <code>streams</code> parameter used
|
||||
* to initialize the decoder.
|
||||
* @param[out] y <tt>OpusDecoder**</tt>: Returns a pointer to the given
|
||||
* decoder state.
|
||||
* @retval OPUS_BAD_ARG The index of the requested stream was out of range.
|
||||
* @hideinitializer
|
||||
*/
|
||||
#define OPUS_MULTISTREAM_GET_DECODER_STATE(x,y) OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST, __opus_check_int(x), __opus_check_decstate_ptr(y)
|
||||
|
||||
/**@}*/
|
||||
|
||||
/** @defgroup opus_multistream Opus Multistream API
|
||||
* @{
|
||||
*
|
||||
* The multistream API allows individual Opus streams to be combined into a
|
||||
* single packet, enabling support for up to 255 channels. Unlike an
|
||||
* elementary Opus stream, the encoder and decoder must negotiate the channel
|
||||
* configuration before the decoder can successfully interpret the data in the
|
||||
* packets produced by the encoder. Some basic information, such as packet
|
||||
* duration, can be computed without any special negotiation.
|
||||
*
|
||||
* The format for multistream Opus packets is defined in
|
||||
* <a href="https://tools.ietf.org/html/rfc7845">RFC 7845</a>
|
||||
* and is based on the self-delimited Opus framing described in Appendix B of
|
||||
* <a href="https://tools.ietf.org/html/rfc6716">RFC 6716</a>.
|
||||
* Normal Opus packets are just a degenerate case of multistream Opus packets,
|
||||
* and can be encoded or decoded with the multistream API by setting
|
||||
* <code>streams</code> to <code>1</code> when initializing the encoder or
|
||||
* decoder.
|
||||
*
|
||||
* Multistream Opus streams can contain up to 255 elementary Opus streams.
|
||||
* These may be either "uncoupled" or "coupled", indicating that the decoder
|
||||
* is configured to decode them to either 1 or 2 channels, respectively.
|
||||
* The streams are ordered so that all coupled streams appear at the
|
||||
* beginning.
|
||||
*
|
||||
* A <code>mapping</code> table defines which decoded channel <code>i</code>
|
||||
* should be used for each input/output (I/O) channel <code>j</code>. This table is
|
||||
* typically provided as an unsigned char array.
|
||||
* Let <code>i = mapping[j]</code> be the index for I/O channel <code>j</code>.
|
||||
* If <code>i < 2*coupled_streams</code>, then I/O channel <code>j</code> is
|
||||
* encoded as the left channel of stream <code>(i/2)</code> if <code>i</code>
|
||||
* is even, or as the right channel of stream <code>(i/2)</code> if
|
||||
* <code>i</code> is odd. Otherwise, I/O channel <code>j</code> is encoded as
|
||||
* mono in stream <code>(i - coupled_streams)</code>, unless it has the special
|
||||
* value 255, in which case it is omitted from the encoding entirely (the
|
||||
* decoder will reproduce it as silence). Each value <code>i</code> must either
|
||||
* be the special value 255 or be less than <code>streams + coupled_streams</code>.
|
||||
*
|
||||
* The output channels specified by the encoder
|
||||
* should use the
|
||||
* <a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810004.3.9">Vorbis
|
||||
* channel ordering</a>. A decoder may wish to apply an additional permutation
|
||||
* to the mapping the encoder used to achieve a different output channel
|
||||
* order (e.g. for outputing in WAV order).
|
||||
*
|
||||
* Each multistream packet contains an Opus packet for each stream, and all of
|
||||
* the Opus packets in a single multistream packet must have the same
|
||||
* duration. Therefore the duration of a multistream packet can be extracted
|
||||
* from the TOC sequence of the first stream, which is located at the
|
||||
* beginning of the packet, just like an elementary Opus stream:
|
||||
*
|
||||
* @code
|
||||
* int nb_samples;
|
||||
* int nb_frames;
|
||||
* nb_frames = opus_packet_get_nb_frames(data, len);
|
||||
* if (nb_frames < 1)
|
||||
* return nb_frames;
|
||||
* nb_samples = opus_packet_get_samples_per_frame(data, 48000) * nb_frames;
|
||||
* @endcode
|
||||
*
|
||||
* The general encoding and decoding process proceeds exactly the same as in
|
||||
* the normal @ref opus_encoder and @ref opus_decoder APIs.
|
||||
* See their documentation for an overview of how to use the corresponding
|
||||
* multistream functions.
|
||||
*/
|
||||
|
||||
/** Opus multistream encoder state.
|
||||
* This contains the complete state of a multistream Opus encoder.
|
||||
* It is position independent and can be freely copied.
|
||||
* @see opus_multistream_encoder_create
|
||||
* @see opus_multistream_encoder_init
|
||||
*/
|
||||
typedef struct OpusMSEncoder OpusMSEncoder;
|
||||
|
||||
/** Opus multistream decoder state.
|
||||
* This contains the complete state of a multistream Opus decoder.
|
||||
* It is position independent and can be freely copied.
|
||||
* @see opus_multistream_decoder_create
|
||||
* @see opus_multistream_decoder_init
|
||||
*/
|
||||
typedef struct OpusMSDecoder OpusMSDecoder;
|
||||
|
||||
/**\name Multistream encoder functions */
|
||||
/**@{*/
|
||||
|
||||
/** Gets the size of an OpusMSEncoder structure.
|
||||
* @param streams <tt>int</tt>: The total number of streams to encode from the
|
||||
* input.
|
||||
* This must be no more than 255.
|
||||
* @param coupled_streams <tt>int</tt>: Number of coupled (2 channel) streams
|
||||
* to encode.
|
||||
* This must be no larger than the total
|
||||
* number of streams.
|
||||
* Additionally, The total number of
|
||||
* encoded channels (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than 255.
|
||||
* @returns The size in bytes on success, or a negative error code
|
||||
* (see @ref opus_errorcodes) on error.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_encoder_get_size(
|
||||
int streams,
|
||||
int coupled_streams
|
||||
);
|
||||
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_surround_encoder_get_size(
|
||||
int channels,
|
||||
int mapping_family
|
||||
);
|
||||
|
||||
|
||||
/** Allocates and initializes a multistream encoder state.
|
||||
* Call opus_multistream_encoder_destroy() to release
|
||||
* this object when finished.
|
||||
* @param Fs <tt>opus_int32</tt>: Sampling rate of the input signal (in Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param channels <tt>int</tt>: Number of channels in the input signal.
|
||||
* This must be at most 255.
|
||||
* It may be greater than the number of
|
||||
* coded channels (<code>streams +
|
||||
* coupled_streams</code>).
|
||||
* @param streams <tt>int</tt>: The total number of streams to encode from the
|
||||
* input.
|
||||
* This must be no more than the number of channels.
|
||||
* @param coupled_streams <tt>int</tt>: Number of coupled (2 channel) streams
|
||||
* to encode.
|
||||
* This must be no larger than the total
|
||||
* number of streams.
|
||||
* Additionally, The total number of
|
||||
* encoded channels (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than the number of input channels.
|
||||
* @param[in] mapping <code>const unsigned char[channels]</code>: Mapping from
|
||||
* encoded channels to input channels, as described in
|
||||
* @ref opus_multistream. As an extra constraint, the
|
||||
* multistream encoder does not allow encoding coupled
|
||||
* streams for which one channel is unused since this
|
||||
* is never a good idea.
|
||||
* @param application <tt>int</tt>: The target encoder application.
|
||||
* This must be one of the following:
|
||||
* <dl>
|
||||
* <dt>#OPUS_APPLICATION_VOIP</dt>
|
||||
* <dd>Process signal for improved speech intelligibility.</dd>
|
||||
* <dt>#OPUS_APPLICATION_AUDIO</dt>
|
||||
* <dd>Favor faithfulness to the original input.</dd>
|
||||
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
|
||||
* <dd>Configure the minimum possible coding delay by disabling certain modes
|
||||
* of operation.</dd>
|
||||
* </dl>
|
||||
* @param[out] error <tt>int *</tt>: Returns #OPUS_OK on success, or an error
|
||||
* code (see @ref opus_errorcodes) on
|
||||
* failure.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_encoder_create(
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int streams,
|
||||
int coupled_streams,
|
||||
const unsigned char *mapping,
|
||||
int application,
|
||||
int *error
|
||||
) OPUS_ARG_NONNULL(5);
|
||||
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_surround_encoder_create(
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int mapping_family,
|
||||
int *streams,
|
||||
int *coupled_streams,
|
||||
unsigned char *mapping,
|
||||
int application,
|
||||
int *error
|
||||
) OPUS_ARG_NONNULL(4) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6);
|
||||
|
||||
/** Initialize a previously allocated multistream encoder state.
|
||||
* The memory pointed to by \a st must be at least the size returned by
|
||||
* opus_multistream_encoder_get_size().
|
||||
* This is intended for applications which use their own allocator instead of
|
||||
* malloc.
|
||||
* To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
|
||||
* @see opus_multistream_encoder_create
|
||||
* @see opus_multistream_encoder_get_size
|
||||
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state to initialize.
|
||||
* @param Fs <tt>opus_int32</tt>: Sampling rate of the input signal (in Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param channels <tt>int</tt>: Number of channels in the input signal.
|
||||
* This must be at most 255.
|
||||
* It may be greater than the number of
|
||||
* coded channels (<code>streams +
|
||||
* coupled_streams</code>).
|
||||
* @param streams <tt>int</tt>: The total number of streams to encode from the
|
||||
* input.
|
||||
* This must be no more than the number of channels.
|
||||
* @param coupled_streams <tt>int</tt>: Number of coupled (2 channel) streams
|
||||
* to encode.
|
||||
* This must be no larger than the total
|
||||
* number of streams.
|
||||
* Additionally, The total number of
|
||||
* encoded channels (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than the number of input channels.
|
||||
* @param[in] mapping <code>const unsigned char[channels]</code>: Mapping from
|
||||
* encoded channels to input channels, as described in
|
||||
* @ref opus_multistream. As an extra constraint, the
|
||||
* multistream encoder does not allow encoding coupled
|
||||
* streams for which one channel is unused since this
|
||||
* is never a good idea.
|
||||
* @param application <tt>int</tt>: The target encoder application.
|
||||
* This must be one of the following:
|
||||
* <dl>
|
||||
* <dt>#OPUS_APPLICATION_VOIP</dt>
|
||||
* <dd>Process signal for improved speech intelligibility.</dd>
|
||||
* <dt>#OPUS_APPLICATION_AUDIO</dt>
|
||||
* <dd>Favor faithfulness to the original input.</dd>
|
||||
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
|
||||
* <dd>Configure the minimum possible coding delay by disabling certain modes
|
||||
* of operation.</dd>
|
||||
* </dl>
|
||||
* @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes)
|
||||
* on failure.
|
||||
*/
|
||||
OPUS_EXPORT int opus_multistream_encoder_init(
|
||||
OpusMSEncoder *st,
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int streams,
|
||||
int coupled_streams,
|
||||
const unsigned char *mapping,
|
||||
int application
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6);
|
||||
|
||||
OPUS_EXPORT int opus_multistream_surround_encoder_init(
|
||||
OpusMSEncoder *st,
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int mapping_family,
|
||||
int *streams,
|
||||
int *coupled_streams,
|
||||
unsigned char *mapping,
|
||||
int application
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6) OPUS_ARG_NONNULL(7);
|
||||
|
||||
/** Encodes a multistream Opus frame.
|
||||
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state.
|
||||
* @param[in] pcm <tt>const opus_int16*</tt>: The input signal as interleaved
|
||||
* samples.
|
||||
* This must contain
|
||||
* <code>frame_size*channels</code>
|
||||
* samples.
|
||||
* @param frame_size <tt>int</tt>: Number of samples per channel in the input
|
||||
* signal.
|
||||
* This must be an Opus frame size for the
|
||||
* encoder's sampling rate.
|
||||
* For example, at 48 kHz the permitted values
|
||||
* are 120, 240, 480, 960, 1920, and 2880.
|
||||
* Passing in a duration of less than 10 ms
|
||||
* (480 samples at 48 kHz) will prevent the
|
||||
* encoder from using the LPC or hybrid modes.
|
||||
* @param[out] data <tt>unsigned char*</tt>: Output payload.
|
||||
* This must contain storage for at
|
||||
* least \a max_data_bytes.
|
||||
* @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
|
||||
* memory for the output
|
||||
* payload. This may be
|
||||
* used to impose an upper limit on
|
||||
* the instant bitrate, but should
|
||||
* not be used as the only bitrate
|
||||
* control. Use #OPUS_SET_BITRATE to
|
||||
* control the bitrate.
|
||||
* @returns The length of the encoded packet (in bytes) on success or a
|
||||
* negative error code (see @ref opus_errorcodes) on failure.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode(
|
||||
OpusMSEncoder *st,
|
||||
const opus_int16 *pcm,
|
||||
int frame_size,
|
||||
unsigned char *data,
|
||||
opus_int32 max_data_bytes
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Encodes a multistream Opus frame from floating point input.
|
||||
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state.
|
||||
* @param[in] pcm <tt>const float*</tt>: The input signal as interleaved
|
||||
* samples with a normal range of
|
||||
* +/-1.0.
|
||||
* Samples with a range beyond +/-1.0
|
||||
* are supported but will be clipped by
|
||||
* decoders using the integer API and
|
||||
* should only be used if it is known
|
||||
* that the far end supports extended
|
||||
* dynamic range.
|
||||
* This must contain
|
||||
* <code>frame_size*channels</code>
|
||||
* samples.
|
||||
* @param frame_size <tt>int</tt>: Number of samples per channel in the input
|
||||
* signal.
|
||||
* This must be an Opus frame size for the
|
||||
* encoder's sampling rate.
|
||||
* For example, at 48 kHz the permitted values
|
||||
* are 120, 240, 480, 960, 1920, and 2880.
|
||||
* Passing in a duration of less than 10 ms
|
||||
* (480 samples at 48 kHz) will prevent the
|
||||
* encoder from using the LPC or hybrid modes.
|
||||
* @param[out] data <tt>unsigned char*</tt>: Output payload.
|
||||
* This must contain storage for at
|
||||
* least \a max_data_bytes.
|
||||
* @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
|
||||
* memory for the output
|
||||
* payload. This may be
|
||||
* used to impose an upper limit on
|
||||
* the instant bitrate, but should
|
||||
* not be used as the only bitrate
|
||||
* control. Use #OPUS_SET_BITRATE to
|
||||
* control the bitrate.
|
||||
* @returns The length of the encoded packet (in bytes) on success or a
|
||||
* negative error code (see @ref opus_errorcodes) on failure.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode_float(
|
||||
OpusMSEncoder *st,
|
||||
const float *pcm,
|
||||
int frame_size,
|
||||
unsigned char *data,
|
||||
opus_int32 max_data_bytes
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Frees an <code>OpusMSEncoder</code> allocated by
|
||||
* opus_multistream_encoder_create().
|
||||
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state to be freed.
|
||||
*/
|
||||
OPUS_EXPORT void opus_multistream_encoder_destroy(OpusMSEncoder *st);
|
||||
|
||||
/** Perform a CTL function on a multistream Opus encoder.
|
||||
*
|
||||
* Generally the request and subsequent arguments are generated by a
|
||||
* convenience macro.
|
||||
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state.
|
||||
* @param request This and all remaining parameters should be replaced by one
|
||||
* of the convenience macros in @ref opus_genericctls,
|
||||
* @ref opus_encoderctls, or @ref opus_multistream_ctls.
|
||||
* @see opus_genericctls
|
||||
* @see opus_encoderctls
|
||||
* @see opus_multistream_ctls
|
||||
*/
|
||||
OPUS_EXPORT int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/**@}*/
|
||||
|
||||
/**\name Multistream decoder functions */
|
||||
/**@{*/
|
||||
|
||||
/** Gets the size of an <code>OpusMSDecoder</code> structure.
|
||||
* @param streams <tt>int</tt>: The total number of streams coded in the
|
||||
* input.
|
||||
* This must be no more than 255.
|
||||
* @param coupled_streams <tt>int</tt>: Number streams to decode as coupled
|
||||
* (2 channel) streams.
|
||||
* This must be no larger than the total
|
||||
* number of streams.
|
||||
* Additionally, The total number of
|
||||
* coded channels (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than 255.
|
||||
* @returns The size in bytes on success, or a negative error code
|
||||
* (see @ref opus_errorcodes) on error.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_decoder_get_size(
|
||||
int streams,
|
||||
int coupled_streams
|
||||
);
|
||||
|
||||
/** Allocates and initializes a multistream decoder state.
|
||||
* Call opus_multistream_decoder_destroy() to release
|
||||
* this object when finished.
|
||||
* @param Fs <tt>opus_int32</tt>: Sampling rate to decode at (in Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param channels <tt>int</tt>: Number of channels to output.
|
||||
* This must be at most 255.
|
||||
* It may be different from the number of coded
|
||||
* channels (<code>streams +
|
||||
* coupled_streams</code>).
|
||||
* @param streams <tt>int</tt>: The total number of streams coded in the
|
||||
* input.
|
||||
* This must be no more than 255.
|
||||
* @param coupled_streams <tt>int</tt>: Number of streams to decode as coupled
|
||||
* (2 channel) streams.
|
||||
* This must be no larger than the total
|
||||
* number of streams.
|
||||
* Additionally, The total number of
|
||||
* coded channels (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than 255.
|
||||
* @param[in] mapping <code>const unsigned char[channels]</code>: Mapping from
|
||||
* coded channels to output channels, as described in
|
||||
* @ref opus_multistream.
|
||||
* @param[out] error <tt>int *</tt>: Returns #OPUS_OK on success, or an error
|
||||
* code (see @ref opus_errorcodes) on
|
||||
* failure.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSDecoder *opus_multistream_decoder_create(
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int streams,
|
||||
int coupled_streams,
|
||||
const unsigned char *mapping,
|
||||
int *error
|
||||
) OPUS_ARG_NONNULL(5);
|
||||
|
||||
/** Intialize a previously allocated decoder state object.
|
||||
* The memory pointed to by \a st must be at least the size returned by
|
||||
* opus_multistream_encoder_get_size().
|
||||
* This is intended for applications which use their own allocator instead of
|
||||
* malloc.
|
||||
* To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
|
||||
* @see opus_multistream_decoder_create
|
||||
* @see opus_multistream_deocder_get_size
|
||||
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state to initialize.
|
||||
* @param Fs <tt>opus_int32</tt>: Sampling rate to decode at (in Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param channels <tt>int</tt>: Number of channels to output.
|
||||
* This must be at most 255.
|
||||
* It may be different from the number of coded
|
||||
* channels (<code>streams +
|
||||
* coupled_streams</code>).
|
||||
* @param streams <tt>int</tt>: The total number of streams coded in the
|
||||
* input.
|
||||
* This must be no more than 255.
|
||||
* @param coupled_streams <tt>int</tt>: Number of streams to decode as coupled
|
||||
* (2 channel) streams.
|
||||
* This must be no larger than the total
|
||||
* number of streams.
|
||||
* Additionally, The total number of
|
||||
* coded channels (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than 255.
|
||||
* @param[in] mapping <code>const unsigned char[channels]</code>: Mapping from
|
||||
* coded channels to output channels, as described in
|
||||
* @ref opus_multistream.
|
||||
* @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes)
|
||||
* on failure.
|
||||
*/
|
||||
OPUS_EXPORT int opus_multistream_decoder_init(
|
||||
OpusMSDecoder *st,
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int streams,
|
||||
int coupled_streams,
|
||||
const unsigned char *mapping
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6);
|
||||
|
||||
/** Decode a multistream Opus packet.
|
||||
* @param st <tt>OpusMSDecoder*</tt>: Multistream decoder state.
|
||||
* @param[in] data <tt>const unsigned char*</tt>: Input payload.
|
||||
* Use a <code>NULL</code>
|
||||
* pointer to indicate packet
|
||||
* loss.
|
||||
* @param len <tt>opus_int32</tt>: Number of bytes in payload.
|
||||
* @param[out] pcm <tt>opus_int16*</tt>: Output signal, with interleaved
|
||||
* samples.
|
||||
* This must contain room for
|
||||
* <code>frame_size*channels</code>
|
||||
* samples.
|
||||
* @param frame_size <tt>int</tt>: The number of samples per channel of
|
||||
* available space in \a pcm.
|
||||
* If this is less than the maximum packet duration
|
||||
* (120 ms; 5760 for 48kHz), this function will not be capable
|
||||
* of decoding some packets. In the case of PLC (data==NULL)
|
||||
* or FEC (decode_fec=1), then frame_size needs to be exactly
|
||||
* the duration of audio that is missing, otherwise the
|
||||
* decoder will not be in the optimal state to decode the
|
||||
* next incoming packet. For the PLC and FEC cases, frame_size
|
||||
* <b>must</b> be a multiple of 2.5 ms.
|
||||
* @param decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band
|
||||
* forward error correction data be decoded.
|
||||
* If no such data is available, the frame is
|
||||
* decoded as if it were lost.
|
||||
* @returns Number of samples decoded on success or a negative error code
|
||||
* (see @ref opus_errorcodes) on failure.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode(
|
||||
OpusMSDecoder *st,
|
||||
const unsigned char *data,
|
||||
opus_int32 len,
|
||||
opus_int16 *pcm,
|
||||
int frame_size,
|
||||
int decode_fec
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Decode a multistream Opus packet with floating point output.
|
||||
* @param st <tt>OpusMSDecoder*</tt>: Multistream decoder state.
|
||||
* @param[in] data <tt>const unsigned char*</tt>: Input payload.
|
||||
* Use a <code>NULL</code>
|
||||
* pointer to indicate packet
|
||||
* loss.
|
||||
* @param len <tt>opus_int32</tt>: Number of bytes in payload.
|
||||
* @param[out] pcm <tt>opus_int16*</tt>: Output signal, with interleaved
|
||||
* samples.
|
||||
* This must contain room for
|
||||
* <code>frame_size*channels</code>
|
||||
* samples.
|
||||
* @param frame_size <tt>int</tt>: The number of samples per channel of
|
||||
* available space in \a pcm.
|
||||
* If this is less than the maximum packet duration
|
||||
* (120 ms; 5760 for 48kHz), this function will not be capable
|
||||
* of decoding some packets. In the case of PLC (data==NULL)
|
||||
* or FEC (decode_fec=1), then frame_size needs to be exactly
|
||||
* the duration of audio that is missing, otherwise the
|
||||
* decoder will not be in the optimal state to decode the
|
||||
* next incoming packet. For the PLC and FEC cases, frame_size
|
||||
* <b>must</b> be a multiple of 2.5 ms.
|
||||
* @param decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band
|
||||
* forward error correction data be decoded.
|
||||
* If no such data is available, the frame is
|
||||
* decoded as if it were lost.
|
||||
* @returns Number of samples decoded on success or a negative error code
|
||||
* (see @ref opus_errorcodes) on failure.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode_float(
|
||||
OpusMSDecoder *st,
|
||||
const unsigned char *data,
|
||||
opus_int32 len,
|
||||
float *pcm,
|
||||
int frame_size,
|
||||
int decode_fec
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Perform a CTL function on a multistream Opus decoder.
|
||||
*
|
||||
* Generally the request and subsequent arguments are generated by a
|
||||
* convenience macro.
|
||||
* @param st <tt>OpusMSDecoder*</tt>: Multistream decoder state.
|
||||
* @param request This and all remaining parameters should be replaced by one
|
||||
* of the convenience macros in @ref opus_genericctls,
|
||||
* @ref opus_decoderctls, or @ref opus_multistream_ctls.
|
||||
* @see opus_genericctls
|
||||
* @see opus_decoderctls
|
||||
* @see opus_multistream_ctls
|
||||
*/
|
||||
OPUS_EXPORT int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Frees an <code>OpusMSDecoder</code> allocated by
|
||||
* opus_multistream_decoder_create().
|
||||
* @param st <tt>OpusMSDecoder</tt>: Multistream decoder state to be freed.
|
||||
*/
|
||||
OPUS_EXPORT void opus_multistream_decoder_destroy(OpusMSDecoder *st);
|
||||
|
||||
/**@}*/
|
||||
|
||||
/**@}*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPUS_MULTISTREAM_H */
|
||||
@@ -0,0 +1,159 @@
|
||||
/* (C) COPYRIGHT 1994-2002 Xiph.Org Foundation */
|
||||
/* Modified by Jean-Marc Valin */
|
||||
/*
|
||||
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.
|
||||
|
||||
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 OWNER
|
||||
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.
|
||||
*/
|
||||
/* opus_types.h based on ogg_types.h from libogg */
|
||||
|
||||
/**
|
||||
@file opus_types.h
|
||||
@brief Opus reference implementation types
|
||||
*/
|
||||
#ifndef OPUS_TYPES_H
|
||||
#define OPUS_TYPES_H
|
||||
|
||||
/* Use the real stdint.h if it's there (taken from Paul Hsieh's pstdint.h) */
|
||||
#if (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_)) || defined (HAVE_STDINT_H))
|
||||
#include <stdint.h>
|
||||
|
||||
typedef int16_t opus_int16;
|
||||
typedef uint16_t opus_uint16;
|
||||
typedef int32_t opus_int32;
|
||||
typedef uint32_t opus_uint32;
|
||||
#elif defined(_WIN32)
|
||||
|
||||
# if defined(__CYGWIN__)
|
||||
# include <_G_config.h>
|
||||
typedef _G_int32_t opus_int32;
|
||||
typedef _G_uint32_t opus_uint32;
|
||||
typedef _G_int16 opus_int16;
|
||||
typedef _G_uint16 opus_uint16;
|
||||
# elif defined(__MINGW32__)
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
# elif defined(__MWERKS__)
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
# else
|
||||
/* MSVC/Borland */
|
||||
typedef __int32 opus_int32;
|
||||
typedef unsigned __int32 opus_uint32;
|
||||
typedef __int16 opus_int16;
|
||||
typedef unsigned __int16 opus_uint16;
|
||||
# endif
|
||||
|
||||
#elif defined(__MACOS__)
|
||||
|
||||
# include <sys/types.h>
|
||||
typedef SInt16 opus_int16;
|
||||
typedef UInt16 opus_uint16;
|
||||
typedef SInt32 opus_int32;
|
||||
typedef UInt32 opus_uint32;
|
||||
|
||||
#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */
|
||||
|
||||
# include <sys/types.h>
|
||||
typedef int16_t opus_int16;
|
||||
typedef u_int16_t opus_uint16;
|
||||
typedef int32_t opus_int32;
|
||||
typedef u_int32_t opus_uint32;
|
||||
|
||||
#elif defined(__BEOS__)
|
||||
|
||||
/* Be */
|
||||
# include <inttypes.h>
|
||||
typedef int16 opus_int16;
|
||||
typedef u_int16 opus_uint16;
|
||||
typedef int32_t opus_int32;
|
||||
typedef u_int32_t opus_uint32;
|
||||
|
||||
#elif defined (__EMX__)
|
||||
|
||||
/* OS/2 GCC */
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
|
||||
#elif defined (DJGPP)
|
||||
|
||||
/* DJGPP */
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
|
||||
#elif defined(R5900)
|
||||
|
||||
/* PS2 EE */
|
||||
typedef int opus_int32;
|
||||
typedef unsigned opus_uint32;
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
|
||||
#elif defined(__SYMBIAN32__)
|
||||
|
||||
/* Symbian GCC */
|
||||
typedef signed short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef signed int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
|
||||
#elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X)
|
||||
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef long opus_int32;
|
||||
typedef unsigned long opus_uint32;
|
||||
|
||||
#elif defined(CONFIG_TI_C6X)
|
||||
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
|
||||
#else
|
||||
|
||||
/* Give up, take a reasonable guess */
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
|
||||
#endif
|
||||
|
||||
#define opus_int int /* used for counters etc; at least 16 bits */
|
||||
#define opus_int64 long long
|
||||
#define opus_int8 signed char
|
||||
|
||||
#define opus_uint unsigned int /* used for counters etc; at least 16 bits */
|
||||
#define opus_uint64 unsigned long long
|
||||
#define opus_uint8 unsigned char
|
||||
|
||||
#endif /* OPUS_TYPES_H */
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,981 @@
|
||||
/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited
|
||||
Written by Jean-Marc Valin and Koen Vos */
|
||||
/*
|
||||
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.
|
||||
|
||||
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 OWNER
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file opus.h
|
||||
* @brief Opus reference implementation API
|
||||
*/
|
||||
|
||||
#ifndef OPUS_H
|
||||
#define OPUS_H
|
||||
|
||||
#include "opus_types.h"
|
||||
#include "opus_defines.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @mainpage Opus
|
||||
*
|
||||
* The Opus codec is designed for interactive speech and audio transmission over the Internet.
|
||||
* It is designed by the IETF Codec Working Group and incorporates technology from
|
||||
* Skype's SILK codec and Xiph.Org's CELT codec.
|
||||
*
|
||||
* The Opus codec is designed to handle a wide range of interactive audio applications,
|
||||
* including Voice over IP, videoconferencing, in-game chat, and even remote live music
|
||||
* performances. It can scale from low bit-rate narrowband speech to very high quality
|
||||
* stereo music. Its main features are:
|
||||
|
||||
* @li Sampling rates from 8 to 48 kHz
|
||||
* @li Bit-rates from 6 kb/s to 510 kb/s
|
||||
* @li Support for both constant bit-rate (CBR) and variable bit-rate (VBR)
|
||||
* @li Audio bandwidth from narrowband to full-band
|
||||
* @li Support for speech and music
|
||||
* @li Support for mono and stereo
|
||||
* @li Support for multichannel (up to 255 channels)
|
||||
* @li Frame sizes from 2.5 ms to 60 ms
|
||||
* @li Good loss robustness and packet loss concealment (PLC)
|
||||
* @li Floating point and fixed-point implementation
|
||||
*
|
||||
* Documentation sections:
|
||||
* @li @ref opus_encoder
|
||||
* @li @ref opus_decoder
|
||||
* @li @ref opus_repacketizer
|
||||
* @li @ref opus_multistream
|
||||
* @li @ref opus_libinfo
|
||||
* @li @ref opus_custom
|
||||
*/
|
||||
|
||||
/** @defgroup opus_encoder Opus Encoder
|
||||
* @{
|
||||
*
|
||||
* @brief This page describes the process and functions used to encode Opus.
|
||||
*
|
||||
* Since Opus is a stateful codec, the encoding process starts with creating an encoder
|
||||
* state. This can be done with:
|
||||
*
|
||||
* @code
|
||||
* int error;
|
||||
* OpusEncoder *enc;
|
||||
* enc = opus_encoder_create(Fs, channels, application, &error);
|
||||
* @endcode
|
||||
*
|
||||
* From this point, @c enc can be used for encoding an audio stream. An encoder state
|
||||
* @b must @b not be used for more than one stream at the same time. Similarly, the encoder
|
||||
* state @b must @b not be re-initialized for each frame.
|
||||
*
|
||||
* While opus_encoder_create() allocates memory for the state, it's also possible
|
||||
* to initialize pre-allocated memory:
|
||||
*
|
||||
* @code
|
||||
* int size;
|
||||
* int error;
|
||||
* OpusEncoder *enc;
|
||||
* size = opus_encoder_get_size(channels);
|
||||
* enc = malloc(size);
|
||||
* error = opus_encoder_init(enc, Fs, channels, application);
|
||||
* @endcode
|
||||
*
|
||||
* where opus_encoder_get_size() returns the required size for the encoder state. Note that
|
||||
* future versions of this code may change the size, so no assuptions should be made about it.
|
||||
*
|
||||
* The encoder state is always continuous in memory and only a shallow copy is sufficient
|
||||
* to copy it (e.g. memcpy())
|
||||
*
|
||||
* It is possible to change some of the encoder's settings using the opus_encoder_ctl()
|
||||
* interface. All these settings already default to the recommended value, so they should
|
||||
* only be changed when necessary. The most common settings one may want to change are:
|
||||
*
|
||||
* @code
|
||||
* opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate));
|
||||
* opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity));
|
||||
* opus_encoder_ctl(enc, OPUS_SET_SIGNAL(signal_type));
|
||||
* @endcode
|
||||
*
|
||||
* where
|
||||
*
|
||||
* @arg bitrate is in bits per second (b/s)
|
||||
* @arg complexity is a value from 1 to 10, where 1 is the lowest complexity and 10 is the highest
|
||||
* @arg signal_type is either OPUS_AUTO (default), OPUS_SIGNAL_VOICE, or OPUS_SIGNAL_MUSIC
|
||||
*
|
||||
* See @ref opus_encoderctls and @ref opus_genericctls for a complete list of parameters that can be set or queried. Most parameters can be set or changed at any time during a stream.
|
||||
*
|
||||
* To encode a frame, opus_encode() or opus_encode_float() must be called with exactly one frame (2.5, 5, 10, 20, 40 or 60 ms) of audio data:
|
||||
* @code
|
||||
* len = opus_encode(enc, audio_frame, frame_size, packet, max_packet);
|
||||
* @endcode
|
||||
*
|
||||
* where
|
||||
* <ul>
|
||||
* <li>audio_frame is the audio data in opus_int16 (or float for opus_encode_float())</li>
|
||||
* <li>frame_size is the duration of the frame in samples (per channel)</li>
|
||||
* <li>packet is the byte array to which the compressed data is written</li>
|
||||
* <li>max_packet is the maximum number of bytes that can be written in the packet (4000 bytes is recommended).
|
||||
* Do not use max_packet to control VBR target bitrate, instead use the #OPUS_SET_BITRATE CTL.</li>
|
||||
* </ul>
|
||||
*
|
||||
* opus_encode() and opus_encode_float() return the number of bytes actually written to the packet.
|
||||
* The return value <b>can be negative</b>, which indicates that an error has occurred. If the return value
|
||||
* is 2 bytes or less, then the packet does not need to be transmitted (DTX).
|
||||
*
|
||||
* Once the encoder state if no longer needed, it can be destroyed with
|
||||
*
|
||||
* @code
|
||||
* opus_encoder_destroy(enc);
|
||||
* @endcode
|
||||
*
|
||||
* If the encoder was created with opus_encoder_init() rather than opus_encoder_create(),
|
||||
* then no action is required aside from potentially freeing the memory that was manually
|
||||
* allocated for it (calling free(enc) for the example above)
|
||||
*
|
||||
*/
|
||||
|
||||
/** Opus encoder state.
|
||||
* This contains the complete state of an Opus encoder.
|
||||
* It is position independent and can be freely copied.
|
||||
* @see opus_encoder_create,opus_encoder_init
|
||||
*/
|
||||
typedef struct OpusEncoder OpusEncoder;
|
||||
|
||||
/** Gets the size of an <code>OpusEncoder</code> structure.
|
||||
* @param[in] channels <tt>int</tt>: Number of channels.
|
||||
* This must be 1 or 2.
|
||||
* @returns The size in bytes.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_encoder_get_size(int channels);
|
||||
|
||||
/**
|
||||
*/
|
||||
|
||||
/** Allocates and initializes an encoder state.
|
||||
* There are three coding modes:
|
||||
*
|
||||
* @ref OPUS_APPLICATION_VOIP gives best quality at a given bitrate for voice
|
||||
* signals. It enhances the input signal by high-pass filtering and
|
||||
* emphasizing formants and harmonics. Optionally it includes in-band
|
||||
* forward error correction to protect against packet loss. Use this
|
||||
* mode for typical VoIP applications. Because of the enhancement,
|
||||
* even at high bitrates the output may sound different from the input.
|
||||
*
|
||||
* @ref OPUS_APPLICATION_AUDIO gives best quality at a given bitrate for most
|
||||
* non-voice signals like music. Use this mode for music and mixed
|
||||
* (music/voice) content, broadcast, and applications requiring less
|
||||
* than 15 ms of coding delay.
|
||||
*
|
||||
* @ref OPUS_APPLICATION_RESTRICTED_LOWDELAY configures low-delay mode that
|
||||
* disables the speech-optimized mode in exchange for slightly reduced delay.
|
||||
* This mode can only be set on an newly initialized or freshly reset encoder
|
||||
* because it changes the codec delay.
|
||||
*
|
||||
* This is useful when the caller knows that the speech-optimized modes will not be needed (use with caution).
|
||||
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate of input signal (Hz)
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param [in] channels <tt>int</tt>: Number of channels (1 or 2) in input signal
|
||||
* @param [in] application <tt>int</tt>: Coding mode (@ref OPUS_APPLICATION_VOIP/@ref OPUS_APPLICATION_AUDIO/@ref OPUS_APPLICATION_RESTRICTED_LOWDELAY)
|
||||
* @param [out] error <tt>int*</tt>: @ref opus_errorcodes
|
||||
* @note Regardless of the sampling rate and number channels selected, the Opus encoder
|
||||
* can switch to a lower audio bandwidth or number of channels if the bitrate
|
||||
* selected is too low. This also means that it is safe to always use 48 kHz stereo input
|
||||
* and let the encoder optimize the encoding.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusEncoder *opus_encoder_create(
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int application,
|
||||
int *error
|
||||
);
|
||||
|
||||
/** Initializes a previously allocated encoder state
|
||||
* The memory pointed to by st must be at least the size returned by opus_encoder_get_size().
|
||||
* This is intended for applications which use their own allocator instead of malloc.
|
||||
* @see opus_encoder_create(),opus_encoder_get_size()
|
||||
* To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
|
||||
* @param [in] st <tt>OpusEncoder*</tt>: Encoder state
|
||||
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate of input signal (Hz)
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param [in] channels <tt>int</tt>: Number of channels (1 or 2) in input signal
|
||||
* @param [in] application <tt>int</tt>: Coding mode (OPUS_APPLICATION_VOIP/OPUS_APPLICATION_AUDIO/OPUS_APPLICATION_RESTRICTED_LOWDELAY)
|
||||
* @retval #OPUS_OK Success or @ref opus_errorcodes
|
||||
*/
|
||||
OPUS_EXPORT int opus_encoder_init(
|
||||
OpusEncoder *st,
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int application
|
||||
) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Encodes an Opus frame.
|
||||
* @param [in] st <tt>OpusEncoder*</tt>: Encoder state
|
||||
* @param [in] pcm <tt>opus_int16*</tt>: Input signal (interleaved if 2 channels). length is frame_size*channels*sizeof(opus_int16)
|
||||
* @param [in] frame_size <tt>int</tt>: Number of samples per channel in the
|
||||
* input signal.
|
||||
* This must be an Opus frame size for
|
||||
* the encoder's sampling rate.
|
||||
* For example, at 48 kHz the permitted
|
||||
* values are 120, 240, 480, 960, 1920,
|
||||
* and 2880.
|
||||
* Passing in a duration of less than
|
||||
* 10 ms (480 samples at 48 kHz) will
|
||||
* prevent the encoder from using the LPC
|
||||
* or hybrid modes.
|
||||
* @param [out] data <tt>unsigned char*</tt>: Output payload.
|
||||
* This must contain storage for at
|
||||
* least \a max_data_bytes.
|
||||
* @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
|
||||
* memory for the output
|
||||
* payload. This may be
|
||||
* used to impose an upper limit on
|
||||
* the instant bitrate, but should
|
||||
* not be used as the only bitrate
|
||||
* control. Use #OPUS_SET_BITRATE to
|
||||
* control the bitrate.
|
||||
* @returns The length of the encoded packet (in bytes) on success or a
|
||||
* negative error code (see @ref opus_errorcodes) on failure.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode(
|
||||
OpusEncoder *st,
|
||||
const opus_int16 *pcm,
|
||||
int frame_size,
|
||||
unsigned char *data,
|
||||
opus_int32 max_data_bytes
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Encodes an Opus frame from floating point input.
|
||||
* @param [in] st <tt>OpusEncoder*</tt>: Encoder state
|
||||
* @param [in] pcm <tt>float*</tt>: Input in float format (interleaved if 2 channels), with a normal range of +/-1.0.
|
||||
* Samples with a range beyond +/-1.0 are supported but will
|
||||
* be clipped by decoders using the integer API and should
|
||||
* only be used if it is known that the far end supports
|
||||
* extended dynamic range.
|
||||
* length is frame_size*channels*sizeof(float)
|
||||
* @param [in] frame_size <tt>int</tt>: Number of samples per channel in the
|
||||
* input signal.
|
||||
* This must be an Opus frame size for
|
||||
* the encoder's sampling rate.
|
||||
* For example, at 48 kHz the permitted
|
||||
* values are 120, 240, 480, 960, 1920,
|
||||
* and 2880.
|
||||
* Passing in a duration of less than
|
||||
* 10 ms (480 samples at 48 kHz) will
|
||||
* prevent the encoder from using the LPC
|
||||
* or hybrid modes.
|
||||
* @param [out] data <tt>unsigned char*</tt>: Output payload.
|
||||
* This must contain storage for at
|
||||
* least \a max_data_bytes.
|
||||
* @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
|
||||
* memory for the output
|
||||
* payload. This may be
|
||||
* used to impose an upper limit on
|
||||
* the instant bitrate, but should
|
||||
* not be used as the only bitrate
|
||||
* control. Use #OPUS_SET_BITRATE to
|
||||
* control the bitrate.
|
||||
* @returns The length of the encoded packet (in bytes) on success or a
|
||||
* negative error code (see @ref opus_errorcodes) on failure.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode_float(
|
||||
OpusEncoder *st,
|
||||
const float *pcm,
|
||||
int frame_size,
|
||||
unsigned char *data,
|
||||
opus_int32 max_data_bytes
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Frees an <code>OpusEncoder</code> allocated by opus_encoder_create().
|
||||
* @param[in] st <tt>OpusEncoder*</tt>: State to be freed.
|
||||
*/
|
||||
OPUS_EXPORT void opus_encoder_destroy(OpusEncoder *st);
|
||||
|
||||
/** Perform a CTL function on an Opus encoder.
|
||||
*
|
||||
* Generally the request and subsequent arguments are generated
|
||||
* by a convenience macro.
|
||||
* @param st <tt>OpusEncoder*</tt>: Encoder state.
|
||||
* @param request This and all remaining parameters should be replaced by one
|
||||
* of the convenience macros in @ref opus_genericctls or
|
||||
* @ref opus_encoderctls.
|
||||
* @see opus_genericctls
|
||||
* @see opus_encoderctls
|
||||
*/
|
||||
OPUS_EXPORT int opus_encoder_ctl(OpusEncoder *st, int request, ...) OPUS_ARG_NONNULL(1);
|
||||
/**@}*/
|
||||
|
||||
/** @defgroup opus_decoder Opus Decoder
|
||||
* @{
|
||||
*
|
||||
* @brief This page describes the process and functions used to decode Opus.
|
||||
*
|
||||
* The decoding process also starts with creating a decoder
|
||||
* state. This can be done with:
|
||||
* @code
|
||||
* int error;
|
||||
* OpusDecoder *dec;
|
||||
* dec = opus_decoder_create(Fs, channels, &error);
|
||||
* @endcode
|
||||
* where
|
||||
* @li Fs is the sampling rate and must be 8000, 12000, 16000, 24000, or 48000
|
||||
* @li channels is the number of channels (1 or 2)
|
||||
* @li error will hold the error code in case of failure (or #OPUS_OK on success)
|
||||
* @li the return value is a newly created decoder state to be used for decoding
|
||||
*
|
||||
* While opus_decoder_create() allocates memory for the state, it's also possible
|
||||
* to initialize pre-allocated memory:
|
||||
* @code
|
||||
* int size;
|
||||
* int error;
|
||||
* OpusDecoder *dec;
|
||||
* size = opus_decoder_get_size(channels);
|
||||
* dec = malloc(size);
|
||||
* error = opus_decoder_init(dec, Fs, channels);
|
||||
* @endcode
|
||||
* where opus_decoder_get_size() returns the required size for the decoder state. Note that
|
||||
* future versions of this code may change the size, so no assuptions should be made about it.
|
||||
*
|
||||
* The decoder state is always continuous in memory and only a shallow copy is sufficient
|
||||
* to copy it (e.g. memcpy())
|
||||
*
|
||||
* To decode a frame, opus_decode() or opus_decode_float() must be called with a packet of compressed audio data:
|
||||
* @code
|
||||
* frame_size = opus_decode(dec, packet, len, decoded, max_size, 0);
|
||||
* @endcode
|
||||
* where
|
||||
*
|
||||
* @li packet is the byte array containing the compressed data
|
||||
* @li len is the exact number of bytes contained in the packet
|
||||
* @li decoded is the decoded audio data in opus_int16 (or float for opus_decode_float())
|
||||
* @li max_size is the max duration of the frame in samples (per channel) that can fit into the decoded_frame array
|
||||
*
|
||||
* opus_decode() and opus_decode_float() return the number of samples (per channel) decoded from the packet.
|
||||
* If that value is negative, then an error has occurred. This can occur if the packet is corrupted or if the audio
|
||||
* buffer is too small to hold the decoded audio.
|
||||
*
|
||||
* Opus is a stateful codec with overlapping blocks and as a result Opus
|
||||
* packets are not coded independently of each other. Packets must be
|
||||
* passed into the decoder serially and in the correct order for a correct
|
||||
* decode. Lost packets can be replaced with loss concealment by calling
|
||||
* the decoder with a null pointer and zero length for the missing packet.
|
||||
*
|
||||
* A single codec state may only be accessed from a single thread at
|
||||
* a time and any required locking must be performed by the caller. Separate
|
||||
* streams must be decoded with separate decoder states and can be decoded
|
||||
* in parallel unless the library was compiled with NONTHREADSAFE_PSEUDOSTACK
|
||||
* defined.
|
||||
*
|
||||
*/
|
||||
|
||||
/** Opus decoder state.
|
||||
* This contains the complete state of an Opus decoder.
|
||||
* It is position independent and can be freely copied.
|
||||
* @see opus_decoder_create,opus_decoder_init
|
||||
*/
|
||||
typedef struct OpusDecoder OpusDecoder;
|
||||
|
||||
/** Gets the size of an <code>OpusDecoder</code> structure.
|
||||
* @param [in] channels <tt>int</tt>: Number of channels.
|
||||
* This must be 1 or 2.
|
||||
* @returns The size in bytes.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_size(int channels);
|
||||
|
||||
/** Allocates and initializes a decoder state.
|
||||
* @param [in] Fs <tt>opus_int32</tt>: Sample rate to decode at (Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param [in] channels <tt>int</tt>: Number of channels (1 or 2) to decode
|
||||
* @param [out] error <tt>int*</tt>: #OPUS_OK Success or @ref opus_errorcodes
|
||||
*
|
||||
* Internally Opus stores data at 48000 Hz, so that should be the default
|
||||
* value for Fs. However, the decoder can efficiently decode to buffers
|
||||
* at 8, 12, 16, and 24 kHz so if for some reason the caller cannot use
|
||||
* data at the full sample rate, or knows the compressed data doesn't
|
||||
* use the full frequency range, it can request decoding at a reduced
|
||||
* rate. Likewise, the decoder is capable of filling in either mono or
|
||||
* interleaved stereo pcm buffers, at the caller's request.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusDecoder *opus_decoder_create(
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int *error
|
||||
);
|
||||
|
||||
/** Initializes a previously allocated decoder state.
|
||||
* The state must be at least the size returned by opus_decoder_get_size().
|
||||
* This is intended for applications which use their own allocator instead of malloc. @see opus_decoder_create,opus_decoder_get_size
|
||||
* To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
|
||||
* @param [in] st <tt>OpusDecoder*</tt>: Decoder state.
|
||||
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate to decode to (Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param [in] channels <tt>int</tt>: Number of channels (1 or 2) to decode
|
||||
* @retval #OPUS_OK Success or @ref opus_errorcodes
|
||||
*/
|
||||
OPUS_EXPORT int opus_decoder_init(
|
||||
OpusDecoder *st,
|
||||
opus_int32 Fs,
|
||||
int channels
|
||||
) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Decode an Opus packet.
|
||||
* @param [in] st <tt>OpusDecoder*</tt>: Decoder state
|
||||
* @param [in] data <tt>char*</tt>: Input payload. Use a NULL pointer to indicate packet loss
|
||||
* @param [in] len <tt>opus_int32</tt>: Number of bytes in payload*
|
||||
* @param [out] pcm <tt>opus_int16*</tt>: Output signal (interleaved if 2 channels). length
|
||||
* is frame_size*channels*sizeof(opus_int16)
|
||||
* @param [in] frame_size Number of samples per channel of available space in \a pcm.
|
||||
* If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will
|
||||
* not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1),
|
||||
* then frame_size needs to be exactly the duration of audio that is missing, otherwise the
|
||||
* decoder will not be in the optimal state to decode the next incoming packet. For the PLC and
|
||||
* FEC cases, frame_size <b>must</b> be a multiple of 2.5 ms.
|
||||
* @param [in] decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band forward error correction data be
|
||||
* decoded. If no such data is available, the frame is decoded as if it were lost.
|
||||
* @returns Number of decoded samples or @ref opus_errorcodes
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode(
|
||||
OpusDecoder *st,
|
||||
const unsigned char *data,
|
||||
opus_int32 len,
|
||||
opus_int16 *pcm,
|
||||
int frame_size,
|
||||
int decode_fec
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Decode an Opus packet with floating point output.
|
||||
* @param [in] st <tt>OpusDecoder*</tt>: Decoder state
|
||||
* @param [in] data <tt>char*</tt>: Input payload. Use a NULL pointer to indicate packet loss
|
||||
* @param [in] len <tt>opus_int32</tt>: Number of bytes in payload
|
||||
* @param [out] pcm <tt>float*</tt>: Output signal (interleaved if 2 channels). length
|
||||
* is frame_size*channels*sizeof(float)
|
||||
* @param [in] frame_size Number of samples per channel of available space in \a pcm.
|
||||
* If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will
|
||||
* not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1),
|
||||
* then frame_size needs to be exactly the duration of audio that is missing, otherwise the
|
||||
* decoder will not be in the optimal state to decode the next incoming packet. For the PLC and
|
||||
* FEC cases, frame_size <b>must</b> be a multiple of 2.5 ms.
|
||||
* @param [in] decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band forward error correction data be
|
||||
* decoded. If no such data is available the frame is decoded as if it were lost.
|
||||
* @returns Number of decoded samples or @ref opus_errorcodes
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode_float(
|
||||
OpusDecoder *st,
|
||||
const unsigned char *data,
|
||||
opus_int32 len,
|
||||
float *pcm,
|
||||
int frame_size,
|
||||
int decode_fec
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Perform a CTL function on an Opus decoder.
|
||||
*
|
||||
* Generally the request and subsequent arguments are generated
|
||||
* by a convenience macro.
|
||||
* @param st <tt>OpusDecoder*</tt>: Decoder state.
|
||||
* @param request This and all remaining parameters should be replaced by one
|
||||
* of the convenience macros in @ref opus_genericctls or
|
||||
* @ref opus_decoderctls.
|
||||
* @see opus_genericctls
|
||||
* @see opus_decoderctls
|
||||
*/
|
||||
OPUS_EXPORT int opus_decoder_ctl(OpusDecoder *st, int request, ...) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Frees an <code>OpusDecoder</code> allocated by opus_decoder_create().
|
||||
* @param[in] st <tt>OpusDecoder*</tt>: State to be freed.
|
||||
*/
|
||||
OPUS_EXPORT void opus_decoder_destroy(OpusDecoder *st);
|
||||
|
||||
/** Parse an opus packet into one or more frames.
|
||||
* Opus_decode will perform this operation internally so most applications do
|
||||
* not need to use this function.
|
||||
* This function does not copy the frames, the returned pointers are pointers into
|
||||
* the input packet.
|
||||
* @param [in] data <tt>char*</tt>: Opus packet to be parsed
|
||||
* @param [in] len <tt>opus_int32</tt>: size of data
|
||||
* @param [out] out_toc <tt>char*</tt>: TOC pointer
|
||||
* @param [out] frames <tt>char*[48]</tt> encapsulated frames
|
||||
* @param [out] size <tt>opus_int16[48]</tt> sizes of the encapsulated frames
|
||||
* @param [out] payload_offset <tt>int*</tt>: returns the position of the payload within the packet (in bytes)
|
||||
* @returns number of frames
|
||||
*/
|
||||
OPUS_EXPORT int opus_packet_parse(
|
||||
const unsigned char *data,
|
||||
opus_int32 len,
|
||||
unsigned char *out_toc,
|
||||
const unsigned char *frames[48],
|
||||
opus_int16 size[48],
|
||||
int *payload_offset
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Gets the bandwidth of an Opus packet.
|
||||
* @param [in] data <tt>char*</tt>: Opus packet
|
||||
* @retval OPUS_BANDWIDTH_NARROWBAND Narrowband (4kHz bandpass)
|
||||
* @retval OPUS_BANDWIDTH_MEDIUMBAND Mediumband (6kHz bandpass)
|
||||
* @retval OPUS_BANDWIDTH_WIDEBAND Wideband (8kHz bandpass)
|
||||
* @retval OPUS_BANDWIDTH_SUPERWIDEBAND Superwideband (12kHz bandpass)
|
||||
* @retval OPUS_BANDWIDTH_FULLBAND Fullband (20kHz bandpass)
|
||||
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_bandwidth(const unsigned char *data) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Gets the number of samples per frame from an Opus packet.
|
||||
* @param [in] data <tt>char*</tt>: Opus packet.
|
||||
* This must contain at least one byte of
|
||||
* data.
|
||||
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate in Hz.
|
||||
* This must be a multiple of 400, or
|
||||
* inaccurate results will be returned.
|
||||
* @returns Number of samples per frame.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_samples_per_frame(const unsigned char *data, opus_int32 Fs) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Gets the number of channels from an Opus packet.
|
||||
* @param [in] data <tt>char*</tt>: Opus packet
|
||||
* @returns Number of channels
|
||||
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_channels(const unsigned char *data) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Gets the number of frames in an Opus packet.
|
||||
* @param [in] packet <tt>char*</tt>: Opus packet
|
||||
* @param [in] len <tt>opus_int32</tt>: Length of packet
|
||||
* @returns Number of frames
|
||||
* @retval OPUS_BAD_ARG Insufficient data was passed to the function
|
||||
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_frames(const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Gets the number of samples of an Opus packet.
|
||||
* @param [in] packet <tt>char*</tt>: Opus packet
|
||||
* @param [in] len <tt>opus_int32</tt>: Length of packet
|
||||
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate in Hz.
|
||||
* This must be a multiple of 400, or
|
||||
* inaccurate results will be returned.
|
||||
* @returns Number of samples
|
||||
* @retval OPUS_BAD_ARG Insufficient data was passed to the function
|
||||
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_samples(const unsigned char packet[], opus_int32 len, opus_int32 Fs) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Gets the number of samples of an Opus packet.
|
||||
* @param [in] dec <tt>OpusDecoder*</tt>: Decoder state
|
||||
* @param [in] packet <tt>char*</tt>: Opus packet
|
||||
* @param [in] len <tt>opus_int32</tt>: Length of packet
|
||||
* @returns Number of samples
|
||||
* @retval OPUS_BAD_ARG Insufficient data was passed to the function
|
||||
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_nb_samples(const OpusDecoder *dec, const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2);
|
||||
|
||||
/** Applies soft-clipping to bring a float signal within the [-1,1] range. If
|
||||
* the signal is already in that range, nothing is done. If there are values
|
||||
* outside of [-1,1], then the signal is clipped as smoothly as possible to
|
||||
* both fit in the range and avoid creating excessive distortion in the
|
||||
* process.
|
||||
* @param [in,out] pcm <tt>float*</tt>: Input PCM and modified PCM
|
||||
* @param [in] frame_size <tt>int</tt> Number of samples per channel to process
|
||||
* @param [in] channels <tt>int</tt>: Number of channels
|
||||
* @param [in,out] softclip_mem <tt>float*</tt>: State memory for the soft clipping process (one float per channel, initialized to zero)
|
||||
*/
|
||||
OPUS_EXPORT void opus_pcm_soft_clip(float *pcm, int frame_size, int channels, float *softclip_mem);
|
||||
|
||||
|
||||
/**@}*/
|
||||
|
||||
/** @defgroup opus_repacketizer Repacketizer
|
||||
* @{
|
||||
*
|
||||
* The repacketizer can be used to merge multiple Opus packets into a single
|
||||
* packet or alternatively to split Opus packets that have previously been
|
||||
* merged. Splitting valid Opus packets is always guaranteed to succeed,
|
||||
* whereas merging valid packets only succeeds if all frames have the same
|
||||
* mode, bandwidth, and frame size, and when the total duration of the merged
|
||||
* packet is no more than 120 ms. The 120 ms limit comes from the
|
||||
* specification and limits decoder memory requirements at a point where
|
||||
* framing overhead becomes negligible.
|
||||
*
|
||||
* The repacketizer currently only operates on elementary Opus
|
||||
* streams. It will not manipualte multistream packets successfully, except in
|
||||
* the degenerate case where they consist of data from a single stream.
|
||||
*
|
||||
* The repacketizing process starts with creating a repacketizer state, either
|
||||
* by calling opus_repacketizer_create() or by allocating the memory yourself,
|
||||
* e.g.,
|
||||
* @code
|
||||
* OpusRepacketizer *rp;
|
||||
* rp = (OpusRepacketizer*)malloc(opus_repacketizer_get_size());
|
||||
* if (rp != NULL)
|
||||
* opus_repacketizer_init(rp);
|
||||
* @endcode
|
||||
*
|
||||
* Then the application should submit packets with opus_repacketizer_cat(),
|
||||
* extract new packets with opus_repacketizer_out() or
|
||||
* opus_repacketizer_out_range(), and then reset the state for the next set of
|
||||
* input packets via opus_repacketizer_init().
|
||||
*
|
||||
* For example, to split a sequence of packets into individual frames:
|
||||
* @code
|
||||
* unsigned char *data;
|
||||
* int len;
|
||||
* while (get_next_packet(&data, &len))
|
||||
* {
|
||||
* unsigned char out[1276];
|
||||
* opus_int32 out_len;
|
||||
* int nb_frames;
|
||||
* int err;
|
||||
* int i;
|
||||
* err = opus_repacketizer_cat(rp, data, len);
|
||||
* if (err != OPUS_OK)
|
||||
* {
|
||||
* release_packet(data);
|
||||
* return err;
|
||||
* }
|
||||
* nb_frames = opus_repacketizer_get_nb_frames(rp);
|
||||
* for (i = 0; i < nb_frames; i++)
|
||||
* {
|
||||
* out_len = opus_repacketizer_out_range(rp, i, i+1, out, sizeof(out));
|
||||
* if (out_len < 0)
|
||||
* {
|
||||
* release_packet(data);
|
||||
* return (int)out_len;
|
||||
* }
|
||||
* output_next_packet(out, out_len);
|
||||
* }
|
||||
* opus_repacketizer_init(rp);
|
||||
* release_packet(data);
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Alternatively, to combine a sequence of frames into packets that each
|
||||
* contain up to <code>TARGET_DURATION_MS</code> milliseconds of data:
|
||||
* @code
|
||||
* // The maximum number of packets with duration TARGET_DURATION_MS occurs
|
||||
* // when the frame size is 2.5 ms, for a total of (TARGET_DURATION_MS*2/5)
|
||||
* // packets.
|
||||
* unsigned char *data[(TARGET_DURATION_MS*2/5)+1];
|
||||
* opus_int32 len[(TARGET_DURATION_MS*2/5)+1];
|
||||
* int nb_packets;
|
||||
* unsigned char out[1277*(TARGET_DURATION_MS*2/2)];
|
||||
* opus_int32 out_len;
|
||||
* int prev_toc;
|
||||
* nb_packets = 0;
|
||||
* while (get_next_packet(data+nb_packets, len+nb_packets))
|
||||
* {
|
||||
* int nb_frames;
|
||||
* int err;
|
||||
* nb_frames = opus_packet_get_nb_frames(data[nb_packets], len[nb_packets]);
|
||||
* if (nb_frames < 1)
|
||||
* {
|
||||
* release_packets(data, nb_packets+1);
|
||||
* return nb_frames;
|
||||
* }
|
||||
* nb_frames += opus_repacketizer_get_nb_frames(rp);
|
||||
* // If adding the next packet would exceed our target, or it has an
|
||||
* // incompatible TOC sequence, output the packets we already have before
|
||||
* // submitting it.
|
||||
* // N.B., The nb_packets > 0 check ensures we've submitted at least one
|
||||
* // packet since the last call to opus_repacketizer_init(). Otherwise a
|
||||
* // single packet longer than TARGET_DURATION_MS would cause us to try to
|
||||
* // output an (invalid) empty packet. It also ensures that prev_toc has
|
||||
* // been set to a valid value. Additionally, len[nb_packets] > 0 is
|
||||
* // guaranteed by the call to opus_packet_get_nb_frames() above, so the
|
||||
* // reference to data[nb_packets][0] should be valid.
|
||||
* if (nb_packets > 0 && (
|
||||
* ((prev_toc & 0xFC) != (data[nb_packets][0] & 0xFC)) ||
|
||||
* opus_packet_get_samples_per_frame(data[nb_packets], 48000)*nb_frames >
|
||||
* TARGET_DURATION_MS*48))
|
||||
* {
|
||||
* out_len = opus_repacketizer_out(rp, out, sizeof(out));
|
||||
* if (out_len < 0)
|
||||
* {
|
||||
* release_packets(data, nb_packets+1);
|
||||
* return (int)out_len;
|
||||
* }
|
||||
* output_next_packet(out, out_len);
|
||||
* opus_repacketizer_init(rp);
|
||||
* release_packets(data, nb_packets);
|
||||
* data[0] = data[nb_packets];
|
||||
* len[0] = len[nb_packets];
|
||||
* nb_packets = 0;
|
||||
* }
|
||||
* err = opus_repacketizer_cat(rp, data[nb_packets], len[nb_packets]);
|
||||
* if (err != OPUS_OK)
|
||||
* {
|
||||
* release_packets(data, nb_packets+1);
|
||||
* return err;
|
||||
* }
|
||||
* prev_toc = data[nb_packets][0];
|
||||
* nb_packets++;
|
||||
* }
|
||||
* // Output the final, partial packet.
|
||||
* if (nb_packets > 0)
|
||||
* {
|
||||
* out_len = opus_repacketizer_out(rp, out, sizeof(out));
|
||||
* release_packets(data, nb_packets);
|
||||
* if (out_len < 0)
|
||||
* return (int)out_len;
|
||||
* output_next_packet(out, out_len);
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* An alternate way of merging packets is to simply call opus_repacketizer_cat()
|
||||
* unconditionally until it fails. At that point, the merged packet can be
|
||||
* obtained with opus_repacketizer_out() and the input packet for which
|
||||
* opus_repacketizer_cat() needs to be re-added to a newly reinitialized
|
||||
* repacketizer state.
|
||||
*/
|
||||
|
||||
typedef struct OpusRepacketizer OpusRepacketizer;
|
||||
|
||||
/** Gets the size of an <code>OpusRepacketizer</code> structure.
|
||||
* @returns The size in bytes.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_size(void);
|
||||
|
||||
/** (Re)initializes a previously allocated repacketizer state.
|
||||
* The state must be at least the size returned by opus_repacketizer_get_size().
|
||||
* This can be used for applications which use their own allocator instead of
|
||||
* malloc().
|
||||
* It must also be called to reset the queue of packets waiting to be
|
||||
* repacketized, which is necessary if the maximum packet duration of 120 ms
|
||||
* is reached or if you wish to submit packets with a different Opus
|
||||
* configuration (coding mode, audio bandwidth, frame size, or channel count).
|
||||
* Failure to do so will prevent a new packet from being added with
|
||||
* opus_repacketizer_cat().
|
||||
* @see opus_repacketizer_create
|
||||
* @see opus_repacketizer_get_size
|
||||
* @see opus_repacketizer_cat
|
||||
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state to
|
||||
* (re)initialize.
|
||||
* @returns A pointer to the same repacketizer state that was passed in.
|
||||
*/
|
||||
OPUS_EXPORT OpusRepacketizer *opus_repacketizer_init(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Allocates memory and initializes the new repacketizer with
|
||||
* opus_repacketizer_init().
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusRepacketizer *opus_repacketizer_create(void);
|
||||
|
||||
/** Frees an <code>OpusRepacketizer</code> allocated by
|
||||
* opus_repacketizer_create().
|
||||
* @param[in] rp <tt>OpusRepacketizer*</tt>: State to be freed.
|
||||
*/
|
||||
OPUS_EXPORT void opus_repacketizer_destroy(OpusRepacketizer *rp);
|
||||
|
||||
/** Add a packet to the current repacketizer state.
|
||||
* This packet must match the configuration of any packets already submitted
|
||||
* for repacketization since the last call to opus_repacketizer_init().
|
||||
* This means that it must have the same coding mode, audio bandwidth, frame
|
||||
* size, and channel count.
|
||||
* This can be checked in advance by examining the top 6 bits of the first
|
||||
* byte of the packet, and ensuring they match the top 6 bits of the first
|
||||
* byte of any previously submitted packet.
|
||||
* The total duration of audio in the repacketizer state also must not exceed
|
||||
* 120 ms, the maximum duration of a single packet, after adding this packet.
|
||||
*
|
||||
* The contents of the current repacketizer state can be extracted into new
|
||||
* packets using opus_repacketizer_out() or opus_repacketizer_out_range().
|
||||
*
|
||||
* In order to add a packet with a different configuration or to add more
|
||||
* audio beyond 120 ms, you must clear the repacketizer state by calling
|
||||
* opus_repacketizer_init().
|
||||
* If a packet is too large to add to the current repacketizer state, no part
|
||||
* of it is added, even if it contains multiple frames, some of which might
|
||||
* fit.
|
||||
* If you wish to be able to add parts of such packets, you should first use
|
||||
* another repacketizer to split the packet into pieces and add them
|
||||
* individually.
|
||||
* @see opus_repacketizer_out_range
|
||||
* @see opus_repacketizer_out
|
||||
* @see opus_repacketizer_init
|
||||
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state to which to
|
||||
* add the packet.
|
||||
* @param[in] data <tt>const unsigned char*</tt>: The packet data.
|
||||
* The application must ensure
|
||||
* this pointer remains valid
|
||||
* until the next call to
|
||||
* opus_repacketizer_init() or
|
||||
* opus_repacketizer_destroy().
|
||||
* @param len <tt>opus_int32</tt>: The number of bytes in the packet data.
|
||||
* @returns An error code indicating whether or not the operation succeeded.
|
||||
* @retval #OPUS_OK The packet's contents have been added to the repacketizer
|
||||
* state.
|
||||
* @retval #OPUS_INVALID_PACKET The packet did not have a valid TOC sequence,
|
||||
* the packet's TOC sequence was not compatible
|
||||
* with previously submitted packets (because
|
||||
* the coding mode, audio bandwidth, frame size,
|
||||
* or channel count did not match), or adding
|
||||
* this packet would increase the total amount of
|
||||
* audio stored in the repacketizer state to more
|
||||
* than 120 ms.
|
||||
*/
|
||||
OPUS_EXPORT int opus_repacketizer_cat(OpusRepacketizer *rp, const unsigned char *data, opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2);
|
||||
|
||||
|
||||
/** Construct a new packet from data previously submitted to the repacketizer
|
||||
* state via opus_repacketizer_cat().
|
||||
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state from which to
|
||||
* construct the new packet.
|
||||
* @param begin <tt>int</tt>: The index of the first frame in the current
|
||||
* repacketizer state to include in the output.
|
||||
* @param end <tt>int</tt>: One past the index of the last frame in the
|
||||
* current repacketizer state to include in the
|
||||
* output.
|
||||
* @param[out] data <tt>const unsigned char*</tt>: The buffer in which to
|
||||
* store the output packet.
|
||||
* @param maxlen <tt>opus_int32</tt>: The maximum number of bytes to store in
|
||||
* the output buffer. In order to guarantee
|
||||
* success, this should be at least
|
||||
* <code>1276</code> for a single frame,
|
||||
* or for multiple frames,
|
||||
* <code>1277*(end-begin)</code>.
|
||||
* However, <code>1*(end-begin)</code> plus
|
||||
* the size of all packet data submitted to
|
||||
* the repacketizer since the last call to
|
||||
* opus_repacketizer_init() or
|
||||
* opus_repacketizer_create() is also
|
||||
* sufficient, and possibly much smaller.
|
||||
* @returns The total size of the output packet on success, or an error code
|
||||
* on failure.
|
||||
* @retval #OPUS_BAD_ARG <code>[begin,end)</code> was an invalid range of
|
||||
* frames (begin < 0, begin >= end, or end >
|
||||
* opus_repacketizer_get_nb_frames()).
|
||||
* @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the
|
||||
* complete output packet.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out_range(OpusRepacketizer *rp, int begin, int end, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Return the total number of frames contained in packet data submitted to
|
||||
* the repacketizer state so far via opus_repacketizer_cat() since the last
|
||||
* call to opus_repacketizer_init() or opus_repacketizer_create().
|
||||
* This defines the valid range of packets that can be extracted with
|
||||
* opus_repacketizer_out_range() or opus_repacketizer_out().
|
||||
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state containing the
|
||||
* frames.
|
||||
* @returns The total number of frames contained in the packet data submitted
|
||||
* to the repacketizer state.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_nb_frames(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Construct a new packet from data previously submitted to the repacketizer
|
||||
* state via opus_repacketizer_cat().
|
||||
* This is a convenience routine that returns all the data submitted so far
|
||||
* in a single packet.
|
||||
* It is equivalent to calling
|
||||
* @code
|
||||
* opus_repacketizer_out_range(rp, 0, opus_repacketizer_get_nb_frames(rp),
|
||||
* data, maxlen)
|
||||
* @endcode
|
||||
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state from which to
|
||||
* construct the new packet.
|
||||
* @param[out] data <tt>const unsigned char*</tt>: The buffer in which to
|
||||
* store the output packet.
|
||||
* @param maxlen <tt>opus_int32</tt>: The maximum number of bytes to store in
|
||||
* the output buffer. In order to guarantee
|
||||
* success, this should be at least
|
||||
* <code>1277*opus_repacketizer_get_nb_frames(rp)</code>.
|
||||
* However,
|
||||
* <code>1*opus_repacketizer_get_nb_frames(rp)</code>
|
||||
* plus the size of all packet data
|
||||
* submitted to the repacketizer since the
|
||||
* last call to opus_repacketizer_init() or
|
||||
* opus_repacketizer_create() is also
|
||||
* sufficient, and possibly much smaller.
|
||||
* @returns The total size of the output packet on success, or an error code
|
||||
* on failure.
|
||||
* @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the
|
||||
* complete output packet.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out(OpusRepacketizer *rp, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Pads a given Opus packet to a larger size (possibly changing the TOC sequence).
|
||||
* @param[in,out] data <tt>const unsigned char*</tt>: The buffer containing the
|
||||
* packet to pad.
|
||||
* @param len <tt>opus_int32</tt>: The size of the packet.
|
||||
* This must be at least 1.
|
||||
* @param new_len <tt>opus_int32</tt>: The desired size of the packet after padding.
|
||||
* This must be at least as large as len.
|
||||
* @returns an error code
|
||||
* @retval #OPUS_OK \a on success.
|
||||
* @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len.
|
||||
* @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet.
|
||||
*/
|
||||
OPUS_EXPORT int opus_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len);
|
||||
|
||||
/** Remove all padding from a given Opus packet and rewrite the TOC sequence to
|
||||
* minimize space usage.
|
||||
* @param[in,out] data <tt>const unsigned char*</tt>: The buffer containing the
|
||||
* packet to strip.
|
||||
* @param len <tt>opus_int32</tt>: The size of the packet.
|
||||
* This must be at least 1.
|
||||
* @returns The new size of the output packet on success, or an error code
|
||||
* on failure.
|
||||
* @retval #OPUS_BAD_ARG \a len was less than 1.
|
||||
* @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_packet_unpad(unsigned char *data, opus_int32 len);
|
||||
|
||||
/** Pads a given Opus multi-stream packet to a larger size (possibly changing the TOC sequence).
|
||||
* @param[in,out] data <tt>const unsigned char*</tt>: The buffer containing the
|
||||
* packet to pad.
|
||||
* @param len <tt>opus_int32</tt>: The size of the packet.
|
||||
* This must be at least 1.
|
||||
* @param new_len <tt>opus_int32</tt>: The desired size of the packet after padding.
|
||||
* This must be at least 1.
|
||||
* @param nb_streams <tt>opus_int32</tt>: The number of streams (not channels) in the packet.
|
||||
* This must be at least as large as len.
|
||||
* @returns an error code
|
||||
* @retval #OPUS_OK \a on success.
|
||||
* @retval #OPUS_BAD_ARG \a len was less than 1.
|
||||
* @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet.
|
||||
*/
|
||||
OPUS_EXPORT int opus_multistream_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len, int nb_streams);
|
||||
|
||||
/** Remove all padding from a given Opus multi-stream packet and rewrite the TOC sequence to
|
||||
* minimize space usage.
|
||||
* @param[in,out] data <tt>const unsigned char*</tt>: The buffer containing the
|
||||
* packet to strip.
|
||||
* @param len <tt>opus_int32</tt>: The size of the packet.
|
||||
* This must be at least 1.
|
||||
* @param nb_streams <tt>opus_int32</tt>: The number of streams (not channels) in the packet.
|
||||
* This must be at least 1.
|
||||
* @returns The new size of the output packet on success, or an error code
|
||||
* on failure.
|
||||
* @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len.
|
||||
* @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_packet_unpad(unsigned char *data, opus_int32 len, int nb_streams);
|
||||
|
||||
/**@}*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPUS_H */
|
||||
@@ -0,0 +1,342 @@
|
||||
/* Copyright (c) 2007-2008 CSIRO
|
||||
Copyright (c) 2007-2009 Xiph.Org Foundation
|
||||
Copyright (c) 2008-2012 Gregory Maxwell
|
||||
Written by Jean-Marc Valin and Gregory Maxwell */
|
||||
/*
|
||||
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.
|
||||
|
||||
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 OWNER
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
@file opus_custom.h
|
||||
@brief Opus-Custom reference implementation API
|
||||
*/
|
||||
|
||||
#ifndef OPUS_CUSTOM_H
|
||||
#define OPUS_CUSTOM_H
|
||||
|
||||
#include "opus_defines.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef CUSTOM_MODES
|
||||
# define OPUS_CUSTOM_EXPORT OPUS_EXPORT
|
||||
# define OPUS_CUSTOM_EXPORT_STATIC OPUS_EXPORT
|
||||
#else
|
||||
# define OPUS_CUSTOM_EXPORT
|
||||
# ifdef OPUS_BUILD
|
||||
# define OPUS_CUSTOM_EXPORT_STATIC static OPUS_INLINE
|
||||
# else
|
||||
# define OPUS_CUSTOM_EXPORT_STATIC
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/** @defgroup opus_custom Opus Custom
|
||||
* @{
|
||||
* Opus Custom is an optional part of the Opus specification and
|
||||
* reference implementation which uses a distinct API from the regular
|
||||
* API and supports frame sizes that are not normally supported.\ Use
|
||||
* of Opus Custom is discouraged for all but very special applications
|
||||
* for which a frame size different from 2.5, 5, 10, or 20 ms is needed
|
||||
* (for either complexity or latency reasons) and where interoperability
|
||||
* is less important.
|
||||
*
|
||||
* In addition to the interoperability limitations the use of Opus custom
|
||||
* disables a substantial chunk of the codec and generally lowers the
|
||||
* quality available at a given bitrate. Normally when an application needs
|
||||
* a different frame size from the codec it should buffer to match the
|
||||
* sizes but this adds a small amount of delay which may be important
|
||||
* in some very low latency applications. Some transports (especially
|
||||
* constant rate RF transports) may also work best with frames of
|
||||
* particular durations.
|
||||
*
|
||||
* Libopus only supports custom modes if they are enabled at compile time.
|
||||
*
|
||||
* The Opus Custom API is similar to the regular API but the
|
||||
* @ref opus_encoder_create and @ref opus_decoder_create calls take
|
||||
* an additional mode parameter which is a structure produced by
|
||||
* a call to @ref opus_custom_mode_create. Both the encoder and decoder
|
||||
* must create a mode using the same sample rate (fs) and frame size
|
||||
* (frame size) so these parameters must either be signaled out of band
|
||||
* or fixed in a particular implementation.
|
||||
*
|
||||
* Similar to regular Opus the custom modes support on the fly frame size
|
||||
* switching, but the sizes available depend on the particular frame size in
|
||||
* use. For some initial frame sizes on a single on the fly size is available.
|
||||
*/
|
||||
|
||||
/** Contains the state of an encoder. One encoder state is needed
|
||||
for each stream. It is initialized once at the beginning of the
|
||||
stream. Do *not* re-initialize the state for every frame.
|
||||
@brief Encoder state
|
||||
*/
|
||||
typedef struct OpusCustomEncoder OpusCustomEncoder;
|
||||
|
||||
/** State of the decoder. One decoder state is needed for each stream.
|
||||
It is initialized once at the beginning of the stream. Do *not*
|
||||
re-initialize the state for every frame.
|
||||
@brief Decoder state
|
||||
*/
|
||||
typedef struct OpusCustomDecoder OpusCustomDecoder;
|
||||
|
||||
/** The mode contains all the information necessary to create an
|
||||
encoder. Both the encoder and decoder need to be initialized
|
||||
with exactly the same mode, otherwise the output will be
|
||||
corrupted.
|
||||
@brief Mode configuration
|
||||
*/
|
||||
typedef struct OpusCustomMode OpusCustomMode;
|
||||
|
||||
/** Creates a new mode struct. This will be passed to an encoder or
|
||||
* decoder. The mode MUST NOT BE DESTROYED until the encoders and
|
||||
* decoders that use it are destroyed as well.
|
||||
* @param [in] Fs <tt>int</tt>: Sampling rate (8000 to 96000 Hz)
|
||||
* @param [in] frame_size <tt>int</tt>: Number of samples (per channel) to encode in each
|
||||
* packet (64 - 1024, prime factorization must contain zero or more 2s, 3s, or 5s and no other primes)
|
||||
* @param [out] error <tt>int*</tt>: Returned error code (if NULL, no error will be returned)
|
||||
* @return A newly created mode
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomMode *opus_custom_mode_create(opus_int32 Fs, int frame_size, int *error);
|
||||
|
||||
/** Destroys a mode struct. Only call this after all encoders and
|
||||
* decoders using this mode are destroyed as well.
|
||||
* @param [in] mode <tt>OpusCustomMode*</tt>: Mode to be freed.
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT void opus_custom_mode_destroy(OpusCustomMode *mode);
|
||||
|
||||
|
||||
#if !defined(OPUS_BUILD) || defined(CELT_ENCODER_C)
|
||||
|
||||
/* Encoder */
|
||||
/** Gets the size of an OpusCustomEncoder structure.
|
||||
* @param [in] mode <tt>OpusCustomMode *</tt>: Mode configuration
|
||||
* @param [in] channels <tt>int</tt>: Number of channels
|
||||
* @returns size
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT_STATIC OPUS_WARN_UNUSED_RESULT int opus_custom_encoder_get_size(
|
||||
const OpusCustomMode *mode,
|
||||
int channels
|
||||
) OPUS_ARG_NONNULL(1);
|
||||
|
||||
# ifdef CUSTOM_MODES
|
||||
/** Initializes a previously allocated encoder state
|
||||
* The memory pointed to by st must be the size returned by opus_custom_encoder_get_size.
|
||||
* This is intended for applications which use their own allocator instead of malloc.
|
||||
* @see opus_custom_encoder_create(),opus_custom_encoder_get_size()
|
||||
* To reset a previously initialized state use the OPUS_RESET_STATE CTL.
|
||||
* @param [in] st <tt>OpusCustomEncoder*</tt>: Encoder state
|
||||
* @param [in] mode <tt>OpusCustomMode *</tt>: Contains all the information about the characteristics of
|
||||
* the stream (must be the same characteristics as used for the
|
||||
* decoder)
|
||||
* @param [in] channels <tt>int</tt>: Number of channels
|
||||
* @return OPUS_OK Success or @ref opus_errorcodes
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT int opus_custom_encoder_init(
|
||||
OpusCustomEncoder *st,
|
||||
const OpusCustomMode *mode,
|
||||
int channels
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2);
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
/** Creates a new encoder state. Each stream needs its own encoder
|
||||
* state (can't be shared across simultaneous streams).
|
||||
* @param [in] mode <tt>OpusCustomMode*</tt>: Contains all the information about the characteristics of
|
||||
* the stream (must be the same characteristics as used for the
|
||||
* decoder)
|
||||
* @param [in] channels <tt>int</tt>: Number of channels
|
||||
* @param [out] error <tt>int*</tt>: Returns an error code
|
||||
* @return Newly created encoder state.
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomEncoder *opus_custom_encoder_create(
|
||||
const OpusCustomMode *mode,
|
||||
int channels,
|
||||
int *error
|
||||
) OPUS_ARG_NONNULL(1);
|
||||
|
||||
|
||||
/** Destroys a an encoder state.
|
||||
* @param[in] st <tt>OpusCustomEncoder*</tt>: State to be freed.
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT void opus_custom_encoder_destroy(OpusCustomEncoder *st);
|
||||
|
||||
/** Encodes a frame of audio.
|
||||
* @param [in] st <tt>OpusCustomEncoder*</tt>: Encoder state
|
||||
* @param [in] pcm <tt>float*</tt>: PCM audio in float format, with a normal range of +/-1.0.
|
||||
* Samples with a range beyond +/-1.0 are supported but will
|
||||
* be clipped by decoders using the integer API and should
|
||||
* only be used if it is known that the far end supports
|
||||
* extended dynamic range. There must be exactly
|
||||
* frame_size samples per channel.
|
||||
* @param [in] frame_size <tt>int</tt>: Number of samples per frame of input signal
|
||||
* @param [out] compressed <tt>char *</tt>: The compressed data is written here. This may not alias pcm and must be at least maxCompressedBytes long.
|
||||
* @param [in] maxCompressedBytes <tt>int</tt>: Maximum number of bytes to use for compressing the frame
|
||||
* (can change from one frame to another)
|
||||
* @return Number of bytes written to "compressed".
|
||||
* If negative, an error has occurred (see error codes). It is IMPORTANT that
|
||||
* the length returned be somehow transmitted to the decoder. Otherwise, no
|
||||
* decoding is possible.
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_encode_float(
|
||||
OpusCustomEncoder *st,
|
||||
const float *pcm,
|
||||
int frame_size,
|
||||
unsigned char *compressed,
|
||||
int maxCompressedBytes
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Encodes a frame of audio.
|
||||
* @param [in] st <tt>OpusCustomEncoder*</tt>: Encoder state
|
||||
* @param [in] pcm <tt>opus_int16*</tt>: PCM audio in signed 16-bit format (native endian).
|
||||
* There must be exactly frame_size samples per channel.
|
||||
* @param [in] frame_size <tt>int</tt>: Number of samples per frame of input signal
|
||||
* @param [out] compressed <tt>char *</tt>: The compressed data is written here. This may not alias pcm and must be at least maxCompressedBytes long.
|
||||
* @param [in] maxCompressedBytes <tt>int</tt>: Maximum number of bytes to use for compressing the frame
|
||||
* (can change from one frame to another)
|
||||
* @return Number of bytes written to "compressed".
|
||||
* If negative, an error has occurred (see error codes). It is IMPORTANT that
|
||||
* the length returned be somehow transmitted to the decoder. Otherwise, no
|
||||
* decoding is possible.
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_encode(
|
||||
OpusCustomEncoder *st,
|
||||
const opus_int16 *pcm,
|
||||
int frame_size,
|
||||
unsigned char *compressed,
|
||||
int maxCompressedBytes
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Perform a CTL function on an Opus custom encoder.
|
||||
*
|
||||
* Generally the request and subsequent arguments are generated
|
||||
* by a convenience macro.
|
||||
* @see opus_encoderctls
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT int opus_custom_encoder_ctl(OpusCustomEncoder * OPUS_RESTRICT st, int request, ...) OPUS_ARG_NONNULL(1);
|
||||
|
||||
|
||||
#if !defined(OPUS_BUILD) || defined(CELT_DECODER_C)
|
||||
/* Decoder */
|
||||
|
||||
/** Gets the size of an OpusCustomDecoder structure.
|
||||
* @param [in] mode <tt>OpusCustomMode *</tt>: Mode configuration
|
||||
* @param [in] channels <tt>int</tt>: Number of channels
|
||||
* @returns size
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT_STATIC OPUS_WARN_UNUSED_RESULT int opus_custom_decoder_get_size(
|
||||
const OpusCustomMode *mode,
|
||||
int channels
|
||||
) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Initializes a previously allocated decoder state
|
||||
* The memory pointed to by st must be the size returned by opus_custom_decoder_get_size.
|
||||
* This is intended for applications which use their own allocator instead of malloc.
|
||||
* @see opus_custom_decoder_create(),opus_custom_decoder_get_size()
|
||||
* To reset a previously initialized state use the OPUS_RESET_STATE CTL.
|
||||
* @param [in] st <tt>OpusCustomDecoder*</tt>: Decoder state
|
||||
* @param [in] mode <tt>OpusCustomMode *</tt>: Contains all the information about the characteristics of
|
||||
* the stream (must be the same characteristics as used for the
|
||||
* encoder)
|
||||
* @param [in] channels <tt>int</tt>: Number of channels
|
||||
* @return OPUS_OK Success or @ref opus_errorcodes
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT_STATIC int opus_custom_decoder_init(
|
||||
OpusCustomDecoder *st,
|
||||
const OpusCustomMode *mode,
|
||||
int channels
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/** Creates a new decoder state. Each stream needs its own decoder state (can't
|
||||
* be shared across simultaneous streams).
|
||||
* @param [in] mode <tt>OpusCustomMode</tt>: Contains all the information about the characteristics of the
|
||||
* stream (must be the same characteristics as used for the encoder)
|
||||
* @param [in] channels <tt>int</tt>: Number of channels
|
||||
* @param [out] error <tt>int*</tt>: Returns an error code
|
||||
* @return Newly created decoder state.
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomDecoder *opus_custom_decoder_create(
|
||||
const OpusCustomMode *mode,
|
||||
int channels,
|
||||
int *error
|
||||
) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Destroys a an decoder state.
|
||||
* @param[in] st <tt>OpusCustomDecoder*</tt>: State to be freed.
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT void opus_custom_decoder_destroy(OpusCustomDecoder *st);
|
||||
|
||||
/** Decode an opus custom frame with floating point output
|
||||
* @param [in] st <tt>OpusCustomDecoder*</tt>: Decoder state
|
||||
* @param [in] data <tt>char*</tt>: Input payload. Use a NULL pointer to indicate packet loss
|
||||
* @param [in] len <tt>int</tt>: Number of bytes in payload
|
||||
* @param [out] pcm <tt>float*</tt>: Output signal (interleaved if 2 channels). length
|
||||
* is frame_size*channels*sizeof(float)
|
||||
* @param [in] frame_size Number of samples per channel of available space in *pcm.
|
||||
* @returns Number of decoded samples or @ref opus_errorcodes
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_decode_float(
|
||||
OpusCustomDecoder *st,
|
||||
const unsigned char *data,
|
||||
int len,
|
||||
float *pcm,
|
||||
int frame_size
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Decode an opus custom frame
|
||||
* @param [in] st <tt>OpusCustomDecoder*</tt>: Decoder state
|
||||
* @param [in] data <tt>char*</tt>: Input payload. Use a NULL pointer to indicate packet loss
|
||||
* @param [in] len <tt>int</tt>: Number of bytes in payload
|
||||
* @param [out] pcm <tt>opus_int16*</tt>: Output signal (interleaved if 2 channels). length
|
||||
* is frame_size*channels*sizeof(opus_int16)
|
||||
* @param [in] frame_size Number of samples per channel of available space in *pcm.
|
||||
* @returns Number of decoded samples or @ref opus_errorcodes
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_decode(
|
||||
OpusCustomDecoder *st,
|
||||
const unsigned char *data,
|
||||
int len,
|
||||
opus_int16 *pcm,
|
||||
int frame_size
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Perform a CTL function on an Opus custom decoder.
|
||||
*
|
||||
* Generally the request and subsequent arguments are generated
|
||||
* by a convenience macro.
|
||||
* @see opus_genericctls
|
||||
*/
|
||||
OPUS_CUSTOM_EXPORT int opus_custom_decoder_ctl(OpusCustomDecoder * OPUS_RESTRICT st, int request, ...) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/**@}*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPUS_CUSTOM_H */
|
||||
@@ -0,0 +1,785 @@
|
||||
/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited
|
||||
Written by Jean-Marc Valin and Koen Vos */
|
||||
/*
|
||||
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.
|
||||
|
||||
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 OWNER
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file opus_defines.h
|
||||
* @brief Opus reference implementation constants
|
||||
*/
|
||||
|
||||
#ifndef OPUS_DEFINES_H
|
||||
#define OPUS_DEFINES_H
|
||||
|
||||
#include "opus_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @defgroup opus_errorcodes Error codes
|
||||
* @{
|
||||
*/
|
||||
/** No error @hideinitializer*/
|
||||
#define OPUS_OK 0
|
||||
/** One or more invalid/out of range arguments @hideinitializer*/
|
||||
#define OPUS_BAD_ARG -1
|
||||
/** Not enough bytes allocated in the buffer @hideinitializer*/
|
||||
#define OPUS_BUFFER_TOO_SMALL -2
|
||||
/** An internal error was detected @hideinitializer*/
|
||||
#define OPUS_INTERNAL_ERROR -3
|
||||
/** The compressed data passed is corrupted @hideinitializer*/
|
||||
#define OPUS_INVALID_PACKET -4
|
||||
/** Invalid/unsupported request number @hideinitializer*/
|
||||
#define OPUS_UNIMPLEMENTED -5
|
||||
/** An encoder or decoder structure is invalid or already freed @hideinitializer*/
|
||||
#define OPUS_INVALID_STATE -6
|
||||
/** Memory allocation has failed @hideinitializer*/
|
||||
#define OPUS_ALLOC_FAIL -7
|
||||
/**@}*/
|
||||
|
||||
/** @cond OPUS_INTERNAL_DOC */
|
||||
/**Export control for opus functions */
|
||||
|
||||
#ifndef OPUS_EXPORT
|
||||
# if defined(WIN32)
|
||||
# if defined(OPUS_BUILD) && defined(DLL_EXPORT)
|
||||
# define OPUS_EXPORT __declspec(dllexport)
|
||||
# else
|
||||
# define OPUS_EXPORT
|
||||
# endif
|
||||
# elif defined(__GNUC__) && defined(OPUS_BUILD)
|
||||
# define OPUS_EXPORT __attribute__ ((visibility ("default")))
|
||||
# else
|
||||
# define OPUS_EXPORT
|
||||
# endif
|
||||
#endif
|
||||
|
||||
# if !defined(OPUS_GNUC_PREREQ)
|
||||
# if defined(__GNUC__)&&defined(__GNUC_MINOR__)
|
||||
# define OPUS_GNUC_PREREQ(_maj,_min) \
|
||||
((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min))
|
||||
# else
|
||||
# define OPUS_GNUC_PREREQ(_maj,_min) 0
|
||||
# endif
|
||||
# endif
|
||||
|
||||
#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) )
|
||||
# if OPUS_GNUC_PREREQ(3,0)
|
||||
# define OPUS_RESTRICT __restrict__
|
||||
# elif (defined(_MSC_VER) && _MSC_VER >= 1400)
|
||||
# define OPUS_RESTRICT __restrict
|
||||
# else
|
||||
# define OPUS_RESTRICT
|
||||
# endif
|
||||
#else
|
||||
# define OPUS_RESTRICT restrict
|
||||
#endif
|
||||
|
||||
#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) )
|
||||
# if OPUS_GNUC_PREREQ(2,7)
|
||||
# define OPUS_INLINE __inline__
|
||||
# elif (defined(_MSC_VER))
|
||||
# define OPUS_INLINE __inline
|
||||
# else
|
||||
# define OPUS_INLINE
|
||||
# endif
|
||||
#else
|
||||
# define OPUS_INLINE inline
|
||||
#endif
|
||||
|
||||
/**Warning attributes for opus functions
|
||||
* NONNULL is not used in OPUS_BUILD to avoid the compiler optimizing out
|
||||
* some paranoid null checks. */
|
||||
#if defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4)
|
||||
# define OPUS_WARN_UNUSED_RESULT __attribute__ ((__warn_unused_result__))
|
||||
#else
|
||||
# define OPUS_WARN_UNUSED_RESULT
|
||||
#endif
|
||||
#if !defined(OPUS_BUILD) && defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4)
|
||||
# define OPUS_ARG_NONNULL(_x) __attribute__ ((__nonnull__(_x)))
|
||||
#else
|
||||
# define OPUS_ARG_NONNULL(_x)
|
||||
#endif
|
||||
|
||||
/** These are the actual Encoder CTL ID numbers.
|
||||
* They should not be used directly by applications.
|
||||
* In general, SETs should be even and GETs should be odd.*/
|
||||
#define OPUS_SET_APPLICATION_REQUEST 4000
|
||||
#define OPUS_GET_APPLICATION_REQUEST 4001
|
||||
#define OPUS_SET_BITRATE_REQUEST 4002
|
||||
#define OPUS_GET_BITRATE_REQUEST 4003
|
||||
#define OPUS_SET_MAX_BANDWIDTH_REQUEST 4004
|
||||
#define OPUS_GET_MAX_BANDWIDTH_REQUEST 4005
|
||||
#define OPUS_SET_VBR_REQUEST 4006
|
||||
#define OPUS_GET_VBR_REQUEST 4007
|
||||
#define OPUS_SET_BANDWIDTH_REQUEST 4008
|
||||
#define OPUS_GET_BANDWIDTH_REQUEST 4009
|
||||
#define OPUS_SET_COMPLEXITY_REQUEST 4010
|
||||
#define OPUS_GET_COMPLEXITY_REQUEST 4011
|
||||
#define OPUS_SET_INBAND_FEC_REQUEST 4012
|
||||
#define OPUS_GET_INBAND_FEC_REQUEST 4013
|
||||
#define OPUS_SET_PACKET_LOSS_PERC_REQUEST 4014
|
||||
#define OPUS_GET_PACKET_LOSS_PERC_REQUEST 4015
|
||||
#define OPUS_SET_DTX_REQUEST 4016
|
||||
#define OPUS_GET_DTX_REQUEST 4017
|
||||
#define OPUS_SET_VBR_CONSTRAINT_REQUEST 4020
|
||||
#define OPUS_GET_VBR_CONSTRAINT_REQUEST 4021
|
||||
#define OPUS_SET_FORCE_CHANNELS_REQUEST 4022
|
||||
#define OPUS_GET_FORCE_CHANNELS_REQUEST 4023
|
||||
#define OPUS_SET_SIGNAL_REQUEST 4024
|
||||
#define OPUS_GET_SIGNAL_REQUEST 4025
|
||||
#define OPUS_GET_LOOKAHEAD_REQUEST 4027
|
||||
/* #define OPUS_RESET_STATE 4028 */
|
||||
#define OPUS_GET_SAMPLE_RATE_REQUEST 4029
|
||||
#define OPUS_GET_FINAL_RANGE_REQUEST 4031
|
||||
#define OPUS_GET_PITCH_REQUEST 4033
|
||||
#define OPUS_SET_GAIN_REQUEST 4034
|
||||
#define OPUS_GET_GAIN_REQUEST 4045 /* Should have been 4035 */
|
||||
#define OPUS_SET_LSB_DEPTH_REQUEST 4036
|
||||
#define OPUS_GET_LSB_DEPTH_REQUEST 4037
|
||||
#define OPUS_GET_LAST_PACKET_DURATION_REQUEST 4039
|
||||
#define OPUS_SET_EXPERT_FRAME_DURATION_REQUEST 4040
|
||||
#define OPUS_GET_EXPERT_FRAME_DURATION_REQUEST 4041
|
||||
#define OPUS_SET_PREDICTION_DISABLED_REQUEST 4042
|
||||
#define OPUS_GET_PREDICTION_DISABLED_REQUEST 4043
|
||||
/* Don't use 4045, it's already taken by OPUS_GET_GAIN_REQUEST */
|
||||
#define OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST 4046
|
||||
#define OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST 4047
|
||||
|
||||
/* Macros to trigger compilation errors when the wrong types are provided to a CTL */
|
||||
#define __opus_check_int(x) (((void)((x) == (opus_int32)0)), (opus_int32)(x))
|
||||
#define __opus_check_int_ptr(ptr) ((ptr) + ((ptr) - (opus_int32*)(ptr)))
|
||||
#define __opus_check_uint_ptr(ptr) ((ptr) + ((ptr) - (opus_uint32*)(ptr)))
|
||||
#define __opus_check_val16_ptr(ptr) ((ptr) + ((ptr) - (opus_val16*)(ptr)))
|
||||
/** @endcond */
|
||||
|
||||
/** @defgroup opus_ctlvalues Pre-defined values for CTL interface
|
||||
* @see opus_genericctls, opus_encoderctls
|
||||
* @{
|
||||
*/
|
||||
/* Values for the various encoder CTLs */
|
||||
#define OPUS_AUTO -1000 /**<Auto/default setting @hideinitializer*/
|
||||
#define OPUS_BITRATE_MAX -1 /**<Maximum bitrate @hideinitializer*/
|
||||
|
||||
/** Best for most VoIP/videoconference applications where listening quality and intelligibility matter most
|
||||
* @hideinitializer */
|
||||
#define OPUS_APPLICATION_VOIP 2048
|
||||
/** Best for broadcast/high-fidelity application where the decoded audio should be as close as possible to the input
|
||||
* @hideinitializer */
|
||||
#define OPUS_APPLICATION_AUDIO 2049
|
||||
/** Only use when lowest-achievable latency is what matters most. Voice-optimized modes cannot be used.
|
||||
* @hideinitializer */
|
||||
#define OPUS_APPLICATION_RESTRICTED_LOWDELAY 2051
|
||||
|
||||
#define OPUS_SIGNAL_VOICE 3001 /**< Signal being encoded is voice */
|
||||
#define OPUS_SIGNAL_MUSIC 3002 /**< Signal being encoded is music */
|
||||
#define OPUS_BANDWIDTH_NARROWBAND 1101 /**< 4 kHz bandpass @hideinitializer*/
|
||||
#define OPUS_BANDWIDTH_MEDIUMBAND 1102 /**< 6 kHz bandpass @hideinitializer*/
|
||||
#define OPUS_BANDWIDTH_WIDEBAND 1103 /**< 8 kHz bandpass @hideinitializer*/
|
||||
#define OPUS_BANDWIDTH_SUPERWIDEBAND 1104 /**<12 kHz bandpass @hideinitializer*/
|
||||
#define OPUS_BANDWIDTH_FULLBAND 1105 /**<20 kHz bandpass @hideinitializer*/
|
||||
|
||||
#define OPUS_FRAMESIZE_ARG 5000 /**< Select frame size from the argument (default) */
|
||||
#define OPUS_FRAMESIZE_2_5_MS 5001 /**< Use 2.5 ms frames */
|
||||
#define OPUS_FRAMESIZE_5_MS 5002 /**< Use 5 ms frames */
|
||||
#define OPUS_FRAMESIZE_10_MS 5003 /**< Use 10 ms frames */
|
||||
#define OPUS_FRAMESIZE_20_MS 5004 /**< Use 20 ms frames */
|
||||
#define OPUS_FRAMESIZE_40_MS 5005 /**< Use 40 ms frames */
|
||||
#define OPUS_FRAMESIZE_60_MS 5006 /**< Use 60 ms frames */
|
||||
#define OPUS_FRAMESIZE_80_MS 5007 /**< Use 80 ms frames */
|
||||
#define OPUS_FRAMESIZE_100_MS 5008 /**< Use 100 ms frames */
|
||||
#define OPUS_FRAMESIZE_120_MS 5009 /**< Use 120 ms frames */
|
||||
|
||||
/**@}*/
|
||||
|
||||
|
||||
/** @defgroup opus_encoderctls Encoder related CTLs
|
||||
*
|
||||
* These are convenience macros for use with the \c opus_encode_ctl
|
||||
* interface. They are used to generate the appropriate series of
|
||||
* arguments for that call, passing the correct type, size and so
|
||||
* on as expected for each particular request.
|
||||
*
|
||||
* Some usage examples:
|
||||
*
|
||||
* @code
|
||||
* int ret;
|
||||
* ret = opus_encoder_ctl(enc_ctx, OPUS_SET_BANDWIDTH(OPUS_AUTO));
|
||||
* if (ret != OPUS_OK) return ret;
|
||||
*
|
||||
* opus_int32 rate;
|
||||
* opus_encoder_ctl(enc_ctx, OPUS_GET_BANDWIDTH(&rate));
|
||||
*
|
||||
* opus_encoder_ctl(enc_ctx, OPUS_RESET_STATE);
|
||||
* @endcode
|
||||
*
|
||||
* @see opus_genericctls, opus_encoder
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Configures the encoder's computational complexity.
|
||||
* The supported range is 0-10 inclusive with 10 representing the highest complexity.
|
||||
* @see OPUS_GET_COMPLEXITY
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values: 0-10, inclusive.
|
||||
*
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_COMPLEXITY(x) OPUS_SET_COMPLEXITY_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's complexity configuration.
|
||||
* @see OPUS_SET_COMPLEXITY
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns a value in the range 0-10,
|
||||
* inclusive.
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_COMPLEXITY(x) OPUS_GET_COMPLEXITY_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Configures the bitrate in the encoder.
|
||||
* Rates from 500 to 512000 bits per second are meaningful, as well as the
|
||||
* special values #OPUS_AUTO and #OPUS_BITRATE_MAX.
|
||||
* The value #OPUS_BITRATE_MAX can be used to cause the codec to use as much
|
||||
* rate as it can, which is useful for controlling the rate by adjusting the
|
||||
* output buffer size.
|
||||
* @see OPUS_GET_BITRATE
|
||||
* @param[in] x <tt>opus_int32</tt>: Bitrate in bits per second. The default
|
||||
* is determined based on the number of
|
||||
* channels and the input sampling rate.
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_BITRATE(x) OPUS_SET_BITRATE_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's bitrate configuration.
|
||||
* @see OPUS_SET_BITRATE
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns the bitrate in bits per second.
|
||||
* The default is determined based on the
|
||||
* number of channels and the input
|
||||
* sampling rate.
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_BITRATE(x) OPUS_GET_BITRATE_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Enables or disables variable bitrate (VBR) in the encoder.
|
||||
* The configured bitrate may not be met exactly because frames must
|
||||
* be an integer number of bytes in length.
|
||||
* @see OPUS_GET_VBR
|
||||
* @see OPUS_SET_VBR_CONSTRAINT
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Hard CBR. For LPC/hybrid modes at very low bit-rate, this can
|
||||
* cause noticeable quality degradation.</dd>
|
||||
* <dt>1</dt><dd>VBR (default). The exact type of VBR is controlled by
|
||||
* #OPUS_SET_VBR_CONSTRAINT.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_VBR(x) OPUS_SET_VBR_REQUEST, __opus_check_int(x)
|
||||
/** Determine if variable bitrate (VBR) is enabled in the encoder.
|
||||
* @see OPUS_SET_VBR
|
||||
* @see OPUS_GET_VBR_CONSTRAINT
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Hard CBR.</dd>
|
||||
* <dt>1</dt><dd>VBR (default). The exact type of VBR may be retrieved via
|
||||
* #OPUS_GET_VBR_CONSTRAINT.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_VBR(x) OPUS_GET_VBR_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Enables or disables constrained VBR in the encoder.
|
||||
* This setting is ignored when the encoder is in CBR mode.
|
||||
* @warning Only the MDCT mode of Opus currently heeds the constraint.
|
||||
* Speech mode ignores it completely, hybrid mode may fail to obey it
|
||||
* if the LPC layer uses more bitrate than the constraint would have
|
||||
* permitted.
|
||||
* @see OPUS_GET_VBR_CONSTRAINT
|
||||
* @see OPUS_SET_VBR
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Unconstrained VBR.</dd>
|
||||
* <dt>1</dt><dd>Constrained VBR (default). This creates a maximum of one
|
||||
* frame of buffering delay assuming a transport with a
|
||||
* serialization speed of the nominal bitrate.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_VBR_CONSTRAINT(x) OPUS_SET_VBR_CONSTRAINT_REQUEST, __opus_check_int(x)
|
||||
/** Determine if constrained VBR is enabled in the encoder.
|
||||
* @see OPUS_SET_VBR_CONSTRAINT
|
||||
* @see OPUS_GET_VBR
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Unconstrained VBR.</dd>
|
||||
* <dt>1</dt><dd>Constrained VBR (default).</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_VBR_CONSTRAINT(x) OPUS_GET_VBR_CONSTRAINT_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Configures mono/stereo forcing in the encoder.
|
||||
* This can force the encoder to produce packets encoded as either mono or
|
||||
* stereo, regardless of the format of the input audio. This is useful when
|
||||
* the caller knows that the input signal is currently a mono source embedded
|
||||
* in a stereo stream.
|
||||
* @see OPUS_GET_FORCE_CHANNELS
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt><dd>Not forced (default)</dd>
|
||||
* <dt>1</dt> <dd>Forced mono</dd>
|
||||
* <dt>2</dt> <dd>Forced stereo</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_FORCE_CHANNELS(x) OPUS_SET_FORCE_CHANNELS_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's forced channel configuration.
|
||||
* @see OPUS_SET_FORCE_CHANNELS
|
||||
* @param[out] x <tt>opus_int32 *</tt>:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt><dd>Not forced (default)</dd>
|
||||
* <dt>1</dt> <dd>Forced mono</dd>
|
||||
* <dt>2</dt> <dd>Forced stereo</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_FORCE_CHANNELS(x) OPUS_GET_FORCE_CHANNELS_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Configures the maximum bandpass that the encoder will select automatically.
|
||||
* Applications should normally use this instead of #OPUS_SET_BANDWIDTH
|
||||
* (leaving that set to the default, #OPUS_AUTO). This allows the
|
||||
* application to set an upper bound based on the type of input it is
|
||||
* providing, but still gives the encoder the freedom to reduce the bandpass
|
||||
* when the bitrate becomes too low, for better overall quality.
|
||||
* @see OPUS_GET_MAX_BANDWIDTH
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
|
||||
* <dt>OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
|
||||
* <dt>OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
|
||||
* <dt>OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
|
||||
* <dt>OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband (default)</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_MAX_BANDWIDTH(x) OPUS_SET_MAX_BANDWIDTH_REQUEST, __opus_check_int(x)
|
||||
|
||||
/** Gets the encoder's configured maximum allowed bandpass.
|
||||
* @see OPUS_SET_MAX_BANDWIDTH
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband (default)</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_MAX_BANDWIDTH(x) OPUS_GET_MAX_BANDWIDTH_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Sets the encoder's bandpass to a specific value.
|
||||
* This prevents the encoder from automatically selecting the bandpass based
|
||||
* on the available bitrate. If an application knows the bandpass of the input
|
||||
* audio it is providing, it should normally use #OPUS_SET_MAX_BANDWIDTH
|
||||
* instead, which still gives the encoder the freedom to reduce the bandpass
|
||||
* when the bitrate becomes too low, for better overall quality.
|
||||
* @see OPUS_GET_BANDWIDTH
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_BANDWIDTH(x) OPUS_SET_BANDWIDTH_REQUEST, __opus_check_int(x)
|
||||
|
||||
/** Configures the type of signal being encoded.
|
||||
* This is a hint which helps the encoder's mode selection.
|
||||
* @see OPUS_GET_SIGNAL
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
|
||||
* <dt>#OPUS_SIGNAL_VOICE</dt><dd>Bias thresholds towards choosing LPC or Hybrid modes.</dd>
|
||||
* <dt>#OPUS_SIGNAL_MUSIC</dt><dd>Bias thresholds towards choosing MDCT modes.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_SIGNAL(x) OPUS_SET_SIGNAL_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's configured signal type.
|
||||
* @see OPUS_SET_SIGNAL
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
|
||||
* <dt>#OPUS_SIGNAL_VOICE</dt><dd>Bias thresholds towards choosing LPC or Hybrid modes.</dd>
|
||||
* <dt>#OPUS_SIGNAL_MUSIC</dt><dd>Bias thresholds towards choosing MDCT modes.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_SIGNAL(x) OPUS_GET_SIGNAL_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
|
||||
/** Configures the encoder's intended application.
|
||||
* The initial value is a mandatory argument to the encoder_create function.
|
||||
* @see OPUS_GET_APPLICATION
|
||||
* @param[in] x <tt>opus_int32</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_APPLICATION_VOIP</dt>
|
||||
* <dd>Process signal for improved speech intelligibility.</dd>
|
||||
* <dt>#OPUS_APPLICATION_AUDIO</dt>
|
||||
* <dd>Favor faithfulness to the original input.</dd>
|
||||
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
|
||||
* <dd>Configure the minimum possible coding delay by disabling certain modes
|
||||
* of operation.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_APPLICATION(x) OPUS_SET_APPLICATION_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's configured application.
|
||||
* @see OPUS_SET_APPLICATION
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_APPLICATION_VOIP</dt>
|
||||
* <dd>Process signal for improved speech intelligibility.</dd>
|
||||
* <dt>#OPUS_APPLICATION_AUDIO</dt>
|
||||
* <dd>Favor faithfulness to the original input.</dd>
|
||||
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
|
||||
* <dd>Configure the minimum possible coding delay by disabling certain modes
|
||||
* of operation.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_APPLICATION(x) OPUS_GET_APPLICATION_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Gets the total samples of delay added by the entire codec.
|
||||
* This can be queried by the encoder and then the provided number of samples can be
|
||||
* skipped on from the start of the decoder's output to provide time aligned input
|
||||
* and output. From the perspective of a decoding application the real data begins this many
|
||||
* samples late.
|
||||
*
|
||||
* The decoder contribution to this delay is identical for all decoders, but the
|
||||
* encoder portion of the delay may vary from implementation to implementation,
|
||||
* version to version, or even depend on the encoder's initial configuration.
|
||||
* Applications needing delay compensation should call this CTL rather than
|
||||
* hard-coding a value.
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Number of lookahead samples
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_LOOKAHEAD(x) OPUS_GET_LOOKAHEAD_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Configures the encoder's use of inband forward error correction (FEC).
|
||||
* @note This is only applicable to the LPC layer
|
||||
* @see OPUS_GET_INBAND_FEC
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Disable inband FEC (default).</dd>
|
||||
* <dt>1</dt><dd>Enable inband FEC.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_INBAND_FEC(x) OPUS_SET_INBAND_FEC_REQUEST, __opus_check_int(x)
|
||||
/** Gets encoder's configured use of inband forward error correction.
|
||||
* @see OPUS_SET_INBAND_FEC
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Inband FEC disabled (default).</dd>
|
||||
* <dt>1</dt><dd>Inband FEC enabled.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_INBAND_FEC(x) OPUS_GET_INBAND_FEC_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Configures the encoder's expected packet loss percentage.
|
||||
* Higher values trigger progressively more loss resistant behavior in the encoder
|
||||
* at the expense of quality at a given bitrate in the absence of packet loss, but
|
||||
* greater quality under loss.
|
||||
* @see OPUS_GET_PACKET_LOSS_PERC
|
||||
* @param[in] x <tt>opus_int32</tt>: Loss percentage in the range 0-100, inclusive (default: 0).
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_PACKET_LOSS_PERC(x) OPUS_SET_PACKET_LOSS_PERC_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's configured packet loss percentage.
|
||||
* @see OPUS_SET_PACKET_LOSS_PERC
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns the configured loss percentage
|
||||
* in the range 0-100, inclusive (default: 0).
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_PACKET_LOSS_PERC(x) OPUS_GET_PACKET_LOSS_PERC_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Configures the encoder's use of discontinuous transmission (DTX).
|
||||
* @note This is only applicable to the LPC layer
|
||||
* @see OPUS_GET_DTX
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Disable DTX (default).</dd>
|
||||
* <dt>1</dt><dd>Enabled DTX.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_DTX(x) OPUS_SET_DTX_REQUEST, __opus_check_int(x)
|
||||
/** Gets encoder's configured use of discontinuous transmission.
|
||||
* @see OPUS_SET_DTX
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>DTX disabled (default).</dd>
|
||||
* <dt>1</dt><dd>DTX enabled.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_DTX(x) OPUS_GET_DTX_REQUEST, __opus_check_int_ptr(x)
|
||||
/** Configures the depth of signal being encoded.
|
||||
*
|
||||
* This is a hint which helps the encoder identify silence and near-silence.
|
||||
* It represents the number of significant bits of linear intensity below
|
||||
* which the signal contains ignorable quantization or other noise.
|
||||
*
|
||||
* For example, OPUS_SET_LSB_DEPTH(14) would be an appropriate setting
|
||||
* for G.711 u-law input. OPUS_SET_LSB_DEPTH(16) would be appropriate
|
||||
* for 16-bit linear pcm input with opus_encode_float().
|
||||
*
|
||||
* When using opus_encode() instead of opus_encode_float(), or when libopus
|
||||
* is compiled for fixed-point, the encoder uses the minimum of the value
|
||||
* set here and the value 16.
|
||||
*
|
||||
* @see OPUS_GET_LSB_DEPTH
|
||||
* @param[in] x <tt>opus_int32</tt>: Input precision in bits, between 8 and 24
|
||||
* (default: 24).
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_LSB_DEPTH(x) OPUS_SET_LSB_DEPTH_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's configured signal depth.
|
||||
* @see OPUS_SET_LSB_DEPTH
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Input precision in bits, between 8 and
|
||||
* 24 (default: 24).
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_LSB_DEPTH(x) OPUS_GET_LSB_DEPTH_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Configures the encoder's use of variable duration frames.
|
||||
* When variable duration is enabled, the encoder is free to use a shorter frame
|
||||
* size than the one requested in the opus_encode*() call.
|
||||
* It is then the user's responsibility
|
||||
* to verify how much audio was encoded by checking the ToC byte of the encoded
|
||||
* packet. The part of the audio that was not encoded needs to be resent to the
|
||||
* encoder for the next call. Do not use this option unless you <b>really</b>
|
||||
* know what you are doing.
|
||||
* @see OPUS_GET_EXPERT_FRAME_DURATION
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>OPUS_FRAMESIZE_ARG</dt><dd>Select frame size from the argument (default).</dd>
|
||||
* <dt>OPUS_FRAMESIZE_2_5_MS</dt><dd>Use 2.5 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_5_MS</dt><dd>Use 5 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_10_MS</dt><dd>Use 10 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_20_MS</dt><dd>Use 20 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_40_MS</dt><dd>Use 40 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_60_MS</dt><dd>Use 60 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_80_MS</dt><dd>Use 80 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_100_MS</dt><dd>Use 100 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_120_MS</dt><dd>Use 120 ms frames.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_EXPERT_FRAME_DURATION(x) OPUS_SET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's configured use of variable duration frames.
|
||||
* @see OPUS_SET_EXPERT_FRAME_DURATION
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>OPUS_FRAMESIZE_ARG</dt><dd>Select frame size from the argument (default).</dd>
|
||||
* <dt>OPUS_FRAMESIZE_2_5_MS</dt><dd>Use 2.5 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_5_MS</dt><dd>Use 5 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_10_MS</dt><dd>Use 10 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_20_MS</dt><dd>Use 20 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_40_MS</dt><dd>Use 40 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_60_MS</dt><dd>Use 60 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_80_MS</dt><dd>Use 80 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_100_MS</dt><dd>Use 100 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_120_MS</dt><dd>Use 120 ms frames.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_EXPERT_FRAME_DURATION(x) OPUS_GET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** If set to 1, disables almost all use of prediction, making frames almost
|
||||
* completely independent. This reduces quality.
|
||||
* @see OPUS_GET_PREDICTION_DISABLED
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Enable prediction (default).</dd>
|
||||
* <dt>1</dt><dd>Disable prediction.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_PREDICTION_DISABLED(x) OPUS_SET_PREDICTION_DISABLED_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's configured prediction status.
|
||||
* @see OPUS_SET_PREDICTION_DISABLED
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Prediction enabled (default).</dd>
|
||||
* <dt>1</dt><dd>Prediction disabled.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_PREDICTION_DISABLED(x) OPUS_GET_PREDICTION_DISABLED_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/**@}*/
|
||||
|
||||
/** @defgroup opus_genericctls Generic CTLs
|
||||
*
|
||||
* These macros are used with the \c opus_decoder_ctl and
|
||||
* \c opus_encoder_ctl calls to generate a particular
|
||||
* request.
|
||||
*
|
||||
* When called on an \c OpusDecoder they apply to that
|
||||
* particular decoder instance. When called on an
|
||||
* \c OpusEncoder they apply to the corresponding setting
|
||||
* on that encoder instance, if present.
|
||||
*
|
||||
* Some usage examples:
|
||||
*
|
||||
* @code
|
||||
* int ret;
|
||||
* opus_int32 pitch;
|
||||
* ret = opus_decoder_ctl(dec_ctx, OPUS_GET_PITCH(&pitch));
|
||||
* if (ret == OPUS_OK) return ret;
|
||||
*
|
||||
* opus_encoder_ctl(enc_ctx, OPUS_RESET_STATE);
|
||||
* opus_decoder_ctl(dec_ctx, OPUS_RESET_STATE);
|
||||
*
|
||||
* opus_int32 enc_bw, dec_bw;
|
||||
* opus_encoder_ctl(enc_ctx, OPUS_GET_BANDWIDTH(&enc_bw));
|
||||
* opus_decoder_ctl(dec_ctx, OPUS_GET_BANDWIDTH(&dec_bw));
|
||||
* if (enc_bw != dec_bw) {
|
||||
* printf("packet bandwidth mismatch!\n");
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @see opus_encoder, opus_decoder_ctl, opus_encoder_ctl, opus_decoderctls, opus_encoderctls
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Resets the codec state to be equivalent to a freshly initialized state.
|
||||
* This should be called when switching streams in order to prevent
|
||||
* the back to back decoding from giving different results from
|
||||
* one at a time decoding.
|
||||
* @hideinitializer */
|
||||
#define OPUS_RESET_STATE 4028
|
||||
|
||||
/** Gets the final state of the codec's entropy coder.
|
||||
* This is used for testing purposes,
|
||||
* The encoder and decoder state should be identical after coding a payload
|
||||
* (assuming no data corruption or software bugs)
|
||||
*
|
||||
* @param[out] x <tt>opus_uint32 *</tt>: Entropy coder state
|
||||
*
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_FINAL_RANGE(x) OPUS_GET_FINAL_RANGE_REQUEST, __opus_check_uint_ptr(x)
|
||||
|
||||
/** Gets the encoder's configured bandpass or the decoder's last bandpass.
|
||||
* @see OPUS_SET_BANDWIDTH
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_BANDWIDTH(x) OPUS_GET_BANDWIDTH_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Gets the sampling rate the encoder or decoder was initialized with.
|
||||
* This simply returns the <code>Fs</code> value passed to opus_encoder_init()
|
||||
* or opus_decoder_init().
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Sampling rate of encoder or decoder.
|
||||
* @hideinitializer
|
||||
*/
|
||||
#define OPUS_GET_SAMPLE_RATE(x) OPUS_GET_SAMPLE_RATE_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** If set to 1, disables the use of phase inversion for intensity stereo,
|
||||
* improving the quality of mono downmixes, but slightly reducing normal
|
||||
* stereo quality. Disabling phase inversion in the decoder does not comply
|
||||
* with RFC 6716, although it does not cause any interoperability issue and
|
||||
* is expected to become part of the Opus standard once RFC 6716 is updated
|
||||
* by draft-ietf-codec-opus-update.
|
||||
* @see OPUS_GET_PHASE_INVERSION_DISABLED
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Enable phase inversion (default).</dd>
|
||||
* <dt>1</dt><dd>Disable phase inversion.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_PHASE_INVERSION_DISABLED(x) OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's configured phase inversion status.
|
||||
* @see OPUS_SET_PHASE_INVERSION_DISABLED
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Stereo phase inversion enabled (default).</dd>
|
||||
* <dt>1</dt><dd>Stereo phase inversion disabled.</dd>
|
||||
* </dl>
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_PHASE_INVERSION_DISABLED(x) OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/**@}*/
|
||||
|
||||
/** @defgroup opus_decoderctls Decoder related CTLs
|
||||
* @see opus_genericctls, opus_encoderctls, opus_decoder
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Configures decoder gain adjustment.
|
||||
* Scales the decoded output by a factor specified in Q8 dB units.
|
||||
* This has a maximum range of -32768 to 32767 inclusive, and returns
|
||||
* OPUS_BAD_ARG otherwise. The default is zero indicating no adjustment.
|
||||
* This setting survives decoder reset.
|
||||
*
|
||||
* gain = pow(10, x/(20.0*256))
|
||||
*
|
||||
* @param[in] x <tt>opus_int32</tt>: Amount to scale PCM signal by in Q8 dB units.
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_GAIN(x) OPUS_SET_GAIN_REQUEST, __opus_check_int(x)
|
||||
/** Gets the decoder's configured gain adjustment. @see OPUS_SET_GAIN
|
||||
*
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Amount to scale PCM signal by in Q8 dB units.
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_GAIN(x) OPUS_GET_GAIN_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Gets the duration (in samples) of the last packet successfully decoded or concealed.
|
||||
* @param[out] x <tt>opus_int32 *</tt>: Number of samples (at current sampling rate).
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_LAST_PACKET_DURATION(x) OPUS_GET_LAST_PACKET_DURATION_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/** Gets the pitch of the last decoded frame, if available.
|
||||
* This can be used for any post-processing algorithm requiring the use of pitch,
|
||||
* e.g. time stretching/shortening. If the last frame was not voiced, or if the
|
||||
* pitch was not coded in the frame, then zero is returned.
|
||||
*
|
||||
* This CTL is only implemented for decoder instances.
|
||||
*
|
||||
* @param[out] x <tt>opus_int32 *</tt>: pitch period at 48 kHz (or 0 if not available)
|
||||
*
|
||||
* @hideinitializer */
|
||||
#define OPUS_GET_PITCH(x) OPUS_GET_PITCH_REQUEST, __opus_check_int_ptr(x)
|
||||
|
||||
/**@}*/
|
||||
|
||||
/** @defgroup opus_libinfo Opus library information functions
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Converts an opus error code into a human readable string.
|
||||
*
|
||||
* @param[in] error <tt>int</tt>: Error number
|
||||
* @returns Error string
|
||||
*/
|
||||
OPUS_EXPORT const char *opus_strerror(int error);
|
||||
|
||||
/** Gets the libopus version string.
|
||||
*
|
||||
* Applications may look for the substring "-fixed" in the version string to
|
||||
* determine whether they have a fixed-point or floating-point build at
|
||||
* runtime.
|
||||
*
|
||||
* @returns Version string
|
||||
*/
|
||||
OPUS_EXPORT const char *opus_get_version_string(void);
|
||||
/**@}*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPUS_DEFINES_H */
|
||||
@@ -0,0 +1,660 @@
|
||||
/* Copyright (c) 2011 Xiph.Org Foundation
|
||||
Written by Jean-Marc Valin */
|
||||
/*
|
||||
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.
|
||||
|
||||
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 OWNER
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file opus_multistream.h
|
||||
* @brief Opus reference implementation multistream API
|
||||
*/
|
||||
|
||||
#ifndef OPUS_MULTISTREAM_H
|
||||
#define OPUS_MULTISTREAM_H
|
||||
|
||||
#include "opus.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @cond OPUS_INTERNAL_DOC */
|
||||
|
||||
/** Macros to trigger compilation errors when the wrong types are provided to a
|
||||
* CTL. */
|
||||
/**@{*/
|
||||
#define __opus_check_encstate_ptr(ptr) ((ptr) + ((ptr) - (OpusEncoder**)(ptr)))
|
||||
#define __opus_check_decstate_ptr(ptr) ((ptr) + ((ptr) - (OpusDecoder**)(ptr)))
|
||||
/**@}*/
|
||||
|
||||
/** These are the actual encoder and decoder CTL ID numbers.
|
||||
* They should not be used directly by applications.
|
||||
* In general, SETs should be even and GETs should be odd.*/
|
||||
/**@{*/
|
||||
#define OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST 5120
|
||||
#define OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST 5122
|
||||
/**@}*/
|
||||
|
||||
/** @endcond */
|
||||
|
||||
/** @defgroup opus_multistream_ctls Multistream specific encoder and decoder CTLs
|
||||
*
|
||||
* These are convenience macros that are specific to the
|
||||
* opus_multistream_encoder_ctl() and opus_multistream_decoder_ctl()
|
||||
* interface.
|
||||
* The CTLs from @ref opus_genericctls, @ref opus_encoderctls, and
|
||||
* @ref opus_decoderctls may be applied to a multistream encoder or decoder as
|
||||
* well.
|
||||
* In addition, you may retrieve the encoder or decoder state for an specific
|
||||
* stream via #OPUS_MULTISTREAM_GET_ENCODER_STATE or
|
||||
* #OPUS_MULTISTREAM_GET_DECODER_STATE and apply CTLs to it individually.
|
||||
*/
|
||||
/**@{*/
|
||||
|
||||
/** Gets the encoder state for an individual stream of a multistream encoder.
|
||||
* @param[in] x <tt>opus_int32</tt>: The index of the stream whose encoder you
|
||||
* wish to retrieve.
|
||||
* This must be non-negative and less than
|
||||
* the <code>streams</code> parameter used
|
||||
* to initialize the encoder.
|
||||
* @param[out] y <tt>OpusEncoder**</tt>: Returns a pointer to the given
|
||||
* encoder state.
|
||||
* @retval OPUS_BAD_ARG The index of the requested stream was out of range.
|
||||
* @hideinitializer
|
||||
*/
|
||||
#define OPUS_MULTISTREAM_GET_ENCODER_STATE(x,y) OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST, __opus_check_int(x), __opus_check_encstate_ptr(y)
|
||||
|
||||
/** Gets the decoder state for an individual stream of a multistream decoder.
|
||||
* @param[in] x <tt>opus_int32</tt>: The index of the stream whose decoder you
|
||||
* wish to retrieve.
|
||||
* This must be non-negative and less than
|
||||
* the <code>streams</code> parameter used
|
||||
* to initialize the decoder.
|
||||
* @param[out] y <tt>OpusDecoder**</tt>: Returns a pointer to the given
|
||||
* decoder state.
|
||||
* @retval OPUS_BAD_ARG The index of the requested stream was out of range.
|
||||
* @hideinitializer
|
||||
*/
|
||||
#define OPUS_MULTISTREAM_GET_DECODER_STATE(x,y) OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST, __opus_check_int(x), __opus_check_decstate_ptr(y)
|
||||
|
||||
/**@}*/
|
||||
|
||||
/** @defgroup opus_multistream Opus Multistream API
|
||||
* @{
|
||||
*
|
||||
* The multistream API allows individual Opus streams to be combined into a
|
||||
* single packet, enabling support for up to 255 channels. Unlike an
|
||||
* elementary Opus stream, the encoder and decoder must negotiate the channel
|
||||
* configuration before the decoder can successfully interpret the data in the
|
||||
* packets produced by the encoder. Some basic information, such as packet
|
||||
* duration, can be computed without any special negotiation.
|
||||
*
|
||||
* The format for multistream Opus packets is defined in
|
||||
* <a href="https://tools.ietf.org/html/rfc7845">RFC 7845</a>
|
||||
* and is based on the self-delimited Opus framing described in Appendix B of
|
||||
* <a href="https://tools.ietf.org/html/rfc6716">RFC 6716</a>.
|
||||
* Normal Opus packets are just a degenerate case of multistream Opus packets,
|
||||
* and can be encoded or decoded with the multistream API by setting
|
||||
* <code>streams</code> to <code>1</code> when initializing the encoder or
|
||||
* decoder.
|
||||
*
|
||||
* Multistream Opus streams can contain up to 255 elementary Opus streams.
|
||||
* These may be either "uncoupled" or "coupled", indicating that the decoder
|
||||
* is configured to decode them to either 1 or 2 channels, respectively.
|
||||
* The streams are ordered so that all coupled streams appear at the
|
||||
* beginning.
|
||||
*
|
||||
* A <code>mapping</code> table defines which decoded channel <code>i</code>
|
||||
* should be used for each input/output (I/O) channel <code>j</code>. This table is
|
||||
* typically provided as an unsigned char array.
|
||||
* Let <code>i = mapping[j]</code> be the index for I/O channel <code>j</code>.
|
||||
* If <code>i < 2*coupled_streams</code>, then I/O channel <code>j</code> is
|
||||
* encoded as the left channel of stream <code>(i/2)</code> if <code>i</code>
|
||||
* is even, or as the right channel of stream <code>(i/2)</code> if
|
||||
* <code>i</code> is odd. Otherwise, I/O channel <code>j</code> is encoded as
|
||||
* mono in stream <code>(i - coupled_streams)</code>, unless it has the special
|
||||
* value 255, in which case it is omitted from the encoding entirely (the
|
||||
* decoder will reproduce it as silence). Each value <code>i</code> must either
|
||||
* be the special value 255 or be less than <code>streams + coupled_streams</code>.
|
||||
*
|
||||
* The output channels specified by the encoder
|
||||
* should use the
|
||||
* <a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810004.3.9">Vorbis
|
||||
* channel ordering</a>. A decoder may wish to apply an additional permutation
|
||||
* to the mapping the encoder used to achieve a different output channel
|
||||
* order (e.g. for outputing in WAV order).
|
||||
*
|
||||
* Each multistream packet contains an Opus packet for each stream, and all of
|
||||
* the Opus packets in a single multistream packet must have the same
|
||||
* duration. Therefore the duration of a multistream packet can be extracted
|
||||
* from the TOC sequence of the first stream, which is located at the
|
||||
* beginning of the packet, just like an elementary Opus stream:
|
||||
*
|
||||
* @code
|
||||
* int nb_samples;
|
||||
* int nb_frames;
|
||||
* nb_frames = opus_packet_get_nb_frames(data, len);
|
||||
* if (nb_frames < 1)
|
||||
* return nb_frames;
|
||||
* nb_samples = opus_packet_get_samples_per_frame(data, 48000) * nb_frames;
|
||||
* @endcode
|
||||
*
|
||||
* The general encoding and decoding process proceeds exactly the same as in
|
||||
* the normal @ref opus_encoder and @ref opus_decoder APIs.
|
||||
* See their documentation for an overview of how to use the corresponding
|
||||
* multistream functions.
|
||||
*/
|
||||
|
||||
/** Opus multistream encoder state.
|
||||
* This contains the complete state of a multistream Opus encoder.
|
||||
* It is position independent and can be freely copied.
|
||||
* @see opus_multistream_encoder_create
|
||||
* @see opus_multistream_encoder_init
|
||||
*/
|
||||
typedef struct OpusMSEncoder OpusMSEncoder;
|
||||
|
||||
/** Opus multistream decoder state.
|
||||
* This contains the complete state of a multistream Opus decoder.
|
||||
* It is position independent and can be freely copied.
|
||||
* @see opus_multistream_decoder_create
|
||||
* @see opus_multistream_decoder_init
|
||||
*/
|
||||
typedef struct OpusMSDecoder OpusMSDecoder;
|
||||
|
||||
/**\name Multistream encoder functions */
|
||||
/**@{*/
|
||||
|
||||
/** Gets the size of an OpusMSEncoder structure.
|
||||
* @param streams <tt>int</tt>: The total number of streams to encode from the
|
||||
* input.
|
||||
* This must be no more than 255.
|
||||
* @param coupled_streams <tt>int</tt>: Number of coupled (2 channel) streams
|
||||
* to encode.
|
||||
* This must be no larger than the total
|
||||
* number of streams.
|
||||
* Additionally, The total number of
|
||||
* encoded channels (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than 255.
|
||||
* @returns The size in bytes on success, or a negative error code
|
||||
* (see @ref opus_errorcodes) on error.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_encoder_get_size(
|
||||
int streams,
|
||||
int coupled_streams
|
||||
);
|
||||
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_surround_encoder_get_size(
|
||||
int channels,
|
||||
int mapping_family
|
||||
);
|
||||
|
||||
|
||||
/** Allocates and initializes a multistream encoder state.
|
||||
* Call opus_multistream_encoder_destroy() to release
|
||||
* this object when finished.
|
||||
* @param Fs <tt>opus_int32</tt>: Sampling rate of the input signal (in Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param channels <tt>int</tt>: Number of channels in the input signal.
|
||||
* This must be at most 255.
|
||||
* It may be greater than the number of
|
||||
* coded channels (<code>streams +
|
||||
* coupled_streams</code>).
|
||||
* @param streams <tt>int</tt>: The total number of streams to encode from the
|
||||
* input.
|
||||
* This must be no more than the number of channels.
|
||||
* @param coupled_streams <tt>int</tt>: Number of coupled (2 channel) streams
|
||||
* to encode.
|
||||
* This must be no larger than the total
|
||||
* number of streams.
|
||||
* Additionally, The total number of
|
||||
* encoded channels (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than the number of input channels.
|
||||
* @param[in] mapping <code>const unsigned char[channels]</code>: Mapping from
|
||||
* encoded channels to input channels, as described in
|
||||
* @ref opus_multistream. As an extra constraint, the
|
||||
* multistream encoder does not allow encoding coupled
|
||||
* streams for which one channel is unused since this
|
||||
* is never a good idea.
|
||||
* @param application <tt>int</tt>: The target encoder application.
|
||||
* This must be one of the following:
|
||||
* <dl>
|
||||
* <dt>#OPUS_APPLICATION_VOIP</dt>
|
||||
* <dd>Process signal for improved speech intelligibility.</dd>
|
||||
* <dt>#OPUS_APPLICATION_AUDIO</dt>
|
||||
* <dd>Favor faithfulness to the original input.</dd>
|
||||
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
|
||||
* <dd>Configure the minimum possible coding delay by disabling certain modes
|
||||
* of operation.</dd>
|
||||
* </dl>
|
||||
* @param[out] error <tt>int *</tt>: Returns #OPUS_OK on success, or an error
|
||||
* code (see @ref opus_errorcodes) on
|
||||
* failure.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_encoder_create(
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int streams,
|
||||
int coupled_streams,
|
||||
const unsigned char *mapping,
|
||||
int application,
|
||||
int *error
|
||||
) OPUS_ARG_NONNULL(5);
|
||||
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_surround_encoder_create(
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int mapping_family,
|
||||
int *streams,
|
||||
int *coupled_streams,
|
||||
unsigned char *mapping,
|
||||
int application,
|
||||
int *error
|
||||
) OPUS_ARG_NONNULL(4) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6);
|
||||
|
||||
/** Initialize a previously allocated multistream encoder state.
|
||||
* The memory pointed to by \a st must be at least the size returned by
|
||||
* opus_multistream_encoder_get_size().
|
||||
* This is intended for applications which use their own allocator instead of
|
||||
* malloc.
|
||||
* To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
|
||||
* @see opus_multistream_encoder_create
|
||||
* @see opus_multistream_encoder_get_size
|
||||
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state to initialize.
|
||||
* @param Fs <tt>opus_int32</tt>: Sampling rate of the input signal (in Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param channels <tt>int</tt>: Number of channels in the input signal.
|
||||
* This must be at most 255.
|
||||
* It may be greater than the number of
|
||||
* coded channels (<code>streams +
|
||||
* coupled_streams</code>).
|
||||
* @param streams <tt>int</tt>: The total number of streams to encode from the
|
||||
* input.
|
||||
* This must be no more than the number of channels.
|
||||
* @param coupled_streams <tt>int</tt>: Number of coupled (2 channel) streams
|
||||
* to encode.
|
||||
* This must be no larger than the total
|
||||
* number of streams.
|
||||
* Additionally, The total number of
|
||||
* encoded channels (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than the number of input channels.
|
||||
* @param[in] mapping <code>const unsigned char[channels]</code>: Mapping from
|
||||
* encoded channels to input channels, as described in
|
||||
* @ref opus_multistream. As an extra constraint, the
|
||||
* multistream encoder does not allow encoding coupled
|
||||
* streams for which one channel is unused since this
|
||||
* is never a good idea.
|
||||
* @param application <tt>int</tt>: The target encoder application.
|
||||
* This must be one of the following:
|
||||
* <dl>
|
||||
* <dt>#OPUS_APPLICATION_VOIP</dt>
|
||||
* <dd>Process signal for improved speech intelligibility.</dd>
|
||||
* <dt>#OPUS_APPLICATION_AUDIO</dt>
|
||||
* <dd>Favor faithfulness to the original input.</dd>
|
||||
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
|
||||
* <dd>Configure the minimum possible coding delay by disabling certain modes
|
||||
* of operation.</dd>
|
||||
* </dl>
|
||||
* @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes)
|
||||
* on failure.
|
||||
*/
|
||||
OPUS_EXPORT int opus_multistream_encoder_init(
|
||||
OpusMSEncoder *st,
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int streams,
|
||||
int coupled_streams,
|
||||
const unsigned char *mapping,
|
||||
int application
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6);
|
||||
|
||||
OPUS_EXPORT int opus_multistream_surround_encoder_init(
|
||||
OpusMSEncoder *st,
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int mapping_family,
|
||||
int *streams,
|
||||
int *coupled_streams,
|
||||
unsigned char *mapping,
|
||||
int application
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6) OPUS_ARG_NONNULL(7);
|
||||
|
||||
/** Encodes a multistream Opus frame.
|
||||
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state.
|
||||
* @param[in] pcm <tt>const opus_int16*</tt>: The input signal as interleaved
|
||||
* samples.
|
||||
* This must contain
|
||||
* <code>frame_size*channels</code>
|
||||
* samples.
|
||||
* @param frame_size <tt>int</tt>: Number of samples per channel in the input
|
||||
* signal.
|
||||
* This must be an Opus frame size for the
|
||||
* encoder's sampling rate.
|
||||
* For example, at 48 kHz the permitted values
|
||||
* are 120, 240, 480, 960, 1920, and 2880.
|
||||
* Passing in a duration of less than 10 ms
|
||||
* (480 samples at 48 kHz) will prevent the
|
||||
* encoder from using the LPC or hybrid modes.
|
||||
* @param[out] data <tt>unsigned char*</tt>: Output payload.
|
||||
* This must contain storage for at
|
||||
* least \a max_data_bytes.
|
||||
* @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
|
||||
* memory for the output
|
||||
* payload. This may be
|
||||
* used to impose an upper limit on
|
||||
* the instant bitrate, but should
|
||||
* not be used as the only bitrate
|
||||
* control. Use #OPUS_SET_BITRATE to
|
||||
* control the bitrate.
|
||||
* @returns The length of the encoded packet (in bytes) on success or a
|
||||
* negative error code (see @ref opus_errorcodes) on failure.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode(
|
||||
OpusMSEncoder *st,
|
||||
const opus_int16 *pcm,
|
||||
int frame_size,
|
||||
unsigned char *data,
|
||||
opus_int32 max_data_bytes
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Encodes a multistream Opus frame from floating point input.
|
||||
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state.
|
||||
* @param[in] pcm <tt>const float*</tt>: The input signal as interleaved
|
||||
* samples with a normal range of
|
||||
* +/-1.0.
|
||||
* Samples with a range beyond +/-1.0
|
||||
* are supported but will be clipped by
|
||||
* decoders using the integer API and
|
||||
* should only be used if it is known
|
||||
* that the far end supports extended
|
||||
* dynamic range.
|
||||
* This must contain
|
||||
* <code>frame_size*channels</code>
|
||||
* samples.
|
||||
* @param frame_size <tt>int</tt>: Number of samples per channel in the input
|
||||
* signal.
|
||||
* This must be an Opus frame size for the
|
||||
* encoder's sampling rate.
|
||||
* For example, at 48 kHz the permitted values
|
||||
* are 120, 240, 480, 960, 1920, and 2880.
|
||||
* Passing in a duration of less than 10 ms
|
||||
* (480 samples at 48 kHz) will prevent the
|
||||
* encoder from using the LPC or hybrid modes.
|
||||
* @param[out] data <tt>unsigned char*</tt>: Output payload.
|
||||
* This must contain storage for at
|
||||
* least \a max_data_bytes.
|
||||
* @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
|
||||
* memory for the output
|
||||
* payload. This may be
|
||||
* used to impose an upper limit on
|
||||
* the instant bitrate, but should
|
||||
* not be used as the only bitrate
|
||||
* control. Use #OPUS_SET_BITRATE to
|
||||
* control the bitrate.
|
||||
* @returns The length of the encoded packet (in bytes) on success or a
|
||||
* negative error code (see @ref opus_errorcodes) on failure.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode_float(
|
||||
OpusMSEncoder *st,
|
||||
const float *pcm,
|
||||
int frame_size,
|
||||
unsigned char *data,
|
||||
opus_int32 max_data_bytes
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Frees an <code>OpusMSEncoder</code> allocated by
|
||||
* opus_multistream_encoder_create().
|
||||
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state to be freed.
|
||||
*/
|
||||
OPUS_EXPORT void opus_multistream_encoder_destroy(OpusMSEncoder *st);
|
||||
|
||||
/** Perform a CTL function on a multistream Opus encoder.
|
||||
*
|
||||
* Generally the request and subsequent arguments are generated by a
|
||||
* convenience macro.
|
||||
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state.
|
||||
* @param request This and all remaining parameters should be replaced by one
|
||||
* of the convenience macros in @ref opus_genericctls,
|
||||
* @ref opus_encoderctls, or @ref opus_multistream_ctls.
|
||||
* @see opus_genericctls
|
||||
* @see opus_encoderctls
|
||||
* @see opus_multistream_ctls
|
||||
*/
|
||||
OPUS_EXPORT int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/**@}*/
|
||||
|
||||
/**\name Multistream decoder functions */
|
||||
/**@{*/
|
||||
|
||||
/** Gets the size of an <code>OpusMSDecoder</code> structure.
|
||||
* @param streams <tt>int</tt>: The total number of streams coded in the
|
||||
* input.
|
||||
* This must be no more than 255.
|
||||
* @param coupled_streams <tt>int</tt>: Number streams to decode as coupled
|
||||
* (2 channel) streams.
|
||||
* This must be no larger than the total
|
||||
* number of streams.
|
||||
* Additionally, The total number of
|
||||
* coded channels (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than 255.
|
||||
* @returns The size in bytes on success, or a negative error code
|
||||
* (see @ref opus_errorcodes) on error.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_decoder_get_size(
|
||||
int streams,
|
||||
int coupled_streams
|
||||
);
|
||||
|
||||
/** Allocates and initializes a multistream decoder state.
|
||||
* Call opus_multistream_decoder_destroy() to release
|
||||
* this object when finished.
|
||||
* @param Fs <tt>opus_int32</tt>: Sampling rate to decode at (in Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param channels <tt>int</tt>: Number of channels to output.
|
||||
* This must be at most 255.
|
||||
* It may be different from the number of coded
|
||||
* channels (<code>streams +
|
||||
* coupled_streams</code>).
|
||||
* @param streams <tt>int</tt>: The total number of streams coded in the
|
||||
* input.
|
||||
* This must be no more than 255.
|
||||
* @param coupled_streams <tt>int</tt>: Number of streams to decode as coupled
|
||||
* (2 channel) streams.
|
||||
* This must be no larger than the total
|
||||
* number of streams.
|
||||
* Additionally, The total number of
|
||||
* coded channels (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than 255.
|
||||
* @param[in] mapping <code>const unsigned char[channels]</code>: Mapping from
|
||||
* coded channels to output channels, as described in
|
||||
* @ref opus_multistream.
|
||||
* @param[out] error <tt>int *</tt>: Returns #OPUS_OK on success, or an error
|
||||
* code (see @ref opus_errorcodes) on
|
||||
* failure.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSDecoder *opus_multistream_decoder_create(
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int streams,
|
||||
int coupled_streams,
|
||||
const unsigned char *mapping,
|
||||
int *error
|
||||
) OPUS_ARG_NONNULL(5);
|
||||
|
||||
/** Intialize a previously allocated decoder state object.
|
||||
* The memory pointed to by \a st must be at least the size returned by
|
||||
* opus_multistream_encoder_get_size().
|
||||
* This is intended for applications which use their own allocator instead of
|
||||
* malloc.
|
||||
* To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
|
||||
* @see opus_multistream_decoder_create
|
||||
* @see opus_multistream_deocder_get_size
|
||||
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state to initialize.
|
||||
* @param Fs <tt>opus_int32</tt>: Sampling rate to decode at (in Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param channels <tt>int</tt>: Number of channels to output.
|
||||
* This must be at most 255.
|
||||
* It may be different from the number of coded
|
||||
* channels (<code>streams +
|
||||
* coupled_streams</code>).
|
||||
* @param streams <tt>int</tt>: The total number of streams coded in the
|
||||
* input.
|
||||
* This must be no more than 255.
|
||||
* @param coupled_streams <tt>int</tt>: Number of streams to decode as coupled
|
||||
* (2 channel) streams.
|
||||
* This must be no larger than the total
|
||||
* number of streams.
|
||||
* Additionally, The total number of
|
||||
* coded channels (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than 255.
|
||||
* @param[in] mapping <code>const unsigned char[channels]</code>: Mapping from
|
||||
* coded channels to output channels, as described in
|
||||
* @ref opus_multistream.
|
||||
* @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes)
|
||||
* on failure.
|
||||
*/
|
||||
OPUS_EXPORT int opus_multistream_decoder_init(
|
||||
OpusMSDecoder *st,
|
||||
opus_int32 Fs,
|
||||
int channels,
|
||||
int streams,
|
||||
int coupled_streams,
|
||||
const unsigned char *mapping
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6);
|
||||
|
||||
/** Decode a multistream Opus packet.
|
||||
* @param st <tt>OpusMSDecoder*</tt>: Multistream decoder state.
|
||||
* @param[in] data <tt>const unsigned char*</tt>: Input payload.
|
||||
* Use a <code>NULL</code>
|
||||
* pointer to indicate packet
|
||||
* loss.
|
||||
* @param len <tt>opus_int32</tt>: Number of bytes in payload.
|
||||
* @param[out] pcm <tt>opus_int16*</tt>: Output signal, with interleaved
|
||||
* samples.
|
||||
* This must contain room for
|
||||
* <code>frame_size*channels</code>
|
||||
* samples.
|
||||
* @param frame_size <tt>int</tt>: The number of samples per channel of
|
||||
* available space in \a pcm.
|
||||
* If this is less than the maximum packet duration
|
||||
* (120 ms; 5760 for 48kHz), this function will not be capable
|
||||
* of decoding some packets. In the case of PLC (data==NULL)
|
||||
* or FEC (decode_fec=1), then frame_size needs to be exactly
|
||||
* the duration of audio that is missing, otherwise the
|
||||
* decoder will not be in the optimal state to decode the
|
||||
* next incoming packet. For the PLC and FEC cases, frame_size
|
||||
* <b>must</b> be a multiple of 2.5 ms.
|
||||
* @param decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band
|
||||
* forward error correction data be decoded.
|
||||
* If no such data is available, the frame is
|
||||
* decoded as if it were lost.
|
||||
* @returns Number of samples decoded on success or a negative error code
|
||||
* (see @ref opus_errorcodes) on failure.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode(
|
||||
OpusMSDecoder *st,
|
||||
const unsigned char *data,
|
||||
opus_int32 len,
|
||||
opus_int16 *pcm,
|
||||
int frame_size,
|
||||
int decode_fec
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Decode a multistream Opus packet with floating point output.
|
||||
* @param st <tt>OpusMSDecoder*</tt>: Multistream decoder state.
|
||||
* @param[in] data <tt>const unsigned char*</tt>: Input payload.
|
||||
* Use a <code>NULL</code>
|
||||
* pointer to indicate packet
|
||||
* loss.
|
||||
* @param len <tt>opus_int32</tt>: Number of bytes in payload.
|
||||
* @param[out] pcm <tt>opus_int16*</tt>: Output signal, with interleaved
|
||||
* samples.
|
||||
* This must contain room for
|
||||
* <code>frame_size*channels</code>
|
||||
* samples.
|
||||
* @param frame_size <tt>int</tt>: The number of samples per channel of
|
||||
* available space in \a pcm.
|
||||
* If this is less than the maximum packet duration
|
||||
* (120 ms; 5760 for 48kHz), this function will not be capable
|
||||
* of decoding some packets. In the case of PLC (data==NULL)
|
||||
* or FEC (decode_fec=1), then frame_size needs to be exactly
|
||||
* the duration of audio that is missing, otherwise the
|
||||
* decoder will not be in the optimal state to decode the
|
||||
* next incoming packet. For the PLC and FEC cases, frame_size
|
||||
* <b>must</b> be a multiple of 2.5 ms.
|
||||
* @param decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band
|
||||
* forward error correction data be decoded.
|
||||
* If no such data is available, the frame is
|
||||
* decoded as if it were lost.
|
||||
* @returns Number of samples decoded on success or a negative error code
|
||||
* (see @ref opus_errorcodes) on failure.
|
||||
*/
|
||||
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode_float(
|
||||
OpusMSDecoder *st,
|
||||
const unsigned char *data,
|
||||
opus_int32 len,
|
||||
float *pcm,
|
||||
int frame_size,
|
||||
int decode_fec
|
||||
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
|
||||
|
||||
/** Perform a CTL function on a multistream Opus decoder.
|
||||
*
|
||||
* Generally the request and subsequent arguments are generated by a
|
||||
* convenience macro.
|
||||
* @param st <tt>OpusMSDecoder*</tt>: Multistream decoder state.
|
||||
* @param request This and all remaining parameters should be replaced by one
|
||||
* of the convenience macros in @ref opus_genericctls,
|
||||
* @ref opus_decoderctls, or @ref opus_multistream_ctls.
|
||||
* @see opus_genericctls
|
||||
* @see opus_decoderctls
|
||||
* @see opus_multistream_ctls
|
||||
*/
|
||||
OPUS_EXPORT int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...) OPUS_ARG_NONNULL(1);
|
||||
|
||||
/** Frees an <code>OpusMSDecoder</code> allocated by
|
||||
* opus_multistream_decoder_create().
|
||||
* @param st <tt>OpusMSDecoder</tt>: Multistream decoder state to be freed.
|
||||
*/
|
||||
OPUS_EXPORT void opus_multistream_decoder_destroy(OpusMSDecoder *st);
|
||||
|
||||
/**@}*/
|
||||
|
||||
/**@}*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPUS_MULTISTREAM_H */
|
||||
@@ -0,0 +1,159 @@
|
||||
/* (C) COPYRIGHT 1994-2002 Xiph.Org Foundation */
|
||||
/* Modified by Jean-Marc Valin */
|
||||
/*
|
||||
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.
|
||||
|
||||
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 OWNER
|
||||
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.
|
||||
*/
|
||||
/* opus_types.h based on ogg_types.h from libogg */
|
||||
|
||||
/**
|
||||
@file opus_types.h
|
||||
@brief Opus reference implementation types
|
||||
*/
|
||||
#ifndef OPUS_TYPES_H
|
||||
#define OPUS_TYPES_H
|
||||
|
||||
/* Use the real stdint.h if it's there (taken from Paul Hsieh's pstdint.h) */
|
||||
#if (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_)) || defined (HAVE_STDINT_H))
|
||||
#include <stdint.h>
|
||||
|
||||
typedef int16_t opus_int16;
|
||||
typedef uint16_t opus_uint16;
|
||||
typedef int32_t opus_int32;
|
||||
typedef uint32_t opus_uint32;
|
||||
#elif defined(_WIN32)
|
||||
|
||||
# if defined(__CYGWIN__)
|
||||
# include <_G_config.h>
|
||||
typedef _G_int32_t opus_int32;
|
||||
typedef _G_uint32_t opus_uint32;
|
||||
typedef _G_int16 opus_int16;
|
||||
typedef _G_uint16 opus_uint16;
|
||||
# elif defined(__MINGW32__)
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
# elif defined(__MWERKS__)
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
# else
|
||||
/* MSVC/Borland */
|
||||
typedef __int32 opus_int32;
|
||||
typedef unsigned __int32 opus_uint32;
|
||||
typedef __int16 opus_int16;
|
||||
typedef unsigned __int16 opus_uint16;
|
||||
# endif
|
||||
|
||||
#elif defined(__MACOS__)
|
||||
|
||||
# include <sys/types.h>
|
||||
typedef SInt16 opus_int16;
|
||||
typedef UInt16 opus_uint16;
|
||||
typedef SInt32 opus_int32;
|
||||
typedef UInt32 opus_uint32;
|
||||
|
||||
#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */
|
||||
|
||||
# include <sys/types.h>
|
||||
typedef int16_t opus_int16;
|
||||
typedef u_int16_t opus_uint16;
|
||||
typedef int32_t opus_int32;
|
||||
typedef u_int32_t opus_uint32;
|
||||
|
||||
#elif defined(__BEOS__)
|
||||
|
||||
/* Be */
|
||||
# include <inttypes.h>
|
||||
typedef int16 opus_int16;
|
||||
typedef u_int16 opus_uint16;
|
||||
typedef int32_t opus_int32;
|
||||
typedef u_int32_t opus_uint32;
|
||||
|
||||
#elif defined (__EMX__)
|
||||
|
||||
/* OS/2 GCC */
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
|
||||
#elif defined (DJGPP)
|
||||
|
||||
/* DJGPP */
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
|
||||
#elif defined(R5900)
|
||||
|
||||
/* PS2 EE */
|
||||
typedef int opus_int32;
|
||||
typedef unsigned opus_uint32;
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
|
||||
#elif defined(__SYMBIAN32__)
|
||||
|
||||
/* Symbian GCC */
|
||||
typedef signed short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef signed int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
|
||||
#elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X)
|
||||
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef long opus_int32;
|
||||
typedef unsigned long opus_uint32;
|
||||
|
||||
#elif defined(CONFIG_TI_C6X)
|
||||
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
|
||||
#else
|
||||
|
||||
/* Give up, take a reasonable guess */
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
|
||||
#endif
|
||||
|
||||
#define opus_int int /* used for counters etc; at least 16 bits */
|
||||
#define opus_int64 long long
|
||||
#define opus_int8 signed char
|
||||
|
||||
#define opus_uint unsigned int /* used for counters etc; at least 16 bits */
|
||||
#define opus_uint64 unsigned long long
|
||||
#define opus_uint8 unsigned char
|
||||
|
||||
#endif /* OPUS_TYPES_H */
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Submodule moonlight-common-c/moonlight-common-c updated: 679f6e83bb...718d6a4b28
@@ -9,6 +9,12 @@ QT -= core gui
|
||||
TARGET = moonlight-common-c
|
||||
TEMPLATE = lib
|
||||
|
||||
# Support debug and release builds from command line for CI
|
||||
CONFIG += debug_and_release
|
||||
|
||||
# Ensure symbols are always generated
|
||||
CONFIG += force_debug_info
|
||||
|
||||
win32 {
|
||||
INCLUDEPATH += $$PWD/../libs/windows/include
|
||||
}
|
||||
|
||||
+5
-2
@@ -1,11 +1,14 @@
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS = \
|
||||
moonlight-common-c \
|
||||
opus \
|
||||
qmdnsengine \
|
||||
app
|
||||
|
||||
CONFIG += ordered
|
||||
# Build the dependencies in parallel before the final app
|
||||
app.depends = qmdnsengine moonlight-common-c
|
||||
|
||||
# Support debug and release builds from command line for CI
|
||||
CONFIG += debug_and_release
|
||||
|
||||
# Run our compile tests
|
||||
load(configure)
|
||||
|
||||
-1
Submodule opus/opus deleted from c1c247d7e7
-269
@@ -1,269 +0,0 @@
|
||||
QT -= core gui
|
||||
|
||||
TARGET = opus
|
||||
TEMPLATE = lib
|
||||
|
||||
OPUS_DIR = $$PWD/opus
|
||||
DEFINES += \
|
||||
USE_ALLOCA=1 \
|
||||
HAVE_LRINT=1 \
|
||||
HAVE_LRINTF=1 \
|
||||
OPUS_HAVE_RTCD=1 \
|
||||
OPUS_BUILD=1
|
||||
|
||||
contains(QT_ARCH, i386) {
|
||||
CONFIG += x86
|
||||
DEFINES += \
|
||||
OPUS_X86_MAY_HAVE_SSE=1 \
|
||||
OPUS_X86_MAY_HAVE_SSE2=1 \
|
||||
OPUS_X86_MAY_HAVE_SSE4_1=1 \
|
||||
OPUS_X86_MAY_HAVE_AVX=1
|
||||
}
|
||||
|
||||
# AMD64 implies SSE2 support
|
||||
contains(QT_ARCH, x86_64) {
|
||||
CONFIG += x86
|
||||
DEFINES += \
|
||||
OPUS_X86_MAY_HAVE_SSE=1 \
|
||||
OPUS_X86_MAY_HAVE_SSE2=1 \
|
||||
OPUS_X86_MAY_HAVE_SSE4_1=1 \
|
||||
OPUS_X86_MAY_HAVE_AVX=1 \
|
||||
OPUS_X86_PRESUME_SSE=1 \
|
||||
OPUS_X86_PRESUME_SSE2=1
|
||||
}
|
||||
|
||||
contains(QT_ARCH, arm) {
|
||||
CONFIG += arm
|
||||
DEFINES += \
|
||||
OPUS_ARM_MAY_HAVE_NEON_INTR=1
|
||||
}
|
||||
|
||||
QMAKE_CFLAGS += -O2
|
||||
|
||||
x86 {
|
||||
SOURCES += \
|
||||
$$OPUS_DIR/celt/x86/x86_celt_map.c \
|
||||
$$OPUS_DIR/silk/x86/x86_silk_map.c \
|
||||
$$OPUS_DIR/celt/x86/x86cpu.c
|
||||
|
||||
SOURCES_SSE = \
|
||||
$$OPUS_DIR/celt/x86/pitch_sse.c
|
||||
|
||||
SOURCES_SSE2 = \
|
||||
$$OPUS_DIR/celt/x86/pitch_sse2.c \
|
||||
$$OPUS_DIR/celt/x86/vq_sse2.c
|
||||
|
||||
SOURCES_SSE41 = \
|
||||
$$OPUS_DIR/celt/x86/pitch_sse4_1.c \
|
||||
$$OPUS_DIR/celt/x86/celt_lpc_sse4_1.c \
|
||||
$$OPUS_DIR/silk/x86/NSQ_sse4_1.c \
|
||||
$$OPUS_DIR/silk/x86/VAD_sse4_1.c \
|
||||
$$OPUS_DIR/silk/x86/NSQ_del_dec_sse4_1.c \
|
||||
$$OPUS_DIR/silk/x86/VQ_WMat_EC_sse4_1.c
|
||||
}
|
||||
|
||||
arm {
|
||||
SOURCES += \
|
||||
$$OPUS_DIR/celt/arm/arm_celt_map.c \
|
||||
$$OPUS_DIR/celt/arm/armcpu.c
|
||||
|
||||
SOURCES_NEON = \
|
||||
$$OPUS_DIR/celt/arm/celt_neon_intr.c \
|
||||
$$OPUS_DIR/celt/arm/pitch_neon_intr.c \
|
||||
$$OPUS_DIR/silk/arm/arm_silk_map.c \
|
||||
$$OPUS_DIR/silk/arm/biquad_alt_neon_intr.c \
|
||||
$$OPUS_DIR/silk/arm/LPC_inv_pred_gain_neon_intr.c \
|
||||
$$OPUS_DIR/silk/arm/NSQ_del_dec_neon_intr.c \
|
||||
$$OPUS_DIR/silk/arm/NSQ_neon.c
|
||||
}
|
||||
|
||||
SOURCES += \
|
||||
$$OPUS_DIR/celt/quant_bands.c \
|
||||
$$OPUS_DIR/celt/laplace.c \
|
||||
$$OPUS_DIR/celt/rate.c \
|
||||
$$OPUS_DIR/celt/celt_decoder.c \
|
||||
$$OPUS_DIR/celt/celt_encoder.c \
|
||||
$$OPUS_DIR/celt/modes.c \
|
||||
$$OPUS_DIR/celt/entenc.c \
|
||||
$$OPUS_DIR/celt/entcode.c \
|
||||
$$OPUS_DIR/celt/cwrs.c \
|
||||
$$OPUS_DIR/celt/pitch.c \
|
||||
$$OPUS_DIR/celt/entdec.c \
|
||||
$$OPUS_DIR/celt/mathops.c \
|
||||
$$OPUS_DIR/celt/vq.c \
|
||||
$$OPUS_DIR/celt/bands.c \
|
||||
$$OPUS_DIR/celt/celt.c \
|
||||
$$OPUS_DIR/celt/celt_lpc.c \
|
||||
$$OPUS_DIR/celt/kiss_fft.c \
|
||||
$$OPUS_DIR/celt/mdct.c \
|
||||
$$OPUS_DIR/src/opus_decoder.c \
|
||||
$$OPUS_DIR/src/opus_compare.c \
|
||||
$$OPUS_DIR/src/opus_multistream_encoder.c \
|
||||
$$OPUS_DIR/src/opus.c \
|
||||
$$OPUS_DIR/src/mapping_matrix.c \
|
||||
$$OPUS_DIR/src/mlp.c \
|
||||
$$OPUS_DIR/src/opus_multistream_decoder.c \
|
||||
$$OPUS_DIR/src/analysis.c \
|
||||
$$OPUS_DIR/src/opus_projection_encoder.c \
|
||||
$$OPUS_DIR/src/repacketizer.c \
|
||||
$$OPUS_DIR/src/mlp_data.c \
|
||||
$$OPUS_DIR/src/opus_projection_decoder.c \
|
||||
$$OPUS_DIR/src/opus_multistream.c \
|
||||
$$OPUS_DIR/src/opus_encoder.c \
|
||||
$$OPUS_DIR/silk/NSQ_del_dec.c \
|
||||
$$OPUS_DIR/silk/ana_filt_bank_1.c \
|
||||
$$OPUS_DIR/silk/stereo_find_predictor.c \
|
||||
$$OPUS_DIR/silk/resampler.c \
|
||||
$$OPUS_DIR/silk/HP_variable_cutoff.c \
|
||||
$$OPUS_DIR/silk/shell_coder.c \
|
||||
$$OPUS_DIR/silk/inner_prod_aligned.c \
|
||||
$$OPUS_DIR/silk/tables_gain.c \
|
||||
$$OPUS_DIR/silk/bwexpander_32.c \
|
||||
$$OPUS_DIR/silk/stereo_quant_pred.c \
|
||||
$$OPUS_DIR/silk/gain_quant.c \
|
||||
$$OPUS_DIR/silk/debug.c \
|
||||
$$OPUS_DIR/silk/decoder_set_fs.c \
|
||||
$$OPUS_DIR/silk/LPC_inv_pred_gain.c \
|
||||
$$OPUS_DIR/silk/tables_pulses_per_block.c \
|
||||
$$OPUS_DIR/silk/NLSF_VQ.c \
|
||||
$$OPUS_DIR/silk/control_audio_bandwidth.c \
|
||||
$$OPUS_DIR/silk/tables_LTP.c \
|
||||
$$OPUS_DIR/silk/dec_API.c \
|
||||
$$OPUS_DIR/silk/tables_NLSF_CB_WB.c \
|
||||
$$OPUS_DIR/silk/bwexpander.c \
|
||||
$$OPUS_DIR/silk/tables_NLSF_CB_NB_MB.c \
|
||||
$$OPUS_DIR/silk/LP_variable_cutoff.c \
|
||||
$$OPUS_DIR/silk/float/LPC_inv_pred_gain_FLP.c \
|
||||
$$OPUS_DIR/silk/float/schur_FLP.c \
|
||||
$$OPUS_DIR/silk/float/wrappers_FLP.c \
|
||||
$$OPUS_DIR/silk/float/autocorrelation_FLP.c \
|
||||
$$OPUS_DIR/silk/float/regularize_correlations_FLP.c \
|
||||
$$OPUS_DIR/silk/float/warped_autocorrelation_FLP.c \
|
||||
$$OPUS_DIR/silk/float/k2a_FLP.c \
|
||||
$$OPUS_DIR/silk/float/find_LPC_FLP.c \
|
||||
$$OPUS_DIR/silk/float/noise_shape_analysis_FLP.c \
|
||||
$$OPUS_DIR/silk/float/scale_vector_FLP.c \
|
||||
$$OPUS_DIR/silk/float/apply_sine_window_FLP.c \
|
||||
$$OPUS_DIR/silk/float/bwexpander_FLP.c \
|
||||
$$OPUS_DIR/silk/float/inner_product_FLP.c \
|
||||
$$OPUS_DIR/silk/float/scale_copy_vector_FLP.c \
|
||||
$$OPUS_DIR/silk/float/energy_FLP.c \
|
||||
$$OPUS_DIR/silk/float/pitch_analysis_core_FLP.c \
|
||||
$$OPUS_DIR/silk/float/process_gains_FLP.c \
|
||||
$$OPUS_DIR/silk/float/residual_energy_FLP.c \
|
||||
$$OPUS_DIR/silk/float/find_pred_coefs_FLP.c \
|
||||
$$OPUS_DIR/silk/float/encode_frame_FLP.c \
|
||||
$$OPUS_DIR/silk/float/find_LTP_FLP.c \
|
||||
$$OPUS_DIR/silk/float/find_pitch_lags_FLP.c \
|
||||
$$OPUS_DIR/silk/float/burg_modified_FLP.c \
|
||||
$$OPUS_DIR/silk/float/corrMatrix_FLP.c \
|
||||
$$OPUS_DIR/silk/float/LTP_scale_ctrl_FLP.c \
|
||||
$$OPUS_DIR/silk/float/sort_FLP.c \
|
||||
$$OPUS_DIR/silk/float/LPC_analysis_filter_FLP.c \
|
||||
$$OPUS_DIR/silk/float/LTP_analysis_filter_FLP.c \
|
||||
$$OPUS_DIR/silk/stereo_MS_to_LR.c \
|
||||
$$OPUS_DIR/silk/log2lin.c \
|
||||
$$OPUS_DIR/silk/resampler_private_up2_HQ.c \
|
||||
$$OPUS_DIR/silk/resampler_rom.c \
|
||||
$$OPUS_DIR/silk/A2NLSF.c \
|
||||
$$OPUS_DIR/silk/NLSF_decode.c \
|
||||
$$OPUS_DIR/silk/resampler_down2.c \
|
||||
$$OPUS_DIR/silk/NLSF2A.c \
|
||||
$$OPUS_DIR/silk/table_LSF_cos.c \
|
||||
$$OPUS_DIR/silk/control_codec.c \
|
||||
$$OPUS_DIR/silk/pitch_est_tables.c \
|
||||
$$OPUS_DIR/silk/NLSF_unpack.c \
|
||||
$$OPUS_DIR/silk/resampler_private_down_FIR.c \
|
||||
$$OPUS_DIR/silk/decode_frame.c \
|
||||
$$OPUS_DIR/silk/PLC.c \
|
||||
$$OPUS_DIR/silk/check_control_input.c \
|
||||
$$OPUS_DIR/silk/decode_indices.c \
|
||||
$$OPUS_DIR/silk/encode_pulses.c \
|
||||
$$OPUS_DIR/silk/LPC_fit.c \
|
||||
$$OPUS_DIR/silk/enc_API.c \
|
||||
$$OPUS_DIR/silk/init_decoder.c \
|
||||
$$OPUS_DIR/silk/biquad_alt.c \
|
||||
$$OPUS_DIR/silk/resampler_private_IIR_FIR.c \
|
||||
$$OPUS_DIR/silk/resampler_private_AR2.c \
|
||||
$$OPUS_DIR/silk/interpolate.c \
|
||||
$$OPUS_DIR/silk/stereo_encode_pred.c \
|
||||
$$OPUS_DIR/silk/sort.c \
|
||||
$$OPUS_DIR/silk/process_NLSFs.c \
|
||||
$$OPUS_DIR/silk/tables_other.c \
|
||||
$$OPUS_DIR/silk/encode_indices.c \
|
||||
$$OPUS_DIR/silk/NLSF_encode.c \
|
||||
$$OPUS_DIR/silk/VQ_WMat_EC.c \
|
||||
$$OPUS_DIR/silk/NSQ.c \
|
||||
$$OPUS_DIR/silk/NLSF_stabilize.c \
|
||||
$$OPUS_DIR/silk/CNG.c \
|
||||
$$OPUS_DIR/silk/sum_sqr_shift.c \
|
||||
$$OPUS_DIR/silk/tables_pitch_lag.c \
|
||||
$$OPUS_DIR/silk/NLSF_del_dec_quant.c \
|
||||
$$OPUS_DIR/silk/init_encoder.c \
|
||||
$$OPUS_DIR/silk/decode_core.c \
|
||||
$$OPUS_DIR/silk/decode_pulses.c \
|
||||
$$OPUS_DIR/silk/stereo_decode_pred.c \
|
||||
$$OPUS_DIR/silk/VAD.c \
|
||||
$$OPUS_DIR/silk/decode_pitch.c \
|
||||
$$OPUS_DIR/silk/NLSF_VQ_weights_laroia.c \
|
||||
$$OPUS_DIR/silk/control_SNR.c \
|
||||
$$OPUS_DIR/silk/code_signs.c \
|
||||
$$OPUS_DIR/silk/quant_LTP_gains.c \
|
||||
$$OPUS_DIR/silk/sigm_Q15.c \
|
||||
$$OPUS_DIR/silk/lin2log.c \
|
||||
$$OPUS_DIR/silk/decode_parameters.c \
|
||||
$$OPUS_DIR/silk/resampler_down2_3.c \
|
||||
$$OPUS_DIR/silk/stereo_LR_to_MS.c \
|
||||
$$OPUS_DIR/silk/LPC_analysis_filter.c
|
||||
|
||||
INCLUDEPATH += \
|
||||
$$OPUS_DIR \
|
||||
$$OPUS_DIR/include \
|
||||
$$OPUS_DIR/src \
|
||||
$$OPUS_DIR/silk \
|
||||
$$OPUS_DIR/celt \
|
||||
$$OPUS_DIR/silk/fixed \
|
||||
$$OPUS_DIR/silk/float
|
||||
|
||||
CONFIG += warn_off staticlib
|
||||
|
||||
win32-msvc* {
|
||||
# No flags required to build with SSE or NEON on MSVC
|
||||
x86 {
|
||||
SOURCES += $$SOURCES_SSE $$SOURCES_SSE2 $$SOURCES_SSE41
|
||||
}
|
||||
arm {
|
||||
SOURCES += $$SOURCES_NEON
|
||||
}
|
||||
}
|
||||
else {
|
||||
sse.name = sse
|
||||
sse.input = SOURCES_SSE
|
||||
sse.dependency_type = TYPE_C
|
||||
sse.variable_out = OBJECTS
|
||||
sse.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_IN_BASE}$${first(QMAKE_EXT_OBJ)}
|
||||
sse.commands = $${QMAKE_CC} -c $(CFLAGS) -msse $(INCPATH) -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_IN}
|
||||
|
||||
sse2.name = sse2
|
||||
sse2.input = SOURCES_SSE2
|
||||
sse2.dependency_type = TYPE_C
|
||||
sse2.variable_out = OBJECTS
|
||||
sse2.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_IN_BASE}$${first(QMAKE_EXT_OBJ)}
|
||||
sse2.commands = $${QMAKE_CC} -c $(CFLAGS) -msse2 $(INCPATH) -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_IN}
|
||||
|
||||
sse41.name = sse41
|
||||
sse41.input = SOURCES_SSE41
|
||||
sse41.dependency_type = TYPE_C
|
||||
sse41.variable_out = OBJECTS
|
||||
sse41.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_IN_BASE}$${first(QMAKE_EXT_OBJ)}
|
||||
sse41.commands = $${QMAKE_CC} -c $(CFLAGS) -msse4.1 $(INCPATH) -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_IN}
|
||||
|
||||
neon.name = neon
|
||||
neon.input = SOURCES_NEON
|
||||
neon.dependency_type = TYPE_C
|
||||
neon.variable_out = OBJECTS
|
||||
neon.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_IN_BASE}$${first(QMAKE_EXT_OBJ)}
|
||||
neon.commands = $${QMAKE_CC} -c $(CFLAGS) $(INCPATH) -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_IN}
|
||||
|
||||
QMAKE_EXTRA_COMPILERS += sse sse2 sse41 neon
|
||||
}
|
||||
@@ -4,6 +4,12 @@ QT += network
|
||||
TARGET = qmdnsengine
|
||||
TEMPLATE = lib
|
||||
|
||||
# Support debug and release builds from command line for CI
|
||||
CONFIG += debug_and_release
|
||||
|
||||
# Ensure symbols are always generated
|
||||
CONFIG += force_debug_info
|
||||
|
||||
QMDNSE_DIR = $$PWD/qmdnsengine/src
|
||||
DEFINES += \
|
||||
QT_NO_SIGNALS_SLOTS_KEYWORDS
|
||||
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
./configure --extra-cflags="-mmacosx-version-min=10.10" --enable-pic --enable-shared --disable-static --disable-all --enable-avcodec --enable-decoder=h264 --enable-decoder=hevc --enable-hwaccel=h264_videotoolbox --enable-hwaccel=hevc_videotoolbox
|
||||
make clean
|
||||
make -j4
|
||||
@@ -0,0 +1,3 @@
|
||||
./configure --toolchain=msvc --enable-cross-compile --enable-pic --enable-shared --disable-static --disable-all --enable-avcodec --enable-decoder=h264 --enable-decoder=hevc --enable-hwaccel=h264_dxva2 --enable-hwaccel=hevc_dxva2
|
||||
make clean
|
||||
make -j4
|
||||
@@ -0,0 +1,3 @@
|
||||
./configure --toolchain=msvc --enable-pic --enable-shared --disable-static --disable-all --enable-avcodec --enable-decoder=h264 --enable-decoder=hevc --enable-hwaccel=h264_dxva2 --enable-hwaccel=hevc_dxva2
|
||||
make clean
|
||||
make -j4
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
# This script requires create-dmg to be installed from https://github.com/sindresorhus/create-dmg
|
||||
BUILD_CONFIG=$1
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1" 1>&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ "$BUILD_CONFIG" != "Debug" ] && [ "$BUILD_CONFIG" != "Release" ]; then
|
||||
fail "Invalid build configuration"
|
||||
fi
|
||||
|
||||
BUILD_ROOT=$PWD/build
|
||||
SOURCE_ROOT=$PWD
|
||||
BUILD_FOLDER=$BUILD_ROOT/build-$BUILD_CONFIG
|
||||
INSTALLER_FOLDER=$BUILD_ROOT/installer-$BUILD_CONFIG
|
||||
|
||||
echo Cleaning output directories
|
||||
rm -rf $BUILD_FOLDER
|
||||
rm -rf $INSTALLER_FOLDER
|
||||
mkdir $BUILD_ROOT
|
||||
mkdir $BUILD_FOLDER
|
||||
mkdir $INSTALLER_FOLDER
|
||||
|
||||
echo Configuring the project
|
||||
pushd $BUILD_FOLDER
|
||||
qmake $SOURCE_ROOT/moonlight-qt.pro || fail "Qmake failed!"
|
||||
popd
|
||||
|
||||
echo Compiling Moonlight in $BUILD_CONFIG configuration
|
||||
pushd $BUILD_FOLDER
|
||||
make $(echo "$BUILD_CONFIG" | tr '[:upper:]' '[:lower:]') || fail "Make failed!"
|
||||
popd
|
||||
|
||||
echo Saving dSYM file
|
||||
pushd $BUILD_FOLDER
|
||||
dsymutil app/Moonlight.app/Contents/MacOS/Moonlight -o Moonlight.dsym || fail "dSYM creation failed!"
|
||||
cp -R Moonlight.dsym $INSTALLER_FOLDER || fail "dSYM copy failed!"
|
||||
popd
|
||||
|
||||
echo Copying dylib dependencies
|
||||
mkdir $BUILD_FOLDER/app/Moonlight.app/Contents/lib
|
||||
cp $SOURCE_ROOT/libs/mac/lib/*.dylib $BUILD_FOLDER/app/Moonlight.app/Contents/lib/ || fail "Dylib copy failed!"
|
||||
|
||||
echo Copying frameworks dependencies
|
||||
mkdir $BUILD_FOLDER/app/Moonlight.app/Contents/Frameworks
|
||||
cp -R $SOURCE_ROOT/libs/mac/Frameworks/ $BUILD_FOLDER/app/Moonlight.app/Contents/Frameworks/ || fail "Framework copy failed!"
|
||||
|
||||
echo Creating app bundle
|
||||
EXTRA_ARGS=
|
||||
if [ "$BUILD_CONFIG" == "Debug" ]; then EXTRA_ARGS="$EXTRA_ARGS -use-debug-libs"; fi
|
||||
if [ "$SIGNING_KEY" != "" ]; then EXTRA_ARGS="$EXTRA_ARGS -codesign=$SIGNING_KEY"; fi
|
||||
echo Extra deployment arguments: $EXTRA_ARGS
|
||||
macdeployqt $BUILD_FOLDER/app/Moonlight.app $EXTRA_ARGS -qmldir=$SOURCE_ROOT/app/gui -appstore-compliant || fail "macdeployqt failed!"
|
||||
|
||||
echo Creating DMG
|
||||
create-dmg $BUILD_FOLDER/app/Moonlight.app $INSTALLER_FOLDER || fail "create-dmg failed!"
|
||||
+104
-14
@@ -6,21 +6,33 @@ setlocal enableDelayedExpansion
|
||||
set BUILD_CONFIG=%1
|
||||
set ARCH=%2
|
||||
|
||||
if "%BUILD_CONFIG%" NEQ "debug" (
|
||||
if "%BUILD_CONFIG%" NEQ "release" (
|
||||
echo Invalid build configuration
|
||||
goto Error
|
||||
rem Convert to lower case for windeployqt
|
||||
if /I "%BUILD_CONFIG%"=="debug" (
|
||||
set BUILD_CONFIG=debug
|
||||
) else (
|
||||
if /I "%BUILD_CONFIG%"=="release" (
|
||||
set BUILD_CONFIG=release
|
||||
) else (
|
||||
if /I "%BUILD_CONFIG%"=="signed-release" (
|
||||
set BUILD_CONFIG=release
|
||||
set SIGN=1
|
||||
set MUST_DEPLOY_SYMBOLS=1
|
||||
) else (
|
||||
echo Invalid build configuration
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if "%ARCH%" NEQ "x86" (
|
||||
if "%ARCH%" NEQ "x64" (
|
||||
if /I "%ARCH%" NEQ "x86" (
|
||||
if /I "%ARCH%" NEQ "x64" (
|
||||
echo Invalid build architecture
|
||||
goto Error
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
set VS_PATH=%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community
|
||||
set SIGNTOOL_PARAMS=sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 /sha1 1B3C676E831A94EC0327C3347EB32C68C26B3A67 /v
|
||||
|
||||
call "%VS_PATH%\VC\Auxiliary\Build\vcvarsall.bat" %ARCH%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
@@ -30,15 +42,18 @@ set SOURCE_ROOT=%cd%
|
||||
set BUILD_FOLDER=%BUILD_ROOT%\build-%ARCH%-%BUILD_CONFIG%
|
||||
set DEPLOY_FOLDER=%BUILD_ROOT%\deploy-%ARCH%-%BUILD_CONFIG%
|
||||
set INSTALLER_FOLDER=%BUILD_ROOT%\installer-%ARCH%-%BUILD_CONFIG%
|
||||
set SYMBOLS_FOLDER=%BUILD_ROOT%\symbols-%ARCH%-%BUILD_CONFIG%
|
||||
|
||||
echo Cleaning output directories
|
||||
rmdir /s /q %DEPLOY_FOLDER%
|
||||
rmdir /s /q %BUILD_FOLDER%
|
||||
rmdir /s /q %INSTALLER_FOLDER%
|
||||
rmdir /s /q %SYMBOLS_FOLDER%
|
||||
mkdir %BUILD_ROOT%
|
||||
mkdir %DEPLOY_FOLDER%
|
||||
mkdir %BUILD_FOLDER%
|
||||
mkdir %INSTALLER_FOLDER%
|
||||
mkdir %SYMBOLS_FOLDER%
|
||||
|
||||
echo Configuring the project
|
||||
pushd %BUILD_FOLDER%
|
||||
@@ -52,30 +67,105 @@ nmake %BUILD_CONFIG%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
popd
|
||||
|
||||
echo Saving PDBs
|
||||
for /r "%BUILD_FOLDER%" %%f in (*.pdb) do (
|
||||
copy "%%f" %SYMBOLS_FOLDER%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
)
|
||||
copy %SOURCE_ROOT%\libs\windows\lib\%ARCH%\*.pdb %SYMBOLS_FOLDER%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
7z a %SYMBOLS_FOLDER%\MoonlightDebuggingSymbols.zip %SYMBOLS_FOLDER%\*.pdb
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
if "%ML_SYMBOL_STORE%" NEQ "" (
|
||||
echo Publishing PDBs to symbol store: %ML_SYMBOL_STORE%
|
||||
symstore add /f %SYMBOLS_FOLDER%\*.pdb /s %ML_SYMBOL_STORE% /t Moonlight
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
) else (
|
||||
if "%MUST_DEPLOY_SYMBOLS%"=="1" (
|
||||
echo "A symbol server must be specified in ML_SYMBOL_STORE for signed release builds"
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
echo Copying DLL dependencies
|
||||
copy %SOURCE_ROOT%\libs\windows\lib\%ARCH%\*.dll %DEPLOY_FOLDER%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
echo Deploying Qt dependencies
|
||||
windeployqt.exe --dir %DEPLOY_FOLDER% --%BUILD_CONFIG% --qmldir %SOURCE_ROOT%\app\gui --no-angle --no-opengl-sw --no-compiler-runtime %BUILD_FOLDER%\app\%BUILD_CONFIG%\Moonlight.exe
|
||||
echo Copying GC mapping list
|
||||
copy %SOURCE_ROOT%\app\gamecontrollerdb.txt %DEPLOY_FOLDER%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
echo Deploying Qt dependencies
|
||||
windeployqt.exe --dir %DEPLOY_FOLDER% --%BUILD_CONFIG% --qmldir %SOURCE_ROOT%\app\gui --no-opengl-sw --no-compiler-runtime %BUILD_FOLDER%\app\%BUILD_CONFIG%\Moonlight.exe
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
echo Harvesting files for WiX
|
||||
heat dir %DEPLOY_FOLDER% -srd -sfrag -ag -sw5150 -cg MoonlightDependencies -var var.SourceDir -dr INSTALLFOLDER -out %BUILD_FOLDER%\Dependencies.wxs
|
||||
"%WIX%\bin\heat" dir %DEPLOY_FOLDER% -srd -sfrag -ag -sw5150 -cg MoonlightDependencies -var var.SourceDir -dr INSTALLFOLDER -out %BUILD_FOLDER%\Dependencies.wxs
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
echo Copying application binary to deployment directory
|
||||
copy %BUILD_FOLDER%\app\%BUILD_CONFIG%\Moonlight.exe %DEPLOY_FOLDER%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
echo Building installer
|
||||
set VCREDIST_INSTALLER=%VS_PATH%\VC\Redist\MSVC\14.14.26405\vcredist_%ARCH%.exe
|
||||
msbuild %SOURCE_ROOT%\wix\Moonlight.sln /p:Configuration=%BUILD_CONFIG% /p:Platform=%ARCH%
|
||||
if "%SIGN%"=="1" (
|
||||
echo Signing deployed binaries
|
||||
for /r "%DEPLOY_FOLDER%" %%f in (*.dll *.exe) do (
|
||||
signtool %SIGNTOOL_PARAMS% %%f
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
)
|
||||
)
|
||||
|
||||
echo Building MSI
|
||||
msbuild %SOURCE_ROOT%\wix\Moonlight\Moonlight.wixproj /p:Configuration=%BUILD_CONFIG% /p:Platform=%ARCH%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
if "%SIGN%"=="1" (
|
||||
echo Signing MSI
|
||||
signtool %SIGNTOOL_PARAMS% %BUILD_FOLDER%\Moonlight.msi
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
)
|
||||
|
||||
echo Building bundle
|
||||
set VCREDIST_INSTALLER=%VCToolsRedistDir%vcredist_%ARCH%.exe
|
||||
rem Bundles are always x86 binaries
|
||||
msbuild %SOURCE_ROOT%\wix\MoonlightSetup\MoonlightSetup.wixproj /p:Configuration=%BUILD_CONFIG% /p:Platform=x86
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
if "%SIGN%"=="1" (
|
||||
echo Signing bundle
|
||||
"%WIX%\bin\insignia" -ib %INSTALLER_FOLDER%\MoonlightSetup.exe -o %BUILD_FOLDER%\engine.exe
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
signtool %SIGNTOOL_PARAMS% %BUILD_FOLDER%\engine.exe
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
"%WIX%\bin\insignia" -ab %BUILD_FOLDER%\engine.exe %INSTALLER_FOLDER%\MoonlightSetup.exe -o %INSTALLER_FOLDER%\MoonlightSetup.exe
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
signtool %SIGNTOOL_PARAMS% %INSTALLER_FOLDER%\MoonlightSetup.exe
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
)
|
||||
|
||||
echo Building portable package
|
||||
rem This must be done after WiX harvesting and signing, since the VCRT dlls are MS signed
|
||||
rem and should not be harvested for inclusion in the full installer
|
||||
copy "%VCToolsRedistDir%%ARCH%\Microsoft.VC141.CRT\*.dll" %DEPLOY_FOLDER%
|
||||
rem This file tells Moonlight that it's a portable installation
|
||||
echo. > %DEPLOY_FOLDER%\portable.dat
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
7z a %INSTALLER_FOLDER%\MoonlightPortable.zip %DEPLOY_FOLDER%\*
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
if /i "%APPVEYOR%"=="true" (
|
||||
echo Pushing artifacts
|
||||
appveyor PushArtifact %INSTALLER_FOLDER%\MoonlightSetup.exe -FileName MoonlightSetup-%ARCH%-%BUILD_CONFIG%.exe
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
appveyor PushArtifact %INSTALLER_FOLDER%\MoonlightPortable.zip -FileName MoonlightPortable-%ARCH%-%BUILD_CONFIG%.zip
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
appveyor PushArtifact %SYMBOLS_FOLDER%\MoonlightDebuggingSymbols.zip -FileName MoonlightDebuggingSymbols-%ARCH%-%BUILD_CONFIG%.zip
|
||||
)
|
||||
|
||||
echo Build successful!
|
||||
exit /b 0
|
||||
|
||||
:Error
|
||||
echo Build failed!
|
||||
exit /b %ERRORLEVEL%
|
||||
exit /b !ERRORLEVEL!
|
||||
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
# https://gist.github.com/zlbruce/883605a635df8d5964bab11ed75e46ad
|
||||
echo "*** SVG 2 ICNS ***"
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: svg2icns filename.svg"
|
||||
exit 100
|
||||
fi
|
||||
filename="$1"
|
||||
name=${filename%.*}
|
||||
ext=${filename##*.}
|
||||
echo "processing: $name"
|
||||
dest="$name".iconset
|
||||
mkdir "$dest"
|
||||
|
||||
convert -background none -resize '!16x16' "$1" "$dest/icon_16x16.png"
|
||||
convert -background none -resize '!32x32' "$1" "$dest/[email protected]"
|
||||
cp "$dest/[email protected]" "$dest/icon_32x32.png"
|
||||
convert -background none -resize '!64x64' "$1" "$dest/[email protected]"
|
||||
convert -background none -resize '!128x128' "$1" "$dest/icon_128x128.png"
|
||||
convert -background none -resize '!256x256' "$1" "$dest/[email protected]"
|
||||
cp "$dest/[email protected]" "$dest/icon_256x256.png"
|
||||
convert -background none -resize '!512x512' "$1" "$dest/[email protected]"
|
||||
cp "$dest/[email protected]" "$dest/icon_512x512.png"
|
||||
convert -background none -resize '!1024x1024' "$1" "$dest/[email protected]"
|
||||
|
||||
iconutil -c icns "$dest"
|
||||
rm -R "$dest"
|
||||
@@ -6,6 +6,7 @@
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<OutputName>Moonlight</OutputName>
|
||||
<OutputType>Package</OutputType>
|
||||
<DefineSolutionProperties>false</DefineSolutionProperties>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DefineConstants>Debug</DefineConstants>
|
||||
@@ -26,6 +27,10 @@
|
||||
<HintPath>$(WixExtDir)\WixFirewallExtension.dll</HintPath>
|
||||
<Name>WixFirewallExtension</Name>
|
||||
</WixExtension>
|
||||
<WixExtension Include="WixUtilExtension">
|
||||
<HintPath>$(WixExtDir)\WixUtilExtension.dll</HintPath>
|
||||
<Name>WixUtilExtension</Name>
|
||||
</WixExtension>
|
||||
</ItemGroup>
|
||||
<Import Project="$(WixTargetsPath)" Condition=" '$(WixTargetsPath)' != '' " />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets" Condition=" '$(WixTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets') " />
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
<?define FullName = "Moonlight Game Streaming Client" ?>
|
||||
|
||||
<?define ShortcutName = "$(var.ShortName)" ?>
|
||||
<?define ShortcutDesc = "Stream games from a NVIDIA GameStream-compatible PC" ?>
|
||||
<?define ShortcutDesc = "Stream games from your NVIDIA GameStream-enabled PC" ?>
|
||||
<?define InstallFolder = "Moonlight Game Streaming" ?>
|
||||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
|
||||
xmlns:fire="http://schemas.microsoft.com/wix/FirewallExtension">
|
||||
xmlns:fire="http://schemas.microsoft.com/wix/FirewallExtension"
|
||||
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
|
||||
|
||||
<Product Id="*"
|
||||
Name="$(var.FullName)"
|
||||
@@ -19,8 +20,8 @@
|
||||
|
||||
<Package InstallerVersion="500" Compressed="yes" InstallScope="perMachine" />
|
||||
|
||||
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
|
||||
<MediaTemplate CompressionLevel="high" />
|
||||
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." Schedule="afterInstallInitialize" />
|
||||
<MediaTemplate CompressionLevel="high" EmbedCab="yes" />
|
||||
|
||||
<?if $(var.Platform) = x64 ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
@@ -38,6 +39,8 @@
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<Property Id="APPDATAFOLDER">%LOCALAPPDATA%\Moonlight Game Streaming Project</Property>
|
||||
|
||||
<!-- There's no way to delete a registry key on uninstall but not major upgrade, so
|
||||
we have to roll our own deletion via custom action -->
|
||||
<CustomAction Id="DeleteRegistryKey"
|
||||
@@ -57,15 +60,38 @@
|
||||
Target="[#MoonlightExe]"
|
||||
Directory="ApplicationProgramsFolder"
|
||||
WorkingDirectory="INSTALLFOLDER" />
|
||||
<RemoveFolder Id="CleanupStartMenuShortcut" Directory="ApplicationProgramsFolder" On="uninstall" />
|
||||
<util:RemoveFolderEx Id="CleanupAppDataFolder" On="uninstall" Property="APPDATAFOLDER" />
|
||||
<RegistryValue Root="HKCU" Key="Software\Moonlight Game Streaming Project" Name="Installed" Type="integer" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<Component Id="MoonlightDesktopShortcut" Guid="*" Directory="INSTALLFOLDER">
|
||||
<Shortcut Id="DesktopShortcut"
|
||||
Name="$(var.ShortcutName)"
|
||||
Description="$(var.ShortcutDesc)"
|
||||
Target="[#MoonlightExe]"
|
||||
Directory="DesktopFolder"
|
||||
WorkingDirectory="INSTALLFOLDER" />
|
||||
<RemoveFolder Id="CleanupStartMenuShortcut" Directory="ApplicationProgramsFolder" On="uninstall" />
|
||||
<RemoveFolder Id="CleanupDesktopShortcut" Directory="DesktopFolder" On="uninstall" />
|
||||
<RegistryValue Root="HKCU" Key="Software\Moonlight Game Streaming Project" Name="Installed" Type="integer" Value="1" KeyPath="yes" />
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\Moonlight Game Streaming Project"
|
||||
Name="DesktopShortcutInstalled"
|
||||
Type="integer"
|
||||
Value="1"
|
||||
KeyPath="yes" />
|
||||
<Condition>ADDDESKTOPSHORTCUT=1</Condition>
|
||||
</Component>
|
||||
|
||||
<!-- Persist desktop shortcut's installed state to let Bundle.wxs know if
|
||||
the desktop shortcut should installed by default when upgrading the
|
||||
product -->
|
||||
<Component Id="MoonlightDesktopShortcutState" Guid="*" Directory="INSTALLFOLDER">
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\Moonlight Game Streaming Project"
|
||||
Name="DesktopShortcutInstallState"
|
||||
Type="integer"
|
||||
Value="[ADDDESKTOPSHORTCUT]"
|
||||
KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<DirectoryRef Id="INSTALLFOLDER">
|
||||
@@ -81,6 +107,8 @@
|
||||
<Feature Id="ProductFeature" Title="Moonlight" Level="1" ConfigurableDirectory="INSTALLFOLDER">
|
||||
<ComponentRef Id="Moonlight" />
|
||||
<ComponentRef Id="MoonlightShortcuts" />
|
||||
<ComponentRef Id="MoonlightDesktopShortcutState" />
|
||||
<ComponentRef Id="MoonlightDesktopShortcut" />
|
||||
<ComponentGroupRef Id="MoonlightDependencies" />
|
||||
</Feature>
|
||||
</Product>
|
||||
|
||||
@@ -33,24 +33,50 @@
|
||||
<?endif ?>
|
||||
|
||||
<?if $(env.ARCH) ~= x64 ?>
|
||||
<bal:Condition Message="This Moonlight installer requires a 64-bit OS.">
|
||||
<bal:Condition Message="This Moonlight installer requires a 64-bit (x64) OS. Please download the 32-bit (x86) installer.">
|
||||
VersionNT64
|
||||
</bal:Condition>
|
||||
<?elseif $(env.ARCH) ~= x86 ?>
|
||||
<bal:Condition Message="This Moonlight installer requires a 32-bit (x86) OS. Please download the 64-bit (x64) installer.">
|
||||
NOT VersionNT64
|
||||
</bal:Condition>
|
||||
<?endif ?>
|
||||
|
||||
<!-- HACK: Force replacement of lower versioned files too. This is only safe because we don't install any shared files -->
|
||||
<Variable Name="REINSTALLMODE" bal:Overridable="yes" Value="dmus" />
|
||||
|
||||
<?if $(env.ARCH) ~= x64 ?>
|
||||
<Variable Name="InstallFolder" Type="string" Value="[ProgramFiles64Folder]Moonlight Game Streaming" />
|
||||
<?elseif $(env.ARCH) ~= x86 ?>
|
||||
<Variable Name="InstallFolder" Type="string" Value="[ProgramFilesFolder]Moonlight Game Streaming" />
|
||||
<?endif ?>
|
||||
|
||||
<!-- Define "Add desktop shortcut" -checkbox's state by defining a variable
|
||||
which has same name as the checkbox has. Value 1 means that checkbox
|
||||
is checked, 0 means that is unchecked-->
|
||||
<!-- Set checkbox's state as checked by default -->
|
||||
<Variable Name="AddDesktopShortcutCheckbox" Type="numeric" Value="1" />
|
||||
<!-- Get checkbox's state from registry if present. The registry value
|
||||
"DesktopShortcutInstallState" is set in Product.wxs. -->
|
||||
<util:RegistrySearch Variable="HasDesktopShortcutInstallStateRegKey"
|
||||
Root="HKCU"
|
||||
Key="Software\Moonlight Game Streaming Project"
|
||||
Value="DesktopShortcutInstallState"
|
||||
Result="exists" />
|
||||
<util:RegistrySearch Variable="AddDesktopShortcutCheckbox"
|
||||
Root="HKCU"
|
||||
Key="Software\Moonlight Game Streaming Project"
|
||||
Value="DesktopShortcutInstallState"
|
||||
Condition="HasDesktopShortcutInstallStateRegKey" />
|
||||
|
||||
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense">
|
||||
<bal:WixStandardBootstrapperApplication
|
||||
ShowVersion="yes"
|
||||
LicenseFile="license.rtf"
|
||||
LogoFile="..\..\app\moonlight_wix.png"
|
||||
ShowFilesInUse="yes"
|
||||
LaunchTarget="[InstallFolder]\Moonlight.exe" />
|
||||
LaunchTarget="[InstallFolder]\Moonlight.exe"
|
||||
ThemeFile="RtfTheme.xml" />
|
||||
</BootstrapperApplicationRef>
|
||||
|
||||
<Chain>
|
||||
@@ -67,6 +93,8 @@
|
||||
</ExePackage>
|
||||
<MsiPackage Id="Moonlight" SourceFile="$(var.Moonlight.TargetPath)" Vital="yes">
|
||||
<MsiProperty Name="INSTALLFOLDER" Value="[InstallFolder]" />
|
||||
<MsiProperty Name="REINSTALLMODE" Value="[REINSTALLMODE]" />
|
||||
<MsiProperty Name="ADDDESKTOPSHORTCUT" Value="[AddDesktopShortcutCheckbox]" />
|
||||
</MsiPackage>
|
||||
</Chain>
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<OutputName>MoonlightSetup</OutputName>
|
||||
<OutputType>Bundle</OutputType>
|
||||
<DefineSolutionProperties>false</DefineSolutionProperties>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DefineConstants>Debug</DefineConstants>
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
|
||||
<!-- This file has been copied from C:\Program Files (x86)\WiX Toolset v3.11\SDK\themes\RtfTheme.xml and then altered. -->
|
||||
|
||||
<Theme xmlns="http://wixtoolset.org/schemas/thmutil/2010">
|
||||
<Window Width="485" Height="300" HexStyle="100a0000" FontId="0">#(loc.Caption)</Window>
|
||||
<Font Id="0" Height="-12" Weight="500" Foreground="000000" Background="FFFFFF">Segoe UI</Font>
|
||||
<Font Id="1" Height="-24" Weight="500" Foreground="000000">Segoe UI</Font>
|
||||
<Font Id="2" Height="-22" Weight="500" Foreground="666666">Segoe UI</Font>
|
||||
<Font Id="3" Height="-12" Weight="500" Foreground="000000" Background="FFFFFF">Segoe UI</Font>
|
||||
<Font Id="4" Height="-12" Weight="500" Foreground="ff0000" Background="FFFFFF" Underline="yes">Segoe UI</Font>
|
||||
|
||||
<Image X="11" Y="11" Width="64" Height="64" ImageFile="logo.png" Visible="yes"/>
|
||||
<Text X="80" Y="11" Width="-11" Height="64" FontId="1" Visible="yes" DisablePrefix="yes">#(loc.Title)</Text>
|
||||
|
||||
<Page Name="Help">
|
||||
<Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.HelpHeader)</Text>
|
||||
<Text X="11" Y="112" Width="-11" Height="-35" FontId="3" DisablePrefix="yes">#(loc.HelpText)</Text>
|
||||
<Button Name="HelpCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.HelpCloseButton)</Button>
|
||||
</Page>
|
||||
<Page Name="Install">
|
||||
<Richedit Name="EulaRichedit" X="11" Y="80" Width="-11" Height="-70" TabStop="yes" FontId="0" HexStyle="0x800000" />
|
||||
<Checkbox Name="EulaAcceptCheckbox" X="-11" Y="-41" Width="260" Height="17" TabStop="yes" FontId="3" HideWhenDisabled="yes">#(loc.InstallAcceptCheckbox)</Checkbox>
|
||||
<Button Name="OptionsButton" X="-171" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.InstallOptionsButton)</Button>
|
||||
<Button Name="InstallButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.InstallInstallButton)</Button>
|
||||
<Button Name="WelcomeCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.InstallCloseButton)</Button>
|
||||
</Page>
|
||||
<Page Name="Options">
|
||||
<Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.OptionsHeader)</Text>
|
||||
<Text X="11" Y="121" Width="-11" Height="17" FontId="3" DisablePrefix="yes">#(loc.OptionsLocationLabel)</Text>
|
||||
<Editbox Name="FolderEditbox" X="11" Y="143" Width="-91" Height="21" TabStop="yes" FontId="3" FileSystemAutoComplete="yes" />
|
||||
<Button Name="BrowseButton" X="-11" Y="142" Width="75" Height="23" TabStop="yes" FontId="3">#(loc.OptionsBrowseButton)</Button>
|
||||
|
||||
<!-- Added a checkbox for user to choose if shortcut is placed on
|
||||
desktop or not. Checkbox's current state (checked/unchecked) is
|
||||
read from a variable which has same name as this checkbox. The
|
||||
variable is defined in Bundle.wxs. -->
|
||||
<Checkbox Name="AddDesktopShortcutCheckbox" X="11" Y="178" Width="250" Height="17" TabStop="yes" FontId="3" HideWhenDisabled="yes">Add desktop shortcut</Checkbox>
|
||||
|
||||
<Button Name="OptionsOkButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.OptionsOkButton)</Button>
|
||||
<Button Name="OptionsCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.OptionsCancelButton)</Button>
|
||||
</Page>
|
||||
<Page Name="FilesInUse">
|
||||
<Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.FilesInUseHeader)</Text>
|
||||
<Text X="11" Y="121" Width="-11" Height="34" FontId="3" DisablePrefix="yes">#(loc.FilesInUseLabel)</Text>
|
||||
<Text Name="FilesInUseText" X="11" Y="150" Width="-11" Height="-86" FontId="3" DisablePrefix="yes" HexStyle="0x0000C000"></Text>
|
||||
|
||||
<Button Name="FilesInUseCloseRadioButton" X="11" Y="-60" Width="-11" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes" HexStyle="0x000009">#(loc.FilesInUseCloseRadioButton)</Button>
|
||||
<Button Name="FilesInUseDontCloseRadioButton" X="11" Y="-40" Width="-11" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes" HexStyle="0x000009">#(loc.FilesInUseDontCloseRadioButton)</Button>
|
||||
|
||||
<Button Name="FilesInUseOkButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.FilesInUseOkButton)</Button>
|
||||
<Button Name="FilesInUseCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.FilesInUseCancelButton)</Button>
|
||||
</Page>
|
||||
<Page Name="Progress">
|
||||
<Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.ProgressHeader)</Text>
|
||||
<Text X="11" Y="121" Width="70" Height="17" FontId="3" DisablePrefix="yes">#(loc.ProgressLabel)</Text>
|
||||
<Text Name="OverallProgressPackageText" X="85" Y="121" Width="-11" Height="17" FontId="3" DisablePrefix="yes">#(loc.OverallProgressPackageText)</Text>
|
||||
<Progressbar Name="OverallCalculatedProgressbar" X="11" Y="143" Width="-11" Height="15" />
|
||||
<Button Name="ProgressCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.ProgressCancelButton)</Button>
|
||||
</Page>
|
||||
<Page Name="Modify">
|
||||
<Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.ModifyHeader)</Text>
|
||||
<Button Name="RepairButton" X="-171" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.ModifyRepairButton)</Button>
|
||||
<Button Name="UninstallButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.ModifyUninstallButton)</Button>
|
||||
<Button Name="ModifyCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.ModifyCloseButton)</Button>
|
||||
</Page>
|
||||
<Page Name="Success">
|
||||
<Text Name="SuccessHeader" X="11" Y="80" Width="-11" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.SuccessHeader)</Text>
|
||||
<Text Name="SuccessInstallHeader" X="11" Y="80" Width="-11" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.SuccessInstallHeader)</Text>
|
||||
<Text Name="SuccessRepairHeader" X="11" Y="80" Width="-11" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.SuccessRepairHeader)</Text>
|
||||
<Text Name="SuccessUninstallHeader" X="11" Y="80" Width="-11" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.SuccessUninstallHeader)</Text>
|
||||
<Button Name="LaunchButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.SuccessLaunchButton)</Button>
|
||||
<Text Name="SuccessRestartText" X="-11" Y="-51" Width="400" Height="34" FontId="3" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.SuccessRestartText)</Text>
|
||||
<Button Name="SuccessRestartButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.SuccessRestartButton)</Button>
|
||||
<Button Name="SuccessCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.SuccessCloseButton)</Button>
|
||||
</Page>
|
||||
<Page Name="Failure">
|
||||
<Text Name="FailureHeader" X="11" Y="80" Width="-11" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.FailureHeader)</Text>
|
||||
<Text Name="FailureInstallHeader" X="11" Y="80" Width="-11" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.FailureInstallHeader)</Text>
|
||||
<Text Name="FailureUninstallHeader" X="11" Y="80" Width="-11" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.FailureUninstallHeader)</Text>
|
||||
<Text Name="FailureRepairHeader" X="11" Y="80" Width="-11" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.FailureRepairHeader)</Text>
|
||||
<Hypertext Name="FailureLogFileLink" X="11" Y="121" Width="-11" Height="42" FontId="3" TabStop="yes" HideWhenDisabled="yes">#(loc.FailureHyperlinkLogText)</Hypertext>
|
||||
<Hypertext Name="FailureMessageText" X="22" Y="163" Width="-11" Height="51" FontId="3" TabStop="yes" HideWhenDisabled="yes" />
|
||||
<Text Name="FailureRestartText" X="-11" Y="-51" Width="400" Height="34" FontId="3" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.FailureRestartText)</Text>
|
||||
<Button Name="FailureRestartButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.FailureRestartButton)</Button>
|
||||
<Button Name="FailureCloseButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.FailureCloseButton)</Button>
|
||||
</Page>
|
||||
</Theme>
|
||||
Reference in New Issue
Block a user