Compare commits
207 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b9f1142af7 | |||
| 38a6a2b74a | |||
| fd2421618a | |||
| 79a9ea7179 | |||
| 34a11c9262 | |||
| 84a9845c1d | |||
| 5b05220008 | |||
| b2bd7257e1 | |||
| 46a998c113 | |||
| 60cd951774 | |||
| d4f8d8f689 | |||
| 608a0ebb5b | |||
| f01a15d182 | |||
| 0268b4f958 | |||
| d71cf0eb98 | |||
| 10ab40f823 | |||
| 427edfa021 | |||
| 6f18831d5c | |||
| a3db09f422 | |||
| d185a05b1d | |||
| 78e575504a | |||
| 0a0be19b69 | |||
| 0792157e9d | |||
| cdd0ecf0b7 | |||
| 1ac721a35b | |||
| e49b1c92a2 | |||
| db4295bf83 | |||
| 824c37f9d5 | |||
| acf4426952 | |||
| e8c50342ab | |||
| 598995de3b | |||
| 01cf0cc649 | |||
| fa560f462f | |||
| f6e40118a9 | |||
| fe7148dbd4 | |||
| 60de065836 | |||
| 6f82f82abb | |||
| 42f18cb4ac | |||
| 1bbd0054c2 | |||
| acdde37a3a | |||
| ad40e12167 | |||
| 1b3322b5ee | |||
| 6340ec6c6d | |||
| babd92c8c0 | |||
| 7f1fe5f520 | |||
| 01458770d2 | |||
| 8d05f044f5 | |||
| f5680b59a5 | |||
| 0ecf86c7ed | |||
| 6789e8d497 | |||
| 7d0160d556 | |||
| f6a0990432 | |||
| 5d6094df97 | |||
| d98d4aeda2 | |||
| 852dcf5a2d | |||
| 82e5aa122d | |||
| fe237d1da3 | |||
| e199fcd2d9 | |||
| d7c6f63592 | |||
| 4b9c6b149a | |||
| d1e41e41a1 | |||
| 96dfe25a14 | |||
| f76d78607a | |||
| a96f688bb2 | |||
| 90a1e68c68 | |||
| b287606106 | |||
| a413185085 | |||
| aa1b283570 | |||
| f07c886711 | |||
| e66b1ebec9 | |||
| 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 |
@@ -30,6 +30,8 @@ Thumbs.db
|
|||||||
#.idea/workspace.xml - remove # and delete .idea if it better suit your needs.
|
#.idea/workspace.xml - remove # and delete .idea if it better suit your needs.
|
||||||
.gradle
|
.gradle
|
||||||
build/
|
build/
|
||||||
|
app/app.iml
|
||||||
|
|
||||||
# Compiled JNI libraries folder
|
# Compiled JNI libraries folder
|
||||||
**/jniLibs
|
**/jniLibs
|
||||||
|
app/.externalNativeBuild/
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
[submodule "app/src/main/jni/jnienet/enet"]
|
[submodule "moonlight-common"]
|
||||||
path = app/src/main/jni/jnienet/enet
|
path = moonlight-common
|
||||||
url = https://github.com/cgutman/enet.git
|
url = https://github.com/moonlight-stream/moonlight-common.git
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function p_h264raw.dissector(buf, pkt, root)
|
|||||||
|
|
||||||
local i = 0
|
local i = 0
|
||||||
local data_start = -1
|
local data_start = -1
|
||||||
while i < buf:len do
|
while i < buf:len() do
|
||||||
-- Make sure we have a potential start sequence and type
|
-- Make sure we have a potential start sequence and type
|
||||||
if buf:len() - i < 5 then
|
if buf:len() - i < 5 then
|
||||||
-- We need more data
|
-- We need more data
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#Moonlight
|
# Moonlight
|
||||||
|
|
||||||
[Moonlight](http://moonlight-stream.com) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
|
[Moonlight](http://moonlight-stream.com) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
|
||||||
We reverse engineered the Shield streaming software and created a version that can be run on any Android device.
|
We reverse engineered the Shield streaming software and created a version that can be run on any Android device.
|
||||||
@@ -10,35 +10,35 @@ whether in your own home or over the internet.
|
|||||||
|
|
||||||
Check our [wiki](https://github.com/moonlight-stream/moonlight-docs/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
|
## Features
|
||||||
|
|
||||||
* Streams any of your games from your PC to your Android device
|
* Streams any of your games from your PC to your Android device
|
||||||
* Full gamepad support for MOGA, Xbox 360, PS3, OUYA, and Shield
|
* Full gamepad support for MOGA, Xbox 360, PS3, OUYA, and Shield
|
||||||
* Automatically finds GameStream-compatible PCs on your network
|
* Automatically finds GameStream-compatible PCs on your network
|
||||||
|
|
||||||
##Installation
|
## Installation
|
||||||
|
|
||||||
* Download and install Moonlight for Android from
|
* Download and install Moonlight for Android from
|
||||||
[Google Play](https://play.google.com/store/apps/details?id=com.limelight), [Amazon App Store](http://www.amazon.com/gp/product/B00JK4MFN2), or directly from the [releases page](https://github.com/moonlight-stream/moonlight-android/releases)
|
[Google Play](https://play.google.com/store/apps/details?id=com.limelight), [Amazon App Store](http://www.amazon.com/gp/product/B00JK4MFN2), or directly from the [releases page](https://github.com/moonlight-stream/moonlight-android/releases)
|
||||||
* Download [GeForce Experience](http://www.geforce.com/geforce-experience) and install on your Windows PC
|
* Download [GeForce Experience](http://www.geforce.com/geforce-experience) and install on your Windows PC
|
||||||
|
|
||||||
##Requirements
|
## Requirements
|
||||||
|
|
||||||
* [GameStream compatible](http://shield.nvidia.com/play-pc-games/) computer with GTX 600/700 series GPU
|
* [GameStream compatible](http://shield.nvidia.com/play-pc-games/) computer with an NVIDIA GeForce GTX 600 series or higher desktop or mobile GPU (GT-series and AMD GPUs not supported)
|
||||||
* Android device running 4.1 (Jelly Bean) or higher
|
* Android device running 4.1 (Jelly Bean) or higher
|
||||||
* High-end wireless router (802.11n dual-band recommended)
|
* High-end wireless router (802.11n dual-band recommended)
|
||||||
|
|
||||||
##Usage
|
## Usage
|
||||||
|
|
||||||
* Turn on GameStream in the GFE settings
|
* Turn on GameStream in the GFE settings
|
||||||
* If you are connecting from outside the same network, turn on internet
|
* If you are connecting from outside the same network, turn on internet
|
||||||
streaming
|
streaming
|
||||||
* When on the same network as your PC, open Moonlight and tap on your PC in the list
|
* When on the same network as your PC, open Moonlight and tap on your PC in the list
|
||||||
* Accept the pairing confirmation on your PC
|
* Accept the pairing confirmation on your PC and add the PIN if needed
|
||||||
* Tap your PC again to view the list of apps to stream
|
* Tap your PC again to view the list of apps to stream
|
||||||
* Play games!
|
* Play games!
|
||||||
|
|
||||||
##Contribute
|
## Contribute
|
||||||
|
|
||||||
This project is being actively developed at [XDA Developers](http://forum.xda-developers.com/showthread.php?t=2505510)
|
This project is being actively developed at [XDA Developers](http://forum.xda-developers.com/showthread.php?t=2505510)
|
||||||
|
|
||||||
@@ -46,7 +46,13 @@ This project is being actively developed at [XDA Developers](http://forum.xda-de
|
|||||||
2. Write code
|
2. Write code
|
||||||
3. Send Pull Requests
|
3. Send Pull Requests
|
||||||
|
|
||||||
##Authors
|
## 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)
|
* [Cameron Gutman](https://github.com/cgutman)
|
||||||
* [Diego Waxemberg](https://github.com/dwaxemberg)
|
* [Diego Waxemberg](https://github.com/dwaxemberg)
|
||||||
|
|||||||
@@ -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,40 @@ import org.apache.tools.ant.taskdefs.condition.Os
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 24
|
compileSdkVersion 26
|
||||||
buildToolsVersion "24.0.0"
|
buildToolsVersion '26.0.1'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 24
|
targetSdkVersion 26
|
||||||
|
|
||||||
versionName "4.6"
|
versionName "5.2"
|
||||||
versionCode = 103
|
versionCode = 131
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flavorDimensions "root"
|
||||||
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
root {
|
root {
|
||||||
|
// Android O has native mouse capture, so don't show the rooted
|
||||||
|
// version to devices running O on the Play Store.
|
||||||
|
maxSdkVersion 25
|
||||||
|
|
||||||
applicationId "com.limelight.root"
|
applicationId "com.limelight.root"
|
||||||
|
ndk {
|
||||||
|
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64"
|
||||||
|
}
|
||||||
|
|
||||||
|
dimension "root"
|
||||||
}
|
}
|
||||||
|
|
||||||
nonRoot {
|
nonRoot {
|
||||||
applicationId "com.limelight"
|
applicationId "com.limelight"
|
||||||
|
ndk {
|
||||||
|
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64"
|
||||||
|
}
|
||||||
|
|
||||||
|
dimension "root"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,55 +48,21 @@ android {
|
|||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled false
|
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
|
externalNativeBuild {
|
||||||
// bundled with limelight-common.jar
|
ndkBuild {
|
||||||
packagingOptions {
|
path "src/main/jni/Android.mk"
|
||||||
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'
|
|
||||||
}
|
}
|
||||||
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 {
|
dependencies {
|
||||||
compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.52'
|
implementation 'org.bouncycastle:bcprov-jdk15on:1.57'
|
||||||
compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.52'
|
implementation 'org.bouncycastle:bcpkix-jdk15on:1.57'
|
||||||
|
implementation files('libs/jcodec-0.1.9-patched.jar')
|
||||||
|
|
||||||
compile group: 'com.squareup.okhttp', name: 'okhttp', version:'2.4.0'
|
implementation project(':moonlight-common')
|
||||||
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 files('libs/jcodec-0.1.9-patched.jar')
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,60 +7,104 @@
|
|||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
<uses-feature
|
||||||
<uses-feature android:name="android.hardware.wifi" android:required="false" />
|
android:name="android.hardware.touchscreen"
|
||||||
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
|
android:required="false" />
|
||||||
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
|
<uses-feature
|
||||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
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" />
|
||||||
|
|
||||||
|
<!-- Disable legacy input emulation on ChromeOS -->
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.type.pc"
|
||||||
|
android:required="false"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
android:isGame="true"
|
||||||
android:banner="@drawable/atv_banner"
|
android:banner="@drawable/atv_banner"
|
||||||
android:theme="@style/AppTheme" >
|
android:appCategory="game"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
|
android:theme="@style/AppTheme">
|
||||||
|
|
||||||
<!-- Samsung multi-window support -->
|
<!-- Samsung multi-window support -->
|
||||||
<uses-library android:name="com.sec.android.app.multiwindow" android:required="false" />
|
<uses-library
|
||||||
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
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
|
<activity
|
||||||
android:name=".PcView"
|
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>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
|
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
|
||||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
<category android:name="tv.ouya.intent.category.APP" />
|
<category android:name="tv.ouya.intent.category.APP" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</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
|
<activity
|
||||||
android:name=".AppView"
|
android:name=".AppView"
|
||||||
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">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="com.limelight.PcView" />
|
android:value="com.limelight.PcView" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".preferences.StreamSettings"
|
android:name=".preferences.StreamSettings"
|
||||||
android:label="Streaming Settings" >
|
android:resizeableActivity="true"
|
||||||
|
android:label="Streaming Settings">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="com.limelight.PcView" />
|
android:value="com.limelight.PcView" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".preferences.AddComputerManually"
|
android:name=".preferences.AddComputerManually"
|
||||||
android:label="Add Computer Manually" >
|
android:resizeableActivity="true"
|
||||||
|
android:label="Add Computer Manually">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="com.limelight.PcView" />
|
android:value="com.limelight.PcView" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".Game"
|
android:name=".Game"
|
||||||
|
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||||
android:screenOrientation="sensorLandscape"
|
android:screenOrientation="sensorLandscape"
|
||||||
android:theme="@style/StreamTheme"
|
android:noHistory="true"
|
||||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection" >
|
android:resizeableActivity="true"
|
||||||
|
android:theme="@style/StreamTheme">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="com.limelight.AppView" />
|
android:value="com.limelight.AppView" />
|
||||||
@@ -75,6 +119,15 @@
|
|||||||
<service
|
<service
|
||||||
android:name=".binding.input.driver.UsbDriverService"
|
android:name=".binding.input.driver.UsbDriverService"
|
||||||
android:label="Usb Driver Service" />
|
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>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
After Width: | Height: | Size: 58 KiB |
@@ -2,7 +2,6 @@ package com.limelight;
|
|||||||
|
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import com.limelight.computers.ComputerManagerListener;
|
import com.limelight.computers.ComputerManagerListener;
|
||||||
@@ -11,12 +10,14 @@ import com.limelight.grid.AppGridAdapter;
|
|||||||
import com.limelight.nvstream.http.ComputerDetails;
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
import com.limelight.nvstream.http.NvApp;
|
import com.limelight.nvstream.http.NvApp;
|
||||||
import com.limelight.nvstream.http.NvHTTP;
|
import com.limelight.nvstream.http.NvHTTP;
|
||||||
|
import com.limelight.nvstream.http.PairingManager;
|
||||||
import com.limelight.preferences.PreferenceConfiguration;
|
import com.limelight.preferences.PreferenceConfiguration;
|
||||||
import com.limelight.ui.AdapterFragment;
|
import com.limelight.ui.AdapterFragment;
|
||||||
import com.limelight.ui.AdapterFragmentCallbacks;
|
import com.limelight.ui.AdapterFragmentCallbacks;
|
||||||
import com.limelight.utils.CacheHelper;
|
import com.limelight.utils.CacheHelper;
|
||||||
import com.limelight.utils.Dialog;
|
import com.limelight.utils.Dialog;
|
||||||
import com.limelight.utils.ServerHelper;
|
import com.limelight.utils.ServerHelper;
|
||||||
|
import com.limelight.utils.ShortcutHelper;
|
||||||
import com.limelight.utils.SpinnerDialog;
|
import com.limelight.utils.SpinnerDialog;
|
||||||
import com.limelight.utils.UiHelper;
|
import com.limelight.utils.UiHelper;
|
||||||
|
|
||||||
@@ -25,7 +26,6 @@ import android.app.Service;
|
|||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
@@ -43,6 +43,7 @@ import android.widget.AdapterView.AdapterContextMenuInfo;
|
|||||||
public class AppView extends Activity implements AdapterFragmentCallbacks {
|
public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||||
private AppGridAdapter appGridAdapter;
|
private AppGridAdapter appGridAdapter;
|
||||||
private String uuidString;
|
private String uuidString;
|
||||||
|
private ShortcutHelper shortcutHelper;
|
||||||
|
|
||||||
private ComputerDetails computer;
|
private ComputerDetails computer;
|
||||||
private ComputerManagerService.ApplistPoller poller;
|
private ComputerManagerService.ApplistPoller poller;
|
||||||
@@ -78,6 +79,10 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
|
|
||||||
// Get the computer object
|
// Get the computer object
|
||||||
computer = managerBinder.getComputer(UUID.fromString(uuidString));
|
computer = managerBinder.getComputer(UUID.fromString(uuidString));
|
||||||
|
if (computer == null) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
appGridAdapter = new AppGridAdapter(AppView.this,
|
appGridAdapter = new AppGridAdapter(AppView.this,
|
||||||
@@ -90,12 +95,15 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
return;
|
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
|
// Start updates
|
||||||
startComputerUpdates();
|
startComputerUpdates();
|
||||||
|
|
||||||
// Load the app grid with cached data (if possible)
|
|
||||||
populateAppGridWithCache();
|
|
||||||
|
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -132,7 +140,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
|
|
||||||
managerBinder.startPolling(new ComputerManagerListener() {
|
managerBinder.startPolling(new ComputerManagerListener() {
|
||||||
@Override
|
@Override
|
||||||
public void notifyComputerUpdated(ComputerDetails details) {
|
public void notifyComputerUpdated(final ComputerDetails details) {
|
||||||
// Do nothing if updates are suspended
|
// Do nothing if updates are suspended
|
||||||
if (suspendGridUpdates) {
|
if (suspendGridUpdates) {
|
||||||
return;
|
return;
|
||||||
@@ -157,6 +165,24 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
return;
|
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
|
// App list is the same or empty
|
||||||
if (details.rawAppList == null || details.rawAppList.equals(lastRawApplist)) {
|
if (details.rawAppList == null || details.rawAppList.equals(lastRawApplist)) {
|
||||||
|
|
||||||
@@ -175,6 +201,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
updateUiWithAppList(NvHTTP.getAppListByReader(new StringReader(details.rawAppList)));
|
updateUiWithAppList(NvHTTP.getAppListByReader(new StringReader(details.rawAppList)));
|
||||||
|
updateUiWithServerinfo(details);
|
||||||
|
|
||||||
if (blockingLoadSpinner != null) {
|
if (blockingLoadSpinner != null) {
|
||||||
blockingLoadSpinner.dismiss();
|
blockingLoadSpinner.dismiss();
|
||||||
@@ -208,12 +235,9 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
shortcutHelper = new ShortcutHelper(this);
|
||||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
|
||||||
Configuration config = new Configuration(getResources().getConfiguration());
|
UiHelper.setLocale(this);
|
||||||
config.locale = new Locale(locale);
|
|
||||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
|
||||||
}
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_app_view);
|
setContentView(R.layout.activity_app_view);
|
||||||
|
|
||||||
@@ -221,11 +245,17 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
|
|
||||||
uuidString = getIntent().getStringExtra(UUID_EXTRA);
|
uuidString = getIntent().getStringExtra(UUID_EXTRA);
|
||||||
|
|
||||||
String labelText = getResources().getString(R.string.title_applist)+" "+getIntent().getStringExtra(NAME_EXTRA);
|
String computerName = getIntent().getStringExtra(NAME_EXTRA);
|
||||||
TextView label = (TextView) findViewById(R.id.appListText);
|
|
||||||
|
String labelText = getResources().getString(R.string.title_applist)+" "+computerName;
|
||||||
|
TextView label = findViewById(R.id.appListText);
|
||||||
setTitle(labelText);
|
setTitle(labelText);
|
||||||
label.setText(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
|
// Bind to the computer manager service
|
||||||
bindService(new Intent(this, ComputerManagerService.class), serviceConnection,
|
bindService(new Intent(this, ComputerManagerService.class), serviceConnection,
|
||||||
Service.BIND_AUTO_CREATE);
|
Service.BIND_AUTO_CREATE);
|
||||||
@@ -282,27 +312,14 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
stopComputerUpdates();
|
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
|
@Override
|
||||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||||
super.onCreateContextMenu(menu, v, menuInfo);
|
super.onCreateContextMenu(menu, v, menuInfo);
|
||||||
|
|
||||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
||||||
AppObject selectedApp = (AppObject) appGridAdapter.getItem(info.position);
|
AppObject selectedApp = (AppObject) appGridAdapter.getItem(info.position);
|
||||||
int runningAppId = getRunningAppId();
|
if (lastRunningAppId != 0) {
|
||||||
if (runningAppId != -1) {
|
if (lastRunningAppId == selectedApp.app.getAppId()) {
|
||||||
if (runningAppId == selectedApp.app.getAppId()) {
|
|
||||||
menu.add(Menu.NONE, START_OR_RESUME_ID, 1, getResources().getString(R.string.applist_menu_resume));
|
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));
|
menu.add(Menu.NONE, QUIT_ID, 2, getResources().getString(R.string.applist_menu_quit));
|
||||||
}
|
}
|
||||||
@@ -378,19 +395,19 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
AppObject existingApp = (AppObject) appGridAdapter.getItem(i);
|
AppObject existingApp = (AppObject) appGridAdapter.getItem(i);
|
||||||
|
|
||||||
// There can only be one or zero apps running.
|
// There can only be one or zero apps running.
|
||||||
if (existingApp.app.getIsRunning() &&
|
if (existingApp.isRunning &&
|
||||||
existingApp.app.getAppId() == details.runningGameId) {
|
existingApp.app.getAppId() == details.runningGameId) {
|
||||||
// This app was running and still is, so we're done now
|
// This app was running and still is, so we're done now
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (existingApp.app.getAppId() == details.runningGameId) {
|
else if (existingApp.app.getAppId() == details.runningGameId) {
|
||||||
// This app wasn't running but now is
|
// This app wasn't running but now is
|
||||||
existingApp.app.setIsRunning(true);
|
existingApp.isRunning = true;
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
else if (existingApp.app.getIsRunning()) {
|
else if (existingApp.isRunning) {
|
||||||
// This app was running but now isn't
|
// This app was running but now isn't
|
||||||
existingApp.app.setIsRunning(false);
|
existingApp.isRunning = false;
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -420,10 +437,6 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
AppObject existingApp = (AppObject) appGridAdapter.getItem(i);
|
AppObject existingApp = (AppObject) appGridAdapter.getItem(i);
|
||||||
if (existingApp.app.getAppId() == app.getAppId()) {
|
if (existingApp.app.getAppId() == app.getAppId()) {
|
||||||
// Found the app; update its properties
|
// 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())) {
|
if (!existingApp.app.getAppName().equals(app.getAppName())) {
|
||||||
existingApp.app.setAppName(app.getAppName());
|
existingApp.app.setAppName(app.getAppName());
|
||||||
updated = true;
|
updated = true;
|
||||||
@@ -493,7 +506,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
AppObject app = (AppObject) appGridAdapter.getItem(pos);
|
AppObject app = (AppObject) appGridAdapter.getItem(pos);
|
||||||
|
|
||||||
// Only open the context menu if something is running, otherwise start it
|
// Only open the context menu if something is running, otherwise start it
|
||||||
if (getRunningAppId() != -1) {
|
if (lastRunningAppId != 0) {
|
||||||
openContextMenu(arg1);
|
openContextMenu(arg1);
|
||||||
} else {
|
} else {
|
||||||
ServerHelper.doStart(AppView.this, app.app, computer, managerBinder);
|
ServerHelper.doStart(AppView.this, app.app, computer, managerBinder);
|
||||||
@@ -506,6 +519,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
|
|
||||||
public class AppObject {
|
public class AppObject {
|
||||||
public final NvApp app;
|
public final NvApp app;
|
||||||
|
public boolean isRunning;
|
||||||
|
|
||||||
public AppObject(NvApp app) {
|
public AppObject(NvApp app) {
|
||||||
if (app == null) {
|
if (app == null) {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,21 +10,23 @@ import com.limelight.binding.input.TouchContext;
|
|||||||
import com.limelight.binding.input.driver.UsbDriverService;
|
import com.limelight.binding.input.driver.UsbDriverService;
|
||||||
import com.limelight.binding.input.evdev.EvdevListener;
|
import com.limelight.binding.input.evdev.EvdevListener;
|
||||||
import com.limelight.binding.input.virtual_controller.VirtualController;
|
import com.limelight.binding.input.virtual_controller.VirtualController;
|
||||||
import com.limelight.binding.video.EnhancedDecoderRenderer;
|
import com.limelight.binding.video.CrashListener;
|
||||||
import com.limelight.binding.video.MediaCodecDecoderRenderer;
|
import com.limelight.binding.video.MediaCodecDecoderRenderer;
|
||||||
import com.limelight.binding.video.MediaCodecHelper;
|
import com.limelight.binding.video.MediaCodecHelper;
|
||||||
import com.limelight.nvstream.NvConnection;
|
import com.limelight.nvstream.NvConnection;
|
||||||
import com.limelight.nvstream.NvConnectionListener;
|
import com.limelight.nvstream.NvConnectionListener;
|
||||||
import com.limelight.nvstream.StreamConfiguration;
|
import com.limelight.nvstream.StreamConfiguration;
|
||||||
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
|
||||||
import com.limelight.nvstream.http.NvApp;
|
import com.limelight.nvstream.http.NvApp;
|
||||||
import com.limelight.nvstream.input.KeyboardPacket;
|
import com.limelight.nvstream.input.KeyboardPacket;
|
||||||
import com.limelight.nvstream.input.MouseButtonPacket;
|
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||||
|
import com.limelight.nvstream.jni.MoonBridge;
|
||||||
import com.limelight.preferences.PreferenceConfiguration;
|
import com.limelight.preferences.PreferenceConfiguration;
|
||||||
import com.limelight.ui.GameGestures;
|
import com.limelight.ui.GameGestures;
|
||||||
import com.limelight.ui.StreamView;
|
import com.limelight.ui.StreamView;
|
||||||
import com.limelight.utils.Dialog;
|
import com.limelight.utils.Dialog;
|
||||||
|
import com.limelight.utils.ShortcutHelper;
|
||||||
import com.limelight.utils.SpinnerDialog;
|
import com.limelight.utils.SpinnerDialog;
|
||||||
|
import com.limelight.utils.UiHelper;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@@ -33,7 +35,7 @@ import android.content.ComponentName;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.content.res.Configuration;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.hardware.input.InputManager;
|
import android.hardware.input.InputManager;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
@@ -59,8 +61,6 @@ import android.widget.FrameLayout;
|
|||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
|
|
||||||
public class Game extends Activity implements SurfaceHolder.Callback,
|
public class Game extends Activity implements SurfaceHolder.Callback,
|
||||||
OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener,
|
OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener,
|
||||||
@@ -81,9 +81,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
private ControllerHandler controllerHandler;
|
private ControllerHandler controllerHandler;
|
||||||
private VirtualController virtualController;
|
private VirtualController virtualController;
|
||||||
private KeyboardTranslator keybTranslator;
|
|
||||||
|
|
||||||
private PreferenceConfiguration prefConfig;
|
private PreferenceConfiguration prefConfig;
|
||||||
|
private SharedPreferences tombstonePrefs;
|
||||||
|
|
||||||
private NvConnection conn;
|
private NvConnection conn;
|
||||||
private SpinnerDialog spinner;
|
private SpinnerDialog spinner;
|
||||||
@@ -97,12 +97,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
private boolean grabComboDown = false;
|
private boolean grabComboDown = false;
|
||||||
private StreamView streamView;
|
private StreamView streamView;
|
||||||
|
|
||||||
private EnhancedDecoderRenderer decoderRenderer;
|
private ShortcutHelper shortcutHelper;
|
||||||
|
|
||||||
|
private MediaCodecDecoderRenderer decoderRenderer;
|
||||||
|
|
||||||
private WifiManager.WifiLock wifiLock;
|
private WifiManager.WifiLock wifiLock;
|
||||||
|
|
||||||
private int drFlags = 0;
|
|
||||||
|
|
||||||
private boolean connectedToUsbDriverService = false;
|
private boolean connectedToUsbDriverService = false;
|
||||||
private ServiceConnection usbDriverServiceConnection = new ServiceConnection() {
|
private ServiceConnection usbDriverServiceConnection = new ServiceConnection() {
|
||||||
@Override
|
@Override
|
||||||
@@ -123,17 +123,16 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
public static final String EXTRA_APP_ID = "AppId";
|
public static final String EXTRA_APP_ID = "AppId";
|
||||||
public static final String EXTRA_UNIQUEID = "UniqueId";
|
public static final String EXTRA_UNIQUEID = "UniqueId";
|
||||||
public static final String EXTRA_STREAMING_REMOTE = "Remote";
|
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
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
shortcutHelper = new ShortcutHelper(this);
|
||||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
|
||||||
Configuration config = new Configuration(getResources().getConfiguration());
|
UiHelper.setLocale(this);
|
||||||
config.locale = new Locale(locale);
|
|
||||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't want a title bar
|
// We don't want a title bar
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
@@ -169,21 +168,33 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
// Read the stream preferences
|
// Read the stream preferences
|
||||||
prefConfig = PreferenceConfiguration.readPreferences(this);
|
prefConfig = PreferenceConfiguration.readPreferences(this);
|
||||||
|
tombstonePrefs = Game.this.getSharedPreferences("DecoderTombstone", 0);
|
||||||
|
|
||||||
if (prefConfig.stretchVideo) {
|
|
||||||
drFlags |= VideoDecoderRenderer.FLAG_FILL_SCREEN;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen for events on the game surface
|
// Listen for events on the game surface
|
||||||
streamView = (StreamView) findViewById(R.id.surfaceView);
|
streamView = findViewById(R.id.surfaceView);
|
||||||
streamView.setOnGenericMotionListener(this);
|
streamView.setOnGenericMotionListener(this);
|
||||||
streamView.setOnTouchListener(this);
|
streamView.setOnTouchListener(this);
|
||||||
|
|
||||||
|
inputCaptureProvider = InputCaptureManager.getInputCaptureProvider(this, this);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
// The view must be focusable for pointer capture to work.
|
||||||
|
streamView.setFocusable(true);
|
||||||
|
streamView.setDefaultFocusHighlightEnabled(false);
|
||||||
|
streamView.setOnCapturedPointerListener(new View.OnCapturedPointerListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onCapturedPointer(View view, MotionEvent motionEvent) {
|
||||||
|
return handleMotionEvent(motionEvent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Warn the user if they're on a metered connection
|
// Warn the user if they're on a metered connection
|
||||||
checkDataConnection();
|
checkDataConnection();
|
||||||
|
|
||||||
// Make sure Wi-Fi is fully powered up
|
// 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 = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "Limelight");
|
||||||
wifiLock.setReferenceCounted(false);
|
wifiLock.setReferenceCounted(false);
|
||||||
wifiLock.acquire();
|
wifiLock.acquire();
|
||||||
@@ -193,16 +204,35 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
int appId = Game.this.getIntent().getIntExtra(EXTRA_APP_ID, StreamConfiguration.INVALID_APP_ID);
|
int appId = Game.this.getIntent().getIntExtra(EXTRA_APP_ID, StreamConfiguration.INVALID_APP_ID);
|
||||||
String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID);
|
String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID);
|
||||||
boolean remote = Game.this.getIntent().getBooleanExtra(EXTRA_STREAMING_REMOTE, false);
|
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) {
|
if (appId == StreamConfiguration.INVALID_APP_ID) {
|
||||||
finish();
|
finish();
|
||||||
return;
|
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
|
// Initialize the MediaCodec helper before creating the decoder
|
||||||
MediaCodecHelper.initializeWithContext(this);
|
MediaCodecHelper.initializeWithContext(this);
|
||||||
|
|
||||||
decoderRenderer = new MediaCodecDecoderRenderer(prefConfig.videoFormat);
|
decoderRenderer = new MediaCodecDecoderRenderer(prefConfig.videoFormat,
|
||||||
|
prefConfig.bitrate,
|
||||||
|
prefConfig.batterySaver,
|
||||||
|
new CrashListener() {
|
||||||
|
@Override
|
||||||
|
public void notifyCrash(Exception e) {
|
||||||
|
// The MediaCodec instance is going down due to a crash
|
||||||
|
// let's tell the user something when they open the app again
|
||||||
|
|
||||||
|
// We must use commit because the app will crash when we return from this function
|
||||||
|
tombstonePrefs.edit().putInt("CrashCount", tombstonePrefs.getInt("CrashCount", 0) + 1).commit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tombstonePrefs.getInt("CrashCount", 0));
|
||||||
|
|
||||||
// Display a message to the user if H.265 was forced on but we still didn't find a decoder
|
// Display a message to the user if H.265 was forced on but we still didn't find a decoder
|
||||||
if (prefConfig.videoFormat == PreferenceConfiguration.FORCE_H265_ON && !decoderRenderer.isHevcSupported()) {
|
if (prefConfig.videoFormat == PreferenceConfiguration.FORCE_H265_ON && !decoderRenderer.isHevcSupported()) {
|
||||||
@@ -227,21 +257,18 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
.setApp(new NvApp(appName, appId))
|
.setApp(new NvApp(appName, appId))
|
||||||
.setBitrate(prefConfig.bitrate * 1000)
|
.setBitrate(prefConfig.bitrate * 1000)
|
||||||
.setEnableSops(prefConfig.enableSops)
|
.setEnableSops(prefConfig.enableSops)
|
||||||
.enableAdaptiveResolution((decoderRenderer.getCapabilities() &
|
|
||||||
VideoDecoderRenderer.CAPABILITY_ADAPTIVE_RESOLUTION) != 0)
|
|
||||||
.enableLocalAudioPlayback(prefConfig.playHostAudio)
|
.enableLocalAudioPlayback(prefConfig.playHostAudio)
|
||||||
.setMaxPacketSize(remote ? 1024 : 1292)
|
.setMaxPacketSize(remote ? 1024 : 1292)
|
||||||
.setRemote(remote)
|
.setRemote(remote)
|
||||||
.setHevcSupported(decoderRenderer.isHevcSupported())
|
.setHevcSupported(decoderRenderer.isHevcSupported())
|
||||||
.setAudioConfiguration(prefConfig.enable51Surround ?
|
.setAudioConfiguration(prefConfig.enable51Surround ?
|
||||||
StreamConfiguration.AUDIO_CONFIGURATION_5_1 :
|
MoonBridge.AUDIO_CONFIGURATION_51_SURROUND :
|
||||||
StreamConfiguration.AUDIO_CONFIGURATION_STEREO)
|
MoonBridge.AUDIO_CONFIGURATION_STEREO)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Initialize the connection
|
// Initialize the connection
|
||||||
conn = new NvConnection(host, uniqueId, Game.this, config, PlatformBinding.getCryptoProvider(this));
|
conn = new NvConnection(host, uniqueId, config, PlatformBinding.getCryptoProvider(this));
|
||||||
keybTranslator = new KeyboardTranslator(conn);
|
controllerHandler = new ControllerHandler(this, conn, this, prefConfig.multiController, prefConfig.deadzonePercentage);
|
||||||
controllerHandler = new ControllerHandler(conn, this, prefConfig.multiController, prefConfig.deadzonePercentage);
|
|
||||||
|
|
||||||
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
||||||
inputManager.registerInputDeviceListener(controllerHandler, null);
|
inputManager.registerInputDeviceListener(controllerHandler, null);
|
||||||
@@ -262,12 +289,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
getWindow().setSustainedPerformanceMode(true);
|
getWindow().setSustainedPerformanceMode(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
inputCaptureProvider = InputCaptureManager.getInputCaptureProvider(this, this);
|
|
||||||
|
|
||||||
if (prefConfig.onscreenController) {
|
if (prefConfig.onscreenController) {
|
||||||
// create virtual onscreen controller
|
// create virtual onscreen controller
|
||||||
virtualController = new VirtualController(conn,
|
virtualController = new VirtualController(conn,
|
||||||
(FrameLayout)findViewById(R.id.surfaceView).getParent(),
|
(FrameLayout)streamView.getParent(),
|
||||||
this);
|
this);
|
||||||
virtualController.refreshLayout();
|
virtualController.refreshLayout();
|
||||||
}
|
}
|
||||||
@@ -282,6 +307,20 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
streamView.getHolder().addCallback(this);
|
streamView.getHolder().addCallback(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWindowFocusChanged(boolean hasFocus) {
|
||||||
|
super.onWindowFocusChanged(hasFocus);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
// Capture is lost when focus is lost, so it must be requested again
|
||||||
|
// when focus is regained.
|
||||||
|
if (inputCaptureProvider.isCapturingEnabled() && hasFocus) {
|
||||||
|
// Recapture the pointer if focus was regained
|
||||||
|
streamView.requestPointerCapture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void prepareDisplayForRendering() {
|
private void prepareDisplayForRendering() {
|
||||||
Display display = getWindowManager().getDefaultDisplay();
|
Display display = getWindowManager().getDefaultDisplay();
|
||||||
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
|
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
|
||||||
@@ -410,11 +449,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onDestroy() {
|
||||||
super.onStop();
|
super.onDestroy();
|
||||||
|
|
||||||
SpinnerDialog.closeDialogs(this);
|
|
||||||
Dialog.closeDialogs();
|
|
||||||
|
|
||||||
if (controllerHandler != null) {
|
if (controllerHandler != null) {
|
||||||
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
||||||
@@ -428,8 +464,19 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
unbindService(usbDriverServiceConnection);
|
unbindService(usbDriverServiceConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroy the capture provider
|
||||||
|
inputCaptureProvider.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
SpinnerDialog.closeDialogs(this);
|
||||||
|
Dialog.closeDialogs();
|
||||||
|
|
||||||
if (conn != null) {
|
if (conn != null) {
|
||||||
VideoDecoderRenderer.VideoFormat videoFormat = conn.getActiveVideoFormat();
|
int videoFormat = decoderRenderer.getActiveVideoFormat();
|
||||||
|
|
||||||
displayedFailureDialog = true;
|
displayedFailureDialog = true;
|
||||||
stopConnection();
|
stopConnection();
|
||||||
@@ -448,11 +495,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add the video codec to the post-stream toast
|
// Add the video codec to the post-stream toast
|
||||||
if (message != null && videoFormat != VideoDecoderRenderer.VideoFormat.Unknown) {
|
if (message != null) {
|
||||||
if (videoFormat == VideoDecoderRenderer.VideoFormat.H265) {
|
if (videoFormat == MoonBridge.VIDEO_FORMAT_H265) {
|
||||||
message += " [H.265]";
|
message += " [H.265]";
|
||||||
}
|
}
|
||||||
else {
|
else if (videoFormat == MoonBridge.VIDEO_FORMAT_H264) {
|
||||||
message += " [H.264]";
|
message += " [H.264]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -460,6 +507,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
if (message != null) {
|
if (message != null) {
|
||||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear the tombstone count
|
||||||
|
if (tombstonePrefs.getInt("CrashCount", 0) != 0) {
|
||||||
|
tombstonePrefs.edit().putInt("CrashCount", 0).apply();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finish();
|
finish();
|
||||||
@@ -565,11 +617,21 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
return super.onKeyDown(keyCode, event);
|
return super.onKeyDown(keyCode, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try the controller handler first
|
boolean handled = false;
|
||||||
boolean handled = controllerHandler.handleButtonDown(event);
|
|
||||||
|
boolean detectedGamepad = event.getDevice() != null && ((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) {
|
if (!handled) {
|
||||||
// Try the keyboard handler
|
// Try the keyboard handler
|
||||||
short translated = keybTranslator.translate(event.getKeyCode());
|
short translated = KeyboardTranslator.translate(event.getKeyCode());
|
||||||
if (translated == 0) {
|
if (translated == 0) {
|
||||||
return super.onKeyDown(keyCode, event);
|
return super.onKeyDown(keyCode, event);
|
||||||
}
|
}
|
||||||
@@ -584,8 +646,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
return super.onKeyDown(keyCode, event);
|
return super.onKeyDown(keyCode, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
keybTranslator.sendKeyDown(translated,
|
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_DOWN, getModifierState());
|
||||||
getModifierState(event));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -598,11 +659,20 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
return super.onKeyUp(keyCode, event);
|
return super.onKeyUp(keyCode, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try the controller handler first
|
boolean handled = false;
|
||||||
boolean handled = controllerHandler.handleButtonUp(event);
|
boolean detectedGamepad = event.getDevice() != null && ((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) {
|
if (!handled) {
|
||||||
// Try the keyboard handler
|
// Try the keyboard handler
|
||||||
short translated = keybTranslator.translate(event.getKeyCode());
|
short translated = KeyboardTranslator.translate(event.getKeyCode());
|
||||||
if (translated == 0) {
|
if (translated == 0) {
|
||||||
return super.onKeyUp(keyCode, event);
|
return super.onKeyUp(keyCode, event);
|
||||||
}
|
}
|
||||||
@@ -616,8 +686,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
return super.onKeyUp(keyCode, event);
|
return super.onKeyUp(keyCode, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
keybTranslator.sendKeyUp(translated,
|
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_UP, getModifierState(event));
|
||||||
getModifierState(event));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -652,7 +721,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
|
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0 ||
|
||||||
|
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE)
|
||||||
{
|
{
|
||||||
// This case is for mice
|
// This case is for mice
|
||||||
if (event.getSource() == InputDevice.SOURCE_MOUSE ||
|
if (event.getSource() == InputDevice.SOURCE_MOUSE ||
|
||||||
@@ -661,6 +731,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
{
|
{
|
||||||
int changedButtons = event.getButtonState() ^ lastButtonState;
|
int changedButtons = event.getButtonState() ^ lastButtonState;
|
||||||
|
|
||||||
|
// Ignore mouse input if we're not capturing from our input source
|
||||||
|
if (!inputCaptureProvider.isCapturingActive()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
|
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
|
||||||
// Send the vertical scroll packet
|
// Send the vertical scroll packet
|
||||||
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
|
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
|
||||||
@@ -859,14 +934,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stageStarting(Stage stage) {
|
public void stageStarting(String stage) {
|
||||||
if (spinner != null) {
|
if (spinner != null) {
|
||||||
spinner.setMessage(getResources().getString(R.string.conn_starting)+" "+stage.getName());
|
spinner.setMessage(getResources().getString(R.string.conn_starting)+" "+stage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stageComplete(Stage stage) {
|
public void stageComplete(String stage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopConnection() {
|
private void stopConnection() {
|
||||||
@@ -874,36 +949,47 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
connecting = connected = false;
|
connecting = connected = false;
|
||||||
conn.stop();
|
conn.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable cursor visibility again
|
|
||||||
inputCaptureProvider.disableCapture();
|
|
||||||
|
|
||||||
// Destroy the capture provider
|
|
||||||
inputCaptureProvider.destroy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stageFailed(Stage stage) {
|
public void stageFailed(String stage, long errorCode) {
|
||||||
if (spinner != null) {
|
if (spinner != null) {
|
||||||
spinner.dismiss();
|
spinner.dismiss();
|
||||||
spinner = null;
|
spinner = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enable cursor visibility again
|
||||||
|
inputCaptureProvider.disableCapture();
|
||||||
|
|
||||||
if (!displayedFailureDialog) {
|
if (!displayedFailureDialog) {
|
||||||
displayedFailureDialog = true;
|
displayedFailureDialog = true;
|
||||||
stopConnection();
|
LimeLog.severe(stage+" failed: "+errorCode);
|
||||||
|
|
||||||
|
// If video initialization failed and the surface is still valid, display extra information for the user
|
||||||
|
if (stage.contains("video") && streamView.getHolder().getSurface().isValid()) {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Toast.makeText(Game.this, "Video decoder failed to initialize. Your device may not support the selected resolution.", Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title),
|
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
|
@Override
|
||||||
public void connectionTerminated(Exception e) {
|
public void connectionTerminated(long errorCode) {
|
||||||
|
// Enable cursor visibility again
|
||||||
|
inputCaptureProvider.disableCapture();
|
||||||
|
|
||||||
if (!displayedFailureDialog) {
|
if (!displayedFailureDialog) {
|
||||||
displayedFailureDialog = true;
|
displayedFailureDialog = true;
|
||||||
e.printStackTrace();
|
LimeLog.severe("Connection terminated: "+errorCode);
|
||||||
|
|
||||||
stopConnection();
|
stopConnection();
|
||||||
|
|
||||||
Dialog.displayDialog(this, getResources().getString(R.string.conn_terminated_title),
|
Dialog.displayDialog(this, getResources().getString(R.string.conn_terminated_title),
|
||||||
getResources().getString(R.string.conn_terminated_msg), true);
|
getResources().getString(R.string.conn_terminated_msg), true);
|
||||||
}
|
}
|
||||||
@@ -963,22 +1049,17 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
if (!connected && !connecting) {
|
if (!connected && !connecting) {
|
||||||
connecting = true;
|
connecting = true;
|
||||||
|
|
||||||
conn.start(PlatformBinding.getDeviceName(), holder, drFlags,
|
decoderRenderer.setRenderTarget(holder);
|
||||||
PlatformBinding.getAudioRenderer(), decoderRenderer);
|
conn.start(PlatformBinding.getAudioRenderer(), decoderRenderer, Game.this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||||
if (connected) {
|
// Let the decoder know immediately that the surface is gone
|
||||||
// HACK: Android is supposed to let you return from this function
|
decoderRenderer.prepareForStop();
|
||||||
// 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();
|
|
||||||
|
|
||||||
|
if (connected) {
|
||||||
stopConnection();
|
stopConnection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1023,17 +1104,17 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyboardEvent(boolean buttonDown, short keyCode) {
|
public void keyboardEvent(boolean buttonDown, short keyCode) {
|
||||||
short keyMap = keybTranslator.translate(keyCode);
|
short keyMap = KeyboardTranslator.translate(keyCode);
|
||||||
if (keyMap != 0) {
|
if (keyMap != 0) {
|
||||||
if (handleSpecialKeys(keyMap, buttonDown)) {
|
if (handleSpecialKeys(keyMap, buttonDown)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buttonDown) {
|
if (buttonDown) {
|
||||||
keybTranslator.sendKeyDown(keyMap, getModifierState());
|
conn.sendKeyboardInput(keyMap, KeyboardPacket.KEY_DOWN, getModifierState());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
keybTranslator.sendKeyUp(keyMap, getModifierState());
|
conn.sendKeyboardInput(keyMap, KeyboardPacket.KEY_UP, getModifierState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
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) {
|
||||||
|
return !(url.toUpperCase().startsWith("https://github.com/moonlight-stream/moonlight-docs/wiki/".toUpperCase()) ||
|
||||||
|
url.toUpperCase().startsWith("http://github.com/moonlight-stream/moonlight-docs/wiki/".toUpperCase()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,7 @@ package com.limelight;
|
|||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import com.limelight.binding.PlatformBinding;
|
import com.limelight.binding.PlatformBinding;
|
||||||
import com.limelight.binding.crypto.AndroidCryptoProvider;
|
import com.limelight.binding.crypto.AndroidCryptoProvider;
|
||||||
@@ -23,7 +21,9 @@ import com.limelight.preferences.StreamSettings;
|
|||||||
import com.limelight.ui.AdapterFragment;
|
import com.limelight.ui.AdapterFragment;
|
||||||
import com.limelight.ui.AdapterFragmentCallbacks;
|
import com.limelight.ui.AdapterFragmentCallbacks;
|
||||||
import com.limelight.utils.Dialog;
|
import com.limelight.utils.Dialog;
|
||||||
|
import com.limelight.utils.HelpLauncher;
|
||||||
import com.limelight.utils.ServerHelper;
|
import com.limelight.utils.ServerHelper;
|
||||||
|
import com.limelight.utils.ShortcutHelper;
|
||||||
import com.limelight.utils.UiHelper;
|
import com.limelight.utils.UiHelper;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@@ -31,6 +31,7 @@ import android.app.Service;
|
|||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
@@ -52,6 +53,7 @@ import android.widget.AdapterView.AdapterContextMenuInfo;
|
|||||||
public class PcView extends Activity implements AdapterFragmentCallbacks {
|
public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||||
private RelativeLayout noPcFoundLayout;
|
private RelativeLayout noPcFoundLayout;
|
||||||
private PcGridAdapter pcGridAdapter;
|
private PcGridAdapter pcGridAdapter;
|
||||||
|
private ShortcutHelper shortcutHelper;
|
||||||
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
||||||
private boolean freezeUpdates, runningPolling, inForeground;
|
private boolean freezeUpdates, runningPolling, inForeground;
|
||||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||||
@@ -107,8 +109,9 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
||||||
|
|
||||||
// Setup the list view
|
// Setup the list view
|
||||||
ImageButton settingsButton = (ImageButton) findViewById(R.id.settingsButton);
|
ImageButton settingsButton = findViewById(R.id.settingsButton);
|
||||||
ImageButton addComputerButton = (ImageButton) findViewById(R.id.manuallyAddPc);
|
ImageButton addComputerButton = findViewById(R.id.manuallyAddPc);
|
||||||
|
ImageButton helpButton = findViewById(R.id.helpButton);
|
||||||
|
|
||||||
settingsButton.setOnClickListener(new OnClickListener() {
|
settingsButton.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -123,12 +126,18 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
startActivity(i);
|
startActivity(i);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
helpButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
HelpLauncher.launchSetupGuide(PcView.this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
getFragmentManager().beginTransaction()
|
getFragmentManager().beginTransaction()
|
||||||
.replace(R.id.pcFragmentContainer, new AdapterFragment())
|
.replace(R.id.pcFragmentContainer, new AdapterFragment())
|
||||||
.commitAllowingStateLoss();
|
.commitAllowingStateLoss();
|
||||||
|
|
||||||
noPcFoundLayout = (RelativeLayout) findViewById(R.id.no_pc_found_layout);
|
noPcFoundLayout = findViewById(R.id.no_pc_found_layout);
|
||||||
if (pcGridAdapter.getCount() == 0) {
|
if (pcGridAdapter.getCount() == 0) {
|
||||||
noPcFoundLayout.setVisibility(View.VISIBLE);
|
noPcFoundLayout.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
@@ -142,12 +151,9 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
shortcutHelper = new ShortcutHelper(this);
|
||||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
|
||||||
Configuration config = new Configuration(getResources().getConfiguration());
|
UiHelper.setLocale(this);
|
||||||
config.locale = new Locale(locale);
|
|
||||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind to the computer manager service
|
// Bind to the computer manager service
|
||||||
bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection,
|
bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection,
|
||||||
@@ -158,6 +164,32 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
PreferenceConfiguration.readPreferences(this).smallIconMode);
|
PreferenceConfiguration.readPreferences(this).smallIconMode);
|
||||||
|
|
||||||
initializeViews();
|
initializeViews();
|
||||||
|
|
||||||
|
SharedPreferences prefs = getSharedPreferences("DecoderTombstone", 0);
|
||||||
|
int crashCount = prefs.getInt("CrashCount", 0);
|
||||||
|
int lastNotifiedCrashCount = prefs.getInt("LastNotifiedCrashCount", 0);
|
||||||
|
|
||||||
|
// Remember the last crash count we notified at, so we don't
|
||||||
|
// display the crash dialog every time the app is started until
|
||||||
|
// they stream again
|
||||||
|
if (crashCount != 0 && crashCount != lastNotifiedCrashCount) {
|
||||||
|
if (crashCount % 3 == 0) {
|
||||||
|
// At 3 consecutive crashes, we'll forcefully reset their settings
|
||||||
|
PreferenceConfiguration.resetStreamingSettings(this);
|
||||||
|
Dialog.displayDialog(this,
|
||||||
|
getResources().getString(R.string.title_decoding_reset),
|
||||||
|
getResources().getString(R.string.message_decoding_reset),
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Dialog.displayDialog(this,
|
||||||
|
getResources().getString(R.string.title_decoding_error),
|
||||||
|
getResources().getString(R.string.message_decoding_error),
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
prefs.edit().putInt("LastNotifiedCrashCount", crashCount).apply();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startComputerUpdates() {
|
private void startComputerUpdates() {
|
||||||
@@ -299,19 +331,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
// Stop updates and wait while pairing
|
// Stop updates and wait while pairing
|
||||||
stopComputerUpdates(true);
|
stopComputerUpdates(true);
|
||||||
|
|
||||||
InetAddress addr;
|
httpConn = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer),
|
||||||
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
|
|
||||||
addr = computer.localIp;
|
|
||||||
}
|
|
||||||
else if (computer.reachability == ComputerDetails.Reachability.REMOTE) {
|
|
||||||
addr = computer.remoteIp;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LimeLog.warning("Unknown reachability - using local IP");
|
|
||||||
addr = computer.localIp;
|
|
||||||
}
|
|
||||||
|
|
||||||
httpConn = new NvHTTP(addr,
|
|
||||||
managerBinder.getUniqueId(),
|
managerBinder.getUniqueId(),
|
||||||
PlatformBinding.getDeviceName(),
|
PlatformBinding.getDeviceName(),
|
||||||
PlatformBinding.getCryptoProvider(PcView.this));
|
PlatformBinding.getCryptoProvider(PcView.this));
|
||||||
@@ -334,10 +354,17 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
else if (pairState == PairingManager.PairState.FAILED) {
|
else if (pairState == PairingManager.PairState.FAILED) {
|
||||||
message = getResources().getString(R.string.pair_fail);
|
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) {
|
else if (pairState == PairingManager.PairState.PAIRED) {
|
||||||
// Just navigate to the app view without displaying a toast
|
// Just navigate to the app view without displaying a toast
|
||||||
message = null;
|
message = null;
|
||||||
success = true;
|
success = true;
|
||||||
|
|
||||||
|
// Invalidate reachability information after pairing to force
|
||||||
|
// a refresh before reading pair state again
|
||||||
|
managerBinder.invalidateStateForComputer(computer.uuid);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Should be no other values
|
// Should be no other values
|
||||||
@@ -429,19 +456,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
NvHTTP httpConn;
|
NvHTTP httpConn;
|
||||||
String message;
|
String message;
|
||||||
try {
|
try {
|
||||||
InetAddress addr;
|
httpConn = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer),
|
||||||
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
|
|
||||||
addr = computer.localIp;
|
|
||||||
}
|
|
||||||
else if (computer.reachability == ComputerDetails.Reachability.REMOTE) {
|
|
||||||
addr = computer.remoteIp;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LimeLog.warning("Unknown reachability - using local IP");
|
|
||||||
addr = computer.localIp;
|
|
||||||
}
|
|
||||||
|
|
||||||
httpConn = new NvHTTP(addr,
|
|
||||||
managerBinder.getUniqueId(),
|
managerBinder.getUniqueId(),
|
||||||
PlatformBinding.getDeviceName(),
|
PlatformBinding.getDeviceName(),
|
||||||
PlatformBinding.getCryptoProvider(PcView.this));
|
PlatformBinding.getCryptoProvider(PcView.this));
|
||||||
@@ -558,6 +573,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i);
|
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i);
|
||||||
|
|
||||||
if (details.equals(computer.details)) {
|
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.removeComputer(computer);
|
||||||
pcGridAdapter.notifyDataSetChanged();
|
pcGridAdapter.notifyDataSetChanged();
|
||||||
|
|
||||||
@@ -584,6 +603,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) {
|
if (existingEntry != null) {
|
||||||
// Replace the information in the existing entry
|
// Replace the information in the existing entry
|
||||||
existingEntry.details = details;
|
existingEntry.details = details;
|
||||||
|
|||||||
@@ -1,39 +1,86 @@
|
|||||||
package com.limelight.binding.audio;
|
package com.limelight.binding.audio;
|
||||||
|
|
||||||
|
import android.media.AudioAttributes;
|
||||||
import android.media.AudioFormat;
|
import android.media.AudioFormat;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.media.AudioTrack;
|
import android.media.AudioTrack;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
import com.limelight.LimeLog;
|
import com.limelight.LimeLog;
|
||||||
import com.limelight.nvstream.av.audio.AudioRenderer;
|
import com.limelight.nvstream.av.audio.AudioRenderer;
|
||||||
|
import com.limelight.nvstream.jni.MoonBridge;
|
||||||
|
|
||||||
public class AndroidAudioRenderer implements AudioRenderer {
|
public class AndroidAudioRenderer implements AudioRenderer {
|
||||||
|
|
||||||
private AudioTrack track;
|
private AudioTrack track;
|
||||||
|
|
||||||
@Override
|
private AudioTrack createAudioTrack(int channelConfig, int bufferSize, boolean lowLatency) {
|
||||||
public boolean streamInitialized(int channelCount, int channelMask, int samplesPerFrame, int sampleRate) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||||
int channelConfig;
|
return new AudioTrack(AudioManager.STREAM_MUSIC,
|
||||||
int bufferSize;
|
48000,
|
||||||
int bytesPerFrame = (samplesPerFrame * 2);
|
channelConfig,
|
||||||
|
AudioFormat.ENCODING_PCM_16BIT,
|
||||||
|
bufferSize,
|
||||||
|
AudioTrack.MODE_STREAM);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder()
|
||||||
|
.setUsage(AudioAttributes.USAGE_GAME);
|
||||||
|
AudioFormat format = new AudioFormat.Builder()
|
||||||
|
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
|
||||||
|
.setSampleRate(48000)
|
||||||
|
.setChannelMask(channelConfig)
|
||||||
|
.build();
|
||||||
|
|
||||||
switch (channelCount)
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||||
|
// Use FLAG_LOW_LATENCY on L through N
|
||||||
|
if (lowLatency) {
|
||||||
|
attributesBuilder.setFlags(AudioAttributes.FLAG_LOW_LATENCY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
AudioTrack.Builder trackBuilder = new AudioTrack.Builder()
|
||||||
|
.setAudioFormat(format)
|
||||||
|
.setAudioAttributes(attributesBuilder.build())
|
||||||
|
.setTransferMode(AudioTrack.MODE_STREAM)
|
||||||
|
.setBufferSizeInBytes(bufferSize);
|
||||||
|
|
||||||
|
// Use PERFORMANCE_MODE_LOW_LATENCY on O and later
|
||||||
|
if (lowLatency) {
|
||||||
|
trackBuilder.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return trackBuilder.build();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new AudioTrack(attributesBuilder.build(),
|
||||||
|
format,
|
||||||
|
bufferSize,
|
||||||
|
AudioTrack.MODE_STREAM,
|
||||||
|
AudioManager.AUDIO_SESSION_ID_GENERATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int setup(int audioConfiguration) {
|
||||||
|
int channelConfig;
|
||||||
|
int bytesPerFrame;
|
||||||
|
|
||||||
|
switch (audioConfiguration)
|
||||||
{
|
{
|
||||||
case 1:
|
case MoonBridge.AUDIO_CONFIGURATION_STEREO:
|
||||||
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
|
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||||
break;
|
bytesPerFrame = 2 * 240 * 2;
|
||||||
case 2:
|
break;
|
||||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
case MoonBridge.AUDIO_CONFIGURATION_51_SURROUND:
|
||||||
break;
|
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||||
case 4:
|
bytesPerFrame = 6 * 240 * 2;
|
||||||
channelConfig = AudioFormat.CHANNEL_OUT_QUAD;
|
break;
|
||||||
break;
|
default:
|
||||||
case 6:
|
LimeLog.severe("Decoder returned unhandled channel count");
|
||||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
return -1;
|
||||||
break;
|
|
||||||
default:
|
|
||||||
LimeLog.severe("Decoder returned unhandled channel count");
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're not supposed to request less than the minimum
|
// We're not supposed to request less than the minimum
|
||||||
@@ -41,62 +88,102 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
|||||||
// do this on many devices and it lowers audio latency.
|
// do this on many devices and it lowers audio latency.
|
||||||
// We'll try the small buffer size first and if it fails,
|
// We'll try the small buffer size first and if it fails,
|
||||||
// use the recommended larger buffer size.
|
// use the recommended larger buffer size.
|
||||||
try {
|
|
||||||
// Buffer two frames of audio if possible
|
|
||||||
bufferSize = bytesPerFrame * 2;
|
|
||||||
|
|
||||||
track = new AudioTrack(AudioManager.STREAM_MUSIC,
|
for (int i = 0; i < 4; i++) {
|
||||||
sampleRate,
|
boolean lowLatency;
|
||||||
channelConfig,
|
int bufferSize;
|
||||||
AudioFormat.ENCODING_PCM_16BIT,
|
|
||||||
bufferSize,
|
|
||||||
AudioTrack.MODE_STREAM);
|
|
||||||
track.play();
|
|
||||||
} catch (Exception e) {
|
|
||||||
// Try to release the AudioTrack if we got far enough
|
|
||||||
try {
|
|
||||||
if (track != null) {
|
|
||||||
track.release();
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
|
|
||||||
// Now try the larger buffer size
|
// We will try:
|
||||||
bufferSize = Math.max(AudioTrack.getMinBufferSize(sampleRate,
|
// 1) Small buffer, low latency mode
|
||||||
|
// 2) Large buffer, low latency mode
|
||||||
|
// 3) Small buffer, standard mode
|
||||||
|
// 4) Large buffer, standard mode
|
||||||
|
|
||||||
|
switch (i) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
lowLatency = true;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
lowLatency = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Unreachable
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (i) {
|
||||||
|
case 0:
|
||||||
|
case 2:
|
||||||
|
bufferSize = bytesPerFrame * 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
case 3:
|
||||||
|
// Try the larger buffer size
|
||||||
|
bufferSize = Math.max(AudioTrack.getMinBufferSize(48000,
|
||||||
channelConfig,
|
channelConfig,
|
||||||
AudioFormat.ENCODING_PCM_16BIT),
|
AudioFormat.ENCODING_PCM_16BIT),
|
||||||
bytesPerFrame * 2);
|
bytesPerFrame * 2);
|
||||||
|
|
||||||
// Round to next frame
|
// Round to next frame
|
||||||
bufferSize = (((bufferSize + (bytesPerFrame - 1)) / bytesPerFrame) * bytesPerFrame);
|
bufferSize = (((bufferSize + (bytesPerFrame - 1)) / bytesPerFrame) * bytesPerFrame);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Unreachable
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
track = new AudioTrack(AudioManager.STREAM_MUSIC,
|
// Skip low latency options if hardware sample rate isn't 48000Hz
|
||||||
sampleRate,
|
if (AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC) != 48000 && lowLatency) {
|
||||||
channelConfig,
|
continue;
|
||||||
AudioFormat.ENCODING_PCM_16BIT,
|
}
|
||||||
bufferSize,
|
|
||||||
AudioTrack.MODE_STREAM);
|
try {
|
||||||
track.play();
|
track = createAudioTrack(channelConfig, bufferSize, lowLatency);
|
||||||
|
track.play();
|
||||||
|
|
||||||
|
// Successfully created working AudioTrack. We're done here.
|
||||||
|
LimeLog.info("Audio track configuration: "+bufferSize+" "+lowLatency);
|
||||||
|
break;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Try to release the AudioTrack if we got far enough
|
||||||
|
e.printStackTrace();
|
||||||
|
try {
|
||||||
|
if (track != null) {
|
||||||
|
track.release();
|
||||||
|
track = null;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LimeLog.info("Audio track buffer size: "+bufferSize);
|
if (track == null) {
|
||||||
|
// Couldn't create any audio track for playback
|
||||||
return true;
|
return -2;
|
||||||
}
|
|
||||||
|
|
||||||
@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;
|
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;
|
package com.limelight.binding.input;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.hardware.input.InputManager;
|
import android.hardware.input.InputManager;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
|
import android.view.InputEvent;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.limelight.LimeLog;
|
import com.limelight.LimeLog;
|
||||||
import com.limelight.binding.input.driver.UsbDriverListener;
|
import com.limelight.binding.input.driver.UsbDriverListener;
|
||||||
import com.limelight.nvstream.NvConnection;
|
import com.limelight.nvstream.NvConnection;
|
||||||
import com.limelight.nvstream.input.ControllerPacket;
|
import com.limelight.nvstream.input.ControllerPacket;
|
||||||
|
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||||
import com.limelight.ui.GameGestures;
|
import com.limelight.ui.GameGestures;
|
||||||
import com.limelight.utils.Vector2d;
|
import com.limelight.utils.Vector2d;
|
||||||
|
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
public class ControllerHandler implements InputManager.InputDeviceListener, UsbDriverListener {
|
public class ControllerHandler implements InputManager.InputDeviceListener, UsbDriverListener {
|
||||||
|
|
||||||
private static final int MAXIMUM_BUMPER_UP_DELAY_MS = 100;
|
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;
|
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 SparseArray<UsbDeviceContext> usbDeviceContexts = new SparseArray<>();
|
||||||
|
|
||||||
private final NvConnection conn;
|
private final NvConnection conn;
|
||||||
|
private final Context activityContext;
|
||||||
private final double stickDeadzone;
|
private final double stickDeadzone;
|
||||||
private final InputDeviceContext defaultContext = new InputDeviceContext();
|
private final InputDeviceContext defaultContext = new InputDeviceContext();
|
||||||
private final GameGestures gestures;
|
private final GameGestures gestures;
|
||||||
@@ -42,7 +50,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
private final boolean multiControllerEnabled;
|
private final boolean multiControllerEnabled;
|
||||||
private short currentControllers;
|
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.conn = conn;
|
||||||
this.gestures = gestures;
|
this.gestures = gestures;
|
||||||
this.multiControllerEnabled = multiControllerEnabled;
|
this.multiControllerEnabled = multiControllerEnabled;
|
||||||
@@ -54,6 +63,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
int[] ids = InputDevice.getDeviceIds();
|
int[] ids = InputDevice.getDeviceIds();
|
||||||
for (int id : ids) {
|
for (int id : ids) {
|
||||||
InputDevice dev = InputDevice.getDevice(id);
|
InputDevice dev = InputDevice.getDevice(id);
|
||||||
|
if (dev == null) {
|
||||||
|
// This device was removed during enumeration
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if ((dev.getSources() & InputDevice.SOURCE_JOYSTICK) != 0 ||
|
if ((dev.getSources() & InputDevice.SOURCE_JOYSTICK) != 0 ||
|
||||||
(dev.getSources() & InputDevice.SOURCE_GAMEPAD) != 0) {
|
(dev.getSources() & InputDevice.SOURCE_GAMEPAD) != 0) {
|
||||||
// This looks like a gamepad, but we'll check X and Y to be sure
|
// This looks like a gamepad, but we'll check X and Y to be sure
|
||||||
@@ -123,7 +136,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
private void releaseControllerNumber(GenericControllerContext context) {
|
private void releaseControllerNumber(GenericControllerContext context) {
|
||||||
// If this device sent data as a gamepad, zero the values before removing
|
// If this device sent data as a gamepad, zero the values before removing
|
||||||
if (context.assignedControllerNumber) {
|
if (context.assignedControllerNumber) {
|
||||||
conn.sendControllerInput(context.controllerNumber, (short) 0,
|
conn.sendControllerInput(context.controllerNumber, getActiveControllerMask(),
|
||||||
|
(short) 0,
|
||||||
(byte) 0, (byte) 0,
|
(byte) 0, (byte) 0,
|
||||||
(short) 0, (short) 0,
|
(short) 0, (short) 0,
|
||||||
(short) 0, (short) 0);
|
(short) 0, (short) 0);
|
||||||
@@ -330,18 +344,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore the back buttonn if a controller has both buttons
|
|
||||||
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 ADT-1 controller needs a similar fixup to the ASUS Gamepad
|
// 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) {
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
|
||||||
// The device name provided is just "Gamepad" which is pretty useless, so we
|
// The device name provided is just "Gamepad" which is pretty useless, so we
|
||||||
@@ -395,6 +397,17 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
context.isServal = true;
|
context.isServal = true;
|
||||||
context.ignoreBack = 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);
|
LimeLog.info("Analog stick deadzone: "+context.leftStickDeadzoneRadius+" "+context.rightStickDeadzoneRadius);
|
||||||
@@ -403,21 +416,27 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
private InputDeviceContext getContextForDevice(InputDevice dev) {
|
private InputDeviceContext getContextForEvent(InputEvent event) {
|
||||||
// Unknown devices use the default context
|
// Unknown devices use the default context
|
||||||
if (dev == null) {
|
if (event.getDeviceId() == 0) {
|
||||||
return defaultContext;
|
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
|
// Return the existing context if it exists
|
||||||
InputDeviceContext context = inputDeviceContexts.get(dev.getId());
|
InputDeviceContext context = inputDeviceContexts.get(event.getDeviceId());
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise create a new context
|
// Otherwise create a new context
|
||||||
context = createInputDeviceContextForDevice(dev);
|
context = createInputDeviceContextForDevice(event.getDevice());
|
||||||
inputDeviceContexts.put(dev.getId(), context);
|
inputDeviceContexts.put(event.getDeviceId(), context);
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
@@ -444,6 +463,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) {
|
private void sendControllerInputPacket(GenericControllerContext originalContext) {
|
||||||
assignControllerNumberIfNeeded(originalContext);
|
assignControllerNumberIfNeeded(originalContext);
|
||||||
|
|
||||||
@@ -462,7 +491,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
// device before we send it.
|
// device before we send it.
|
||||||
for (int i = 0; i < inputDeviceContexts.size(); i++) {
|
for (int i = 0; i < inputDeviceContexts.size(); i++) {
|
||||||
GenericControllerContext context = inputDeviceContexts.valueAt(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;
|
inputMap |= context.inputMap;
|
||||||
leftTrigger |= maxByMagnitude(leftTrigger, context.leftTrigger);
|
leftTrigger |= maxByMagnitude(leftTrigger, context.leftTrigger);
|
||||||
rightTrigger |= maxByMagnitude(rightTrigger, context.rightTrigger);
|
rightTrigger |= maxByMagnitude(rightTrigger, context.rightTrigger);
|
||||||
@@ -474,7 +505,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
for (int i = 0; i < usbDeviceContexts.size(); i++) {
|
for (int i = 0; i < usbDeviceContexts.size(); i++) {
|
||||||
GenericControllerContext context = usbDeviceContexts.valueAt(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;
|
inputMap |= context.inputMap;
|
||||||
leftTrigger |= maxByMagnitude(leftTrigger, context.leftTrigger);
|
leftTrigger |= maxByMagnitude(leftTrigger, context.leftTrigger);
|
||||||
rightTrigger |= maxByMagnitude(rightTrigger, context.rightTrigger);
|
rightTrigger |= maxByMagnitude(rightTrigger, context.rightTrigger);
|
||||||
@@ -494,10 +527,41 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
rightStickY |= maxByMagnitude(rightStickY, defaultContext.rightStickY);
|
rightStickY |= maxByMagnitude(rightStickY, defaultContext.rightStickY);
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.sendControllerInput(controllerNumber, inputMap,
|
if (originalContext.mouseEmulationActive) {
|
||||||
leftTrigger, rightTrigger,
|
int changedMask = inputMap ^ originalContext.mouseEmulationLastInputMap;
|
||||||
leftStickX, leftStickY,
|
|
||||||
rightStickX, rightStickY);
|
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
|
// Return a valid keycode, 0 to consume, or -1 to not consume the event
|
||||||
@@ -511,41 +575,38 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (context.isDualShock4) {
|
if (context.isDualShock4) {
|
||||||
switch (event.getKeyCode()) {
|
switch (event.getScanCode()) {
|
||||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
case 304:
|
||||||
return KeyEvent.KEYCODE_BUTTON_L1;
|
return KeyEvent.KEYCODE_BUTTON_X;
|
||||||
|
case 305:
|
||||||
case KeyEvent.KEYCODE_BUTTON_Z:
|
return KeyEvent.KEYCODE_BUTTON_A;
|
||||||
return KeyEvent.KEYCODE_BUTTON_R1;
|
case 306:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_B;
|
||||||
case KeyEvent.KEYCODE_BUTTON_C:
|
case 307:
|
||||||
return KeyEvent.KEYCODE_BUTTON_B;
|
return KeyEvent.KEYCODE_BUTTON_Y;
|
||||||
|
case 308:
|
||||||
case KeyEvent.KEYCODE_BUTTON_X:
|
return KeyEvent.KEYCODE_BUTTON_L1;
|
||||||
return KeyEvent.KEYCODE_BUTTON_Y;
|
case 309:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_R1;
|
||||||
case KeyEvent.KEYCODE_BUTTON_B:
|
/*
|
||||||
return KeyEvent.KEYCODE_BUTTON_A;
|
**** Using analog triggers instead ****
|
||||||
|
case 310:
|
||||||
case KeyEvent.KEYCODE_BUTTON_A:
|
return KeyEvent.KEYCODE_BUTTON_L2;
|
||||||
return KeyEvent.KEYCODE_BUTTON_X;
|
case 311:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_R2;
|
||||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
*/
|
||||||
return KeyEvent.KEYCODE_BUTTON_THUMBL;
|
case 312:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_SELECT;
|
||||||
case KeyEvent.KEYCODE_BUTTON_START:
|
case 313:
|
||||||
return KeyEvent.KEYCODE_BUTTON_THUMBR;
|
return KeyEvent.KEYCODE_BUTTON_START;
|
||||||
|
case 314:
|
||||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
return KeyEvent.KEYCODE_BUTTON_THUMBL;
|
||||||
return KeyEvent.KEYCODE_BUTTON_SELECT;
|
case 315:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_THUMBR;
|
||||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
case 316:
|
||||||
return KeyEvent.KEYCODE_BUTTON_START;
|
return KeyEvent.KEYCODE_BUTTON_MODE;
|
||||||
|
default:
|
||||||
// These are duplicate trigger events
|
return 0;
|
||||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
|
||||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If this is a Serval controller sending an unknown key code, it's probably
|
// If this is a Serval controller sending an unknown key code, it's probably
|
||||||
@@ -558,6 +619,35 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
return KeyEvent.KEYCODE_BUTTON_START;
|
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) {
|
if (context.hatXAxis != -1 && context.hatYAxis != -1) {
|
||||||
switch (event.getKeyCode()) {
|
switch (event.getKeyCode()) {
|
||||||
@@ -572,7 +662,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
else if (context.hatXAxis == -1 &&
|
else if (context.hatXAxis == -1 &&
|
||||||
context.hatYAxis == -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) {
|
event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
|
||||||
// If there's not a proper Xbox controller mapping, we'll translate the raw d-pad
|
// If there's not a proper Xbox controller mapping, we'll translate the raw d-pad
|
||||||
// scan codes into proper key codes
|
// scan codes into proper key codes
|
||||||
@@ -662,9 +757,23 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (context.leftTriggerAxis != -1 && context.rightTriggerAxis != -1) {
|
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) {
|
if (context.triggersIdleNegative) {
|
||||||
lt = (lt + 1) / 2;
|
if (context.leftTriggerUsed) {
|
||||||
rt = (rt + 1) / 2;
|
lt = (lt + 1) / 2;
|
||||||
|
}
|
||||||
|
if (context.rightTriggerUsed) {
|
||||||
|
rt = (rt + 1) / 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lt <= context.triggerDeadzone) {
|
if (lt <= context.triggerDeadzone) {
|
||||||
@@ -700,7 +809,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean handleMotionEvent(MotionEvent event) {
|
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;
|
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
|
// We purposefully ignore the historical values in the motion event as it makes
|
||||||
@@ -731,8 +844,51 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
return true;
|
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) {
|
public boolean handleButtonUp(KeyEvent event) {
|
||||||
InputDeviceContext context = getContextForDevice(event.getDevice());
|
InputDeviceContext context = getContextForEvent(event);
|
||||||
|
if (context == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int keyCode = handleRemapping(context, event);
|
int keyCode = handleRemapping(context, event);
|
||||||
if (keyCode == 0) {
|
if (keyCode == 0) {
|
||||||
@@ -757,10 +913,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
break;
|
break;
|
||||||
case KeyEvent.KEYCODE_BUTTON_START:
|
case KeyEvent.KEYCODE_BUTTON_START:
|
||||||
case KeyEvent.KEYCODE_MENU:
|
case KeyEvent.KEYCODE_MENU:
|
||||||
if (SystemClock.uptimeMillis() - context.startDownTime > ControllerHandler.START_DOWN_TIME_KEYB_MS) {
|
// Sometimes we'll get a spurious key up event on controller disconnect.
|
||||||
// FIXME: The stock keyboard doesn't have controller focus so isn't usable. I'm not enabling this shortcut
|
// Make sure it's real by checking that the key is actually down before taking
|
||||||
// until we have a custom keyboard or some other fix
|
// any action.
|
||||||
//gestures.showKeyboard();
|
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;
|
context.inputMap &= ~ControllerPacket.PLAY_FLAG;
|
||||||
break;
|
break;
|
||||||
@@ -857,7 +1015,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean handleButtonDown(KeyEvent event) {
|
public boolean handleButtonDown(KeyEvent event) {
|
||||||
InputDeviceContext context = getContextForDevice(event.getDevice());
|
InputDeviceContext context = getContextForEvent(event);
|
||||||
|
if (context == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int keyCode = handleRemapping(context, event);
|
int keyCode = handleRemapping(context, event);
|
||||||
if (keyCode == 0) {
|
if (keyCode == 0) {
|
||||||
@@ -949,11 +1110,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL;
|
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
|
// We don't need to send repeat key down events, but the platform
|
||||||
if (event.getRepeatCount() == 0 || context.emulatingButtonFlags != 0) {
|
// sends us events that claim to be repeats but they're from different
|
||||||
sendControllerInputPacket(context);
|
// devices, so we just send them all and deal with some duplicates.
|
||||||
}
|
sendControllerInputPacket(context);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -963,6 +1124,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
float rightStickX, float rightStickY,
|
float rightStickX, float rightStickY,
|
||||||
float leftTrigger, float rightTrigger) {
|
float leftTrigger, float rightTrigger) {
|
||||||
UsbDeviceContext context = usbDeviceContexts.get(controllerId);
|
UsbDeviceContext context = usbDeviceContexts.get(controllerId);
|
||||||
|
if (context == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Vector2d leftStickVector = populateCachedVector(leftStickX, leftStickY);
|
Vector2d leftStickVector = populateCachedVector(leftStickX, leftStickY);
|
||||||
|
|
||||||
@@ -1027,6 +1191,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
public short rightStickY = 0x0000;
|
public short rightStickY = 0x0000;
|
||||||
public short leftStickX = 0x0000;
|
public short leftStickX = 0x0000;
|
||||||
public short leftStickY = 0x0000;
|
public short leftStickY = 0x0000;
|
||||||
|
|
||||||
|
public boolean mouseEmulationActive;
|
||||||
|
public Timer mouseEmulationTimer;
|
||||||
|
public short mouseEmulationLastInputMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
class InputDeviceContext extends GenericControllerContext {
|
class InputDeviceContext extends GenericControllerContext {
|
||||||
@@ -1041,12 +1209,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
public int leftTriggerAxis = -1;
|
public int leftTriggerAxis = -1;
|
||||||
public int rightTriggerAxis = -1;
|
public int rightTriggerAxis = -1;
|
||||||
public boolean triggersIdleNegative;
|
public boolean triggersIdleNegative;
|
||||||
|
public boolean leftTriggerUsed, rightTriggerUsed;
|
||||||
|
|
||||||
public int hatXAxis = -1;
|
public int hatXAxis = -1;
|
||||||
public int hatYAxis = -1;
|
public int hatYAxis = -1;
|
||||||
|
|
||||||
public boolean isDualShock4;
|
public boolean isDualShock4;
|
||||||
public boolean isXboxController;
|
public boolean isXboxController;
|
||||||
|
public boolean isXboxBtController;
|
||||||
public boolean isServal;
|
public boolean isServal;
|
||||||
public boolean backIsStart;
|
public boolean backIsStart;
|
||||||
public boolean modeIsSelect;
|
public boolean modeIsSelect;
|
||||||
|
|||||||
@@ -2,15 +2,12 @@ package com.limelight.binding.input;
|
|||||||
|
|
||||||
import android.view.KeyEvent;
|
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
|
* Class to translate a Android key code into the codes GFE is expecting
|
||||||
* @author Diego Waxemberg
|
* @author Diego Waxemberg
|
||||||
* @author Cameron Gutman
|
* @author Cameron Gutman
|
||||||
*/
|
*/
|
||||||
public class KeyboardTranslator extends KeycodeTranslator {
|
public class KeyboardTranslator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GFE's prefix for every key code
|
* GFE's prefix for every key code
|
||||||
@@ -59,21 +56,12 @@ public class KeyboardTranslator extends KeycodeTranslator {
|
|||||||
public static final int VK_QUOTE = 222;
|
public static final int VK_QUOTE = 222;
|
||||||
public static final int VK_PAUSE = 19;
|
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
|
* Translates the given keycode and returns the GFE keycode
|
||||||
* @param keycode the code to be translated
|
* @param keycode the code to be translated
|
||||||
* @return a GFE keycode for the given keycode
|
* @return a GFE keycode for the given keycode
|
||||||
*/
|
*/
|
||||||
@Override
|
public static short translate(int keycode) {
|
||||||
public short translate(int keycode) {
|
|
||||||
int translated;
|
int translated;
|
||||||
|
|
||||||
/* There seems to be no clean mapping between Android key codes
|
/* There seems to be no clean mapping between Android key codes
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.limelight.binding.input.capture;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.view.InputDevice;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.O)
|
||||||
|
public class AndroidNativePointerCaptureProvider extends InputCaptureProvider {
|
||||||
|
|
||||||
|
private View targetView;
|
||||||
|
|
||||||
|
public AndroidNativePointerCaptureProvider(View targetView) {
|
||||||
|
this.targetView = targetView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isCaptureProviderSupported() {
|
||||||
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enableCapture() {
|
||||||
|
super.enableCapture();
|
||||||
|
targetView.requestPointerCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disableCapture() {
|
||||||
|
super.disableCapture();
|
||||||
|
targetView.releasePointerCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCapturingActive() {
|
||||||
|
return targetView.hasPointerCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
||||||
|
return event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getRelativeAxisX(MotionEvent event) {
|
||||||
|
return event.getX();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getRelativeAxisY(MotionEvent event) {
|
||||||
|
return event.getY();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,11 +10,11 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.N)
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
public class AndroidCaptureProvider extends InputCaptureProvider {
|
public class AndroidPointerIconCaptureProvider extends InputCaptureProvider {
|
||||||
private ViewGroup rootViewGroup;
|
private ViewGroup rootViewGroup;
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
public AndroidCaptureProvider(Activity activity) {
|
public AndroidPointerIconCaptureProvider(Activity activity) {
|
||||||
this.context = activity;
|
this.context = activity;
|
||||||
this.rootViewGroup = (ViewGroup) activity.getWindow().getDecorView();
|
this.rootViewGroup = (ViewGroup) activity.getWindow().getDecorView();
|
||||||
}
|
}
|
||||||
@@ -33,11 +33,13 @@ public class AndroidCaptureProvider extends InputCaptureProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enableCapture() {
|
public void enableCapture() {
|
||||||
|
super.enableCapture();
|
||||||
setPointerIconOnAllViews(PointerIcon.getSystemIcon(context, PointerIcon.TYPE_NULL));
|
setPointerIconOnAllViews(PointerIcon.getSystemIcon(context, PointerIcon.TYPE_NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disableCapture() {
|
public void disableCapture() {
|
||||||
|
super.disableCapture();
|
||||||
setPointerIconOnAllViews(null);
|
setPointerIconOnAllViews(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,26 +3,30 @@ package com.limelight.binding.input.capture;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
|
||||||
import com.limelight.LimeLog;
|
import com.limelight.LimeLog;
|
||||||
|
import com.limelight.R;
|
||||||
import com.limelight.binding.input.evdev.EvdevCaptureProvider;
|
import com.limelight.binding.input.evdev.EvdevCaptureProvider;
|
||||||
import com.limelight.binding.input.evdev.EvdevListener;
|
import com.limelight.binding.input.evdev.EvdevListener;
|
||||||
|
|
||||||
public class InputCaptureManager {
|
public class InputCaptureManager {
|
||||||
public static InputCaptureProvider getInputCaptureProvider(Activity activity, EvdevListener rootListener) {
|
public static InputCaptureProvider getInputCaptureProvider(Activity activity, EvdevListener rootListener) {
|
||||||
// Shield capture is preferred because it can capture when the cursor is over
|
if (AndroidNativePointerCaptureProvider.isCaptureProviderSupported()) {
|
||||||
// the system UI. Android N native capture can only capture over views owned
|
LimeLog.info("Using Android O+ native mouse capture");
|
||||||
// by the application.
|
return new AndroidNativePointerCaptureProvider(activity.findViewById(R.id.surfaceView));
|
||||||
if (ShieldCaptureProvider.isCaptureProviderSupported()) {
|
}
|
||||||
|
else if (ShieldCaptureProvider.isCaptureProviderSupported()) {
|
||||||
LimeLog.info("Using NVIDIA mouse capture extension");
|
LimeLog.info("Using NVIDIA mouse capture extension");
|
||||||
return new ShieldCaptureProvider(activity);
|
return new ShieldCaptureProvider(activity);
|
||||||
}
|
}
|
||||||
else if (AndroidCaptureProvider.isCaptureProviderSupported()) {
|
|
||||||
LimeLog.info("Using Android N+ native mouse capture");
|
|
||||||
return new AndroidCaptureProvider(activity);
|
|
||||||
}
|
|
||||||
else if (EvdevCaptureProvider.isCaptureProviderSupported()) {
|
else if (EvdevCaptureProvider.isCaptureProviderSupported()) {
|
||||||
LimeLog.info("Using Evdev mouse capture");
|
LimeLog.info("Using Evdev mouse capture");
|
||||||
return new EvdevCaptureProvider(activity, rootListener);
|
return new EvdevCaptureProvider(activity, rootListener);
|
||||||
}
|
}
|
||||||
|
else if (AndroidPointerIconCaptureProvider.isCaptureProviderSupported()) {
|
||||||
|
// Android N's native capture can't capture over system UI elements
|
||||||
|
// so we want to only use it if there's no other option.
|
||||||
|
LimeLog.info("Using Android N+ pointer hiding");
|
||||||
|
return new AndroidPointerIconCaptureProvider(activity);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
LimeLog.info("Mouse capture not available");
|
LimeLog.info("Mouse capture not available");
|
||||||
return new NullCaptureProvider();
|
return new NullCaptureProvider();
|
||||||
|
|||||||
@@ -3,10 +3,25 @@ package com.limelight.binding.input.capture;
|
|||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
public abstract class InputCaptureProvider {
|
public abstract class InputCaptureProvider {
|
||||||
public abstract void enableCapture();
|
protected boolean isCapturing;
|
||||||
public abstract void disableCapture();
|
|
||||||
|
public void enableCapture() {
|
||||||
|
isCapturing = true;
|
||||||
|
}
|
||||||
|
public void disableCapture() {
|
||||||
|
isCapturing = false;
|
||||||
|
}
|
||||||
|
|
||||||
public void destroy() {}
|
public void destroy() {}
|
||||||
|
|
||||||
|
public boolean isCapturingEnabled() {
|
||||||
|
return isCapturing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCapturingActive() {
|
||||||
|
return isCapturing;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
package com.limelight.binding.input.capture;
|
package com.limelight.binding.input.capture;
|
||||||
|
|
||||||
|
|
||||||
public class NullCaptureProvider extends InputCaptureProvider {
|
public class NullCaptureProvider extends InputCaptureProvider {}
|
||||||
@Override
|
|
||||||
public void enableCapture() {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void disableCapture() {}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -63,11 +63,13 @@ public class ShieldCaptureProvider extends InputCaptureProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enableCapture() {
|
public void enableCapture() {
|
||||||
|
super.enableCapture();
|
||||||
setCursorVisibility(false);
|
setCursorVisibility(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disableCapture() {
|
public void disableCapture() {
|
||||||
|
super.disableCapture();
|
||||||
setCursorVisibility(true);
|
setCursorVisibility(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,13 +114,13 @@ public abstract class AbstractXboxController extends AbstractController {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Report that we're added _before_ starting the input thread
|
||||||
|
notifyDeviceAdded();
|
||||||
|
|
||||||
// Start listening for controller input
|
// Start listening for controller input
|
||||||
inputThread = createInputThread();
|
inputThread = createInputThread();
|
||||||
inputThread.start();
|
inputThread.start();
|
||||||
|
|
||||||
// Now report we're added
|
|
||||||
notifyDeviceAdded();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,11 +137,11 @@ public abstract class AbstractXboxController extends AbstractController {
|
|||||||
inputThread = null;
|
inputThread = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report the device removed
|
|
||||||
notifyDeviceRemoved();
|
|
||||||
|
|
||||||
// Close the USB connection
|
// Close the USB connection
|
||||||
connection.close();
|
connection.close();
|
||||||
|
|
||||||
|
// Report the device removed
|
||||||
|
notifyDeviceRemoved();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract boolean handleRead(ByteBuffer buffer);
|
protected abstract boolean handleRead(ByteBuffer buffer);
|
||||||
|
|||||||
@@ -17,18 +17,22 @@ public class Xbox360Controller extends AbstractXboxController {
|
|||||||
0x044f, // Thrustmaster
|
0x044f, // Thrustmaster
|
||||||
0x045e, // Microsoft
|
0x045e, // Microsoft
|
||||||
0x046d, // Logitech
|
0x046d, // Logitech
|
||||||
|
0x056e, // Elecom
|
||||||
|
0x06a3, // Saitek
|
||||||
0x0738, // Mad Catz
|
0x0738, // Mad Catz
|
||||||
|
0x07ff, // Mad Catz
|
||||||
0x0e6f, // Unknown
|
0x0e6f, // Unknown
|
||||||
|
0x0f0d, // Hori
|
||||||
|
0x11c9, // Nacon
|
||||||
0x12ab, // Unknown
|
0x12ab, // Unknown
|
||||||
0x1430, // RedOctane
|
0x1430, // RedOctane
|
||||||
0x146b, // BigBen
|
0x146b, // BigBen
|
||||||
0x1bad, // Harmonix
|
|
||||||
0x0f0d, // Hori
|
|
||||||
0x1689, // Razer Onza
|
|
||||||
0x24c6, // PowerA
|
|
||||||
0x1532, // Razer Sabertooth
|
0x1532, // Razer Sabertooth
|
||||||
0x15e4, // Numark
|
0x15e4, // Numark
|
||||||
0x162e, // Joytech
|
0x162e, // Joytech
|
||||||
|
0x1689, // Razer Onza
|
||||||
|
0x1bad, // Harmonix
|
||||||
|
0x24c6, // PowerA
|
||||||
};
|
};
|
||||||
|
|
||||||
public static boolean canClaimDevice(UsbDevice device) {
|
public static boolean canClaimDevice(UsbDevice device) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public class XboxOneController extends AbstractXboxController {
|
|||||||
0x0738, // Mad Catz
|
0x0738, // Mad Catz
|
||||||
0x0e6f, // Unknown
|
0x0e6f, // Unknown
|
||||||
0x0f0d, // Hori
|
0x0f0d, // Hori
|
||||||
|
0x1532, // Razer Wildcat
|
||||||
0x24c6, // PowerA
|
0x24c6, // PowerA
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,6 +63,12 @@ public class XboxOneController extends AbstractXboxController {
|
|||||||
rightStickY = ~buffer.getShort() / 32767.0f;
|
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
|
@Override
|
||||||
protected boolean handleRead(ByteBuffer buffer) {
|
protected boolean handleRead(ByteBuffer buffer) {
|
||||||
switch (buffer.get())
|
switch (buffer.get())
|
||||||
@@ -72,7 +79,15 @@ public class XboxOneController extends AbstractXboxController {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 0x07:
|
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);
|
setButtonFlag(ControllerPacket.SPECIAL_BUTTON_FLAG, buffer.get() & 0x01);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.limelight.binding.input.evdev;
|
package com.limelight.binding.input.evdev;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Looper;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.limelight.LimeLog;
|
import com.limelight.LimeLog;
|
||||||
@@ -47,26 +49,42 @@ public class EvdevCaptureProvider extends InputCaptureProvider {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Launch a su shell
|
final String evdevReaderCmd = libraryPath+File.separatorChar+"libevdev_reader.so "+servSock.getLocalPort();
|
||||||
ProcessBuilder builder = new ProcessBuilder("su");
|
|
||||||
builder.redirectErrorStream(true);
|
|
||||||
|
|
||||||
try {
|
// On Nougat and later, we'll need to pass the command directly to SU.
|
||||||
su = builder.start();
|
// Writing to SU's input stream after it has started doesn't seem to work anymore.
|
||||||
} catch (IOException e) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
reportDeviceNotRooted();
|
// Launch evdev_reader directly via SU
|
||||||
e.printStackTrace();
|
try {
|
||||||
return;
|
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
|
try {
|
||||||
DataOutputStream suOut = new DataOutputStream(su.getOutputStream());
|
su = builder.start();
|
||||||
try {
|
} catch (IOException e) {
|
||||||
suOut.writeChars(libraryPath+File.separatorChar+"libevdev_reader.so "+servSock.getLocalPort()+"\n");
|
reportDeviceNotRooted();
|
||||||
} catch (IOException e) {
|
e.printStackTrace();
|
||||||
reportDeviceNotRooted();
|
return;
|
||||||
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
|
// Wait for evdevreader's connection
|
||||||
@@ -181,8 +199,29 @@ public class EvdevCaptureProvider extends InputCaptureProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
t.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// The main thread should never be interrupted
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Run the runnable directly
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enableCapture() {
|
public void enableCapture() {
|
||||||
|
super.enableCapture();
|
||||||
if (!started) {
|
if (!started) {
|
||||||
// Start the handler thread if it's our first time
|
// Start the handler thread if it's our first time
|
||||||
// capturing
|
// capturing
|
||||||
@@ -190,26 +229,39 @@ public class EvdevCaptureProvider extends InputCaptureProvider {
|
|||||||
started = true;
|
started = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Send a request to regrab if we're already capturing
|
// This may be called on the main thread
|
||||||
if (!shutdown && evdevOut != null) {
|
runInNetworkSafeContextSynchronously(new Runnable() {
|
||||||
try {
|
@Override
|
||||||
evdevOut.write(REGRAB_REQUEST);
|
public void run() {
|
||||||
} catch (IOException e) {
|
// Send a request to regrab if we're already capturing
|
||||||
e.printStackTrace();
|
if (!shutdown && evdevOut != null) {
|
||||||
|
try {
|
||||||
|
evdevOut.write(REGRAB_REQUEST);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disableCapture() {
|
public void disableCapture() {
|
||||||
if (started && !shutdown && evdevOut != null) {
|
super.disableCapture();
|
||||||
try {
|
// This may be called on the main thread
|
||||||
evdevOut.write(UNGRAB_REQUEST);
|
runInNetworkSafeContextSynchronously(new Runnable() {
|
||||||
} catch (IOException e) {
|
@Override
|
||||||
e.printStackTrace();
|
public void run() {
|
||||||
|
if (started && !shutdown && evdevOut != null) {
|
||||||
|
try {
|
||||||
|
evdevOut.write(UNGRAB_REQUEST);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -217,6 +269,8 @@ public class EvdevCaptureProvider extends InputCaptureProvider {
|
|||||||
// We need to stop the process in this context otherwise
|
// We need to stop the process in this context otherwise
|
||||||
// we could get stuck waiting on output from the process
|
// we could get stuck waiting on output from the process
|
||||||
// in order to terminate it.
|
// in order to terminate it.
|
||||||
|
//
|
||||||
|
// This may be called on the main thread.
|
||||||
|
|
||||||
if (!started) {
|
if (!started) {
|
||||||
return;
|
return;
|
||||||
@@ -225,37 +279,42 @@ public class EvdevCaptureProvider extends InputCaptureProvider {
|
|||||||
shutdown = true;
|
shutdown = true;
|
||||||
handlerThread.interrupt();
|
handlerThread.interrupt();
|
||||||
|
|
||||||
if (servSock != null) {
|
runInNetworkSafeContextSynchronously(new Runnable() {
|
||||||
try {
|
@Override
|
||||||
servSock.close();
|
public void run() {
|
||||||
} catch (IOException e) {
|
if (servSock != null) {
|
||||||
e.printStackTrace();
|
try {
|
||||||
}
|
servSock.close();
|
||||||
}
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (evdevSock != null) {
|
if (evdevSock != null) {
|
||||||
try {
|
try {
|
||||||
evdevSock.close();
|
evdevSock.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evdevIn != null) {
|
if (evdevIn != null) {
|
||||||
try {
|
try {
|
||||||
evdevIn.close();
|
evdevIn.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evdevOut != null) {
|
if (evdevOut != null) {
|
||||||
try {
|
try {
|
||||||
evdevOut.close();
|
evdevOut.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
if (su != null) {
|
if (su != null) {
|
||||||
su.destroy();
|
su.destroy();
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package com.limelight.binding.input.evdev;
|
package com.limelight.binding.input.evdev;
|
||||||
|
|
||||||
public interface EvdevListener {
|
public interface EvdevListener {
|
||||||
public static final int BUTTON_LEFT = 1;
|
int BUTTON_LEFT = 1;
|
||||||
public static final int BUTTON_MIDDLE = 2;
|
int BUTTON_MIDDLE = 2;
|
||||||
public static final int BUTTON_RIGHT = 3;
|
int BUTTON_RIGHT = 3;
|
||||||
|
|
||||||
public void mouseMove(int deltaX, int deltaY);
|
void mouseMove(int deltaX, int deltaY);
|
||||||
public void mouseButtonEvent(int buttonId, boolean down);
|
void mouseButtonEvent(int buttonId, boolean down);
|
||||||
public void mouseScroll(byte amount);
|
void mouseScroll(byte amount);
|
||||||
public void keyboardEvent(boolean buttonDown, short keyCode);
|
void keyboardEvent(boolean buttonDown, short keyCode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,8 +190,14 @@ public class DigitalButton extends VirtualControllerElement {
|
|||||||
for (DigitalButtonListener listener : listeners) {
|
for (DigitalButtonListener listener : listeners) {
|
||||||
listener.onRelease();
|
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
|
@Override
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ public class VirtualController {
|
|||||||
frame_layout.addView(relative_layout);
|
frame_layout.addView(relative_layout);
|
||||||
|
|
||||||
buttonConfigure = new Button(context);
|
buttonConfigure = new Button(context);
|
||||||
buttonConfigure.setBackgroundResource(R.drawable.settings);
|
buttonConfigure.setBackgroundResource(R.drawable.ic_settings);
|
||||||
buttonConfigure.setOnClickListener(new View.OnClickListener() {
|
buttonConfigure.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.limelight.binding.video;
|
||||||
|
|
||||||
|
public interface CrashListener {
|
||||||
|
void notifyCrash(Exception e);
|
||||||
|
}
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
@@ -32,6 +32,8 @@ public class MediaCodecHelper {
|
|||||||
private static final List<String> directSubmitPrefixes;
|
private static final List<String> directSubmitPrefixes;
|
||||||
private static final List<String> constrainedHighProfilePrefixes;
|
private static final List<String> constrainedHighProfilePrefixes;
|
||||||
private static final List<String> whitelistedHevcDecoders;
|
private static final List<String> whitelistedHevcDecoders;
|
||||||
|
private static final List<String> refFrameInvalidationAvcPrefixes;
|
||||||
|
private static final List<String> refFrameInvalidationHevcPrefixes;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
directSubmitPrefixes = new LinkedList<>();
|
directSubmitPrefixes = new LinkedList<>();
|
||||||
@@ -45,26 +47,45 @@ public class MediaCodecHelper {
|
|||||||
directSubmitPrefixes.add("omx.brcm");
|
directSubmitPrefixes.add("omx.brcm");
|
||||||
directSubmitPrefixes.add("omx.TI");
|
directSubmitPrefixes.add("omx.TI");
|
||||||
directSubmitPrefixes.add("omx.arc");
|
directSubmitPrefixes.add("omx.arc");
|
||||||
|
directSubmitPrefixes.add("omx.nvidia");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
refFrameInvalidationAvcPrefixes = new LinkedList<>();
|
||||||
|
refFrameInvalidationHevcPrefixes = new LinkedList<>();
|
||||||
|
|
||||||
|
// Qualcomm and NVIDIA may be added at runtime
|
||||||
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
preferredDecoders = new LinkedList<>();
|
preferredDecoders = new LinkedList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
blacklistedDecoderPrefixes = new LinkedList<>();
|
blacklistedDecoderPrefixes = new LinkedList<>();
|
||||||
|
|
||||||
// Software decoders that don't support H264 high profile
|
// Blacklist software decoders that don't support H264 high profile,
|
||||||
blacklistedDecoderPrefixes.add("omx.google");
|
// but exclude the official AOSP emulator from this restriction.
|
||||||
blacklistedDecoderPrefixes.add("AVCDecoder");
|
if (!Build.HARDWARE.equals("ranchu") || !Build.BRAND.equals("google")) {
|
||||||
|
blacklistedDecoderPrefixes.add("omx.google");
|
||||||
|
blacklistedDecoderPrefixes.add("AVCDecoder");
|
||||||
|
}
|
||||||
|
|
||||||
// Without bitstream fixups, we perform horribly on NVIDIA's HEVC
|
// Without bitstream fixups, we perform horribly on NVIDIA's HEVC
|
||||||
// decoder. While not strictly necessary, I'm going to fully blacklist this
|
// decoder. While not strictly necessary, I'm going to fully blacklist this
|
||||||
// one to avoid users getting inaccurate impressions of Tegra X1/Moonlight performance.
|
// one to avoid users getting inaccurate impressions of Tegra X1/Moonlight performance.
|
||||||
blacklistedDecoderPrefixes.add("OMX.Nvidia.h265.decode");
|
blacklistedDecoderPrefixes.add("OMX.Nvidia.h265.decode");
|
||||||
|
|
||||||
|
// Force these decoders disabled because:
|
||||||
|
// 1) They are software decoders, so the performance is terrible
|
||||||
|
// 2) They crash with our HEVC stream anyway (at least prior to CSD batching)
|
||||||
|
blacklistedDecoderPrefixes.add("OMX.qcom.video.decoder.hevcswvdec");
|
||||||
|
blacklistedDecoderPrefixes.add("OMX.SEC.hevc.sw.dec");
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
// If a decoder qualifies for reference frame invalidation,
|
||||||
|
// these entries will be ignored for those decoders.
|
||||||
spsFixupBitstreamFixupDecoderPrefixes = new LinkedList<>();
|
spsFixupBitstreamFixupDecoderPrefixes = new LinkedList<>();
|
||||||
spsFixupBitstreamFixupDecoderPrefixes.add("omx.nvidia");
|
spsFixupBitstreamFixupDecoderPrefixes.add("omx.nvidia");
|
||||||
spsFixupBitstreamFixupDecoderPrefixes.add("omx.qcom");
|
spsFixupBitstreamFixupDecoderPrefixes.add("omx.qcom");
|
||||||
@@ -86,6 +107,11 @@ public class MediaCodecHelper {
|
|||||||
static {
|
static {
|
||||||
whitelistedHevcDecoders = new LinkedList<>();
|
whitelistedHevcDecoders = new LinkedList<>();
|
||||||
|
|
||||||
|
// Allow software HEVC decoding in the official AOSP emulator
|
||||||
|
if (Build.HARDWARE.equals("ranchu") && Build.BRAND.equals("google")) {
|
||||||
|
whitelistedHevcDecoders.add("omx.google");
|
||||||
|
}
|
||||||
|
|
||||||
// Exynos seems to be the only HEVC decoder that works reliably
|
// Exynos seems to be the only HEVC decoder that works reliably
|
||||||
whitelistedHevcDecoders.add("omx.exynos");
|
whitelistedHevcDecoders.add("omx.exynos");
|
||||||
|
|
||||||
@@ -113,20 +139,40 @@ public class MediaCodecHelper {
|
|||||||
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||||
ConfigurationInfo configInfo = activityManager.getDeviceConfigurationInfo();
|
ConfigurationInfo configInfo = activityManager.getDeviceConfigurationInfo();
|
||||||
if (configInfo.reqGlEsVersion != ConfigurationInfo.GL_ES_VERSION_UNDEFINED) {
|
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);
|
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");
|
// Tegra K1 and later can do reference frame invalidation properly
|
||||||
whitelistedHevcDecoders.add("omx.qcom");
|
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) {
|
private static boolean isDecoderInList(List<String> decoderList, String decoderName) {
|
||||||
for (String badPrefix : decoderList) {
|
for (String badPrefix : decoderList) {
|
||||||
@@ -190,6 +236,14 @@ public class MediaCodecHelper {
|
|||||||
return isDecoderInList(baselineProfileHackPrefixes, decoderName);
|
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) {
|
public static boolean decoderIsWhitelistedForHevc(String decoderName) {
|
||||||
// TODO: Shield Tablet K1/LTE?
|
// TODO: Shield Tablet K1/LTE?
|
||||||
//
|
//
|
||||||
@@ -212,6 +266,16 @@ public class MediaCodecHelper {
|
|||||||
return false;
|
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);
|
return isDecoderInList(whitelistedHevcDecoders, decoderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ public class ComputerDatabaseManager {
|
|||||||
private static final String REMOTE_IP_COLUMN_NAME = "RemoteIp";
|
private static final String REMOTE_IP_COLUMN_NAME = "RemoteIp";
|
||||||
private static final String MAC_COLUMN_NAME = "Mac";
|
private static final String MAC_COLUMN_NAME = "Mac";
|
||||||
|
|
||||||
|
private static final String ADDRESS_PREFIX = "ADDRESS_PREFIX__";
|
||||||
|
|
||||||
private SQLiteDatabase computerDb;
|
private SQLiteDatabase computerDb;
|
||||||
|
|
||||||
public ComputerDatabaseManager(Context c) {
|
public ComputerDatabaseManager(Context c) {
|
||||||
@@ -60,50 +62,74 @@ public class ComputerDatabaseManager {
|
|||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
|
values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
|
||||||
values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString());
|
values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString());
|
||||||
values.put(LOCAL_IP_COLUMN_NAME, details.localIp.getAddress());
|
values.put(LOCAL_IP_COLUMN_NAME, ADDRESS_PREFIX+details.localAddress);
|
||||||
values.put(REMOTE_IP_COLUMN_NAME, details.remoteIp.getAddress());
|
values.put(REMOTE_IP_COLUMN_NAME, ADDRESS_PREFIX+details.remoteAddress);
|
||||||
values.put(MAC_COLUMN_NAME, details.macAddress);
|
values.put(MAC_COLUMN_NAME, details.macAddress);
|
||||||
return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ComputerDetails getComputerFromCursor(Cursor c) {
|
||||||
|
ComputerDetails details = new ComputerDetails();
|
||||||
|
|
||||||
|
details.name = c.getString(0);
|
||||||
|
|
||||||
|
String uuidStr = c.getString(1);
|
||||||
|
try {
|
||||||
|
details.uuid = UUID.fromString(uuidStr);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// We'll delete this entry
|
||||||
|
LimeLog.severe("DB: Corrupted UUID for "+details.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// An earlier schema defined addresses as byte blobs. We'll
|
||||||
|
// gracefully migrate those to strings so we can store DNS names
|
||||||
|
// too. To disambiguate, we'll need to prefix them with a string
|
||||||
|
// greater than the allowable IP address length.
|
||||||
|
try {
|
||||||
|
details.localAddress = InetAddress.getByAddress(c.getBlob(2)).getHostAddress();
|
||||||
|
LimeLog.warning("DB: Legacy local address for "+details.name);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
// This is probably a hostname/address with the prefix string
|
||||||
|
String stringData = c.getString(2);
|
||||||
|
if (stringData.startsWith(ADDRESS_PREFIX)) {
|
||||||
|
details.localAddress = c.getString(2).substring(ADDRESS_PREFIX.length());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LimeLog.severe("DB: Corrupted local address for "+details.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
details.remoteAddress = InetAddress.getByAddress(c.getBlob(3)).getHostAddress();
|
||||||
|
LimeLog.warning("DB: Legacy remote address for "+details.name);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
// This is probably a hostname/address with the prefix string
|
||||||
|
String stringData = c.getString(3);
|
||||||
|
if (stringData.startsWith(ADDRESS_PREFIX)) {
|
||||||
|
details.remoteAddress = c.getString(3).substring(ADDRESS_PREFIX.length());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LimeLog.severe("DB: Corrupted local address for "+details.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
details.macAddress = c.getString(4);
|
||||||
|
|
||||||
|
// This signifies we don't have dynamic state (like pair state)
|
||||||
|
details.state = ComputerDetails.State.UNKNOWN;
|
||||||
|
details.reachability = ComputerDetails.Reachability.UNKNOWN;
|
||||||
|
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
public List<ComputerDetails> getAllComputers() {
|
public List<ComputerDetails> getAllComputers() {
|
||||||
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null);
|
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null);
|
||||||
LinkedList<ComputerDetails> computerList = new LinkedList<>();
|
LinkedList<ComputerDetails> computerList = new LinkedList<>();
|
||||||
while (c.moveToNext()) {
|
while (c.moveToNext()) {
|
||||||
ComputerDetails details = new ComputerDetails();
|
ComputerDetails details = getComputerFromCursor(c);
|
||||||
|
|
||||||
details.name = c.getString(0);
|
|
||||||
|
|
||||||
String uuidStr = c.getString(1);
|
|
||||||
try {
|
|
||||||
details.uuid = UUID.fromString(uuidStr);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// We'll delete this entry
|
|
||||||
LimeLog.severe("DB: Corrupted UUID for "+details.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
details.localIp = InetAddress.getByAddress(c.getBlob(2));
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
// We'll delete this entry
|
|
||||||
LimeLog.severe("DB: Corrupted local IP for "+details.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
details.remoteIp = InetAddress.getByAddress(c.getBlob(3));
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
// We'll delete this entry
|
|
||||||
LimeLog.severe("DB: Corrupted remote IP for "+details.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
details.macAddress = c.getString(4);
|
|
||||||
|
|
||||||
// This signifies we don't have dynamic state (like pair state)
|
|
||||||
details.state = ComputerDetails.State.UNKNOWN;
|
|
||||||
details.reachability = ComputerDetails.Reachability.UNKNOWN;
|
|
||||||
|
|
||||||
// If a field is corrupt or missing, skip the database entry
|
// If a field is corrupt or missing, skip the database entry
|
||||||
if (details.uuid == null || details.localIp == null || details.remoteIp == null ||
|
if (details.uuid == null || details.localAddress == null || details.remoteAddress == null ||
|
||||||
details.macAddress == null) {
|
details.macAddress == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -119,46 +145,17 @@ public class ComputerDatabaseManager {
|
|||||||
|
|
||||||
public ComputerDetails getComputerByName(String name) {
|
public ComputerDetails getComputerByName(String name) {
|
||||||
Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_NAME_COLUMN_NAME+"=?", new String[]{name}, null, null, null);
|
Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_NAME_COLUMN_NAME+"=?", new String[]{name}, null, null, null);
|
||||||
ComputerDetails details = new ComputerDetails();
|
|
||||||
if (!c.moveToFirst()) {
|
if (!c.moveToFirst()) {
|
||||||
// No matching computer
|
// No matching computer
|
||||||
c.close();
|
c.close();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
details.name = c.getString(0);
|
ComputerDetails details = getComputerFromCursor(c);
|
||||||
|
|
||||||
String uuidStr = c.getString(1);
|
|
||||||
try {
|
|
||||||
details.uuid = UUID.fromString(uuidStr);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// We'll delete this entry
|
|
||||||
LimeLog.severe("DB: Corrupted UUID for "+details.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
details.localIp = InetAddress.getByAddress(c.getBlob(2));
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
// We'll delete this entry
|
|
||||||
LimeLog.severe("DB: Corrupted local IP for "+details.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
details.remoteIp = InetAddress.getByAddress(c.getBlob(3));
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
// We'll delete this entry
|
|
||||||
LimeLog.severe("DB: Corrupted remote IP for "+details.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
details.macAddress = c.getString(4);
|
|
||||||
|
|
||||||
c.close();
|
c.close();
|
||||||
|
|
||||||
details.state = ComputerDetails.State.UNKNOWN;
|
|
||||||
details.reachability = ComputerDetails.Reachability.UNKNOWN;
|
|
||||||
|
|
||||||
// If a field is corrupt or missing, delete the database entry
|
// If a field is corrupt or missing, delete the database entry
|
||||||
if (details.uuid == null || details.localIp == null || details.remoteIp == null ||
|
if (details.uuid == null || details.localAddress == null || details.remoteAddress == null ||
|
||||||
details.macAddress == null) {
|
details.macAddress == null) {
|
||||||
deleteComputer(details.name);
|
deleteComputer(details.name);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ package com.limelight.computers;
|
|||||||
import com.limelight.nvstream.http.ComputerDetails;
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
|
|
||||||
public interface ComputerManagerListener {
|
public interface ComputerManagerListener {
|
||||||
public void notifyComputerUpdated(ComputerDetails details);
|
void notifyComputerUpdated(ComputerDetails details);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.limelight.computers;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@@ -20,6 +19,7 @@ import com.limelight.nvstream.http.NvHTTP;
|
|||||||
import com.limelight.nvstream.mdns.MdnsComputer;
|
import com.limelight.nvstream.mdns.MdnsComputer;
|
||||||
import com.limelight.nvstream.mdns.MdnsDiscoveryListener;
|
import com.limelight.nvstream.mdns.MdnsDiscoveryListener;
|
||||||
import com.limelight.utils.CacheHelper;
|
import com.limelight.utils.CacheHelper;
|
||||||
|
import com.limelight.utils.ServerHelper;
|
||||||
|
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
@@ -37,7 +37,9 @@ public class ComputerManagerService extends Service {
|
|||||||
private static final int MDNS_QUERY_PERIOD_MS = 1000;
|
private static final int MDNS_QUERY_PERIOD_MS = 1000;
|
||||||
private static final int FAST_POLL_TIMEOUT = 500;
|
private static final int FAST_POLL_TIMEOUT = 500;
|
||||||
private static final int OFFLINE_POLL_TRIES = 5;
|
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 EMPTY_LIST_THRESHOLD = 3;
|
||||||
|
private static final int POLL_DATA_TTL_MS = 30000;
|
||||||
|
|
||||||
private final ComputerManagerBinder binder = new ComputerManagerBinder();
|
private final ComputerManagerBinder binder = new ComputerManagerBinder();
|
||||||
|
|
||||||
@@ -76,12 +78,15 @@ public class ComputerManagerService extends Service {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final int pollTriesBeforeOffline = details.state == ComputerDetails.State.UNKNOWN ?
|
||||||
|
INITIAL_POLL_TRIES : OFFLINE_POLL_TRIES;
|
||||||
|
|
||||||
activePolls.incrementAndGet();
|
activePolls.incrementAndGet();
|
||||||
|
|
||||||
// Poll the machine
|
// Poll the machine
|
||||||
try {
|
try {
|
||||||
if (!pollComputer(details)) {
|
if (!pollComputer(details)) {
|
||||||
if (!newPc && offlineCount < OFFLINE_POLL_TRIES) {
|
if (!newPc && offlineCount < pollTriesBeforeOffline) {
|
||||||
// Return without calling the listener
|
// Return without calling the listener
|
||||||
releaseLocalDatabaseReference();
|
releaseLocalDatabaseReference();
|
||||||
return false;
|
return false;
|
||||||
@@ -136,6 +141,7 @@ public class ComputerManagerService extends Service {
|
|||||||
LimeLog.warning(tuple.computer.name + " is offline (try " + offlineCount + ")");
|
LimeLog.warning(tuple.computer.name + " is offline (try " + offlineCount + ")");
|
||||||
offlineCount++;
|
offlineCount++;
|
||||||
} else {
|
} else {
|
||||||
|
tuple.lastSuccessfulPollMs = System.currentTimeMillis();
|
||||||
offlineCount = 0;
|
offlineCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,7 +154,7 @@ public class ComputerManagerService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
t.setName("Polling thread for " + tuple.computer.localIp.getHostAddress());
|
t.setName("Polling thread for " + tuple.computer.localAddress);
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,11 +171,18 @@ public class ComputerManagerService extends Service {
|
|||||||
|
|
||||||
synchronized (pollingTuples) {
|
synchronized (pollingTuples) {
|
||||||
for (PollingTuple tuple : 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
|
// This polling thread might already be there
|
||||||
if (tuple.thread == null) {
|
if (tuple.thread == null) {
|
||||||
// Report this computer initially
|
|
||||||
listener.notifyComputerUpdated(tuple.computer);
|
|
||||||
|
|
||||||
tuple.thread = createPollingThread(tuple);
|
tuple.thread = createPollingThread(tuple);
|
||||||
tuple.thread.start();
|
tuple.thread.start();
|
||||||
}
|
}
|
||||||
@@ -197,8 +210,8 @@ public class ComputerManagerService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addComputerBlocking(InetAddress addr) {
|
public boolean addComputerBlocking(String addr, boolean manuallyAdded) {
|
||||||
return ComputerManagerService.this.addComputerBlocking(addr);
|
return ComputerManagerService.this.addComputerBlocking(addr, manuallyAdded);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeComputer(String name) {
|
public void removeComputer(String name) {
|
||||||
@@ -229,6 +242,21 @@ public class ComputerManagerService extends Service {
|
|||||||
|
|
||||||
return null;
|
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
|
@Override
|
||||||
@@ -261,7 +289,7 @@ public class ComputerManagerService extends Service {
|
|||||||
@Override
|
@Override
|
||||||
public void notifyComputerAdded(MdnsComputer computer) {
|
public void notifyComputerAdded(MdnsComputer computer) {
|
||||||
// Kick off a serverinfo poll on this machine
|
// Kick off a serverinfo poll on this machine
|
||||||
addComputerBlocking(computer.getAddress());
|
addComputerBlocking(computer.getAddress().getHostAddress(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -277,15 +305,22 @@ public class ComputerManagerService extends Service {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTuple(ComputerDetails details) {
|
private void addTuple(ComputerDetails details, boolean manuallyAdded) {
|
||||||
synchronized (pollingTuples) {
|
synchronized (pollingTuples) {
|
||||||
for (PollingTuple tuple : pollingTuples) {
|
for (PollingTuple tuple : pollingTuples) {
|
||||||
// Check if this is the same computer
|
// Check if this is the same computer
|
||||||
if (tuple.computer.uuid.equals(details.uuid)) {
|
if (tuple.computer.uuid.equals(details.uuid)) {
|
||||||
// Update details anyway in case this machine has been re-added by IP
|
if (manuallyAdded) {
|
||||||
// after not being reachable by our existing information
|
// Update details anyway in case this machine has been re-added by IP
|
||||||
tuple.computer.localIp = details.localIp;
|
// after not being reachable by our existing information
|
||||||
tuple.computer.remoteIp = details.remoteIp;
|
tuple.computer.localAddress = details.localAddress;
|
||||||
|
tuple.computer.remoteAddress = details.remoteAddress;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// This indicates that mDNS discovered this address, so we
|
||||||
|
// should only apply the local address.
|
||||||
|
tuple.computer.localAddress = details.localAddress;
|
||||||
|
}
|
||||||
|
|
||||||
// Start a polling thread if polling is active
|
// Start a polling thread if polling is active
|
||||||
if (pollingActive && tuple.thread == null) {
|
if (pollingActive && tuple.thread == null) {
|
||||||
@@ -310,11 +345,11 @@ public class ComputerManagerService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addComputerBlocking(InetAddress addr) {
|
public boolean addComputerBlocking(String addr, boolean manuallyAdded) {
|
||||||
// Setup a placeholder
|
// Setup a placeholder
|
||||||
ComputerDetails fakeDetails = new ComputerDetails();
|
ComputerDetails fakeDetails = new ComputerDetails();
|
||||||
fakeDetails.localIp = addr;
|
fakeDetails.localAddress = addr;
|
||||||
fakeDetails.remoteIp = addr;
|
fakeDetails.remoteAddress = addr;
|
||||||
|
|
||||||
// Block while we try to fill the details
|
// Block while we try to fill the details
|
||||||
try {
|
try {
|
||||||
@@ -328,7 +363,7 @@ public class ComputerManagerService extends Service {
|
|||||||
LimeLog.info("New PC ("+fakeDetails.name+") is UUID "+fakeDetails.uuid);
|
LimeLog.info("New PC ("+fakeDetails.name+") is UUID "+fakeDetails.uuid);
|
||||||
|
|
||||||
// Start a polling thread for this machine
|
// Start a polling thread for this machine
|
||||||
addTuple(fakeDetails);
|
addTuple(fakeDetails, manuallyAdded);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -376,14 +411,14 @@ public class ComputerManagerService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ComputerDetails tryPollIp(ComputerDetails details, InetAddress ipAddr) {
|
private ComputerDetails tryPollIp(ComputerDetails details, String address) {
|
||||||
// Fast poll this address first to determine if we can connect at the TCP layer
|
// Fast poll this address first to determine if we can connect at the TCP layer
|
||||||
if (!fastPollIp(ipAddr)) {
|
if (!fastPollIp(address)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(),
|
NvHTTP http = new NvHTTP(address, idManager.getUniqueId(),
|
||||||
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
||||||
|
|
||||||
ComputerDetails newDetails = http.getComputerDetails();
|
ComputerDetails newDetails = http.getComputerDetails();
|
||||||
@@ -404,10 +439,10 @@ public class ComputerManagerService extends Service {
|
|||||||
|
|
||||||
// Just try to establish a TCP connection to speculatively detect a running
|
// Just try to establish a TCP connection to speculatively detect a running
|
||||||
// GFE server
|
// GFE server
|
||||||
private boolean fastPollIp(InetAddress addr) {
|
private boolean fastPollIp(String address) {
|
||||||
Socket s = new Socket();
|
Socket s = new Socket();
|
||||||
try {
|
try {
|
||||||
s.connect(new InetSocketAddress(addr, NvHTTP.HTTPS_PORT), FAST_POLL_TIMEOUT);
|
s.connect(new InetSocketAddress(address, NvHTTP.HTTPS_PORT), FAST_POLL_TIMEOUT);
|
||||||
s.close();
|
s.close();
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -415,11 +450,11 @@ public class ComputerManagerService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startFastPollThread(final InetAddress addr, final boolean[] info) {
|
private void startFastPollThread(final String address, final boolean[] info) {
|
||||||
Thread t = new Thread() {
|
Thread t = new Thread() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
boolean pollRes = fastPollIp(addr);
|
boolean pollRes = fastPollIp(address);
|
||||||
|
|
||||||
synchronized (info) {
|
synchronized (info) {
|
||||||
info[0] = true; // Done
|
info[0] = true; // Done
|
||||||
@@ -429,16 +464,16 @@ public class ComputerManagerService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
t.setName("Fast Poll - "+addr.getHostAddress());
|
t.setName("Fast Poll - "+address);
|
||||||
t.start();
|
t.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ComputerDetails.Reachability fastPollPc(final InetAddress local, final InetAddress remote) throws InterruptedException {
|
private ComputerDetails.Reachability fastPollPc(final String localAddress, final String remoteAddress) throws InterruptedException {
|
||||||
final boolean[] remoteInfo = new boolean[2];
|
final boolean[] remoteInfo = new boolean[2];
|
||||||
final boolean[] localInfo = new boolean[2];
|
final boolean[] localInfo = new boolean[2];
|
||||||
|
|
||||||
startFastPollThread(local, localInfo);
|
startFastPollThread(localAddress, localInfo);
|
||||||
startFastPollThread(remote, remoteInfo);
|
startFastPollThread(remoteAddress, remoteInfo);
|
||||||
|
|
||||||
// Check local first
|
// Check local first
|
||||||
synchronized (localInfo) {
|
synchronized (localInfo) {
|
||||||
@@ -471,13 +506,13 @@ public class ComputerManagerService extends Service {
|
|||||||
|
|
||||||
// If the local address is routable across the Internet,
|
// If the local address is routable across the Internet,
|
||||||
// always consider this PC remote to be conservative
|
// always consider this PC remote to be conservative
|
||||||
if (details.localIp.equals(details.remoteIp)) {
|
if (details.localAddress.equals(details.remoteAddress)) {
|
||||||
reachability = ComputerDetails.Reachability.REMOTE;
|
reachability = ComputerDetails.Reachability.REMOTE;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Do a TCP-level connection to the HTTP server to see if it's listening
|
// Do a TCP-level connection to the HTTP server to see if it's listening
|
||||||
LimeLog.info("Starting fast poll for "+details.name+" ("+details.localIp+", "+details.remoteIp+")");
|
LimeLog.info("Starting fast poll for "+details.name+" ("+details.localAddress +", "+details.remoteAddress +")");
|
||||||
reachability = fastPollPc(details.localIp, details.remoteIp);
|
reachability = fastPollPc(details.localAddress, details.remoteAddress);
|
||||||
LimeLog.info("Fast poll for "+details.name+" returned "+reachability.toString());
|
LimeLog.info("Fast poll for "+details.name+" returned "+reachability.toString());
|
||||||
|
|
||||||
// If no connection could be established to either IP address, there's nothing we can do
|
// If no connection could be established to either IP address, there's nothing we can do
|
||||||
@@ -489,39 +524,39 @@ public class ComputerManagerService extends Service {
|
|||||||
boolean localFirst = (reachability == ComputerDetails.Reachability.LOCAL);
|
boolean localFirst = (reachability == ComputerDetails.Reachability.LOCAL);
|
||||||
|
|
||||||
if (localFirst) {
|
if (localFirst) {
|
||||||
polledDetails = tryPollIp(details, details.localIp);
|
polledDetails = tryPollIp(details, details.localAddress);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
polledDetails = tryPollIp(details, details.remoteIp);
|
polledDetails = tryPollIp(details, details.remoteAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
InetAddress reachableAddr = null;
|
String reachableAddr = null;
|
||||||
if (polledDetails == null && !details.localIp.equals(details.remoteIp)) {
|
if (polledDetails == null && !details.localAddress.equals(details.remoteAddress)) {
|
||||||
// Failed, so let's try the fallback
|
// Failed, so let's try the fallback
|
||||||
if (!localFirst) {
|
if (!localFirst) {
|
||||||
polledDetails = tryPollIp(details, details.localIp);
|
polledDetails = tryPollIp(details, details.localAddress);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
polledDetails = tryPollIp(details, details.remoteIp);
|
polledDetails = tryPollIp(details, details.remoteAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (polledDetails != null) {
|
if (polledDetails != null) {
|
||||||
// The fallback poll worked
|
// The fallback poll worked
|
||||||
reachableAddr = !localFirst ? details.localIp : details.remoteIp;
|
reachableAddr = !localFirst ? details.localAddress : details.remoteAddress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (polledDetails != null) {
|
else if (polledDetails != null) {
|
||||||
reachableAddr = localFirst ? details.localIp : details.remoteIp;
|
reachableAddr = localFirst ? details.localAddress : details.remoteAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reachableAddr == null) {
|
if (reachableAddr == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (polledDetails.remoteIp.equals(reachableAddr)) {
|
if (polledDetails.remoteAddress.equals(reachableAddr)) {
|
||||||
polledDetails.reachability = ComputerDetails.Reachability.REMOTE;
|
polledDetails.reachability = ComputerDetails.Reachability.REMOTE;
|
||||||
}
|
}
|
||||||
else if (polledDetails.localIp.equals(reachableAddr)) {
|
else if (polledDetails.localAddress.equals(reachableAddr)) {
|
||||||
polledDetails.reachability = ComputerDetails.Reachability.LOCAL;
|
polledDetails.reachability = ComputerDetails.Reachability.LOCAL;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -543,7 +578,7 @@ public class ComputerManagerService extends Service {
|
|||||||
ReachabilityTuple confirmationReachTuple = pollForReachability(initialReachTuple.computer);
|
ReachabilityTuple confirmationReachTuple = pollForReachability(initialReachTuple.computer);
|
||||||
if (confirmationReachTuple == null) {
|
if (confirmationReachTuple == null) {
|
||||||
// Neither of those seem to work, so we'll hold onto the address that did work
|
// Neither of those seem to work, so we'll hold onto the address that did work
|
||||||
initialReachTuple.computer.localIp = initialReachTuple.reachableAddress;
|
initialReachTuple.computer.localAddress = initialReachTuple.reachableAddress;
|
||||||
initialReachTuple.computer.reachability = ComputerDetails.Reachability.LOCAL;
|
initialReachTuple.computer.reachability = ComputerDetails.Reachability.LOCAL;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -553,8 +588,11 @@ public class ComputerManagerService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the old MAC address
|
// Save some details about the old state of the PC that we may wish
|
||||||
|
// to restore later.
|
||||||
String savedMacAddress = details.macAddress;
|
String savedMacAddress = details.macAddress;
|
||||||
|
String savedLocalAddress = details.localAddress;
|
||||||
|
String savedRemoteAddress = details.remoteAddress;
|
||||||
|
|
||||||
// If we got here, it's reachable
|
// If we got here, it's reachable
|
||||||
details.update(initialReachTuple.computer);
|
details.update(initialReachTuple.computer);
|
||||||
@@ -565,6 +603,33 @@ public class ComputerManagerService extends Service {
|
|||||||
details.macAddress = savedMacAddress;
|
details.macAddress = savedMacAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We never want to lose IP addresses by polling server info. If we get a poll back
|
||||||
|
// where localAddress == remoteAddress but savedLocalAddress != savedRemoteAddress,
|
||||||
|
// then we've lost an address in the polling and we should restore the one that's missing.
|
||||||
|
if (details.localAddress.equals(details.remoteAddress) &&
|
||||||
|
!savedLocalAddress.equals(savedRemoteAddress)) {
|
||||||
|
if (details.localAddress.equals(savedLocalAddress)) {
|
||||||
|
// Local addresses are identical, so put the old remote address back
|
||||||
|
details.remoteAddress = savedRemoteAddress;
|
||||||
|
}
|
||||||
|
else if (details.remoteAddress.equals(savedRemoteAddress)) {
|
||||||
|
// Remote addresses are identical, so put the old local address back
|
||||||
|
details.localAddress = savedLocalAddress;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Neither IP address match. Let's restore the remote address to be safe.
|
||||||
|
details.remoteAddress = savedRemoteAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now update the reachability so the correct address is used
|
||||||
|
if (details.localAddress.equals(initialReachTuple.reachableAddress)) {
|
||||||
|
details.reachability = ComputerDetails.Reachability.LOCAL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
details.reachability = ComputerDetails.Reachability.REMOTE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,7 +653,7 @@ public class ComputerManagerService extends Service {
|
|||||||
|
|
||||||
for (ComputerDetails computer : dbManager.getAllComputers()) {
|
for (ComputerDetails computer : dbManager.getAllComputers()) {
|
||||||
// Add tuples for each computer
|
// Add tuples for each computer
|
||||||
addTuple(computer);
|
addTuple(computer, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
releaseLocalDatabaseReference();
|
releaseLocalDatabaseReference();
|
||||||
@@ -666,8 +731,6 @@ public class ComputerManagerService extends Service {
|
|||||||
public void run() {
|
public void run() {
|
||||||
int emptyAppListResponses = 0;
|
int emptyAppListResponses = 0;
|
||||||
do {
|
do {
|
||||||
InetAddress selectedAddr;
|
|
||||||
|
|
||||||
// Can't poll if it's not online
|
// Can't poll if it's not online
|
||||||
if (computer.state != ComputerDetails.State.ONLINE) {
|
if (computer.state != ComputerDetails.State.ONLINE) {
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
@@ -681,19 +744,12 @@ public class ComputerManagerService extends Service {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
|
|
||||||
selectedAddr = computer.localIp;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
selectedAddr = computer.remoteIp;
|
|
||||||
}
|
|
||||||
|
|
||||||
NvHTTP http = new NvHTTP(selectedAddr, idManager.getUniqueId(),
|
|
||||||
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
|
||||||
|
|
||||||
PollingTuple tuple = getPollingTuple(computer);
|
PollingTuple tuple = getPollingTuple(computer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
NvHTTP http = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer), idManager.getUniqueId(),
|
||||||
|
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
||||||
|
|
||||||
String appList;
|
String appList;
|
||||||
if (tuple != null) {
|
if (tuple != null) {
|
||||||
// If we're polling this machine too, grab the network lock
|
// If we're polling this machine too, grab the network lock
|
||||||
@@ -778,6 +834,7 @@ class PollingTuple {
|
|||||||
public Thread thread;
|
public Thread thread;
|
||||||
public final ComputerDetails computer;
|
public final ComputerDetails computer;
|
||||||
public final Object networkLock;
|
public final Object networkLock;
|
||||||
|
public long lastSuccessfulPollMs;
|
||||||
|
|
||||||
public PollingTuple(ComputerDetails computer, Thread thread) {
|
public PollingTuple(ComputerDetails computer, Thread thread) {
|
||||||
this.computer = computer;
|
this.computer = computer;
|
||||||
@@ -787,10 +844,10 @@ class PollingTuple {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ReachabilityTuple {
|
class ReachabilityTuple {
|
||||||
public final InetAddress reachableAddress;
|
public final String reachableAddress;
|
||||||
public final ComputerDetails computer;
|
public final ComputerDetails computer;
|
||||||
|
|
||||||
public ReachabilityTuple(ComputerDetails computer, InetAddress reachableAddress) {
|
public ReachabilityTuple(ComputerDetails computer, String reachableAddress) {
|
||||||
this.computer = computer;
|
this.computer = computer;
|
||||||
this.reachableAddress = reachableAddress;
|
this.reachableAddress = reachableAddress;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class DiscoveryService extends Service {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE);
|
WifiManager wifiMgr = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||||
multicastLock = wifiMgr.createMulticastLock("Limelight mDNS");
|
multicastLock = wifiMgr.createMulticastLock("Limelight mDNS");
|
||||||
multicastLock.setReferenceCounted(false);
|
multicastLock.setReferenceCounted(false);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.limelight.grid;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.limelight.AppView;
|
import com.limelight.AppView;
|
||||||
@@ -26,7 +27,7 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
|||||||
private final CachedAppAssetLoader loader;
|
private final CachedAppAssetLoader loader;
|
||||||
|
|
||||||
public AppGridAdapter(Activity activity, boolean listMode, boolean small, ComputerDetails computer, String uniqueId) {
|
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 dpi = activity.getResources().getDisplayMetrics().densityDpi;
|
||||||
int dp;
|
int dp;
|
||||||
@@ -51,9 +52,7 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
|||||||
this.loader = new CachedAppAssetLoader(computer, scalingDivisor,
|
this.loader = new CachedAppAssetLoader(computer, scalingDivisor,
|
||||||
new NetworkAssetLoader(context, uniqueId),
|
new NetworkAssetLoader(context, uniqueId),
|
||||||
new MemoryAssetLoader(),
|
new MemoryAssetLoader(),
|
||||||
new DiskAssetLoader(context.getCacheDir()),
|
new DiskAssetLoader(context.getCacheDir()));
|
||||||
BitmapFactory.decodeResource(activity.getResources(),
|
|
||||||
R.drawable.image_loading, options));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancelQueuedOperations() {
|
public void cancelQueuedOperations() {
|
||||||
@@ -66,7 +65,7 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
|||||||
Collections.sort(itemList, new Comparator<AppView.AppObject>() {
|
Collections.sort(itemList, new Comparator<AppView.AppObject>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(AppView.AppObject lhs, AppView.AppObject rhs) {
|
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());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -84,9 +83,10 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
|||||||
itemList.remove(app);
|
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
|
// Let the cached asset loader handle it
|
||||||
loader.populateImageView(obj.app, imgView);
|
loader.populateImageView(obj.app, imgView, prgView);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,9 +101,9 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean populateOverlayView(ImageView overlayView, AppView.AppObject obj) {
|
public boolean populateOverlayView(ImageView overlayView, AppView.AppObject obj) {
|
||||||
if (obj.app.getIsRunning()) {
|
if (obj.isRunning) {
|
||||||
// Show the play button overlay
|
// Show the play button overlay
|
||||||
overlayView.setImageResource(R.drawable.play);
|
overlayView.setImageResource(R.drawable.ic_play);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.BaseAdapter;
|
import android.widget.BaseAdapter;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.limelight.R;
|
import com.limelight.R;
|
||||||
@@ -14,15 +15,13 @@ import java.util.ArrayList;
|
|||||||
|
|
||||||
public abstract class GenericGridAdapter<T> extends BaseAdapter {
|
public abstract class GenericGridAdapter<T> extends BaseAdapter {
|
||||||
protected final Context context;
|
protected final Context context;
|
||||||
protected final int defaultImageRes;
|
|
||||||
protected final int layoutId;
|
protected final int layoutId;
|
||||||
protected final ArrayList<T> itemList = new ArrayList<>();
|
protected final ArrayList<T> itemList = new ArrayList<>();
|
||||||
protected final LayoutInflater inflater;
|
protected final LayoutInflater inflater;
|
||||||
|
|
||||||
public GenericGridAdapter(Context context, int layoutId, int defaultImageRes) {
|
public GenericGridAdapter(Context context, int layoutId) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.layoutId = layoutId;
|
this.layoutId = layoutId;
|
||||||
this.defaultImageRes = defaultImageRes;
|
|
||||||
|
|
||||||
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
}
|
}
|
||||||
@@ -46,7 +45,7 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
|
|||||||
return i;
|
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 populateTextView(TextView txtView, T obj);
|
||||||
public abstract boolean populateOverlayView(ImageView overlayView, T obj);
|
public abstract boolean populateOverlayView(ImageView overlayView, T obj);
|
||||||
|
|
||||||
@@ -56,13 +55,14 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
|
|||||||
convertView = inflater.inflate(layoutId, viewGroup, false);
|
convertView = inflater.inflate(layoutId, viewGroup, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageView imgView = (ImageView) convertView.findViewById(R.id.grid_image);
|
ImageView imgView = convertView.findViewById(R.id.grid_image);
|
||||||
ImageView overlayView = (ImageView) convertView.findViewById(R.id.grid_overlay);
|
ImageView overlayView = convertView.findViewById(R.id.grid_overlay);
|
||||||
TextView txtView = (TextView) convertView.findViewById(R.id.grid_text);
|
TextView txtView = convertView.findViewById(R.id.grid_text);
|
||||||
|
ProgressBar prgView = convertView.findViewById(R.id.grid_spinner);
|
||||||
|
|
||||||
if (imgView != null) {
|
if (imgView != null) {
|
||||||
if (!populateImageView(imgView, itemList.get(i))) {
|
if (!populateImageView(imgView, prgView, itemList.get(i))) {
|
||||||
imgView.setImageResource(defaultImageRes);
|
imgView.setImageBitmap(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!populateTextView(txtView, itemList.get(i))) {
|
if (!populateTextView(txtView, itemList.get(i))) {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.limelight.grid;
|
package com.limelight.grid;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.limelight.PcView;
|
import com.limelight.PcView;
|
||||||
@@ -14,7 +16,7 @@ import java.util.Comparator;
|
|||||||
public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
||||||
|
|
||||||
public PcGridAdapter(Context context, boolean listMode, boolean small) {
|
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) {
|
public void addComputer(PcView.ComputerObject computer) {
|
||||||
@@ -26,7 +28,7 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
|||||||
Collections.sort(itemList, new Comparator<PcView.ComputerObject>() {
|
Collections.sort(itemList, new Comparator<PcView.ComputerObject>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(PcView.ComputerObject lhs, PcView.ComputerObject rhs) {
|
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
|
@Override
|
||||||
public boolean populateImageView(ImageView imgView, PcView.ComputerObject obj) {
|
public boolean populateImageView(ImageView imgView, ProgressBar prgView, PcView.ComputerObject obj) {
|
||||||
if (obj.details.reachability != ComputerDetails.Reachability.OFFLINE) {
|
if (obj.details.state == ComputerDetails.State.ONLINE) {
|
||||||
imgView.setAlpha(1.0f);
|
imgView.setAlpha(1.0f);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
imgView.setAlpha(0.4f);
|
imgView.setAlpha(0.4f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return false to use the default drawable
|
if (obj.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
|
||||||
return false;
|
prgView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prgView.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
imgView.setImageResource(R.drawable.ic_computer);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean populateTextView(TextView txtView, PcView.ComputerObject obj) {
|
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);
|
txtView.setAlpha(1.0f);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -63,13 +72,11 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean populateOverlayView(ImageView overlayView, PcView.ComputerObject obj) {
|
public boolean populateOverlayView(ImageView overlayView, PcView.ComputerObject obj) {
|
||||||
if (obj.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
|
if (obj.details.state == ComputerDetails.State.OFFLINE) {
|
||||||
// Still refreshing this PC so display the overlay
|
overlayView.setImageResource(R.drawable.ic_pc_offline);
|
||||||
overlayView.setImageResource(R.drawable.image_loading);
|
overlayView.setAlpha(0.4f);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No overlay
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import android.graphics.Bitmap;
|
|||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
import com.limelight.nvstream.http.ComputerDetails;
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
import com.limelight.nvstream.http.NvApp;
|
import com.limelight.nvstream.http.NvApp;
|
||||||
@@ -53,13 +55,13 @@ public class CachedAppAssetLoader {
|
|||||||
|
|
||||||
public CachedAppAssetLoader(ComputerDetails computer, double scalingDivider,
|
public CachedAppAssetLoader(ComputerDetails computer, double scalingDivider,
|
||||||
NetworkAssetLoader networkLoader, MemoryAssetLoader memoryLoader,
|
NetworkAssetLoader networkLoader, MemoryAssetLoader memoryLoader,
|
||||||
DiskAssetLoader diskLoader, Bitmap placeholderBitmap) {
|
DiskAssetLoader diskLoader) {
|
||||||
this.computer = computer;
|
this.computer = computer;
|
||||||
this.scalingDivider = scalingDivider;
|
this.scalingDivider = scalingDivider;
|
||||||
this.networkLoader = networkLoader;
|
this.networkLoader = networkLoader;
|
||||||
this.memoryLoader = memoryLoader;
|
this.memoryLoader = memoryLoader;
|
||||||
this.diskLoader = diskLoader;
|
this.diskLoader = diskLoader;
|
||||||
this.placeholderBitmap = placeholderBitmap;
|
this.placeholderBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancelBackgroundLoads() {
|
public void cancelBackgroundLoads() {
|
||||||
@@ -130,12 +132,14 @@ public class CachedAppAssetLoader {
|
|||||||
|
|
||||||
private class LoaderTask extends AsyncTask<LoaderTuple, Void, Bitmap> {
|
private class LoaderTask extends AsyncTask<LoaderTuple, Void, Bitmap> {
|
||||||
private final WeakReference<ImageView> imageViewRef;
|
private final WeakReference<ImageView> imageViewRef;
|
||||||
|
private final WeakReference<ProgressBar> progressViewRef;
|
||||||
private final boolean diskOnly;
|
private final boolean diskOnly;
|
||||||
|
|
||||||
private LoaderTuple tuple;
|
private LoaderTuple tuple;
|
||||||
|
|
||||||
public LoaderTask(ImageView imageView, boolean diskOnly) {
|
public LoaderTask(ImageView imageView, ProgressBar prgView, boolean diskOnly) {
|
||||||
this.imageViewRef = new WeakReference<>(imageView);
|
this.imageViewRef = new WeakReference<>(imageView);
|
||||||
|
this.progressViewRef = new WeakReference<>(prgView);
|
||||||
this.diskOnly = diskOnly;
|
this.diskOnly = diskOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,8 +147,8 @@ public class CachedAppAssetLoader {
|
|||||||
protected Bitmap doInBackground(LoaderTuple... params) {
|
protected Bitmap doInBackground(LoaderTuple... params) {
|
||||||
tuple = params[0];
|
tuple = params[0];
|
||||||
|
|
||||||
// Check whether it has been cancelled or the image view is gone
|
// Check whether it has been cancelled or the views are gone
|
||||||
if (isCancelled() || imageViewRef.get() == null) {
|
if (isCancelled() || imageViewRef.get() == null || progressViewRef.get() == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,11 +181,17 @@ public class CachedAppAssetLoader {
|
|||||||
|
|
||||||
// If the current loader task for this view isn't us, do nothing
|
// If the current loader task for this view isn't us, do nothing
|
||||||
final ImageView imageView = imageViewRef.get();
|
final ImageView imageView = imageViewRef.get();
|
||||||
|
final ProgressBar prgView = progressViewRef.get();
|
||||||
if (getLoaderTask(imageView) == this) {
|
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
|
// 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);
|
AsyncDrawable asyncDrawable = new AsyncDrawable(imageView.getResources(), placeholderBitmap, task);
|
||||||
imageView.setAlpha(1.0f);
|
imageView.setVisibility(View.VISIBLE);
|
||||||
imageView.setImageDrawable(asyncDrawable);
|
imageView.setImageDrawable(asyncDrawable);
|
||||||
task.executeOnExecutor(networkExecutor, tuple);
|
task.executeOnExecutor(networkExecutor, tuple);
|
||||||
}
|
}
|
||||||
@@ -195,14 +205,20 @@ public class CachedAppAssetLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final ImageView imageView = imageViewRef.get();
|
final ImageView imageView = imageViewRef.get();
|
||||||
|
final ProgressBar prgView = progressViewRef.get();
|
||||||
if (getLoaderTask(imageView) == this) {
|
if (getLoaderTask(imageView) == this) {
|
||||||
// Set the bitmap
|
// Set the bitmap
|
||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
imageView.setImageBitmap(bitmap);
|
imageView.setImageBitmap(bitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide the progress bar
|
||||||
|
if (prgView != null) {
|
||||||
|
prgView.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
// Show the view
|
// Show the view
|
||||||
imageView.setAlpha(1.0f);
|
imageView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
LoaderTuple tuple = new LoaderTuple(computer, app);
|
||||||
|
|
||||||
// If there's already a task in progress for this view,
|
// If there's already a task in progress for this view,
|
||||||
// cancel it. If the task is already loading the same image,
|
// cancel it. If the task is already loading the same image,
|
||||||
// we return and let that load finish.
|
// we return and let that load finish.
|
||||||
if (!cancelPendingLoad(tuple, view)) {
|
if (!cancelPendingLoad(tuple, imgView)) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide the progress bar always on initial load
|
||||||
|
prgView.setVisibility(View.INVISIBLE);
|
||||||
|
|
||||||
// First, try the memory cache in the current context
|
// First, try the memory cache in the current context
|
||||||
Bitmap bmp = memoryLoader.loadBitmapFromCache(tuple);
|
Bitmap bmp = memoryLoader.loadBitmapFromCache(tuple);
|
||||||
if (bmp != null) {
|
if (bmp != null) {
|
||||||
// Show the bitmap immediately
|
// Show the bitmap immediately
|
||||||
view.setAlpha(1.0f);
|
imgView.setVisibility(View.VISIBLE);
|
||||||
view.setImageBitmap(bmp);
|
imgView.setImageBitmap(bmp);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's not in memory, create an async task to load it. This task will be attached
|
// If it's not in memory, create an async task to load it. This task will be attached
|
||||||
// via AsyncDrawable to this view.
|
// via AsyncDrawable to this view.
|
||||||
final LoaderTask task = new LoaderTask(view, true);
|
final LoaderTask task = new LoaderTask(imgView, prgView, true);
|
||||||
final AsyncDrawable asyncDrawable = new AsyncDrawable(view.getResources(), placeholderBitmap, task);
|
final AsyncDrawable asyncDrawable = new AsyncDrawable(imgView.getResources(), placeholderBitmap, task);
|
||||||
view.setAlpha(0.0f);
|
imgView.setVisibility(View.INVISIBLE);
|
||||||
view.setImageDrawable(asyncDrawable);
|
imgView.setImageDrawable(asyncDrawable);
|
||||||
|
|
||||||
// Run the task on our foreground executor
|
// Run the task on our foreground executor
|
||||||
task.executeOnExecutor(foregroundExecutor, tuple);
|
task.executeOnExecutor(foregroundExecutor, tuple);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LoaderTuple {
|
public class LoaderTuple {
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ import android.content.Context;
|
|||||||
|
|
||||||
import com.limelight.LimeLog;
|
import com.limelight.LimeLog;
|
||||||
import com.limelight.binding.PlatformBinding;
|
import com.limelight.binding.PlatformBinding;
|
||||||
import com.limelight.nvstream.http.ComputerDetails;
|
|
||||||
import com.limelight.nvstream.http.NvHTTP;
|
import com.limelight.nvstream.http.NvHTTP;
|
||||||
|
import com.limelight.utils.ServerHelper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.InetAddress;
|
|
||||||
|
|
||||||
public class NetworkAssetLoader {
|
public class NetworkAssetLoader {
|
||||||
private final Context context;
|
private final Context context;
|
||||||
@@ -21,10 +20,9 @@ public class NetworkAssetLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public InputStream getBitmapStream(CachedAppAssetLoader.LoaderTuple tuple) {
|
public InputStream getBitmapStream(CachedAppAssetLoader.LoaderTuple tuple) {
|
||||||
NvHTTP http = new NvHTTP(getCurrentAddress(tuple.computer), uniqueId, null, PlatformBinding.getCryptoProvider(context));
|
|
||||||
|
|
||||||
InputStream in = null;
|
InputStream in = null;
|
||||||
try {
|
try {
|
||||||
|
NvHTTP http = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(tuple.computer), uniqueId, null, PlatformBinding.getCryptoProvider(context));
|
||||||
in = http.getBoxArt(tuple.app);
|
in = http.getBoxArt(tuple.app);
|
||||||
} catch (IOException ignored) {}
|
} catch (IOException ignored) {}
|
||||||
|
|
||||||
@@ -37,13 +35,4 @@ public class NetworkAssetLoader {
|
|||||||
|
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InetAddress getCurrentAddress(ComputerDetails computer) {
|
|
||||||
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
|
|
||||||
return computer.localIp;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return computer.remoteIp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
package com.limelight.preferences;
|
package com.limelight.preferences;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
import com.limelight.computers.ComputerManagerService;
|
import com.limelight.computers.ComputerManagerService;
|
||||||
@@ -17,7 +14,6 @@ import android.content.ComponentName;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
@@ -50,18 +46,12 @@ public class AddComputerManually extends Activity {
|
|||||||
SpinnerDialog dialog = SpinnerDialog.displayDialog(this, getResources().getString(R.string.title_add_pc),
|
SpinnerDialog dialog = SpinnerDialog.displayDialog(this, getResources().getString(R.string.title_add_pc),
|
||||||
getResources().getString(R.string.msg_add_pc), false);
|
getResources().getString(R.string.msg_add_pc), false);
|
||||||
|
|
||||||
try {
|
if (!managerBinder.addComputerBlocking(host, true)){
|
||||||
InetAddress addr = InetAddress.getByName(host);
|
msg = getResources().getString(R.string.addpc_fail);
|
||||||
|
}
|
||||||
if (!managerBinder.addComputerBlocking(addr)){
|
else {
|
||||||
msg = getResources().getString(R.string.addpc_fail);
|
msg = getResources().getString(R.string.addpc_success);
|
||||||
}
|
finish = true;
|
||||||
else {
|
|
||||||
msg = getResources().getString(R.string.addpc_success);
|
|
||||||
finish = true;
|
|
||||||
}
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
msg = getResources().getString(R.string.addpc_unknown_host);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
@@ -136,18 +126,13 @@ public class AddComputerManually extends Activity {
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
UiHelper.setLocale(this);
|
||||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
|
||||||
Configuration config = new Configuration(getResources().getConfiguration());
|
|
||||||
config.locale = new Locale(locale);
|
|
||||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
|
||||||
}
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_add_computer_manually);
|
setContentView(R.layout.activity_add_computer_manually);
|
||||||
|
|
||||||
UiHelper.notifyNewRootView(this);
|
UiHelper.notifyNewRootView(this);
|
||||||
|
|
||||||
this.hostText = (TextView) findViewById(R.id.hostTextView);
|
this.hostText = findViewById(R.id.hostTextView);
|
||||||
hostText.setImeOptions(EditorInfo.IME_ACTION_DONE);
|
hostText.setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||||
hostText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
hostText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.limelight.preferences;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
@@ -23,6 +22,7 @@ public class PreferenceConfiguration {
|
|||||||
private static final String USB_DRIVER_PREF_SRING = "checkbox_usb_driver";
|
private static final String USB_DRIVER_PREF_SRING = "checkbox_usb_driver";
|
||||||
private static final String VIDEO_FORMAT_PREF_STRING = "video_format";
|
private static final String VIDEO_FORMAT_PREF_STRING = "video_format";
|
||||||
private static final String ONSCREEN_CONTROLLER_PREF_STRING = "checkbox_show_onscreen_controls";
|
private static final String ONSCREEN_CONTROLLER_PREF_STRING = "checkbox_show_onscreen_controls";
|
||||||
|
private static final String BATTERY_SAVER_PREF_STRING = "checkbox_battery_saver";
|
||||||
|
|
||||||
private static final int BITRATE_DEFAULT_720_30 = 5;
|
private static final int BITRATE_DEFAULT_720_30 = 5;
|
||||||
private static final int BITRATE_DEFAULT_720_60 = 10;
|
private static final int BITRATE_DEFAULT_720_60 = 10;
|
||||||
@@ -45,6 +45,7 @@ public class PreferenceConfiguration {
|
|||||||
private static final boolean DEFAULT_USB_DRIVER = true;
|
private static final boolean DEFAULT_USB_DRIVER = true;
|
||||||
private static final String DEFAULT_VIDEO_FORMAT = "auto";
|
private static final String DEFAULT_VIDEO_FORMAT = "auto";
|
||||||
private static final boolean ONSCREEN_CONTROLLER_DEFAULT = false;
|
private static final boolean ONSCREEN_CONTROLLER_DEFAULT = false;
|
||||||
|
private static final boolean DEFAULT_BATTERY_SAVER = false;
|
||||||
|
|
||||||
public static final int FORCE_H265_ON = -1;
|
public static final int FORCE_H265_ON = -1;
|
||||||
public static final int AUTOSELECT_H265 = 0;
|
public static final int AUTOSELECT_H265 = 0;
|
||||||
@@ -58,6 +59,7 @@ public class PreferenceConfiguration {
|
|||||||
public String language;
|
public String language;
|
||||||
public boolean listMode, smallIconMode, multiController, enable51Surround, usbDriver;
|
public boolean listMode, smallIconMode, multiController, enable51Surround, usbDriver;
|
||||||
public boolean onscreenController;
|
public boolean onscreenController;
|
||||||
|
public boolean batterySaver;
|
||||||
|
|
||||||
public static int getDefaultBitrate(String resFpsString) {
|
public static int getDefaultBitrate(String resFpsString) {
|
||||||
if (resFpsString.equals("720p30")) {
|
if (resFpsString.equals("720p30")) {
|
||||||
@@ -128,6 +130,16 @@ public class PreferenceConfiguration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void resetStreamingSettings(Context context) {
|
||||||
|
// We consider resolution, FPS, bitrate, and video format as "streaming settings" here
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
prefs.edit()
|
||||||
|
.remove(BITRATE_PREF_STRING)
|
||||||
|
.remove(RES_FPS_PREF_STRING)
|
||||||
|
.remove(VIDEO_FORMAT_PREF_STRING)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
|
||||||
public static PreferenceConfiguration readPreferences(Context context) {
|
public static PreferenceConfiguration readPreferences(Context context) {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
PreferenceConfiguration config = new PreferenceConfiguration();
|
PreferenceConfiguration config = new PreferenceConfiguration();
|
||||||
@@ -188,6 +200,7 @@ public class PreferenceConfiguration {
|
|||||||
config.enable51Surround = prefs.getBoolean(ENABLE_51_SURROUND_PREF_STRING, DEFAULT_ENABLE_51_SURROUND);
|
config.enable51Surround = prefs.getBoolean(ENABLE_51_SURROUND_PREF_STRING, DEFAULT_ENABLE_51_SURROUND);
|
||||||
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
|
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
|
||||||
config.onscreenController = prefs.getBoolean(ONSCREEN_CONTROLLER_PREF_STRING, ONSCREEN_CONTROLLER_DEFAULT);
|
config.onscreenController = prefs.getBoolean(ONSCREEN_CONTROLLER_PREF_STRING, ONSCREEN_CONTROLLER_DEFAULT);
|
||||||
|
config.batterySaver = prefs.getBoolean(BATTERY_SAVER_PREF_STRING, DEFAULT_BATTERY_SAVER);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.limelight.preferences;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
@@ -15,8 +14,6 @@ import com.limelight.PcView;
|
|||||||
import com.limelight.R;
|
import com.limelight.R;
|
||||||
import com.limelight.utils.UiHelper;
|
import com.limelight.utils.UiHelper;
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
public class StreamSettings extends Activity {
|
public class StreamSettings extends Activity {
|
||||||
private PreferenceConfiguration previousPrefs;
|
private PreferenceConfiguration previousPrefs;
|
||||||
|
|
||||||
@@ -26,11 +23,7 @@ public class StreamSettings extends Activity {
|
|||||||
|
|
||||||
previousPrefs = PreferenceConfiguration.readPreferences(this);
|
previousPrefs = PreferenceConfiguration.readPreferences(this);
|
||||||
|
|
||||||
if (!previousPrefs.language.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
UiHelper.setLocale(this);
|
||||||
Configuration config = new Configuration(getResources().getConfiguration());
|
|
||||||
config.locale = new Locale(previousPrefs.language);
|
|
||||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
|
||||||
}
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_stream_settings);
|
setContentView(R.layout.activity_stream_settings);
|
||||||
getFragmentManager().beginTransaction().replace(
|
getFragmentManager().beginTransaction().replace(
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ package com.limelight.ui;
|
|||||||
import android.widget.AbsListView;
|
import android.widget.AbsListView;
|
||||||
|
|
||||||
public interface AdapterFragmentCallbacks {
|
public interface AdapterFragmentCallbacks {
|
||||||
public int getAdapterFragmentLayoutId();
|
int getAdapterFragmentLayoutId();
|
||||||
public void receiveAbsListView(AbsListView gridView);
|
void receiveAbsListView(AbsListView gridView);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package com.limelight.ui;
|
package com.limelight.ui;
|
||||||
|
|
||||||
public interface GameGestures {
|
public interface GameGestures {
|
||||||
public void showKeyboard();
|
void showKeyboard();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import java.util.ArrayList;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
|
import android.widget.Button;
|
||||||
|
|
||||||
|
import com.limelight.R;
|
||||||
|
|
||||||
public class Dialog implements Runnable {
|
public class Dialog implements Runnable {
|
||||||
private final String title;
|
private final String title;
|
||||||
@@ -55,7 +58,7 @@ public class Dialog implements Runnable {
|
|||||||
alert.setCancelable(false);
|
alert.setCancelable(false);
|
||||||
alert.setCanceledOnTouchOutside(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) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
synchronized (rundownDialogs) {
|
synchronized (rundownDialogs) {
|
||||||
rundownDialogs.remove(Dialog.this);
|
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) {
|
synchronized (rundownDialogs) {
|
||||||
rundownDialogs.add(this);
|
rundownDialogs.add(this);
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.limelight.utils;
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,31 +14,35 @@ import com.limelight.nvstream.http.NvApp;
|
|||||||
import com.limelight.nvstream.http.NvHTTP;
|
import com.limelight.nvstream.http.NvHTTP;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
public class ServerHelper {
|
public class ServerHelper {
|
||||||
public static InetAddress getCurrentAddressFromComputer(ComputerDetails computer) {
|
public static String getCurrentAddressFromComputer(ComputerDetails computer) {
|
||||||
return computer.reachability == ComputerDetails.Reachability.LOCAL ?
|
return computer.reachability == ComputerDetails.Reachability.LOCAL ?
|
||||||
computer.localIp : computer.remoteIp;
|
computer.localAddress : computer.remoteAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void doStart(Activity parent, NvApp app, ComputerDetails computer,
|
public static Intent createStartIntent(Activity parent, NvApp app, ComputerDetails computer,
|
||||||
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
||||||
Intent intent = new Intent(parent, Game.class);
|
Intent intent = new Intent(parent, Game.class);
|
||||||
intent.putExtra(Game.EXTRA_HOST,
|
intent.putExtra(Game.EXTRA_HOST, getCurrentAddressFromComputer(computer));
|
||||||
computer.reachability == ComputerDetails.Reachability.LOCAL ?
|
|
||||||
computer.localIp.getHostAddress() : computer.remoteIp.getHostAddress());
|
|
||||||
intent.putExtra(Game.EXTRA_APP_NAME, app.getAppName());
|
intent.putExtra(Game.EXTRA_APP_NAME, app.getAppName());
|
||||||
intent.putExtra(Game.EXTRA_APP_ID, app.getAppId());
|
intent.putExtra(Game.EXTRA_APP_ID, app.getAppId());
|
||||||
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
|
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
|
||||||
intent.putExtra(Game.EXTRA_STREAMING_REMOTE,
|
intent.putExtra(Game.EXTRA_STREAMING_REMOTE,
|
||||||
computer.reachability != ComputerDetails.Reachability.LOCAL);
|
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,
|
public static void doQuit(final Activity parent,
|
||||||
final InetAddress address,
|
final String address,
|
||||||
final NvApp app,
|
final NvApp app,
|
||||||
final ComputerManagerService.ComputerManagerBinder managerBinder,
|
final ComputerManagerService.ComputerManagerBinder managerBinder,
|
||||||
final Runnable onComplete) {
|
final Runnable onComplete) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,9 @@ import android.content.res.Configuration;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import com.limelight.R;
|
import com.limelight.R;
|
||||||
|
import com.limelight.preferences.PreferenceConfiguration;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class UiHelper {
|
public class UiHelper {
|
||||||
|
|
||||||
@@ -16,6 +19,28 @@ public class UiHelper {
|
|||||||
private static final int TV_VERTICAL_PADDING_DP = 27;
|
private static final int TV_VERTICAL_PADDING_DP = 27;
|
||||||
private static final int TV_HORIZONTAL_PADDING_DP = 48;
|
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)
|
public static void notifyNewRootView(Activity activity)
|
||||||
{
|
{
|
||||||
View rootView = activity.findViewById(android.R.id.content);
|
View rootView = activity.findViewById(android.R.id.content);
|
||||||
|
|||||||
@@ -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/in.h>
|
||||||
#include <netinet/tcp.h>
|
#include <netinet/tcp.h>
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
|
|
||||||
#define EVDEV_MAX_EVENT_SIZE 24
|
#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: 5.2 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 40 KiB |
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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" >
|
tools:context=".PcView" >
|
||||||
|
|
||||||
<RelativeLayout
|
<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:id="@+id/pcFragmentContainer"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:layout_toLeftOf="@+id/manuallyAddPc"
|
|
||||||
android:layout_toStartOf="@+id/manuallyAddPc"
|
|
||||||
android:layout_toRightOf="@+id/settingsButton"
|
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
|
<ImageButton
|
||||||
android:id="@+id/settingsButton"
|
android:id="@+id/settingsButton"
|
||||||
@@ -51,11 +50,24 @@
|
|||||||
android:layout_height="65dp"
|
android:layout_height="65dp"
|
||||||
android:cropToPadding="false"
|
android:cropToPadding="false"
|
||||||
android:scaleType="fitXY"
|
android:scaleType="fitXY"
|
||||||
android:nextFocusDown="@+id/pcGridView"
|
android:nextFocusForward="@id/helpButton"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_alignParentTop="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"/>
|
style="?android:attr/borderlessButtonStyle"/>
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
@@ -64,11 +76,10 @@
|
|||||||
android:layout_height="65dp"
|
android:layout_height="65dp"
|
||||||
android:cropToPadding="false"
|
android:cropToPadding="false"
|
||||||
android:scaleType="fitXY"
|
android:scaleType="fitXY"
|
||||||
android:nextFocusDown="@+id/pcGridView"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_below="@id/helpButton"
|
||||||
android:layout_alignParentEnd="true"
|
android:src="@drawable/ic_add"
|
||||||
android:src="@drawable/add_computer"
|
|
||||||
style="?android:attr/borderlessButtonStyle"/>
|
style="?android:attr/borderlessButtonStyle"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|||||||
@@ -9,41 +9,44 @@
|
|||||||
tools:context=".PcView" >
|
tools:context=".PcView" >
|
||||||
|
|
||||||
<RelativeLayout
|
<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:id="@+id/pcFragmentContainer"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:layout_toLeftOf="@+id/manuallyAddPc"
|
android:layout_below="@+id/settingsButton"
|
||||||
android:layout_toStartOf="@+id/manuallyAddPc"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_toRightOf="@+id/settingsButton"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_toEndOf="@+id/settingsButton"
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_alignParentBottom="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
|
<ImageButton
|
||||||
android:id="@+id/settingsButton"
|
android:id="@+id/settingsButton"
|
||||||
@@ -54,7 +57,19 @@
|
|||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_alignParentTop="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"/>
|
style="?android:attr/borderlessButtonStyle"/>
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
@@ -66,7 +81,7 @@
|
|||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:src="@drawable/add_computer"
|
android:src="@drawable/ic_add"
|
||||||
style="?android:attr/borderlessButtonStyle"/>
|
style="?android:attr/borderlessButtonStyle"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
tools:context=".AddComputerManually" >
|
tools:context=".preferences.AddComputerManually" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/manuallyAddPcText"
|
android:id="@+id/manuallyAddPcText"
|
||||||
|
|||||||
@@ -7,6 +7,6 @@
|
|||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
android:id="@+id/stream_settings"
|
android:id="@+id/stream_settings"
|
||||||
tools:context=".StreamSettings">
|
tools:context=".preferences.StreamSettings">
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||