Compare commits
162 Commits
v4.5.10
...
frame-paci
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91261f9520 | ||
|
|
a96f688bb2 | ||
|
|
b287606106 | ||
|
|
d06912e81a | ||
|
|
08bcd97594 | ||
|
|
49e51f5f6f | ||
|
|
4223a7fd30 | ||
|
|
6edd0ab540 | ||
|
|
ce7146175a | ||
|
|
3176a85f35 | ||
|
|
ad1c11bba5 | ||
|
|
ac640a6842 | ||
|
|
8962497a8c | ||
|
|
83141d3f91 | ||
|
|
55f2e89bbe | ||
|
|
3558655b72 | ||
|
|
44cbf8adc1 | ||
|
|
686490ba70 | ||
|
|
d0ecde1e16 | ||
|
|
9417908848 | ||
|
|
93b0073467 | ||
|
|
1434be262c | ||
|
|
75aabd6471 | ||
|
|
bafa2addd3 | ||
|
|
32b787e77c | ||
|
|
43b58b7a5e | ||
|
|
9ae1fe2696 | ||
|
|
6d0f34e2c4 | ||
|
|
f7d91b5107 | ||
|
|
a3c95480d8 | ||
|
|
732311c2a4 | ||
|
|
043c9a978e | ||
|
|
36b248be4b | ||
|
|
8e247ad9a6 | ||
|
|
a2de98c91a | ||
|
|
81d1e615bf | ||
|
|
244fae07ab | ||
|
|
e7d96f0ac2 | ||
|
|
4555b3c74c | ||
|
|
8c13186757 | ||
|
|
feafc4ef3c | ||
|
|
5c03295478 | ||
|
|
dc3a923041 | ||
|
|
eccba807bc | ||
|
|
35fa8f5bcc | ||
|
|
0380910588 | ||
|
|
e85bb4372e | ||
|
|
2c345cd6c2 | ||
|
|
b5c96cbb53 | ||
|
|
b21ee5ca31 | ||
|
|
9c7bff6c75 | ||
|
|
3d470d9aed | ||
|
|
b2a36c2c73 | ||
|
|
7978687bfc | ||
|
|
f612ec80e2 | ||
|
|
7df1a39fcb | ||
|
|
a539ac62ec | ||
|
|
fa52e5edc2 | ||
|
|
3ca681f050 | ||
|
|
8086c3d46b | ||
|
|
928fca843f | ||
|
|
25d74785d0 | ||
|
|
e12a8e7946 | ||
|
|
b14f2ce219 | ||
|
|
d31be3d64e | ||
|
|
0704f2aaf6 | ||
|
|
832e52ac74 | ||
|
|
f5444551b2 | ||
|
|
3143797b55 | ||
|
|
cc9b1aeaab | ||
|
|
3d177e97e4 | ||
|
|
6c3aaedc83 | ||
|
|
bf84ebef6d | ||
|
|
8991b29329 | ||
|
|
fa84575be5 | ||
|
|
0432d5725b | ||
|
|
8e7b144339 | ||
|
|
fc629db653 | ||
|
|
d5863e1bef | ||
|
|
c2c3a6b37c | ||
|
|
e701699dea | ||
|
|
17179bd027 | ||
|
|
b2f210700d | ||
|
|
f0e85c4c53 | ||
|
|
92f8425ace | ||
|
|
6ad001e8be | ||
|
|
b6e4d5528b | ||
|
|
0f0b83badc | ||
|
|
453fbb5f58 | ||
|
|
e7dc3a4c11 | ||
|
|
d68b2382cf | ||
|
|
1b5330323c | ||
|
|
8aba4888e1 | ||
|
|
1c3b9a3859 | ||
|
|
e8f04f5a3b | ||
|
|
56b814e877 | ||
|
|
628ccd39d6 | ||
|
|
59db3f9b62 | ||
|
|
416f922b56 | ||
|
|
b52a86e6cc | ||
|
|
e523b5069e | ||
|
|
e8ae8d9807 | ||
|
|
64e56a861d | ||
|
|
c1bcd09c9b | ||
|
|
574258804f | ||
|
|
21ea3d8a2b | ||
|
|
6de4288a85 | ||
|
|
a107b5e652 | ||
|
|
b02db2c182 | ||
|
|
f8a04cda7a | ||
|
|
226e8edefc | ||
|
|
9b90b30a1f | ||
|
|
2ed245b25a | ||
|
|
4b769839d0 | ||
|
|
239dd1d5a1 | ||
|
|
37509cce9b | ||
|
|
227c71549b | ||
|
|
a10d8334f3 | ||
|
|
f88c9904fb | ||
|
|
0fc61e52dd | ||
|
|
5e44c33bb6 | ||
|
|
df3655e958 | ||
|
|
fe43e13145 | ||
|
|
acd3aad8d9 | ||
|
|
811b4b4f22 | ||
|
|
7db3b9f401 | ||
|
|
a5a099cf43 | ||
|
|
ba605643bb | ||
|
|
96e98c1abb | ||
|
|
5de6f6ae2b | ||
|
|
0685722773 | ||
|
|
29df3b2859 | ||
|
|
fc6f859ced | ||
|
|
6b21a5416f | ||
|
|
74e7c8bbf1 | ||
|
|
757075b16a | ||
|
|
e8903c4d48 | ||
|
|
98262d16ee | ||
|
|
339506cf10 | ||
|
|
63bd5df09b | ||
|
|
32af2d0831 | ||
|
|
242b03d4b5 | ||
|
|
87a62666ac | ||
|
|
2dcf5486da | ||
|
|
60d3d8b3ae | ||
|
|
e9141d65fe | ||
|
|
aae591daec | ||
|
|
a5ca8a7472 | ||
|
|
36f8cc02cb | ||
|
|
55b9645651 | ||
|
|
d30ecbed5b | ||
|
|
0bbd27f04c | ||
|
|
3c53fb7403 | ||
|
|
7a81950819 | ||
|
|
74f212c702 | ||
|
|
36be943854 | ||
|
|
26a4fc75a5 | ||
|
|
a5ec5fc265 | ||
|
|
541ac44be4 | ||
|
|
117b555fcd | ||
|
|
a10cd04441 | ||
|
|
53dccbde2a |
4
.gitignore
vendored
@@ -30,6 +30,8 @@ Thumbs.db
|
||||
#.idea/workspace.xml - remove # and delete .idea if it better suit your needs.
|
||||
.gradle
|
||||
build/
|
||||
app/app.iml
|
||||
|
||||
# Compiled JNI libraries folder
|
||||
**/jniLibs
|
||||
**/jniLibs
|
||||
app/.externalNativeBuild/
|
||||
|
||||
6
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "app/src/main/jni/jnienet/enet"]
|
||||
path = app/src/main/jni/jnienet/enet
|
||||
url = https://github.com/cgutman/enet.git
|
||||
[submodule "moonlight-common"]
|
||||
path = moonlight-common
|
||||
url = https://github.com/moonlight-stream/moonlight-common.git
|
||||
|
||||
@@ -20,7 +20,7 @@ function p_h264raw.dissector(buf, pkt, root)
|
||||
|
||||
local i = 0
|
||||
local data_start = -1
|
||||
while i < buf:len do
|
||||
while i < buf:len() do
|
||||
-- Make sure we have a potential start sequence and type
|
||||
if buf:len() - i < 5 then
|
||||
-- We need more data
|
||||
|
||||
@@ -8,7 +8,7 @@ whether in your own home or over the internet.
|
||||
|
||||
[Moonlight-pc](https://github.com/moonlight-stream/moonlight-pc) is also currently in development for Windows, OS X and Linux. Versions for [iOS](https://github.com/moonlight-stream/moonlight-ios) and [Windows and Windows Phone](https://github.com/moonlight-stream/moonlight-windows) are also in development.
|
||||
|
||||
Check our [wiki](https://github.com/moonlight-stream/moonlight-android/wiki) for more detailed information or a troubleshooting guide.
|
||||
Check our [wiki](https://github.com/moonlight-stream/moonlight-docs/wiki) for more detailed information or a troubleshooting guide.
|
||||
|
||||
##Features
|
||||
|
||||
@@ -46,6 +46,12 @@ This project is being actively developed at [XDA Developers](http://forum.xda-de
|
||||
2. Write code
|
||||
3. Send Pull Requests
|
||||
|
||||
##Building
|
||||
* Install Android Studio and the Android NDK
|
||||
* Run ‘git submodule update --init --recursive’ from within moonlight-android/
|
||||
* In moonlight-android/, create a file called ‘local.properties’. Add an ‘ndk.dir=’ property to the local.properties file and set it equal to your NDK directory.
|
||||
* Build the APK using Android Studio
|
||||
|
||||
##Authors
|
||||
|
||||
* [Cameron Gutman](https://github.com/cgutman)
|
||||
|
||||
120
app/app.iml
@@ -1,120 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="moonlight-android" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="android-gradle" name="Android-Gradle">
|
||||
<configuration>
|
||||
<option name="GRADLE_PROJECT_PATH" value=":app" />
|
||||
</configuration>
|
||||
</facet>
|
||||
<facet type="android" name="Android">
|
||||
<configuration>
|
||||
<option name="SELECTED_BUILD_VARIANT" value="nonRootDebug" />
|
||||
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
|
||||
<option name="ASSEMBLE_TASK_NAME" value="assembleNonRootDebug" />
|
||||
<option name="COMPILE_JAVA_TASK_NAME" value="compileNonRootDebugSources" />
|
||||
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleNonRootDebugAndroidTest" />
|
||||
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileNonRootDebugAndroidTestSources" />
|
||||
<afterSyncTasks>
|
||||
<task>generateNonRootDebugAndroidTestSources</task>
|
||||
<task>generateNonRootDebugSources</task>
|
||||
</afterSyncTasks>
|
||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
||||
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
|
||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
|
||||
<output url="file://$MODULE_DIR$/build/intermediates/classes/nonRoot/debug" />
|
||||
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/nonRoot/debug" />
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/nonRoot/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/nonRoot/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/nonRoot/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/nonRoot/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/nonRoot/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/nonRoot/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/jni" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/nonRoot/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/nonRoot/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/nonRoot/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/nonRoot/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/nonRoot/debug" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/nonRoot/debug" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/jni" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/jni" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" exported="" name="bcprov-jdk15on-1.52" level="project" />
|
||||
<orderEntry type="library" exported="" name="bcpkix-jdk15on-1.52" level="project" />
|
||||
<orderEntry type="library" exported="" name="tinyrtsp" level="project" />
|
||||
<orderEntry type="library" exported="" name="limelight-common" level="project" />
|
||||
<orderEntry type="library" exported="" name="jmdns-3.4.2" level="project" />
|
||||
<orderEntry type="library" exported="" name="okhttp-2.4.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="jcodec-0.1.9-patched" level="project" />
|
||||
<orderEntry type="library" exported="" name="okio-1.5.0" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -4,24 +4,30 @@ import org.apache.tools.ant.taskdefs.condition.Os
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.3"
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion '25.0.3'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 23
|
||||
targetSdkVersion 25
|
||||
|
||||
versionName "4.5.10"
|
||||
versionCode = 101
|
||||
versionName "5.0.1"
|
||||
versionCode = 123
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
root {
|
||||
applicationId "com.limelight.root"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64"
|
||||
}
|
||||
}
|
||||
|
||||
nonRoot {
|
||||
applicationId "com.limelight"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,55 +38,20 @@ android {
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt')
|
||||
}
|
||||
}
|
||||
|
||||
// These lines are required to avoid dexing issues with the BouncyCastle library
|
||||
// bundled with limelight-common.jar
|
||||
packagingOptions {
|
||||
exclude 'META-INF/BCKEY.SF'
|
||||
exclude 'META-INF/BCKEY.DSA'
|
||||
}
|
||||
|
||||
sourceSets.main.jni.srcDirs = []
|
||||
|
||||
//noinspection GroovyAssignabilityCheck,GroovyAssignabilityCheck
|
||||
task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {
|
||||
Properties properties = new Properties()
|
||||
properties.load(project.rootProject.file('local.properties').newDataInputStream())
|
||||
def ndkDir = properties.getProperty('ndk.dir')
|
||||
|
||||
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
commandLine "$ndkDir\\ndk-build.cmd",
|
||||
'NDK_PROJECT_PATH=build/intermediates/ndk',
|
||||
'NDK_LIBS_OUT=src/main/jniLibs',
|
||||
'APP_BUILD_SCRIPT=src/main/jni/Android.mk',
|
||||
'NDK_APPLICATION_MK=src/main/jni/Application.mk'
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
path "src/main/jni/Android.mk"
|
||||
}
|
||||
else {
|
||||
commandLine "$ndkDir/ndk-build",
|
||||
'NDK_PROJECT_PATH=build/intermediates/ndk',
|
||||
'NDK_LIBS_OUT=src/main/jniLibs',
|
||||
'APP_BUILD_SCRIPT=src/main/jni/Android.mk',
|
||||
'NDK_APPLICATION_MK=src/main/jni/Application.mk'
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
compileTask -> compileTask.dependsOn ndkBuild
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.52'
|
||||
compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.52'
|
||||
|
||||
compile group: 'com.squareup.okhttp', name: 'okhttp', version:'2.4.0'
|
||||
compile group: 'com.squareup.okio', name:'okio', version:'1.5.0'
|
||||
|
||||
compile files('libs/jmdns-3.4.2.jar')
|
||||
compile files('libs/limelight-common.jar')
|
||||
compile files('libs/tinyrtsp.jar')
|
||||
compile 'org.bouncycastle:bcprov-jdk15on:1.52'
|
||||
compile 'org.bouncycastle:bcpkix-jdk15on:1.52'
|
||||
compile project(':moonlight-common')
|
||||
compile files('libs/jcodec-0.1.9-patched.jar')
|
||||
}
|
||||
|
||||
@@ -7,60 +7,97 @@
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.wifi" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.wifi"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.gamepad"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.usb.host"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:isGame="true"
|
||||
android:banner="@drawable/atv_banner"
|
||||
android:theme="@style/AppTheme" >
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<!-- Samsung multi-window support -->
|
||||
<uses-library android:name="com.sec.android.app.multiwindow" android:required="false" />
|
||||
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
||||
|
||||
<uses-library
|
||||
android:name="com.sec.android.app.multiwindow"
|
||||
android:required="false" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.sec.android.support.multiwindow"
|
||||
android:value="true" />
|
||||
|
||||
<!-- Samsung DeX support requires explicit placement of android:resizeableActivity="true"
|
||||
in each activity even though it is implied by targeting API 24+ -->
|
||||
|
||||
<activity
|
||||
android:name=".PcView"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
<category android:name="tv.ouya.intent.category.APP" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Small hack to support launcher shortcuts without relaunching over and over again when the back button is pressed -->
|
||||
<activity
|
||||
android:name=".AppViewShortcutTrampoline"
|
||||
android:noHistory="true"
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.limelight.PcView" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".AppView"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|fontScale|uiMode" >
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.limelight.PcView" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".preferences.StreamSettings"
|
||||
android:label="Streaming Settings" >
|
||||
android:resizeableActivity="true"
|
||||
android:label="Streaming Settings">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.limelight.PcView" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".preferences.AddComputerManually"
|
||||
android:label="Add Computer Manually" >
|
||||
android:resizeableActivity="true"
|
||||
android:label="Add Computer Manually">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.limelight.PcView" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".Game"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:theme="@style/StreamTheme"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection" >
|
||||
android:noHistory="true"
|
||||
android:resizeableActivity="true"
|
||||
android:theme="@style/StreamTheme">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.limelight.AppView" />
|
||||
@@ -75,6 +112,15 @@
|
||||
<service
|
||||
android:name=".binding.input.driver.UsbDriverService"
|
||||
android:label="Usb Driver Service" />
|
||||
|
||||
<activity
|
||||
android:name=".HelpActivity"
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.limelight.PcView" />
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
@@ -11,12 +11,14 @@ import com.limelight.grid.AppGridAdapter;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.NvApp;
|
||||
import com.limelight.nvstream.http.NvHTTP;
|
||||
import com.limelight.nvstream.http.PairingManager;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
import com.limelight.ui.AdapterFragment;
|
||||
import com.limelight.ui.AdapterFragmentCallbacks;
|
||||
import com.limelight.utils.CacheHelper;
|
||||
import com.limelight.utils.Dialog;
|
||||
import com.limelight.utils.ServerHelper;
|
||||
import com.limelight.utils.ShortcutHelper;
|
||||
import com.limelight.utils.SpinnerDialog;
|
||||
import com.limelight.utils.UiHelper;
|
||||
|
||||
@@ -43,6 +45,7 @@ import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
private AppGridAdapter appGridAdapter;
|
||||
private String uuidString;
|
||||
private ShortcutHelper shortcutHelper;
|
||||
|
||||
private ComputerDetails computer;
|
||||
private ComputerManagerService.ApplistPoller poller;
|
||||
@@ -78,6 +81,10 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
// Get the computer object
|
||||
computer = managerBinder.getComputer(UUID.fromString(uuidString));
|
||||
if (computer == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
appGridAdapter = new AppGridAdapter(AppView.this,
|
||||
@@ -90,12 +97,15 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the app grid with cached data (if possible).
|
||||
// This must be done _before_ startComputerUpdates()
|
||||
// so the initial serverinfo response can update the running
|
||||
// icon.
|
||||
populateAppGridWithCache();
|
||||
|
||||
// Start updates
|
||||
startComputerUpdates();
|
||||
|
||||
// Load the app grid with cached data (if possible)
|
||||
populateAppGridWithCache();
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -132,7 +142,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
managerBinder.startPolling(new ComputerManagerListener() {
|
||||
@Override
|
||||
public void notifyComputerUpdated(ComputerDetails details) {
|
||||
public void notifyComputerUpdated(final ComputerDetails details) {
|
||||
// Do nothing if updates are suspended
|
||||
if (suspendGridUpdates) {
|
||||
return;
|
||||
@@ -157,6 +167,24 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
return;
|
||||
}
|
||||
|
||||
// Close immediately if the PC is no longer paired
|
||||
if (details.state == ComputerDetails.State.ONLINE && details.pairState != PairingManager.PairState.PAIRED) {
|
||||
AppView.this.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Disable shortcuts referencing this PC for now
|
||||
shortcutHelper.disableShortcut(details.uuid.toString(),
|
||||
getResources().getString(R.string.scut_not_paired));
|
||||
|
||||
// Display a toast to the user and quit the activity
|
||||
Toast.makeText(AppView.this, getResources().getText(R.string.scut_not_paired), Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// App list is the same or empty
|
||||
if (details.rawAppList == null || details.rawAppList.equals(lastRawApplist)) {
|
||||
|
||||
@@ -175,6 +203,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
try {
|
||||
updateUiWithAppList(NvHTTP.getAppListByReader(new StringReader(details.rawAppList)));
|
||||
updateUiWithServerinfo(details);
|
||||
|
||||
if (blockingLoadSpinner != null) {
|
||||
blockingLoadSpinner.dismiss();
|
||||
@@ -208,12 +237,9 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(getResources().getConfiguration());
|
||||
config.locale = new Locale(locale);
|
||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||
}
|
||||
shortcutHelper = new ShortcutHelper(this);
|
||||
|
||||
UiHelper.setLocale(this);
|
||||
|
||||
setContentView(R.layout.activity_app_view);
|
||||
|
||||
@@ -221,11 +247,17 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
uuidString = getIntent().getStringExtra(UUID_EXTRA);
|
||||
|
||||
String labelText = getResources().getString(R.string.title_applist)+" "+getIntent().getStringExtra(NAME_EXTRA);
|
||||
String computerName = getIntent().getStringExtra(NAME_EXTRA);
|
||||
|
||||
String labelText = getResources().getString(R.string.title_applist)+" "+computerName;
|
||||
TextView label = (TextView) findViewById(R.id.appListText);
|
||||
setTitle(labelText);
|
||||
label.setText(labelText);
|
||||
|
||||
// Add a launcher shortcut for this PC (forced, since this is user interaction)
|
||||
shortcutHelper.createAppViewShortcut(uuidString, computerName, uuidString, true);
|
||||
shortcutHelper.reportShortcutUsed(uuidString);
|
||||
|
||||
// Bind to the computer manager service
|
||||
bindService(new Intent(this, ComputerManagerService.class), serviceConnection,
|
||||
Service.BIND_AUTO_CREATE);
|
||||
@@ -282,27 +314,14 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
stopComputerUpdates();
|
||||
}
|
||||
|
||||
private int getRunningAppId() {
|
||||
int runningAppId = -1;
|
||||
for (int i = 0; i < appGridAdapter.getCount(); i++) {
|
||||
AppObject app = (AppObject) appGridAdapter.getItem(i);
|
||||
if (app.app.getIsRunning()) {
|
||||
runningAppId = app.app.getAppId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return runningAppId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
|
||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
||||
AppObject selectedApp = (AppObject) appGridAdapter.getItem(info.position);
|
||||
int runningAppId = getRunningAppId();
|
||||
if (runningAppId != -1) {
|
||||
if (runningAppId == selectedApp.app.getAppId()) {
|
||||
if (lastRunningAppId != 0) {
|
||||
if (lastRunningAppId == selectedApp.app.getAppId()) {
|
||||
menu.add(Menu.NONE, START_OR_RESUME_ID, 1, getResources().getString(R.string.applist_menu_resume));
|
||||
menu.add(Menu.NONE, QUIT_ID, 2, getResources().getString(R.string.applist_menu_quit));
|
||||
}
|
||||
@@ -378,19 +397,19 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
AppObject existingApp = (AppObject) appGridAdapter.getItem(i);
|
||||
|
||||
// There can only be one or zero apps running.
|
||||
if (existingApp.app.getIsRunning() &&
|
||||
if (existingApp.isRunning &&
|
||||
existingApp.app.getAppId() == details.runningGameId) {
|
||||
// This app was running and still is, so we're done now
|
||||
return;
|
||||
}
|
||||
else if (existingApp.app.getAppId() == details.runningGameId) {
|
||||
// This app wasn't running but now is
|
||||
existingApp.app.setIsRunning(true);
|
||||
existingApp.isRunning = true;
|
||||
updated = true;
|
||||
}
|
||||
else if (existingApp.app.getIsRunning()) {
|
||||
else if (existingApp.isRunning) {
|
||||
// This app was running but now isn't
|
||||
existingApp.app.setIsRunning(false);
|
||||
existingApp.isRunning = false;
|
||||
updated = true;
|
||||
}
|
||||
else {
|
||||
@@ -420,10 +439,6 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
AppObject existingApp = (AppObject) appGridAdapter.getItem(i);
|
||||
if (existingApp.app.getAppId() == app.getAppId()) {
|
||||
// Found the app; update its properties
|
||||
if (existingApp.app.getIsRunning() != app.getIsRunning()) {
|
||||
existingApp.app.setIsRunning(app.getIsRunning());
|
||||
updated = true;
|
||||
}
|
||||
if (!existingApp.app.getAppName().equals(app.getAppName())) {
|
||||
existingApp.app.setAppName(app.getAppName());
|
||||
updated = true;
|
||||
@@ -493,7 +508,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
AppObject app = (AppObject) appGridAdapter.getItem(pos);
|
||||
|
||||
// Only open the context menu if something is running, otherwise start it
|
||||
if (getRunningAppId() != -1) {
|
||||
if (lastRunningAppId != 0) {
|
||||
openContextMenu(arg1);
|
||||
} else {
|
||||
ServerHelper.doStart(AppView.this, app.app, computer, managerBinder);
|
||||
@@ -506,6 +521,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
public class AppObject {
|
||||
public final NvApp app;
|
||||
public boolean isRunning;
|
||||
|
||||
public AppObject(NvApp app) {
|
||||
if (app == null) {
|
||||
|
||||
164
app/src/main/java/com/limelight/AppViewShortcutTrampoline.java
Normal file
@@ -0,0 +1,164 @@
|
||||
package com.limelight;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
|
||||
import com.limelight.computers.ComputerManagerListener;
|
||||
import com.limelight.computers.ComputerManagerService;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.NvApp;
|
||||
import com.limelight.utils.Dialog;
|
||||
import com.limelight.utils.ServerHelper;
|
||||
import com.limelight.utils.SpinnerDialog;
|
||||
import com.limelight.utils.UiHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AppViewShortcutTrampoline extends Activity {
|
||||
private String uuidString;
|
||||
|
||||
private ComputerDetails computer;
|
||||
private SpinnerDialog blockingLoadSpinner;
|
||||
|
||||
public final static String UUID_EXTRA = "UUID";
|
||||
|
||||
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
public void onServiceConnected(ComponentName className, IBinder binder) {
|
||||
final ComputerManagerService.ComputerManagerBinder localBinder =
|
||||
((ComputerManagerService.ComputerManagerBinder)binder);
|
||||
|
||||
// Wait in a separate thread to avoid stalling the UI
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Wait for the binder to be ready
|
||||
localBinder.waitForReady();
|
||||
|
||||
// Now make the binder visible
|
||||
managerBinder = localBinder;
|
||||
|
||||
// Get the computer object
|
||||
computer = managerBinder.getComputer(UUID.fromString(uuidString));
|
||||
|
||||
// Force CMS to repoll this machine
|
||||
managerBinder.invalidateStateForComputer(computer.uuid);
|
||||
|
||||
// Start polling
|
||||
managerBinder.startPolling(new ComputerManagerListener() {
|
||||
@Override
|
||||
public void notifyComputerUpdated(final ComputerDetails details) {
|
||||
// Don't care about other computers
|
||||
if (!details.uuid.toString().equalsIgnoreCase(uuidString)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (details.state != ComputerDetails.State.UNKNOWN) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Stop showing the spinner
|
||||
if (blockingLoadSpinner != null) {
|
||||
blockingLoadSpinner.dismiss();
|
||||
blockingLoadSpinner = null;
|
||||
}
|
||||
|
||||
if (details.state == ComputerDetails.State.ONLINE) {
|
||||
// Close this activity
|
||||
finish();
|
||||
|
||||
// Create a new activity stack for this launch
|
||||
ArrayList<Intent> intentStack = new ArrayList<>();
|
||||
Intent i;
|
||||
|
||||
// Add the PC view at the back (and clear the task)
|
||||
i = new Intent(AppViewShortcutTrampoline.this, PcView.class);
|
||||
i.setAction(Intent.ACTION_MAIN);
|
||||
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intentStack.add(i);
|
||||
|
||||
// Take this intent's data and create an intent to start the app view
|
||||
i = new Intent(getIntent());
|
||||
i.setClass(AppViewShortcutTrampoline.this, AppView.class);
|
||||
intentStack.add(i);
|
||||
|
||||
// If a game is running, we'll make the stream the top level activity
|
||||
if (details.runningGameId != 0) {
|
||||
intentStack.add(ServerHelper.createStartIntent(AppViewShortcutTrampoline.this,
|
||||
new NvApp("app", details.runningGameId), details, managerBinder));
|
||||
}
|
||||
|
||||
// Now start the activities
|
||||
startActivities(intentStack.toArray(new Intent[]{}));
|
||||
}
|
||||
else if (details.state == ComputerDetails.State.OFFLINE) {
|
||||
// Computer offline - display an error dialog
|
||||
Dialog.displayDialog(AppViewShortcutTrampoline.this,
|
||||
getResources().getString(R.string.conn_error_title),
|
||||
getResources().getString(R.string.error_pc_offline),
|
||||
true);
|
||||
}
|
||||
|
||||
// We don't want any more callbacks from now on, so go ahead
|
||||
// and unbind from the service
|
||||
if (managerBinder != null) {
|
||||
managerBinder.stopPolling();
|
||||
unbindService(serviceConnection);
|
||||
managerBinder = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
managerBinder = null;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
UiHelper.notifyNewRootView(this);
|
||||
|
||||
uuidString = getIntent().getStringExtra(UUID_EXTRA);
|
||||
|
||||
// Bind to the computer manager service
|
||||
bindService(new Intent(this, ComputerManagerService.class), serviceConnection,
|
||||
Service.BIND_AUTO_CREATE);
|
||||
|
||||
blockingLoadSpinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.conn_establishing_title),
|
||||
getResources().getString(R.string.applist_connect_msg), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
if (blockingLoadSpinner != null) {
|
||||
blockingLoadSpinner.dismiss();
|
||||
blockingLoadSpinner = null;
|
||||
}
|
||||
|
||||
Dialog.closeDialogs();
|
||||
|
||||
if (managerBinder != null) {
|
||||
managerBinder.stopPolling();
|
||||
unbindService(serviceConnection);
|
||||
managerBinder = null;
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -4,27 +4,28 @@ package com.limelight;
|
||||
import com.limelight.binding.PlatformBinding;
|
||||
import com.limelight.binding.input.ControllerHandler;
|
||||
import com.limelight.binding.input.KeyboardTranslator;
|
||||
import com.limelight.binding.input.NvMouseHelper;
|
||||
import com.limelight.binding.input.capture.InputCaptureManager;
|
||||
import com.limelight.binding.input.capture.InputCaptureProvider;
|
||||
import com.limelight.binding.input.TouchContext;
|
||||
import com.limelight.binding.input.driver.UsbDriverService;
|
||||
import com.limelight.binding.input.evdev.EvdevHandler;
|
||||
import com.limelight.binding.input.evdev.EvdevListener;
|
||||
import com.limelight.binding.input.virtual_controller.VirtualController;
|
||||
import com.limelight.binding.video.EnhancedDecoderRenderer;
|
||||
import com.limelight.binding.video.MediaCodecDecoderRenderer;
|
||||
import com.limelight.binding.video.MediaCodecHelper;
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
import com.limelight.nvstream.NvConnectionListener;
|
||||
import com.limelight.nvstream.StreamConfiguration;
|
||||
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
||||
import com.limelight.nvstream.http.NvApp;
|
||||
import com.limelight.nvstream.input.KeyboardPacket;
|
||||
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||
import com.limelight.nvstream.jni.MoonBridge;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
import com.limelight.ui.GameGestures;
|
||||
import com.limelight.ui.StreamView;
|
||||
import com.limelight.utils.Dialog;
|
||||
import com.limelight.utils.ShortcutHelper;
|
||||
import com.limelight.utils.SpinnerDialog;
|
||||
import com.limelight.utils.UiHelper;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
@@ -33,12 +34,12 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Point;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.media.AudioManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
@@ -48,20 +49,16 @@ import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.View.OnGenericMotionListener;
|
||||
import android.view.View.OnSystemUiVisibilityChangeListener;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener,
|
||||
@@ -82,7 +79,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
private ControllerHandler controllerHandler;
|
||||
private VirtualController virtualController;
|
||||
private KeyboardTranslator keybTranslator;
|
||||
|
||||
private PreferenceConfiguration prefConfig;
|
||||
|
||||
@@ -92,18 +88,18 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
private boolean connecting = false;
|
||||
private boolean connected = false;
|
||||
|
||||
private EvdevHandler evdevHandler;
|
||||
private InputCaptureProvider inputCaptureProvider;
|
||||
private int modifierFlags = 0;
|
||||
private boolean grabbedInput = true;
|
||||
private boolean grabComboDown = false;
|
||||
private StreamView streamView;
|
||||
|
||||
private EnhancedDecoderRenderer decoderRenderer;
|
||||
private ShortcutHelper shortcutHelper;
|
||||
|
||||
private MediaCodecDecoderRenderer decoderRenderer;
|
||||
|
||||
private WifiManager.WifiLock wifiLock;
|
||||
|
||||
private int drFlags = 0;
|
||||
|
||||
private boolean connectedToUsbDriverService = false;
|
||||
private ServiceConnection usbDriverServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
@@ -124,17 +120,16 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
public static final String EXTRA_APP_ID = "AppId";
|
||||
public static final String EXTRA_UNIQUEID = "UniqueId";
|
||||
public static final String EXTRA_STREAMING_REMOTE = "Remote";
|
||||
public static final String EXTRA_PC_UUID = "UUID";
|
||||
public static final String EXTRA_PC_NAME = "PcName";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(getResources().getConfiguration());
|
||||
config.locale = new Locale(locale);
|
||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||
}
|
||||
shortcutHelper = new ShortcutHelper(this);
|
||||
|
||||
UiHelper.setLocale(this);
|
||||
|
||||
// We don't want a title bar
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
@@ -171,10 +166,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
// Read the stream preferences
|
||||
prefConfig = PreferenceConfiguration.readPreferences(this);
|
||||
|
||||
if (prefConfig.stretchVideo) {
|
||||
drFlags |= VideoDecoderRenderer.FLAG_FILL_SCREEN;
|
||||
}
|
||||
|
||||
// Listen for events on the game surface
|
||||
streamView = (StreamView) findViewById(R.id.surfaceView);
|
||||
streamView.setOnGenericMotionListener(this);
|
||||
@@ -184,7 +175,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
checkDataConnection();
|
||||
|
||||
// Make sure Wi-Fi is fully powered up
|
||||
WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE);
|
||||
WifiManager wifiMgr = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||
wifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "Limelight");
|
||||
wifiLock.setReferenceCounted(false);
|
||||
wifiLock.acquire();
|
||||
@@ -194,12 +185,18 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
int appId = Game.this.getIntent().getIntExtra(EXTRA_APP_ID, StreamConfiguration.INVALID_APP_ID);
|
||||
String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID);
|
||||
boolean remote = Game.this.getIntent().getBooleanExtra(EXTRA_STREAMING_REMOTE, false);
|
||||
String uuid = Game.this.getIntent().getStringExtra(EXTRA_PC_UUID);
|
||||
String pcName = Game.this.getIntent().getStringExtra(EXTRA_PC_NAME);
|
||||
|
||||
if (appId == StreamConfiguration.INVALID_APP_ID) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Add a launcher shortcut for this PC (forced, since this is user interaction)
|
||||
shortcutHelper.createAppViewShortcut(uuid, pcName, uuid, true);
|
||||
shortcutHelper.reportShortcutUsed(uuid);
|
||||
|
||||
// Initialize the MediaCodec helper before creating the decoder
|
||||
MediaCodecHelper.initializeWithContext(this);
|
||||
|
||||
@@ -228,54 +225,24 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
.setApp(new NvApp(appName, appId))
|
||||
.setBitrate(prefConfig.bitrate * 1000)
|
||||
.setEnableSops(prefConfig.enableSops)
|
||||
.enableAdaptiveResolution((decoderRenderer.getCapabilities() &
|
||||
VideoDecoderRenderer.CAPABILITY_ADAPTIVE_RESOLUTION) != 0)
|
||||
.enableLocalAudioPlayback(prefConfig.playHostAudio)
|
||||
.setMaxPacketSize(remote ? 1024 : 1292)
|
||||
.setRemote(remote)
|
||||
.setHevcSupported(decoderRenderer.isHevcSupported())
|
||||
.setAudioConfiguration(prefConfig.enable51Surround ?
|
||||
StreamConfiguration.AUDIO_CONFIGURATION_5_1 :
|
||||
StreamConfiguration.AUDIO_CONFIGURATION_STEREO)
|
||||
MoonBridge.AUDIO_CONFIGURATION_51_SURROUND :
|
||||
MoonBridge.AUDIO_CONFIGURATION_STEREO)
|
||||
.build();
|
||||
|
||||
// Initialize the connection
|
||||
conn = new NvConnection(host, uniqueId, Game.this, config, PlatformBinding.getCryptoProvider(this));
|
||||
keybTranslator = new KeyboardTranslator(conn);
|
||||
controllerHandler = new ControllerHandler(conn, this, prefConfig.multiController, prefConfig.deadzonePercentage);
|
||||
conn = new NvConnection(host, uniqueId, config, PlatformBinding.getCryptoProvider(this));
|
||||
controllerHandler = new ControllerHandler(this, conn, this, prefConfig.multiController, prefConfig.deadzonePercentage);
|
||||
|
||||
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
||||
inputManager.registerInputDeviceListener(controllerHandler, null);
|
||||
|
||||
boolean aspectRatioMatch = false;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
|
||||
// On KitKat and later (where we can use the whole screen via immersive mode), we'll
|
||||
// calculate whether we need to scale by aspect ratio or not. If not, we'll use
|
||||
// setFixedSize so we can handle 4K properly. The only known devices that have
|
||||
// >= 4K screens have exactly 4K screens, so we'll be able to hit this good path
|
||||
// on these devices. On Marshmallow, we can start changing to 4K manually but no
|
||||
// 4K devices run 6.0 at the moment.
|
||||
Display display = getWindowManager().getDefaultDisplay();
|
||||
Point screenSize = new Point(0, 0);
|
||||
display.getSize(screenSize);
|
||||
|
||||
double screenAspectRatio = ((double)screenSize.y) / screenSize.x;
|
||||
double streamAspectRatio = ((double)prefConfig.height) / prefConfig.width;
|
||||
if (Math.abs(screenAspectRatio - streamAspectRatio) < 0.001) {
|
||||
LimeLog.info("Stream has compatible aspect ratio with output display");
|
||||
aspectRatioMatch = true;
|
||||
}
|
||||
}
|
||||
|
||||
SurfaceHolder sh = streamView.getHolder();
|
||||
if (prefConfig.stretchVideo || aspectRatioMatch) {
|
||||
// Set the surface to the size of the video
|
||||
sh.setFixedSize(prefConfig.width, prefConfig.height);
|
||||
}
|
||||
else {
|
||||
// Set the surface to scale based on the aspect ratio of the stream
|
||||
streamView.setDesiredAspectRatio((double)prefConfig.width / (double)prefConfig.height);
|
||||
}
|
||||
// Set to the optimal mode for streaming
|
||||
prepareDisplayForRendering();
|
||||
|
||||
// Initialize touch contexts
|
||||
for (int i = 0; i < touchContextMap.length; i++) {
|
||||
@@ -284,12 +251,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
streamView);
|
||||
}
|
||||
|
||||
if (LimelightBuildProps.ROOT_BUILD) {
|
||||
// Start watching for raw input
|
||||
evdevHandler = new EvdevHandler(this, this);
|
||||
evdevHandler.start();
|
||||
// Use sustained performance mode on N+ to ensure consistent
|
||||
// CPU availability
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
getWindow().setSustainedPerformanceMode(true);
|
||||
}
|
||||
|
||||
inputCaptureProvider = InputCaptureManager.getInputCaptureProvider(this, this);
|
||||
|
||||
if (prefConfig.onscreenController) {
|
||||
// create virtual onscreen controller
|
||||
virtualController = new VirtualController(conn,
|
||||
@@ -305,7 +274,96 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
// The connection will be started when the surface gets created
|
||||
sh.addCallback(this);
|
||||
streamView.getHolder().addCallback(this);
|
||||
}
|
||||
|
||||
private void prepareDisplayForRendering() {
|
||||
Display display = getWindowManager().getDefaultDisplay();
|
||||
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
|
||||
|
||||
// On M, we can explicitly set the optimal display mode
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
Display.Mode bestMode = display.getMode();
|
||||
for (Display.Mode candidate : display.getSupportedModes()) {
|
||||
boolean refreshRateOk = candidate.getRefreshRate() >= bestMode.getRefreshRate() &&
|
||||
candidate.getRefreshRate() < 63;
|
||||
boolean resolutionOk = candidate.getPhysicalWidth() >= bestMode.getPhysicalWidth() &&
|
||||
candidate.getPhysicalHeight() >= bestMode.getPhysicalHeight() &&
|
||||
candidate.getPhysicalWidth() <= 4096;
|
||||
|
||||
LimeLog.info("Examining display mode: "+candidate.getPhysicalWidth()+"x"+
|
||||
candidate.getPhysicalHeight()+"x"+candidate.getRefreshRate());
|
||||
|
||||
// On non-4K streams, we force the resolution to never change
|
||||
if (prefConfig.width < 3840) {
|
||||
if (display.getMode().getPhysicalWidth() != candidate.getPhysicalWidth() ||
|
||||
display.getMode().getPhysicalHeight() != candidate.getPhysicalHeight()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the refresh rate doesn't regress
|
||||
if (!refreshRateOk) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure the resolution doesn't regress
|
||||
if (!resolutionOk) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bestMode = candidate;
|
||||
}
|
||||
LimeLog.info("Selected display mode: "+bestMode.getPhysicalWidth()+"x"+
|
||||
bestMode.getPhysicalHeight()+"x"+bestMode.getRefreshRate());
|
||||
windowLayoutParams.preferredDisplayModeId = bestMode.getModeId();
|
||||
}
|
||||
// On L, we can at least tell the OS that we want 60 Hz
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
float bestRefreshRate = display.getRefreshRate();
|
||||
for (float candidate : display.getSupportedRefreshRates()) {
|
||||
if (candidate > bestRefreshRate && candidate < 63) {
|
||||
LimeLog.info("Examining refresh rate: "+candidate);
|
||||
bestRefreshRate = candidate;
|
||||
}
|
||||
}
|
||||
LimeLog.info("Selected refresh rate: "+bestRefreshRate);
|
||||
windowLayoutParams.preferredRefreshRate = bestRefreshRate;
|
||||
}
|
||||
|
||||
// Apply the display mode change
|
||||
getWindow().setAttributes(windowLayoutParams);
|
||||
|
||||
// From 4.4 to 5.1 we can't ask for a 4K display mode, so we'll
|
||||
// need to hint the OS to provide one.
|
||||
boolean aspectRatioMatch = false;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
|
||||
Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
|
||||
// On KitKat and later (where we can use the whole screen via immersive mode), we'll
|
||||
// calculate whether we need to scale by aspect ratio or not. If not, we'll use
|
||||
// setFixedSize so we can handle 4K properly. The only known devices that have
|
||||
// >= 4K screens have exactly 4K screens, so we'll be able to hit this good path
|
||||
// on these devices. On Marshmallow, we can start changing to 4K manually but no
|
||||
// 4K devices run 6.0 at the moment.
|
||||
Point screenSize = new Point(0, 0);
|
||||
display.getSize(screenSize);
|
||||
|
||||
double screenAspectRatio = ((double)screenSize.y) / screenSize.x;
|
||||
double streamAspectRatio = ((double)prefConfig.height) / prefConfig.width;
|
||||
if (Math.abs(screenAspectRatio - streamAspectRatio) < 0.001) {
|
||||
LimeLog.info("Stream has compatible aspect ratio with output display");
|
||||
aspectRatioMatch = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (prefConfig.stretchVideo || aspectRatioMatch) {
|
||||
// Set the surface to the size of the video
|
||||
streamView.getHolder().setFixedSize(prefConfig.width, prefConfig.height);
|
||||
}
|
||||
else {
|
||||
// Set the surface to scale based on the aspect ratio of the stream
|
||||
streamView.setDesiredAspectRatio((double)prefConfig.width / (double)prefConfig.height);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkDataConnection()
|
||||
@@ -347,11 +405,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
SpinnerDialog.closeDialogs(this);
|
||||
Dialog.closeDialogs();
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (controllerHandler != null) {
|
||||
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
||||
@@ -366,7 +421,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
if (conn != null) {
|
||||
VideoDecoderRenderer.VideoFormat videoFormat = conn.getActiveVideoFormat();
|
||||
int videoFormat = decoderRenderer.getActiveVideoFormat();
|
||||
|
||||
displayedFailureDialog = true;
|
||||
stopConnection();
|
||||
@@ -385,11 +440,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
// Add the video codec to the post-stream toast
|
||||
if (message != null && videoFormat != VideoDecoderRenderer.VideoFormat.Unknown) {
|
||||
if (videoFormat == VideoDecoderRenderer.VideoFormat.H265) {
|
||||
if (message != null) {
|
||||
if (videoFormat == MoonBridge.VIDEO_FORMAT_H265) {
|
||||
message += " [H.265]";
|
||||
}
|
||||
else {
|
||||
else if (videoFormat == MoonBridge.VIDEO_FORMAT_H264) {
|
||||
message += " [H.264]";
|
||||
}
|
||||
}
|
||||
@@ -398,6 +453,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
SpinnerDialog.closeDialogs(this);
|
||||
Dialog.closeDialogs();
|
||||
|
||||
finish();
|
||||
}
|
||||
@@ -406,16 +469,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
@Override
|
||||
public void run() {
|
||||
if (grabbedInput) {
|
||||
NvMouseHelper.setCursorVisibility(Game.this, true);
|
||||
if (evdevHandler != null) {
|
||||
evdevHandler.ungrabAll();
|
||||
}
|
||||
inputCaptureProvider.disableCapture();
|
||||
}
|
||||
else {
|
||||
NvMouseHelper.setCursorVisibility(Game.this, false);
|
||||
if (evdevHandler != null) {
|
||||
evdevHandler.regrabAll();
|
||||
}
|
||||
inputCaptureProvider.enableCapture();
|
||||
}
|
||||
|
||||
grabbedInput = !grabbedInput;
|
||||
@@ -508,11 +565,22 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
// Try the controller handler first
|
||||
boolean handled = controllerHandler.handleButtonDown(event);
|
||||
boolean handled = false;
|
||||
|
||||
boolean detectedGamepad = event.getDevice() == null ? false :
|
||||
((event.getDevice().getSources() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK ||
|
||||
(event.getDevice().getSources() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD);
|
||||
if (detectedGamepad || (event.getDevice() == null ||
|
||||
event.getDevice().getKeyboardType() != InputDevice.KEYBOARD_TYPE_ALPHABETIC
|
||||
)) {
|
||||
// Always try the controller handler first, unless it's an alphanumeric keyboard device.
|
||||
// Otherwise, controller handler will eat keyboard d-pad events.
|
||||
handled = controllerHandler.handleButtonDown(event);
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
// Try the keyboard handler
|
||||
short translated = keybTranslator.translate(event.getKeyCode());
|
||||
short translated = KeyboardTranslator.translate(event.getKeyCode());
|
||||
if (translated == 0) {
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
@@ -522,18 +590,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
return true;
|
||||
}
|
||||
|
||||
// Eat repeat down events
|
||||
if (event.getRepeatCount() > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pass through keyboard input if we're not grabbing
|
||||
if (!grabbedInput) {
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
keybTranslator.sendKeyDown(translated,
|
||||
getModifierState(event));
|
||||
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_DOWN, getModifierState());
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -546,11 +608,21 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
// Try the controller handler first
|
||||
boolean handled = controllerHandler.handleButtonUp(event);
|
||||
boolean handled = false;
|
||||
boolean detectedGamepad = event.getDevice() == null ? false :
|
||||
((event.getDevice().getSources() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK ||
|
||||
(event.getDevice().getSources() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD);
|
||||
if (detectedGamepad || (event.getDevice() == null ||
|
||||
event.getDevice().getKeyboardType() != InputDevice.KEYBOARD_TYPE_ALPHABETIC
|
||||
)) {
|
||||
// Always try the controller handler first, unless it's an alphanumeric keyboard device.
|
||||
// Otherwise, controller handler will eat keyboard d-pad events.
|
||||
handled = controllerHandler.handleButtonUp(event);
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
// Try the keyboard handler
|
||||
short translated = keybTranslator.translate(event.getKeyCode());
|
||||
short translated = KeyboardTranslator.translate(event.getKeyCode());
|
||||
if (translated == 0) {
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
@@ -564,8 +636,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
keybTranslator.sendKeyUp(translated,
|
||||
getModifierState(event));
|
||||
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_UP, getModifierState(event));
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -643,10 +714,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
// Get relative axis values if we can
|
||||
if (NvMouseHelper.eventHasRelativeMouseAxes(event)) {
|
||||
if (inputCaptureProvider.eventHasRelativeMouseAxes(event)) {
|
||||
// Send the deltas straight from the motion event
|
||||
conn.sendMouseMove((short)NvMouseHelper.getRelativeAxisX(event),
|
||||
(short)NvMouseHelper.getRelativeAxisY(event));
|
||||
conn.sendMouseMove((short) inputCaptureProvider.getRelativeAxisX(event),
|
||||
(short) inputCaptureProvider.getRelativeAxisY(event));
|
||||
|
||||
// We have to also update the position Android thinks the cursor is at
|
||||
// in order to avoid jumping when we stop moving or click.
|
||||
@@ -807,14 +878,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stageStarting(Stage stage) {
|
||||
public void stageStarting(String stage) {
|
||||
if (spinner != null) {
|
||||
spinner.setMessage(getResources().getString(R.string.conn_starting)+" "+stage.getName());
|
||||
spinner.setMessage(getResources().getString(R.string.conn_starting)+" "+stage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stageComplete(Stage stage) {
|
||||
public void stageComplete(String stage) {
|
||||
}
|
||||
|
||||
private void stopConnection() {
|
||||
@@ -823,18 +894,15 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
conn.stop();
|
||||
}
|
||||
|
||||
// Close the Evdev reader to allow use of captured input devices
|
||||
if (evdevHandler != null) {
|
||||
evdevHandler.stop();
|
||||
evdevHandler = null;
|
||||
}
|
||||
|
||||
// Enable cursor visibility again
|
||||
NvMouseHelper.setCursorVisibility(this, true);
|
||||
inputCaptureProvider.disableCapture();
|
||||
|
||||
// Destroy the capture provider
|
||||
inputCaptureProvider.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stageFailed(Stage stage) {
|
||||
public void stageFailed(String stage, long errorCode) {
|
||||
if (spinner != null) {
|
||||
spinner.dismiss();
|
||||
spinner = null;
|
||||
@@ -842,17 +910,19 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
if (!displayedFailureDialog) {
|
||||
displayedFailureDialog = true;
|
||||
LimeLog.severe(stage+" failed: "+errorCode);
|
||||
|
||||
stopConnection();
|
||||
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title),
|
||||
getResources().getString(R.string.conn_error_msg)+" "+stage.getName(), true);
|
||||
getResources().getString(R.string.conn_error_msg)+" "+stage, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionTerminated(Exception e) {
|
||||
public void connectionTerminated(long errorCode) {
|
||||
if (!displayedFailureDialog) {
|
||||
displayedFailureDialog = true;
|
||||
e.printStackTrace();
|
||||
LimeLog.severe("Connection terminated: "+errorCode);
|
||||
|
||||
stopConnection();
|
||||
Dialog.displayDialog(this, getResources().getString(R.string.conn_terminated_title),
|
||||
@@ -876,7 +946,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
// Hide the mouse cursor now. Doing it before
|
||||
// dismissing the spinner seems to be undone
|
||||
// when the spinner gets displayed.
|
||||
NvMouseHelper.setCursorVisibility(Game.this, false);
|
||||
inputCaptureProvider.enableCapture();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -914,14 +984,22 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
if (!connected && !connecting) {
|
||||
connecting = true;
|
||||
|
||||
conn.start(PlatformBinding.getDeviceName(), holder, drFlags,
|
||||
PlatformBinding.getAudioRenderer(), decoderRenderer);
|
||||
decoderRenderer.setRenderTarget(holder);
|
||||
conn.start(PlatformBinding.getAudioRenderer(), decoderRenderer, Game.this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
if (connected) {
|
||||
// HACK: Android is supposed to let you return from this function
|
||||
// before throwing a fit if you access the surface again. Unfortunately,
|
||||
// MediaCodec often tries to access the destroyed surface and triggers
|
||||
// an IllegalStateException. To workaround this, we will invoke
|
||||
// the DecoderRenderer's stop function ourselves, so it will hopefully
|
||||
// happen early enough to not trigger the bug
|
||||
decoderRenderer.stop();
|
||||
|
||||
stopConnection();
|
||||
}
|
||||
}
|
||||
@@ -966,17 +1044,17 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
@Override
|
||||
public void keyboardEvent(boolean buttonDown, short keyCode) {
|
||||
short keyMap = keybTranslator.translate(keyCode);
|
||||
short keyMap = KeyboardTranslator.translate(keyCode);
|
||||
if (keyMap != 0) {
|
||||
if (handleSpecialKeys(keyMap, buttonDown)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (buttonDown) {
|
||||
keybTranslator.sendKeyDown(keyMap, getModifierState());
|
||||
conn.sendKeyboardInput(keyMap, KeyboardPacket.KEY_DOWN, getModifierState());
|
||||
}
|
||||
else {
|
||||
keybTranslator.sendKeyUp(keyMap, getModifierState());
|
||||
conn.sendKeyboardInput(keyMap, KeyboardPacket.KEY_UP, getModifierState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
79
app/src/main/java/com/limelight/HelpActivity.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package com.limelight;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import com.limelight.utils.SpinnerDialog;
|
||||
|
||||
public class HelpActivity extends Activity {
|
||||
|
||||
private SpinnerDialog loadingDialog;
|
||||
private WebView webView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
webView = new WebView(this);
|
||||
setContentView(webView);
|
||||
|
||||
// These allow the user to zoom the page
|
||||
webView.getSettings().setBuiltInZoomControls(true);
|
||||
webView.getSettings().setDisplayZoomControls(false);
|
||||
|
||||
// This sets the view to display the whole page by default
|
||||
webView.getSettings().setUseWideViewPort(true);
|
||||
webView.getSettings().setLoadWithOverviewMode(true);
|
||||
|
||||
// This allows the links to places on the same page to work
|
||||
webView.getSettings().setJavaScriptEnabled(true);
|
||||
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
if (loadingDialog == null) {
|
||||
loadingDialog = SpinnerDialog.displayDialog(HelpActivity.this,
|
||||
getResources().getString(R.string.help_loading_title),
|
||||
getResources().getString(R.string.help_loading_msg), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
if (loadingDialog != null) {
|
||||
loadingDialog.dismiss();
|
||||
loadingDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (url.toUpperCase().startsWith("https://github.com/moonlight-stream/moonlight-docs/wiki/".toUpperCase()) ||
|
||||
url.toUpperCase().startsWith("http://github.com/moonlight-stream/moonlight-docs/wiki/".toUpperCase())) {
|
||||
// Allow navigation to Moonlight docs
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
webView.loadUrl(getIntent().getData().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Back goes back through the WebView history
|
||||
// until no more history remains
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
}
|
||||
else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,9 @@ import com.limelight.preferences.StreamSettings;
|
||||
import com.limelight.ui.AdapterFragment;
|
||||
import com.limelight.ui.AdapterFragmentCallbacks;
|
||||
import com.limelight.utils.Dialog;
|
||||
import com.limelight.utils.HelpLauncher;
|
||||
import com.limelight.utils.ServerHelper;
|
||||
import com.limelight.utils.ShortcutHelper;
|
||||
import com.limelight.utils.UiHelper;
|
||||
|
||||
import android.app.Activity;
|
||||
@@ -52,6 +54,7 @@ import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
private RelativeLayout noPcFoundLayout;
|
||||
private PcGridAdapter pcGridAdapter;
|
||||
private ShortcutHelper shortcutHelper;
|
||||
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
||||
private boolean freezeUpdates, runningPolling, inForeground;
|
||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
@@ -109,6 +112,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
// Setup the list view
|
||||
ImageButton settingsButton = (ImageButton) findViewById(R.id.settingsButton);
|
||||
ImageButton addComputerButton = (ImageButton) findViewById(R.id.manuallyAddPc);
|
||||
ImageButton helpButton = (ImageButton) findViewById(R.id.helpButton);
|
||||
|
||||
settingsButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
@@ -123,6 +127,12 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
startActivity(i);
|
||||
}
|
||||
});
|
||||
helpButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
HelpLauncher.launchSetupGuide(PcView.this);
|
||||
}
|
||||
});
|
||||
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(R.id.pcFragmentContainer, new AdapterFragment())
|
||||
@@ -142,12 +152,9 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(getResources().getConfiguration());
|
||||
config.locale = new Locale(locale);
|
||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||
}
|
||||
shortcutHelper = new ShortcutHelper(this);
|
||||
|
||||
UiHelper.setLocale(this);
|
||||
|
||||
// Bind to the computer manager service
|
||||
bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection,
|
||||
@@ -299,7 +306,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
// Stop updates and wait while pairing
|
||||
stopComputerUpdates(true);
|
||||
|
||||
InetAddress addr = null;
|
||||
InetAddress addr;
|
||||
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
|
||||
addr = computer.localIp;
|
||||
}
|
||||
@@ -334,10 +341,17 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
else if (pairState == PairingManager.PairState.FAILED) {
|
||||
message = getResources().getString(R.string.pair_fail);
|
||||
}
|
||||
else if (pairState == PairingManager.PairState.ALREADY_IN_PROGRESS) {
|
||||
message = getResources().getString(R.string.pair_already_in_progress);
|
||||
}
|
||||
else if (pairState == PairingManager.PairState.PAIRED) {
|
||||
// Just navigate to the app view without displaying a toast
|
||||
message = null;
|
||||
success = true;
|
||||
|
||||
// Invalidate reachability information after pairing to force
|
||||
// a refresh before reading pair state again
|
||||
managerBinder.invalidateStateForComputer(computer.uuid);
|
||||
}
|
||||
else {
|
||||
// Should be no other values
|
||||
@@ -429,7 +443,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
NvHTTP httpConn;
|
||||
String message;
|
||||
try {
|
||||
InetAddress addr = null;
|
||||
InetAddress addr;
|
||||
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
|
||||
addr = computer.localIp;
|
||||
}
|
||||
@@ -558,6 +572,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i);
|
||||
|
||||
if (details.equals(computer.details)) {
|
||||
// Disable or delete shortcuts referencing this PC
|
||||
shortcutHelper.disableShortcut(details.uuid.toString(),
|
||||
getResources().getString(R.string.scut_deleted_pc));
|
||||
|
||||
pcGridAdapter.removeComputer(computer);
|
||||
pcGridAdapter.notifyDataSetChanged();
|
||||
|
||||
@@ -584,6 +602,11 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
}
|
||||
}
|
||||
|
||||
// Add a launcher shortcut for this PC
|
||||
if (details.pairState == PairState.PAIRED) {
|
||||
shortcutHelper.createAppViewShortcut(details.uuid.toString(), details, false);
|
||||
}
|
||||
|
||||
if (existingEntry != null) {
|
||||
// Replace the information in the existing entry
|
||||
existingEntry.details = details;
|
||||
|
||||
@@ -6,34 +6,31 @@ import android.media.AudioTrack;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.nvstream.av.audio.AudioRenderer;
|
||||
import com.limelight.nvstream.jni.MoonBridge;
|
||||
|
||||
public class AndroidAudioRenderer implements AudioRenderer {
|
||||
|
||||
private AudioTrack track;
|
||||
|
||||
@Override
|
||||
public boolean streamInitialized(int channelCount, int channelMask, int samplesPerFrame, int sampleRate) {
|
||||
public int setup(int audioConfiguration) {
|
||||
int channelConfig;
|
||||
int bufferSize;
|
||||
int bytesPerFrame = (samplesPerFrame * 2);
|
||||
int bytesPerFrame;
|
||||
|
||||
switch (channelCount)
|
||||
switch (audioConfiguration)
|
||||
{
|
||||
case 1:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
|
||||
break;
|
||||
case 2:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
break;
|
||||
case 4:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_QUAD;
|
||||
break;
|
||||
case 6:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||
break;
|
||||
default:
|
||||
LimeLog.severe("Decoder returned unhandled channel count");
|
||||
return false;
|
||||
case MoonBridge.AUDIO_CONFIGURATION_STEREO:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
bytesPerFrame = 2 * 240 * 2;
|
||||
break;
|
||||
case MoonBridge.AUDIO_CONFIGURATION_51_SURROUND:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||
bytesPerFrame = 6 * 240 * 2;
|
||||
break;
|
||||
default:
|
||||
LimeLog.severe("Decoder returned unhandled channel count");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We're not supposed to request less than the minimum
|
||||
@@ -46,7 +43,7 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
bufferSize = bytesPerFrame * 2;
|
||||
|
||||
track = new AudioTrack(AudioManager.STREAM_MUSIC,
|
||||
sampleRate,
|
||||
48000,
|
||||
channelConfig,
|
||||
AudioFormat.ENCODING_PCM_16BIT,
|
||||
bufferSize,
|
||||
@@ -61,7 +58,7 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// Now try the larger buffer size
|
||||
bufferSize = Math.max(AudioTrack.getMinBufferSize(sampleRate,
|
||||
bufferSize = Math.max(AudioTrack.getMinBufferSize(48000,
|
||||
channelConfig,
|
||||
AudioFormat.ENCODING_PCM_16BIT),
|
||||
bytesPerFrame * 2);
|
||||
@@ -70,7 +67,7 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
bufferSize = (((bufferSize + (bytesPerFrame - 1)) / bytesPerFrame) * bytesPerFrame);
|
||||
|
||||
track = new AudioTrack(AudioManager.STREAM_MUSIC,
|
||||
sampleRate,
|
||||
48000,
|
||||
channelConfig,
|
||||
AudioFormat.ENCODING_PCM_16BIT,
|
||||
bufferSize,
|
||||
@@ -79,24 +76,26 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
}
|
||||
|
||||
LimeLog.info("Audio track buffer size: "+bufferSize);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playDecodedAudio(byte[] audioData, int offset, int length) {
|
||||
track.write(audioData, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void streamClosing() {
|
||||
if (track != null) {
|
||||
track.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCapabilities() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playDecodedAudio(byte[] audioData) {
|
||||
track.write(audioData, 0, audioData.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
// Immediately drop all pending data
|
||||
track.pause();
|
||||
track.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
track.release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
package com.limelight.binding.input;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.os.SystemClock;
|
||||
import android.util.SparseArray;
|
||||
import android.view.InputDevice;
|
||||
import android.view.InputEvent;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.binding.input.driver.UsbDriverListener;
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
import com.limelight.nvstream.input.ControllerPacket;
|
||||
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||
import com.limelight.ui.GameGestures;
|
||||
import com.limelight.utils.Vector2d;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class ControllerHandler implements InputManager.InputDeviceListener, UsbDriverListener {
|
||||
|
||||
private static final int MAXIMUM_BUMPER_UP_DELAY_MS = 100;
|
||||
|
||||
private static final int START_DOWN_TIME_KEYB_MS = 750;
|
||||
private static final int START_DOWN_TIME_MOUSE_MODE_MS = 750;
|
||||
|
||||
private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25;
|
||||
|
||||
@@ -34,6 +41,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
private final SparseArray<UsbDeviceContext> usbDeviceContexts = new SparseArray<>();
|
||||
|
||||
private final NvConnection conn;
|
||||
private final Context activityContext;
|
||||
private final double stickDeadzone;
|
||||
private final InputDeviceContext defaultContext = new InputDeviceContext();
|
||||
private final GameGestures gestures;
|
||||
@@ -42,7 +50,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
private final boolean multiControllerEnabled;
|
||||
private short currentControllers;
|
||||
|
||||
public ControllerHandler(NvConnection conn, GameGestures gestures, boolean multiControllerEnabled, int deadzonePercentage) {
|
||||
public ControllerHandler(Context activityContext, NvConnection conn, GameGestures gestures, boolean multiControllerEnabled, int deadzonePercentage) {
|
||||
this.activityContext = activityContext;
|
||||
this.conn = conn;
|
||||
this.gestures = gestures;
|
||||
this.multiControllerEnabled = multiControllerEnabled;
|
||||
@@ -123,7 +132,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
private void releaseControllerNumber(GenericControllerContext context) {
|
||||
// If this device sent data as a gamepad, zero the values before removing
|
||||
if (context.assignedControllerNumber) {
|
||||
conn.sendControllerInput(context.controllerNumber, (short) 0,
|
||||
conn.sendControllerInput(context.controllerNumber, getActiveControllerMask(),
|
||||
(short) 0,
|
||||
(byte) 0, (byte) 0,
|
||||
(short) 0, (short) 0,
|
||||
(short) 0, (short) 0);
|
||||
@@ -330,15 +340,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore the back buttonn if a controller has both buttons
|
||||
// The ADT-1 controller needs a similar fixup to the ASUS Gamepad
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
|
||||
boolean[] hasSelectKey = dev.hasKeys(KeyEvent.KEYCODE_BUTTON_SELECT, KeyEvent.KEYCODE_BACK, 0);
|
||||
if (hasSelectKey[0] && hasSelectKey[1]) {
|
||||
// Xiaomi gamepads claim to have both buttons then only send KEYCODE_BACK events
|
||||
if (dev.getVendorId() != 0x2717) {
|
||||
LimeLog.info("Ignoring back button because select is present");
|
||||
context.ignoreBack = true;
|
||||
}
|
||||
// The device name provided is just "Gamepad" which is pretty useless, so we
|
||||
// use VID/PID instead
|
||||
if (dev.getVendorId() == 0x18d1 && dev.getProductId() == 0x2c40) {
|
||||
context.backIsStart = true;
|
||||
context.modeIsSelect = true;
|
||||
context.triggerDeadzone = 0.30f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,6 +393,17 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
context.isServal = true;
|
||||
context.ignoreBack = true;
|
||||
}
|
||||
// The Xbox One S Bluetooth controller has some mappings that need fixing up.
|
||||
// However, Microsoft released a firmware update with no change to VID/PID
|
||||
// or device name that fixed the mappings for Android. Since there's
|
||||
// no good way to detect this, we'll use the presence of GAS/BRAKE axes
|
||||
// that were added in the latest firmware. If those are present, the only
|
||||
// required fixup is ignoring the select button.
|
||||
else if (devName.equals("Xbox Wireless Controller")) {
|
||||
if (gasRange == null) {
|
||||
context.isXboxBtController = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LimeLog.info("Analog stick deadzone: "+context.leftStickDeadzoneRadius+" "+context.rightStickDeadzoneRadius);
|
||||
@@ -392,21 +412,27 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
return context;
|
||||
}
|
||||
|
||||
private InputDeviceContext getContextForDevice(InputDevice dev) {
|
||||
private InputDeviceContext getContextForEvent(InputEvent event) {
|
||||
// Unknown devices use the default context
|
||||
if (dev == null) {
|
||||
if (event.getDeviceId() == 0) {
|
||||
return defaultContext;
|
||||
}
|
||||
else if (event.getDevice() == null) {
|
||||
// During device removal, sometimes we can get events after the
|
||||
// input device has been destroyed. In this case we'll see a
|
||||
// != 0 device ID but no device attached.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Return the existing context if it exists
|
||||
InputDeviceContext context = inputDeviceContexts.get(dev.getId());
|
||||
InputDeviceContext context = inputDeviceContexts.get(event.getDeviceId());
|
||||
if (context != null) {
|
||||
return context;
|
||||
}
|
||||
|
||||
// Otherwise create a new context
|
||||
context = createInputDeviceContextForDevice(dev);
|
||||
inputDeviceContexts.put(dev.getId(), context);
|
||||
context = createInputDeviceContextForDevice(event.getDevice());
|
||||
inputDeviceContexts.put(event.getDeviceId(), context);
|
||||
|
||||
return context;
|
||||
}
|
||||
@@ -433,6 +459,16 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
}
|
||||
|
||||
private short getActiveControllerMask() {
|
||||
if (multiControllerEnabled) {
|
||||
return currentControllers;
|
||||
}
|
||||
else {
|
||||
// Only Player 1 is active with multi-controller disabled
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendControllerInputPacket(GenericControllerContext originalContext) {
|
||||
assignControllerNumberIfNeeded(originalContext);
|
||||
|
||||
@@ -451,7 +487,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
// device before we send it.
|
||||
for (int i = 0; i < inputDeviceContexts.size(); i++) {
|
||||
GenericControllerContext context = inputDeviceContexts.valueAt(i);
|
||||
if (context.assignedControllerNumber && context.controllerNumber == controllerNumber) {
|
||||
if (context.assignedControllerNumber &&
|
||||
context.controllerNumber == controllerNumber &&
|
||||
context.mouseEmulationActive == originalContext.mouseEmulationActive) {
|
||||
inputMap |= context.inputMap;
|
||||
leftTrigger |= maxByMagnitude(leftTrigger, context.leftTrigger);
|
||||
rightTrigger |= maxByMagnitude(rightTrigger, context.rightTrigger);
|
||||
@@ -463,7 +501,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
for (int i = 0; i < usbDeviceContexts.size(); i++) {
|
||||
GenericControllerContext context = usbDeviceContexts.valueAt(i);
|
||||
if (context.assignedControllerNumber && context.controllerNumber == controllerNumber) {
|
||||
if (context.assignedControllerNumber &&
|
||||
context.controllerNumber == controllerNumber &&
|
||||
context.mouseEmulationActive == originalContext.mouseEmulationActive) {
|
||||
inputMap |= context.inputMap;
|
||||
leftTrigger |= maxByMagnitude(leftTrigger, context.leftTrigger);
|
||||
rightTrigger |= maxByMagnitude(rightTrigger, context.rightTrigger);
|
||||
@@ -483,10 +523,41 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
rightStickY |= maxByMagnitude(rightStickY, defaultContext.rightStickY);
|
||||
}
|
||||
|
||||
conn.sendControllerInput(controllerNumber, inputMap,
|
||||
leftTrigger, rightTrigger,
|
||||
leftStickX, leftStickY,
|
||||
rightStickX, rightStickY);
|
||||
if (originalContext.mouseEmulationActive) {
|
||||
int changedMask = inputMap ^ originalContext.mouseEmulationLastInputMap;
|
||||
|
||||
boolean aDown = (inputMap & ControllerPacket.A_FLAG) != 0;
|
||||
boolean bDown = (inputMap & ControllerPacket.B_FLAG) != 0;
|
||||
|
||||
originalContext.mouseEmulationLastInputMap = inputMap;
|
||||
|
||||
if ((changedMask & ControllerPacket.A_FLAG) != 0) {
|
||||
if (aDown) {
|
||||
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT);
|
||||
}
|
||||
else {
|
||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
||||
}
|
||||
}
|
||||
if ((changedMask & ControllerPacket.B_FLAG) != 0) {
|
||||
if (bDown) {
|
||||
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
|
||||
}
|
||||
else {
|
||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
conn.sendControllerInput(controllerNumber, getActiveControllerMask(),
|
||||
(short)0, (byte)0, (byte)0, (short)0, (short)0, (short)0, (short)0);
|
||||
}
|
||||
else {
|
||||
conn.sendControllerInput(controllerNumber, getActiveControllerMask(),
|
||||
inputMap,
|
||||
leftTrigger, rightTrigger,
|
||||
leftStickX, leftStickY,
|
||||
rightStickX, rightStickY);
|
||||
}
|
||||
}
|
||||
|
||||
// Return a valid keycode, 0 to consume, or -1 to not consume the event
|
||||
@@ -500,41 +571,38 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
|
||||
if (context.isDualShock4) {
|
||||
switch (event.getKeyCode()) {
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
return KeyEvent.KEYCODE_BUTTON_L1;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_Z:
|
||||
return KeyEvent.KEYCODE_BUTTON_R1;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_C:
|
||||
return KeyEvent.KEYCODE_BUTTON_B;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_X:
|
||||
return KeyEvent.KEYCODE_BUTTON_Y;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
return KeyEvent.KEYCODE_BUTTON_A;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
return KeyEvent.KEYCODE_BUTTON_X;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
||||
return KeyEvent.KEYCODE_BUTTON_THUMBL;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
return KeyEvent.KEYCODE_BUTTON_THUMBR;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||
return KeyEvent.KEYCODE_BUTTON_SELECT;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||
return KeyEvent.KEYCODE_BUTTON_START;
|
||||
|
||||
// These are duplicate trigger events
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
return 0;
|
||||
switch (event.getScanCode()) {
|
||||
case 304:
|
||||
return KeyEvent.KEYCODE_BUTTON_X;
|
||||
case 305:
|
||||
return KeyEvent.KEYCODE_BUTTON_A;
|
||||
case 306:
|
||||
return KeyEvent.KEYCODE_BUTTON_B;
|
||||
case 307:
|
||||
return KeyEvent.KEYCODE_BUTTON_Y;
|
||||
case 308:
|
||||
return KeyEvent.KEYCODE_BUTTON_L1;
|
||||
case 309:
|
||||
return KeyEvent.KEYCODE_BUTTON_R1;
|
||||
/*
|
||||
**** Using analog triggers instead ****
|
||||
case 310:
|
||||
return KeyEvent.KEYCODE_BUTTON_L2;
|
||||
case 311:
|
||||
return KeyEvent.KEYCODE_BUTTON_R2;
|
||||
*/
|
||||
case 312:
|
||||
return KeyEvent.KEYCODE_BUTTON_SELECT;
|
||||
case 313:
|
||||
return KeyEvent.KEYCODE_BUTTON_START;
|
||||
case 314:
|
||||
return KeyEvent.KEYCODE_BUTTON_THUMBL;
|
||||
case 315:
|
||||
return KeyEvent.KEYCODE_BUTTON_THUMBR;
|
||||
case 316:
|
||||
return KeyEvent.KEYCODE_BUTTON_MODE;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// If this is a Serval controller sending an unknown key code, it's probably
|
||||
@@ -547,6 +615,35 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
return KeyEvent.KEYCODE_BUTTON_START;
|
||||
}
|
||||
}
|
||||
else if (context.isXboxBtController) {
|
||||
switch (event.getScanCode()) {
|
||||
case 306:
|
||||
return KeyEvent.KEYCODE_BUTTON_X;
|
||||
case 307:
|
||||
return KeyEvent.KEYCODE_BUTTON_Y;
|
||||
case 308:
|
||||
return KeyEvent.KEYCODE_BUTTON_L1;
|
||||
case 309:
|
||||
return KeyEvent.KEYCODE_BUTTON_R1;
|
||||
case 310:
|
||||
return KeyEvent.KEYCODE_BUTTON_SELECT;
|
||||
case 311:
|
||||
return KeyEvent.KEYCODE_BUTTON_START;
|
||||
case 312:
|
||||
return KeyEvent.KEYCODE_BUTTON_THUMBL;
|
||||
case 313:
|
||||
return KeyEvent.KEYCODE_BUTTON_THUMBR;
|
||||
case 139:
|
||||
return KeyEvent.KEYCODE_BUTTON_MODE;
|
||||
default:
|
||||
// Other buttons are mapped correctly
|
||||
}
|
||||
|
||||
// The Xbox button is sent as MENU
|
||||
if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) {
|
||||
return KeyEvent.KEYCODE_BUTTON_MODE;
|
||||
}
|
||||
}
|
||||
|
||||
if (context.hatXAxis != -1 && context.hatYAxis != -1) {
|
||||
switch (event.getKeyCode()) {
|
||||
@@ -561,7 +658,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
else if (context.hatXAxis == -1 &&
|
||||
context.hatYAxis == -1 &&
|
||||
context.isXboxController &&
|
||||
/* FIXME: There's no good way to know for sure if xpad is bound
|
||||
to this device, so we won't use the name to validate if these
|
||||
scancodes should be mapped to DPAD
|
||||
|
||||
context.isXboxController &&
|
||||
*/
|
||||
event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
|
||||
// If there's not a proper Xbox controller mapping, we'll translate the raw d-pad
|
||||
// scan codes into proper key codes
|
||||
@@ -651,9 +753,23 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
|
||||
if (context.leftTriggerAxis != -1 && context.rightTriggerAxis != -1) {
|
||||
// Android sends an initial 0 value for trigger axes even if the trigger
|
||||
// should be negative when idle. After the first touch, the axes will go back
|
||||
// to normal behavior, so ignore triggersIdleNegative for each trigger until
|
||||
// first touch.
|
||||
if (lt != 0) {
|
||||
context.leftTriggerUsed = true;
|
||||
}
|
||||
if (rt != 0) {
|
||||
context.rightTriggerUsed = true;
|
||||
}
|
||||
if (context.triggersIdleNegative) {
|
||||
lt = (lt + 1) / 2;
|
||||
rt = (rt + 1) / 2;
|
||||
if (context.leftTriggerUsed) {
|
||||
lt = (lt + 1) / 2;
|
||||
}
|
||||
if (context.rightTriggerUsed) {
|
||||
rt = (rt + 1) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (lt <= context.triggerDeadzone) {
|
||||
@@ -689,7 +805,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
|
||||
public boolean handleMotionEvent(MotionEvent event) {
|
||||
InputDeviceContext context = getContextForDevice(event.getDevice());
|
||||
InputDeviceContext context = getContextForEvent(event);
|
||||
if (context == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
float lsX = 0, lsY = 0, rsX = 0, rsY = 0, rt = 0, lt = 0, hatX = 0, hatY = 0;
|
||||
|
||||
// We purposefully ignore the historical values in the motion event as it makes
|
||||
@@ -720,8 +840,51 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
return true;
|
||||
}
|
||||
|
||||
private short scaleRawStickAxis(float stickValue) {
|
||||
return (short)Math.pow(stickValue, 3);
|
||||
}
|
||||
|
||||
private void sendEmulatedMouseEvent(short x, short y) {
|
||||
Vector2d vector = new Vector2d();
|
||||
vector.initialize(x, y);
|
||||
vector.scalarMultiply(1 / 32766.0f);
|
||||
vector.scalarMultiply(4);
|
||||
if (vector.getMagnitude() > 0) {
|
||||
// Move faster as the stick is pressed further from center
|
||||
vector.scalarMultiply(Math.pow(vector.getMagnitude(), 2));
|
||||
if (vector.getMagnitude() >= 1) {
|
||||
conn.sendMouseMove((short)vector.getX(), (short)-vector.getY());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleMouseEmulation(final GenericControllerContext context) {
|
||||
if (context.mouseEmulationTimer != null) {
|
||||
context.mouseEmulationTimer.cancel();
|
||||
context.mouseEmulationTimer = null;
|
||||
}
|
||||
|
||||
context.mouseEmulationActive = !context.mouseEmulationActive;
|
||||
Toast.makeText(activityContext, "Mouse emulation is: " + (context.mouseEmulationActive ? "ON" : "OFF"), Toast.LENGTH_SHORT).show();
|
||||
|
||||
if (context.mouseEmulationActive) {
|
||||
context.mouseEmulationTimer = new Timer();
|
||||
context.mouseEmulationTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Send mouse movement events from analog sticks
|
||||
sendEmulatedMouseEvent(context.leftStickX, context.leftStickY);
|
||||
sendEmulatedMouseEvent(context.rightStickX, context.rightStickY);
|
||||
}
|
||||
}, 50, 50);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean handleButtonUp(KeyEvent event) {
|
||||
InputDeviceContext context = getContextForDevice(event.getDevice());
|
||||
InputDeviceContext context = getContextForEvent(event);
|
||||
if (context == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int keyCode = handleRemapping(context, event);
|
||||
if (keyCode == 0) {
|
||||
@@ -746,10 +909,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
if (SystemClock.uptimeMillis() - context.startDownTime > ControllerHandler.START_DOWN_TIME_KEYB_MS) {
|
||||
// FIXME: The stock keyboard doesn't have controller focus so isn't usable. I'm not enabling this shortcut
|
||||
// until we have a custom keyboard or some other fix
|
||||
//gestures.showKeyboard();
|
||||
// Sometimes we'll get a spurious key up event on controller disconnect.
|
||||
// Make sure it's real by checking that the key is actually down before taking
|
||||
// any action.
|
||||
if ((context.inputMap & ControllerPacket.PLAY_FLAG) != 0 &&
|
||||
SystemClock.uptimeMillis() - context.startDownTime > ControllerHandler.START_DOWN_TIME_MOUSE_MODE_MS) {
|
||||
toggleMouseEmulation(context);
|
||||
}
|
||||
context.inputMap &= ~ControllerPacket.PLAY_FLAG;
|
||||
break;
|
||||
@@ -846,7 +1011,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
|
||||
public boolean handleButtonDown(KeyEvent event) {
|
||||
InputDeviceContext context = getContextForDevice(event.getDevice());
|
||||
InputDeviceContext context = getContextForEvent(event);
|
||||
if (context == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int keyCode = handleRemapping(context, event);
|
||||
if (keyCode == 0) {
|
||||
@@ -938,11 +1106,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL;
|
||||
}
|
||||
|
||||
// Send a new input packet if this is the first instance of a button down event
|
||||
// or anytime if we're emulating a button
|
||||
if (event.getRepeatCount() == 0 || context.emulatingButtonFlags != 0) {
|
||||
sendControllerInputPacket(context);
|
||||
}
|
||||
|
||||
// We don't need to send repeat key down events, but the platform
|
||||
// sends us events that claim to be repeats but they're from different
|
||||
// devices, so we just send them all and deal with some duplicates.
|
||||
sendControllerInputPacket(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -952,6 +1120,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
float rightStickX, float rightStickY,
|
||||
float leftTrigger, float rightTrigger) {
|
||||
UsbDeviceContext context = usbDeviceContexts.get(controllerId);
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2d leftStickVector = populateCachedVector(leftStickX, leftStickY);
|
||||
|
||||
@@ -1016,6 +1187,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
public short rightStickY = 0x0000;
|
||||
public short leftStickX = 0x0000;
|
||||
public short leftStickY = 0x0000;
|
||||
|
||||
public boolean mouseEmulationActive;
|
||||
public Timer mouseEmulationTimer;
|
||||
public short mouseEmulationLastInputMap;
|
||||
}
|
||||
|
||||
class InputDeviceContext extends GenericControllerContext {
|
||||
@@ -1030,12 +1205,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
public int leftTriggerAxis = -1;
|
||||
public int rightTriggerAxis = -1;
|
||||
public boolean triggersIdleNegative;
|
||||
public boolean leftTriggerUsed, rightTriggerUsed;
|
||||
|
||||
public int hatXAxis = -1;
|
||||
public int hatYAxis = -1;
|
||||
|
||||
public boolean isDualShock4;
|
||||
public boolean isXboxController;
|
||||
public boolean isXboxBtController;
|
||||
public boolean isServal;
|
||||
public boolean backIsStart;
|
||||
public boolean modeIsSelect;
|
||||
|
||||
@@ -3,14 +3,13 @@ package com.limelight.binding.input;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
import com.limelight.nvstream.input.KeycodeTranslator;
|
||||
|
||||
/**
|
||||
* Class to translate a Android key code into the codes GFE is expecting
|
||||
* @author Diego Waxemberg
|
||||
* @author Cameron Gutman
|
||||
*/
|
||||
public class KeyboardTranslator extends KeycodeTranslator {
|
||||
public class KeyboardTranslator {
|
||||
|
||||
/**
|
||||
* GFE's prefix for every key code
|
||||
@@ -59,21 +58,12 @@ public class KeyboardTranslator extends KeycodeTranslator {
|
||||
public static final int VK_QUOTE = 222;
|
||||
public static final int VK_PAUSE = 19;
|
||||
|
||||
/**
|
||||
* Constructs a new translator for the specified connection
|
||||
* @param conn the connection to which the translated codes are sent
|
||||
*/
|
||||
public KeyboardTranslator(NvConnection conn) {
|
||||
super(conn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the given keycode and returns the GFE keycode
|
||||
* @param keycode the code to be translated
|
||||
* @return a GFE keycode for the given keycode
|
||||
*/
|
||||
@Override
|
||||
public short translate(int keycode) {
|
||||
public static short translate(int keycode) {
|
||||
int translated;
|
||||
|
||||
/* There seems to be no clean mapping between Android key codes
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.limelight.binding.input.capture;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.PointerIcon;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
public class AndroidCaptureProvider extends InputCaptureProvider {
|
||||
private ViewGroup rootViewGroup;
|
||||
private Context context;
|
||||
|
||||
public AndroidCaptureProvider(Activity activity) {
|
||||
this.context = activity;
|
||||
this.rootViewGroup = (ViewGroup) activity.getWindow().getDecorView();
|
||||
}
|
||||
|
||||
public static boolean isCaptureProviderSupported() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
|
||||
}
|
||||
|
||||
private void setPointerIconOnAllViews(PointerIcon icon) {
|
||||
for (int i = 0; i < rootViewGroup.getChildCount(); i++) {
|
||||
View view = rootViewGroup.getChildAt(i);
|
||||
view.setPointerIcon(icon);
|
||||
}
|
||||
rootViewGroup.setPointerIcon(icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableCapture() {
|
||||
setPointerIconOnAllViews(PointerIcon.getSystemIcon(context, PointerIcon.TYPE_NULL));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCapture() {
|
||||
setPointerIconOnAllViews(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
||||
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X) != 0 ||
|
||||
event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getRelativeAxisX(MotionEvent event) {
|
||||
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getRelativeAxisY(MotionEvent event) {
|
||||
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.limelight.binding.input.capture;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.binding.input.evdev.EvdevCaptureProvider;
|
||||
import com.limelight.binding.input.evdev.EvdevListener;
|
||||
|
||||
public class InputCaptureManager {
|
||||
public static InputCaptureProvider getInputCaptureProvider(Activity activity, EvdevListener rootListener) {
|
||||
// Shield capture is preferred because it can capture when the cursor is over
|
||||
// the system UI. Android N native capture can only capture over views owned
|
||||
// by the application.
|
||||
if (ShieldCaptureProvider.isCaptureProviderSupported()) {
|
||||
LimeLog.info("Using NVIDIA mouse capture extension");
|
||||
return new ShieldCaptureProvider(activity);
|
||||
}
|
||||
else if (EvdevCaptureProvider.isCaptureProviderSupported()) {
|
||||
LimeLog.info("Using Evdev mouse capture");
|
||||
return new EvdevCaptureProvider(activity, rootListener);
|
||||
}
|
||||
else if (AndroidCaptureProvider.isCaptureProviderSupported()) {
|
||||
LimeLog.info("Using Android N+ native mouse capture");
|
||||
return new AndroidCaptureProvider(activity);
|
||||
}
|
||||
else {
|
||||
LimeLog.info("Mouse capture not available");
|
||||
return new NullCaptureProvider();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.limelight.binding.input.capture;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
public abstract class InputCaptureProvider {
|
||||
public abstract void enableCapture();
|
||||
public abstract void disableCapture();
|
||||
public void destroy() {}
|
||||
|
||||
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public float getRelativeAxisX(MotionEvent event) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public float getRelativeAxisY(MotionEvent event) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.limelight.binding.input.capture;
|
||||
|
||||
|
||||
public class NullCaptureProvider extends InputCaptureProvider {
|
||||
@Override
|
||||
public void enableCapture() {}
|
||||
|
||||
@Override
|
||||
public void disableCapture() {}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
package com.limelight.binding.input;
|
||||
package com.limelight.binding.input.capture;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -18,12 +16,14 @@ import java.lang.reflect.Method;
|
||||
//
|
||||
// http://docs.nvidia.com/gameworks/index.html#technologies/mobile/game_controller_handling_mouse.htm
|
||||
|
||||
public class NvMouseHelper {
|
||||
public class ShieldCaptureProvider extends InputCaptureProvider {
|
||||
private static boolean nvExtensionSupported;
|
||||
private static Method methodSetCursorVisibility;
|
||||
private static int AXIS_RELATIVE_X;
|
||||
private static int AXIS_RELATIVE_Y;
|
||||
|
||||
private Context context;
|
||||
|
||||
static {
|
||||
try {
|
||||
methodSetCursorVisibility = InputManager.class.getMethod("setCursorVisibility", boolean.class);
|
||||
@@ -36,16 +36,19 @@ public class NvMouseHelper {
|
||||
|
||||
nvExtensionSupported = true;
|
||||
} catch (Exception e) {
|
||||
LimeLog.info("NvMouseHelper not supported");
|
||||
nvExtensionSupported = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean setCursorVisibility(Context context, boolean visible) {
|
||||
if (!nvExtensionSupported) {
|
||||
return false;
|
||||
}
|
||||
public ShieldCaptureProvider(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public static boolean isCaptureProviderSupported() {
|
||||
return nvExtensionSupported;
|
||||
}
|
||||
|
||||
private boolean setCursorVisibility(boolean visible) {
|
||||
try {
|
||||
methodSetCursorVisibility.invoke(context.getSystemService(Context.INPUT_SERVICE), visible);
|
||||
return true;
|
||||
@@ -58,19 +61,29 @@ public class NvMouseHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
||||
if (!nvExtensionSupported) {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public void enableCapture() {
|
||||
setCursorVisibility(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCapture() {
|
||||
setCursorVisibility(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
||||
return event.getAxisValue(AXIS_RELATIVE_X) != 0 ||
|
||||
event.getAxisValue(AXIS_RELATIVE_Y) != 0;
|
||||
}
|
||||
|
||||
public static float getRelativeAxisX(MotionEvent event) {
|
||||
@Override
|
||||
public float getRelativeAxisX(MotionEvent event) {
|
||||
return event.getAxisValue(AXIS_RELATIVE_X);
|
||||
}
|
||||
|
||||
public static float getRelativeAxisY(MotionEvent event) {
|
||||
@Override
|
||||
public float getRelativeAxisY(MotionEvent event) {
|
||||
return event.getAxisValue(AXIS_RELATIVE_Y);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,5 @@
|
||||
package com.limelight.binding.input.driver;
|
||||
|
||||
import android.hardware.usb.UsbEndpoint;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.binding.video.MediaCodecHelper;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public abstract class AbstractController {
|
||||
|
||||
private final int deviceId;
|
||||
|
||||
@@ -114,13 +114,13 @@ public abstract class AbstractXboxController extends AbstractController {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Report that we're added _before_ starting the input thread
|
||||
notifyDeviceAdded();
|
||||
|
||||
// Start listening for controller input
|
||||
inputThread = createInputThread();
|
||||
inputThread.start();
|
||||
|
||||
// Now report we're added
|
||||
notifyDeviceAdded();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -137,11 +137,11 @@ public abstract class AbstractXboxController extends AbstractController {
|
||||
inputThread = null;
|
||||
}
|
||||
|
||||
// Report the device removed
|
||||
notifyDeviceRemoved();
|
||||
|
||||
// Close the USB connection
|
||||
connection.close();
|
||||
|
||||
// Report the device removed
|
||||
notifyDeviceRemoved();
|
||||
}
|
||||
|
||||
protected abstract boolean handleRead(ByteBuffer buffer);
|
||||
|
||||
@@ -72,14 +72,14 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
|
||||
// Initial attachment broadcast
|
||||
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
|
||||
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
|
||||
// Continue the state machine
|
||||
handleUsbDeviceState(device);
|
||||
}
|
||||
// Subsequent permission dialog completion intent
|
||||
else if (action.equals(ACTION_USB_PERMISSION)) {
|
||||
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
|
||||
// If we got this far, we've already found we're able to handle this device
|
||||
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
|
||||
|
||||
@@ -10,56 +10,38 @@ import com.limelight.nvstream.input.ControllerPacket;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class Xbox360Controller extends AbstractXboxController {
|
||||
private static final int XB360_IFACE_SUBCLASS = 93;
|
||||
private static final int XB360_IFACE_PROTOCOL = 1; // Wired only
|
||||
|
||||
// This list is taken from the Xpad driver in the Linux kernel.
|
||||
// I've excluded the devices that aren't "controllers" in the traditional sense, but
|
||||
// if people really want to use their dancepads or fight sticks with Moonlight, I can
|
||||
// put them in.
|
||||
private static final DeviceIdTuple[] supportedDeviceTuples = {
|
||||
new DeviceIdTuple(0x045e, 0x028e, "Microsoft X-Box 360 pad"),
|
||||
new DeviceIdTuple(0x044f, 0xb326, "Thrustmaster Gamepad GP XID"),
|
||||
new DeviceIdTuple(0x046d, 0xc21d, "Logitech Gamepad F310"),
|
||||
new DeviceIdTuple(0x046d, 0xc21e, "Logitech Gamepad F510"),
|
||||
new DeviceIdTuple(0x046d, 0xc21f, "Logitech Gamepad F710"),
|
||||
new DeviceIdTuple(0x046d, 0xc242, "Logitech Chillstream Controller"),
|
||||
new DeviceIdTuple(0x0738, 0x4716, "Mad Catz Wired Xbox 360 Controller"),
|
||||
new DeviceIdTuple(0x0738, 0x4726, "Mad Catz Xbox 360 Controller"),
|
||||
new DeviceIdTuple(0x0738, 0xb726, "Mad Catz Xbox controller - MW2"),
|
||||
new DeviceIdTuple(0x0738, 0xbeef, "Mad Catz JOYTECH NEO SE Advanced GamePad"),
|
||||
new DeviceIdTuple(0x0738, 0xcb02, "Saitek Cyborg Rumble Pad - PC/Xbox 360"),
|
||||
new DeviceIdTuple(0x0738, 0xcb03, "Saitek P3200 Rumble Pad - PC/Xbox 360"),
|
||||
new DeviceIdTuple(0x0e6f, 0x0113, "Afterglow AX.1 Gamepad for Xbox 360"),
|
||||
new DeviceIdTuple(0x0e6f, 0x0201, "Pelican PL-3601 'TSZ' Wired Xbox 360 Controller"),
|
||||
new DeviceIdTuple(0x0e6f, 0x0213, "Afterglow Gamepad for Xbox 360"),
|
||||
new DeviceIdTuple(0x0e6f, 0x021f, "Rock Candy Gamepad for Xbox 360"),
|
||||
new DeviceIdTuple(0x0e6f, 0x0301, "Logic3 Controller"),
|
||||
new DeviceIdTuple(0x0e6f, 0x0401, "Logic3 Controller"),
|
||||
new DeviceIdTuple(0x12ab, 0x0301, "PDP AFTERGLOW AX.1"),
|
||||
new DeviceIdTuple(0x146b, 0x0601, "BigBen Interactive XBOX 360 Controller"),
|
||||
new DeviceIdTuple(0x1532, 0x0037, "Razer Sabertooth"),
|
||||
new DeviceIdTuple(0x15e4, 0x3f00, "Power A Mini Pro Elite"),
|
||||
new DeviceIdTuple(0x15e4, 0x3f0a, "Xbox Airflo wired controller"),
|
||||
new DeviceIdTuple(0x15e4, 0x3f10, "Batarang Xbox 360 controller"),
|
||||
new DeviceIdTuple(0x162e, 0xbeef, "Joytech Neo-Se Take2"),
|
||||
new DeviceIdTuple(0x1689, 0xfd00, "Razer Onza Tournament Edition"),
|
||||
new DeviceIdTuple(0x1689, 0xfd01, "Razer Onza Classic Edition"),
|
||||
new DeviceIdTuple(0x24c6, 0x5d04, "Razer Sabertooth"),
|
||||
new DeviceIdTuple(0x1bad, 0xf016, "Mad Catz Xbox 360 Controller"),
|
||||
new DeviceIdTuple(0x1bad, 0xf023, "MLG Pro Circuit Controller (Xbox)"),
|
||||
new DeviceIdTuple(0x1bad, 0xf900, "Harmonix Xbox 360 Controller"),
|
||||
new DeviceIdTuple(0x1bad, 0xf901, "Gamestop Xbox 360 Controller"),
|
||||
new DeviceIdTuple(0x1bad, 0xf903, "Tron Xbox 360 controller"),
|
||||
new DeviceIdTuple(0x24c6, 0x5300, "PowerA MINI PROEX Controller"),
|
||||
new DeviceIdTuple(0x24c6, 0x5303, "Xbox Airflo wired controller"),
|
||||
private static final int[] SUPPORTED_VENDORS = {
|
||||
0x044f, // Thrustmaster
|
||||
0x045e, // Microsoft
|
||||
0x046d, // Logitech
|
||||
0x0738, // Mad Catz
|
||||
0x0e6f, // Unknown
|
||||
0x12ab, // Unknown
|
||||
0x1430, // RedOctane
|
||||
0x146b, // BigBen
|
||||
0x1bad, // Harmonix
|
||||
0x0f0d, // Hori
|
||||
0x1689, // Razer Onza
|
||||
0x24c6, // PowerA
|
||||
0x1532, // Razer Sabertooth
|
||||
0x15e4, // Numark
|
||||
0x162e, // Joytech
|
||||
};
|
||||
|
||||
public static boolean canClaimDevice(UsbDevice device) {
|
||||
for (DeviceIdTuple tuple : supportedDeviceTuples) {
|
||||
if (device.getVendorId() == tuple.vid && device.getProductId() == tuple.pid) {
|
||||
LimeLog.info("XB360 can claim device: " + tuple.name);
|
||||
for (int supportedVid : SUPPORTED_VENDORS) {
|
||||
if (device.getVendorId() == supportedVid &&
|
||||
device.getInterfaceCount() >= 1 &&
|
||||
device.getInterface(0).getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||
device.getInterface(0).getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&
|
||||
device.getInterface(0).getInterfaceProtocol() == XB360_IFACE_PROTOCOL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -151,16 +133,4 @@ public class Xbox360Controller extends AbstractXboxController {
|
||||
// No need to fail init if the LED command fails
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class DeviceIdTuple {
|
||||
public final int vid;
|
||||
public final int pid;
|
||||
public final String name;
|
||||
|
||||
public DeviceIdTuple(int vid, int pid, String name) {
|
||||
this.vid = vid;
|
||||
this.pid = pid;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,22 +3,25 @@ package com.limelight.binding.input.driver;
|
||||
import android.hardware.usb.UsbConstants;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbDeviceConnection;
|
||||
import android.hardware.usb.UsbEndpoint;
|
||||
import android.hardware.usb.UsbInterface;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.binding.video.MediaCodecHelper;
|
||||
import com.limelight.nvstream.input.ControllerPacket;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public class XboxOneController extends AbstractXboxController {
|
||||
|
||||
private static final int MICROSOFT_VID = 0x045e;
|
||||
private static final int XB1_IFACE_SUBCLASS = 71;
|
||||
private static final int XB1_IFACE_PROTOCOL = 208;
|
||||
|
||||
private static final int[] SUPPORTED_VENDORS = {
|
||||
0x045e, // Microsoft
|
||||
0x0738, // Mad Catz
|
||||
0x0e6f, // Unknown
|
||||
0x0f0d, // Hori
|
||||
0x24c6, // PowerA
|
||||
};
|
||||
|
||||
// FIXME: odata_serial
|
||||
private static final byte[] XB1_INIT_DATA = {0x05, 0x20, 0x00, 0x01, 0x00};
|
||||
|
||||
@@ -59,6 +62,12 @@ public class XboxOneController extends AbstractXboxController {
|
||||
rightStickY = ~buffer.getShort() / 32767.0f;
|
||||
}
|
||||
|
||||
private void ackModeReport(byte seqNum) {
|
||||
byte[] payload = {0x01, 0x20, seqNum, 0x09, 0x00, 0x07, 0x20, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
connection.bulkTransfer(outEndpt, payload, payload.length, 3000);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean handleRead(ByteBuffer buffer) {
|
||||
switch (buffer.get())
|
||||
@@ -69,7 +78,15 @@ public class XboxOneController extends AbstractXboxController {
|
||||
return true;
|
||||
|
||||
case 0x07:
|
||||
buffer.position(buffer.position() + 3);
|
||||
// The Xbox One S controller needs acks for mode reports otherwise
|
||||
// it retransmits them forever.
|
||||
if (buffer.get() == 0x30) {
|
||||
ackModeReport(buffer.get());
|
||||
buffer.position(buffer.position() + 1);
|
||||
}
|
||||
else {
|
||||
buffer.position(buffer.position() + 2);
|
||||
}
|
||||
setButtonFlag(ControllerPacket.SPECIAL_BUTTON_FLAG, buffer.get() & 0x01);
|
||||
return true;
|
||||
}
|
||||
@@ -78,11 +95,17 @@ public class XboxOneController extends AbstractXboxController {
|
||||
}
|
||||
|
||||
public static boolean canClaimDevice(UsbDevice device) {
|
||||
return device.getVendorId() == MICROSOFT_VID &&
|
||||
device.getInterfaceCount() >= 1 &&
|
||||
device.getInterface(0).getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||
device.getInterface(0).getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
|
||||
device.getInterface(0).getInterfaceProtocol() == XB1_IFACE_PROTOCOL;
|
||||
for (int supportedVid : SUPPORTED_VENDORS) {
|
||||
if (device.getVendorId() == supportedVid &&
|
||||
device.getInterfaceCount() >= 1 &&
|
||||
device.getInterface(0).getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||
device.getInterface(0).getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
|
||||
device.getInterface(0).getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.limelight.binding.input.evdev;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.LimelightBuildProps;
|
||||
import com.limelight.binding.input.capture.InputCaptureProvider;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
@@ -13,7 +17,7 @@ import java.io.OutputStream;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
public class EvdevHandler {
|
||||
public class EvdevCaptureProvider extends InputCaptureProvider {
|
||||
|
||||
private final EvdevListener listener;
|
||||
private final String libraryPath;
|
||||
@@ -25,6 +29,7 @@ public class EvdevHandler {
|
||||
private ServerSocket servSock;
|
||||
private Socket evdevSock;
|
||||
private Activity activity;
|
||||
private boolean started = false;
|
||||
|
||||
private static final byte UNGRAB_REQUEST = 1;
|
||||
private static final byte REGRAB_REQUEST = 2;
|
||||
@@ -44,30 +49,42 @@ public class EvdevHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// Launch a su shell
|
||||
ProcessBuilder builder = new ProcessBuilder("su");
|
||||
builder.redirectErrorStream(true);
|
||||
final String evdevReaderCmd = libraryPath+File.separatorChar+"libevdev_reader.so "+servSock.getLocalPort();
|
||||
|
||||
try {
|
||||
su = builder.start();
|
||||
} catch (IOException e) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(activity, "This device is not rooted - Mouse capture is unavailable", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
e.printStackTrace();
|
||||
return;
|
||||
// On Nougat and later, we'll need to pass the command directly to SU.
|
||||
// Writing to SU's input stream after it has started doesn't seem to work anymore.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
// Launch evdev_reader directly via SU
|
||||
try {
|
||||
su = Runtime.getRuntime().exec("su -c "+evdevReaderCmd);
|
||||
} catch (IOException e) {
|
||||
reportDeviceNotRooted();
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Launch a SU shell on Marshmallow and earlier
|
||||
ProcessBuilder builder = new ProcessBuilder("su");
|
||||
builder.redirectErrorStream(true);
|
||||
|
||||
// Start evdevreader
|
||||
DataOutputStream suOut = new DataOutputStream(su.getOutputStream());
|
||||
try {
|
||||
suOut.writeChars(libraryPath+File.separatorChar+"libevdev_reader.so "+servSock.getLocalPort()+"\n");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
try {
|
||||
su = builder.start();
|
||||
} catch (IOException e) {
|
||||
reportDeviceNotRooted();
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
// Start evdevreader
|
||||
DataOutputStream suOut = new DataOutputStream(su.getOutputStream());
|
||||
try {
|
||||
suOut.writeChars(evdevReaderCmd+"\n");
|
||||
} catch (IOException e) {
|
||||
reportDeviceNotRooted();
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for evdevreader's connection
|
||||
@@ -163,75 +180,139 @@ public class EvdevHandler {
|
||||
}
|
||||
};
|
||||
|
||||
public EvdevHandler(Activity activity, EvdevListener listener) {
|
||||
public EvdevCaptureProvider(Activity activity, EvdevListener listener) {
|
||||
this.listener = listener;
|
||||
this.activity = activity;
|
||||
this.libraryPath = activity.getApplicationInfo().nativeLibraryDir;
|
||||
}
|
||||
|
||||
public void regrabAll() {
|
||||
if (!shutdown && evdevOut != null) {
|
||||
public static boolean isCaptureProviderSupported() {
|
||||
return LimelightBuildProps.ROOT_BUILD;
|
||||
}
|
||||
|
||||
private void reportDeviceNotRooted() {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(activity, "This device is not rooted - Mouse capture is unavailable", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void runInNetworkSafeContextSynchronously(Runnable runnable) {
|
||||
// This function is used to avoid Android's strict NetworkOnMainThreadException.
|
||||
// For our usage, it is highly unlikely to cause problems since we only do
|
||||
// write operations and only to localhost sockets.
|
||||
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
|
||||
Thread t = new Thread(runnable);
|
||||
t.start();
|
||||
try {
|
||||
evdevOut.write(REGRAB_REQUEST);
|
||||
} catch (IOException e) {
|
||||
t.join();
|
||||
} catch (InterruptedException e) {
|
||||
// The main thread should never be interrupted
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ungrabAll() {
|
||||
if (!shutdown && evdevOut != null) {
|
||||
try {
|
||||
evdevOut.write(UNGRAB_REQUEST);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
else {
|
||||
// Run the runnable directly
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
handlerThread.start();
|
||||
@Override
|
||||
public void enableCapture() {
|
||||
if (!started) {
|
||||
// Start the handler thread if it's our first time
|
||||
// capturing
|
||||
handlerThread.start();
|
||||
started = true;
|
||||
}
|
||||
else {
|
||||
// This may be called on the main thread
|
||||
runInNetworkSafeContextSynchronously(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Send a request to regrab if we're already capturing
|
||||
if (!shutdown && evdevOut != null) {
|
||||
try {
|
||||
evdevOut.write(REGRAB_REQUEST);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
@Override
|
||||
public void disableCapture() {
|
||||
// This may be called on the main thread
|
||||
runInNetworkSafeContextSynchronously(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (started && !shutdown && evdevOut != null) {
|
||||
try {
|
||||
evdevOut.write(UNGRAB_REQUEST);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// We need to stop the process in this context otherwise
|
||||
// we could get stuck waiting on output from the process
|
||||
// in order to terminate it.
|
||||
//
|
||||
// This may be called on the main thread.
|
||||
|
||||
if (!started) {
|
||||
return;
|
||||
}
|
||||
|
||||
shutdown = true;
|
||||
handlerThread.interrupt();
|
||||
|
||||
if (servSock != null) {
|
||||
try {
|
||||
servSock.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
runInNetworkSafeContextSynchronously(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (servSock != null) {
|
||||
try {
|
||||
servSock.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (evdevSock != null) {
|
||||
try {
|
||||
evdevSock.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (evdevSock != null) {
|
||||
try {
|
||||
evdevSock.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (evdevIn != null) {
|
||||
try {
|
||||
evdevIn.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (evdevIn != null) {
|
||||
try {
|
||||
evdevIn.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (evdevOut != null) {
|
||||
try {
|
||||
evdevOut.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
if (evdevOut != null) {
|
||||
try {
|
||||
evdevOut.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (su != null) {
|
||||
su.destroy();
|
||||
@@ -52,7 +52,7 @@ public class DigitalButton extends VirtualControllerElement {
|
||||
}
|
||||
}
|
||||
|
||||
private List<DigitalButtonListener> listeners = new ArrayList<DigitalButtonListener>();
|
||||
private List<DigitalButtonListener> listeners = new ArrayList<>();
|
||||
private String text = "";
|
||||
private int icon = -1;
|
||||
private long timerLongClickTimeout = 3000;
|
||||
@@ -190,8 +190,14 @@ public class DigitalButton extends VirtualControllerElement {
|
||||
for (DigitalButtonListener listener : listeners) {
|
||||
listener.onRelease();
|
||||
}
|
||||
timerLongClick.cancel();
|
||||
longClickTimerTask.cancel();
|
||||
|
||||
// We may be called for a release without a prior click
|
||||
if (timerLongClick != null) {
|
||||
timerLongClick.cancel();
|
||||
}
|
||||
if (longClickTimerTask != null) {
|
||||
longClickTimerTask.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,7 +20,7 @@ public class DigitalPad extends VirtualControllerElement {
|
||||
public final static int DIGITAL_PAD_DIRECTION_UP = 2;
|
||||
public final static int DIGITAL_PAD_DIRECTION_RIGHT = 4;
|
||||
public final static int DIGITAL_PAD_DIRECTION_DOWN = 8;
|
||||
List<DigitalPadListener> listeners = new ArrayList<DigitalPadListener>();
|
||||
List<DigitalPadListener> listeners = new ArrayList<>();
|
||||
|
||||
private static final int DPAD_MARGIN = 5;
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ public class VirtualController {
|
||||
private RelativeLayout.LayoutParams layoutParamsButtonConfigure = null;
|
||||
private Button buttonConfigure = null;
|
||||
|
||||
private List<VirtualControllerElement> elements = new ArrayList<VirtualControllerElement>();
|
||||
private List<VirtualControllerElement> elements = new ArrayList<>();
|
||||
|
||||
public VirtualController(final NvConnection conn, FrameLayout layout, final Context context) {
|
||||
this.connection = conn;
|
||||
@@ -60,7 +60,7 @@ public class VirtualController {
|
||||
frame_layout.addView(relative_layout);
|
||||
|
||||
buttonConfigure = new Button(context);
|
||||
buttonConfigure.setBackgroundResource(R.drawable.settings);
|
||||
buttonConfigure.setBackgroundResource(R.drawable.ic_settings);
|
||||
buttonConfigure.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.limelight.binding.video;
|
||||
|
||||
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
||||
|
||||
public abstract class EnhancedDecoderRenderer extends VideoDecoderRenderer {
|
||||
public abstract boolean isHevcSupported();
|
||||
public abstract boolean isAvcSupported();
|
||||
}
|
||||
@@ -2,17 +2,14 @@ package com.limelight.binding.video;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import org.jcodec.codecs.h264.H264Utils;
|
||||
import org.jcodec.codecs.h264.io.model.SeqParameterSet;
|
||||
import org.jcodec.codecs.h264.io.model.VUIParameters;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.nvstream.av.ByteBufferDescriptor;
|
||||
import com.limelight.nvstream.av.DecodeUnit;
|
||||
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
||||
import com.limelight.nvstream.av.video.VideoDepacketizer;
|
||||
import com.limelight.nvstream.jni.MoonBridge;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
@@ -23,35 +20,51 @@ import android.media.MediaCodec.CodecException;
|
||||
import android.os.Build;
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
|
||||
private static final boolean USE_FRAME_RENDER_TIME = false;
|
||||
|
||||
// This value is added to the PTS for each frame to force frames delivered right at
|
||||
// V-sync time to be delayed until the next V-sync rather than dropping the existing frame.
|
||||
// To make matters worse, these frames often end up being displayed for 2 V-syncs because
|
||||
// the next frame isn't ready at the next V-sync. So without this, we end up with a pattern of:
|
||||
// F0 - Displayed | F1 - Dropped | F2 - Displayed | F2 - Displayed again | F3 - Displayed ...
|
||||
// which can be very noticeable for some video content and users.
|
||||
//
|
||||
// This value is currently 6 ms, which means that a frame ready for display less than 6 ms from
|
||||
// the next V-sync will be delayed until the following V-sync to ensure smooth frame pacing.
|
||||
private static final int PTS_OFFSET_NS = 6 * 1000000;
|
||||
|
||||
// Used on versions < 5.0
|
||||
private ByteBuffer[] legacyInputBuffers;
|
||||
|
||||
private String avcDecoderName;
|
||||
private String hevcDecoderName;
|
||||
private MediaCodecInfo avcDecoder;
|
||||
private MediaCodecInfo hevcDecoder;
|
||||
|
||||
private MediaCodec videoDecoder;
|
||||
private Thread rendererThread;
|
||||
private Thread[] spinnerThreads;
|
||||
private boolean needsSpsBitstreamFixup, isExynos4;
|
||||
private VideoDepacketizer depacketizer;
|
||||
private boolean adaptivePlayback, directSubmit;
|
||||
private boolean constrainedHighProfile;
|
||||
private boolean refFrameInvalidationAvc, refFrameInvalidationHevc;
|
||||
private boolean refFrameInvalidationActive;
|
||||
private int initialWidth, initialHeight;
|
||||
private VideoFormat videoFormat;
|
||||
private int videoFormat;
|
||||
private Object renderTarget;
|
||||
private boolean stopping;
|
||||
|
||||
private boolean needsBaselineSpsHack;
|
||||
private SeqParameterSet savedSps;
|
||||
|
||||
private long lastTimestampUs;
|
||||
private long totalTimeMs;
|
||||
private long decoderTimeMs;
|
||||
private long totalTimeMs;
|
||||
private int totalFrames;
|
||||
|
||||
private int numSpsIn;
|
||||
private int numPpsIn;
|
||||
private int numVpsIn;
|
||||
private int numIframeIn;
|
||||
|
||||
private MediaCodecInfo findAvcDecoder() {
|
||||
MediaCodecInfo decoder = MediaCodecHelper.findProbableSafeDecoder("video/avc", MediaCodecInfo.CodecProfileLevel.AVCProfileHigh);
|
||||
@@ -89,22 +102,26 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
return decoderInfo;
|
||||
}
|
||||
|
||||
public void setRenderTarget(Object renderTarget) {
|
||||
this.renderTarget = renderTarget;
|
||||
}
|
||||
|
||||
public MediaCodecDecoderRenderer(int videoFormat) {
|
||||
//dumpDecoders();
|
||||
|
||||
MediaCodecInfo avcDecoder = findAvcDecoder();
|
||||
spinnerThreads = new Thread[Runtime.getRuntime().availableProcessors()];
|
||||
|
||||
avcDecoder = findAvcDecoder();
|
||||
if (avcDecoder != null) {
|
||||
avcDecoderName = avcDecoder.getName();
|
||||
LimeLog.info("Selected AVC decoder: "+avcDecoderName);
|
||||
LimeLog.info("Selected AVC decoder: "+avcDecoder.getName());
|
||||
}
|
||||
else {
|
||||
LimeLog.warning("No AVC decoder found");
|
||||
}
|
||||
|
||||
MediaCodecInfo hevcDecoder = findHevcDecoder(videoFormat);
|
||||
hevcDecoder = findHevcDecoder(videoFormat);
|
||||
if (hevcDecoder != null) {
|
||||
hevcDecoderName = hevcDecoder.getName();
|
||||
LimeLog.info("Selected HEVC decoder: "+hevcDecoderName);
|
||||
LimeLog.info("Selected HEVC decoder: "+hevcDecoder.getName());
|
||||
}
|
||||
else {
|
||||
LimeLog.info("No HEVC decoder found");
|
||||
@@ -115,28 +132,38 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
// library. The limitation of this is that we don't know whether we're using HEVC or AVC, so
|
||||
// we just assume AVC. This isn't really a problem because the capabilities are usually
|
||||
// shared between AVC and HEVC decoders on the same device.
|
||||
if (avcDecoderName != null) {
|
||||
directSubmit = MediaCodecHelper.decoderCanDirectSubmit(avcDecoderName);
|
||||
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(avcDecoderName);
|
||||
if (avcDecoder != null) {
|
||||
directSubmit = MediaCodecHelper.decoderCanDirectSubmit(avcDecoder.getName());
|
||||
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(avcDecoder.getName());
|
||||
refFrameInvalidationAvc = MediaCodecHelper.decoderSupportsRefFrameInvalidationAvc(avcDecoder.getName());
|
||||
refFrameInvalidationHevc = MediaCodecHelper.decoderSupportsRefFrameInvalidationHevc(avcDecoder.getName());
|
||||
|
||||
if (directSubmit) {
|
||||
LimeLog.info("Decoder "+avcDecoderName+" will use direct submit");
|
||||
LimeLog.info("Decoder "+avcDecoder.getName()+" will use direct submit");
|
||||
}
|
||||
if (refFrameInvalidationAvc) {
|
||||
LimeLog.info("Decoder "+avcDecoder.getName()+" will use reference frame invalidation for AVC");
|
||||
}
|
||||
if (refFrameInvalidationHevc) {
|
||||
LimeLog.info("Decoder "+avcDecoder.getName()+" will use reference frame invalidation for HEVC");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHevcSupported() {
|
||||
return hevcDecoderName != null;
|
||||
return hevcDecoder != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvcSupported() {
|
||||
return avcDecoderName != null;
|
||||
return avcDecoder != null;
|
||||
}
|
||||
|
||||
public int getActiveVideoFormat() {
|
||||
return this.videoFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setup(VideoDecoderRenderer.VideoFormat format, int width, int height, int redrawRate, Object renderTarget, int drFlags) {
|
||||
public int setup(int format, int width, int height, int redrawRate) {
|
||||
this.initialWidth = width;
|
||||
this.initialHeight = height;
|
||||
this.videoFormat = format;
|
||||
@@ -144,13 +171,13 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
String mimeType;
|
||||
String selectedDecoderName;
|
||||
|
||||
if (videoFormat == VideoFormat.H264) {
|
||||
if (videoFormat == MoonBridge.VIDEO_FORMAT_H264) {
|
||||
mimeType = "video/avc";
|
||||
selectedDecoderName = avcDecoderName;
|
||||
selectedDecoderName = avcDecoder.getName();
|
||||
|
||||
if (avcDecoderName == null) {
|
||||
if (avcDecoder == null) {
|
||||
LimeLog.severe("No available AVC decoder!");
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// These fixups only apply to H264 decoders
|
||||
@@ -170,19 +197,24 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
if (isExynos4) {
|
||||
LimeLog.info("Decoder "+selectedDecoderName+" is on Exynos 4");
|
||||
}
|
||||
}
|
||||
else if (videoFormat == VideoFormat.H265) {
|
||||
mimeType = "video/hevc";
|
||||
selectedDecoderName = hevcDecoderName;
|
||||
|
||||
if (hevcDecoderName == null) {
|
||||
refFrameInvalidationActive = refFrameInvalidationAvc;
|
||||
}
|
||||
else if (videoFormat == MoonBridge.VIDEO_FORMAT_H265) {
|
||||
mimeType = "video/hevc";
|
||||
selectedDecoderName = hevcDecoder.getName();
|
||||
|
||||
if (hevcDecoder == null) {
|
||||
LimeLog.severe("No available HEVC decoder!");
|
||||
return false;
|
||||
return -2;
|
||||
}
|
||||
|
||||
refFrameInvalidationActive = refFrameInvalidationHevc;
|
||||
}
|
||||
else {
|
||||
// Unknown format
|
||||
return false;
|
||||
LimeLog.severe("Unknown format");
|
||||
return -3;
|
||||
}
|
||||
|
||||
// Codecs have been known to throw all sorts of crazy runtime exceptions
|
||||
@@ -190,7 +222,8 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
try {
|
||||
videoDecoder = MediaCodec.createByCodecName(selectedDecoderName);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
e.printStackTrace();
|
||||
return -4;
|
||||
}
|
||||
|
||||
MediaFormat videoFormat = MediaFormat.createVideoFormat(mimeType, width, height);
|
||||
@@ -202,12 +235,46 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height);
|
||||
}
|
||||
|
||||
videoDecoder.configure(videoFormat, ((SurfaceHolder)renderTarget).getSurface(), null, 0);
|
||||
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// Operate at maximum rate to lower latency as much as possible on
|
||||
// some Qualcomm platforms. We could also set KEY_PRIORITY to 0 (realtime)
|
||||
// but that will actually result in the decoder crashing if it can't satisfy
|
||||
// our (ludicrous) operating rate requirement.
|
||||
videoFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
LimeLog.info("Using codec "+selectedDecoderName+" for hardware decoding "+mimeType);
|
||||
try {
|
||||
videoDecoder.configure(videoFormat, ((SurfaceHolder)renderTarget).getSurface(), null, 0);
|
||||
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
|
||||
|
||||
return true;
|
||||
if (USE_FRAME_RENDER_TIME && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
videoDecoder.setOnFrameRenderedListener(new MediaCodec.OnFrameRenderedListener() {
|
||||
@Override
|
||||
public void onFrameRendered(MediaCodec mediaCodec, long presentationTimeUs, long renderTimeNanos) {
|
||||
long delta = (renderTimeNanos / 1000000L) - (presentationTimeUs / 1000);
|
||||
if (delta >= 0 && delta < 1000) {
|
||||
if (USE_FRAME_RENDER_TIME) {
|
||||
totalTimeMs += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
LimeLog.info("Using codec "+selectedDecoderName+" for hardware decoding "+mimeType);
|
||||
|
||||
// Start the decoder
|
||||
videoDecoder.start();
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
legacyInputBuffers = videoDecoder.getInputBuffers();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return -5;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void handleDecoderException(Exception e, ByteBuffer buf, int codecFlags) {
|
||||
@@ -226,7 +293,8 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
}
|
||||
|
||||
// Only throw if this happens at the beginning of a stream
|
||||
if (totalFrames < 60) {
|
||||
// but not if we're stopping
|
||||
if (totalFrames > 0 && totalFrames < 20 && !stopping) {
|
||||
if (buf != null || codecFlags != 0) {
|
||||
throw new RendererException(this, e, buf, codecFlags);
|
||||
}
|
||||
@@ -236,7 +304,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
private void startDirectSubmitRendererThread()
|
||||
private void startRendererThread()
|
||||
{
|
||||
rendererThread = new Thread() {
|
||||
@Override
|
||||
@@ -259,13 +327,25 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
}
|
||||
|
||||
// Render the last buffer
|
||||
videoDecoder.releaseOutputBuffer(lastIndex, true);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// On L and later, we can fine tune our PTS right before rendering, so
|
||||
// we take advantage of that for frame pacing using the PTS offset. Using
|
||||
// this version of releaseOutputBuffer() also lets SurfaceFlinger drop extra
|
||||
// frames in the SurfaceView<->SurfaceFlinger BufferQueue that reduces
|
||||
// latency if an stray extra frame ends up in the output queue.
|
||||
videoDecoder.releaseOutputBuffer(lastIndex, System.nanoTime() + PTS_OFFSET_NS);
|
||||
}
|
||||
else {
|
||||
videoDecoder.releaseOutputBuffer(lastIndex, true);
|
||||
}
|
||||
|
||||
// Add delta time to the totals (excluding probable outliers)
|
||||
long delta = MediaCodecHelper.getMonotonicMillis() - (presentationTimeUs / 1000);
|
||||
if (delta >= 0 && delta < 1000) {
|
||||
decoderTimeMs += delta;
|
||||
totalTimeMs += delta;
|
||||
if (!USE_FRAME_RENDER_TIME) {
|
||||
totalTimeMs += delta;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (outIndex) {
|
||||
@@ -290,13 +370,44 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
rendererThread.start();
|
||||
}
|
||||
|
||||
private int dequeueInputBuffer(boolean wait, boolean infiniteWait) {
|
||||
int index;
|
||||
private void startSpinnerThreads() {
|
||||
LimeLog.info("Using "+spinnerThreads.length+" spinner threads");
|
||||
for (int i = 0; i < spinnerThreads.length; i++) {
|
||||
spinnerThreads[i] = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
// This thread exists to keep the CPU at a higher DVFS state on devices
|
||||
// where the governor scales clock speed sporadically, causing dropped frames.
|
||||
while (!isInterrupted()) {
|
||||
try {
|
||||
Thread.sleep(0, 1);
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
spinnerThreads[i].setName("Spinner-"+i);
|
||||
spinnerThreads[i].setPriority(Thread.MIN_PRIORITY);
|
||||
spinnerThreads[i].start();
|
||||
}
|
||||
}
|
||||
|
||||
private int dequeueInputBuffer() {
|
||||
int index = -1;
|
||||
long startTime, queueTime;
|
||||
|
||||
startTime = MediaCodecHelper.getMonotonicMillis();
|
||||
|
||||
index = videoDecoder.dequeueInputBuffer(wait ? (infiniteWait ? -1 : 3000) : 0);
|
||||
try {
|
||||
while (rendererThread.isAlive() && index < 0 && !stopping) {
|
||||
index = videoDecoder.dequeueInputBuffer(10000);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleDecoderException(e, null, 0);
|
||||
return MediaCodec.INFO_TRY_AGAIN_LATER;
|
||||
}
|
||||
|
||||
if (index < 0) {
|
||||
return index;
|
||||
}
|
||||
@@ -310,183 +421,39 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
return index;
|
||||
}
|
||||
|
||||
private void startLegacyRendererThread()
|
||||
{
|
||||
rendererThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
BufferInfo info = new BufferInfo();
|
||||
DecodeUnit du = null;
|
||||
int inputIndex = -1;
|
||||
long lastDuDequeueTime = 0;
|
||||
while (!isInterrupted())
|
||||
{
|
||||
// In order to get as much data to the decoder as early as possible,
|
||||
// try to submit up to 5 decode units at once without blocking.
|
||||
if (inputIndex == -1 && du == null) {
|
||||
try {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
inputIndex = dequeueInputBuffer(false, false);
|
||||
du = depacketizer.pollNextDecodeUnit();
|
||||
if (du != null) {
|
||||
lastDuDequeueTime = MediaCodecHelper.getMonotonicMillis();
|
||||
notifyDuReceived(du);
|
||||
}
|
||||
|
||||
// Stop if we can't get a DU or input buffer
|
||||
if (du == null || inputIndex == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
submitDecodeUnit(du, inputIndex);
|
||||
|
||||
du = null;
|
||||
inputIndex = -1;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
inputIndex = -1;
|
||||
handleDecoderException(e, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Grab an input buffer if we don't have one already.
|
||||
// This way we can have one ready hopefully by the time
|
||||
// the depacketizer is done with this frame. It's important
|
||||
// that this can timeout because it's possible that we could exhaust
|
||||
// the decoder's input buffers and deadlocks because aren't pulling
|
||||
// frames out of the other end.
|
||||
if (inputIndex == -1) {
|
||||
try {
|
||||
// If we've got a DU waiting to be given to the decoder,
|
||||
// wait a full 3 ms for an input buffer. Otherwise
|
||||
// just see if we can get one immediately.
|
||||
inputIndex = dequeueInputBuffer(du != null, false);
|
||||
} catch (Exception e) {
|
||||
inputIndex = -1;
|
||||
handleDecoderException(e, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Grab a decode unit if we don't have one already
|
||||
if (du == null) {
|
||||
du = depacketizer.pollNextDecodeUnit();
|
||||
if (du != null) {
|
||||
lastDuDequeueTime = MediaCodecHelper.getMonotonicMillis();
|
||||
notifyDuReceived(du);
|
||||
}
|
||||
}
|
||||
|
||||
// If we've got both a decode unit and an input buffer, we'll
|
||||
// submit now. Otherwise, we wait until we have one.
|
||||
if (du != null && inputIndex >= 0) {
|
||||
long submissionTime = MediaCodecHelper.getMonotonicMillis();
|
||||
if (submissionTime - lastDuDequeueTime >= 20) {
|
||||
LimeLog.warning("Receiving an input buffer took too long: "+(submissionTime - lastDuDequeueTime)+" ms");
|
||||
}
|
||||
|
||||
submitDecodeUnit(du, inputIndex);
|
||||
|
||||
// DU and input buffer have both been consumed
|
||||
du = null;
|
||||
inputIndex = -1;
|
||||
}
|
||||
|
||||
// Try to output a frame
|
||||
try {
|
||||
int outIndex = videoDecoder.dequeueOutputBuffer(info, 0);
|
||||
|
||||
if (outIndex >= 0) {
|
||||
long presentationTimeUs = info.presentationTimeUs;
|
||||
int lastIndex = outIndex;
|
||||
|
||||
// Get the last output buffer in the queue
|
||||
while ((outIndex = videoDecoder.dequeueOutputBuffer(info, 0)) >= 0) {
|
||||
videoDecoder.releaseOutputBuffer(lastIndex, false);
|
||||
lastIndex = outIndex;
|
||||
presentationTimeUs = info.presentationTimeUs;
|
||||
}
|
||||
|
||||
// Render the last buffer
|
||||
videoDecoder.releaseOutputBuffer(lastIndex, true);
|
||||
|
||||
// Add delta time to the totals (excluding probable outliers)
|
||||
long delta = MediaCodecHelper.getMonotonicMillis()-(presentationTimeUs/1000);
|
||||
if (delta >= 0 && delta < 1000) {
|
||||
decoderTimeMs += delta;
|
||||
totalTimeMs += delta;
|
||||
}
|
||||
} else {
|
||||
switch (outIndex) {
|
||||
case MediaCodec.INFO_TRY_AGAIN_LATER:
|
||||
// Getting an input buffer may already block
|
||||
// so don't park if we still need to do that
|
||||
if (inputIndex >= 0) {
|
||||
LockSupport.parkNanos(1);
|
||||
}
|
||||
break;
|
||||
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
|
||||
LimeLog.info("Output format changed");
|
||||
LimeLog.info("New output Format: " + videoDecoder.getOutputFormat());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleDecoderException(e, null, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
rendererThread.setName("Video - Renderer (MediaCodec)");
|
||||
rendererThread.setPriority(Thread.MAX_PRIORITY);
|
||||
rendererThread.start();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public boolean start(VideoDepacketizer depacketizer) {
|
||||
this.depacketizer = depacketizer;
|
||||
|
||||
// Start the decoder
|
||||
videoDecoder.start();
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
legacyInputBuffers = videoDecoder.getInputBuffers();
|
||||
}
|
||||
|
||||
if (directSubmit) {
|
||||
startDirectSubmitRendererThread();
|
||||
}
|
||||
else {
|
||||
startLegacyRendererThread();
|
||||
}
|
||||
|
||||
return true;
|
||||
public void start() {
|
||||
startRendererThread();
|
||||
startSpinnerThreads();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (rendererThread != null) {
|
||||
// Halt the rendering thread
|
||||
rendererThread.interrupt();
|
||||
stopping = true;
|
||||
|
||||
// Halt the rendering thread
|
||||
rendererThread.interrupt();
|
||||
try {
|
||||
rendererThread.join();
|
||||
} catch (InterruptedException ignored) { }
|
||||
|
||||
// Halt the spinner threads
|
||||
for (Thread t : spinnerThreads) {
|
||||
t.interrupt();
|
||||
}
|
||||
for (Thread t : spinnerThreads) {
|
||||
try {
|
||||
rendererThread.join();
|
||||
t.join();
|
||||
} catch (InterruptedException ignored) { }
|
||||
}
|
||||
|
||||
// Stop the decoder
|
||||
videoDecoder.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
if (videoDecoder != null) {
|
||||
videoDecoder.release();
|
||||
}
|
||||
public void cleanup() {
|
||||
videoDecoder.release();
|
||||
}
|
||||
|
||||
private void queueInputBuffer(int inputBufferIndex, int offset, int length, long timestampUs, int codecFlags) {
|
||||
private boolean queueInputBuffer(int inputBufferIndex, int offset, int length, long timestampUs, int codecFlags) {
|
||||
// Try 25 times to submit the input buffer before throwing a real exception
|
||||
int i;
|
||||
Exception lastException = null;
|
||||
@@ -503,9 +470,17 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
if (i == 25) {
|
||||
if (i == 25 && totalFrames > 0 && totalFrames < 20 && !stopping) {
|
||||
throw new RendererException(this, lastException, null, codecFlags);
|
||||
}
|
||||
else if (i != 25) {
|
||||
// Queued input buffer
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// Failed to queue
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Using the new getInputBuffer() API on Lollipop allows
|
||||
@@ -544,7 +519,10 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void submitDecodeUnit(DecodeUnit decodeUnit, int inputBufferIndex) {
|
||||
@Override
|
||||
public int submitDecodeUnit(byte[] frameData, int frameLength) {
|
||||
totalFrames++;
|
||||
|
||||
long timestampUs = System.nanoTime() / 1000;
|
||||
if (timestampUs <= lastTimestampUs) {
|
||||
// We can't submit multiple buffers with the same timestamp
|
||||
@@ -553,39 +531,36 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
}
|
||||
lastTimestampUs = timestampUs;
|
||||
|
||||
int inputBufferIndex = dequeueInputBuffer();
|
||||
if (inputBufferIndex < 0) {
|
||||
// We're being torn down now
|
||||
return MoonBridge.DR_OK;
|
||||
}
|
||||
|
||||
ByteBuffer buf = getEmptyInputBuffer(inputBufferIndex);
|
||||
|
||||
int codecFlags = 0;
|
||||
int decodeUnitFlags = decodeUnit.getFlags();
|
||||
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0) {
|
||||
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
||||
}
|
||||
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_SYNC_FRAME) != 0) {
|
||||
codecFlags |= MediaCodec.BUFFER_FLAG_SYNC_FRAME;
|
||||
numIframeIn++;
|
||||
}
|
||||
|
||||
boolean needsSpsReplay = false;
|
||||
|
||||
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0) {
|
||||
ByteBufferDescriptor header = decodeUnit.getBufferHead();
|
||||
// H264 SPS
|
||||
if (frameData[4] == 0x67) {
|
||||
numSpsIn++;
|
||||
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
||||
|
||||
// H264 SPS
|
||||
if (header.data[header.offset+4] == 0x67) {
|
||||
numSpsIn++;
|
||||
ByteBuffer spsBuf = ByteBuffer.wrap(frameData);
|
||||
|
||||
ByteBuffer spsBuf = ByteBuffer.wrap(header.data);
|
||||
// Skip to the start of the NALU data
|
||||
spsBuf.position(5);
|
||||
|
||||
// Skip to the start of the NALU data
|
||||
spsBuf.position(header.offset+5);
|
||||
// The H264Utils.readSPS function safely handles
|
||||
// Annex B NALUs (including NALUs with escape sequences)
|
||||
SeqParameterSet sps = H264Utils.readSPS(spsBuf);
|
||||
|
||||
// The H264Utils.readSPS function safely handles
|
||||
// Annex B NALUs (including NALUs with escape sequences)
|
||||
SeqParameterSet sps = H264Utils.readSPS(spsBuf);
|
||||
|
||||
// Some decoders rely on H264 level to decide how many buffers are needed
|
||||
// Since we only need one frame buffered, we'll set the level as low as we can
|
||||
// for known resolution combinations
|
||||
// Some decoders rely on H264 level to decide how many buffers are needed
|
||||
// Since we only need one frame buffered, we'll set the level as low as we can
|
||||
// for known resolution combinations. Reference frame invalidation may need
|
||||
// these, so leave them be for those decoders.
|
||||
if (!refFrameInvalidationActive) {
|
||||
if (initialWidth == 1280 && initialHeight == 720) {
|
||||
// Max 5 buffered frames at 1280x720x60
|
||||
LimeLog.info("Patching level_idc to 32");
|
||||
@@ -599,123 +574,142 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
else {
|
||||
// Leave the profile alone (currently 5.0)
|
||||
}
|
||||
}
|
||||
|
||||
// TI OMAP4 requires a reference frame count of 1 to decode successfully. Exynos 4
|
||||
// also requires this fixup.
|
||||
//
|
||||
// I'm doing this fixup for all devices because I haven't seen any devices that
|
||||
// this causes issues for. At worst, it seems to do nothing and at best it fixes
|
||||
// issues with video lag, hangs, and crashes.
|
||||
// TI OMAP4 requires a reference frame count of 1 to decode successfully. Exynos 4
|
||||
// also requires this fixup.
|
||||
//
|
||||
// I'm doing this fixup for all devices because I haven't seen any devices that
|
||||
// this causes issues for. At worst, it seems to do nothing and at best it fixes
|
||||
// issues with video lag, hangs, and crashes.
|
||||
//
|
||||
// It does break reference frame invalidation, so we will not do that for decoders
|
||||
// where we've enabled reference frame invalidation.
|
||||
if (!refFrameInvalidationActive) {
|
||||
LimeLog.info("Patching num_ref_frames in SPS");
|
||||
sps.num_ref_frames = 1;
|
||||
}
|
||||
|
||||
// GFE 2.5.11 changed the SPS to add additional extensions
|
||||
// Some devices don't like these so we remove them here.
|
||||
sps.vuiParams.video_signal_type_present_flag = false;
|
||||
sps.vuiParams.colour_description_present_flag = false;
|
||||
sps.vuiParams.chroma_loc_info_present_flag = false;
|
||||
// GFE 2.5.11 changed the SPS to add additional extensions
|
||||
// Some devices don't like these so we remove them here.
|
||||
sps.vuiParams.video_signal_type_present_flag = false;
|
||||
sps.vuiParams.colour_description_present_flag = false;
|
||||
sps.vuiParams.chroma_loc_info_present_flag = false;
|
||||
|
||||
if (needsSpsBitstreamFixup || isExynos4) {
|
||||
// The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag
|
||||
// or max_dec_frame_buffering which increases decoding latency on Tegra.
|
||||
if ((needsSpsBitstreamFixup || isExynos4) && !refFrameInvalidationActive) {
|
||||
// The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag
|
||||
// or max_dec_frame_buffering which increases decoding latency on Tegra.
|
||||
|
||||
// GFE 2.5.11 started sending bitstream restrictions
|
||||
if (sps.vuiParams.bitstreamRestriction == null) {
|
||||
LimeLog.info("Adding bitstream restrictions");
|
||||
sps.vuiParams.bitstreamRestriction = new VUIParameters.BitstreamRestriction();
|
||||
sps.vuiParams.bitstreamRestriction.motion_vectors_over_pic_boundaries_flag = true;
|
||||
sps.vuiParams.bitstreamRestriction.log2_max_mv_length_horizontal = 16;
|
||||
sps.vuiParams.bitstreamRestriction.log2_max_mv_length_vertical = 16;
|
||||
sps.vuiParams.bitstreamRestriction.num_reorder_frames = 0;
|
||||
}
|
||||
else {
|
||||
LimeLog.info("Patching bitstream restrictions");
|
||||
}
|
||||
|
||||
// Some devices throw errors if max_dec_frame_buffering < num_ref_frames
|
||||
sps.vuiParams.bitstreamRestriction.max_dec_frame_buffering = sps.num_ref_frames;
|
||||
|
||||
// These values are the defaults for the fields, but they are more aggressive
|
||||
// than what GFE sends in 2.5.11, but it doesn't seem to cause picture problems.
|
||||
sps.vuiParams.bitstreamRestriction.max_bytes_per_pic_denom = 2;
|
||||
sps.vuiParams.bitstreamRestriction.max_bits_per_mb_denom = 1;
|
||||
|
||||
// log2_max_mv_length_horizontal and log2_max_mv_length_vertical are set to more
|
||||
// conservative values by GFE 2.5.11. We'll let those values stand.
|
||||
// GFE 2.5.11 started sending bitstream restrictions
|
||||
if (sps.vuiParams.bitstreamRestriction == null) {
|
||||
LimeLog.info("Adding bitstream restrictions");
|
||||
sps.vuiParams.bitstreamRestriction = new VUIParameters.BitstreamRestriction();
|
||||
sps.vuiParams.bitstreamRestriction.motion_vectors_over_pic_boundaries_flag = true;
|
||||
sps.vuiParams.bitstreamRestriction.log2_max_mv_length_horizontal = 16;
|
||||
sps.vuiParams.bitstreamRestriction.log2_max_mv_length_vertical = 16;
|
||||
sps.vuiParams.bitstreamRestriction.num_reorder_frames = 0;
|
||||
}
|
||||
else {
|
||||
// Devices that didn't/couldn't get bitstream restrictions before GFE 2.5.11
|
||||
// will continue to not receive them now
|
||||
sps.vuiParams.bitstreamRestriction = null;
|
||||
LimeLog.info("Patching bitstream restrictions");
|
||||
}
|
||||
|
||||
// If we need to hack this SPS to say we're baseline, do so now
|
||||
if (needsBaselineSpsHack) {
|
||||
LimeLog.info("Hacking SPS to baseline");
|
||||
sps.profile_idc = 66;
|
||||
savedSps = sps;
|
||||
}
|
||||
// Some devices throw errors if max_dec_frame_buffering < num_ref_frames
|
||||
sps.vuiParams.bitstreamRestriction.max_dec_frame_buffering = sps.num_ref_frames;
|
||||
|
||||
// Patch the SPS constraint flags
|
||||
doProfileSpecificSpsPatching(sps);
|
||||
// These values are the defaults for the fields, but they are more aggressive
|
||||
// than what GFE sends in 2.5.11, but it doesn't seem to cause picture problems.
|
||||
sps.vuiParams.bitstreamRestriction.max_bytes_per_pic_denom = 2;
|
||||
sps.vuiParams.bitstreamRestriction.max_bits_per_mb_denom = 1;
|
||||
|
||||
// Write the annex B header
|
||||
buf.put(header.data, header.offset, 5);
|
||||
|
||||
// The H264Utils.writeSPS function safely handles
|
||||
// Annex B NALUs (including NALUs with escape sequences)
|
||||
ByteBuffer escapedNalu = H264Utils.writeSPS(sps, header.length);
|
||||
buf.put(escapedNalu);
|
||||
|
||||
queueInputBuffer(inputBufferIndex,
|
||||
0, buf.position(),
|
||||
timestampUs, codecFlags);
|
||||
|
||||
depacketizer.freeDecodeUnit(decodeUnit);
|
||||
return;
|
||||
|
||||
// H264 PPS
|
||||
} else if (header.data[header.offset+4] == 0x68) {
|
||||
numPpsIn++;
|
||||
|
||||
if (needsBaselineSpsHack) {
|
||||
LimeLog.info("Saw PPS; disabling SPS hack");
|
||||
needsBaselineSpsHack = false;
|
||||
|
||||
// Give the decoder the SPS again with the proper profile now
|
||||
needsSpsReplay = true;
|
||||
}
|
||||
// log2_max_mv_length_horizontal and log2_max_mv_length_vertical are set to more
|
||||
// conservative values by GFE 2.5.11. We'll let those values stand.
|
||||
}
|
||||
else if (header.data[header.offset+4] == 0x40) {
|
||||
numVpsIn++;
|
||||
else {
|
||||
// Devices that didn't/couldn't get bitstream restrictions before GFE 2.5.11
|
||||
// will continue to not receive them now
|
||||
sps.vuiParams.bitstreamRestriction = null;
|
||||
}
|
||||
else if (header.data[header.offset+4] == 0x42) {
|
||||
numSpsIn++;
|
||||
|
||||
// If we need to hack this SPS to say we're baseline, do so now
|
||||
if (needsBaselineSpsHack) {
|
||||
LimeLog.info("Hacking SPS to baseline");
|
||||
sps.profile_idc = 66;
|
||||
savedSps = sps;
|
||||
}
|
||||
else if (header.data[header.offset+4] == 0x44) {
|
||||
numPpsIn++;
|
||||
|
||||
// Patch the SPS constraint flags
|
||||
doProfileSpecificSpsPatching(sps);
|
||||
|
||||
// Write the annex B header
|
||||
buf.put(frameData, 0, 5);
|
||||
|
||||
// The H264Utils.writeSPS function safely handles
|
||||
// Annex B NALUs (including NALUs with escape sequences)
|
||||
ByteBuffer escapedNalu = H264Utils.writeSPS(sps, frameLength);
|
||||
buf.put(escapedNalu);
|
||||
|
||||
if (queueInputBuffer(inputBufferIndex,
|
||||
0, buf.position(),
|
||||
timestampUs, codecFlags)) {
|
||||
return MoonBridge.DR_OK;
|
||||
}
|
||||
else {
|
||||
return MoonBridge.DR_NEED_IDR;
|
||||
}
|
||||
|
||||
// H264 PPS
|
||||
} else if (frameData[4] == 0x68) {
|
||||
numPpsIn++;
|
||||
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
||||
|
||||
|
||||
if (needsBaselineSpsHack) {
|
||||
LimeLog.info("Saw PPS; disabling SPS hack");
|
||||
needsBaselineSpsHack = false;
|
||||
|
||||
// Give the decoder the SPS again with the proper profile now
|
||||
needsSpsReplay = true;
|
||||
}
|
||||
}
|
||||
else if (frameData[4] == 0x40) {
|
||||
numVpsIn++;
|
||||
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
||||
}
|
||||
else if (frameData[4] == 0x42) {
|
||||
numSpsIn++;
|
||||
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
||||
}
|
||||
else if (frameData[4] == 0x44) {
|
||||
numPpsIn++;
|
||||
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
||||
}
|
||||
|
||||
// Copy data from our buffer list into the input buffer
|
||||
for (ByteBufferDescriptor desc = decodeUnit.getBufferHead();
|
||||
desc != null; desc = desc.nextDescriptor) {
|
||||
buf.put(desc.data, desc.offset, desc.length);
|
||||
buf.put(frameData, 0, frameLength);
|
||||
|
||||
if (!queueInputBuffer(inputBufferIndex,
|
||||
0, frameLength,
|
||||
timestampUs, codecFlags)) {
|
||||
return MoonBridge.DR_NEED_IDR;
|
||||
}
|
||||
|
||||
queueInputBuffer(inputBufferIndex,
|
||||
0, decodeUnit.getDataLength(),
|
||||
timestampUs, codecFlags);
|
||||
|
||||
depacketizer.freeDecodeUnit(decodeUnit);
|
||||
|
||||
if (needsSpsReplay) {
|
||||
replaySps();
|
||||
if (!replaySps()) {
|
||||
return MoonBridge.DR_NEED_IDR;
|
||||
}
|
||||
|
||||
LimeLog.info("SPS replay complete");
|
||||
}
|
||||
|
||||
return MoonBridge.DR_OK;
|
||||
}
|
||||
|
||||
private void replaySps() {
|
||||
int inputIndex = dequeueInputBuffer(true, true);
|
||||
private boolean replaySps() {
|
||||
int inputIndex = dequeueInputBuffer();
|
||||
if (inputIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ByteBuffer inputBuffer = getEmptyInputBuffer(inputIndex);
|
||||
|
||||
// Write the Annex B header
|
||||
@@ -736,36 +730,35 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
savedSps = null;
|
||||
|
||||
// Queue the new SPS
|
||||
queueInputBuffer(inputIndex,
|
||||
return queueInputBuffer(inputIndex,
|
||||
0, inputBuffer.position(),
|
||||
System.nanoTime() / 1000,
|
||||
MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
|
||||
|
||||
LimeLog.info("SPS replay complete");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCapabilities() {
|
||||
int caps = 0;
|
||||
int capabilities = 0;
|
||||
|
||||
caps |= adaptivePlayback ?
|
||||
VideoDecoderRenderer.CAPABILITY_ADAPTIVE_RESOLUTION : 0;
|
||||
// We always request 4 slices per frame to speed up decoding on some hardware
|
||||
capabilities |= MoonBridge.CAPABILITY_SLICES_PER_FRAME((byte) 4);
|
||||
|
||||
caps |= directSubmit ?
|
||||
VideoDecoderRenderer.CAPABILITY_DIRECT_SUBMIT : 0;
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAverageDecoderLatency() {
|
||||
if (totalFrames == 0) {
|
||||
return 0;
|
||||
// Enable reference frame invalidation on supported hardware
|
||||
if (refFrameInvalidationAvc) {
|
||||
capabilities |= MoonBridge.CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC;
|
||||
}
|
||||
return (int)(decoderTimeMs / totalFrames);
|
||||
if (refFrameInvalidationHevc) {
|
||||
capabilities |= MoonBridge.CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC;
|
||||
}
|
||||
|
||||
// Enable direct submit on supported hardware
|
||||
if (directSubmit) {
|
||||
capabilities |= MoonBridge.CAPABILITY_DIRECT_SUBMIT;
|
||||
}
|
||||
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAverageEndToEndLatency() {
|
||||
if (totalFrames == 0) {
|
||||
return 0;
|
||||
@@ -773,33 +766,11 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
return (int)(totalTimeMs / totalFrames);
|
||||
}
|
||||
|
||||
private void notifyDuReceived(DecodeUnit du) {
|
||||
long currentTime = MediaCodecHelper.getMonotonicMillis();
|
||||
long delta = currentTime-du.getReceiveTimestamp();
|
||||
if (delta >= 0 && delta < 1000) {
|
||||
totalTimeMs += currentTime-du.getReceiveTimestamp();
|
||||
totalFrames++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void directSubmitDecodeUnit(DecodeUnit du) {
|
||||
int inputIndex;
|
||||
|
||||
notifyDuReceived(du);
|
||||
|
||||
for (;;) {
|
||||
try {
|
||||
inputIndex = dequeueInputBuffer(true, true);
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
handleDecoderException(e, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (inputIndex >= 0) {
|
||||
submitDecodeUnit(du, inputIndex);
|
||||
public int getAverageDecoderLatency() {
|
||||
if (totalFrames == 0) {
|
||||
return 0;
|
||||
}
|
||||
return (int)(decoderTimeMs / totalFrames);
|
||||
}
|
||||
|
||||
public class RendererException extends RuntimeException {
|
||||
@@ -826,10 +797,10 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
String str = "";
|
||||
|
||||
str += "Format: "+renderer.videoFormat+"\n";
|
||||
str += "AVC Decoder: "+renderer.avcDecoderName+"\n";
|
||||
str += "HEVC Decoder: "+renderer.hevcDecoderName+"\n";
|
||||
str += "AVC Decoder: "+((renderer.avcDecoder != null) ? renderer.avcDecoder.getName():"(none)")+"\n";
|
||||
str += "HEVC Decoder: "+((renderer.hevcDecoder != null) ? renderer.hevcDecoder.getName():"(none)")+"\n";
|
||||
str += "Initial video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
|
||||
str += "In stats: "+renderer.numVpsIn+", "+renderer.numSpsIn+", "+renderer.numPpsIn+", "+renderer.numIframeIn+"\n";
|
||||
str += "In stats: "+renderer.numVpsIn+", "+renderer.numSpsIn+", "+renderer.numPpsIn+"\n";
|
||||
str += "Total frames: "+renderer.totalFrames+"\n";
|
||||
str += "Average end-to-end client latency: "+getAverageEndToEndLatency()+"ms\n";
|
||||
str += "Average hardware decoder latency: "+getAverageDecoderLatency()+"ms\n";
|
||||
|
||||
@@ -32,9 +32,11 @@ public class MediaCodecHelper {
|
||||
private static final List<String> directSubmitPrefixes;
|
||||
private static final List<String> constrainedHighProfilePrefixes;
|
||||
private static final List<String> whitelistedHevcDecoders;
|
||||
private static final List<String> refFrameInvalidationAvcPrefixes;
|
||||
private static final List<String> refFrameInvalidationHevcPrefixes;
|
||||
|
||||
static {
|
||||
directSubmitPrefixes = new LinkedList<String>();
|
||||
directSubmitPrefixes = new LinkedList<>();
|
||||
|
||||
// These decoders have low enough input buffer latency that they
|
||||
// can be directly invoked from the receive thread
|
||||
@@ -45,14 +47,22 @@ public class MediaCodecHelper {
|
||||
directSubmitPrefixes.add("omx.brcm");
|
||||
directSubmitPrefixes.add("omx.TI");
|
||||
directSubmitPrefixes.add("omx.arc");
|
||||
directSubmitPrefixes.add("omx.nvidia");
|
||||
}
|
||||
|
||||
static {
|
||||
refFrameInvalidationAvcPrefixes = new LinkedList<>();
|
||||
refFrameInvalidationHevcPrefixes = new LinkedList<>();
|
||||
|
||||
// Qualcomm and NVIDIA may be added at runtime
|
||||
}
|
||||
|
||||
static {
|
||||
preferredDecoders = new LinkedList<String>();
|
||||
preferredDecoders = new LinkedList<>();
|
||||
}
|
||||
|
||||
static {
|
||||
blacklistedDecoderPrefixes = new LinkedList<String>();
|
||||
blacklistedDecoderPrefixes = new LinkedList<>();
|
||||
|
||||
// Software decoders that don't support H264 high profile
|
||||
blacklistedDecoderPrefixes.add("omx.google");
|
||||
@@ -65,21 +75,23 @@ public class MediaCodecHelper {
|
||||
}
|
||||
|
||||
static {
|
||||
spsFixupBitstreamFixupDecoderPrefixes = new LinkedList<String>();
|
||||
// If a decoder qualifies for reference frame invalidation,
|
||||
// these entries will be ignored for those decoders.
|
||||
spsFixupBitstreamFixupDecoderPrefixes = new LinkedList<>();
|
||||
spsFixupBitstreamFixupDecoderPrefixes.add("omx.nvidia");
|
||||
spsFixupBitstreamFixupDecoderPrefixes.add("omx.qcom");
|
||||
spsFixupBitstreamFixupDecoderPrefixes.add("omx.brcm");
|
||||
|
||||
baselineProfileHackPrefixes = new LinkedList<String>();
|
||||
baselineProfileHackPrefixes = new LinkedList<>();
|
||||
baselineProfileHackPrefixes.add("omx.intel");
|
||||
|
||||
whitelistedAdaptiveResolutionPrefixes = new LinkedList<String>();
|
||||
whitelistedAdaptiveResolutionPrefixes = new LinkedList<>();
|
||||
whitelistedAdaptiveResolutionPrefixes.add("omx.nvidia");
|
||||
whitelistedAdaptiveResolutionPrefixes.add("omx.qcom");
|
||||
whitelistedAdaptiveResolutionPrefixes.add("omx.sec");
|
||||
whitelistedAdaptiveResolutionPrefixes.add("omx.TI");
|
||||
|
||||
constrainedHighProfilePrefixes = new LinkedList<String>();
|
||||
constrainedHighProfilePrefixes = new LinkedList<>();
|
||||
constrainedHighProfilePrefixes.add("omx.intel");
|
||||
}
|
||||
|
||||
@@ -113,20 +125,40 @@ public class MediaCodecHelper {
|
||||
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
ConfigurationInfo configInfo = activityManager.getDeviceConfigurationInfo();
|
||||
if (configInfo.reqGlEsVersion != ConfigurationInfo.GL_ES_VERSION_UNDEFINED) {
|
||||
// Qualcomm's early HEVC decoders break hard on our HEVC stream. The best check to
|
||||
// tell the good from the bad decoders are the generation of Adreno GPU included:
|
||||
// 3xx - bad
|
||||
// 4xx - good
|
||||
//
|
||||
// Unfortunately, it's not that easy to get that information here, so I'll use an
|
||||
// approximation by checking the GLES level (<= 3.0 is bad).
|
||||
LimeLog.info("OpenGL ES version: "+configInfo.reqGlEsVersion);
|
||||
if (configInfo.reqGlEsVersion > 0x30000) {
|
||||
LimeLog.info("Added omx.qcom to supported decoders based on GLES 3.1+ support");
|
||||
whitelistedHevcDecoders.add("omx.qcom");
|
||||
|
||||
// Tegra K1 and later can do reference frame invalidation properly
|
||||
if (configInfo.reqGlEsVersion >= 0x30000) {
|
||||
LimeLog.info("Added omx.nvidia to AVC reference frame invalidation support list");
|
||||
refFrameInvalidationAvcPrefixes.add("omx.nvidia");
|
||||
|
||||
LimeLog.info("Added omx.qcom to AVC reference frame invalidation support list");
|
||||
refFrameInvalidationAvcPrefixes.add("omx.qcom");
|
||||
|
||||
// Prior to M, we were tricking the decoder into using baseline profile, which
|
||||
// won't support RFI properly.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
LimeLog.info("Added omx.intel to AVC reference frame invalidation support list");
|
||||
refFrameInvalidationAvcPrefixes.add("omx.intel");
|
||||
}
|
||||
|
||||
// Qualcomm's early HEVC decoders break hard on our HEVC stream. The best check to
|
||||
// tell the good from the bad decoders are the generation of Adreno GPU included:
|
||||
// 3xx - bad
|
||||
// 4xx - good
|
||||
//
|
||||
// Unfortunately, it's not that easy to get that information here, so I'll use an
|
||||
// approximation by checking the GLES level (<= 3.0 is bad).
|
||||
if (configInfo.reqGlEsVersion > 0x30000) {
|
||||
// FIXME: We prefer reference frame invalidation support (which is only doable on AVC on
|
||||
// older Qualcomm chips) vs. enabling HEVC by default. The user can override using the settings
|
||||
// to force HEVC on.
|
||||
//LimeLog.info("Added omx.qcom to supported HEVC decoders based on GLES 3.1+ support");
|
||||
//whitelistedHevcDecoders.add("omx.qcom");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDecoderInList(List<String> decoderList, String decoderName) {
|
||||
for (String badPrefix : decoderList) {
|
||||
@@ -190,6 +222,14 @@ public class MediaCodecHelper {
|
||||
return isDecoderInList(baselineProfileHackPrefixes, decoderName);
|
||||
}
|
||||
|
||||
public static boolean decoderSupportsRefFrameInvalidationAvc(String decoderName) {
|
||||
return isDecoderInList(refFrameInvalidationAvcPrefixes, decoderName);
|
||||
}
|
||||
|
||||
public static boolean decoderSupportsRefFrameInvalidationHevc(String decoderName) {
|
||||
return isDecoderInList(refFrameInvalidationHevcPrefixes, decoderName);
|
||||
}
|
||||
|
||||
public static boolean decoderIsWhitelistedForHevc(String decoderName) {
|
||||
// TODO: Shield Tablet K1/LTE?
|
||||
//
|
||||
@@ -212,13 +252,23 @@ public class MediaCodecHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Software decoders are terrible and we never want to use them.
|
||||
// We want to catch decoders like:
|
||||
// OMX.qcom.video.decoder.hevcswvdec
|
||||
// OMX.SEC.hevc.sw.dec
|
||||
//
|
||||
if (decoderName.contains("sw")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isDecoderInList(whitelistedHevcDecoders, decoderName);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@SuppressLint("NewApi")
|
||||
private static LinkedList<MediaCodecInfo> getMediaCodecList() {
|
||||
LinkedList<MediaCodecInfo> infoList = new LinkedList<MediaCodecInfo>();
|
||||
LinkedList<MediaCodecInfo> infoList = new LinkedList<>();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
|
||||
@@ -376,7 +426,7 @@ public class MediaCodecHelper {
|
||||
break;
|
||||
cpuInfo.append((char)ch);
|
||||
}
|
||||
|
||||
|
||||
return cpuInfo.toString();
|
||||
} finally {
|
||||
br.close();
|
||||
|
||||
@@ -68,7 +68,7 @@ public class ComputerDatabaseManager {
|
||||
|
||||
public List<ComputerDetails> getAllComputers() {
|
||||
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null);
|
||||
LinkedList<ComputerDetails> computerList = new LinkedList<ComputerDetails>();
|
||||
LinkedList<ComputerDetails> computerList = new LinkedList<>();
|
||||
while (c.moveToNext()) {
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
|
||||
|
||||
@@ -37,7 +37,9 @@ public class ComputerManagerService extends Service {
|
||||
private static final int MDNS_QUERY_PERIOD_MS = 1000;
|
||||
private static final int FAST_POLL_TIMEOUT = 500;
|
||||
private static final int OFFLINE_POLL_TRIES = 5;
|
||||
private static final int INITIAL_POLL_TRIES = 2;
|
||||
private static final int EMPTY_LIST_THRESHOLD = 3;
|
||||
private static final int POLL_DATA_TTL_MS = 30000;
|
||||
|
||||
private final ComputerManagerBinder binder = new ComputerManagerBinder();
|
||||
|
||||
@@ -45,7 +47,7 @@ public class ComputerManagerService extends Service {
|
||||
private final AtomicInteger dbRefCount = new AtomicInteger(0);
|
||||
|
||||
private IdentityManager idManager;
|
||||
private final LinkedList<PollingTuple> pollingTuples = new LinkedList<PollingTuple>();
|
||||
private final LinkedList<PollingTuple> pollingTuples = new LinkedList<>();
|
||||
private ComputerManagerListener listener = null;
|
||||
private final AtomicInteger activePolls = new AtomicInteger(0);
|
||||
private boolean pollingActive = false;
|
||||
@@ -76,12 +78,15 @@ public class ComputerManagerService extends Service {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int pollTriesBeforeOffline = details.state == ComputerDetails.State.UNKNOWN ?
|
||||
INITIAL_POLL_TRIES : OFFLINE_POLL_TRIES;
|
||||
|
||||
activePolls.incrementAndGet();
|
||||
|
||||
// Poll the machine
|
||||
try {
|
||||
if (!pollComputer(details)) {
|
||||
if (!newPc && offlineCount < OFFLINE_POLL_TRIES) {
|
||||
if (!newPc && offlineCount < pollTriesBeforeOffline) {
|
||||
// Return without calling the listener
|
||||
releaseLocalDatabaseReference();
|
||||
return false;
|
||||
@@ -136,6 +141,7 @@ public class ComputerManagerService extends Service {
|
||||
LimeLog.warning(tuple.computer.name + " is offline (try " + offlineCount + ")");
|
||||
offlineCount++;
|
||||
} else {
|
||||
tuple.lastSuccessfulPollMs = System.currentTimeMillis();
|
||||
offlineCount = 0;
|
||||
}
|
||||
}
|
||||
@@ -165,11 +171,18 @@ public class ComputerManagerService extends Service {
|
||||
|
||||
synchronized (pollingTuples) {
|
||||
for (PollingTuple tuple : pollingTuples) {
|
||||
// Enforce the poll data TTL
|
||||
if (System.currentTimeMillis() - tuple.lastSuccessfulPollMs > POLL_DATA_TTL_MS) {
|
||||
LimeLog.info("Timing out polled state for "+tuple.computer.name);
|
||||
tuple.computer.state = ComputerDetails.State.UNKNOWN;
|
||||
tuple.computer.reachability = ComputerDetails.Reachability.UNKNOWN;
|
||||
}
|
||||
|
||||
// Report this computer initially
|
||||
listener.notifyComputerUpdated(tuple.computer);
|
||||
|
||||
// This polling thread might already be there
|
||||
if (tuple.thread == null) {
|
||||
// Report this computer initially
|
||||
listener.notifyComputerUpdated(tuple.computer);
|
||||
|
||||
tuple.thread = createPollingThread(tuple);
|
||||
tuple.thread.start();
|
||||
}
|
||||
@@ -229,6 +242,21 @@ public class ComputerManagerService extends Service {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void invalidateStateForComputer(UUID uuid) {
|
||||
synchronized (pollingTuples) {
|
||||
for (PollingTuple tuple : pollingTuples) {
|
||||
if (uuid.equals(tuple.computer.uuid)) {
|
||||
// We need the network lock to prevent a concurrent poll
|
||||
// from wiping this change out
|
||||
synchronized (tuple.networkLock) {
|
||||
tuple.computer.state = ComputerDetails.State.UNKNOWN;
|
||||
tuple.computer.reachability = ComputerDetails.Reachability.UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -778,6 +806,7 @@ class PollingTuple {
|
||||
public Thread thread;
|
||||
public final ComputerDetails computer;
|
||||
public final Object networkLock;
|
||||
public long lastSuccessfulPollMs;
|
||||
|
||||
public PollingTuple(ComputerDetails computer, Thread thread) {
|
||||
this.computer = computer;
|
||||
|
||||
@@ -42,7 +42,7 @@ public class DiscoveryService extends Service {
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE);
|
||||
WifiManager wifiMgr = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||
multicastLock = wifiMgr.createMulticastLock("Limelight mDNS");
|
||||
multicastLock.setReferenceCounted(false);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.limelight.grid;
|
||||
import android.app.Activity;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.limelight.AppView;
|
||||
@@ -14,8 +15,6 @@ import com.limelight.grid.assets.MemoryAssetLoader;
|
||||
import com.limelight.grid.assets.NetworkAssetLoader;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
|
||||
@@ -28,7 +27,7 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
||||
private final CachedAppAssetLoader loader;
|
||||
|
||||
public AppGridAdapter(Activity activity, boolean listMode, boolean small, ComputerDetails computer, String uniqueId) {
|
||||
super(activity, listMode ? R.layout.simple_row : (small ? R.layout.app_grid_item_small : R.layout.app_grid_item), R.drawable.image_loading);
|
||||
super(activity, listMode ? R.layout.simple_row : (small ? R.layout.app_grid_item_small : R.layout.app_grid_item));
|
||||
|
||||
int dpi = activity.getResources().getDisplayMetrics().densityDpi;
|
||||
int dp;
|
||||
@@ -53,9 +52,7 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
||||
this.loader = new CachedAppAssetLoader(computer, scalingDivisor,
|
||||
new NetworkAssetLoader(context, uniqueId),
|
||||
new MemoryAssetLoader(),
|
||||
new DiskAssetLoader(context.getCacheDir()),
|
||||
BitmapFactory.decodeResource(activity.getResources(),
|
||||
R.drawable.image_loading, options));
|
||||
new DiskAssetLoader(context.getCacheDir()));
|
||||
}
|
||||
|
||||
public void cancelQueuedOperations() {
|
||||
@@ -68,7 +65,7 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
||||
Collections.sort(itemList, new Comparator<AppView.AppObject>() {
|
||||
@Override
|
||||
public int compare(AppView.AppObject lhs, AppView.AppObject rhs) {
|
||||
return lhs.app.getAppName().compareTo(rhs.app.getAppName());
|
||||
return lhs.app.getAppName().toLowerCase().compareTo(rhs.app.getAppName().toLowerCase());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -86,9 +83,10 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
||||
itemList.remove(app);
|
||||
}
|
||||
|
||||
public boolean populateImageView(ImageView imgView, AppView.AppObject obj) {
|
||||
@Override
|
||||
public boolean populateImageView(ImageView imgView, ProgressBar prgView, AppView.AppObject obj) {
|
||||
// Let the cached asset loader handle it
|
||||
loader.populateImageView(obj.app, imgView);
|
||||
loader.populateImageView(obj.app, imgView, prgView);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -103,9 +101,9 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
||||
|
||||
@Override
|
||||
public boolean populateOverlayView(ImageView overlayView, AppView.AppObject obj) {
|
||||
if (obj.app.getIsRunning()) {
|
||||
if (obj.isRunning) {
|
||||
// Show the play button overlay
|
||||
overlayView.setImageResource(R.drawable.play);
|
||||
overlayView.setImageResource(R.drawable.ic_play);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.limelight.R;
|
||||
@@ -14,15 +15,13 @@ import java.util.ArrayList;
|
||||
|
||||
public abstract class GenericGridAdapter<T> extends BaseAdapter {
|
||||
protected final Context context;
|
||||
protected final int defaultImageRes;
|
||||
protected final int layoutId;
|
||||
protected final ArrayList<T> itemList = new ArrayList<T>();
|
||||
protected final ArrayList<T> itemList = new ArrayList<>();
|
||||
protected final LayoutInflater inflater;
|
||||
|
||||
public GenericGridAdapter(Context context, int layoutId, int defaultImageRes) {
|
||||
public GenericGridAdapter(Context context, int layoutId) {
|
||||
this.context = context;
|
||||
this.layoutId = layoutId;
|
||||
this.defaultImageRes = defaultImageRes;
|
||||
|
||||
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
@@ -46,7 +45,7 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
|
||||
return i;
|
||||
}
|
||||
|
||||
public abstract boolean populateImageView(ImageView imgView, T obj);
|
||||
public abstract boolean populateImageView(ImageView imgView, ProgressBar prgView, T obj);
|
||||
public abstract boolean populateTextView(TextView txtView, T obj);
|
||||
public abstract boolean populateOverlayView(ImageView overlayView, T obj);
|
||||
|
||||
@@ -59,10 +58,11 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
|
||||
ImageView imgView = (ImageView) convertView.findViewById(R.id.grid_image);
|
||||
ImageView overlayView = (ImageView) convertView.findViewById(R.id.grid_overlay);
|
||||
TextView txtView = (TextView) convertView.findViewById(R.id.grid_text);
|
||||
ProgressBar prgView = (ProgressBar) convertView.findViewById(R.id.grid_spinner);
|
||||
|
||||
if (imgView != null) {
|
||||
if (!populateImageView(imgView, itemList.get(i))) {
|
||||
imgView.setImageResource(defaultImageRes);
|
||||
if (!populateImageView(imgView, prgView, itemList.get(i))) {
|
||||
imgView.setImageBitmap(null);
|
||||
}
|
||||
}
|
||||
if (!populateTextView(txtView, itemList.get(i))) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.limelight.grid;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.limelight.PcView;
|
||||
@@ -14,7 +16,7 @@ import java.util.Comparator;
|
||||
public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
||||
|
||||
public PcGridAdapter(Context context, boolean listMode, boolean small) {
|
||||
super(context, listMode ? R.layout.simple_row : (small ? R.layout.pc_grid_item_small : R.layout.pc_grid_item), R.drawable.computer);
|
||||
super(context, listMode ? R.layout.simple_row : (small ? R.layout.pc_grid_item_small : R.layout.pc_grid_item));
|
||||
}
|
||||
|
||||
public void addComputer(PcView.ComputerObject computer) {
|
||||
@@ -26,7 +28,7 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
||||
Collections.sort(itemList, new Comparator<PcView.ComputerObject>() {
|
||||
@Override
|
||||
public int compare(PcView.ComputerObject lhs, PcView.ComputerObject rhs) {
|
||||
return lhs.details.name.compareTo(rhs.details.name);
|
||||
return lhs.details.name.toLowerCase().compareTo(rhs.details.name.toLowerCase());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -36,21 +38,28 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean populateImageView(ImageView imgView, PcView.ComputerObject obj) {
|
||||
if (obj.details.reachability != ComputerDetails.Reachability.OFFLINE) {
|
||||
public boolean populateImageView(ImageView imgView, ProgressBar prgView, PcView.ComputerObject obj) {
|
||||
if (obj.details.state == ComputerDetails.State.ONLINE) {
|
||||
imgView.setAlpha(1.0f);
|
||||
}
|
||||
else {
|
||||
imgView.setAlpha(0.4f);
|
||||
}
|
||||
|
||||
// Return false to use the default drawable
|
||||
return false;
|
||||
if (obj.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
|
||||
prgView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else {
|
||||
prgView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
imgView.setImageResource(R.drawable.ic_computer);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean populateTextView(TextView txtView, PcView.ComputerObject obj) {
|
||||
if (obj.details.reachability != ComputerDetails.Reachability.OFFLINE) {
|
||||
if (obj.details.state == ComputerDetails.State.ONLINE) {
|
||||
txtView.setAlpha(1.0f);
|
||||
}
|
||||
else {
|
||||
@@ -63,13 +72,11 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
||||
|
||||
@Override
|
||||
public boolean populateOverlayView(ImageView overlayView, PcView.ComputerObject obj) {
|
||||
if (obj.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
|
||||
// Still refreshing this PC so display the overlay
|
||||
overlayView.setImageResource(R.drawable.image_loading);
|
||||
if (obj.details.state == ComputerDetails.State.OFFLINE) {
|
||||
overlayView.setImageResource(R.drawable.ic_pc_offline);
|
||||
overlayView.setAlpha(0.4f);
|
||||
return true;
|
||||
}
|
||||
|
||||
// No overlay
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.NvApp;
|
||||
@@ -53,13 +55,13 @@ public class CachedAppAssetLoader {
|
||||
|
||||
public CachedAppAssetLoader(ComputerDetails computer, double scalingDivider,
|
||||
NetworkAssetLoader networkLoader, MemoryAssetLoader memoryLoader,
|
||||
DiskAssetLoader diskLoader, Bitmap placeholderBitmap) {
|
||||
DiskAssetLoader diskLoader) {
|
||||
this.computer = computer;
|
||||
this.scalingDivider = scalingDivider;
|
||||
this.networkLoader = networkLoader;
|
||||
this.memoryLoader = memoryLoader;
|
||||
this.diskLoader = diskLoader;
|
||||
this.placeholderBitmap = placeholderBitmap;
|
||||
this.placeholderBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
|
||||
}
|
||||
|
||||
public void cancelBackgroundLoads() {
|
||||
@@ -130,12 +132,14 @@ public class CachedAppAssetLoader {
|
||||
|
||||
private class LoaderTask extends AsyncTask<LoaderTuple, Void, Bitmap> {
|
||||
private final WeakReference<ImageView> imageViewRef;
|
||||
private final WeakReference<ProgressBar> progressViewRef;
|
||||
private final boolean diskOnly;
|
||||
|
||||
private LoaderTuple tuple;
|
||||
|
||||
public LoaderTask(ImageView imageView, boolean diskOnly) {
|
||||
this.imageViewRef = new WeakReference<ImageView>(imageView);
|
||||
public LoaderTask(ImageView imageView, ProgressBar prgView, boolean diskOnly) {
|
||||
this.imageViewRef = new WeakReference<>(imageView);
|
||||
this.progressViewRef = new WeakReference<>(prgView);
|
||||
this.diskOnly = diskOnly;
|
||||
}
|
||||
|
||||
@@ -143,8 +147,8 @@ public class CachedAppAssetLoader {
|
||||
protected Bitmap doInBackground(LoaderTuple... params) {
|
||||
tuple = params[0];
|
||||
|
||||
// Check whether it has been cancelled or the image view is gone
|
||||
if (isCancelled() || imageViewRef.get() == null) {
|
||||
// Check whether it has been cancelled or the views are gone
|
||||
if (isCancelled() || imageViewRef.get() == null || progressViewRef.get() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -177,11 +181,17 @@ public class CachedAppAssetLoader {
|
||||
|
||||
// If the current loader task for this view isn't us, do nothing
|
||||
final ImageView imageView = imageViewRef.get();
|
||||
final ProgressBar prgView = progressViewRef.get();
|
||||
if (getLoaderTask(imageView) == this) {
|
||||
// Now display the progress bar since we have to hit the network
|
||||
if (prgView != null) {
|
||||
prgView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
// Set off another loader task on the network executor
|
||||
LoaderTask task = new LoaderTask(imageView, false);
|
||||
LoaderTask task = new LoaderTask(imageView, prgView, false);
|
||||
AsyncDrawable asyncDrawable = new AsyncDrawable(imageView.getResources(), placeholderBitmap, task);
|
||||
imageView.setAlpha(1.0f);
|
||||
imageView.setVisibility(View.VISIBLE);
|
||||
imageView.setImageDrawable(asyncDrawable);
|
||||
task.executeOnExecutor(networkExecutor, tuple);
|
||||
}
|
||||
@@ -195,14 +205,20 @@ public class CachedAppAssetLoader {
|
||||
}
|
||||
|
||||
final ImageView imageView = imageViewRef.get();
|
||||
final ProgressBar prgView = progressViewRef.get();
|
||||
if (getLoaderTask(imageView) == this) {
|
||||
// Set the bitmap
|
||||
if (bitmap != null) {
|
||||
imageView.setImageBitmap(bitmap);
|
||||
}
|
||||
|
||||
// Hide the progress bar
|
||||
if (prgView != null) {
|
||||
prgView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
// Show the view
|
||||
imageView.setAlpha(1.0f);
|
||||
imageView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,7 +229,7 @@ public class CachedAppAssetLoader {
|
||||
public AsyncDrawable(Resources res, Bitmap bitmap,
|
||||
LoaderTask loaderTask) {
|
||||
super(res, bitmap);
|
||||
loaderTaskReference = new WeakReference<LoaderTask>(loaderTask);
|
||||
loaderTaskReference = new WeakReference<>(loaderTask);
|
||||
}
|
||||
|
||||
public LoaderTask getLoaderTask() {
|
||||
@@ -280,34 +296,38 @@ public class CachedAppAssetLoader {
|
||||
});
|
||||
}
|
||||
|
||||
public void populateImageView(NvApp app, ImageView view) {
|
||||
public boolean populateImageView(NvApp app, ImageView imgView, ProgressBar prgView) {
|
||||
LoaderTuple tuple = new LoaderTuple(computer, app);
|
||||
|
||||
// If there's already a task in progress for this view,
|
||||
// cancel it. If the task is already loading the same image,
|
||||
// we return and let that load finish.
|
||||
if (!cancelPendingLoad(tuple, view)) {
|
||||
return;
|
||||
if (!cancelPendingLoad(tuple, imgView)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hide the progress bar always on initial load
|
||||
prgView.setVisibility(View.INVISIBLE);
|
||||
|
||||
// First, try the memory cache in the current context
|
||||
Bitmap bmp = memoryLoader.loadBitmapFromCache(tuple);
|
||||
if (bmp != null) {
|
||||
// Show the bitmap immediately
|
||||
view.setAlpha(1.0f);
|
||||
view.setImageBitmap(bmp);
|
||||
return;
|
||||
imgView.setVisibility(View.VISIBLE);
|
||||
imgView.setImageBitmap(bmp);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If it's not in memory, create an async task to load it. This task will be attached
|
||||
// via AsyncDrawable to this view.
|
||||
final LoaderTask task = new LoaderTask(view, true);
|
||||
final AsyncDrawable asyncDrawable = new AsyncDrawable(view.getResources(), placeholderBitmap, task);
|
||||
view.setAlpha(0.0f);
|
||||
view.setImageDrawable(asyncDrawable);
|
||||
final LoaderTask task = new LoaderTask(imgView, prgView, true);
|
||||
final AsyncDrawable asyncDrawable = new AsyncDrawable(imgView.getResources(), placeholderBitmap, task);
|
||||
imgView.setVisibility(View.INVISIBLE);
|
||||
imgView.setImageDrawable(asyncDrawable);
|
||||
|
||||
// Run the task on our foreground executor
|
||||
task.executeOnExecutor(foregroundExecutor, tuple);
|
||||
return false;
|
||||
}
|
||||
|
||||
public class LoaderTuple {
|
||||
|
||||
@@ -7,7 +7,6 @@ import com.limelight.LimeLog;
|
||||
import com.limelight.utils.CacheHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
@@ -29,7 +29,7 @@ import android.widget.Toast;
|
||||
public class AddComputerManually extends Activity {
|
||||
private TextView hostText;
|
||||
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
||||
private final LinkedBlockingQueue<String> computersToAdd = new LinkedBlockingQueue<String>();
|
||||
private final LinkedBlockingQueue<String> computersToAdd = new LinkedBlockingQueue<>();
|
||||
private Thread addThread;
|
||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
public void onServiceConnected(ComponentName className, final IBinder binder) {
|
||||
@@ -136,12 +136,7 @@ public class AddComputerManually extends Activity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(getResources().getConfiguration());
|
||||
config.locale = new Locale(locale);
|
||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||
}
|
||||
UiHelper.setLocale(this);
|
||||
|
||||
setContentView(R.layout.activity_add_computer_manually);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.limelight.preferences;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
@@ -100,7 +101,7 @@ public class PreferenceConfiguration {
|
||||
}
|
||||
|
||||
// Use small mode on anything smaller than a 7" tablet
|
||||
return context.getResources().getConfiguration().screenWidthDp < 600;
|
||||
return context.getResources().getConfiguration().smallestScreenWidthDp < 500;
|
||||
}
|
||||
|
||||
public static int getDefaultBitrate(Context context) {
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.limelight.preferences;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.app.Activity;
|
||||
@@ -27,11 +26,7 @@ public class StreamSettings extends Activity {
|
||||
|
||||
previousPrefs = PreferenceConfiguration.readPreferences(this);
|
||||
|
||||
if (!previousPrefs.language.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(getResources().getConfiguration());
|
||||
config.locale = new Locale(previousPrefs.language);
|
||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||
}
|
||||
UiHelper.setLocale(this);
|
||||
|
||||
setContentView(R.layout.activity_stream_settings);
|
||||
getFragmentManager().beginTransaction().replace(
|
||||
|
||||
@@ -5,6 +5,9 @@ import java.util.ArrayList;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.limelight.R;
|
||||
|
||||
public class Dialog implements Runnable {
|
||||
private final String title;
|
||||
@@ -14,7 +17,7 @@ public class Dialog implements Runnable {
|
||||
|
||||
private AlertDialog alert;
|
||||
|
||||
private static final ArrayList<Dialog> rundownDialogs = new ArrayList<Dialog>();
|
||||
private static final ArrayList<Dialog> rundownDialogs = new ArrayList<>();
|
||||
|
||||
private Dialog(Activity activity, String title, String message, boolean endAfterDismiss)
|
||||
{
|
||||
@@ -55,7 +58,7 @@ public class Dialog implements Runnable {
|
||||
alert.setCancelable(false);
|
||||
alert.setCanceledOnTouchOutside(false);
|
||||
|
||||
alert.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() {
|
||||
alert.setButton(AlertDialog.BUTTON_POSITIVE, activity.getResources().getText(android.R.string.ok), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
synchronized (rundownDialogs) {
|
||||
rundownDialogs.remove(Dialog.this);
|
||||
@@ -67,6 +70,31 @@ public class Dialog implements Runnable {
|
||||
}
|
||||
}
|
||||
});
|
||||
alert.setButton(AlertDialog.BUTTON_NEUTRAL, activity.getResources().getText(R.string.help), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
synchronized (rundownDialogs) {
|
||||
rundownDialogs.remove(Dialog.this);
|
||||
alert.dismiss();
|
||||
}
|
||||
|
||||
if (endAfterDismiss) {
|
||||
activity.finish();
|
||||
}
|
||||
|
||||
HelpLauncher.launchTroubleshooting(activity);
|
||||
}
|
||||
});
|
||||
alert.setOnShowListener(new DialogInterface.OnShowListener(){
|
||||
|
||||
@Override
|
||||
public void onShow(DialogInterface dialog) {
|
||||
// Set focus to the OK button by default
|
||||
Button button = alert.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
button.setFocusable(true);
|
||||
button.setFocusableInTouchMode(true);
|
||||
button.requestFocus();
|
||||
}
|
||||
});
|
||||
|
||||
synchronized (rundownDialogs) {
|
||||
rundownDialogs.add(this);
|
||||
|
||||
47
app/src/main/java/com/limelight/utils/HelpLauncher.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package com.limelight.utils;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import com.limelight.HelpActivity;
|
||||
|
||||
public class HelpLauncher {
|
||||
|
||||
private static void launchUrl(Context context, String url) {
|
||||
// Try to launch the default browser
|
||||
try {
|
||||
// Fire TV devices will lie and say they do have a browser
|
||||
// even though the OS just shows an error dialog if we
|
||||
// try to use it.
|
||||
if (!"Amazon".equalsIgnoreCase(Build.MANUFACTURER)) {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse(url));
|
||||
context.startActivity(i);
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// This is only supposed to throw ActivityNotFoundException but
|
||||
// it can (at least) also throw SecurityException if a user's default
|
||||
// browser is not exported. We'll catch everything to workaround this.
|
||||
|
||||
// Fall through
|
||||
}
|
||||
|
||||
// This platform has no browser (possibly a leanback device)
|
||||
// We'll launch our WebView activity
|
||||
Intent i = new Intent(context, HelpActivity.class);
|
||||
i.setData(Uri.parse(url));
|
||||
context.startActivity(i);
|
||||
}
|
||||
|
||||
public static void launchSetupGuide(Context context) {
|
||||
launchUrl(context, "https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide");
|
||||
}
|
||||
|
||||
public static void launchTroubleshooting(Context context) {
|
||||
launchUrl(context, "https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting");
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,8 @@ public class ServerHelper {
|
||||
computer.localIp : computer.remoteIp;
|
||||
}
|
||||
|
||||
public static void doStart(Activity parent, NvApp app, ComputerDetails computer,
|
||||
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
||||
public static Intent createStartIntent(Activity parent, NvApp app, ComputerDetails computer,
|
||||
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
||||
Intent intent = new Intent(parent, Game.class);
|
||||
intent.putExtra(Game.EXTRA_HOST,
|
||||
computer.reachability == ComputerDetails.Reachability.LOCAL ?
|
||||
@@ -34,7 +34,14 @@ public class ServerHelper {
|
||||
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
|
||||
intent.putExtra(Game.EXTRA_STREAMING_REMOTE,
|
||||
computer.reachability != ComputerDetails.Reachability.LOCAL);
|
||||
parent.startActivity(intent);
|
||||
intent.putExtra(Game.EXTRA_PC_UUID, computer.uuid.toString());
|
||||
intent.putExtra(Game.EXTRA_PC_NAME, computer.name);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static void doStart(Activity parent, NvApp app, ComputerDetails computer,
|
||||
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
||||
parent.startActivity(createStartIntent(parent, app, computer, managerBinder));
|
||||
}
|
||||
|
||||
public static void doQuit(final Activity parent,
|
||||
|
||||
138
app/src/main/java/com/limelight/utils/ShortcutHelper.java
Normal file
@@ -0,0 +1,138 @@
|
||||
package com.limelight.utils;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
|
||||
import com.limelight.AppView;
|
||||
import com.limelight.AppViewShortcutTrampoline;
|
||||
import com.limelight.R;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class ShortcutHelper {
|
||||
|
||||
private final ShortcutManager sm;
|
||||
private final Context context;
|
||||
|
||||
public ShortcutHelper(Context context) {
|
||||
this.context = context;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
sm = context.getSystemService(ShortcutManager.class);
|
||||
}
|
||||
else {
|
||||
sm = null;
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
private void reapShortcutsForDynamicAdd() {
|
||||
List<ShortcutInfo> dynamicShortcuts = sm.getDynamicShortcuts();
|
||||
while (dynamicShortcuts.size() >= sm.getMaxShortcutCountPerActivity()) {
|
||||
ShortcutInfo maxRankShortcut = dynamicShortcuts.get(0);
|
||||
for (ShortcutInfo scut : dynamicShortcuts) {
|
||||
if (maxRankShortcut.getRank() < scut.getRank()) {
|
||||
maxRankShortcut = scut;
|
||||
}
|
||||
}
|
||||
sm.removeDynamicShortcuts(Collections.singletonList(maxRankShortcut.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
private List<ShortcutInfo> getAllShortcuts() {
|
||||
LinkedList<ShortcutInfo> list = new LinkedList<>();
|
||||
list.addAll(sm.getDynamicShortcuts());
|
||||
list.addAll(sm.getPinnedShortcuts());
|
||||
return list;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
private ShortcutInfo getInfoForId(String id) {
|
||||
List<ShortcutInfo> shortcuts = getAllShortcuts();
|
||||
|
||||
for (ShortcutInfo info : shortcuts) {
|
||||
if (info.getId().equals(id)) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
private boolean isExistingDynamicShortcut(String id) {
|
||||
for (ShortcutInfo si : sm.getDynamicShortcuts()) {
|
||||
if (si.getId().equals(id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void reportShortcutUsed(String id) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
ShortcutInfo sinfo = getInfoForId(id);
|
||||
if (sinfo != null) {
|
||||
sm.reportShortcutUsed(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void createAppViewShortcut(String id, String computerName, String computerUuid, boolean forceAdd) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
Intent i = new Intent(context, AppViewShortcutTrampoline.class);
|
||||
i.putExtra(AppView.NAME_EXTRA, computerName);
|
||||
i.putExtra(AppView.UUID_EXTRA, computerUuid);
|
||||
i.setAction(Intent.ACTION_DEFAULT);
|
||||
|
||||
ShortcutInfo sinfo = new ShortcutInfo.Builder(context, id)
|
||||
.setIntent(i)
|
||||
.setShortLabel(computerName)
|
||||
.setLongLabel(computerName)
|
||||
.setIcon(Icon.createWithResource(context, R.mipmap.ic_pc_scut))
|
||||
.build();
|
||||
|
||||
ShortcutInfo existingSinfo = getInfoForId(id);
|
||||
if (existingSinfo != null) {
|
||||
// Update in place
|
||||
sm.updateShortcuts(Collections.singletonList(sinfo));
|
||||
sm.enableShortcuts(Collections.singletonList(id));
|
||||
}
|
||||
|
||||
// Reap shortcuts to make space for this if it's new
|
||||
// NOTE: This CAN'T be an else on the above if, because it's
|
||||
// possible that we have an existing shortcut but it's not a dynamic one.
|
||||
if (!isExistingDynamicShortcut(id)) {
|
||||
// To avoid a random carousel of shortcuts popping in and out based on polling status,
|
||||
// we only add shortcuts if it's not at the limit or the user made a conscious action
|
||||
// to interact with this PC.
|
||||
if (forceAdd || sm.getDynamicShortcuts().size() < sm.getMaxShortcutCountPerActivity()) {
|
||||
reapShortcutsForDynamicAdd();
|
||||
sm.addDynamicShortcuts(Collections.singletonList(sinfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void createAppViewShortcut(String id, ComputerDetails details, boolean forceAdd) {
|
||||
createAppViewShortcut(id, details.name, details.uuid.toString(), forceAdd);
|
||||
}
|
||||
|
||||
public void disableShortcut(String id, CharSequence reason) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
ShortcutInfo sinfo = getInfoForId(id);
|
||||
if (sinfo != null) {
|
||||
sm.disableShortcuts(Collections.singletonList(id), reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ public class SpinnerDialog implements Runnable,OnCancelListener {
|
||||
private ProgressDialog progress;
|
||||
private final boolean finish;
|
||||
|
||||
private static final ArrayList<SpinnerDialog> rundownDialogs = new ArrayList<SpinnerDialog>();
|
||||
private static final ArrayList<SpinnerDialog> rundownDialogs = new ArrayList<>();
|
||||
|
||||
private SpinnerDialog(Activity activity, String title, String message, boolean finish)
|
||||
{
|
||||
|
||||
@@ -9,6 +9,9 @@ import android.content.res.Configuration;
|
||||
import android.view.View;
|
||||
|
||||
import com.limelight.R;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class UiHelper {
|
||||
|
||||
@@ -16,6 +19,28 @@ public class UiHelper {
|
||||
private static final int TV_VERTICAL_PADDING_DP = 27;
|
||||
private static final int TV_HORIZONTAL_PADDING_DP = 48;
|
||||
|
||||
public static void setLocale(Activity activity)
|
||||
{
|
||||
String locale = PreferenceConfiguration.readPreferences(activity).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(activity.getResources().getConfiguration());
|
||||
|
||||
// Some locales include both language and country which must be separated
|
||||
// before calling the Locale constructor.
|
||||
if (locale.contains("-"))
|
||||
{
|
||||
config.locale = new Locale(locale.substring(0, locale.indexOf('-')),
|
||||
locale.substring(locale.indexOf('-') + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
config.locale = new Locale(locale);
|
||||
}
|
||||
|
||||
activity.getResources().updateConfiguration(config, activity.getResources().getDisplayMetrics());
|
||||
}
|
||||
}
|
||||
|
||||
public static void notifyNewRootView(Activity activity)
|
||||
{
|
||||
View rootView = activity.findViewById(android.R.id.content);
|
||||
|
||||
47
app/src/main/java/com/limelight/utils/Vector2d.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package com.limelight.utils;
|
||||
|
||||
public class Vector2d {
|
||||
private float x;
|
||||
private float y;
|
||||
private double magnitude;
|
||||
|
||||
public static final Vector2d ZERO = new Vector2d();
|
||||
|
||||
public Vector2d() {
|
||||
initialize(0, 0);
|
||||
}
|
||||
|
||||
public void initialize(float x, float y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.magnitude = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
|
||||
}
|
||||
|
||||
public double getMagnitude() {
|
||||
return magnitude;
|
||||
}
|
||||
|
||||
public void getNormalized(Vector2d vector) {
|
||||
vector.initialize((float)(x / magnitude), (float)(y / magnitude));
|
||||
}
|
||||
|
||||
public void scalarMultiply(double factor) {
|
||||
initialize((float)(x * factor), (float)(y * factor));
|
||||
}
|
||||
|
||||
public void setX(float x) {
|
||||
initialize(x, this.y);
|
||||
}
|
||||
|
||||
public void setY(float y) {
|
||||
initialize(this.x, y);
|
||||
}
|
||||
|
||||
public float getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public float getY() {
|
||||
return y;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <android/log.h>
|
||||
|
||||
#define EVDEV_MAX_EVENT_SIZE 24
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# Android.mk for Moonlight's ENet JNI binding
|
||||
MY_LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(call all-subdir-makefiles)
|
||||
|
||||
LOCAL_PATH := $(MY_LOCAL_PATH)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := jnienet
|
||||
|
||||
LOCAL_SRC_FILES := jnienet.c \
|
||||
enet/callbacks.c \
|
||||
enet/compress.c \
|
||||
enet/host.c \
|
||||
enet/list.c \
|
||||
enet/packet.c \
|
||||
enet/peer.c \
|
||||
enet/protocol.c \
|
||||
enet/unix.c \
|
||||
enet/win32.c \
|
||||
|
||||
LOCAL_CFLAGS := -DHAS_SOCKLEN_T=1
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/enet/include
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
@@ -1,148 +0,0 @@
|
||||
#include "enet/enet.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#define CLIENT_TO_LONG(x) ((intptr_t)(x))
|
||||
#define LONG_TO_CLIENT(x) ((ENetHost*)(intptr_t)(x))
|
||||
|
||||
#define PEER_TO_LONG(x) ((intptr_t)(x))
|
||||
#define LONG_TO_PEER(x) ((ENetPeer*)(intptr_t)(x))
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_limelight_nvstream_enet_EnetConnection_initializeEnet(JNIEnv *env, jobject class) {
|
||||
return enet_initialize();
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_limelight_nvstream_enet_EnetConnection_createClient(JNIEnv *env, jobject class, jstring address) {
|
||||
ENetAddress enetAddress;
|
||||
const char *addrStr;
|
||||
int err;
|
||||
|
||||
// Perform a lookup on the address to determine the address family
|
||||
addrStr = (*env)->GetStringUTFChars(env, address, 0);
|
||||
err = enet_address_set_host(&enetAddress, addrStr);
|
||||
(*env)->ReleaseStringUTFChars(env, address, addrStr);
|
||||
if (err < 0) {
|
||||
return CLIENT_TO_LONG(NULL);
|
||||
}
|
||||
|
||||
// Create a client that can use 1 outgoing connection and 1 channel
|
||||
return CLIENT_TO_LONG(enet_host_create(enetAddress.address.ss_family, NULL, 1, 1, 0, 0));
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_limelight_nvstream_enet_EnetConnection_connectToPeer(JNIEnv *env, jobject class, jlong client, jstring address, jint port, jint timeout) {
|
||||
ENetPeer* peer;
|
||||
ENetAddress enetAddress;
|
||||
ENetEvent event;
|
||||
const char *addrStr;
|
||||
int err;
|
||||
|
||||
// Initialize the ENet address
|
||||
addrStr = (*env)->GetStringUTFChars(env, address, 0);
|
||||
err = enet_address_set_host(&enetAddress, addrStr);
|
||||
enet_address_set_port(&enetAddress, port);
|
||||
(*env)->ReleaseStringUTFChars(env, address, addrStr);
|
||||
if (err < 0) {
|
||||
return PEER_TO_LONG(NULL);
|
||||
}
|
||||
|
||||
// Start the connection
|
||||
peer = enet_host_connect(LONG_TO_CLIENT(client), &enetAddress, 1, 0);
|
||||
if (peer == NULL) {
|
||||
return PEER_TO_LONG(NULL);
|
||||
}
|
||||
|
||||
// Wait for the connect to complete
|
||||
if (enet_host_service(LONG_TO_CLIENT(client), &event, timeout) <= 0 || event.type != ENET_EVENT_TYPE_CONNECT) {
|
||||
enet_peer_reset(peer);
|
||||
return PEER_TO_LONG(NULL);
|
||||
}
|
||||
|
||||
// Ensure the connect verify ACK is sent immediately
|
||||
enet_host_flush(LONG_TO_CLIENT(client));
|
||||
|
||||
// Set the max peer timeout to 10 seconds
|
||||
enet_peer_timeout(peer, ENET_PEER_TIMEOUT_LIMIT, ENET_PEER_TIMEOUT_MINIMUM, 10000);
|
||||
|
||||
return PEER_TO_LONG(peer);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_limelight_nvstream_enet_EnetConnection_readPacket(JNIEnv *env, jobject class, jlong client, jbyteArray data, jint length, jint timeout) {
|
||||
jint err;
|
||||
jbyte* dataPtr;
|
||||
ENetEvent event;
|
||||
|
||||
// Wait for a receive event, timeout, or disconnect
|
||||
err = enet_host_service(LONG_TO_CLIENT(client), &event, timeout);
|
||||
if (err <= 0) {
|
||||
return err;
|
||||
}
|
||||
else if (event.type != ENET_EVENT_TYPE_RECEIVE) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check that the packet isn't too large
|
||||
if (event.packet->dataLength > length) {
|
||||
enet_packet_destroy(event.packet);
|
||||
return event.packet->dataLength;
|
||||
}
|
||||
|
||||
// Copy the packet data into the caller's buffer
|
||||
dataPtr = (*env)->GetByteArrayElements(env, data, 0);
|
||||
memcpy(dataPtr, event.packet->data, event.packet->dataLength);
|
||||
err = event.packet->dataLength;
|
||||
(*env)->ReleaseByteArrayElements(env, data, dataPtr, 0);
|
||||
|
||||
// Free the packet
|
||||
enet_packet_destroy(event.packet);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_limelight_nvstream_enet_EnetConnection_writePacket(JNIEnv *env, jobject class, jlong client, jlong peer, jbyteArray data, jint length, jint packetFlags) {
|
||||
ENetPacket* packet;
|
||||
jboolean ret;
|
||||
jbyte* dataPtr;
|
||||
|
||||
dataPtr = (*env)->GetByteArrayElements(env, data, 0);
|
||||
|
||||
// Create the reliable packet that describes our outgoing message
|
||||
packet = enet_packet_create(dataPtr, length, packetFlags);
|
||||
if (packet != NULL) {
|
||||
// Send the message to the peer
|
||||
if (enet_peer_send(LONG_TO_PEER(peer), 0, packet) < 0) {
|
||||
// This can fail if the peer has been disconnected
|
||||
enet_packet_destroy(packet);
|
||||
ret = JNI_FALSE;
|
||||
}
|
||||
else {
|
||||
// Force the client to send the packet now
|
||||
enet_host_flush(LONG_TO_CLIENT(client));
|
||||
ret = JNI_TRUE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ret = JNI_FALSE;
|
||||
}
|
||||
|
||||
(*env)->ReleaseByteArrayElements(env, data, dataPtr, JNI_ABORT);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_limelight_nvstream_enet_EnetConnection_destroyClient(JNIEnv *env, jobject class, jlong client) {
|
||||
enet_host_destroy(LONG_TO_CLIENT(client));
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_limelight_nvstream_enet_EnetConnection_disconnectPeer(JNIEnv *env, jobject class, jlong peer) {
|
||||
enet_peer_disconnect_now(LONG_TO_PEER(peer), 0);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
# Android.mk for Limelight's Opus decoder
|
||||
MY_LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(call all-subdir-makefiles)
|
||||
|
||||
LOCAL_PATH := $(MY_LOCAL_PATH)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := nv_opus_dec
|
||||
LOCAL_SRC_FILES := nv_opus_dec.c nv_opus_dec_jni.c
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/libopus/inc
|
||||
|
||||
# Link to libopus library
|
||||
LOCAL_STATIC_LIBRARIES := libopus
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
@@ -1,7 +0,0 @@
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE:= libopus
|
||||
LOCAL_SRC_FILES:= $(TARGET_ARCH_ABI)/libopus.a
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/inc
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
@@ -1,100 +0,0 @@
|
||||
ANDROID_API_TARGET=21
|
||||
PARALLEL_JOBS=$(nproc)
|
||||
|
||||
rm -r ./android
|
||||
mkdir android
|
||||
|
||||
function build_one
|
||||
{
|
||||
PREFIX=$(pwd)/android/$CPU
|
||||
SYSROOT=$NDK/platforms/android-$ANDROID_API_TARGET/arch-$SYSROOT_CPU
|
||||
TOOLCHAIN_PATH=$NDK/toolchains/$TOOLCHAIN_DIR/prebuilt/linux-x86_64
|
||||
export PATH=$PATH:$TOOLCHAIN_PATH/bin
|
||||
./configure \
|
||||
--build=x86_64-unknown-linux-gnu \
|
||||
--host=$TOOLCHAIN_BIN_PREFIX \
|
||||
--target=$TOOLCHAIN_BIN_PREFIX \
|
||||
CFLAGS="--sysroot=$SYSROOT -O2 $ADDI_CFLAGS" \
|
||||
$ADDI_CONFIGURE_FLAGS
|
||||
make clean
|
||||
make -j$PARALLEL_JOBS
|
||||
mkdir android/$CPU
|
||||
cp .libs/libopus.a android/$CPU
|
||||
}
|
||||
|
||||
function build_mips
|
||||
{
|
||||
CPU=mips
|
||||
SYSROOT_CPU=mips
|
||||
TOOLCHAIN_BIN_PREFIX=mipsel-linux-android
|
||||
TOOLCHAIN_DIR=mipsel-linux-android-4.9
|
||||
ADDI_CFLAGS="-mips32 -mhard-float -EL -mno-dsp"
|
||||
ADDI_CONFIGURE_FLAGS="--enable-fixed-point" # fixed point
|
||||
build_one
|
||||
}
|
||||
|
||||
function build_mips64
|
||||
{
|
||||
CPU=mips64
|
||||
SYSROOT_CPU=mips64
|
||||
TOOLCHAIN_BIN_PREFIX=mips64el-linux-android
|
||||
TOOLCHAIN_DIR=mips64el-linux-android-4.9
|
||||
ADDI_CFLAGS="-mips64r6"
|
||||
ADDI_CONFIGURE_FLAGS="--enable-fixed-point" # fixed point
|
||||
build_one
|
||||
}
|
||||
|
||||
function build_x86
|
||||
{
|
||||
CPU=x86
|
||||
SYSROOT_CPU=x86
|
||||
TOOLCHAIN_BIN_PREFIX=i686-linux-android
|
||||
TOOLCHAIN_DIR=x86-4.9
|
||||
ADDI_CFLAGS="-march=i686 -mtune=atom -mstackrealign -msse -msse2 -msse3 -mssse3 -mfpmath=sse -m32"
|
||||
ADDI_CONFIGURE_FLAGS="" # floating point for SSE optimizations
|
||||
build_one
|
||||
}
|
||||
|
||||
function build_x86_64
|
||||
{
|
||||
CPU=x86_64
|
||||
SYSROOT_CPU=x86_64
|
||||
TOOLCHAIN_BIN_PREFIX=x86_64-linux-android
|
||||
TOOLCHAIN_DIR=x86_64-4.9
|
||||
ADDI_CFLAGS="-msse -msse2 -msse3 -mssse3 -msse4 -msse4.1 -msse4.2 -mpopcnt -m64"
|
||||
ADDI_CONFIGURE_FLAGS="" # floating point for SSE optimizations
|
||||
build_one
|
||||
}
|
||||
|
||||
function build_armv7
|
||||
{
|
||||
CPU=arm
|
||||
SYSROOT_CPU=arm
|
||||
TOOLCHAIN_BIN_PREFIX=arm-linux-androideabi
|
||||
TOOLCHAIN_DIR=arm-linux-androideabi-4.9
|
||||
ADDI_CFLAGS="-marm -mfpu=vfpv3-d16"
|
||||
ADDI_LDFLAGS=""
|
||||
ADDI_CONFIGURE_FLAGS="--enable-fixed-point" # fixed point for NEON, EDSP, Media
|
||||
build_one
|
||||
}
|
||||
|
||||
# ARMv8 doesn't currently have assembly in the opus project. We still use fixed point
|
||||
# anyway in the hopes that it will be more performant even without assembly.
|
||||
function build_armv8
|
||||
{
|
||||
CPU=aarch64
|
||||
SYSROOT_CPU=arm64
|
||||
TOOLCHAIN_BIN_PREFIX=aarch64-linux-android
|
||||
TOOLCHAIN_DIR=aarch64-linux-android-4.9
|
||||
ADDI_CFLAGS=""
|
||||
ADDI_LDFLAGS=""
|
||||
ADDI_CONFIGURE_FLAGS="--enable-fixed-point"
|
||||
build_one
|
||||
}
|
||||
|
||||
build_mips
|
||||
build_mips64
|
||||
build_x86
|
||||
build_x86_64
|
||||
build_armv7
|
||||
build_armv8
|
||||
@@ -1,978 +0,0 @@
|
||||
/* 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 1 byte, 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 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 */
|
||||
@@ -1,342 +0,0 @@
|
||||
/* 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 */
|
||||
@@ -1,726 +0,0 @@
|
||||
/* 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)
|
||||
# ifdef OPUS_BUILD
|
||||
# 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 */
|
||||
|
||||
/* 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 */
|
||||
|
||||
/**@}*/
|
||||
|
||||
|
||||
/** @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.
|
||||
* @warning Only the MDCT mode of Opus can provide hard CBR behavior.
|
||||
* @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 with trigger progressively more loss resistant behavior in the encoder
|
||||
* at the expense of quality at a given bitrate in the lossless case, 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.
|
||||
* @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_VARIABLE_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 2.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_VARIABLE</dt><dd>Optimize the frame size dynamically.</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_VARIABLE_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 2.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_VARIABLE</dt><dd>Optimize the frame size dynamically.</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. (default : 0)
|
||||
* @hideinitializer */
|
||||
#define OPUS_SET_PREDICTION_DISABLED(x) OPUS_SET_PREDICTION_DISABLED_REQUEST, __opus_check_int(x)
|
||||
/** Gets the encoder's configured prediction status.
|
||||
* @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)
|
||||
|
||||
/**@}*/
|
||||
|
||||
/** @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.
|
||||
*
|
||||
* @returns Version string
|
||||
*/
|
||||
OPUS_EXPORT const char *opus_get_version_string(void);
|
||||
/**@}*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPUS_DEFINES_H */
|
||||
@@ -1,660 +0,0 @@
|
||||
/* 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 the
|
||||
* <a href="http://tools.ietf.org/html/draft-terriberry-oggopus">Ogg
|
||||
* encapsulation specification</a> and is based on the self-delimited Opus
|
||||
* framing described in Appendix B of <a href="http://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="http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-800004.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(5);
|
||||
|
||||
/** 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(6);
|
||||
|
||||
/** 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 */
|
||||
@@ -1,159 +0,0 @@
|
||||
/* (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__ && __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 */
|
||||
@@ -1,42 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <opus_multistream.h>
|
||||
#include "nv_opus_dec.h"
|
||||
|
||||
OpusMSDecoder* decoder;
|
||||
|
||||
// This function must be called before
|
||||
// any other decoding functions
|
||||
int nv_opus_init(int sampleRate, int channelCount, int streams,
|
||||
int coupledStreams, const unsigned char *mapping) {
|
||||
int err;
|
||||
decoder = opus_multistream_decoder_create(
|
||||
sampleRate,
|
||||
channelCount,
|
||||
streams,
|
||||
coupledStreams,
|
||||
mapping,
|
||||
&err);
|
||||
return err;
|
||||
}
|
||||
|
||||
// This function must be called after
|
||||
// decoding is finished
|
||||
void nv_opus_destroy(void) {
|
||||
if (decoder != NULL) {
|
||||
opus_multistream_decoder_destroy(decoder);
|
||||
}
|
||||
}
|
||||
|
||||
// packets must be decoded in order
|
||||
// a packet loss must call this function with NULL indata and 0 inlen
|
||||
// returns the number of decoded samples
|
||||
int nv_opus_decode(unsigned char* indata, int inlen, short* outpcmdata, int framesize) {
|
||||
int err;
|
||||
|
||||
// Decoding to 16-bit PCM with FEC off
|
||||
// Maximum length assuming 48KHz sample rate
|
||||
err = opus_multistream_decode(decoder, indata, inlen,
|
||||
outpcmdata, framesize, 0);
|
||||
|
||||
return err;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
int nv_opus_init(int sampleRate, int channelCount, int streams,
|
||||
int coupledStreams, const unsigned char *mapping);
|
||||
void nv_opus_destroy(void);
|
||||
int nv_opus_decode(unsigned char* indata, int inlen, short* outpcmdata, int framesize);
|
||||
@@ -1,69 +0,0 @@
|
||||
#include "nv_opus_dec.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <jni.h>
|
||||
|
||||
static int SamplesPerChannel;
|
||||
static int ChannelCount;
|
||||
|
||||
// This function must be called before
|
||||
// any other decoding functions
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_limelight_nvstream_av_audio_OpusDecoder_init(JNIEnv *env, jobject this, int sampleRate,
|
||||
int samplesPerChannel, int channelCount, int streams,
|
||||
int coupledStreams, jbyteArray mapping) {
|
||||
jbyte* jni_mapping_data;
|
||||
jint ret;
|
||||
|
||||
SamplesPerChannel = samplesPerChannel;
|
||||
ChannelCount = channelCount;
|
||||
|
||||
jni_mapping_data = (*env)->GetByteArrayElements(env, mapping, 0);
|
||||
ret = nv_opus_init(sampleRate, channelCount, streams, coupledStreams, jni_mapping_data);
|
||||
(*env)->ReleaseByteArrayElements(env, mapping, jni_mapping_data, JNI_ABORT);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// This function must be called after
|
||||
// decoding is finished
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_limelight_nvstream_av_audio_OpusDecoder_destroy(JNIEnv *env, jobject this) {
|
||||
nv_opus_destroy();
|
||||
}
|
||||
|
||||
// packets must be decoded in order
|
||||
// a packet loss must call this function with NULL indata and 0 inlen
|
||||
// returns the number of decoded bytes
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_limelight_nvstream_av_audio_OpusDecoder_decode(
|
||||
JNIEnv *env, jobject this, // JNI parameters
|
||||
jbyteArray indata, jint inoff, jint inlen, // Input parameters
|
||||
jbyteArray outpcmdata) // Output parameter
|
||||
{
|
||||
jint ret;
|
||||
jbyte* jni_input_data;
|
||||
jbyte* jni_pcm_data;
|
||||
|
||||
jni_pcm_data = (*env)->GetByteArrayElements(env, outpcmdata, 0);
|
||||
if (indata != NULL) {
|
||||
jni_input_data = (*env)->GetByteArrayElements(env, indata, 0);
|
||||
|
||||
ret = nv_opus_decode(&jni_input_data[inoff], inlen, (jshort*)jni_pcm_data, SamplesPerChannel);
|
||||
|
||||
// The input data isn't changed so it can be safely aborted
|
||||
(*env)->ReleaseByteArrayElements(env, indata, jni_input_data, JNI_ABORT);
|
||||
}
|
||||
else {
|
||||
ret = nv_opus_decode(NULL, 0, (jshort*)jni_pcm_data, SamplesPerChannel);
|
||||
}
|
||||
|
||||
// Convert samples (2 bytes) per channel to total bytes returned
|
||||
if (ret > 0) {
|
||||
ret *= ChannelCount * 2;
|
||||
}
|
||||
|
||||
(*env)->ReleaseByteArrayElements(env, outpcmdata, jni_pcm_data, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 40 KiB |
9
app/src/main/res/drawable/ic_add.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="256dp"
|
||||
android:height="256dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13h-4v4h-2v-4L7,13v-2h4L11,7h2v4h4v2z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_computer.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="512dp"
|
||||
android:height="512dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:pathData="M21,2L3,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h7v2L8,20v2h8v-2h-2v-2h7c1.1,0 2,-0.9 2,-2L23,4c0,-1.1 -0.9,-2 -2,-2zM21,16L3,16L3,4h18v12z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_help.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="256dp"
|
||||
android:height="256dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_pc_offline.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:pathData="M23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4 -1.5,0 -2.89,0.19 -4.15,0.48L18.18,13.8 23.64,7zM17.04,15.22L3.27,1.44 2,2.72l2.05,2.06C1.91,5.76 0.59,6.82 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01 3.9,-4.86 3.32,3.32 1.27,-1.27 -3.46,-3.46z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_play.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,16.5v-9l6,4.5 -6,4.5z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_settings.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="256dp"
|
||||
android:height="256dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
||||
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 24 KiB |
@@ -9,41 +9,40 @@
|
||||
tools:context=".PcView" >
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/no_pc_found_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_centerHorizontal="true">
|
||||
<ImageView
|
||||
android:id="@+id/pcs_loading"
|
||||
android:layout_width="75dp"
|
||||
android:layout_height="75dp"
|
||||
android:src="@drawable/image_loading"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@+id/pcs_loading"
|
||||
android:layout_toEndOf="@+id/pcs_loading"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:gravity="center"
|
||||
android:text="@string/searching_pc"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/pcFragmentContainer"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toLeftOf="@+id/manuallyAddPc"
|
||||
android:layout_toStartOf="@+id/manuallyAddPc"
|
||||
android:layout_toRightOf="@+id/settingsButton"
|
||||
android:layout_toEndOf="@+id/settingsButton"/>
|
||||
android:layout_toEndOf="@+id/settingsButton">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/no_pc_found_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_centerHorizontal="true">
|
||||
<ProgressBar
|
||||
android:id="@+id/pcs_loading"
|
||||
android:layout_width="75dp"
|
||||
android:layout_height="75dp"
|
||||
android:indeterminate="true"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@+id/pcs_loading"
|
||||
android:layout_toEndOf="@+id/pcs_loading"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:gravity="center"
|
||||
android:text="@string/searching_pc"/>
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/settingsButton"
|
||||
@@ -51,11 +50,24 @@
|
||||
android:layout_height="65dp"
|
||||
android:cropToPadding="false"
|
||||
android:scaleType="fitXY"
|
||||
android:nextFocusDown="@+id/pcGridView"
|
||||
android:nextFocusForward="@id/helpButton"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:src="@drawable/settings"
|
||||
android:src="@drawable/ic_settings"
|
||||
style="?android:attr/borderlessButtonStyle"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/helpButton"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="65dp"
|
||||
android:cropToPadding="false"
|
||||
android:scaleType="fitXY"
|
||||
android:nextFocusForward="@id/manuallyAddPc"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_below="@id/settingsButton"
|
||||
android:src="@drawable/ic_help"
|
||||
style="?android:attr/borderlessButtonStyle"/>
|
||||
|
||||
<ImageButton
|
||||
@@ -64,11 +76,10 @@
|
||||
android:layout_height="65dp"
|
||||
android:cropToPadding="false"
|
||||
android:scaleType="fitXY"
|
||||
android:nextFocusDown="@+id/pcGridView"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:src="@drawable/add_computer"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_below="@id/helpButton"
|
||||
android:src="@drawable/ic_add"
|
||||
style="?android:attr/borderlessButtonStyle"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -9,41 +9,44 @@
|
||||
tools:context=".PcView" >
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/no_pc_found_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_centerHorizontal="true">
|
||||
<ImageView
|
||||
android:id="@+id/pcs_loading"
|
||||
android:layout_width="75dp"
|
||||
android:layout_height="75dp"
|
||||
android:src="@drawable/image_loading"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@+id/pcs_loading"
|
||||
android:layout_toEndOf="@+id/pcs_loading"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:gravity="center"
|
||||
android:text="@string/searching_pc"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/pcFragmentContainer"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:layout_toLeftOf="@+id/manuallyAddPc"
|
||||
android:layout_toStartOf="@+id/manuallyAddPc"
|
||||
android:layout_toRightOf="@+id/settingsButton"
|
||||
android:layout_toEndOf="@+id/settingsButton"
|
||||
android:layout_below="@+id/settingsButton"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentTop="true"/>
|
||||
>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/no_pc_found_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_centerHorizontal="true">
|
||||
<ProgressBar
|
||||
android:id="@+id/pcs_loading"
|
||||
android:layout_width="75dp"
|
||||
android:layout_height="75dp"
|
||||
android:indeterminate="true"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@+id/pcs_loading"
|
||||
android:layout_toEndOf="@+id/pcs_loading"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:gravity="center"
|
||||
android:text="@string/searching_pc"/>
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/settingsButton"
|
||||
@@ -54,7 +57,19 @@
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:src="@drawable/settings"
|
||||
android:src="@drawable/ic_settings"
|
||||
style="?android:attr/borderlessButtonStyle"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/helpButton"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="65dp"
|
||||
android:cropToPadding="false"
|
||||
android:scaleType="fitXY"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toRightOf="@+id/settingsButton"
|
||||
android:layout_toEndOf="@+id/settingsButton"
|
||||
android:src="@drawable/ic_help"
|
||||
style="?android:attr/borderlessButtonStyle"/>
|
||||
|
||||
<ImageButton
|
||||
@@ -66,7 +81,7 @@
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:src="@drawable/add_computer"
|
||||
android:src="@drawable/ic_add"
|
||||
style="?android:attr/borderlessButtonStyle"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
19
app/src/main/res/layout/activity_help.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/activity_help"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
tools:context="com.limelight.HelpActivity">
|
||||
|
||||
<WebView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true" />
|
||||
</RelativeLayout>
|
||||
@@ -8,6 +8,14 @@
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<ProgressBar
|
||||
android:id="@+id/grid_spinner"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_width="75dp"
|
||||
android:layout_height="75dp"
|
||||
android:indeterminate="true">
|
||||
</ProgressBar>
|
||||
<ImageView
|
||||
android:id="@+id/grid_image"
|
||||
android:cropToPadding="false"
|
||||
|
||||
@@ -8,6 +8,14 @@
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<ProgressBar
|
||||
android:id="@+id/grid_spinner"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:indeterminate="true">
|
||||
</ProgressBar>
|
||||
<ImageView
|
||||
android:id="@+id/grid_image"
|
||||
android:cropToPadding="false"
|
||||
|
||||
@@ -10,23 +10,28 @@
|
||||
<ImageView
|
||||
android:id="@+id/grid_image"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="100dp">
|
||||
android:layout_width="125dp"
|
||||
android:layout_height="125dp">
|
||||
</ImageView>
|
||||
<ImageView
|
||||
android:id="@+id/grid_overlay"
|
||||
android:layout_marginTop="15dp"
|
||||
android:layout_marginLeft="65dp"
|
||||
android:layout_marginStart="65dp"
|
||||
android:layout_marginRight="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="28dp"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp">
|
||||
</ImageView>
|
||||
<ProgressBar
|
||||
android:id="@+id/grid_spinner"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="28dp"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:indeterminate="true">
|
||||
</ProgressBar>
|
||||
</RelativeLayout>
|
||||
<TextView
|
||||
android:id="@+id/grid_text"
|
||||
android:layout_width="150dp"
|
||||
android:layout_width="125dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/grid_image_layout"
|
||||
android:layout_marginTop="10dp"
|
||||
|
||||
@@ -10,23 +10,28 @@
|
||||
<ImageView
|
||||
android:id="@+id/grid_image"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="67dp">
|
||||
android:layout_width="75dp"
|
||||
android:layout_height="75dp">
|
||||
</ImageView>
|
||||
<ImageView
|
||||
android:id="@+id/grid_overlay"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginLeft="42dp"
|
||||
android:layout_marginStart="42dp"
|
||||
android:layout_marginRight="13dp"
|
||||
android:layout_marginEnd="13dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_width="33dp"
|
||||
android:layout_height="33dp">
|
||||
</ImageView>
|
||||
<ProgressBar
|
||||
android:id="@+id/grid_spinner"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_width="33dp"
|
||||
android:layout_height="33dp"
|
||||
android:indeterminate="true">
|
||||
</ProgressBar>
|
||||
</RelativeLayout>
|
||||
<TextView
|
||||
android:id="@+id/grid_text"
|
||||
android:layout_width="100dp"
|
||||
android:layout_width="75dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/grid_image_layout"
|
||||
android:layout_marginTop="10dp"
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
android:columnWidth="160dp"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:nextFocusLeft="@id/settingsButton"
|
||||
android:gravity="center"/>
|
||||
</LinearLayout>
|
||||
@@ -10,5 +10,6 @@
|
||||
android:columnWidth="105dp"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:nextFocusLeft="@id/settingsButton"
|
||||
android:gravity="center"/>
|
||||
</LinearLayout>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_pc_scut.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_pc_scut.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_pc_scut.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_pc_scut.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_pc_scut.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
14
app/src/main/res/values-es/arrays.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="decoder_names">
|
||||
<item>Automático</item>
|
||||
<item>Forzar decodificación por software</item>
|
||||
<item>Forzar decodificación por hardware</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="video_format_names">
|
||||
<item>Usar H.265 solo si es estable</item>
|
||||
<item>Siempre usar H.265 (puede fallar)</item>
|
||||
<item>Nunca usar H.265</item>
|
||||
</string-array>
|
||||
</resources>
|
||||