mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-24 01:31:46 +00:00
Compare commits
156 Commits
v2019.1.1-
...
v2019.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60c2f59051 | ||
|
|
d55ca191b8 | ||
|
|
e8b24717c7 | ||
|
|
182758c05b | ||
|
|
74f7ba04b0 | ||
|
|
997d4fdf47 | ||
|
|
76d9e26633 | ||
|
|
a230c814cb | ||
|
|
12cb77cd7c | ||
|
|
8a9822a96b | ||
|
|
a9371a7586 | ||
|
|
6992f5421f | ||
|
|
43696956d2 | ||
|
|
ae3fd5adac | ||
|
|
404666b298 | ||
|
|
1eb4c99d15 | ||
|
|
910b9f3af7 | ||
|
|
09d90b02fb | ||
|
|
0e1f9c2ed2 | ||
|
|
f156a00117 | ||
|
|
4a6087ed56 | ||
|
|
88a09dd13a | ||
|
|
7d19596367 | ||
|
|
bd05dfa1c7 | ||
|
|
05d6660a6b | ||
|
|
1349dd4bd8 | ||
|
|
fdf298b172 | ||
|
|
453a9047e4 | ||
|
|
e97e7a7611 | ||
|
|
308bdbe298 | ||
|
|
f889b45d59 | ||
|
|
444b899a9f | ||
|
|
f121ccff0d | ||
|
|
bc2c932f92 | ||
|
|
6bdd7ce506 | ||
|
|
c12d7729e3 | ||
|
|
3635116049 | ||
|
|
6105873cbe | ||
|
|
80f87ff8ad | ||
|
|
a2368a6199 | ||
|
|
ae3cb6c83b | ||
|
|
f0f196e5b3 | ||
|
|
7c35355d29 | ||
|
|
75cc09a9e4 | ||
|
|
0e2e180635 | ||
|
|
01d1322066 | ||
|
|
ceed1d74dc | ||
|
|
e1bf623997 | ||
|
|
d46ce13ffe | ||
|
|
300eeb330d | ||
|
|
d817001259 | ||
|
|
8ac4b113a5 | ||
|
|
f3864e9abb | ||
|
|
799c3ea8a6 | ||
|
|
8d95c38e39 | ||
|
|
a7f4e29b73 | ||
|
|
b88369f5e8 | ||
|
|
ce6f1d0588 | ||
|
|
f163216a4c | ||
|
|
c449ef1064 | ||
|
|
6593f4346e | ||
|
|
ce1367a115 | ||
|
|
0d7d880261 | ||
|
|
ca2acec88c | ||
|
|
3721463eb3 | ||
|
|
6e8f8be370 | ||
|
|
d84240d8e9 | ||
|
|
1823cb2b68 | ||
|
|
41596608cc | ||
|
|
0c3b488e18 | ||
|
|
7d7af287f6 | ||
|
|
4119622994 | ||
|
|
d528a77963 | ||
|
|
dab7e1b7a2 | ||
|
|
ab49345460 | ||
|
|
608d82423d | ||
|
|
e0e15eafeb | ||
|
|
0fb24538a7 | ||
|
|
d65547ea74 | ||
|
|
bfe15245a6 | ||
|
|
ff58c5156a | ||
|
|
6d4326a560 | ||
|
|
97ba195b88 | ||
|
|
3d546428ab | ||
|
|
b64dfacff3 | ||
|
|
dcbf02a1ec | ||
|
|
7e1ec28839 | ||
|
|
ef16317f8f | ||
|
|
26e8e587f9 | ||
|
|
0d0492bfcc | ||
|
|
3b33abfc7b | ||
|
|
99033481e0 | ||
|
|
b4901985b7 | ||
|
|
97edb6c68f | ||
|
|
73de3364b7 | ||
|
|
5551981b3f | ||
|
|
90572a3cc5 | ||
|
|
c405188052 | ||
|
|
bea0565ac9 | ||
|
|
0b03454366 | ||
|
|
489701cacc | ||
|
|
a769d56ec1 | ||
|
|
6f0c185a05 | ||
|
|
a60f312d19 | ||
|
|
acb786a791 | ||
|
|
df347e3d80 | ||
|
|
e4aa45f34b | ||
|
|
75cc3cda28 | ||
|
|
45f4472d42 | ||
|
|
69cb53b51b | ||
|
|
70a66fc943 | ||
|
|
9207d788ab | ||
|
|
ef3a31aa20 | ||
|
|
63775362fe | ||
|
|
55493b0c18 | ||
|
|
1696557c46 | ||
|
|
ecd376be4c | ||
|
|
f54c0f70f6 | ||
|
|
9bc998f4b0 | ||
|
|
43d188a429 | ||
|
|
563d5334c9 | ||
|
|
193b0a222c | ||
|
|
76f5d153fa | ||
|
|
19caeca990 | ||
|
|
0abae17653 | ||
|
|
81d10bc656 | ||
|
|
b51b86525d | ||
|
|
ace37c517e | ||
|
|
ac751d3224 | ||
|
|
7c9a3c4d77 | ||
|
|
8be693f55d | ||
|
|
622ae29dff | ||
|
|
e7c98feca2 | ||
|
|
28087424ec | ||
|
|
b6830638df | ||
|
|
fb557f49ea | ||
|
|
746f950a0b | ||
|
|
9a38a3e188 | ||
|
|
2e3e3a47b9 | ||
|
|
e27d6d7bb8 | ||
|
|
1dec0393a1 | ||
|
|
d03b020326 | ||
|
|
71e29b1d91 | ||
|
|
f0b0965f9b | ||
|
|
f774e47c80 | ||
|
|
761933a164 | ||
|
|
99e0f08a6f | ||
|
|
e89d5eb692 | ||
|
|
2501e11886 | ||
|
|
9174f23f36 | ||
|
|
9f6544fa87 | ||
|
|
9a1af132bf | ||
|
|
a8aacd3657 | ||
|
|
8ff81f5a2a | ||
|
|
349e273ecc | ||
|
|
0a2ab4f0d7 |
6
.wpilib/wpilib_preferences.json
Normal file
6
.wpilib/wpilib_preferences.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"enableCppIntellisense": true,
|
||||
"currentLanguage": "cpp",
|
||||
"projectYear": "2019",
|
||||
"teamNumber": 0
|
||||
}
|
||||
19
README.md
19
README.md
@@ -25,10 +25,10 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
|
||||
|
||||
- A C++ compiler
|
||||
- On Linux, GCC works fine
|
||||
- On Windows, you need Visual Studio 2015 (the free community edition works fine).
|
||||
- On Windows, you need Visual Studio 2017 (the free community edition works fine).
|
||||
Make sure to select the C++ Programming Language for installation
|
||||
- [ARM Compiler Toolchain](http://first.wpi.edu/FRC/roborio/toolchains/)
|
||||
* Note that for 2017-2018 and beyond, you will need version 5 or greater of GCC
|
||||
- [ARM Compiler Toolchain](https://github.com/wpilibsuite/toolchain-builder/releases)
|
||||
* Note that for 2019 and beyond, you should use version 6 or greater of GCC
|
||||
- Doxygen (Only required if you want to build the C++ documentation)
|
||||
|
||||
## Setup
|
||||
@@ -79,23 +79,18 @@ There are a few tasks other than `build` available. To see them, run the meta-ta
|
||||
|
||||
wpiformat can be executed anywhere in the repository via `py -3 -m wpiformat` on Windows or `python3 -m wpiformat` on other platforms.
|
||||
|
||||
CMake is also supported for building. See [README-CMAKE.md](README-CMAKE.md).
|
||||
|
||||
## Publishing
|
||||
|
||||
If you are building to test with the Eclipse plugins or just want to export the build as a Maven-style dependency, simply run the `publish` task. This task will publish all available packages to ~/releases/maven/development. If you need to publish the project to a different repo, you can specify it with `-Prepo=repo_name`. Valid options are:
|
||||
If you are building to test with other dependencies or just want to export the build as a Maven-style dependency, simply run the `publish` task. This task will publish all available packages to ~/releases/maven/development. If you need to publish the project to a different repo, you can specify it with `-Prepo=repo_name`. Valid options are:
|
||||
|
||||
- development - The default repo.
|
||||
- beta - Publishes to ~/releases/maven/beta.
|
||||
- stable - Publishes to ~/releases/maven/stable.
|
||||
- release - Publishes to ~/releases/maven/release.
|
||||
|
||||
The following maven targets a published by this task:
|
||||
|
||||
- edu.wpi.first.wpilib.cmake:cpp-root:1.0.0 - roboRIO C++
|
||||
- edu.wpi.first.wpilibc.simulation:WPILibCSim:0.1.0 - Simulation C++
|
||||
- edu.wpi.first.wpilibj:wpilibJavaFinal:0.1.0-SNAPSHOT - roboRIO Java
|
||||
- edu.wpi.first.wpilibj:wpilibJavaSim:0.1.0-SNAPSHOT - Simulation Java
|
||||
- edu.wpi.first.wpilibj.simulation:SimDS:0.1.0-SNAPSHOT - The driverstation for controlling simulation.
|
||||
- org.gazebosim:JavaGazebo:0.1.0-SNAPSHOT - Gazebo protocol for Java.
|
||||
The maven artifacts are described in [MavenArtifacts.md](MavenArtifacts.md)
|
||||
|
||||
## Structure and Organization
|
||||
|
||||
|
||||
@@ -31,6 +31,13 @@ sigslot wpiutil/src/main/native/include/wpi/Signal.h
|
||||
wpiutil/src/test/native/cpp/sigslot/
|
||||
tcpsockets wpiutil/src/main/native/cpp/TCP{Stream,Connector,Acceptor}.cpp
|
||||
wpiutil/src/main/native/include/wpi/TCP*.h
|
||||
Optional wpiutil/src/main/native/include/wpi/optional.h
|
||||
wpiutil/src/test/native/cpp/test_optional.cpp
|
||||
Bootstrap wpiutil/src/main/native/resources/bootstrap-*
|
||||
CoreUI wpiutil/src/main/native/resources/coreui-*
|
||||
Feather Icons wpiutil/src/main/native/resources/feather-*
|
||||
jQuery wpiutil/src/main/native/resources/jquery-*
|
||||
popper.js wpiutil/src/main/native/resources/popper-*
|
||||
|
||||
|
||||
==============================================================================
|
||||
@@ -212,3 +219,155 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
==============================================================================
|
||||
Optional License
|
||||
==============================================================================
|
||||
Copyright (C) 2011 - 2017 Andrzej Krzemienski.
|
||||
|
||||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
Bootstrap License
|
||||
==============================================================================
|
||||
Copyright (c) 2011-2018 Twitter, Inc.
|
||||
Copyright (c) 2011-2018 The Bootstrap Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
CoreUI License
|
||||
==============================================================================
|
||||
Copyright (c) 2018 creativeLabs tukasz Holeczek.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
Feather Icons License
|
||||
==============================================================================
|
||||
Copyright (c) 2013-2017 Cole Bemis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
jQuery License
|
||||
==============================================================================
|
||||
Copyright JS Foundation and other contributors, https://js.foundation/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
popper.js License
|
||||
==============================================================================
|
||||
Copyright (c) 2016 Federico Zivolo and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -7,6 +7,8 @@ resources:
|
||||
containers:
|
||||
- container: wpilib2019
|
||||
image: wpilib/roborio-cross-ubuntu:2019-18.04
|
||||
- container: raspbian
|
||||
image: wpilib/raspbian-cross-ubuntu:18.04
|
||||
|
||||
jobs:
|
||||
- job: Linux_Arm
|
||||
@@ -28,6 +30,25 @@ jobs:
|
||||
# checkStyleRunAnalysis: true
|
||||
# pmdRunAnalysis: true
|
||||
|
||||
- job: Linux_Raspbian
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
|
||||
container: raspbian
|
||||
|
||||
steps:
|
||||
- task: Gradle@2
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-PonlyRaspbian'
|
||||
# checkStyleRunAnalysis: true
|
||||
# pmdRunAnalysis: true
|
||||
|
||||
- job: Linux
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
@@ -145,6 +166,9 @@ jobs:
|
||||
sudo tar xvzf build/jdk.tar.gz -C /Library/Java/JavaVirtualMachines/
|
||||
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home/
|
||||
displayName: 'Setup JDK'
|
||||
- script: |
|
||||
rm /Users/vsts/.gradle/init.d/log-gradle-version-plugin.gradle
|
||||
displayName: 'Delete Version init script'
|
||||
- task: Gradle@2
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
|
||||
20
build.gradle
20
build.gradle
@@ -1,12 +1,14 @@
|
||||
plugins {
|
||||
id 'base'
|
||||
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '2.2'
|
||||
id 'edu.wpi.first.NativeUtils' version '1.7.7'
|
||||
id 'edu.wpi.first.GradleJni' version '0.3.0'
|
||||
id 'edu.wpi.first.GradleVsCode' version '0.4.2'
|
||||
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '2.3'
|
||||
id 'edu.wpi.first.NativeUtils' version '2.1.2'
|
||||
id 'edu.wpi.first.GradleJni' version '0.3.1'
|
||||
id 'edu.wpi.first.GradleVsCode' version '0.7.1'
|
||||
id 'idea'
|
||||
id 'com.gradle.build-scan' version '1.15.1'
|
||||
id 'visual-studio'
|
||||
id 'com.gradle.build-scan' version '2.0.2'
|
||||
id 'net.ltgt.errorprone' version '0.6' apply false
|
||||
id 'com.github.johnrengelman.shadow' version '4.0.3' apply false
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -77,6 +79,12 @@ subprojects {
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: 'idea'
|
||||
|
||||
def subproj = it
|
||||
|
||||
plugins.withType(NativeComponentPlugin) {
|
||||
subproj.apply plugin: MultiBuilds
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/java/javastyle.gradle"
|
||||
|
||||
repositories {
|
||||
@@ -92,5 +100,5 @@ subprojects {
|
||||
}
|
||||
|
||||
wrapper {
|
||||
gradleVersion = '4.9'
|
||||
gradleVersion = '5.0'
|
||||
}
|
||||
|
||||
95
buildSrc/src/main/groovy/MultiBuilds.groovy
Normal file
95
buildSrc/src/main/groovy/MultiBuilds.groovy
Normal file
@@ -0,0 +1,95 @@
|
||||
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.file.FileTree;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.language.base.internal.ProjectLayout;
|
||||
import org.gradle.language.base.plugins.ComponentModelBasePlugin;
|
||||
import org.gradle.language.nativeplatform.tasks.AbstractNativeSourceCompileTask;
|
||||
import org.gradle.model.ModelMap;
|
||||
import org.gradle.model.Mutate;
|
||||
import org.gradle.nativeplatform.test.googletest.GoogleTestTestSuiteBinarySpec;
|
||||
import org.gradle.model.RuleSource;
|
||||
import org.gradle.model.Validate;
|
||||
import org.gradle.nativeplatform.NativeExecutableBinarySpec
|
||||
import org.gradle.nativeplatform.NativeBinarySpec;
|
||||
import org.gradle.nativeplatform.NativeComponentSpec;
|
||||
import org.gradle.nativeplatform.NativeLibrarySpec;
|
||||
import org.gradle.nativeplatform.SharedLibraryBinarySpec;
|
||||
import org.gradle.nativeplatform.StaticLibraryBinarySpec;
|
||||
import org.gradle.nativeplatform.platform.internal.NativePlatformInternal;
|
||||
import org.gradle.nativeplatform.toolchain.NativeToolChain;
|
||||
import org.gradle.nativeplatform.toolchain.NativeToolChainRegistry;
|
||||
import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
|
||||
import org.gradle.nativeplatform.toolchain.internal.ToolType;
|
||||
import org.gradle.nativeplatform.toolchain.internal.gcc.AbstractGccCompatibleToolChain;
|
||||
import org.gradle.nativeplatform.toolchain.internal.msvcpp.VisualCppToolChain;
|
||||
import org.gradle.nativeplatform.toolchain.internal.tools.ToolRegistry;
|
||||
import org.gradle.platform.base.BinarySpec;
|
||||
import org.gradle.platform.base.ComponentSpec;
|
||||
import org.gradle.platform.base.ComponentSpecContainer;
|
||||
import org.gradle.platform.base.BinaryContainer;
|
||||
import org.gradle.platform.base.ComponentType;
|
||||
import org.gradle.platform.base.TypeBuilder;
|
||||
import org.gradle.nativeplatform.tasks.ObjectFilesToBinary;
|
||||
import groovy.transform.CompileStatic;
|
||||
import groovy.transform.CompileDynamic
|
||||
import org.gradle.nativeplatform.BuildTypeContainer
|
||||
|
||||
@CompileStatic
|
||||
class MultiBuilds implements Plugin<Project> {
|
||||
@CompileStatic
|
||||
public void apply(Project project) {
|
||||
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
static class Rules extends RuleSource {
|
||||
@Mutate
|
||||
void setupBuildTypes(BuildTypeContainer buildTypes, ProjectLayout projectLayout) {
|
||||
def project = (Project) projectLayout.projectIdentifier
|
||||
if (project.hasProperty('releaseBuild')) {
|
||||
buildTypes.create('debug')
|
||||
} else {
|
||||
buildTypes.create('release')
|
||||
}
|
||||
}
|
||||
|
||||
@CompileDynamic
|
||||
private static void setBuildableFalseDynamically(NativeBinarySpec binary) {
|
||||
binary.buildable = false
|
||||
}
|
||||
|
||||
@Mutate
|
||||
@CompileStatic
|
||||
void disableReleaseGoogleTest(BinaryContainer binaries, ProjectLayout projectLayout) {
|
||||
def project = (Project) projectLayout.projectIdentifier
|
||||
if (project.hasProperty('testRelease')) {
|
||||
return
|
||||
}
|
||||
binaries.withType(GoogleTestTestSuiteBinarySpec) { oSpec ->
|
||||
GoogleTestTestSuiteBinarySpec spec = (GoogleTestTestSuiteBinarySpec) oSpec
|
||||
if (spec.buildType.name == 'release') {
|
||||
Rules.setBuildableFalseDynamically(spec)
|
||||
}
|
||||
}
|
||||
// def crossCompileConfigs = []
|
||||
// for (BuildConfig config : configs) {
|
||||
// if (!BuildConfigRulesBase.isCrossCompile(config)) {
|
||||
// continue
|
||||
// }
|
||||
// crossCompileConfigs << config.architecture
|
||||
// }
|
||||
// if (!crossCompileConfigs.empty) {
|
||||
// binaries.withType(GoogleTestTestSuiteBinarySpec) { oSpec ->
|
||||
// GoogleTestTestSuiteBinarySpec spec = (GoogleTestTestSuiteBinarySpec) oSpec
|
||||
// if (crossCompileConfigs.contains(spec.targetPlatform.architecture.name)) {
|
||||
// setBuildableFalseDynamically(spec)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,8 @@ class SingleNativeBuild implements Plugin<Project> {
|
||||
}
|
||||
def tmpBaseBin = (NativeBinarySpec) oTmpBaseBin
|
||||
if (tmpBaseBin.targetPlatform.operatingSystem.name == binary.targetPlatform.operatingSystem.name &&
|
||||
tmpBaseBin.targetPlatform.architecture.name == binary.targetPlatform.architecture.name) {
|
||||
tmpBaseBin.targetPlatform.architecture.name == binary.targetPlatform.architecture.name &&
|
||||
tmpBaseBin.buildType == binary.buildType) {
|
||||
baseBin = tmpBaseBin
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ endif()
|
||||
file(GLOB_RECURSE
|
||||
cameraserver_native_src src/main/native/cpp/*.cpp)
|
||||
add_library(cameraserver ${cameraserver_native_src})
|
||||
set_target_properties(cameraserver PROPERTIES DEBUG_POSTFIX "d")
|
||||
target_include_directories(cameraserver PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
|
||||
$<INSTALL_INTERFACE:${include_dest}/cameraserver>)
|
||||
@@ -50,3 +51,7 @@ endif()
|
||||
|
||||
install(FILES cameraserver-config.cmake DESTINATION ${cameraserver_config_dir})
|
||||
install(EXPORT cameraserver DESTINATION ${cameraserver_config_dir})
|
||||
|
||||
file(GLOB multiCameraServer_src multiCameraServer/src/main/native/cpp/*.cpp)
|
||||
add_executable(multiCameraServer ${multiCameraServer_src})
|
||||
target_link_libraries(multiCameraServer cameraserver)
|
||||
|
||||
62
cameraserver/multiCameraServer/build.gradle
Normal file
62
cameraserver/multiCameraServer/build.gradle
Normal file
@@ -0,0 +1,62 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id 'cpp'
|
||||
id 'visual-studio'
|
||||
}
|
||||
|
||||
apply plugin: 'edu.wpi.first.NativeUtils'
|
||||
|
||||
apply from: "${rootDir}/shared/config.gradle"
|
||||
|
||||
ext {
|
||||
staticCvConfigs = [multiCameraServerCpp: []]
|
||||
useJava = true
|
||||
useCpp = true
|
||||
skipDev = true
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/opencv.gradle"
|
||||
|
||||
mainClassName = 'Main'
|
||||
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.google.code.gson:gson:2.8.5'
|
||||
|
||||
compile project(':wpiutil')
|
||||
compile project(':ntcore')
|
||||
compile project(':cscore')
|
||||
compile project(':cameraserver')
|
||||
}
|
||||
|
||||
model {
|
||||
components {
|
||||
multiCameraServerCpp(NativeExecutableSpec) {
|
||||
targetBuildTypes 'release'
|
||||
sources {
|
||||
cpp {
|
||||
source {
|
||||
srcDirs = ['src/main/native/cpp']
|
||||
includes = ['**/*.cpp']
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs = ['src/main/native/include']
|
||||
includes = ['**/*.h']
|
||||
}
|
||||
}
|
||||
}
|
||||
binaries.all { binary ->
|
||||
lib project: ':cameraserver', library: 'cameraserver', linkage: 'static'
|
||||
lib project: ':ntcore', library: 'ntcore', linkage: 'static'
|
||||
lib project: ':cscore', library: 'cscore', linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
211
cameraserver/multiCameraServer/src/main/java/Main.java
Normal file
211
cameraserver/multiCameraServer/src/main/java/Main.java
Normal file
@@ -0,0 +1,211 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import edu.wpi.cscore.VideoSource;
|
||||
import edu.wpi.first.cameraserver.CameraServer;
|
||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
||||
|
||||
/*
|
||||
JSON format:
|
||||
{
|
||||
"team": <team number>,
|
||||
"ntmode": <"client" or "server", "client" if unspecified>
|
||||
"cameras": [
|
||||
{
|
||||
"name": <camera name>
|
||||
"path": <path, e.g. "/dev/video0">
|
||||
"pixel format": <"MJPEG", "YUYV", etc> // optional
|
||||
"width": <video mode width> // optional
|
||||
"height": <video mode height> // optional
|
||||
"fps": <video mode fps> // optional
|
||||
"brightness": <percentage brightness> // optional
|
||||
"white balance": <"auto", "hold", value> // optional
|
||||
"exposure": <"auto", "hold", value> // optional
|
||||
"properties": [ // optional
|
||||
{
|
||||
"name": <property name>
|
||||
"value": <property value>
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
|
||||
public final class Main {
|
||||
private static String configFile = "/boot/frc.json";
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public static class CameraConfig {
|
||||
public String name;
|
||||
public String path;
|
||||
public JsonObject config;
|
||||
}
|
||||
|
||||
public static int team;
|
||||
public static boolean server;
|
||||
public static List<CameraConfig> cameras = new ArrayList<>();
|
||||
|
||||
private Main() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Report parse error.
|
||||
*/
|
||||
public static void parseError(String str) {
|
||||
System.err.println("config error in '" + configFile + "': " + str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read single camera configuration.
|
||||
*/
|
||||
public static boolean readCameraConfig(JsonObject config) {
|
||||
CameraConfig cam = new CameraConfig();
|
||||
|
||||
// name
|
||||
JsonElement nameElement = config.get("name");
|
||||
if (nameElement == null) {
|
||||
parseError("could not read camera name");
|
||||
return false;
|
||||
}
|
||||
cam.name = nameElement.getAsString();
|
||||
|
||||
// path
|
||||
JsonElement pathElement = config.get("path");
|
||||
if (pathElement == null) {
|
||||
parseError("camera '" + cam.name + "': could not read path");
|
||||
return false;
|
||||
}
|
||||
cam.path = pathElement.getAsString();
|
||||
|
||||
cam.config = config;
|
||||
|
||||
cameras.add(cam);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read configuration file.
|
||||
*/
|
||||
@SuppressWarnings("PMD.CyclomaticComplexity")
|
||||
public static boolean readConfig() {
|
||||
// parse file
|
||||
JsonElement top;
|
||||
try {
|
||||
top = new JsonParser().parse(Files.newBufferedReader(Paths.get(configFile)));
|
||||
} catch (IOException ex) {
|
||||
System.err.println("could not open '" + configFile + "': " + ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
// top level must be an object
|
||||
if (!top.isJsonObject()) {
|
||||
parseError("must be JSON object");
|
||||
return false;
|
||||
}
|
||||
JsonObject obj = top.getAsJsonObject();
|
||||
|
||||
// team number
|
||||
JsonElement teamElement = obj.get("team");
|
||||
if (teamElement == null) {
|
||||
parseError("could not read team number");
|
||||
return false;
|
||||
}
|
||||
team = teamElement.getAsInt();
|
||||
|
||||
// ntmode (optional)
|
||||
if (obj.has("ntmode")) {
|
||||
String str = obj.get("ntmode").getAsString();
|
||||
if ("client".equalsIgnoreCase(str)) {
|
||||
server = false;
|
||||
} else if ("server".equalsIgnoreCase(str)) {
|
||||
server = true;
|
||||
} else {
|
||||
parseError("could not understand ntmode value '" + str + "'");
|
||||
}
|
||||
}
|
||||
|
||||
// cameras
|
||||
JsonElement camerasElement = obj.get("cameras");
|
||||
if (camerasElement == null) {
|
||||
parseError("could not read cameras");
|
||||
return false;
|
||||
}
|
||||
JsonArray cameras = camerasElement.getAsJsonArray();
|
||||
for (JsonElement camera : cameras) {
|
||||
if (!readCameraConfig(camera.getAsJsonObject())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start running the camera.
|
||||
*/
|
||||
public static void startCamera(CameraConfig config) {
|
||||
System.out.println("Starting camera '" + config.name + "' on " + config.path);
|
||||
VideoSource camera = CameraServer.getInstance().startAutomaticCapture(
|
||||
config.name, config.path);
|
||||
|
||||
Gson gson = new GsonBuilder().create();
|
||||
|
||||
camera.setConfigJson(gson.toJson(config.config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Main.
|
||||
*/
|
||||
public static void main(String... args) {
|
||||
if (args.length > 0) {
|
||||
configFile = args[0];
|
||||
}
|
||||
|
||||
// read configuration
|
||||
if (!readConfig()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// start NetworkTables
|
||||
NetworkTableInstance ntinst = NetworkTableInstance.getDefault();
|
||||
if (server) {
|
||||
System.out.println("Setting up NetworkTables server");
|
||||
ntinst.startServer();
|
||||
} else {
|
||||
System.out.println("Setting up NetworkTables client for team " + team);
|
||||
ntinst.startClientTeam(team);
|
||||
}
|
||||
|
||||
// start cameras
|
||||
for (CameraConfig camera : cameras) {
|
||||
startCamera(camera);
|
||||
}
|
||||
|
||||
// loop forever
|
||||
for (;;) {
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
} catch (InterruptedException ex) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
190
cameraserver/multiCameraServer/src/main/native/cpp/main.cpp
Normal file
190
cameraserver/multiCameraServer/src/main/native/cpp/main.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "cameraserver/CameraServer.h"
|
||||
|
||||
/*
|
||||
JSON format:
|
||||
{
|
||||
"team": <team number>,
|
||||
"ntmode": <"client" or "server", "client" if unspecified>
|
||||
"cameras": [
|
||||
{
|
||||
"name": <camera name>
|
||||
"path": <path, e.g. "/dev/video0">
|
||||
"pixel format": <"MJPEG", "YUYV", etc> // optional
|
||||
"width": <video mode width> // optional
|
||||
"height": <video mode height> // optional
|
||||
"fps": <video mode fps> // optional
|
||||
"brightness": <percentage brightness> // optional
|
||||
"white balance": <"auto", "hold", value> // optional
|
||||
"exposure": <"auto", "hold", value> // optional
|
||||
"properties": [ // optional
|
||||
{
|
||||
"name": <property name>
|
||||
"value": <property value>
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
|
||||
#ifdef __RASPBIAN__
|
||||
static const char* configFile = "/boot/frc.json";
|
||||
#else
|
||||
static const char* configFile = "frc.json";
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
unsigned int team;
|
||||
bool server = false;
|
||||
|
||||
struct CameraConfig {
|
||||
std::string name;
|
||||
std::string path;
|
||||
wpi::json config;
|
||||
};
|
||||
|
||||
std::vector<CameraConfig> cameras;
|
||||
|
||||
wpi::raw_ostream& ParseError() {
|
||||
return wpi::errs() << "config error in '" << configFile << "': ";
|
||||
}
|
||||
|
||||
bool ReadCameraConfig(const wpi::json& config) {
|
||||
CameraConfig c;
|
||||
|
||||
// name
|
||||
try {
|
||||
c.name = config.at("name").get<std::string>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
ParseError() << "could not read camera name: " << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// path
|
||||
try {
|
||||
c.path = config.at("path").get<std::string>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
ParseError() << "camera '" << c.name
|
||||
<< "': could not read path: " << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
c.config = config;
|
||||
|
||||
cameras.emplace_back(std::move(c));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadConfig() {
|
||||
// open config file
|
||||
std::error_code ec;
|
||||
wpi::raw_fd_istream is(configFile, ec);
|
||||
if (ec) {
|
||||
wpi::errs() << "could not open '" << configFile << "': " << ec.message()
|
||||
<< '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// parse file
|
||||
wpi::json j;
|
||||
try {
|
||||
j = wpi::json::parse(is);
|
||||
} catch (const wpi::json::parse_error& e) {
|
||||
ParseError() << "byte " << e.byte << ": " << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// top level must be an object
|
||||
if (!j.is_object()) {
|
||||
ParseError() << "must be JSON object\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// team number
|
||||
try {
|
||||
team = j.at("team").get<unsigned int>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
ParseError() << "could not read team number: " << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// ntmode (optional)
|
||||
if (j.count("ntmode") != 0) {
|
||||
try {
|
||||
auto str = j.at("ntmode").get<std::string>();
|
||||
wpi::StringRef s(str);
|
||||
if (s.equals_lower("client")) {
|
||||
server = false;
|
||||
} else if (s.equals_lower("server")) {
|
||||
server = true;
|
||||
} else {
|
||||
ParseError() << "could not understand ntmode value '" << str << "'\n";
|
||||
}
|
||||
} catch (const wpi::json::exception& e) {
|
||||
ParseError() << "could not read ntmode: " << e.what() << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// cameras
|
||||
try {
|
||||
for (auto&& camera : j.at("cameras")) {
|
||||
if (!ReadCameraConfig(camera)) return false;
|
||||
}
|
||||
} catch (const wpi::json::exception& e) {
|
||||
ParseError() << "could not read cameras: " << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void StartCamera(const CameraConfig& config) {
|
||||
wpi::outs() << "Starting camera '" << config.name << "' on " << config.path
|
||||
<< '\n';
|
||||
auto camera = frc::CameraServer::GetInstance()->StartAutomaticCapture(
|
||||
config.name, config.path);
|
||||
|
||||
camera.SetConfigJson(config.config);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc >= 2) configFile = argv[1];
|
||||
|
||||
// read configuration
|
||||
if (!ReadConfig()) return EXIT_FAILURE;
|
||||
|
||||
// start NetworkTables
|
||||
auto ntinst = nt::NetworkTableInstance::GetDefault();
|
||||
if (server) {
|
||||
wpi::outs() << "Setting up NetworkTables server\n";
|
||||
ntinst.StartServer();
|
||||
} else {
|
||||
wpi::outs() << "Setting up NetworkTables client for team " << team << '\n';
|
||||
ntinst.StartClientTeam(team);
|
||||
}
|
||||
|
||||
// start cameras
|
||||
for (auto&& camera : cameras) StartCamera(camera);
|
||||
|
||||
// loop forever
|
||||
for (;;) std::this_thread::sleep_for(std::chrono::seconds(10));
|
||||
}
|
||||
@@ -65,6 +65,8 @@ public final class CameraServer {
|
||||
private final Map<String, VideoSource> m_sources;
|
||||
private final Map<String, VideoSink> m_sinks;
|
||||
private final Map<Integer, NetworkTable> m_tables; // indexed by source handle
|
||||
// source handle indexed by sink handle
|
||||
private final Map<Integer, Integer> m_fixedSources;
|
||||
private final NetworkTable m_publishTable;
|
||||
private final VideoListener m_videoListener; //NOPMD
|
||||
private final int m_tableListener; //NOPMD
|
||||
@@ -85,9 +87,7 @@ public final class CameraServer {
|
||||
}
|
||||
}
|
||||
case kCv:
|
||||
// FIXME: Should be "cv:", but LabVIEW dashboard requires "usb:".
|
||||
// https://github.com/wpilibsuite/allwpilib/issues/407
|
||||
return "usb:";
|
||||
return "cv:";
|
||||
default:
|
||||
return "unknown:";
|
||||
}
|
||||
@@ -125,7 +125,7 @@ public final class CameraServer {
|
||||
}
|
||||
}
|
||||
|
||||
return values.toArray(String[]::new);
|
||||
return values.toArray(new String[0]);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"JavadocMethod", "PMD.AvoidUsingHardCodedIP"})
|
||||
@@ -159,14 +159,20 @@ public final class CameraServer {
|
||||
return values;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"JavadocMethod", "PMD.AvoidUsingHardCodedIP"})
|
||||
@SuppressWarnings({"JavadocMethod", "PMD.AvoidUsingHardCodedIP", "PMD.CyclomaticComplexity"})
|
||||
private synchronized void updateStreamValues() {
|
||||
// Over all the sinks...
|
||||
for (VideoSink i : m_sinks.values()) {
|
||||
int sink = i.getHandle();
|
||||
|
||||
// Get the source's subtable (if none exists, we're done)
|
||||
int source = CameraServerJNI.getSinkSource(sink);
|
||||
int source;
|
||||
Integer fixedSource = m_fixedSources.get(sink);
|
||||
if (fixedSource != null) {
|
||||
source = fixedSource;
|
||||
} else {
|
||||
source = CameraServerJNI.getSinkSource(sink);
|
||||
}
|
||||
if (source == 0) {
|
||||
continue;
|
||||
}
|
||||
@@ -297,6 +303,7 @@ public final class CameraServer {
|
||||
m_defaultUsbDevice = new AtomicInteger();
|
||||
m_sources = new Hashtable<>();
|
||||
m_sinks = new Hashtable<>();
|
||||
m_fixedSources = new Hashtable<>();
|
||||
m_tables = new Hashtable<>();
|
||||
m_publishTable = NetworkTableInstance.getDefault().getTable(kPublishName);
|
||||
m_nextPort = kBasePort;
|
||||
@@ -539,10 +546,11 @@ public final class CameraServer {
|
||||
*
|
||||
* @param camera Camera
|
||||
*/
|
||||
public void startAutomaticCapture(VideoSource camera) {
|
||||
public MjpegServer startAutomaticCapture(VideoSource camera) {
|
||||
addCamera(camera);
|
||||
VideoSink server = addServer("serve_" + camera.getName());
|
||||
MjpegServer server = addServer("serve_" + camera.getName());
|
||||
server.setSource(camera);
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -597,6 +605,21 @@ public final class CameraServer {
|
||||
return camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a virtual camera for switching between two streams. Unlike the
|
||||
* other addCamera methods, this returns a VideoSink rather than a
|
||||
* VideoSource. Calling setSource() on the returned object can be used
|
||||
* to switch the actual source of the stream.
|
||||
*/
|
||||
public MjpegServer addSwitchedCamera(String name) {
|
||||
// create a dummy CvSource
|
||||
CvSource source = new CvSource(name, VideoMode.PixelFormat.kMJPEG, 160, 120, 30);
|
||||
MjpegServer server = startAutomaticCapture(source);
|
||||
m_fixedSources.put(server.getHandle(), source.getHandle());
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OpenCV access to the primary camera feed. This allows you to
|
||||
* get images from the camera for image processing on the roboRIO.
|
||||
|
||||
@@ -33,10 +33,11 @@ struct CameraServer::Impl {
|
||||
void UpdateStreamValues();
|
||||
|
||||
wpi::mutex m_mutex;
|
||||
std::atomic<int> m_defaultUsbDevice;
|
||||
std::atomic<int> m_defaultUsbDevice{0};
|
||||
std::string m_primarySourceName;
|
||||
wpi::StringMap<cs::VideoSource> m_sources;
|
||||
wpi::StringMap<cs::VideoSink> m_sinks;
|
||||
wpi::DenseMap<CS_Sink, CS_Source> m_fixedSources;
|
||||
wpi::DenseMap<CS_Source, std::shared_ptr<nt::NetworkTable>> m_tables;
|
||||
std::shared_ptr<nt::NetworkTable> m_publishTable;
|
||||
cs::VideoListener m_videoListener;
|
||||
@@ -55,7 +56,6 @@ static wpi::StringRef MakeSourceValue(CS_Source source,
|
||||
CS_Status status = 0;
|
||||
buf.clear();
|
||||
switch (cs::GetSourceKind(source, &status)) {
|
||||
#ifdef __linux__
|
||||
case cs::VideoSource::kUsb: {
|
||||
wpi::StringRef prefix{"usb:"};
|
||||
buf.append(prefix.begin(), prefix.end());
|
||||
@@ -63,7 +63,6 @@ static wpi::StringRef MakeSourceValue(CS_Source source,
|
||||
buf.append(path.begin(), path.end());
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case cs::VideoSource::kHttp: {
|
||||
wpi::StringRef prefix{"ip:"};
|
||||
buf.append(prefix.begin(), prefix.end());
|
||||
@@ -72,9 +71,7 @@ static wpi::StringRef MakeSourceValue(CS_Source source,
|
||||
break;
|
||||
}
|
||||
case cs::VideoSource::kCv:
|
||||
// FIXME: Should be "cv:", but LabVIEW dashboard requires "usb:".
|
||||
// https://github.com/wpilibsuite/allwpilib/issues/407
|
||||
return "usb:";
|
||||
return "cv:";
|
||||
default:
|
||||
return "unknown:";
|
||||
}
|
||||
@@ -160,7 +157,8 @@ void CameraServer::Impl::UpdateStreamValues() {
|
||||
CS_Sink sink = i.second.GetHandle();
|
||||
|
||||
// Get the source's subtable (if none exists, we're done)
|
||||
CS_Source source = cs::GetSinkSource(sink, &status);
|
||||
CS_Source source = m_fixedSources.lookup(sink);
|
||||
if (source == 0) source = cs::GetSinkSource(sink, &status);
|
||||
if (source == 0) continue;
|
||||
auto table = m_tables.lookup(source);
|
||||
if (table) {
|
||||
@@ -457,7 +455,6 @@ CameraServer::CameraServer() : m_impl(new Impl) {}
|
||||
|
||||
CameraServer::~CameraServer() {}
|
||||
|
||||
#ifdef __linux__
|
||||
cs::UsbCamera CameraServer::StartAutomaticCapture() {
|
||||
cs::UsbCamera camera = StartAutomaticCapture(m_impl->m_defaultUsbDevice++);
|
||||
auto csShared = GetCameraServerShared();
|
||||
@@ -490,7 +487,6 @@ cs::UsbCamera CameraServer::StartAutomaticCapture(const wpi::Twine& name,
|
||||
csShared->ReportUsbCamera(camera.GetHandle());
|
||||
return camera;
|
||||
}
|
||||
#endif
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(const wpi::Twine& host) {
|
||||
return AddAxisCamera("Axis Camera", host);
|
||||
@@ -544,10 +540,21 @@ cs::AxisCamera CameraServer::AddAxisCamera(const wpi::Twine& name,
|
||||
return camera;
|
||||
}
|
||||
|
||||
void CameraServer::StartAutomaticCapture(const cs::VideoSource& camera) {
|
||||
cs::MjpegServer CameraServer::AddSwitchedCamera(const wpi::Twine& name) {
|
||||
// create a dummy CvSource
|
||||
cs::CvSource source{name, cs::VideoMode::PixelFormat::kMJPEG, 160, 120, 30};
|
||||
cs::MjpegServer server = StartAutomaticCapture(source);
|
||||
m_impl->m_fixedSources[server.GetHandle()] = source.GetHandle();
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
cs::MjpegServer CameraServer::StartAutomaticCapture(
|
||||
const cs::VideoSource& camera) {
|
||||
AddCamera(camera);
|
||||
auto server = AddServer(wpi::Twine("serve_") + camera.GetName());
|
||||
server.SetSource(camera);
|
||||
return server;
|
||||
}
|
||||
|
||||
cs::CvSink CameraServer::GetVideo() {
|
||||
|
||||
@@ -36,8 +36,6 @@ class CameraServer {
|
||||
*/
|
||||
static CameraServer* GetInstance();
|
||||
|
||||
#ifdef __linux__
|
||||
// USBCamera does not work on anything except Linux.
|
||||
/**
|
||||
* Start automatically capturing images to send to the dashboard.
|
||||
*
|
||||
@@ -77,7 +75,6 @@ class CameraServer {
|
||||
*/
|
||||
cs::UsbCamera StartAutomaticCapture(const wpi::Twine& name,
|
||||
const wpi::Twine& path);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Start automatically capturing images to send to the dashboard from
|
||||
@@ -85,7 +82,7 @@ class CameraServer {
|
||||
*
|
||||
* @param camera Camera
|
||||
*/
|
||||
void StartAutomaticCapture(const cs::VideoSource& camera);
|
||||
cs::MjpegServer StartAutomaticCapture(const cs::VideoSource& camera);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
@@ -176,6 +173,14 @@ class CameraServer {
|
||||
cs::AxisCamera AddAxisCamera(const wpi::Twine& name,
|
||||
std::initializer_list<T> hosts);
|
||||
|
||||
/**
|
||||
* Adds a virtual camera for switching between two streams. Unlike the
|
||||
* other addCamera methods, this returns a VideoSink rather than a
|
||||
* VideoSource. Calling SetSource() on the returned object can be used
|
||||
* to switch the actual source of the stream.
|
||||
*/
|
||||
cs::MjpegServer AddSwitchedCamera(const wpi::Twine& name);
|
||||
|
||||
/**
|
||||
* Get OpenCV access to the primary camera feed. This allows you to
|
||||
* get images from the camera for image processing on the roboRIO.
|
||||
|
||||
25
cmake/modules/GenResources.cmake
Normal file
25
cmake/modules/GenResources.cmake
Normal file
@@ -0,0 +1,25 @@
|
||||
MACRO(GENERATE_RESOURCES inputDir outputDir prefix namespace outputFiles)
|
||||
FILE(GLOB inputFiles ${inputDir}/*)
|
||||
SET(${outputFiles})
|
||||
FOREACH(input ${inputFiles})
|
||||
GET_FILENAME_COMPONENT(inputBase ${input} NAME)
|
||||
IF("${inputBase}" MATCHES "^\\.")
|
||||
CONTINUE()
|
||||
ENDIF()
|
||||
SET(output "${outputDir}/${inputBase}.cpp")
|
||||
LIST(APPEND ${outputFiles} "${output}")
|
||||
|
||||
ADD_CUSTOM_COMMAND(
|
||||
OUTPUT ${output}
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
"-Dinput=${input}"
|
||||
"-Doutput=${output}"
|
||||
"-Dprefix=${prefix}"
|
||||
"-Dnamespace=${namespace}"
|
||||
-P "${CMAKE_SOURCE_DIR}/cmake/scripts/GenResource.cmake"
|
||||
MAIN_DEPENDENCY ${input}
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/cmake/scripts/GenResource.cmake
|
||||
VERBATIM
|
||||
)
|
||||
ENDFOREACH()
|
||||
ENDMACRO()
|
||||
23
cmake/scripts/GenResource.cmake
Normal file
23
cmake/scripts/GenResource.cmake
Normal file
@@ -0,0 +1,23 @@
|
||||
# Parameters: input output prefix namespace
|
||||
FILE(READ ${input} fileHex HEX)
|
||||
STRING(LENGTH "${fileHex}" fileHexSize)
|
||||
MATH(EXPR fileSize "${fileHexSize} / 2")
|
||||
|
||||
GET_FILENAME_COMPONENT(inputBase ${input} NAME)
|
||||
STRING(REGEX REPLACE "[^a-zA-Z0-9]" "_" funcName "${inputBase}")
|
||||
SET(funcName "GetResource_${funcName}")
|
||||
|
||||
FILE(WRITE "${output}" "#include <stddef.h>\n#include <wpi/StringRef.h>\nextern \"C\" {\nstatic const unsigned char contents[] = {")
|
||||
|
||||
STRING(REGEX MATCHALL ".." outputData "${fileHex}")
|
||||
STRING(REGEX REPLACE ";" ", 0x" outputData "${outputData}")
|
||||
FILE(APPEND "${output}" " 0x${outputData} };\n")
|
||||
FILE(APPEND "${output}" "const unsigned char* ${prefix}${funcName}(size_t* len) {\n *len = ${fileSize};\n return contents;\n}\n}\n")
|
||||
|
||||
IF(NOT namespace STREQUAL "")
|
||||
FILE(APPEND "${output}" "namespace ${namespace} {\n")
|
||||
ENDIF()
|
||||
FILE(APPEND "${output}" "wpi::StringRef ${funcName}() {\n return wpi::StringRef(reinterpret_cast<const char*>(contents), ${fileSize});\n}\n")
|
||||
IF(NOT namespace STREQUAL "")
|
||||
FILE(APPEND "${output}" "}\n")
|
||||
ENDIF()
|
||||
@@ -19,6 +19,9 @@ licenseUpdateExclude {
|
||||
includeGuardRoots {
|
||||
cscore/src/main/native/cpp/
|
||||
cscore/src/main/native/include/
|
||||
cscore/src/main/native/linux/
|
||||
cscore/src/main/native/osx/
|
||||
cscore/src/main/native/windows/
|
||||
cscore/src/main/test/native/cpp/
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,56 @@ include(SubDirList)
|
||||
|
||||
find_package( OpenCV REQUIRED )
|
||||
|
||||
file(GLOB
|
||||
cscore_native_src src/main/native/cpp/*.cpp)
|
||||
file(GLOB cscore_linux_src src/main/native/linux/*.cpp)
|
||||
file(GLOB cscore_osx_src src/main/native/osx/*.cpp)
|
||||
file(GLOB cscore_windows_src src/main/native/windows/*.cpp)
|
||||
|
||||
add_library(cscore ${cscore_native_src})
|
||||
set_target_properties(cscore PROPERTIES DEBUG_POSTFIX "d")
|
||||
|
||||
if(NOT MSVC)
|
||||
if (APPLE)
|
||||
target_sources(cscore PRIVATE ${cscore_osx_src})
|
||||
else()
|
||||
target_sources(cscore PRIVATE ${cscore_linux_src})
|
||||
endif()
|
||||
else()
|
||||
target_sources(cscore PRIVATE ${cscore_windows_src})
|
||||
target_compile_options(cscore PUBLIC -DNOMINMAX)
|
||||
target_compile_options(cscore PRIVATE -D_CRT_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
|
||||
target_include_directories(cscore PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
|
||||
$<INSTALL_INTERFACE:${include_dest}/cscore>)
|
||||
target_include_directories(cscore PRIVATE src/main/native/cpp)
|
||||
target_link_libraries(cscore PUBLIC wpiutil ${OpenCV_LIBS})
|
||||
|
||||
set_property(TARGET cscore PROPERTY FOLDER "libraries")
|
||||
|
||||
install(TARGETS cscore EXPORT cscore DESTINATION "${main_lib_dest}")
|
||||
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/cscore")
|
||||
|
||||
if (MSVC)
|
||||
set (cscore_config_dir ${wpilib_dest})
|
||||
else()
|
||||
set (cscore_config_dir share/cscore)
|
||||
endif()
|
||||
|
||||
install(FILES cscore-config.cmake DESTINATION ${cscore_config_dir})
|
||||
install(EXPORT cscore DESTINATION ${cscore_config_dir})
|
||||
|
||||
SUBDIR_LIST(cscore_examples "${CMAKE_CURRENT_SOURCE_DIR}/examples")
|
||||
foreach(example ${cscore_examples})
|
||||
file(GLOB cscore_example_src examples/${example}/*.cpp)
|
||||
if(cscore_example_src)
|
||||
add_executable(cscore_${example} ${cscore_example_src})
|
||||
target_link_libraries(cscore_${example} cscore)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Java bindings
|
||||
if (NOT WITHOUT_JAVA)
|
||||
find_package(Java REQUIRED)
|
||||
@@ -50,49 +100,23 @@ if (NOT WITHOUT_JAVA)
|
||||
|
||||
set_property(TARGET cscore_jar PROPERTY FOLDER "java")
|
||||
|
||||
endif()
|
||||
add_library(cscorejni ${cscore_jni_src})
|
||||
target_link_libraries(cscorejni PUBLIC cscore wpiutil ${OpenCV_LIBS})
|
||||
|
||||
file(GLOB
|
||||
cscore_native_src src/main/native/cpp/*.cpp)
|
||||
add_library(cscore ${cscore_native_src} ${cscore_jni_src})
|
||||
target_include_directories(cscore PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
|
||||
$<INSTALL_INTERFACE:${include_dest}/cscore>)
|
||||
target_link_libraries(cscore PUBLIC wpiutil ${OpenCV_LIBS})
|
||||
set_property(TARGET cscorejni PROPERTY FOLDER "libraries")
|
||||
|
||||
set_property(TARGET cscore PROPERTY FOLDER "libraries")
|
||||
|
||||
if (NOT WITHOUT_JAVA)
|
||||
if(${CMAKE_VERSION} VERSION_LESS "3.11.0")
|
||||
target_include_directories(cscore PRIVATE ${JNI_INCLUDE_DIRS})
|
||||
target_include_directories(cscore PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/jniheaders")
|
||||
target_include_directories(cscorejni PRIVATE ${JNI_INCLUDE_DIRS})
|
||||
target_include_directories(cscorejni PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/jniheaders")
|
||||
else()
|
||||
target_link_libraries(cscore PRIVATE cscore_jni_headers)
|
||||
target_link_libraries(cscorejni PRIVATE cscore_jni_headers)
|
||||
endif()
|
||||
add_dependencies(cscore cscore_jar)
|
||||
endif()
|
||||
add_dependencies(cscorejni cscore_jar)
|
||||
|
||||
install(TARGETS cscore EXPORT cscore DESTINATION "${main_lib_dest}")
|
||||
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/cscore")
|
||||
|
||||
if (NOT WITHOUT_JAVA AND MSVC)
|
||||
install(TARGETS cscore RUNTIME DESTINATION "${jni_lib_dest}" COMPONENT Runtime)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
set (cscore_config_dir ${wpilib_dest})
|
||||
else()
|
||||
set (cscore_config_dir share/cscore)
|
||||
endif()
|
||||
|
||||
install(FILES cscore-config.cmake DESTINATION ${cscore_config_dir})
|
||||
install(EXPORT cscore DESTINATION ${cscore_config_dir})
|
||||
|
||||
SUBDIR_LIST(cscore_examples "${CMAKE_CURRENT_SOURCE_DIR}/examples")
|
||||
foreach(example ${cscore_examples})
|
||||
file(GLOB cscore_example_src examples/${example}/*.cpp)
|
||||
if(cscore_example_src)
|
||||
add_executable(cscore_${example} ${cscore_example_src})
|
||||
target_link_libraries(cscore_${example} cscore)
|
||||
if (MSVC)
|
||||
install(TARGETS cscorejni RUNTIME DESTINATION "${jni_lib_dest}" COMPONENT Runtime)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
install(TARGETS cscorejni EXPORT cscorejni DESTINATION "${main_lib_dest}")
|
||||
|
||||
endif()
|
||||
|
||||
@@ -29,6 +29,43 @@ ext {
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include'
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
cscoreMacCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/osx'
|
||||
include '**/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include', 'src/main/native/cpp'
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (it.targetPlatform.operatingSystem.name == 'linux') {
|
||||
it.sources {
|
||||
cscoreLinuxCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/linux'
|
||||
include '**/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include', 'src/main/native/cpp'
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (it.targetPlatform.operatingSystem.name == 'windows') {
|
||||
it.sources {
|
||||
cscoreWindowsCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/windows'
|
||||
include '**/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include', 'src/main/native/cpp'
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,17 +75,15 @@ ext {
|
||||
|
||||
def examplesMap = [:];
|
||||
|
||||
if (OperatingSystem.current().isLinux()) {
|
||||
File examplesTree = file("$projectDir/examples")
|
||||
examplesTree.list(new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File current, String name) {
|
||||
return new File(current, name).isDirectory();
|
||||
}
|
||||
}).each {
|
||||
sharedCvConfigs.put(it, [])
|
||||
examplesMap.put(it, [])
|
||||
File examplesTree = file("$projectDir/examples")
|
||||
examplesTree.list(new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File current, String name) {
|
||||
return new File(current, name).isDirectory();
|
||||
}
|
||||
}).each {
|
||||
sharedCvConfigs.put(it, [])
|
||||
examplesMap.put(it, [])
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/opencv.gradle"
|
||||
@@ -63,17 +98,17 @@ model {
|
||||
x86ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
|
||||
'_CT??_R0?AVbad_cast',
|
||||
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
|
||||
'_TI5?AVfailure']
|
||||
'_TI5?AVfailure', '==']
|
||||
x64ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
|
||||
'_CT??_R0?AVbad_cast',
|
||||
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
|
||||
'_TI5?AVfailure']
|
||||
'_TI5?AVfailure', '==']
|
||||
}
|
||||
cscoreJNI(ExportsConfig) {
|
||||
x86SymbolFilter = { symbols ->
|
||||
def retList = []
|
||||
symbols.each { symbol ->
|
||||
if (symbol.startsWith('CS_') || symbol.startsWith('Java_') || symbol.startsWith('JNI_')) {
|
||||
if (symbol.startsWith('CS_')) {
|
||||
retList << symbol
|
||||
}
|
||||
}
|
||||
@@ -82,7 +117,7 @@ model {
|
||||
x64SymbolFilter = { symbols ->
|
||||
def retList = []
|
||||
symbols.each { symbol ->
|
||||
if (symbol.startsWith('CS_') || symbol.startsWith('Java_') || symbol.startsWith('JNI_')) {
|
||||
if (symbol.startsWith('CS_')) {
|
||||
retList << symbol
|
||||
}
|
||||
}
|
||||
@@ -93,6 +128,7 @@ model {
|
||||
components {
|
||||
examplesMap.each { key, value ->
|
||||
"${key}"(NativeExecutableSpec) {
|
||||
targetBuildTypes 'debug'
|
||||
binaries.all {
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
lib library: 'cscore', linkage: 'shared'
|
||||
|
||||
@@ -17,6 +17,12 @@ int main() {
|
||||
for (const auto& caminfo : cs::EnumerateUsbCameras(&status)) {
|
||||
wpi::outs() << caminfo.dev << ": " << caminfo.path << " (" << caminfo.name
|
||||
<< ")\n";
|
||||
if (!caminfo.otherPaths.empty()) {
|
||||
wpi::outs() << "Other device paths:\n";
|
||||
for (auto&& path : caminfo.otherPaths)
|
||||
wpi::outs() << " " << path << '\n';
|
||||
}
|
||||
|
||||
cs::UsbCamera camera{"usbcam", caminfo.dev};
|
||||
|
||||
wpi::outs() << "Properties:\n";
|
||||
|
||||
@@ -24,7 +24,7 @@ public class CameraServerJNI {
|
||||
static {
|
||||
if (!libraryLoaded) {
|
||||
try {
|
||||
loader = new RuntimeLoader<>("cscore", RuntimeLoader.getDefaultExtractionRoot(), CameraServerJNI.class);
|
||||
loader = new RuntimeLoader<>("cscorejni", RuntimeLoader.getDefaultExtractionRoot(), CameraServerJNI.class);
|
||||
loader.loadLibrary();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
@@ -89,6 +89,8 @@ public class CameraServerJNI {
|
||||
public static native boolean setSourcePixelFormat(int source, int pixelFormat);
|
||||
public static native boolean setSourceResolution(int source, int width, int height);
|
||||
public static native boolean setSourceFPS(int source, int fps);
|
||||
public static native boolean setSourceConfigJson(int source, String config);
|
||||
public static native String getSourceConfigJson(int source);
|
||||
public static native VideoMode[] enumerateSourceVideoModes(int source);
|
||||
public static native int[] enumerateSourceSinks(int source);
|
||||
public static native int copySource(int source);
|
||||
@@ -110,6 +112,7 @@ public class CameraServerJNI {
|
||||
// UsbCamera Source Functions
|
||||
//
|
||||
public static native String getUsbCameraPath(int source);
|
||||
public static native UsbCameraInfo getUsbCameraInfo(int source);
|
||||
|
||||
//
|
||||
// HttpCamera Source Functions
|
||||
@@ -144,6 +147,8 @@ public class CameraServerJNI {
|
||||
public static native String getSinkDescription(int sink);
|
||||
public static native int getSinkProperty(int sink, String name);
|
||||
public static native int[] enumerateSinkProperties(int sink);
|
||||
public static native boolean setSinkConfigJson(int sink, String config);
|
||||
public static native String getSinkConfigJson(int sink);
|
||||
public static native void setSinkSource(int sink, int source);
|
||||
public static native int getSinkSourceProperty(int sink, String name);
|
||||
public static native int getSinkSource(int sink);
|
||||
|
||||
@@ -60,7 +60,7 @@ public class MjpegServer extends VideoSink {
|
||||
* @param width width, 0 for unspecified
|
||||
* @param height height, 0 for unspecified
|
||||
*/
|
||||
void setResolution(int width, int height) {
|
||||
public void setResolution(int width, int height) {
|
||||
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "width"), width);
|
||||
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "height"), height);
|
||||
}
|
||||
@@ -72,7 +72,7 @@ public class MjpegServer extends VideoSink {
|
||||
*
|
||||
* @param fps FPS, 0 for unspecified
|
||||
*/
|
||||
void setFPS(int fps) {
|
||||
public void setFPS(int fps) {
|
||||
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "fps"), fps);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public class MjpegServer extends VideoSink {
|
||||
*
|
||||
* @param quality JPEG compression quality (0-100), -1 for unspecified
|
||||
*/
|
||||
void setCompression(int quality) {
|
||||
public void setCompression(int quality) {
|
||||
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "compression"),
|
||||
quality);
|
||||
}
|
||||
@@ -97,7 +97,7 @@ public class MjpegServer extends VideoSink {
|
||||
*
|
||||
* @param quality JPEG compression quality (0-100)
|
||||
*/
|
||||
void setDefaultCompression(int quality) {
|
||||
public void setDefaultCompression(int quality) {
|
||||
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "default_compression"),
|
||||
quality);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,13 @@ public class UsbCamera extends VideoCamera {
|
||||
return CameraServerJNI.getUsbCameraPath(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full camera information for the device.
|
||||
*/
|
||||
public UsbCameraInfo getInfo() {
|
||||
return CameraServerJNI.getUsbCameraInfo(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set how verbose the camera connection messages are.
|
||||
*
|
||||
|
||||
@@ -17,11 +17,14 @@ public class UsbCameraInfo {
|
||||
* @param dev Device number (e.g. N in '/dev/videoN' on Linux)
|
||||
* @param path Path to device if available (e.g. '/dev/video0' on Linux)
|
||||
* @param name Vendor/model name of the camera as provided by the USB driver
|
||||
* @param otherPaths Other path aliases to device
|
||||
*/
|
||||
public UsbCameraInfo(int dev, String path, String name) {
|
||||
@SuppressWarnings("PMD.ArrayIsStoredDirectly")
|
||||
public UsbCameraInfo(int dev, String path, String name, String[] otherPaths) {
|
||||
this.dev = dev;
|
||||
this.path = path;
|
||||
this.name = name;
|
||||
this.otherPaths = otherPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,4 +44,10 @@ public class UsbCameraInfo {
|
||||
*/
|
||||
@SuppressWarnings("MemberName")
|
||||
public String name;
|
||||
|
||||
/**
|
||||
* Other path aliases to device (e.g. '/dev/v4l/by-id/...' etc on Linux).
|
||||
*/
|
||||
@SuppressWarnings("MemberName")
|
||||
public String[] otherPaths;
|
||||
}
|
||||
|
||||
@@ -133,6 +133,38 @@ public class VideoSink implements AutoCloseable {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set properties from a JSON configuration string.
|
||||
*
|
||||
* <p>The format of the JSON input is:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "properties": [
|
||||
* {
|
||||
* "name": property name
|
||||
* "value": property value
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param config configuration
|
||||
* @return True if set successfully
|
||||
*/
|
||||
public boolean setConfigJson(String config) {
|
||||
return CameraServerJNI.setSinkConfigJson(m_handle, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON configuration string.
|
||||
*
|
||||
* @return JSON configuration string
|
||||
*/
|
||||
public String getConfigJson() {
|
||||
return CameraServerJNI.getSinkConfigJson(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure which source should provide frames to this sink. Each sink
|
||||
* can accept frames from only a single source, but a single source can
|
||||
|
||||
@@ -270,6 +270,45 @@ public class VideoSource implements AutoCloseable {
|
||||
return CameraServerJNI.setSourceFPS(m_handle, fps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set video mode and properties from a JSON configuration string.
|
||||
*
|
||||
* <p>The format of the JSON input is:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "pixel format": "MJPEG", "YUYV", etc
|
||||
* "width": video mode width
|
||||
* "height": video mode height
|
||||
* "fps": video mode fps
|
||||
* "brightness": percentage brightness
|
||||
* "white balance": "auto", "hold", or value
|
||||
* "exposure": "auto", "hold", or value
|
||||
* "properties": [
|
||||
* {
|
||||
* "name": property name
|
||||
* "value": property value
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param config configuration
|
||||
* @return True if set successfully
|
||||
*/
|
||||
public boolean setConfigJson(String config) {
|
||||
return CameraServerJNI.setSourceConfigJson(m_handle, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON configuration string.
|
||||
*
|
||||
* @return JSON configuration string
|
||||
*/
|
||||
public String getConfigJson() {
|
||||
return CameraServerJNI.getSourceConfigJson(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actual FPS.
|
||||
*
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <wpi/SmallString.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Instance.h"
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
#include "c_util.h"
|
||||
@@ -20,14 +21,17 @@
|
||||
|
||||
using namespace cs;
|
||||
|
||||
CvSinkImpl::CvSinkImpl(const wpi::Twine& name) : SinkImpl{name} {
|
||||
CvSinkImpl::CvSinkImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry)
|
||||
: SinkImpl{name, logger, notifier, telemetry} {
|
||||
m_active = true;
|
||||
// m_thread = std::thread(&CvSinkImpl::ThreadMain, this);
|
||||
}
|
||||
|
||||
CvSinkImpl::CvSinkImpl(const wpi::Twine& name,
|
||||
CvSinkImpl::CvSinkImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry,
|
||||
std::function<void(uint64_t time)> processFrame)
|
||||
: SinkImpl{name} {}
|
||||
: SinkImpl{name, logger, notifier, telemetry} {}
|
||||
|
||||
CvSinkImpl::~CvSinkImpl() { Stop(); }
|
||||
|
||||
@@ -119,24 +123,24 @@ void CvSinkImpl::ThreadMain() {
|
||||
namespace cs {
|
||||
|
||||
CS_Sink CreateCvSink(const wpi::Twine& name, CS_Status* status) {
|
||||
auto sink = std::make_shared<CvSinkImpl>(name);
|
||||
auto handle = Sinks::GetInstance().Allocate(CS_SINK_CV, sink);
|
||||
Notifier::GetInstance().NotifySink(name, handle, CS_SINK_CREATED);
|
||||
return handle;
|
||||
auto& inst = Instance::GetInstance();
|
||||
return inst.CreateSink(
|
||||
CS_SINK_CV, std::make_shared<CvSinkImpl>(name, inst.logger, inst.notifier,
|
||||
inst.telemetry));
|
||||
}
|
||||
|
||||
CS_Sink CreateCvSinkCallback(const wpi::Twine& name,
|
||||
std::function<void(uint64_t time)> processFrame,
|
||||
CS_Status* status) {
|
||||
auto sink = std::make_shared<CvSinkImpl>(name, processFrame);
|
||||
auto handle = Sinks::GetInstance().Allocate(CS_SINK_CV, sink);
|
||||
Notifier::GetInstance().NotifySink(name, handle, CS_SINK_CREATED);
|
||||
return handle;
|
||||
auto& inst = Instance::GetInstance();
|
||||
return inst.CreateSink(
|
||||
CS_SINK_CV, std::make_shared<CvSinkImpl>(name, inst.logger, inst.notifier,
|
||||
inst.telemetry, processFrame));
|
||||
}
|
||||
|
||||
void SetSinkDescription(CS_Sink sink, const wpi::Twine& description,
|
||||
CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data || data->kind != CS_SINK_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
@@ -145,7 +149,7 @@ void SetSinkDescription(CS_Sink sink, const wpi::Twine& description,
|
||||
}
|
||||
|
||||
uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data || data->kind != CS_SINK_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
@@ -155,7 +159,7 @@ uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status) {
|
||||
|
||||
uint64_t GrabSinkFrameTimeout(CS_Sink sink, cv::Mat& image, double timeout,
|
||||
CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data || data->kind != CS_SINK_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
@@ -164,7 +168,7 @@ uint64_t GrabSinkFrameTimeout(CS_Sink sink, cv::Mat& image, double timeout,
|
||||
}
|
||||
|
||||
std::string GetSinkError(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data || data->kind != CS_SINK_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
@@ -174,7 +178,7 @@ std::string GetSinkError(CS_Sink sink, CS_Status* status) {
|
||||
|
||||
wpi::StringRef GetSinkError(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data || data->kind != CS_SINK_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return wpi::StringRef{};
|
||||
@@ -183,7 +187,7 @@ wpi::StringRef GetSinkError(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
|
||||
}
|
||||
|
||||
void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data || data->kind != CS_SINK_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
|
||||
@@ -8,20 +8,17 @@
|
||||
#ifndef CSCORE_CVSINKIMPL_H_
|
||||
#define CSCORE_CVSINKIMPL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/NetworkAcceptor.h>
|
||||
#include <wpi/NetworkStream.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <wpi/Twine.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
#include <wpi/raw_socket_ostream.h>
|
||||
#include <wpi/condition_variable.h>
|
||||
|
||||
#include "Frame.h"
|
||||
#include "SinkImpl.h"
|
||||
|
||||
namespace cs {
|
||||
@@ -30,8 +27,10 @@ class SourceImpl;
|
||||
|
||||
class CvSinkImpl : public SinkImpl {
|
||||
public:
|
||||
explicit CvSinkImpl(const wpi::Twine& name);
|
||||
CvSinkImpl(const wpi::Twine& name,
|
||||
CvSinkImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
|
||||
Telemetry& telemetry);
|
||||
CvSinkImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
|
||||
Telemetry& telemetry,
|
||||
std::function<void(uint64_t time)> processFrame);
|
||||
~CvSinkImpl() override;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <wpi/timestamp.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Instance.h"
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
#include "c_util.h"
|
||||
@@ -21,15 +22,21 @@
|
||||
|
||||
using namespace cs;
|
||||
|
||||
CvSourceImpl::CvSourceImpl(const wpi::Twine& name, const VideoMode& mode)
|
||||
: SourceImpl{name} {
|
||||
CvSourceImpl::CvSourceImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry,
|
||||
const VideoMode& mode)
|
||||
: SourceImpl{name, logger, notifier, telemetry} {
|
||||
m_mode = mode;
|
||||
m_videoModes.push_back(m_mode);
|
||||
}
|
||||
|
||||
CvSourceImpl::~CvSourceImpl() {}
|
||||
|
||||
void CvSourceImpl::Start() {}
|
||||
void CvSourceImpl::Start() {
|
||||
m_notifier.NotifySource(*this, CS_SOURCE_CONNECTED);
|
||||
m_notifier.NotifySource(*this, CS_SOURCE_VIDEOMODES_UPDATED);
|
||||
m_notifier.NotifySourceVideoMode(*this, m_mode);
|
||||
}
|
||||
|
||||
bool CvSourceImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
|
||||
{
|
||||
@@ -37,7 +44,7 @@ bool CvSourceImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
|
||||
m_mode = mode;
|
||||
m_videoModes[0] = mode;
|
||||
}
|
||||
Notifier::GetInstance().NotifySourceVideoMode(*this, mode);
|
||||
m_notifier.NotifySourceVideoMode(*this, mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -105,8 +112,8 @@ int CvSourceImpl::CreateProperty(const wpi::Twine& name, CS_PropertyKind kind,
|
||||
prop.defaultValue = defaultValue;
|
||||
value = prop.value;
|
||||
});
|
||||
Notifier::GetInstance().NotifySourceProperty(
|
||||
*this, CS_SOURCE_PROPERTY_CREATED, name, ndx, kind, value, wpi::Twine{});
|
||||
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, name, ndx,
|
||||
kind, value, wpi::Twine{});
|
||||
return ndx;
|
||||
}
|
||||
|
||||
@@ -132,29 +139,23 @@ void CvSourceImpl::SetEnumPropertyChoices(int property,
|
||||
return;
|
||||
}
|
||||
prop->enumChoices = choices;
|
||||
Notifier::GetInstance().NotifySourceProperty(
|
||||
*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED, prop->name, property,
|
||||
CS_PROP_ENUM, prop->value, wpi::Twine{});
|
||||
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED,
|
||||
prop->name, property, CS_PROP_ENUM,
|
||||
prop->value, wpi::Twine{});
|
||||
}
|
||||
|
||||
namespace cs {
|
||||
|
||||
CS_Source CreateCvSource(const wpi::Twine& name, const VideoMode& mode,
|
||||
CS_Status* status) {
|
||||
auto source = std::make_shared<CvSourceImpl>(name, mode);
|
||||
auto handle = Sources::GetInstance().Allocate(CS_SOURCE_CV, source);
|
||||
auto& notifier = Notifier::GetInstance();
|
||||
notifier.NotifySource(name, handle, CS_SOURCE_CREATED);
|
||||
// Generate initial events here so they come after the source created event
|
||||
source->Start(); // causes a property event
|
||||
notifier.NotifySource(name, handle, CS_SOURCE_CONNECTED);
|
||||
notifier.NotifySource(name, handle, CS_SOURCE_VIDEOMODES_UPDATED);
|
||||
notifier.NotifySourceVideoMode(*source, mode);
|
||||
return handle;
|
||||
auto& inst = Instance::GetInstance();
|
||||
return inst.CreateSource(CS_SOURCE_CV, std::make_shared<CvSourceImpl>(
|
||||
name, inst.logger, inst.notifier,
|
||||
inst.telemetry, mode));
|
||||
}
|
||||
|
||||
void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
@@ -164,7 +165,7 @@ void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status) {
|
||||
|
||||
void NotifySourceError(CS_Source source, const wpi::Twine& msg,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
@@ -173,7 +174,7 @@ void NotifySourceError(CS_Source source, const wpi::Twine& msg,
|
||||
}
|
||||
|
||||
void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
@@ -183,7 +184,7 @@ void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) {
|
||||
|
||||
void SetSourceDescription(CS_Source source, const wpi::Twine& description,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
@@ -195,7 +196,7 @@ CS_Property CreateSourceProperty(CS_Source source, const wpi::Twine& name,
|
||||
CS_PropertyKind kind, int minimum, int maximum,
|
||||
int step, int defaultValue, int value,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return -1;
|
||||
@@ -210,7 +211,7 @@ CS_Property CreateSourcePropertyCallback(
|
||||
CS_Source source, const wpi::Twine& name, CS_PropertyKind kind, int minimum,
|
||||
int maximum, int step, int defaultValue, int value,
|
||||
std::function<void(CS_Property property)> onChange, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return -1;
|
||||
@@ -224,7 +225,7 @@ CS_Property CreateSourcePropertyCallback(
|
||||
void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
|
||||
wpi::ArrayRef<std::string> choices,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_CV) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
@@ -237,7 +238,7 @@ void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
auto data2 = Sources::GetInstance().Get(Handle{i, Handle::kSource});
|
||||
auto data2 = Instance::GetInstance().GetSource(Handle{i, Handle::kSource});
|
||||
if (!data2 || data->source.get() != data2->source.get()) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
|
||||
@@ -14,16 +14,21 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <wpi/ArrayRef.h>
|
||||
#include <wpi/Twine.h>
|
||||
|
||||
#include "SourceImpl.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class CvSourceImpl : public SourceImpl {
|
||||
public:
|
||||
CvSourceImpl(const wpi::Twine& name, const VideoMode& mode);
|
||||
CvSourceImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
|
||||
Telemetry& telemetry, const VideoMode& mode);
|
||||
~CvSourceImpl() override;
|
||||
|
||||
void Start();
|
||||
void Start() override;
|
||||
|
||||
bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <opencv2/highgui/highgui.hpp>
|
||||
#include <opencv2/imgproc/imgproc.hpp>
|
||||
|
||||
#include "Instance.h"
|
||||
#include "Log.h"
|
||||
#include "SourceImpl.h"
|
||||
|
||||
@@ -450,9 +451,11 @@ Image* Frame::GetImageImpl(int width, int height,
|
||||
if (!cur || cur->Is(width, height, pixelFormat, requiredJpegQuality))
|
||||
return cur;
|
||||
|
||||
DEBUG4("converting image from "
|
||||
<< cur->width << "x" << cur->height << " type " << cur->pixelFormat
|
||||
<< " to " << width << "x" << height << " type " << pixelFormat);
|
||||
WPI_DEBUG4(Instance::GetInstance().logger,
|
||||
"converting image from " << cur->width << "x" << cur->height
|
||||
<< " type " << cur->pixelFormat << " to "
|
||||
<< width << "x" << height << " type "
|
||||
<< pixelFormat);
|
||||
|
||||
// If the source image is a JPEG, we need to decode it before we can do
|
||||
// anything else with it. Note that if the destination format is JPEG, we
|
||||
|
||||
@@ -8,20 +8,10 @@
|
||||
#ifndef CSCORE_HANDLE_H_
|
||||
#define CSCORE_HANDLE_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <wpi/StringRef.h>
|
||||
|
||||
#include "UnlimitedHandleResource.h"
|
||||
#include "cscore_c.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class SinkImpl;
|
||||
class SourceImpl;
|
||||
|
||||
// Handle data layout:
|
||||
// Bits 0-15: Handle index
|
||||
// Bits 16-23: Parent index (property only)
|
||||
@@ -74,59 +64,6 @@ class Handle {
|
||||
CS_Handle m_handle;
|
||||
};
|
||||
|
||||
struct SourceData {
|
||||
SourceData(CS_SourceKind kind_, std::shared_ptr<SourceImpl> source_)
|
||||
: kind{kind_}, refCount{0}, source{source_} {}
|
||||
|
||||
CS_SourceKind kind;
|
||||
std::atomic_int refCount;
|
||||
std::shared_ptr<SourceImpl> source;
|
||||
};
|
||||
|
||||
class Sources
|
||||
: public UnlimitedHandleResource<Handle, SourceData, Handle::kSource> {
|
||||
public:
|
||||
static Sources& GetInstance() {
|
||||
static Sources instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
std::pair<CS_Source, std::shared_ptr<SourceData>> Find(
|
||||
const SourceImpl& source) {
|
||||
return FindIf(
|
||||
[&](const SourceData& data) { return data.source.get() == &source; });
|
||||
}
|
||||
|
||||
private:
|
||||
Sources() = default;
|
||||
};
|
||||
|
||||
struct SinkData {
|
||||
explicit SinkData(CS_SinkKind kind_, std::shared_ptr<SinkImpl> sink_)
|
||||
: kind{kind_}, refCount{0}, sourceHandle{0}, sink{sink_} {}
|
||||
|
||||
CS_SinkKind kind;
|
||||
std::atomic_int refCount;
|
||||
std::atomic<CS_Source> sourceHandle;
|
||||
std::shared_ptr<SinkImpl> sink;
|
||||
};
|
||||
|
||||
class Sinks : public UnlimitedHandleResource<Handle, SinkData, Handle::kSink> {
|
||||
public:
|
||||
static Sinks& GetInstance() {
|
||||
static Sinks instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
std::pair<CS_Sink, std::shared_ptr<SinkData>> Find(const SinkImpl& sink) {
|
||||
return FindIf(
|
||||
[&](const SinkData& data) { return data.sink.get() == &sink; });
|
||||
}
|
||||
|
||||
private:
|
||||
Sinks() = default;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_HANDLE_H_
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <wpi/timestamp.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Instance.h"
|
||||
#include "JpegUtil.h"
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
@@ -21,12 +22,20 @@
|
||||
|
||||
using namespace cs;
|
||||
|
||||
HttpCameraImpl::HttpCameraImpl(const wpi::Twine& name, CS_HttpCameraKind kind)
|
||||
: SourceImpl{name}, m_kind{kind} {}
|
||||
HttpCameraImpl::HttpCameraImpl(const wpi::Twine& name, CS_HttpCameraKind kind,
|
||||
wpi::Logger& logger, Notifier& notifier,
|
||||
Telemetry& telemetry)
|
||||
: SourceImpl{name, logger, notifier, telemetry}, m_kind{kind} {}
|
||||
|
||||
HttpCameraImpl::~HttpCameraImpl() {
|
||||
m_active = false;
|
||||
|
||||
// force wakeup of monitor thread
|
||||
m_monitorCond.notify_one();
|
||||
|
||||
// join monitor thread
|
||||
if (m_monitorThread.joinable()) m_monitorThread.join();
|
||||
|
||||
// Close file if it's open
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
@@ -51,16 +60,32 @@ void HttpCameraImpl::Start() {
|
||||
// Kick off the stream and settings threads
|
||||
m_streamThread = std::thread(&HttpCameraImpl::StreamThreadMain, this);
|
||||
m_settingsThread = std::thread(&HttpCameraImpl::SettingsThreadMain, this);
|
||||
m_monitorThread = std::thread(&HttpCameraImpl::MonitorThreadMain, this);
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
static inline void DoFdSet(int fd, fd_set* set, int* nfds) {
|
||||
if (fd >= 0) {
|
||||
FD_SET(fd, set);
|
||||
if ((fd + 1) > *nfds) *nfds = fd + 1;
|
||||
void HttpCameraImpl::MonitorThreadMain() {
|
||||
while (m_active) {
|
||||
std::unique_lock<wpi::mutex> lock(m_mutex);
|
||||
// sleep for 1 second between checks
|
||||
m_monitorCond.wait_for(lock, std::chrono::seconds(1),
|
||||
[=] { return !m_active; });
|
||||
|
||||
if (!m_active) break;
|
||||
|
||||
// check to see if we got any frames, and close the stream if not
|
||||
// (this will result in an error at the read point, and ultimately
|
||||
// a reconnect attempt)
|
||||
if (m_streamConn && m_frameCount == 0) {
|
||||
SWARNING("Monitor detected stream hung, disconnecting");
|
||||
m_streamConn->stream->close();
|
||||
}
|
||||
|
||||
// reset the frame counter
|
||||
m_frameCount = 0;
|
||||
}
|
||||
|
||||
SDEBUG("Monitor Thread exiting");
|
||||
}
|
||||
#endif
|
||||
|
||||
void HttpCameraImpl::StreamThreadMain() {
|
||||
while (m_active) {
|
||||
@@ -92,6 +117,10 @@ void HttpCameraImpl::StreamThreadMain() {
|
||||
|
||||
// stream
|
||||
DeviceStream(conn->is, boundary);
|
||||
{
|
||||
std::unique_lock<wpi::mutex> lock(m_mutex);
|
||||
m_streamConn = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
SDEBUG("Camera Thread exiting");
|
||||
@@ -115,8 +144,8 @@ wpi::HttpConnection* HttpCameraImpl::DeviceStreamConnect(
|
||||
}
|
||||
|
||||
// Try to connect
|
||||
auto stream = wpi::TCPConnector::connect(req.host.c_str(), req.port,
|
||||
Logger::GetInstance(), 1);
|
||||
auto stream =
|
||||
wpi::TCPConnector::connect(req.host.c_str(), req.port, m_logger, 1);
|
||||
|
||||
if (!m_active || !stream) return nullptr;
|
||||
|
||||
@@ -126,6 +155,7 @@ wpi::HttpConnection* HttpCameraImpl::DeviceStreamConnect(
|
||||
// update m_streamConn
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_frameCount = 1; // avoid a race with monitor thread
|
||||
m_streamConn = std::move(connPtr);
|
||||
}
|
||||
|
||||
@@ -235,6 +265,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
}
|
||||
PutFrame(VideoMode::PixelFormat::kMJPEG, width, height, imageBuf,
|
||||
wpi::Now());
|
||||
++m_frameCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -252,6 +283,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
image->width = width;
|
||||
image->height = height;
|
||||
PutFrame(std::move(image), wpi::Now());
|
||||
++m_frameCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -277,8 +309,8 @@ void HttpCameraImpl::SettingsThreadMain() {
|
||||
|
||||
void HttpCameraImpl::DeviceSendSettings(wpi::HttpRequest& req) {
|
||||
// Try to connect
|
||||
auto stream = wpi::TCPConnector::connect(req.host.c_str(), req.port,
|
||||
Logger::GetInstance(), 1);
|
||||
auto stream =
|
||||
wpi::TCPConnector::connect(req.host.c_str(), req.port, m_logger, 1);
|
||||
|
||||
if (!m_active || !stream) return;
|
||||
|
||||
@@ -341,9 +373,9 @@ void HttpCameraImpl::CreateProperty(const wpi::Twine& name,
|
||||
name, httpParam, viaSettings, kind, minimum, maximum, step, defaultValue,
|
||||
value));
|
||||
|
||||
Notifier::GetInstance().NotifySourceProperty(
|
||||
*this, CS_SOURCE_PROPERTY_CREATED, name, m_propertyData.size() + 1, kind,
|
||||
value, wpi::Twine{});
|
||||
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, name,
|
||||
m_propertyData.size() + 1, kind, value,
|
||||
wpi::Twine{});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -359,12 +391,12 @@ void HttpCameraImpl::CreateEnumProperty(
|
||||
enumChoices.clear();
|
||||
for (const auto& choice : choices) enumChoices.emplace_back(choice);
|
||||
|
||||
Notifier::GetInstance().NotifySourceProperty(
|
||||
*this, CS_SOURCE_PROPERTY_CREATED, name, m_propertyData.size() + 1,
|
||||
CS_PROP_ENUM, value, wpi::Twine{});
|
||||
Notifier::GetInstance().NotifySourceProperty(
|
||||
*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED, name,
|
||||
m_propertyData.size() + 1, CS_PROP_ENUM, value, wpi::Twine{});
|
||||
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, name,
|
||||
m_propertyData.size() + 1, CS_PROP_ENUM,
|
||||
value, wpi::Twine{});
|
||||
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED,
|
||||
name, m_propertyData.size() + 1, CS_PROP_ENUM,
|
||||
value, wpi::Twine{});
|
||||
}
|
||||
|
||||
std::unique_ptr<PropertyImpl> HttpCameraImpl::CreateEmptyProperty(
|
||||
@@ -475,41 +507,38 @@ namespace cs {
|
||||
|
||||
CS_Source CreateHttpCamera(const wpi::Twine& name, const wpi::Twine& url,
|
||||
CS_HttpCameraKind kind, CS_Status* status) {
|
||||
auto& inst = Instance::GetInstance();
|
||||
std::shared_ptr<HttpCameraImpl> source;
|
||||
switch (kind) {
|
||||
case CS_HTTP_AXIS:
|
||||
source = std::make_shared<AxisCameraImpl>(name);
|
||||
source = std::make_shared<AxisCameraImpl>(name, inst.logger,
|
||||
inst.notifier, inst.telemetry);
|
||||
break;
|
||||
default:
|
||||
source = std::make_shared<HttpCameraImpl>(name, kind);
|
||||
source = std::make_shared<HttpCameraImpl>(name, kind, inst.logger,
|
||||
inst.notifier, inst.telemetry);
|
||||
break;
|
||||
}
|
||||
if (!source->SetUrls(url.str(), status)) return 0;
|
||||
auto handle = Sources::GetInstance().Allocate(CS_SOURCE_HTTP, source);
|
||||
auto& notifier = Notifier::GetInstance();
|
||||
notifier.NotifySource(name, handle, CS_SOURCE_CREATED);
|
||||
source->Start();
|
||||
return handle;
|
||||
return inst.CreateSource(CS_SOURCE_HTTP, source);
|
||||
}
|
||||
|
||||
CS_Source CreateHttpCamera(const wpi::Twine& name,
|
||||
wpi::ArrayRef<std::string> urls,
|
||||
CS_HttpCameraKind kind, CS_Status* status) {
|
||||
auto& inst = Instance::GetInstance();
|
||||
if (urls.empty()) {
|
||||
*status = CS_EMPTY_VALUE;
|
||||
return 0;
|
||||
}
|
||||
auto source = std::make_shared<HttpCameraImpl>(name, kind);
|
||||
auto source = std::make_shared<HttpCameraImpl>(name, kind, inst.logger,
|
||||
inst.notifier, inst.telemetry);
|
||||
if (!source->SetUrls(urls, status)) return 0;
|
||||
auto handle = Sources::GetInstance().Allocate(CS_SOURCE_HTTP, source);
|
||||
auto& notifier = Notifier::GetInstance();
|
||||
notifier.NotifySource(name, handle, CS_SOURCE_CREATED);
|
||||
source->Start();
|
||||
return handle;
|
||||
return inst.CreateSource(CS_SOURCE_HTTP, source);
|
||||
}
|
||||
|
||||
CS_HttpCameraKind GetHttpCameraKind(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_HTTP) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return CS_HTTP_UNKNOWN;
|
||||
@@ -523,7 +552,7 @@ void SetHttpCameraUrls(CS_Source source, wpi::ArrayRef<std::string> urls,
|
||||
*status = CS_EMPTY_VALUE;
|
||||
return;
|
||||
}
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_HTTP) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
@@ -533,7 +562,7 @@ void SetHttpCameraUrls(CS_Source source, wpi::ArrayRef<std::string> urls,
|
||||
|
||||
std::vector<std::string> GetHttpCameraUrls(CS_Source source,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_HTTP) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::vector<std::string>{};
|
||||
|
||||
@@ -30,10 +30,11 @@ namespace cs {
|
||||
|
||||
class HttpCameraImpl : public SourceImpl {
|
||||
public:
|
||||
HttpCameraImpl(const wpi::Twine& name, CS_HttpCameraKind kind);
|
||||
HttpCameraImpl(const wpi::Twine& name, CS_HttpCameraKind kind,
|
||||
wpi::Logger& logger, Notifier& notifier, Telemetry& telemetry);
|
||||
~HttpCameraImpl() override;
|
||||
|
||||
void Start();
|
||||
void Start() override;
|
||||
|
||||
// Property functions
|
||||
void SetProperty(int property, int value, CS_Status* status) override;
|
||||
@@ -110,10 +111,14 @@ class HttpCameraImpl : public SourceImpl {
|
||||
void SettingsThreadMain();
|
||||
void DeviceSendSettings(wpi::HttpRequest& req);
|
||||
|
||||
// The monitor thread
|
||||
void MonitorThreadMain();
|
||||
|
||||
std::atomic_bool m_connected{false};
|
||||
std::atomic_bool m_active{true}; // set to false to terminate thread
|
||||
std::thread m_streamThread;
|
||||
std::thread m_settingsThread;
|
||||
std::thread m_monitorThread;
|
||||
|
||||
//
|
||||
// Variables protected by m_mutex
|
||||
@@ -129,6 +134,8 @@ class HttpCameraImpl : public SourceImpl {
|
||||
size_t m_nextLocation{0};
|
||||
int m_prefLocation{-1}; // preferred location
|
||||
|
||||
std::atomic_int m_frameCount{0};
|
||||
|
||||
wpi::condition_variable m_sinkEnabledCond;
|
||||
|
||||
wpi::StringMap<wpi::SmallString<16>> m_settings;
|
||||
@@ -136,12 +143,15 @@ class HttpCameraImpl : public SourceImpl {
|
||||
|
||||
wpi::StringMap<wpi::SmallString<16>> m_streamSettings;
|
||||
std::atomic_bool m_streamSettingsUpdated{false};
|
||||
|
||||
wpi::condition_variable m_monitorCond;
|
||||
};
|
||||
|
||||
class AxisCameraImpl : public HttpCameraImpl {
|
||||
public:
|
||||
explicit AxisCameraImpl(const wpi::Twine& name)
|
||||
: HttpCameraImpl{name, CS_HTTP_AXIS} {}
|
||||
AxisCameraImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry)
|
||||
: HttpCameraImpl{name, CS_HTTP_AXIS, logger, notifier, telemetry} {}
|
||||
#if 0
|
||||
void SetProperty(int property, int value, CS_Status* status) override;
|
||||
void SetStringProperty(int property, const wpi::Twine& value,
|
||||
|
||||
97
cscore/src/main/native/cpp/Instance.cpp
Normal file
97
cscore/src/main/native/cpp/Instance.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "Instance.h"
|
||||
|
||||
#include <wpi/Path.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
using namespace cs;
|
||||
|
||||
static void def_log_func(unsigned int level, const char* file,
|
||||
unsigned int line, const char* msg) {
|
||||
wpi::SmallString<128> buf;
|
||||
wpi::raw_svector_ostream oss(buf);
|
||||
if (level == 20) {
|
||||
oss << "CS: " << msg << '\n';
|
||||
wpi::errs() << oss.str();
|
||||
return;
|
||||
}
|
||||
|
||||
wpi::StringRef levelmsg;
|
||||
if (level >= 50)
|
||||
levelmsg = "CRITICAL: ";
|
||||
else if (level >= 40)
|
||||
levelmsg = "ERROR: ";
|
||||
else if (level >= 30)
|
||||
levelmsg = "WARNING: ";
|
||||
else
|
||||
return;
|
||||
oss << "CS: " << levelmsg << msg << " (" << wpi::sys::path::filename(file)
|
||||
<< ':' << line << ")\n";
|
||||
wpi::errs() << oss.str();
|
||||
}
|
||||
|
||||
Instance::Instance() : telemetry(notifier), networkListener(logger, notifier) {
|
||||
SetDefaultLogger();
|
||||
}
|
||||
|
||||
Instance::~Instance() {}
|
||||
|
||||
Instance& Instance::GetInstance() {
|
||||
static Instance* inst = new Instance;
|
||||
return *inst;
|
||||
}
|
||||
|
||||
void Instance::Shutdown() {
|
||||
eventLoop.Stop();
|
||||
m_sinks.FreeAll();
|
||||
m_sources.FreeAll();
|
||||
networkListener.Stop();
|
||||
telemetry.Stop();
|
||||
notifier.Stop();
|
||||
}
|
||||
|
||||
void Instance::SetDefaultLogger() { logger.SetLogger(def_log_func); }
|
||||
|
||||
std::pair<CS_Source, std::shared_ptr<SourceData>> Instance::FindSource(
|
||||
const SourceImpl& source) {
|
||||
return m_sources.FindIf(
|
||||
[&](const SourceData& data) { return data.source.get() == &source; });
|
||||
}
|
||||
|
||||
std::pair<CS_Sink, std::shared_ptr<SinkData>> Instance::FindSink(
|
||||
const SinkImpl& sink) {
|
||||
return m_sinks.FindIf(
|
||||
[&](const SinkData& data) { return data.sink.get() == &sink; });
|
||||
}
|
||||
|
||||
CS_Source Instance::CreateSource(CS_SourceKind kind,
|
||||
std::shared_ptr<SourceImpl> source) {
|
||||
auto handle = m_sources.Allocate(kind, source);
|
||||
notifier.NotifySource(source->GetName(), handle, CS_SOURCE_CREATED);
|
||||
source->Start();
|
||||
return handle;
|
||||
}
|
||||
|
||||
CS_Sink Instance::CreateSink(CS_SinkKind kind, std::shared_ptr<SinkImpl> sink) {
|
||||
auto handle = m_sinks.Allocate(kind, sink);
|
||||
notifier.NotifySink(sink->GetName(), handle, CS_SINK_CREATED);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void Instance::DestroySource(CS_Source handle) {
|
||||
if (auto data = m_sources.Free(handle))
|
||||
notifier.NotifySource(data->source->GetName(), handle, CS_SOURCE_DESTROYED);
|
||||
}
|
||||
|
||||
void Instance::DestroySink(CS_Sink handle) {
|
||||
if (auto data = m_sinks.Free(handle))
|
||||
notifier.NotifySink(data->sink->GetName(), handle, CS_SINK_DESTROYED);
|
||||
}
|
||||
115
cscore/src/main/native/cpp/Instance.h
Normal file
115
cscore/src/main/native/cpp/Instance.h
Normal file
@@ -0,0 +1,115 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_INSTANCE_H_
|
||||
#define CSCORE_INSTANCE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <wpi/EventLoopRunner.h>
|
||||
#include <wpi/Logger.h>
|
||||
|
||||
#include "Log.h"
|
||||
#include "NetworkListener.h"
|
||||
#include "Notifier.h"
|
||||
#include "SinkImpl.h"
|
||||
#include "SourceImpl.h"
|
||||
#include "Telemetry.h"
|
||||
#include "UnlimitedHandleResource.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
struct SourceData {
|
||||
SourceData(CS_SourceKind kind_, std::shared_ptr<SourceImpl> source_)
|
||||
: kind{kind_}, refCount{0}, source{source_} {}
|
||||
|
||||
CS_SourceKind kind;
|
||||
std::atomic_int refCount;
|
||||
std::shared_ptr<SourceImpl> source;
|
||||
};
|
||||
|
||||
struct SinkData {
|
||||
explicit SinkData(CS_SinkKind kind_, std::shared_ptr<SinkImpl> sink_)
|
||||
: kind{kind_}, refCount{0}, sourceHandle{0}, sink{sink_} {}
|
||||
|
||||
CS_SinkKind kind;
|
||||
std::atomic_int refCount;
|
||||
std::atomic<CS_Source> sourceHandle;
|
||||
std::shared_ptr<SinkImpl> sink;
|
||||
};
|
||||
|
||||
class Instance {
|
||||
public:
|
||||
Instance(const Instance&) = delete;
|
||||
Instance& operator=(const Instance&) = delete;
|
||||
~Instance();
|
||||
|
||||
static Instance& GetInstance();
|
||||
|
||||
void Shutdown();
|
||||
|
||||
wpi::Logger logger;
|
||||
Notifier notifier;
|
||||
Telemetry telemetry;
|
||||
NetworkListener networkListener;
|
||||
|
||||
private:
|
||||
UnlimitedHandleResource<Handle, SourceData, Handle::kSource> m_sources;
|
||||
UnlimitedHandleResource<Handle, SinkData, Handle::kSink> m_sinks;
|
||||
|
||||
public:
|
||||
wpi::EventLoopRunner eventLoop;
|
||||
|
||||
std::pair<CS_Sink, std::shared_ptr<SinkData>> FindSink(const SinkImpl& sink);
|
||||
std::pair<CS_Source, std::shared_ptr<SourceData>> FindSource(
|
||||
const SourceImpl& source);
|
||||
|
||||
void SetDefaultLogger();
|
||||
|
||||
std::shared_ptr<SourceData> GetSource(CS_Source handle) {
|
||||
return m_sources.Get(handle);
|
||||
}
|
||||
|
||||
std::shared_ptr<SinkData> GetSink(CS_Sink handle) {
|
||||
return m_sinks.Get(handle);
|
||||
}
|
||||
|
||||
CS_Source CreateSource(CS_SourceKind kind,
|
||||
std::shared_ptr<SourceImpl> source);
|
||||
|
||||
CS_Sink CreateSink(CS_SinkKind kind, std::shared_ptr<SinkImpl> sink);
|
||||
|
||||
void DestroySource(CS_Source handle);
|
||||
void DestroySink(CS_Sink handle);
|
||||
|
||||
wpi::ArrayRef<CS_Source> EnumerateSourceHandles(
|
||||
wpi::SmallVectorImpl<CS_Source>& vec) {
|
||||
return m_sources.GetAll(vec);
|
||||
}
|
||||
|
||||
wpi::ArrayRef<CS_Sink> EnumerateSinkHandles(
|
||||
wpi::SmallVectorImpl<CS_Sink>& vec) {
|
||||
return m_sinks.GetAll(vec);
|
||||
}
|
||||
|
||||
wpi::ArrayRef<CS_Sink> EnumerateSourceSinks(
|
||||
CS_Source source, wpi::SmallVectorImpl<CS_Sink>& vec) {
|
||||
vec.clear();
|
||||
m_sinks.ForEach([&](CS_Sink sinkHandle, const SinkData& data) {
|
||||
if (source == data.sourceHandle.load()) vec.push_back(sinkHandle);
|
||||
});
|
||||
return vec;
|
||||
}
|
||||
|
||||
private:
|
||||
Instance();
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_INSTANCE_H_
|
||||
@@ -1,45 +0,0 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
#include <wpi/Path.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
using namespace cs;
|
||||
|
||||
static void def_log_func(unsigned int level, const char* file,
|
||||
unsigned int line, const char* msg) {
|
||||
wpi::SmallString<128> buf;
|
||||
wpi::raw_svector_ostream oss(buf);
|
||||
if (level == 20) {
|
||||
oss << "CS: " << msg << '\n';
|
||||
wpi::errs() << oss.str();
|
||||
return;
|
||||
}
|
||||
|
||||
wpi::StringRef levelmsg;
|
||||
if (level >= 50)
|
||||
levelmsg = "CRITICAL: ";
|
||||
else if (level >= 40)
|
||||
levelmsg = "ERROR: ";
|
||||
else if (level >= 30)
|
||||
levelmsg = "WARNING: ";
|
||||
else
|
||||
return;
|
||||
oss << "CS: " << levelmsg << msg << " (" << wpi::sys::path::filename(file)
|
||||
<< ':' << line << ")\n";
|
||||
wpi::errs() << oss.str();
|
||||
}
|
||||
|
||||
Logger::Logger() { SetDefaultLogger(); }
|
||||
|
||||
Logger::~Logger() {}
|
||||
|
||||
void Logger::SetDefaultLogger() { SetLogger(def_log_func); }
|
||||
@@ -10,34 +10,18 @@
|
||||
|
||||
#include <wpi/Logger.h>
|
||||
|
||||
namespace cs {
|
||||
|
||||
class Logger : public wpi::Logger {
|
||||
public:
|
||||
static Logger& GetInstance() {
|
||||
static Logger instance;
|
||||
return instance;
|
||||
}
|
||||
~Logger();
|
||||
|
||||
void SetDefaultLogger();
|
||||
|
||||
private:
|
||||
Logger();
|
||||
};
|
||||
|
||||
#define LOG(level, x) WPI_LOG(cs::Logger::GetInstance(), level, x)
|
||||
#define LOG(level, x) WPI_LOG(m_logger, level, x)
|
||||
|
||||
#undef ERROR
|
||||
#define ERROR(x) WPI_ERROR(cs::Logger::GetInstance(), x)
|
||||
#define WARNING(x) WPI_WARNING(cs::Logger::GetInstance(), x)
|
||||
#define INFO(x) WPI_INFO(cs::Logger::GetInstance(), x)
|
||||
#define ERROR(x) WPI_ERROR(m_logger, x)
|
||||
#define WARNING(x) WPI_WARNING(m_logger, x)
|
||||
#define INFO(x) WPI_INFO(m_logger, x)
|
||||
|
||||
#define DEBUG(x) WPI_DEBUG(cs::Logger::GetInstance(), x)
|
||||
#define DEBUG1(x) WPI_DEBUG1(cs::Logger::GetInstance(), x)
|
||||
#define DEBUG2(x) WPI_DEBUG2(cs::Logger::GetInstance(), x)
|
||||
#define DEBUG3(x) WPI_DEBUG3(cs::Logger::GetInstance(), x)
|
||||
#define DEBUG4(x) WPI_DEBUG4(cs::Logger::GetInstance(), x)
|
||||
#define DEBUG(x) WPI_DEBUG(m_logger, x)
|
||||
#define DEBUG1(x) WPI_DEBUG1(m_logger, x)
|
||||
#define DEBUG2(x) WPI_DEBUG2(m_logger, x)
|
||||
#define DEBUG3(x) WPI_DEBUG3(m_logger, x)
|
||||
#define DEBUG4(x) WPI_DEBUG4(m_logger, x)
|
||||
|
||||
#define SERROR(x) ERROR(GetName() << ": " << x)
|
||||
#define SWARNING(x) WARNING(GetName() << ": " << x)
|
||||
@@ -49,6 +33,4 @@ class Logger : public wpi::Logger {
|
||||
#define SDEBUG3(x) DEBUG3(GetName() << ": " << x)
|
||||
#define SDEBUG4(x) DEBUG4(GetName() << ": " << x)
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_LOG_H_
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <wpi/raw_socket_ostream.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Instance.h"
|
||||
#include "JpegUtil.h"
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
@@ -66,14 +67,16 @@ static const char* startRootPage =
|
||||
"</head><body>\n"
|
||||
"<div class=\"stream\">\n"
|
||||
"<img src=\"/stream.mjpg\" /><p />\n"
|
||||
"<a href=\"/settings.json\">Settings JSON</a>\n"
|
||||
"<a href=\"/settings.json\">Settings JSON</a> |\n"
|
||||
"<a href=\"/config.json\">Source Config JSON</a>\n"
|
||||
"</div>\n"
|
||||
"<div class=\"settings\">\n";
|
||||
static const char* endRootPage = "</div></body></html>";
|
||||
|
||||
class MjpegServerImpl::ConnThread : public wpi::SafeThread {
|
||||
public:
|
||||
explicit ConnThread(const wpi::Twine& name) : m_name(name.str()) {}
|
||||
explicit ConnThread(const wpi::Twine& name, wpi::Logger& logger)
|
||||
: m_name(name.str()), m_logger(logger) {}
|
||||
|
||||
void Main();
|
||||
|
||||
@@ -97,6 +100,7 @@ class MjpegServerImpl::ConnThread : public wpi::SafeThread {
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
wpi::Logger& m_logger;
|
||||
|
||||
wpi::StringRef GetName() { return m_name; }
|
||||
|
||||
@@ -107,13 +111,13 @@ class MjpegServerImpl::ConnThread : public wpi::SafeThread {
|
||||
|
||||
void StartStream() {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_source->EnableSink();
|
||||
if (m_source) m_source->EnableSink();
|
||||
m_streaming = true;
|
||||
}
|
||||
|
||||
void StopStream() {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_source->DisableSink();
|
||||
if (m_source) m_source->DisableSink();
|
||||
m_streaming = false;
|
||||
}
|
||||
};
|
||||
@@ -331,13 +335,14 @@ bool MjpegServerImpl::ConnThread::ProcessCommand(wpi::raw_ostream& os,
|
||||
|
||||
void MjpegServerImpl::ConnThread::SendHTMLHeadTitle(
|
||||
wpi::raw_ostream& os) const {
|
||||
os << "<html><head><title>" << m_name << " CameraServer</title>";
|
||||
os << "<html><head><title>" << m_name << " CameraServer</title>"
|
||||
<< "<meta charset=\"UTF-8\">";
|
||||
}
|
||||
|
||||
// Send the root html file with controls for all the settable properties.
|
||||
void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
|
||||
SourceImpl& source, bool header) {
|
||||
if (header) SendHeader(os, 200, "OK", "application/x-javascript");
|
||||
if (header) SendHeader(os, 200, "OK", "text/html");
|
||||
|
||||
SendHTMLHeadTitle(os);
|
||||
os << startRootPage;
|
||||
@@ -409,6 +414,15 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
|
||||
}
|
||||
}
|
||||
|
||||
status = 0;
|
||||
auto info = GetUsbCameraInfo(Instance::GetInstance().FindSource(source).first,
|
||||
&status);
|
||||
if (status == CS_OK) {
|
||||
os << "<p>USB device path: " << info.path << '\n';
|
||||
for (auto&& path : info.otherPaths)
|
||||
os << "<p>Alternate device path: " << path << '\n';
|
||||
}
|
||||
|
||||
os << "<p>Supported Video Modes:</p>\n";
|
||||
os << "<table cols=\"4\" style=\"border: 1px solid black\">\n";
|
||||
os << "<tr><th>Pixel Format</th>"
|
||||
@@ -450,7 +464,7 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
|
||||
// Send a JSON file which is contains information about the source parameters.
|
||||
void MjpegServerImpl::ConnThread::SendJSON(wpi::raw_ostream& os,
|
||||
SourceImpl& source, bool header) {
|
||||
if (header) SendHeader(os, 200, "OK", "application/x-javascript");
|
||||
if (header) SendHeader(os, 200, "OK", "application/json");
|
||||
|
||||
os << "{\n\"controls\": [\n";
|
||||
wpi::SmallVector<int, 32> properties_vec;
|
||||
@@ -548,10 +562,11 @@ void MjpegServerImpl::ConnThread::SendJSON(wpi::raw_ostream& os,
|
||||
os.flush();
|
||||
}
|
||||
|
||||
MjpegServerImpl::MjpegServerImpl(const wpi::Twine& name,
|
||||
MjpegServerImpl::MjpegServerImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry,
|
||||
const wpi::Twine& listenAddress, int port,
|
||||
std::unique_ptr<wpi::NetworkAcceptor> acceptor)
|
||||
: SinkImpl{name},
|
||||
: SinkImpl{name, logger, notifier, telemetry},
|
||||
m_listenAddress(listenAddress.str()),
|
||||
m_port(port),
|
||||
m_acceptor{std::move(acceptor)} {
|
||||
@@ -628,8 +643,9 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
Frame::Time lastFrameTime = 0;
|
||||
Frame::Time timePerFrame = 0;
|
||||
if (m_fps != 0) timePerFrame = 1000000.0 / m_fps;
|
||||
// Allow fudge factor of 1 ms in frame rate
|
||||
if (timePerFrame >= 1000) timePerFrame -= 1000;
|
||||
Frame::Time averageFrameTime = 0;
|
||||
Frame::Time averagePeriod = 1000000; // 1 second window
|
||||
if (averagePeriod < timePerFrame) averagePeriod = timePerFrame * 10;
|
||||
|
||||
StartStream();
|
||||
while (m_active && !os.has_error()) {
|
||||
@@ -650,10 +666,26 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (frame.GetTime() < (lastFrameTime + timePerFrame)) {
|
||||
// Limit FPS; sleep for 10 ms so we don't consume all processor time
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
continue;
|
||||
auto thisFrameTime = frame.GetTime();
|
||||
if (thisFrameTime != 0 && timePerFrame != 0 && lastFrameTime != 0) {
|
||||
Frame::Time deltaTime = thisFrameTime - lastFrameTime;
|
||||
|
||||
// drop frame if it is early compared to the desired frame rate AND
|
||||
// the current average is higher than the desired average
|
||||
if (deltaTime < timePerFrame && averageFrameTime < timePerFrame) {
|
||||
// sleep for 1 ms so we don't consume all processor time
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
// update average
|
||||
if (averageFrameTime != 0) {
|
||||
averageFrameTime =
|
||||
averageFrameTime * (averagePeriod - timePerFrame) / averagePeriod +
|
||||
deltaTime * timePerFrame / averagePeriod;
|
||||
} else {
|
||||
averageFrameTime = deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
int width = m_width != 0 ? m_width : frame.GetOriginalWidth();
|
||||
@@ -690,7 +722,7 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
// print the individual mimetype and the length
|
||||
// sending the content-length fixes random stream disruption observed
|
||||
// with firefox
|
||||
lastFrameTime = frame.GetTime();
|
||||
lastFrameTime = thisFrameTime;
|
||||
double timestamp = lastFrameTime / 1000000.0;
|
||||
header.clear();
|
||||
oss << "\r\n--" BOUNDARY "\r\n"
|
||||
@@ -724,7 +756,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
return;
|
||||
}
|
||||
|
||||
enum { kCommand, kStream, kGetSettings, kRootPage } kind;
|
||||
enum { kCommand, kStream, kGetSettings, kGetSourceConfig, kRootPage } kind;
|
||||
wpi::StringRef parameters;
|
||||
size_t pos;
|
||||
|
||||
@@ -744,6 +776,9 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
} else if (req.find("GET /settings") != wpi::StringRef::npos &&
|
||||
req.find(".json") != wpi::StringRef::npos) {
|
||||
kind = kGetSettings;
|
||||
} else if (req.find("GET /config") != wpi::StringRef::npos &&
|
||||
req.find(".json") != wpi::StringRef::npos) {
|
||||
kind = kGetSourceConfig;
|
||||
} else if (req.find("GET /input") != wpi::StringRef::npos &&
|
||||
req.find(".json") != wpi::StringRef::npos) {
|
||||
kind = kGetSettings;
|
||||
@@ -802,6 +837,17 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
else
|
||||
SendError(os, 404, "Resource not found");
|
||||
break;
|
||||
case kGetSourceConfig:
|
||||
SDEBUG("request for JSON file");
|
||||
if (auto source = GetSource()) {
|
||||
SendHeader(os, 200, "OK", "application/json");
|
||||
CS_Status status = CS_OK;
|
||||
os << source->GetConfigJson(&status);
|
||||
os.flush();
|
||||
} else {
|
||||
SendError(os, 404, "Resource not found");
|
||||
}
|
||||
break;
|
||||
case kRootPage:
|
||||
SDEBUG("request for root page");
|
||||
SendHeader(os, 200, "OK", "text/html");
|
||||
@@ -865,7 +911,7 @@ void MjpegServerImpl::ServerThreadMain() {
|
||||
}
|
||||
|
||||
// Start it if not already started
|
||||
it->Start(GetName());
|
||||
it->Start(GetName(), m_logger);
|
||||
|
||||
auto nstreams =
|
||||
std::count_if(m_connThreads.begin(), m_connThreads.end(),
|
||||
@@ -909,20 +955,20 @@ namespace cs {
|
||||
CS_Sink CreateMjpegServer(const wpi::Twine& name,
|
||||
const wpi::Twine& listenAddress, int port,
|
||||
CS_Status* status) {
|
||||
auto& inst = Instance::GetInstance();
|
||||
wpi::SmallString<128> listenAddressBuf;
|
||||
auto sink = std::make_shared<MjpegServerImpl>(
|
||||
name, listenAddress, port,
|
||||
std::unique_ptr<wpi::NetworkAcceptor>(new wpi::TCPAcceptor(
|
||||
port,
|
||||
listenAddress.toNullTerminatedStringRef(listenAddressBuf).data(),
|
||||
Logger::GetInstance())));
|
||||
auto handle = Sinks::GetInstance().Allocate(CS_SINK_MJPEG, sink);
|
||||
Notifier::GetInstance().NotifySink(name, handle, CS_SINK_CREATED);
|
||||
return handle;
|
||||
return inst.CreateSink(
|
||||
CS_SINK_MJPEG,
|
||||
std::make_shared<MjpegServerImpl>(
|
||||
name, inst.logger, inst.notifier, inst.telemetry, listenAddress, port,
|
||||
std::unique_ptr<wpi::NetworkAcceptor>(new wpi::TCPAcceptor(
|
||||
port,
|
||||
listenAddress.toNullTerminatedStringRef(listenAddressBuf).data(),
|
||||
inst.logger))));
|
||||
}
|
||||
|
||||
std::string GetMjpegServerListenAddress(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data || data->kind != CS_SINK_MJPEG) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
@@ -931,7 +977,7 @@ std::string GetMjpegServerListenAddress(CS_Sink sink, CS_Status* status) {
|
||||
}
|
||||
|
||||
int GetMjpegServerPort(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data || data->kind != CS_SINK_MJPEG) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
|
||||
@@ -31,8 +31,10 @@ class SourceImpl;
|
||||
|
||||
class MjpegServerImpl : public SinkImpl {
|
||||
public:
|
||||
MjpegServerImpl(const wpi::Twine& name, const wpi::Twine& listenAddress,
|
||||
int port, std::unique_ptr<wpi::NetworkAcceptor> acceptor);
|
||||
MjpegServerImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry,
|
||||
const wpi::Twine& listenAddress, int port,
|
||||
std::unique_ptr<wpi::NetworkAcceptor> acceptor);
|
||||
~MjpegServerImpl() override;
|
||||
|
||||
void Stop();
|
||||
|
||||
@@ -8,26 +8,25 @@
|
||||
#ifndef CSCORE_NETWORKLISTENER_H_
|
||||
#define CSCORE_NETWORKLISTENER_H_
|
||||
|
||||
#include <wpi/SafeThread.h>
|
||||
#include <memory>
|
||||
|
||||
#include <wpi/Logger.h>
|
||||
|
||||
namespace cs {
|
||||
|
||||
class Notifier;
|
||||
|
||||
class NetworkListener {
|
||||
public:
|
||||
static NetworkListener& GetInstance() {
|
||||
static NetworkListener instance;
|
||||
return instance;
|
||||
}
|
||||
NetworkListener(wpi::Logger& logger, Notifier& notifier);
|
||||
~NetworkListener();
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
NetworkListener() = default;
|
||||
|
||||
class Thread;
|
||||
wpi::SafeThreadOwner<Thread> m_owner;
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Instance.h"
|
||||
#include "SinkImpl.h"
|
||||
#include "SourceImpl.h"
|
||||
|
||||
@@ -157,7 +158,7 @@ void Notifier::NotifySource(const wpi::Twine& name, CS_Source source,
|
||||
}
|
||||
|
||||
void Notifier::NotifySource(const SourceImpl& source, CS_EventKind kind) {
|
||||
auto handleData = Sources::GetInstance().Find(source);
|
||||
auto handleData = Instance::GetInstance().FindSource(source);
|
||||
NotifySource(source.GetName(), handleData.first, kind);
|
||||
}
|
||||
|
||||
@@ -166,7 +167,7 @@ void Notifier::NotifySourceVideoMode(const SourceImpl& source,
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
auto handleData = Sources::GetInstance().Find(source);
|
||||
auto handleData = Instance::GetInstance().FindSource(source);
|
||||
|
||||
thr->m_notifications.emplace(source.GetName(), handleData.first, mode);
|
||||
thr->m_cond.notify_one();
|
||||
@@ -179,7 +180,7 @@ void Notifier::NotifySourceProperty(const SourceImpl& source, CS_EventKind kind,
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
auto handleData = Sources::GetInstance().Find(source);
|
||||
auto handleData = Instance::GetInstance().FindSource(source);
|
||||
|
||||
thr->m_notifications.emplace(
|
||||
propertyName, handleData.first, static_cast<RawEvent::Kind>(kind),
|
||||
@@ -198,7 +199,7 @@ void Notifier::NotifySink(const wpi::Twine& name, CS_Sink sink,
|
||||
}
|
||||
|
||||
void Notifier::NotifySink(const SinkImpl& sink, CS_EventKind kind) {
|
||||
auto handleData = Sinks::GetInstance().Find(sink);
|
||||
auto handleData = Instance::GetInstance().FindSink(sink);
|
||||
NotifySink(sink.GetName(), handleData.first, kind);
|
||||
}
|
||||
|
||||
@@ -221,7 +222,7 @@ void Notifier::NotifySinkProperty(const SinkImpl& sink, CS_EventKind kind,
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
auto handleData = Sinks::GetInstance().Find(sink);
|
||||
auto handleData = Instance::GetInstance().FindSink(sink);
|
||||
|
||||
thr->m_notifications.emplace(
|
||||
propertyName, handleData.first, static_cast<RawEvent::Kind>(kind),
|
||||
|
||||
@@ -23,10 +23,7 @@ class Notifier {
|
||||
friend class NotifierTest;
|
||||
|
||||
public:
|
||||
static Notifier& GetInstance() {
|
||||
static Notifier instance;
|
||||
return instance;
|
||||
}
|
||||
Notifier();
|
||||
~Notifier();
|
||||
|
||||
void Start();
|
||||
@@ -62,8 +59,6 @@ class Notifier {
|
||||
void NotifyTelemetryUpdated();
|
||||
|
||||
private:
|
||||
Notifier();
|
||||
|
||||
class Thread;
|
||||
wpi::SafeThreadOwner<Thread> m_owner;
|
||||
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
|
||||
#include "PropertyContainer.h"
|
||||
|
||||
#include <wpi/Logger.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/json.h>
|
||||
|
||||
using namespace cs;
|
||||
|
||||
int PropertyContainer::GetPropertyIndex(const wpi::Twine& name) const {
|
||||
@@ -204,3 +209,74 @@ bool PropertyContainer::CacheProperties(CS_Status* status) const {
|
||||
m_properties_cached = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PropertyContainer::SetPropertiesJson(const wpi::json& config,
|
||||
wpi::Logger& logger,
|
||||
wpi::StringRef logName,
|
||||
CS_Status* status) {
|
||||
for (auto&& prop : config) {
|
||||
std::string name;
|
||||
try {
|
||||
name = prop.at("name").get<std::string>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
WPI_WARNING(logger,
|
||||
logName << ": SetConfigJson: could not read property name: "
|
||||
<< e.what());
|
||||
continue;
|
||||
}
|
||||
int n = GetPropertyIndex(name);
|
||||
try {
|
||||
auto& v = prop.at("value");
|
||||
if (v.is_string()) {
|
||||
std::string val = v.get<std::string>();
|
||||
WPI_INFO(logger, logName << ": SetConfigJson: setting property '"
|
||||
<< name << "' to '" << val << '\'');
|
||||
SetStringProperty(n, val, status);
|
||||
} else if (v.is_boolean()) {
|
||||
bool val = v.get<bool>();
|
||||
WPI_INFO(logger, logName << ": SetConfigJson: setting property '"
|
||||
<< name << "' to " << val);
|
||||
SetProperty(n, val, status);
|
||||
} else {
|
||||
int val = v.get<int>();
|
||||
WPI_INFO(logger, logName << ": SetConfigJson: setting property '"
|
||||
<< name << "' to " << val);
|
||||
SetProperty(n, val, status);
|
||||
}
|
||||
} catch (const wpi::json::exception& e) {
|
||||
WPI_WARNING(logger,
|
||||
logName << ": SetConfigJson: could not read property value: "
|
||||
<< e.what());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
wpi::json PropertyContainer::GetPropertiesJsonObject(CS_Status* status) {
|
||||
wpi::json j;
|
||||
wpi::SmallVector<int, 32> propVec;
|
||||
for (int p : EnumerateProperties(propVec, status)) {
|
||||
wpi::json prop;
|
||||
wpi::SmallString<128> strBuf;
|
||||
prop.emplace("name", GetPropertyName(p, strBuf, status));
|
||||
switch (GetPropertyKind(p)) {
|
||||
case CS_PROP_BOOLEAN:
|
||||
prop.emplace("value", static_cast<bool>(GetProperty(p, status)));
|
||||
break;
|
||||
case CS_PROP_INTEGER:
|
||||
case CS_PROP_ENUM:
|
||||
prop.emplace("value", GetProperty(p, status));
|
||||
break;
|
||||
case CS_PROP_STRING:
|
||||
prop.emplace("value", GetStringProperty(p, strBuf, status));
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
j.emplace_back(prop);
|
||||
}
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,11 @@
|
||||
#include "PropertyImpl.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace wpi {
|
||||
class Logger;
|
||||
class json;
|
||||
} // namespace wpi
|
||||
|
||||
namespace cs {
|
||||
|
||||
class PropertyContainer {
|
||||
@@ -50,6 +55,10 @@ class PropertyContainer {
|
||||
std::vector<std::string> GetEnumPropertyChoices(int property,
|
||||
CS_Status* status) const;
|
||||
|
||||
bool SetPropertiesJson(const wpi::json& config, wpi::Logger& logger,
|
||||
wpi::StringRef logName, CS_Status* status);
|
||||
wpi::json GetPropertiesJsonObject(CS_Status* status);
|
||||
|
||||
protected:
|
||||
// Get a property; must be called with m_mutex held.
|
||||
PropertyImpl* GetProperty(int property) {
|
||||
|
||||
@@ -7,12 +7,20 @@
|
||||
|
||||
#include "SinkImpl.h"
|
||||
|
||||
#include <wpi/json.h>
|
||||
|
||||
#include "Instance.h"
|
||||
#include "Notifier.h"
|
||||
#include "SourceImpl.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
SinkImpl::SinkImpl(const wpi::Twine& name) : m_name{name.str()} {}
|
||||
SinkImpl::SinkImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry)
|
||||
: m_logger(logger),
|
||||
m_notifier(notifier),
|
||||
m_telemetry(telemetry),
|
||||
m_name{name.str()} {}
|
||||
|
||||
SinkImpl::~SinkImpl() {
|
||||
if (m_source) {
|
||||
@@ -37,7 +45,7 @@ void SinkImpl::Enable() {
|
||||
++m_enabledCount;
|
||||
if (m_enabledCount == 1) {
|
||||
if (m_source) m_source->EnableSink();
|
||||
Notifier::GetInstance().NotifySink(*this, CS_SINK_ENABLED);
|
||||
m_notifier.NotifySink(*this, CS_SINK_ENABLED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +54,7 @@ void SinkImpl::Disable() {
|
||||
--m_enabledCount;
|
||||
if (m_enabledCount == 0) {
|
||||
if (m_source) m_source->DisableSink();
|
||||
Notifier::GetInstance().NotifySink(*this, CS_SINK_DISABLED);
|
||||
m_notifier.NotifySink(*this, CS_SINK_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,11 +63,11 @@ void SinkImpl::SetEnabled(bool enabled) {
|
||||
if (enabled && m_enabledCount == 0) {
|
||||
if (m_source) m_source->EnableSink();
|
||||
m_enabledCount = 1;
|
||||
Notifier::GetInstance().NotifySink(*this, CS_SINK_ENABLED);
|
||||
m_notifier.NotifySink(*this, CS_SINK_ENABLED);
|
||||
} else if (!enabled && m_enabledCount > 0) {
|
||||
if (m_source) m_source->DisableSink();
|
||||
m_enabledCount = 0;
|
||||
Notifier::GetInstance().NotifySink(*this, CS_SINK_DISABLED);
|
||||
m_notifier.NotifySink(*this, CS_SINK_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,16 +104,52 @@ wpi::StringRef SinkImpl::GetError(wpi::SmallVectorImpl<char>& buf) const {
|
||||
return wpi::StringRef{buf.data(), buf.size()};
|
||||
}
|
||||
|
||||
bool SinkImpl::SetConfigJson(wpi::StringRef config, CS_Status* status) {
|
||||
wpi::json j;
|
||||
try {
|
||||
j = wpi::json::parse(config);
|
||||
} catch (const wpi::json::parse_error& e) {
|
||||
SWARNING("SetConfigJson: parse error at byte " << e.byte << ": "
|
||||
<< e.what());
|
||||
*status = CS_PROPERTY_WRITE_FAILED;
|
||||
return false;
|
||||
}
|
||||
return SetConfigJson(j, status);
|
||||
}
|
||||
|
||||
bool SinkImpl::SetConfigJson(const wpi::json& config, CS_Status* status) {
|
||||
if (config.count("properties") != 0)
|
||||
SetPropertiesJson(config.at("properties"), m_logger, GetName(), status);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string SinkImpl::GetConfigJson(CS_Status* status) {
|
||||
std::string rv;
|
||||
wpi::raw_string_ostream os(rv);
|
||||
GetConfigJsonObject(status).dump(os, 4);
|
||||
os.flush();
|
||||
return rv;
|
||||
}
|
||||
|
||||
wpi::json SinkImpl::GetConfigJsonObject(CS_Status* status) {
|
||||
wpi::json j;
|
||||
|
||||
wpi::json props = GetPropertiesJsonObject(status);
|
||||
if (props.is_array()) j.emplace("properties", props);
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
void SinkImpl::NotifyPropertyCreated(int propIndex, PropertyImpl& prop) {
|
||||
auto& notifier = Notifier::GetInstance();
|
||||
notifier.NotifySinkProperty(*this, CS_SINK_PROPERTY_CREATED, prop.name,
|
||||
propIndex, prop.propKind, prop.value,
|
||||
prop.valueStr);
|
||||
m_notifier.NotifySinkProperty(*this, CS_SINK_PROPERTY_CREATED, prop.name,
|
||||
propIndex, prop.propKind, prop.value,
|
||||
prop.valueStr);
|
||||
// also notify choices updated event for enum types
|
||||
if (prop.propKind == CS_PROP_ENUM)
|
||||
notifier.NotifySinkProperty(*this, CS_SINK_PROPERTY_CHOICES_UPDATED,
|
||||
prop.name, propIndex, prop.propKind, prop.value,
|
||||
wpi::Twine{});
|
||||
m_notifier.NotifySinkProperty(*this, CS_SINK_PROPERTY_CHOICES_UPDATED,
|
||||
prop.name, propIndex, prop.propKind,
|
||||
prop.value, wpi::Twine{});
|
||||
}
|
||||
|
||||
void SinkImpl::UpdatePropertyValue(int property, bool setString, int value,
|
||||
@@ -119,10 +163,11 @@ void SinkImpl::UpdatePropertyValue(int property, bool setString, int value,
|
||||
prop->SetValue(value);
|
||||
|
||||
// Only notify updates after we've notified created
|
||||
if (m_properties_cached)
|
||||
Notifier::GetInstance().NotifySinkProperty(
|
||||
*this, CS_SINK_PROPERTY_VALUE_UPDATED, prop->name, property,
|
||||
prop->propKind, prop->value, prop->valueStr);
|
||||
if (m_properties_cached) {
|
||||
m_notifier.NotifySinkProperty(*this, CS_SINK_PROPERTY_VALUE_UPDATED,
|
||||
prop->name, property, prop->propKind,
|
||||
prop->value, prop->valueStr);
|
||||
}
|
||||
}
|
||||
|
||||
void SinkImpl::SetSourceImpl(std::shared_ptr<SourceImpl> source) {}
|
||||
|
||||
@@ -11,19 +11,27 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <wpi/Logger.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/Twine.h>
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
#include "SourceImpl.h"
|
||||
|
||||
namespace wpi {
|
||||
class json;
|
||||
} // namespace wpi
|
||||
|
||||
namespace cs {
|
||||
|
||||
class Frame;
|
||||
class Notifier;
|
||||
class Telemetry;
|
||||
|
||||
class SinkImpl : public PropertyContainer {
|
||||
public:
|
||||
explicit SinkImpl(const wpi::Twine& name);
|
||||
explicit SinkImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry);
|
||||
virtual ~SinkImpl();
|
||||
SinkImpl(const SinkImpl& queue) = delete;
|
||||
SinkImpl& operator=(const SinkImpl& queue) = delete;
|
||||
@@ -47,6 +55,11 @@ class SinkImpl : public PropertyContainer {
|
||||
std::string GetError() const;
|
||||
wpi::StringRef GetError(wpi::SmallVectorImpl<char>& buf) const;
|
||||
|
||||
bool SetConfigJson(wpi::StringRef config, CS_Status* status);
|
||||
virtual bool SetConfigJson(const wpi::json& config, CS_Status* status);
|
||||
std::string GetConfigJson(CS_Status* status);
|
||||
virtual wpi::json GetConfigJsonObject(CS_Status* status);
|
||||
|
||||
protected:
|
||||
// PropertyContainer implementation
|
||||
void NotifyPropertyCreated(int propIndex, PropertyImpl& prop) override;
|
||||
@@ -55,6 +68,11 @@ class SinkImpl : public PropertyContainer {
|
||||
|
||||
virtual void SetSourceImpl(std::shared_ptr<SourceImpl> source);
|
||||
|
||||
protected:
|
||||
wpi::Logger& m_logger;
|
||||
Notifier& m_notifier;
|
||||
Telemetry& m_telemetry;
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
std::string m_description;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <cstring>
|
||||
|
||||
#include <wpi/STLExtras.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/timestamp.h>
|
||||
|
||||
#include "Log.h"
|
||||
@@ -21,7 +22,12 @@ using namespace cs;
|
||||
|
||||
static constexpr size_t kMaxImagesAvail = 32;
|
||||
|
||||
SourceImpl::SourceImpl(const wpi::Twine& name) : m_name{name.str()} {
|
||||
SourceImpl::SourceImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry)
|
||||
: m_logger(logger),
|
||||
m_notifier(notifier),
|
||||
m_telemetry(telemetry),
|
||||
m_name{name.str()} {
|
||||
m_frame = Frame{*this, wpi::StringRef{}, 0};
|
||||
}
|
||||
|
||||
@@ -53,9 +59,9 @@ wpi::StringRef SourceImpl::GetDescription(
|
||||
void SourceImpl::SetConnected(bool connected) {
|
||||
bool wasConnected = m_connected.exchange(connected);
|
||||
if (wasConnected && !connected)
|
||||
Notifier::GetInstance().NotifySource(*this, CS_SOURCE_DISCONNECTED);
|
||||
m_notifier.NotifySource(*this, CS_SOURCE_DISCONNECTED);
|
||||
else if (!wasConnected && connected)
|
||||
Notifier::GetInstance().NotifySource(*this, CS_SOURCE_CONNECTED);
|
||||
m_notifier.NotifySource(*this, CS_SOURCE_CONNECTED);
|
||||
}
|
||||
|
||||
uint64_t SourceImpl::GetCurFrameTime() {
|
||||
@@ -156,6 +162,221 @@ bool SourceImpl::SetFPS(int fps, CS_Status* status) {
|
||||
return SetVideoMode(mode, status);
|
||||
}
|
||||
|
||||
bool SourceImpl::SetConfigJson(wpi::StringRef config, CS_Status* status) {
|
||||
wpi::json j;
|
||||
try {
|
||||
j = wpi::json::parse(config);
|
||||
} catch (const wpi::json::parse_error& e) {
|
||||
SWARNING("SetConfigJson: parse error at byte " << e.byte << ": "
|
||||
<< e.what());
|
||||
*status = CS_PROPERTY_WRITE_FAILED;
|
||||
return false;
|
||||
}
|
||||
return SetConfigJson(j, status);
|
||||
}
|
||||
|
||||
bool SourceImpl::SetConfigJson(const wpi::json& config, CS_Status* status) {
|
||||
VideoMode mode;
|
||||
|
||||
// pixel format
|
||||
if (config.count("pixel format") != 0) {
|
||||
try {
|
||||
auto str = config.at("pixel format").get<std::string>();
|
||||
wpi::StringRef s(str);
|
||||
if (s.equals_lower("mjpeg")) {
|
||||
mode.pixelFormat = cs::VideoMode::kMJPEG;
|
||||
} else if (s.equals_lower("yuyv")) {
|
||||
mode.pixelFormat = cs::VideoMode::kYUYV;
|
||||
} else if (s.equals_lower("rgb565")) {
|
||||
mode.pixelFormat = cs::VideoMode::kRGB565;
|
||||
} else if (s.equals_lower("bgr")) {
|
||||
mode.pixelFormat = cs::VideoMode::kBGR;
|
||||
} else if (s.equals_lower("gray")) {
|
||||
mode.pixelFormat = cs::VideoMode::kGray;
|
||||
} else {
|
||||
SWARNING("SetConfigJson: could not understand pixel format value '"
|
||||
<< str << '\'');
|
||||
}
|
||||
} catch (const wpi::json::exception& e) {
|
||||
SWARNING("SetConfigJson: could not read pixel format: " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// width
|
||||
if (config.count("width") != 0) {
|
||||
try {
|
||||
mode.width = config.at("width").get<unsigned int>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
SWARNING("SetConfigJson: could not read width: " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// height
|
||||
if (config.count("height") != 0) {
|
||||
try {
|
||||
mode.height = config.at("height").get<unsigned int>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
SWARNING("SetConfigJson: could not read height: " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// fps
|
||||
if (config.count("fps") != 0) {
|
||||
try {
|
||||
mode.fps = config.at("fps").get<unsigned int>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
SWARNING("SetConfigJson: could not read fps: " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// if all of video mode is set, use SetVideoMode, otherwise piecemeal it
|
||||
if (mode.pixelFormat != VideoMode::kUnknown && mode.width != 0 &&
|
||||
mode.height != 0 && mode.fps != 0) {
|
||||
SINFO("SetConfigJson: setting video mode to pixelFormat "
|
||||
<< mode.pixelFormat << ", width " << mode.width << ", height "
|
||||
<< mode.height << ", fps " << mode.fps);
|
||||
SetVideoMode(mode, status);
|
||||
} else {
|
||||
if (mode.pixelFormat != cs::VideoMode::kUnknown) {
|
||||
SINFO("SetConfigJson: setting pixelFormat " << mode.pixelFormat);
|
||||
SetPixelFormat(static_cast<cs::VideoMode::PixelFormat>(mode.pixelFormat),
|
||||
status);
|
||||
}
|
||||
if (mode.width != 0 && mode.height != 0) {
|
||||
SINFO("SetConfigJson: setting width " << mode.width << ", height "
|
||||
<< mode.height);
|
||||
SetResolution(mode.width, mode.height, status);
|
||||
}
|
||||
if (mode.fps != 0) {
|
||||
SINFO("SetConfigJson: setting fps " << mode.fps);
|
||||
SetFPS(mode.fps, status);
|
||||
}
|
||||
}
|
||||
|
||||
// brightness
|
||||
if (config.count("brightness") != 0) {
|
||||
try {
|
||||
int val = config.at("brightness").get<int>();
|
||||
SINFO("SetConfigJson: setting brightness to " << val);
|
||||
SetBrightness(val, status);
|
||||
} catch (const wpi::json::exception& e) {
|
||||
SWARNING("SetConfigJson: could not read brightness: " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// white balance
|
||||
if (config.count("white balance") != 0) {
|
||||
try {
|
||||
auto& setting = config.at("white balance");
|
||||
if (setting.is_string()) {
|
||||
auto str = setting.get<std::string>();
|
||||
wpi::StringRef s(str);
|
||||
if (s.equals_lower("auto")) {
|
||||
SINFO("SetConfigJson: setting white balance to auto");
|
||||
SetWhiteBalanceAuto(status);
|
||||
} else if (s.equals_lower("hold")) {
|
||||
SINFO("SetConfigJson: setting white balance to hold current");
|
||||
SetWhiteBalanceHoldCurrent(status);
|
||||
} else {
|
||||
SWARNING("SetConfigJson: could not understand white balance value '"
|
||||
<< str << '\'');
|
||||
}
|
||||
} else {
|
||||
int val = setting.get<int>();
|
||||
SINFO("SetConfigJson: setting white balance to " << val);
|
||||
SetWhiteBalanceManual(val, status);
|
||||
}
|
||||
} catch (const wpi::json::exception& e) {
|
||||
SWARNING("SetConfigJson: could not read white balance: " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// exposure
|
||||
if (config.count("exposure") != 0) {
|
||||
try {
|
||||
auto& setting = config.at("exposure");
|
||||
if (setting.is_string()) {
|
||||
auto str = setting.get<std::string>();
|
||||
wpi::StringRef s(str);
|
||||
if (s.equals_lower("auto")) {
|
||||
SINFO("SetConfigJson: setting exposure to auto");
|
||||
SetExposureAuto(status);
|
||||
} else if (s.equals_lower("hold")) {
|
||||
SINFO("SetConfigJson: setting exposure to hold current");
|
||||
SetExposureHoldCurrent(status);
|
||||
} else {
|
||||
SWARNING("SetConfigJson: could not understand exposure value '"
|
||||
<< str << '\'');
|
||||
}
|
||||
} else {
|
||||
int val = setting.get<int>();
|
||||
SINFO("SetConfigJson: setting exposure to " << val);
|
||||
SetExposureManual(val, status);
|
||||
}
|
||||
} catch (const wpi::json::exception& e) {
|
||||
SWARNING("SetConfigJson: could not read exposure: " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// properties
|
||||
if (config.count("properties") != 0)
|
||||
SetPropertiesJson(config.at("properties"), m_logger, GetName(), status);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string SourceImpl::GetConfigJson(CS_Status* status) {
|
||||
std::string rv;
|
||||
wpi::raw_string_ostream os(rv);
|
||||
GetConfigJsonObject(status).dump(os, 4);
|
||||
os.flush();
|
||||
return rv;
|
||||
}
|
||||
|
||||
wpi::json SourceImpl::GetConfigJsonObject(CS_Status* status) {
|
||||
wpi::json j;
|
||||
|
||||
// pixel format
|
||||
wpi::StringRef pixelFormat;
|
||||
switch (m_mode.pixelFormat) {
|
||||
case VideoMode::kMJPEG:
|
||||
pixelFormat = "mjpeg";
|
||||
break;
|
||||
case VideoMode::kYUYV:
|
||||
pixelFormat = "yuyv";
|
||||
break;
|
||||
case VideoMode::kRGB565:
|
||||
pixelFormat = "rgb565";
|
||||
break;
|
||||
case VideoMode::kBGR:
|
||||
pixelFormat = "bgr";
|
||||
break;
|
||||
case VideoMode::kGray:
|
||||
pixelFormat = "gray";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!pixelFormat.empty()) j.emplace("pixel format", pixelFormat);
|
||||
|
||||
// width
|
||||
if (m_mode.width != 0) j.emplace("width", m_mode.width);
|
||||
|
||||
// height
|
||||
if (m_mode.height != 0) j.emplace("height", m_mode.height);
|
||||
|
||||
// fps
|
||||
if (m_mode.fps != 0) j.emplace("fps", m_mode.fps);
|
||||
|
||||
// TODO: output brightness, white balance, and exposure?
|
||||
|
||||
// properties
|
||||
wpi::json props = GetPropertiesJsonObject(status);
|
||||
if (props.is_array()) j.emplace("properties", props);
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
std::vector<VideoMode> SourceImpl::EnumerateVideoModes(
|
||||
CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status))
|
||||
@@ -215,9 +436,8 @@ void SourceImpl::PutFrame(VideoMode::PixelFormat pixelFormat, int width,
|
||||
|
||||
void SourceImpl::PutFrame(std::unique_ptr<Image> image, Frame::Time time) {
|
||||
// Update telemetry
|
||||
Telemetry::GetInstance().RecordSourceFrames(*this, 1);
|
||||
Telemetry::GetInstance().RecordSourceBytes(*this,
|
||||
static_cast<int>(image->size()));
|
||||
m_telemetry.RecordSourceFrames(*this, 1);
|
||||
m_telemetry.RecordSourceBytes(*this, static_cast<int>(image->size()));
|
||||
|
||||
// Update frame
|
||||
{
|
||||
@@ -241,15 +461,14 @@ void SourceImpl::PutError(const wpi::Twine& msg, Frame::Time time) {
|
||||
}
|
||||
|
||||
void SourceImpl::NotifyPropertyCreated(int propIndex, PropertyImpl& prop) {
|
||||
auto& notifier = Notifier::GetInstance();
|
||||
notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, prop.name,
|
||||
propIndex, prop.propKind, prop.value,
|
||||
prop.valueStr);
|
||||
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, prop.name,
|
||||
propIndex, prop.propKind, prop.value,
|
||||
prop.valueStr);
|
||||
// also notify choices updated event for enum types
|
||||
if (prop.propKind == CS_PROP_ENUM)
|
||||
notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED,
|
||||
prop.name, propIndex, prop.propKind,
|
||||
prop.value, wpi::Twine{});
|
||||
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED,
|
||||
prop.name, propIndex, prop.propKind,
|
||||
prop.value, wpi::Twine{});
|
||||
}
|
||||
|
||||
void SourceImpl::UpdatePropertyValue(int property, bool setString, int value,
|
||||
@@ -263,10 +482,11 @@ void SourceImpl::UpdatePropertyValue(int property, bool setString, int value,
|
||||
prop->SetValue(value);
|
||||
|
||||
// Only notify updates after we've notified created
|
||||
if (m_properties_cached)
|
||||
Notifier::GetInstance().NotifySourceProperty(
|
||||
*this, CS_SOURCE_PROPERTY_VALUE_UPDATED, prop->name, property,
|
||||
prop->propKind, prop->value, prop->valueStr);
|
||||
if (m_properties_cached) {
|
||||
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_VALUE_UPDATED,
|
||||
prop->name, property, prop->propKind,
|
||||
prop->value, prop->valueStr);
|
||||
}
|
||||
}
|
||||
|
||||
void SourceImpl::ReleaseImage(std::unique_ptr<Image> image) {
|
||||
|
||||
@@ -15,27 +15,39 @@
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/ArrayRef.h>
|
||||
#include <wpi/Logger.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/Twine.h>
|
||||
#include <wpi/condition_variable.h>
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
#include "Frame.h"
|
||||
#include "Handle.h"
|
||||
#include "Image.h"
|
||||
#include "PropertyContainer.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace wpi {
|
||||
class json;
|
||||
} // namespace wpi
|
||||
|
||||
namespace cs {
|
||||
|
||||
class Notifier;
|
||||
class Telemetry;
|
||||
|
||||
class SourceImpl : public PropertyContainer {
|
||||
friend class Frame;
|
||||
|
||||
public:
|
||||
explicit SourceImpl(const wpi::Twine& name);
|
||||
SourceImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
|
||||
Telemetry& telemetry);
|
||||
virtual ~SourceImpl();
|
||||
SourceImpl(const SourceImpl& oth) = delete;
|
||||
SourceImpl& operator=(const SourceImpl& oth) = delete;
|
||||
|
||||
virtual void Start() = 0;
|
||||
|
||||
wpi::StringRef GetName() const { return m_name; }
|
||||
|
||||
void SetDescription(const wpi::Twine& description);
|
||||
@@ -119,6 +131,11 @@ class SourceImpl : public PropertyContainer {
|
||||
virtual bool SetResolution(int width, int height, CS_Status* status);
|
||||
virtual bool SetFPS(int fps, CS_Status* status);
|
||||
|
||||
bool SetConfigJson(wpi::StringRef config, CS_Status* status);
|
||||
virtual bool SetConfigJson(const wpi::json& config, CS_Status* status);
|
||||
std::string GetConfigJson(CS_Status* status);
|
||||
virtual wpi::json GetConfigJsonObject(CS_Status* status);
|
||||
|
||||
std::vector<VideoMode> EnumerateVideoModes(CS_Status* status) const;
|
||||
|
||||
std::unique_ptr<Image> AllocImage(VideoMode::PixelFormat pixelFormat,
|
||||
@@ -146,6 +163,10 @@ class SourceImpl : public PropertyContainer {
|
||||
// Current video mode
|
||||
mutable VideoMode m_mode;
|
||||
|
||||
wpi::Logger& m_logger;
|
||||
Notifier& m_notifier;
|
||||
Telemetry& m_telemetry;
|
||||
|
||||
private:
|
||||
void ReleaseImage(std::unique_ptr<Image> image);
|
||||
std::unique_ptr<Frame::Impl> AllocFrameImpl();
|
||||
|
||||
@@ -14,15 +14,20 @@
|
||||
#include <wpi/timestamp.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Instance.h"
|
||||
#include "Notifier.h"
|
||||
#include "SourceImpl.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
class Telemetry::Thread : public wpi::SafeThread {
|
||||
public:
|
||||
explicit Thread(Notifier& notifier) : m_notifier(notifier) {}
|
||||
|
||||
void Main();
|
||||
|
||||
Notifier& m_notifier;
|
||||
wpi::DenseMap<std::pair<CS_Handle, int>, int64_t> m_user;
|
||||
wpi::DenseMap<std::pair<CS_Handle, int>, int64_t> m_current;
|
||||
double m_period = 0.0;
|
||||
@@ -41,11 +46,9 @@ int64_t Telemetry::Thread::GetValue(CS_Handle handle, CS_TelemetryKind kind,
|
||||
return it->getSecond();
|
||||
}
|
||||
|
||||
Telemetry::Telemetry() {}
|
||||
|
||||
Telemetry::~Telemetry() {}
|
||||
|
||||
void Telemetry::Start() { m_owner.Start(); }
|
||||
void Telemetry::Start() { m_owner.Start(m_notifier); }
|
||||
|
||||
void Telemetry::Stop() { m_owner.Stop(); }
|
||||
|
||||
@@ -74,7 +77,7 @@ void Telemetry::Thread::Main() {
|
||||
prevTime = curTime;
|
||||
|
||||
// notify
|
||||
Notifier::GetInstance().NotifyTelemetryUpdated();
|
||||
m_notifier.NotifyTelemetryUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +120,7 @@ double Telemetry::GetAverageValue(CS_Handle handle, CS_TelemetryKind kind,
|
||||
void Telemetry::RecordSourceBytes(const SourceImpl& source, int quantity) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
auto handleData = Sources::GetInstance().Find(source);
|
||||
auto handleData = Instance::GetInstance().FindSource(source);
|
||||
thr->m_current[std::make_pair(Handle{handleData.first, Handle::kSource},
|
||||
static_cast<int>(CS_SOURCE_BYTES_RECEIVED))] +=
|
||||
quantity;
|
||||
@@ -126,7 +129,7 @@ void Telemetry::RecordSourceBytes(const SourceImpl& source, int quantity) {
|
||||
void Telemetry::RecordSourceFrames(const SourceImpl& source, int quantity) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
auto handleData = Sources::GetInstance().Find(source);
|
||||
auto handleData = Instance::GetInstance().FindSource(source);
|
||||
thr->m_current[std::make_pair(Handle{handleData.first, Handle::kSource},
|
||||
static_cast<int>(CS_SOURCE_FRAMES_RECEIVED))] +=
|
||||
quantity;
|
||||
|
||||
@@ -14,16 +14,14 @@
|
||||
|
||||
namespace cs {
|
||||
|
||||
class Notifier;
|
||||
class SourceImpl;
|
||||
|
||||
class Telemetry {
|
||||
friend class TelemetryTest;
|
||||
|
||||
public:
|
||||
static Telemetry& GetInstance() {
|
||||
static Telemetry instance;
|
||||
return instance;
|
||||
}
|
||||
explicit Telemetry(Notifier& notifier) : m_notifier(notifier) {}
|
||||
~Telemetry();
|
||||
|
||||
void Start();
|
||||
@@ -41,7 +39,7 @@ class Telemetry {
|
||||
void RecordSourceFrames(const SourceImpl& source, int quantity);
|
||||
|
||||
private:
|
||||
Telemetry();
|
||||
Notifier& m_notifier;
|
||||
|
||||
class Thread;
|
||||
wpi::SafeThreadOwner<Thread> m_owner;
|
||||
|
||||
@@ -50,11 +50,13 @@ class UnlimitedHandleResource {
|
||||
|
||||
std::shared_ptr<TStruct> Get(THandle handle);
|
||||
|
||||
void Free(THandle handle);
|
||||
std::shared_ptr<TStruct> Free(THandle handle);
|
||||
|
||||
template <typename T>
|
||||
wpi::ArrayRef<T> GetAll(wpi::SmallVectorImpl<T>& vec);
|
||||
|
||||
std::vector<std::shared_ptr<TStruct>> FreeAll();
|
||||
|
||||
// @param func functor with (THandle, const TStruct&) parameters
|
||||
template <typename F>
|
||||
void ForEach(F func);
|
||||
@@ -121,14 +123,17 @@ UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Get(
|
||||
}
|
||||
|
||||
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
|
||||
inline void UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Free(
|
||||
inline std::shared_ptr<TStruct>
|
||||
UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Free(
|
||||
THandle handle) {
|
||||
auto index =
|
||||
handle.GetTypedIndex(static_cast<typename THandle::Type>(typeValue));
|
||||
if (index < 0) return;
|
||||
if (index < 0) return nullptr;
|
||||
std::lock_guard<TMutex> sync(m_handleMutex);
|
||||
if (index >= static_cast<int>(m_structures.size())) return;
|
||||
if (index >= static_cast<int>(m_structures.size())) return nullptr;
|
||||
auto rv = std::move(m_structures[index]);
|
||||
m_structures[index].reset();
|
||||
return rv;
|
||||
}
|
||||
|
||||
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
|
||||
@@ -140,6 +145,15 @@ UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::GetAll(
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
|
||||
inline std::vector<std::shared_ptr<TStruct>>
|
||||
UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::FreeAll() {
|
||||
std::lock_guard<TMutex> sync(m_handleMutex);
|
||||
auto rv = std::move(m_structures);
|
||||
m_structures.clear();
|
||||
return rv;
|
||||
}
|
||||
|
||||
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
|
||||
template <typename F>
|
||||
inline void
|
||||
@@ -163,20 +177,6 @@ UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::FindIf(F func) {
|
||||
return std::make_pair(0, nullptr);
|
||||
}
|
||||
|
||||
template <typename THandle, typename TStruct, int typeValue,
|
||||
typename TMutex = wpi::mutex>
|
||||
class StaticUnlimitedHandleResource
|
||||
: public UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex> {
|
||||
public:
|
||||
static StaticUnlimitedHandleResource& GetInstance() {
|
||||
static StaticUnlimitedHandleResource instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
private:
|
||||
StaticUnlimitedHandleResource() = default;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_UNLIMITEDHANDLERESOURCE_H_
|
||||
|
||||
79
cscore/src/main/native/cpp/UsbCameraImplCommon.cpp
Normal file
79
cscore/src/main/native/cpp/UsbCameraImplCommon.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "cscore_c.h" // NOLINT(build/include_order)
|
||||
|
||||
#include "c_util.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
static void ConvertToC(CS_UsbCameraInfo* out, const UsbCameraInfo& in) {
|
||||
out->dev = in.dev;
|
||||
out->path = ConvertToC(in.path);
|
||||
out->name = ConvertToC(in.name);
|
||||
out->otherPaths = static_cast<char**>(
|
||||
wpi::CheckedMalloc(in.otherPaths.size() * sizeof(char*)));
|
||||
out->otherPathsCount = in.otherPaths.size();
|
||||
for (size_t i = 0; i < in.otherPaths.size(); ++i)
|
||||
out->otherPaths[i] = cs::ConvertToC(in.otherPaths[i]);
|
||||
}
|
||||
|
||||
static void FreeUsbCameraInfo(CS_UsbCameraInfo* info) {
|
||||
std::free(info->path);
|
||||
std::free(info->name);
|
||||
for (int i = 0; i < info->otherPathsCount; ++i)
|
||||
std::free(info->otherPaths[i]);
|
||||
std::free(info->otherPaths);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
CS_Source CS_CreateUsbCameraDev(const char* name, int dev, CS_Status* status) {
|
||||
return cs::CreateUsbCameraDev(name, dev, status);
|
||||
}
|
||||
|
||||
CS_Source CS_CreateUsbCameraPath(const char* name, const char* path,
|
||||
CS_Status* status) {
|
||||
return cs::CreateUsbCameraPath(name, path, status);
|
||||
}
|
||||
|
||||
char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status) {
|
||||
return ConvertToC(cs::GetUsbCameraPath(source, status));
|
||||
}
|
||||
|
||||
CS_UsbCameraInfo* CS_GetUsbCameraInfo(CS_Source source, CS_Status* status) {
|
||||
auto info = cs::GetUsbCameraInfo(source, status);
|
||||
if (*status != CS_OK) return nullptr;
|
||||
CS_UsbCameraInfo* out = static_cast<CS_UsbCameraInfo*>(
|
||||
wpi::CheckedMalloc(sizeof(CS_UsbCameraInfo)));
|
||||
ConvertToC(out, info);
|
||||
return out;
|
||||
}
|
||||
|
||||
CS_UsbCameraInfo* CS_EnumerateUsbCameras(int* count, CS_Status* status) {
|
||||
auto cameras = cs::EnumerateUsbCameras(status);
|
||||
CS_UsbCameraInfo* out = static_cast<CS_UsbCameraInfo*>(
|
||||
wpi::CheckedMalloc(cameras.size() * sizeof(CS_UsbCameraInfo)));
|
||||
*count = cameras.size();
|
||||
for (size_t i = 0; i < cameras.size(); ++i) ConvertToC(&out[i], cameras[i]);
|
||||
return out;
|
||||
}
|
||||
|
||||
void CS_FreeEnumeratedUsbCameras(CS_UsbCameraInfo* cameras, int count) {
|
||||
if (!cameras) return;
|
||||
for (int i = 0; i < count; ++i) FreeUsbCameraInfo(&cameras[i]);
|
||||
std::free(cameras);
|
||||
}
|
||||
|
||||
void CS_FreeUsbCameraInfo(CS_UsbCameraInfo* info) {
|
||||
if (!info) return;
|
||||
FreeUsbCameraInfo(info);
|
||||
std::free(info);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -170,6 +170,15 @@ CS_Bool CS_SetSourceFPS(CS_Source source, int fps, CS_Status* status) {
|
||||
return cs::SetSourceFPS(source, fps, status);
|
||||
}
|
||||
|
||||
CS_Bool CS_SetSourceConfigJson(CS_Source source, const char* config,
|
||||
CS_Status* status) {
|
||||
return cs::SetSourceConfigJson(source, config, status);
|
||||
}
|
||||
|
||||
char* CS_GetSourceConfigJson(CS_Source source, CS_Status* status) {
|
||||
return cs::ConvertToC(cs::GetSourceConfigJson(source, status));
|
||||
}
|
||||
|
||||
CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count,
|
||||
CS_Status* status) {
|
||||
auto vec = cs::EnumerateSourceVideoModes(source, status);
|
||||
@@ -268,6 +277,15 @@ CS_Property* CS_EnumerateSinkProperties(CS_Sink sink, int* count,
|
||||
return out;
|
||||
}
|
||||
|
||||
CS_Bool CS_SetSinkConfigJson(CS_Sink sink, const char* config,
|
||||
CS_Status* status) {
|
||||
return cs::SetSinkConfigJson(sink, config, status);
|
||||
}
|
||||
|
||||
char* CS_GetSinkConfigJson(CS_Sink sink, CS_Status* status) {
|
||||
return cs::ConvertToC(cs::GetSinkConfigJson(sink, status));
|
||||
}
|
||||
|
||||
void CS_SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) {
|
||||
return cs::SetSinkSource(sink, source, status);
|
||||
}
|
||||
@@ -348,6 +366,8 @@ void CS_SetDefaultLogger(unsigned int min_level) {
|
||||
cs::SetDefaultLogger(min_level);
|
||||
}
|
||||
|
||||
void CS_Shutdown(void) { cs::Shutdown(); }
|
||||
|
||||
CS_Source* CS_EnumerateSources(int* count, CS_Status* status) {
|
||||
wpi::SmallVector<CS_Source, 32> buf;
|
||||
auto handles = cs::EnumerateSourceHandles(buf, status);
|
||||
|
||||
@@ -7,16 +7,12 @@
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <arpa/inet.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/hostname.h>
|
||||
#include <wpi/json.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Instance.h"
|
||||
#include "Log.h"
|
||||
#include "NetworkListener.h"
|
||||
#include "Notifier.h"
|
||||
@@ -33,7 +29,7 @@ static std::shared_ptr<PropertyContainer> GetPropertyContainer(
|
||||
Handle handle{propertyHandle};
|
||||
if (handle.IsType(Handle::kProperty)) {
|
||||
int i = handle.GetParentIndex();
|
||||
auto data = Sources::GetInstance().Get(Handle{i, Handle::kSource});
|
||||
auto data = Instance::GetInstance().GetSource(Handle{i, Handle::kSource});
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return nullptr;
|
||||
@@ -41,7 +37,7 @@ static std::shared_ptr<PropertyContainer> GetPropertyContainer(
|
||||
container = data->source;
|
||||
} else if (handle.IsType(Handle::kSinkProperty)) {
|
||||
int i = handle.GetParentIndex();
|
||||
auto data = Sinks::GetInstance().Get(Handle{i, Handle::kSink});
|
||||
auto data = Instance::GetInstance().GetSink(Handle{i, Handle::kSink});
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return nullptr;
|
||||
@@ -165,7 +161,7 @@ std::vector<std::string> GetEnumPropertyChoices(CS_Property property,
|
||||
//
|
||||
|
||||
CS_SourceKind GetSourceKind(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return CS_SOURCE_UNKNOWN;
|
||||
@@ -174,7 +170,7 @@ CS_SourceKind GetSourceKind(CS_Source source, CS_Status* status) {
|
||||
}
|
||||
|
||||
std::string GetSourceName(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
@@ -184,7 +180,7 @@ std::string GetSourceName(CS_Source source, CS_Status* status) {
|
||||
|
||||
wpi::StringRef GetSourceName(CS_Source source, wpi::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return wpi::StringRef{};
|
||||
@@ -193,7 +189,7 @@ wpi::StringRef GetSourceName(CS_Source source, wpi::SmallVectorImpl<char>& buf,
|
||||
}
|
||||
|
||||
std::string GetSourceDescription(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
@@ -205,7 +201,7 @@ std::string GetSourceDescription(CS_Source source, CS_Status* status) {
|
||||
wpi::StringRef GetSourceDescription(CS_Source source,
|
||||
wpi::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return wpi::StringRef{};
|
||||
@@ -214,7 +210,7 @@ wpi::StringRef GetSourceDescription(CS_Source source,
|
||||
}
|
||||
|
||||
uint64_t GetSourceLastFrameTime(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
@@ -225,7 +221,7 @@ uint64_t GetSourceLastFrameTime(CS_Source source, CS_Status* status) {
|
||||
void SetSourceConnectionStrategy(CS_Source source,
|
||||
CS_ConnectionStrategy strategy,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
@@ -234,7 +230,7 @@ void SetSourceConnectionStrategy(CS_Source source,
|
||||
}
|
||||
|
||||
bool IsSourceConnected(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
@@ -243,7 +239,7 @@ bool IsSourceConnected(CS_Source source, CS_Status* status) {
|
||||
}
|
||||
|
||||
bool IsSourceEnabled(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
@@ -253,7 +249,7 @@ bool IsSourceEnabled(CS_Source source, CS_Status* status) {
|
||||
|
||||
CS_Property GetSourceProperty(CS_Source source, const wpi::Twine& name,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
@@ -269,7 +265,7 @@ CS_Property GetSourceProperty(CS_Source source, const wpi::Twine& name,
|
||||
wpi::ArrayRef<CS_Property> EnumerateSourceProperties(
|
||||
CS_Source source, wpi::SmallVectorImpl<CS_Property>& vec,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
@@ -282,7 +278,7 @@ wpi::ArrayRef<CS_Property> EnumerateSourceProperties(
|
||||
}
|
||||
|
||||
VideoMode GetSourceVideoMode(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return VideoMode{};
|
||||
@@ -292,7 +288,7 @@ VideoMode GetSourceVideoMode(CS_Source source, CS_Status* status) {
|
||||
|
||||
bool SetSourceVideoMode(CS_Source source, const VideoMode& mode,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
@@ -302,7 +298,7 @@ bool SetSourceVideoMode(CS_Source source, const VideoMode& mode,
|
||||
|
||||
bool SetSourcePixelFormat(CS_Source source, VideoMode::PixelFormat pixelFormat,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
@@ -312,7 +308,7 @@ bool SetSourcePixelFormat(CS_Source source, VideoMode::PixelFormat pixelFormat,
|
||||
|
||||
bool SetSourceResolution(CS_Source source, int width, int height,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
@@ -321,7 +317,7 @@ bool SetSourceResolution(CS_Source source, int width, int height,
|
||||
}
|
||||
|
||||
bool SetSourceFPS(CS_Source source, int fps, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
@@ -329,9 +325,47 @@ bool SetSourceFPS(CS_Source source, int fps, CS_Status* status) {
|
||||
return data->source->SetFPS(fps, status);
|
||||
}
|
||||
|
||||
bool SetSourceConfigJson(CS_Source source, wpi::StringRef config,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
}
|
||||
return data->source->SetConfigJson(config, status);
|
||||
}
|
||||
|
||||
bool SetSourceConfigJson(CS_Source source, const wpi::json& config,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
}
|
||||
return data->source->SetConfigJson(config, status);
|
||||
}
|
||||
|
||||
std::string GetSourceConfigJson(CS_Source source, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
}
|
||||
return data->source->GetConfigJson(status);
|
||||
}
|
||||
|
||||
wpi::json GetSourceConfigJsonObject(CS_Source source, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return wpi::json{};
|
||||
}
|
||||
return data->source->GetConfigJsonObject(status);
|
||||
}
|
||||
|
||||
std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::vector<VideoMode>{};
|
||||
@@ -342,21 +376,18 @@ std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
|
||||
wpi::ArrayRef<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto& inst = Instance::GetInstance();
|
||||
auto data = inst.GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return wpi::ArrayRef<CS_Sink>{};
|
||||
}
|
||||
vec.clear();
|
||||
Sinks::GetInstance().ForEach([&](CS_Sink sinkHandle, const SinkData& data) {
|
||||
if (source == data.sourceHandle.load()) vec.push_back(sinkHandle);
|
||||
});
|
||||
return vec;
|
||||
return inst.EnumerateSourceSinks(source, vec);
|
||||
}
|
||||
|
||||
CS_Source CopySource(CS_Source source, CS_Status* status) {
|
||||
if (source == 0) return 0;
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
@@ -367,17 +398,13 @@ CS_Source CopySource(CS_Source source, CS_Status* status) {
|
||||
|
||||
void ReleaseSource(CS_Source source, CS_Status* status) {
|
||||
if (source == 0) return;
|
||||
auto& inst = Sources::GetInstance();
|
||||
auto data = inst.Get(source);
|
||||
auto& inst = Instance::GetInstance();
|
||||
auto data = inst.GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
if (data->refCount-- == 0) {
|
||||
Notifier::GetInstance().NotifySource(data->source->GetName(), source,
|
||||
CS_SOURCE_DESTROYED);
|
||||
inst.Free(source);
|
||||
}
|
||||
if (data->refCount-- == 0) inst.DestroySource(source);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -385,7 +412,7 @@ void ReleaseSource(CS_Source source, CS_Status* status) {
|
||||
//
|
||||
|
||||
void SetCameraBrightness(CS_Source source, int brightness, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
@@ -394,7 +421,7 @@ void SetCameraBrightness(CS_Source source, int brightness, CS_Status* status) {
|
||||
}
|
||||
|
||||
int GetCameraBrightness(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
@@ -403,7 +430,7 @@ int GetCameraBrightness(CS_Source source, CS_Status* status) {
|
||||
}
|
||||
|
||||
void SetCameraWhiteBalanceAuto(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
@@ -412,7 +439,7 @@ void SetCameraWhiteBalanceAuto(CS_Source source, CS_Status* status) {
|
||||
}
|
||||
|
||||
void SetCameraWhiteBalanceHoldCurrent(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
@@ -422,7 +449,7 @@ void SetCameraWhiteBalanceHoldCurrent(CS_Source source, CS_Status* status) {
|
||||
|
||||
void SetCameraWhiteBalanceManual(CS_Source source, int value,
|
||||
CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
@@ -431,7 +458,7 @@ void SetCameraWhiteBalanceManual(CS_Source source, int value,
|
||||
}
|
||||
|
||||
void SetCameraExposureAuto(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
@@ -440,7 +467,7 @@ void SetCameraExposureAuto(CS_Source source, CS_Status* status) {
|
||||
}
|
||||
|
||||
void SetCameraExposureHoldCurrent(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
@@ -449,7 +476,7 @@ void SetCameraExposureHoldCurrent(CS_Source source, CS_Status* status) {
|
||||
}
|
||||
|
||||
void SetCameraExposureManual(CS_Source source, int value, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
@@ -462,7 +489,7 @@ void SetCameraExposureManual(CS_Source source, int value, CS_Status* status) {
|
||||
//
|
||||
|
||||
CS_SinkKind GetSinkKind(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return CS_SINK_UNKNOWN;
|
||||
@@ -471,7 +498,7 @@ CS_SinkKind GetSinkKind(CS_Sink sink, CS_Status* status) {
|
||||
}
|
||||
|
||||
std::string GetSinkName(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
@@ -481,7 +508,7 @@ std::string GetSinkName(CS_Sink sink, CS_Status* status) {
|
||||
|
||||
wpi::StringRef GetSinkName(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return wpi::StringRef{};
|
||||
@@ -490,7 +517,7 @@ wpi::StringRef GetSinkName(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
|
||||
}
|
||||
|
||||
std::string GetSinkDescription(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
@@ -501,7 +528,7 @@ std::string GetSinkDescription(CS_Sink sink, CS_Status* status) {
|
||||
|
||||
wpi::StringRef GetSinkDescription(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
|
||||
CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return wpi::StringRef{};
|
||||
@@ -511,7 +538,7 @@ wpi::StringRef GetSinkDescription(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
|
||||
|
||||
CS_Property GetSinkProperty(CS_Sink sink, const wpi::Twine& name,
|
||||
CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
@@ -526,7 +553,7 @@ CS_Property GetSinkProperty(CS_Sink sink, const wpi::Twine& name,
|
||||
|
||||
wpi::ArrayRef<CS_Property> EnumerateSinkProperties(
|
||||
CS_Sink sink, wpi::SmallVectorImpl<CS_Property>& vec, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
@@ -537,8 +564,45 @@ wpi::ArrayRef<CS_Property> EnumerateSinkProperties(
|
||||
return vec;
|
||||
}
|
||||
|
||||
bool SetSinkConfigJson(CS_Sink sink, wpi::StringRef config, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
}
|
||||
return data->sink->SetConfigJson(config, status);
|
||||
}
|
||||
|
||||
bool SetSinkConfigJson(CS_Sink sink, const wpi::json& config,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return false;
|
||||
}
|
||||
return data->sink->SetConfigJson(config, status);
|
||||
}
|
||||
|
||||
std::string GetSinkConfigJson(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
}
|
||||
return data->sink->GetConfigJson(status);
|
||||
}
|
||||
|
||||
wpi::json GetSinkConfigJsonObject(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return wpi::json{};
|
||||
}
|
||||
return data->sink->GetConfigJsonObject(status);
|
||||
}
|
||||
|
||||
void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
@@ -546,7 +610,7 @@ void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) {
|
||||
if (source == 0) {
|
||||
data->sink->SetSource(nullptr);
|
||||
} else {
|
||||
auto sourceData = Sources::GetInstance().Get(source);
|
||||
auto sourceData = Instance::GetInstance().GetSource(source);
|
||||
if (!sourceData) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
@@ -554,12 +618,12 @@ void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) {
|
||||
data->sink->SetSource(sourceData->source);
|
||||
}
|
||||
data->sourceHandle.store(source);
|
||||
Notifier::GetInstance().NotifySinkSourceChanged(data->sink->GetName(), sink,
|
||||
source);
|
||||
Instance::GetInstance().notifier.NotifySinkSourceChanged(
|
||||
data->sink->GetName(), sink, source);
|
||||
}
|
||||
|
||||
CS_Source GetSinkSource(CS_Sink sink, CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
@@ -569,7 +633,7 @@ CS_Source GetSinkSource(CS_Sink sink, CS_Status* status) {
|
||||
|
||||
CS_Property GetSinkSourceProperty(CS_Sink sink, const wpi::Twine& name,
|
||||
CS_Status* status) {
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
@@ -579,7 +643,7 @@ CS_Property GetSinkSourceProperty(CS_Sink sink, const wpi::Twine& name,
|
||||
|
||||
CS_Sink CopySink(CS_Sink sink, CS_Status* status) {
|
||||
if (sink == 0) return 0;
|
||||
auto data = Sinks::GetInstance().Get(sink);
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
@@ -590,17 +654,13 @@ CS_Sink CopySink(CS_Sink sink, CS_Status* status) {
|
||||
|
||||
void ReleaseSink(CS_Sink sink, CS_Status* status) {
|
||||
if (sink == 0) return;
|
||||
auto& inst = Sinks::GetInstance();
|
||||
auto data = inst.Get(sink);
|
||||
auto& inst = Instance::GetInstance();
|
||||
auto data = inst.GetSink(sink);
|
||||
if (!data) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
if (data->refCount-- == 0) {
|
||||
Notifier::GetInstance().NotifySink(data->sink->GetName(), sink,
|
||||
CS_SINK_DESTROYED);
|
||||
inst.Free(sink);
|
||||
}
|
||||
if (data->refCount-- == 0) inst.DestroySink(sink);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -608,22 +668,22 @@ void ReleaseSink(CS_Sink sink, CS_Status* status) {
|
||||
//
|
||||
|
||||
void SetListenerOnStart(std::function<void()> onStart) {
|
||||
Notifier::GetInstance().SetOnStart(onStart);
|
||||
Instance::GetInstance().notifier.SetOnStart(onStart);
|
||||
}
|
||||
|
||||
void SetListenerOnExit(std::function<void()> onExit) {
|
||||
Notifier::GetInstance().SetOnExit(onExit);
|
||||
Instance::GetInstance().notifier.SetOnExit(onExit);
|
||||
}
|
||||
|
||||
CS_Listener AddListener(std::function<void(const RawEvent& event)> callback,
|
||||
int eventMask, bool immediateNotify,
|
||||
CS_Status* status) {
|
||||
int uid = Notifier::GetInstance().AddListener(callback, eventMask);
|
||||
auto& inst = Instance::GetInstance();
|
||||
int uid = inst.notifier.AddListener(callback, eventMask);
|
||||
if ((eventMask & CS_NETWORK_INTERFACES_CHANGED) != 0) {
|
||||
// start network interface event listener
|
||||
NetworkListener::GetInstance().Start();
|
||||
if (immediateNotify)
|
||||
Notifier::GetInstance().NotifyNetworkInterfacesChanged();
|
||||
inst.networkListener.Start();
|
||||
if (immediateNotify) inst.notifier.NotifyNetworkInterfacesChanged();
|
||||
}
|
||||
if (immediateNotify) {
|
||||
// TODO
|
||||
@@ -637,7 +697,7 @@ void RemoveListener(CS_Listener handle, CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
Notifier::GetInstance().RemoveListener(uid);
|
||||
Instance::GetInstance().notifier.RemoveListener(uid);
|
||||
}
|
||||
|
||||
bool NotifierDestroyed() { return Notifier::destroyed(); }
|
||||
@@ -646,86 +706,60 @@ bool NotifierDestroyed() { return Notifier::destroyed(); }
|
||||
// Telemetry Functions
|
||||
//
|
||||
void SetTelemetryPeriod(double seconds) {
|
||||
Telemetry::GetInstance().Start();
|
||||
Telemetry::GetInstance().SetPeriod(seconds);
|
||||
auto& inst = Instance::GetInstance();
|
||||
inst.telemetry.Start();
|
||||
inst.telemetry.SetPeriod(seconds);
|
||||
}
|
||||
|
||||
double GetTelemetryElapsedTime() {
|
||||
return Telemetry::GetInstance().GetElapsedTime();
|
||||
return Instance::GetInstance().telemetry.GetElapsedTime();
|
||||
}
|
||||
|
||||
int64_t GetTelemetryValue(CS_Handle handle, CS_TelemetryKind kind,
|
||||
CS_Status* status) {
|
||||
return Telemetry::GetInstance().GetValue(handle, kind, status);
|
||||
return Instance::GetInstance().telemetry.GetValue(handle, kind, status);
|
||||
}
|
||||
|
||||
double GetTelemetryAverageValue(CS_Handle handle, CS_TelemetryKind kind,
|
||||
CS_Status* status) {
|
||||
return Telemetry::GetInstance().GetAverageValue(handle, kind, status);
|
||||
return Instance::GetInstance().telemetry.GetAverageValue(handle, kind,
|
||||
status);
|
||||
}
|
||||
|
||||
//
|
||||
// Logging Functions
|
||||
//
|
||||
void SetLogger(LogFunc func, unsigned int min_level) {
|
||||
Logger& logger = Logger::GetInstance();
|
||||
auto& logger = Instance::GetInstance().logger;
|
||||
logger.SetLogger(func);
|
||||
logger.set_min_level(min_level);
|
||||
}
|
||||
|
||||
void SetDefaultLogger(unsigned int min_level) {
|
||||
Logger& logger = Logger::GetInstance();
|
||||
logger.SetDefaultLogger();
|
||||
logger.set_min_level(min_level);
|
||||
auto& inst = Instance::GetInstance();
|
||||
inst.SetDefaultLogger();
|
||||
inst.logger.set_min_level(min_level);
|
||||
}
|
||||
|
||||
//
|
||||
// Shutdown Function
|
||||
//
|
||||
void Shutdown() { Instance::GetInstance().Shutdown(); }
|
||||
|
||||
//
|
||||
// Utility Functions
|
||||
//
|
||||
|
||||
wpi::ArrayRef<CS_Source> EnumerateSourceHandles(
|
||||
wpi::SmallVectorImpl<CS_Source>& vec, CS_Status* status) {
|
||||
return Sources::GetInstance().GetAll(vec);
|
||||
return Instance::GetInstance().EnumerateSourceHandles(vec);
|
||||
}
|
||||
|
||||
wpi::ArrayRef<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
CS_Status* status) {
|
||||
return Sinks::GetInstance().GetAll(vec);
|
||||
return Instance::GetInstance().EnumerateSinkHandles(vec);
|
||||
}
|
||||
|
||||
std::string GetHostname() {
|
||||
#ifdef __linux__
|
||||
char name[256];
|
||||
if (::gethostname(name, sizeof(name)) != 0) return "";
|
||||
name[255] = '\0'; // Per POSIX, may not be null terminated if too long
|
||||
return name;
|
||||
#else
|
||||
return ""; // TODO
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<std::string> GetNetworkInterfaces() {
|
||||
#ifdef __linux__
|
||||
struct ifaddrs* ifa;
|
||||
if (::getifaddrs(&ifa) != 0) return std::vector<std::string>{};
|
||||
|
||||
std::vector<std::string> rv;
|
||||
char buf[256];
|
||||
for (struct ifaddrs* i = ifa; i; i = i->ifa_next) {
|
||||
if (!i->ifa_addr) continue; // no address
|
||||
if (i->ifa_addr->sa_family != AF_INET) continue; // only return IPv4
|
||||
struct sockaddr_in* addr_in = reinterpret_cast<sockaddr_in*>(i->ifa_addr);
|
||||
const char* addr =
|
||||
::inet_ntop(addr_in->sin_family, &addr_in->sin_addr, buf, sizeof(buf));
|
||||
if (!addr) continue; // error converting address
|
||||
rv.emplace_back(addr);
|
||||
}
|
||||
|
||||
::freeifaddrs(ifa);
|
||||
return rv;
|
||||
#else
|
||||
return std::vector<std::string>{}; // TODO
|
||||
#endif
|
||||
}
|
||||
std::string GetHostname() { return wpi::GetHostname(); }
|
||||
|
||||
} // namespace cs
|
||||
|
||||
@@ -7,8 +7,20 @@
|
||||
|
||||
#include "cscore_oo.h"
|
||||
|
||||
#include <wpi/json.h>
|
||||
|
||||
using namespace cs;
|
||||
|
||||
wpi::json VideoSource::GetConfigJsonObject() const {
|
||||
m_status = 0;
|
||||
return GetSourceConfigJsonObject(m_handle, &m_status);
|
||||
}
|
||||
|
||||
wpi::json VideoSink::GetConfigJsonObject() const {
|
||||
m_status = 0;
|
||||
return GetSinkConfigJsonObject(m_handle, &m_status);
|
||||
}
|
||||
|
||||
std::vector<VideoProperty> VideoSource::EnumerateProperties() const {
|
||||
wpi::SmallVector<CS_Property, 32> handles_buf;
|
||||
CS_Status status = 0;
|
||||
|
||||
@@ -87,6 +87,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
|
||||
cs::Shutdown();
|
||||
|
||||
JNIEnv* env;
|
||||
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
|
||||
return;
|
||||
@@ -181,16 +183,17 @@ static inline bool CheckStatus(JNIEnv* env, CS_Status status) {
|
||||
return status == CS_OK;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
static jobject MakeJObject(JNIEnv* env, const cs::UsbCameraInfo& info) {
|
||||
static jmethodID constructor = env->GetMethodID(
|
||||
usbCameraInfoCls, "<init>", "(ILjava/lang/String;Ljava/lang/String;)V");
|
||||
usbCameraInfoCls, "<init>",
|
||||
"(ILjava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V");
|
||||
JLocal<jstring> path(env, MakeJString(env, info.path));
|
||||
JLocal<jstring> name(env, MakeJString(env, info.name));
|
||||
JLocal<jobjectArray> otherPaths(env, MakeJStringArray(env, info.otherPaths));
|
||||
return env->NewObject(usbCameraInfoCls, constructor,
|
||||
static_cast<jint>(info.dev), path.obj(), name.obj());
|
||||
static_cast<jint>(info.dev), path.obj(), name.obj(),
|
||||
otherPaths.obj());
|
||||
}
|
||||
#endif
|
||||
|
||||
static jobject MakeJObject(JNIEnv* env, const cs::VideoMode& videoMode) {
|
||||
static jmethodID constructor =
|
||||
@@ -406,10 +409,6 @@ JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_createUsbCameraDev
|
||||
(JNIEnv* env, jclass, jstring name, jint dev)
|
||||
{
|
||||
#ifndef __linux__
|
||||
unsupportedEx.Throw(env, "USB is not supported yet");
|
||||
return 0;
|
||||
#else
|
||||
if (!name) {
|
||||
nullPointerEx.Throw(env, "name cannot be null");
|
||||
return 0;
|
||||
@@ -418,7 +417,6 @@ Java_edu_wpi_cscore_CameraServerJNI_createUsbCameraDev
|
||||
auto val = cs::CreateUsbCameraDev(JStringRef{env, name}.str(), dev, &status);
|
||||
CheckStatus(env, status);
|
||||
return val;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -430,10 +428,6 @@ JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_createUsbCameraPath
|
||||
(JNIEnv* env, jclass, jstring name, jstring path)
|
||||
{
|
||||
#ifndef __linux__
|
||||
unsupportedEx.Throw(env, "USB is not supported yet");
|
||||
return 0;
|
||||
#else
|
||||
if (!name) {
|
||||
nullPointerEx.Throw(env, "name cannot be null");
|
||||
return 0;
|
||||
@@ -447,7 +441,6 @@ Java_edu_wpi_cscore_CameraServerJNI_createUsbCameraPath
|
||||
JStringRef{env, path}.str(), &status);
|
||||
CheckStatus(env, status);
|
||||
return val;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -761,6 +754,36 @@ Java_edu_wpi_cscore_CameraServerJNI_setSourceFPS
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: setSourceConfigJson
|
||||
* Signature: (ILjava/lang/String;)Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_setSourceConfigJson
|
||||
(JNIEnv* env, jclass, jint source, jstring config)
|
||||
{
|
||||
CS_Status status = 0;
|
||||
auto val = cs::SetSourceConfigJson(source, JStringRef{env, config}, &status);
|
||||
CheckStatus(env, status);
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: getSourceConfigJson
|
||||
* Signature: (I)Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_getSourceConfigJson
|
||||
(JNIEnv* env, jclass, jint source)
|
||||
{
|
||||
CS_Status status = 0;
|
||||
auto val = cs::GetSourceConfigJson(source, &status);
|
||||
CheckStatus(env, status);
|
||||
return MakeJString(env, val);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: enumerateSourceVideoModes
|
||||
@@ -949,15 +972,25 @@ JNIEXPORT jstring JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_getUsbCameraPath
|
||||
(JNIEnv* env, jclass, jint source)
|
||||
{
|
||||
#ifndef __linux__
|
||||
unsupportedEx.Throw(env, "USB is not supported yet");
|
||||
return 0;
|
||||
#else
|
||||
CS_Status status = 0;
|
||||
auto str = cs::GetUsbCameraPath(source, &status);
|
||||
if (!CheckStatus(env, status)) return nullptr;
|
||||
return MakeJString(env, str);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: getUsbCameraInfo
|
||||
* Signature: (I)Ljava/lang/Object;
|
||||
*/
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_getUsbCameraInfo
|
||||
(JNIEnv* env, jclass, jint source)
|
||||
{
|
||||
CS_Status status = 0;
|
||||
auto info = cs::GetUsbCameraInfo(source, &status);
|
||||
if (!CheckStatus(env, status)) return nullptr;
|
||||
return MakeJObject(env, info);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1259,6 +1292,36 @@ Java_edu_wpi_cscore_CameraServerJNI_enumerateSinkProperties
|
||||
return MakeJIntArray(env, arr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: setSinkConfigJson
|
||||
* Signature: (ILjava/lang/String;)Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_setSinkConfigJson
|
||||
(JNIEnv* env, jclass, jint source, jstring config)
|
||||
{
|
||||
CS_Status status = 0;
|
||||
auto val = cs::SetSinkConfigJson(source, JStringRef{env, config}, &status);
|
||||
CheckStatus(env, status);
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: getSinkConfigJson
|
||||
* Signature: (I)Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_getSinkConfigJson
|
||||
(JNIEnv* env, jclass, jint source)
|
||||
{
|
||||
CS_Status status = 0;
|
||||
auto val = cs::GetSinkConfigJson(source, &status);
|
||||
CheckStatus(env, status);
|
||||
return MakeJString(env, val);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: setSinkSource
|
||||
@@ -1582,10 +1645,6 @@ JNIEXPORT jobjectArray JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_enumerateUsbCameras
|
||||
(JNIEnv* env, jclass)
|
||||
{
|
||||
#ifndef __linux__
|
||||
unsupportedEx.Throw(env, "USB is not supported yet");
|
||||
return 0;
|
||||
#else
|
||||
CS_Status status = 0;
|
||||
auto arr = cs::EnumerateUsbCameras(&status);
|
||||
if (!CheckStatus(env, status)) return nullptr;
|
||||
@@ -1597,7 +1656,6 @@ Java_edu_wpi_cscore_CameraServerJNI_enumerateUsbCameras
|
||||
env->SetObjectArrayElement(jarr, i, jelem);
|
||||
}
|
||||
return jarr;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -69,7 +69,8 @@ enum CS_StatusValue {
|
||||
CS_SOURCE_IS_DISCONNECTED = -2005,
|
||||
CS_EMPTY_VALUE = -2006,
|
||||
CS_BAD_URL = -2007,
|
||||
CS_TELEMETRY_NOT_ENABLED = -2008
|
||||
CS_TELEMETRY_NOT_ENABLED = -2008,
|
||||
CS_UNSUPPORTED_MODE = -2009
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -222,6 +223,17 @@ struct CS_Event {
|
||||
const char* valueStr;
|
||||
};
|
||||
|
||||
/**
|
||||
* USB camera infomation
|
||||
*/
|
||||
typedef struct CS_UsbCameraInfo {
|
||||
int dev;
|
||||
char* path;
|
||||
char* name;
|
||||
int otherPathsCount;
|
||||
char** otherPaths;
|
||||
} CS_UsbCameraInfo;
|
||||
|
||||
/**
|
||||
* @defgroup cscore_property_cfunc Property Functions
|
||||
* @{
|
||||
@@ -289,6 +301,9 @@ CS_Bool CS_SetSourcePixelFormat(CS_Source source,
|
||||
CS_Bool CS_SetSourceResolution(CS_Source source, int width, int height,
|
||||
CS_Status* status);
|
||||
CS_Bool CS_SetSourceFPS(CS_Source source, int fps, CS_Status* status);
|
||||
CS_Bool CS_SetSourceConfigJson(CS_Source source, const char* config,
|
||||
CS_Status* status);
|
||||
char* CS_GetSourceConfigJson(CS_Source source, CS_Status* status);
|
||||
CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count,
|
||||
CS_Status* status);
|
||||
CS_Sink* CS_EnumerateSourceSinks(CS_Source source, int* count,
|
||||
@@ -318,6 +333,7 @@ void CS_SetCameraExposureManual(CS_Source source, int value, CS_Status* status);
|
||||
* @{
|
||||
*/
|
||||
char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status);
|
||||
CS_UsbCameraInfo* CS_GetUsbCameraInfo(CS_Source source, CS_Status* status);
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
@@ -377,6 +393,9 @@ CS_Property* CS_EnumerateSinkProperties(CS_Sink sink, int* count,
|
||||
void CS_SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status);
|
||||
CS_Property CS_GetSinkSourceProperty(CS_Sink sink, const char* name,
|
||||
CS_Status* status);
|
||||
CS_Bool CS_SetSinkConfigJson(CS_Sink sink, const char* config,
|
||||
CS_Status* status);
|
||||
char* CS_GetSinkConfigJson(CS_Sink sink, CS_Status* status);
|
||||
CS_Source CS_GetSinkSource(CS_Sink sink, CS_Status* status);
|
||||
CS_Sink CS_CopySink(CS_Sink sink, CS_Status* status);
|
||||
void CS_ReleaseSink(CS_Sink sink, CS_Status* status);
|
||||
@@ -440,20 +459,18 @@ void CS_SetLogger(CS_LogFunc func, unsigned int min_level);
|
||||
void CS_SetDefaultLogger(unsigned int min_level);
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup cscore_shutdown_cfunc Library Shutdown Function
|
||||
* @{
|
||||
*/
|
||||
void CS_Shutdown(void);
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup cscore_utility_cfunc Utility Functions
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* USB camera infomation
|
||||
*/
|
||||
typedef struct CS_UsbCameraInfo {
|
||||
int dev;
|
||||
char* path;
|
||||
char* name;
|
||||
} CS_UsbCameraInfo;
|
||||
|
||||
CS_UsbCameraInfo* CS_EnumerateUsbCameras(int* count, CS_Status* status);
|
||||
void CS_FreeEnumeratedUsbCameras(CS_UsbCameraInfo* cameras, int count);
|
||||
|
||||
@@ -465,6 +482,7 @@ void CS_ReleaseEnumeratedSinks(CS_Sink* sinks, int count);
|
||||
|
||||
void CS_FreeString(char* str);
|
||||
void CS_FreeEnumPropertyChoices(char** choices, int count);
|
||||
void CS_FreeUsbCameraInfo(CS_UsbCameraInfo* info);
|
||||
void CS_FreeHttpCameraUrls(char** urls, int count);
|
||||
|
||||
void CS_FreeEnumeratedProperties(CS_Property* properties, int count);
|
||||
|
||||
@@ -25,6 +25,10 @@ namespace cv {
|
||||
class Mat;
|
||||
} // namespace cv
|
||||
|
||||
namespace wpi {
|
||||
class json;
|
||||
} // namespace wpi
|
||||
|
||||
/** CameraServer (cscore) namespace */
|
||||
namespace cs {
|
||||
|
||||
@@ -43,11 +47,13 @@ namespace cs {
|
||||
*/
|
||||
struct UsbCameraInfo {
|
||||
/** Device number (e.g. N in '/dev/videoN' on Linux) */
|
||||
int dev;
|
||||
int dev = -1;
|
||||
/** Path to device if available (e.g. '/dev/video0' on Linux) */
|
||||
std::string path;
|
||||
/** Vendor/model name of the camera as provided by the USB driver */
|
||||
std::string name;
|
||||
/** Other path aliases to device (e.g. '/dev/v4l/by-id/...' etc on Linux) */
|
||||
std::vector<std::string> otherPaths;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -75,6 +81,13 @@ struct VideoMode : public CS_VideoMode {
|
||||
fps = fps_;
|
||||
}
|
||||
explicit operator bool() const { return pixelFormat == kUnknown; }
|
||||
|
||||
bool operator==(const VideoMode& other) const {
|
||||
return pixelFormat == other.pixelFormat && width == other.width &&
|
||||
height == other.height && fps == other.fps;
|
||||
}
|
||||
|
||||
bool operator!=(const VideoMode& other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -221,6 +234,12 @@ bool SetSourcePixelFormat(CS_Source source, VideoMode::PixelFormat pixelFormat,
|
||||
bool SetSourceResolution(CS_Source source, int width, int height,
|
||||
CS_Status* status);
|
||||
bool SetSourceFPS(CS_Source source, int fps, CS_Status* status);
|
||||
bool SetSourceConfigJson(CS_Source source, wpi::StringRef config,
|
||||
CS_Status* status);
|
||||
bool SetSourceConfigJson(CS_Source source, const wpi::json& config,
|
||||
CS_Status* status);
|
||||
std::string GetSourceConfigJson(CS_Source source, CS_Status* status);
|
||||
wpi::json GetSourceConfigJsonObject(CS_Source source, CS_Status* status);
|
||||
std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
|
||||
CS_Status* status);
|
||||
wpi::ArrayRef<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
@@ -250,6 +269,7 @@ void SetCameraExposureManual(CS_Source source, int value, CS_Status* status);
|
||||
* @{
|
||||
*/
|
||||
std::string GetUsbCameraPath(CS_Source source, CS_Status* status);
|
||||
UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status);
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
@@ -312,6 +332,11 @@ wpi::ArrayRef<CS_Property> EnumerateSinkProperties(
|
||||
void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status);
|
||||
CS_Property GetSinkSourceProperty(CS_Sink sink, const wpi::Twine& name,
|
||||
CS_Status* status);
|
||||
bool SetSinkConfigJson(CS_Sink sink, wpi::StringRef config, CS_Status* status);
|
||||
bool SetSinkConfigJson(CS_Sink sink, const wpi::json& config,
|
||||
CS_Status* status);
|
||||
std::string GetSinkConfigJson(CS_Sink sink, CS_Status* status);
|
||||
wpi::json GetSinkConfigJsonObject(CS_Sink sink, CS_Status* status);
|
||||
CS_Source GetSinkSource(CS_Sink sink, CS_Status* status);
|
||||
CS_Sink CopySink(CS_Sink sink, CS_Status* status);
|
||||
void ReleaseSink(CS_Sink sink, CS_Status* status);
|
||||
@@ -377,6 +402,13 @@ void SetLogger(LogFunc func, unsigned int min_level);
|
||||
void SetDefaultLogger(unsigned int min_level);
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup cscore_shutdown_func Library Shutdown Function
|
||||
* @{
|
||||
*/
|
||||
void Shutdown();
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup cscore_utility_func Utility Functions
|
||||
* @{
|
||||
|
||||
@@ -253,6 +253,56 @@ class VideoSource {
|
||||
*/
|
||||
bool SetFPS(int fps);
|
||||
|
||||
/**
|
||||
* Set video mode and properties from a JSON configuration string.
|
||||
*
|
||||
* The format of the JSON input is:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "pixel format": "MJPEG", "YUYV", etc
|
||||
* "width": video mode width
|
||||
* "height": video mode height
|
||||
* "fps": video mode fps
|
||||
* "brightness": percentage brightness
|
||||
* "white balance": "auto", "hold", or value
|
||||
* "exposure": "auto", "hold", or value
|
||||
* "properties": [
|
||||
* {
|
||||
* "name": property name
|
||||
* "value": property value
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param config configuration
|
||||
* @return True if set successfully
|
||||
*/
|
||||
bool SetConfigJson(wpi::StringRef config);
|
||||
|
||||
/**
|
||||
* Set video mode and properties from a JSON configuration object.
|
||||
*
|
||||
* @param config configuration
|
||||
* @return True if set successfully
|
||||
*/
|
||||
bool SetConfigJson(const wpi::json& config);
|
||||
|
||||
/**
|
||||
* Get a JSON configuration string.
|
||||
*
|
||||
* @return JSON configuration string
|
||||
*/
|
||||
std::string GetConfigJson() const;
|
||||
|
||||
/**
|
||||
* Get a JSON configuration object.
|
||||
*
|
||||
* @return JSON configuration object
|
||||
*/
|
||||
wpi::json GetConfigJsonObject() const;
|
||||
|
||||
/**
|
||||
* Get the actual FPS.
|
||||
*
|
||||
@@ -399,6 +449,11 @@ class UsbCamera : public VideoCamera {
|
||||
*/
|
||||
std::string GetPath() const;
|
||||
|
||||
/**
|
||||
* Get the full camera information for the device.
|
||||
*/
|
||||
UsbCameraInfo GetInfo() const;
|
||||
|
||||
/**
|
||||
* Set how verbose the camera connection messages are.
|
||||
*
|
||||
@@ -532,6 +587,15 @@ class AxisCamera : public HttpCamera {
|
||||
*/
|
||||
AxisCamera(const wpi::Twine& name, const std::string& host);
|
||||
|
||||
/**
|
||||
* Create a source for an Axis IP camera.
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
* @param kind Camera kind (e.g. kAxis)
|
||||
*/
|
||||
AxisCamera(const wpi::Twine& name, wpi::StringRef host);
|
||||
|
||||
/**
|
||||
* Create a source for an Axis IP camera.
|
||||
*
|
||||
@@ -743,6 +807,49 @@ class VideoSink {
|
||||
*/
|
||||
std::vector<VideoProperty> EnumerateProperties() const;
|
||||
|
||||
/**
|
||||
* Set properties from a JSON configuration string.
|
||||
*
|
||||
* The format of the JSON input is:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "properties": [
|
||||
* {
|
||||
* "name": property name
|
||||
* "value": property value
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param config configuration
|
||||
* @return True if set successfully
|
||||
*/
|
||||
bool SetConfigJson(wpi::StringRef config);
|
||||
|
||||
/**
|
||||
* Set properties from a JSON configuration object.
|
||||
*
|
||||
* @param config configuration
|
||||
* @return True if set successfully
|
||||
*/
|
||||
bool SetConfigJson(const wpi::json& config);
|
||||
|
||||
/**
|
||||
* Get a JSON configuration string.
|
||||
*
|
||||
* @return JSON configuration string
|
||||
*/
|
||||
std::string GetConfigJson() const;
|
||||
|
||||
/**
|
||||
* Get a JSON configuration object.
|
||||
*
|
||||
* @return JSON configuration object
|
||||
*/
|
||||
wpi::json GetConfigJsonObject() const;
|
||||
|
||||
/**
|
||||
* Configure which source should provide frames to this sink. Each sink
|
||||
* can accept frames from only a single source, but a single source can
|
||||
|
||||
@@ -170,6 +170,21 @@ inline bool VideoSource::SetFPS(int fps) {
|
||||
return SetSourceFPS(m_handle, fps, &m_status);
|
||||
}
|
||||
|
||||
inline bool VideoSource::SetConfigJson(wpi::StringRef config) {
|
||||
m_status = 0;
|
||||
return SetSourceConfigJson(m_handle, config, &m_status);
|
||||
}
|
||||
|
||||
inline bool VideoSource::SetConfigJson(const wpi::json& config) {
|
||||
m_status = 0;
|
||||
return SetSourceConfigJson(m_handle, config, &m_status);
|
||||
}
|
||||
|
||||
inline std::string VideoSource::GetConfigJson() const {
|
||||
m_status = 0;
|
||||
return GetSourceConfigJson(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline double VideoSource::GetActualFPS() const {
|
||||
m_status = 0;
|
||||
return cs::GetTelemetryAverageValue(m_handle, CS_SOURCE_FRAMES_RECEIVED,
|
||||
@@ -245,6 +260,11 @@ inline std::string UsbCamera::GetPath() const {
|
||||
return ::cs::GetUsbCameraPath(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline UsbCameraInfo UsbCamera::GetInfo() const {
|
||||
m_status = 0;
|
||||
return ::cs::GetUsbCameraInfo(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline void UsbCamera::SetConnectVerbose(int level) {
|
||||
m_status = 0;
|
||||
SetProperty(GetSourceProperty(m_handle, "connect_verbose", &m_status), level,
|
||||
@@ -346,6 +366,9 @@ inline AxisCamera::AxisCamera(const wpi::Twine& name, const char* host)
|
||||
inline AxisCamera::AxisCamera(const wpi::Twine& name, const std::string& host)
|
||||
: HttpCamera(name, HostToUrl(wpi::Twine{host}), kAxis) {}
|
||||
|
||||
inline AxisCamera::AxisCamera(const wpi::Twine& name, wpi::StringRef host)
|
||||
: HttpCamera(name, HostToUrl(host), kAxis) {}
|
||||
|
||||
inline AxisCamera::AxisCamera(const wpi::Twine& name,
|
||||
wpi::ArrayRef<std::string> hosts)
|
||||
: HttpCamera(name, HostToUrl(hosts), kAxis) {}
|
||||
@@ -498,6 +521,21 @@ inline VideoProperty VideoSink::GetSourceProperty(const wpi::Twine& name) {
|
||||
return VideoProperty{GetSinkSourceProperty(m_handle, name, &m_status)};
|
||||
}
|
||||
|
||||
inline bool VideoSink::SetConfigJson(wpi::StringRef config) {
|
||||
m_status = 0;
|
||||
return SetSinkConfigJson(m_handle, config, &m_status);
|
||||
}
|
||||
|
||||
inline bool VideoSink::SetConfigJson(const wpi::json& config) {
|
||||
m_status = 0;
|
||||
return SetSinkConfigJson(m_handle, config, &m_status);
|
||||
}
|
||||
|
||||
inline std::string VideoSink::GetConfigJson() const {
|
||||
m_status = 0;
|
||||
return GetSinkConfigJson(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline MjpegServer::MjpegServer(const wpi::Twine& name,
|
||||
const wpi::Twine& listenAddress, int port) {
|
||||
m_handle = CreateMjpegServer(name, listenAddress, port, &m_status);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include "NetworkListener.h"
|
||||
|
||||
#ifdef __linux__
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <sys/eventfd.h>
|
||||
@@ -18,42 +17,55 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#endif
|
||||
|
||||
#include <wpi/SafeThread.h>
|
||||
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
class NetworkListener::Thread : public wpi::SafeThread {
|
||||
class NetworkListener::Impl {
|
||||
public:
|
||||
void Main();
|
||||
Impl(wpi::Logger& logger, Notifier& notifier)
|
||||
: m_logger(logger), m_notifier(notifier) {}
|
||||
|
||||
#ifdef __linux__
|
||||
int m_command_fd = -1;
|
||||
#endif
|
||||
wpi::Logger& m_logger;
|
||||
Notifier& m_notifier;
|
||||
|
||||
class Thread : public wpi::SafeThread {
|
||||
public:
|
||||
Thread(wpi::Logger& logger, Notifier& notifier)
|
||||
: m_logger(logger), m_notifier(notifier) {}
|
||||
void Main();
|
||||
|
||||
wpi::Logger& m_logger;
|
||||
Notifier& m_notifier;
|
||||
int m_command_fd = -1;
|
||||
};
|
||||
|
||||
wpi::SafeThreadOwner<Thread> m_owner;
|
||||
};
|
||||
|
||||
NetworkListener::NetworkListener(wpi::Logger& logger, Notifier& notifier)
|
||||
: m_impl(std::make_unique<Impl>(logger, notifier)) {}
|
||||
|
||||
NetworkListener::~NetworkListener() { Stop(); }
|
||||
|
||||
void NetworkListener::Start() {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) m_owner.Start();
|
||||
m_impl->m_owner.Start(m_impl->m_logger, m_impl->m_notifier);
|
||||
}
|
||||
|
||||
void NetworkListener::Stop() {
|
||||
// Wake up thread
|
||||
if (auto thr = m_owner.GetThread()) {
|
||||
if (auto thr = m_impl->m_owner.GetThread()) {
|
||||
thr->m_active = false;
|
||||
#ifdef __linux__
|
||||
if (thr->m_command_fd >= 0) eventfd_write(thr->m_command_fd, 1);
|
||||
#endif
|
||||
}
|
||||
m_owner.Stop();
|
||||
m_impl->m_owner.Stop();
|
||||
}
|
||||
|
||||
void NetworkListener::Thread::Main() {
|
||||
#ifdef __linux__
|
||||
void NetworkListener::Impl::Thread::Main() {
|
||||
// Create event socket so we can be shut down
|
||||
m_command_fd = ::eventfd(0, 0);
|
||||
if (m_command_fd < 0) {
|
||||
@@ -125,12 +137,11 @@ void NetworkListener::Thread::Main() {
|
||||
if (nh->nlmsg_type == NLMSG_DONE) break;
|
||||
if (nh->nlmsg_type == RTM_NEWLINK || nh->nlmsg_type == RTM_DELLINK ||
|
||||
nh->nlmsg_type == RTM_NEWADDR || nh->nlmsg_type == RTM_DELADDR) {
|
||||
Notifier::GetInstance().NotifyNetworkInterfacesChanged();
|
||||
m_notifier.NotifyNetworkInterfacesChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
::close(sd);
|
||||
::close(m_command_fd);
|
||||
m_command_fd = -1;
|
||||
#endif
|
||||
}
|
||||
37
cscore/src/main/native/linux/NetworkUtil.cpp
Normal file
37
cscore/src/main/native/linux/NetworkUtil.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "cscore_cpp.h" // NOLINT(build/include_order)
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace cs {
|
||||
|
||||
std::vector<std::string> GetNetworkInterfaces() {
|
||||
struct ifaddrs* ifa;
|
||||
if (::getifaddrs(&ifa) != 0) return std::vector<std::string>{};
|
||||
|
||||
std::vector<std::string> rv;
|
||||
char buf[256];
|
||||
for (struct ifaddrs* i = ifa; i; i = i->ifa_next) {
|
||||
if (!i->ifa_addr) continue; // no address
|
||||
if (i->ifa_addr->sa_family != AF_INET) continue; // only return IPv4
|
||||
struct sockaddr_in* addr_in = reinterpret_cast<sockaddr_in*>(i->ifa_addr);
|
||||
const char* addr =
|
||||
::inet_ntop(addr_in->sin_family, &addr_in->sin_addr, buf, sizeof(buf));
|
||||
if (!addr) continue; // error converting address
|
||||
rv.emplace_back(addr);
|
||||
}
|
||||
|
||||
::freeifaddrs(ifa);
|
||||
return rv;
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
@@ -8,9 +8,7 @@
|
||||
#ifndef CSCORE_USBCAMERABUFFER_H_
|
||||
#define CSCORE_USBCAMERABUFFER_H_
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#include <utility>
|
||||
|
||||
@@ -29,7 +27,6 @@ class UsbCameraBuffer {
|
||||
UsbCameraBuffer(const UsbCameraBuffer&) = delete;
|
||||
UsbCameraBuffer& operator=(const UsbCameraBuffer&) = delete;
|
||||
|
||||
#ifdef __linux__
|
||||
UsbCameraBuffer(int fd, size_t length, off_t offset) noexcept
|
||||
: m_length{length} {
|
||||
m_data =
|
||||
@@ -43,7 +40,6 @@ class UsbCameraBuffer {
|
||||
~UsbCameraBuffer() {
|
||||
if (m_data) munmap(m_data, m_length);
|
||||
}
|
||||
#endif
|
||||
|
||||
friend void swap(UsbCameraBuffer& first, UsbCameraBuffer& second) noexcept {
|
||||
using std::swap;
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include "UsbCameraImpl.h"
|
||||
|
||||
#ifdef __linux__
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <libgen.h>
|
||||
@@ -22,29 +21,27 @@
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#elif defined(_WIN32)
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <wpi/FileSystem.h>
|
||||
#include <wpi/Path.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/memory.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
#include <wpi/timestamp.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Instance.h"
|
||||
#include "JpegUtil.h"
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
#include "Telemetry.h"
|
||||
#include "UsbUtil.h"
|
||||
#include "c_util.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
static constexpr char const* kPropWbAuto = "white_balance_temperature_auto";
|
||||
static constexpr char const* kPropWbValue = "white_balance_temperature";
|
||||
static constexpr char const* kPropExAuto = "exposure_auto";
|
||||
@@ -215,14 +212,17 @@ static std::string GetDescriptionImpl(const char* cpath) {
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
UsbCameraImpl::UsbCameraImpl(const wpi::Twine& name, const wpi::Twine& path)
|
||||
: SourceImpl{name},
|
||||
UsbCameraImpl::UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry,
|
||||
const wpi::Twine& path)
|
||||
: SourceImpl{name, logger, notifier, telemetry},
|
||||
m_path{path.str()},
|
||||
m_fd{-1},
|
||||
m_command_fd{eventfd(0, 0)},
|
||||
m_active{true} {
|
||||
SetDescription(GetDescriptionImpl(m_path.c_str()));
|
||||
SetQuirks();
|
||||
|
||||
CreateProperty(kPropConnectVerbose, [] {
|
||||
return std::make_unique<UsbCameraProperty>(kPropConnectVerbose,
|
||||
kPropConnectVerboseId,
|
||||
@@ -646,7 +646,7 @@ CS_StatusValue UsbCameraImpl::DeviceCmdSetMode(
|
||||
DeviceConnect();
|
||||
}
|
||||
if (wasStreaming) DeviceStreamOn();
|
||||
Notifier::GetInstance().NotifySourceVideoMode(*this, newMode);
|
||||
m_notifier.NotifySourceVideoMode(*this, newMode);
|
||||
lock.lock();
|
||||
} else if (newMode.fps != m_mode.fps) {
|
||||
m_mode = newMode;
|
||||
@@ -656,7 +656,7 @@ CS_StatusValue UsbCameraImpl::DeviceCmdSetMode(
|
||||
if (wasStreaming) DeviceStreamOff();
|
||||
DeviceSetFPS();
|
||||
if (wasStreaming) DeviceStreamOn();
|
||||
Notifier::GetInstance().NotifySourceVideoMode(*this, newMode);
|
||||
m_notifier.NotifySourceVideoMode(*this, newMode);
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
@@ -892,7 +892,7 @@ void UsbCameraImpl::DeviceCacheMode() {
|
||||
if (formatChanged) DeviceSetMode();
|
||||
if (fpsChanged) DeviceSetFPS();
|
||||
|
||||
Notifier::GetInstance().NotifySourceVideoMode(*this, m_mode);
|
||||
m_notifier.NotifySourceVideoMode(*this, m_mode);
|
||||
}
|
||||
|
||||
void UsbCameraImpl::DeviceCacheProperty(
|
||||
@@ -1074,7 +1074,7 @@ void UsbCameraImpl::DeviceCacheVideoModes() {
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_videoModes.swap(modes);
|
||||
}
|
||||
Notifier::GetInstance().NotifySource(*this, CS_SOURCE_VIDEOMODES_UPDATED);
|
||||
m_notifier.NotifySource(*this, CS_SOURCE_VIDEOMODES_UPDATED);
|
||||
}
|
||||
|
||||
CS_StatusValue UsbCameraImpl::SendAndWait(Message&& msg) const {
|
||||
@@ -1265,17 +1265,14 @@ CS_Source CreateUsbCameraDev(const wpi::Twine& name, int dev,
|
||||
|
||||
CS_Source CreateUsbCameraPath(const wpi::Twine& name, const wpi::Twine& path,
|
||||
CS_Status* status) {
|
||||
auto source = std::make_shared<UsbCameraImpl>(name, path);
|
||||
auto handle = Sources::GetInstance().Allocate(CS_SOURCE_USB, source);
|
||||
Notifier::GetInstance().NotifySource(name, handle, CS_SOURCE_CREATED);
|
||||
// Start thread after the source created event to ensure other events
|
||||
// come after it.
|
||||
source->Start();
|
||||
return handle;
|
||||
auto& inst = Instance::GetInstance();
|
||||
return inst.CreateSource(CS_SOURCE_USB, std::make_shared<UsbCameraImpl>(
|
||||
name, inst.logger, inst.notifier,
|
||||
inst.telemetry, path));
|
||||
}
|
||||
|
||||
std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
|
||||
auto data = Sources::GetInstance().Get(source);
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_USB) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
@@ -1283,17 +1280,79 @@ std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
|
||||
return static_cast<UsbCameraImpl&>(*data->source).GetPath();
|
||||
}
|
||||
|
||||
static const char* symlinkDirs[] = {"/dev/v4l/by-id", "/dev/v4l/by-path"};
|
||||
|
||||
UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status) {
|
||||
UsbCameraInfo info;
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_USB) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return info;
|
||||
}
|
||||
std::string keypath = static_cast<UsbCameraImpl&>(*data->source).GetPath();
|
||||
info.path = keypath;
|
||||
|
||||
// it might be a symlink; if so, find the symlink target (e.g. /dev/videoN),
|
||||
// add that to the list and make it the keypath
|
||||
if (wpi::sys::fs::is_symlink_file(keypath)) {
|
||||
char* target = ::realpath(keypath.c_str(), nullptr);
|
||||
if (target) {
|
||||
keypath.assign(target);
|
||||
info.otherPaths.emplace_back(keypath);
|
||||
std::free(target);
|
||||
}
|
||||
}
|
||||
|
||||
// device number
|
||||
wpi::StringRef fname = wpi::sys::path::filename(keypath);
|
||||
if (fname.startswith("video")) fname.substr(5).getAsInteger(10, info.dev);
|
||||
|
||||
// description
|
||||
info.name = GetDescriptionImpl(keypath.c_str());
|
||||
|
||||
// look through /dev/v4l/by-id and /dev/v4l/by-path for symlinks to the
|
||||
// keypath
|
||||
wpi::SmallString<128> path;
|
||||
for (auto symlinkDir : symlinkDirs) {
|
||||
if (DIR* dp = ::opendir(symlinkDir)) {
|
||||
while (struct dirent* ep = ::readdir(dp)) {
|
||||
if (ep->d_type == DT_LNK) {
|
||||
path = symlinkDir;
|
||||
path += '/';
|
||||
path += ep->d_name;
|
||||
char* target = ::realpath(path.c_str(), nullptr);
|
||||
if (target) {
|
||||
if (keypath == target) info.otherPaths.emplace_back(path.str());
|
||||
std::free(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
::closedir(dp);
|
||||
}
|
||||
}
|
||||
|
||||
// eliminate any duplicates
|
||||
std::sort(info.otherPaths.begin(), info.otherPaths.end());
|
||||
info.otherPaths.erase(
|
||||
std::unique(info.otherPaths.begin(), info.otherPaths.end()),
|
||||
info.otherPaths.end());
|
||||
return info;
|
||||
}
|
||||
|
||||
std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
std::vector<UsbCameraInfo> retval;
|
||||
|
||||
if (DIR* dp = opendir("/dev")) {
|
||||
while (struct dirent* ep = readdir(dp)) {
|
||||
if (DIR* dp = ::opendir("/dev")) {
|
||||
while (struct dirent* ep = ::readdir(dp)) {
|
||||
wpi::StringRef fname{ep->d_name};
|
||||
if (!fname.startswith("video")) continue;
|
||||
|
||||
unsigned int dev = 0;
|
||||
if (fname.substr(5).getAsInteger(10, dev)) continue;
|
||||
|
||||
UsbCameraInfo info;
|
||||
info.dev = -1;
|
||||
fname.substr(5).getAsInteger(10, info.dev);
|
||||
info.dev = dev;
|
||||
|
||||
wpi::SmallString<32> path{"/dev/"};
|
||||
path += fname;
|
||||
info.path = path.str();
|
||||
@@ -1301,92 +1360,49 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
info.name = GetDescriptionImpl(path.c_str());
|
||||
if (info.name.empty()) continue;
|
||||
|
||||
retval.emplace_back(std::move(info));
|
||||
if (dev >= retval.size()) retval.resize(info.dev + 1);
|
||||
retval[info.dev] = std::move(info);
|
||||
}
|
||||
closedir(dp);
|
||||
::closedir(dp);
|
||||
} else {
|
||||
// *status = ;
|
||||
ERROR("Could not open /dev");
|
||||
WPI_ERROR(Instance::GetInstance().logger, "Could not open /dev");
|
||||
return retval;
|
||||
}
|
||||
|
||||
// sort by device number
|
||||
std::sort(retval.begin(), retval.end(),
|
||||
[](const UsbCameraInfo& a, const UsbCameraInfo& b) {
|
||||
return a.dev < b.dev;
|
||||
});
|
||||
// look through /dev/v4l/by-id and /dev/v4l/by-path for symlinks to
|
||||
// /dev/videoN
|
||||
wpi::SmallString<128> path;
|
||||
for (auto symlinkDir : symlinkDirs) {
|
||||
if (DIR* dp = ::opendir(symlinkDir)) {
|
||||
while (struct dirent* ep = ::readdir(dp)) {
|
||||
if (ep->d_type == DT_LNK) {
|
||||
path = symlinkDir;
|
||||
path += '/';
|
||||
path += ep->d_name;
|
||||
char* target = ::realpath(path.c_str(), nullptr);
|
||||
if (target) {
|
||||
wpi::StringRef fname = wpi::sys::path::filename(target);
|
||||
unsigned int dev = 0;
|
||||
if (fname.startswith("video") &&
|
||||
!fname.substr(5).getAsInteger(10, dev) && dev < retval.size()) {
|
||||
retval[dev].otherPaths.emplace_back(path.str());
|
||||
}
|
||||
std::free(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
::closedir(dp);
|
||||
}
|
||||
}
|
||||
|
||||
// remove devices with empty names
|
||||
retval.erase(
|
||||
std::remove_if(retval.begin(), retval.end(),
|
||||
[](const UsbCameraInfo& x) { return x.name.empty(); }),
|
||||
retval.end());
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
|
||||
extern "C" {
|
||||
|
||||
CS_Source CS_CreateUsbCameraDev(const char* name, int dev, CS_Status* status) {
|
||||
return cs::CreateUsbCameraDev(name, dev, status);
|
||||
}
|
||||
|
||||
CS_Source CS_CreateUsbCameraPath(const char* name, const char* path,
|
||||
CS_Status* status) {
|
||||
return cs::CreateUsbCameraPath(name, path, status);
|
||||
}
|
||||
|
||||
char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status) {
|
||||
return ConvertToC(cs::GetUsbCameraPath(source, status));
|
||||
}
|
||||
|
||||
CS_UsbCameraInfo* CS_EnumerateUsbCameras(int* count, CS_Status* status) {
|
||||
auto cameras = cs::EnumerateUsbCameras(status);
|
||||
CS_UsbCameraInfo* out = static_cast<CS_UsbCameraInfo*>(
|
||||
wpi::CheckedMalloc(cameras.size() * sizeof(CS_UsbCameraInfo)));
|
||||
*count = cameras.size();
|
||||
for (size_t i = 0; i < cameras.size(); ++i) {
|
||||
out[i].dev = cameras[i].dev;
|
||||
out[i].path = ConvertToC(cameras[i].path);
|
||||
out[i].name = ConvertToC(cameras[i].name);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void CS_FreeEnumeratedUsbCameras(CS_UsbCameraInfo* cameras, int count) {
|
||||
if (!cameras) return;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
std::free(cameras[i].path);
|
||||
std::free(cameras[i].name);
|
||||
}
|
||||
std::free(cameras);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#else
|
||||
|
||||
extern "C" {
|
||||
|
||||
CS_Source CS_CreateUsbCameraDev(const char* name, int dev, CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
CS_Source CS_CreateUsbCameraPath(const char* name, const char* path,
|
||||
CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CS_UsbCameraInfo* CS_EnumerateUsbCameras(int* count, CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CS_FreeEnumeratedUsbCameras(CS_UsbCameraInfo* cameras, int count) {}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // __linux__
|
||||
@@ -8,9 +8,7 @@
|
||||
#ifndef CSCORE_USBCAMERAIMPL_H_
|
||||
#define CSCORE_USBCAMERAIMPL_H_
|
||||
|
||||
#ifdef __linux__
|
||||
#include <linux/videodev2.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
@@ -33,12 +31,16 @@
|
||||
|
||||
namespace cs {
|
||||
|
||||
class Notifier;
|
||||
class Telemetry;
|
||||
|
||||
class UsbCameraImpl : public SourceImpl {
|
||||
public:
|
||||
UsbCameraImpl(const wpi::Twine& name, const wpi::Twine& path);
|
||||
UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
|
||||
Telemetry& telemetry, const wpi::Twine& path);
|
||||
~UsbCameraImpl() override;
|
||||
|
||||
void Start();
|
||||
void Start() override;
|
||||
|
||||
// Property functions
|
||||
void SetProperty(int property, int value, CS_Status* status) override;
|
||||
@@ -145,24 +147,18 @@ class UsbCameraImpl : public SourceImpl {
|
||||
bool m_modeSetResolution{false};
|
||||
bool m_modeSetFPS{false};
|
||||
int m_connectVerbose{1};
|
||||
#ifdef __linux__
|
||||
unsigned m_capabilities = 0;
|
||||
#endif
|
||||
// Number of buffers to ask OS for
|
||||
static constexpr int kNumBuffers = 4;
|
||||
#ifdef __linux__
|
||||
std::array<UsbCameraBuffer, kNumBuffers> m_buffers;
|
||||
#endif
|
||||
|
||||
//
|
||||
// Path never changes, so not protected by mutex.
|
||||
//
|
||||
std::string m_path;
|
||||
|
||||
#ifdef __linux__
|
||||
std::atomic_int m_fd;
|
||||
std::atomic_int m_command_fd; // for command eventfd
|
||||
#endif
|
||||
|
||||
std::atomic_bool m_active; // set to false to terminate thread
|
||||
std::thread m_cameraThread;
|
||||
@@ -9,13 +9,12 @@
|
||||
|
||||
#include <wpi/STLExtras.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "UsbUtil.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
static int GetIntCtrlIoctl(int fd, unsigned id, int type, int64_t* value) {
|
||||
unsigned ctrl_class = V4L2_CTRL_ID2CLASS(id);
|
||||
if (type == V4L2_CTRL_TYPE_INTEGER64 || V4L2_CTRL_DRIVER_PRIV(id) ||
|
||||
@@ -153,6 +152,9 @@ UsbCameraProperty::UsbCameraProperty(const struct v4l2_query_ext_ctrl& ctrl)
|
||||
propKind = CS_PROP_BOOLEAN;
|
||||
break;
|
||||
case V4L2_CTRL_TYPE_INTEGER_MENU:
|
||||
propKind = CS_PROP_ENUM;
|
||||
intMenu = true;
|
||||
break;
|
||||
case V4L2_CTRL_TYPE_MENU:
|
||||
propKind = CS_PROP_ENUM;
|
||||
break;
|
||||
@@ -245,7 +247,12 @@ std::unique_ptr<UsbCameraProperty> UsbCameraProperty::DeviceQuery(int fd,
|
||||
for (int i = prop->minimum; i <= prop->maximum; ++i) {
|
||||
qmenu.index = static_cast<__u32>(i);
|
||||
if (TryIoctl(fd, VIDIOC_QUERYMENU, &qmenu) != 0) continue;
|
||||
prop->enumChoices[i] = reinterpret_cast<const char*>(qmenu.name);
|
||||
if (prop->intMenu) {
|
||||
wpi::raw_string_ostream os(prop->enumChoices[i]);
|
||||
os << qmenu.value;
|
||||
} else {
|
||||
prop->enumChoices[i] = reinterpret_cast<const char*>(qmenu.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,5 +329,3 @@ bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock, int fd,
|
||||
|
||||
return rv >= 0;
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
@@ -8,9 +8,7 @@
|
||||
#ifndef CSCORE_USBCAMERAPROPERTY_H_
|
||||
#define CSCORE_USBCAMERAPROPERTY_H_
|
||||
|
||||
#ifdef __linux__
|
||||
#include <linux/videodev2.h>
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -50,7 +48,6 @@ class UsbCameraProperty : public PropertyImpl {
|
||||
maximum = 100;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
#ifdef VIDIOC_QUERY_EXT_CTRL
|
||||
explicit UsbCameraProperty(const struct v4l2_query_ext_ctrl& ctrl);
|
||||
#endif
|
||||
@@ -62,7 +59,6 @@ class UsbCameraProperty : public PropertyImpl {
|
||||
bool DeviceSet(std::unique_lock<wpi::mutex>& lock, int fd) const;
|
||||
bool DeviceSet(std::unique_lock<wpi::mutex>& lock, int fd, int newValue,
|
||||
const wpi::Twine& newValueStr) const;
|
||||
#endif
|
||||
|
||||
// If this is a device (rather than software) property
|
||||
bool device{true};
|
||||
@@ -75,6 +71,9 @@ class UsbCameraProperty : public PropertyImpl {
|
||||
|
||||
unsigned id{0}; // implementation-level id
|
||||
int type{0}; // implementation type, not CS_PropertyKind!
|
||||
|
||||
// If the enum property is integer rather than string
|
||||
bool intMenu{false};
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
@@ -8,23 +8,19 @@
|
||||
#include "UsbUtil.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <libgen.h>
|
||||
#include <sys/ioctl.h>
|
||||
#endif
|
||||
|
||||
#include <wpi/Format.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "Instance.h"
|
||||
#include "Log.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
static wpi::StringRef GetUsbNameFromFile(int vendor, int product,
|
||||
wpi::SmallVectorImpl<char>& buf) {
|
||||
int fd = open("/var/lib/usbutils/usb.ids", O_RDONLY);
|
||||
@@ -155,12 +151,11 @@ int CheckedIoctl(int fd, unsigned long req, void* data, // NOLINT(runtime/int)
|
||||
if (!quiet && retval < 0) {
|
||||
wpi::SmallString<64> localfile{file};
|
||||
localfile.push_back('\0');
|
||||
ERROR("ioctl " << name << " failed at " << basename(localfile.data()) << ":"
|
||||
<< line << ": " << std::strerror(errno));
|
||||
WPI_ERROR(Instance::GetInstance().logger,
|
||||
"ioctl " << name << " failed at " << basename(localfile.data())
|
||||
<< ":" << line << ": " << std::strerror(errno));
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
} // namespace cs
|
||||
@@ -15,8 +15,6 @@
|
||||
|
||||
namespace cs {
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
wpi::StringRef GetUsbNameFromId(int vendor, int product,
|
||||
wpi::SmallVectorImpl<char>& buf);
|
||||
|
||||
@@ -28,8 +26,6 @@ int CheckedIoctl(int fd, unsigned long req, void* data, // NOLINT(runtime/int)
|
||||
#define TryIoctl(fd, req, data) \
|
||||
CheckedIoctl(fd, req, data, #req, __FILE__, __LINE__, true)
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_USBUTIL_H_
|
||||
20
cscore/src/main/native/osx/NetworkListener.cpp
Normal file
20
cscore/src/main/native/osx/NetworkListener.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "NetworkListener.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
class NetworkListener::Impl {};
|
||||
|
||||
NetworkListener::NetworkListener(wpi::Logger& logger, Notifier& notifier) {}
|
||||
|
||||
NetworkListener::~NetworkListener() {}
|
||||
|
||||
void NetworkListener::Start() {}
|
||||
|
||||
void NetworkListener::Stop() {}
|
||||
@@ -5,16 +5,12 @@
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
// clang-format off
|
||||
#ifdef _MSC_VER
|
||||
#pragma message "warning: llvm/None.h is deprecated; include wpi/None.h instead"
|
||||
#else
|
||||
#warning "llvm/None.h is deprecated; include wpi/None.h instead"
|
||||
#endif
|
||||
// clang-format on
|
||||
namespace cs {
|
||||
|
||||
#include "wpi/None.h"
|
||||
std::vector<std::string> GetNetworkInterfaces() {
|
||||
return std::vector<std::string>{}; // TODO
|
||||
}
|
||||
|
||||
namespace llvm = wpi;
|
||||
} // namespace cs
|
||||
39
cscore/src/main/native/osx/UsbCameraImpl.cpp
Normal file
39
cscore/src/main/native/osx/UsbCameraImpl.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
CS_Source CreateUsbCameraDev(const wpi::Twine& name, int dev,
|
||||
CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
CS_Source CreateUsbCameraPath(const wpi::Twine& name, const wpi::Twine& path,
|
||||
CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return UsbCameraInfo{};
|
||||
}
|
||||
|
||||
std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::vector<UsbCameraInfo>{};
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
158
cscore/src/main/native/windows/COMCreators.cpp
Normal file
158
cscore/src/main/native/windows/COMCreators.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <mfapi.h>
|
||||
#include <mfidl.h>
|
||||
#include <shlwapi.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include "UsbCameraImpl.h"
|
||||
|
||||
// https://github.com/opencv/opencv/blob/master/modules/videoio/src/cap_msmf.cpp
|
||||
|
||||
#include <mfidl.h>
|
||||
#include <mfapi.h>
|
||||
#include <Dbt.h>
|
||||
#include <ks.h>
|
||||
#include <ksmedia.h>
|
||||
#include <mfreadwrite.h>
|
||||
|
||||
#include "COMCreators.h"
|
||||
#include "ComPtr.h"
|
||||
|
||||
#pragma comment(lib, "Mfplat.lib")
|
||||
#pragma comment(lib, "Mf.lib")
|
||||
#pragma comment(lib, "mfuuid.lib")
|
||||
#pragma comment(lib, "Ole32.lib")
|
||||
#pragma comment(lib, "User32.lib")
|
||||
#pragma comment(lib, "Mfreadwrite.lib")
|
||||
#pragma comment(lib, "Shlwapi.lib")
|
||||
|
||||
namespace cs {
|
||||
|
||||
SourceReaderCB::SourceReaderCB(std::weak_ptr<cs::UsbCameraImpl> source,
|
||||
const cs::VideoMode& mode)
|
||||
: m_nRefCount(1), m_source(source), m_mode{mode} {}
|
||||
|
||||
// IUnknown methods
|
||||
STDMETHODIMP SourceReaderCB::QueryInterface(REFIID iid, void** ppv) {
|
||||
static const QITAB qit[] = {
|
||||
QITABENT(SourceReaderCB, IMFSourceReaderCallback),
|
||||
{0},
|
||||
};
|
||||
return QISearch(this, qit, iid, ppv);
|
||||
}
|
||||
STDMETHODIMP_(ULONG) SourceReaderCB::AddRef() {
|
||||
return InterlockedIncrement(&m_nRefCount);
|
||||
}
|
||||
STDMETHODIMP_(ULONG) SourceReaderCB::Release() {
|
||||
ULONG uCount = InterlockedDecrement(&m_nRefCount);
|
||||
if (uCount == 0) {
|
||||
delete this;
|
||||
}
|
||||
return uCount;
|
||||
}
|
||||
|
||||
STDMETHODIMP SourceReaderCB::OnEvent(DWORD, IMFMediaEvent*) { return S_OK; }
|
||||
|
||||
STDMETHODIMP SourceReaderCB::OnFlush(DWORD) { return S_OK; }
|
||||
|
||||
void SourceReaderCB::NotifyError(HRESULT hr) {
|
||||
wprintf(L"Source Reader error: 0x%X\n", hr);
|
||||
}
|
||||
|
||||
STDMETHODIMP SourceReaderCB::OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
|
||||
DWORD dwStreamFlags,
|
||||
LONGLONG llTimestamp,
|
||||
IMFSample* pSample // Can be NULL
|
||||
) {
|
||||
auto source = m_source.lock();
|
||||
if (!source) return S_OK;
|
||||
if (SUCCEEDED(hrStatus)) {
|
||||
if (pSample) {
|
||||
// Prcoess sample
|
||||
source->ProcessFrame(pSample, m_mode);
|
||||
// DO NOT release the frame
|
||||
}
|
||||
} else {
|
||||
// Streaming error.
|
||||
NotifyError(hrStatus);
|
||||
}
|
||||
// Trigger asking for a new frame.
|
||||
// This is piped through the message pump for concurrency reasons
|
||||
source->PostRequestNewFrame();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Create a Source Reader COM Smart Object
|
||||
ComPtr<SourceReaderCB> CreateSourceReaderCB(
|
||||
std::weak_ptr<cs::UsbCameraImpl> source, const cs::VideoMode& mode) {
|
||||
SourceReaderCB* ptr = new SourceReaderCB(source, mode);
|
||||
ComPtr<SourceReaderCB> sourceReaderCB;
|
||||
sourceReaderCB.Attach(ptr);
|
||||
return sourceReaderCB;
|
||||
}
|
||||
|
||||
ComPtr<IMFMediaSource> CreateVideoCaptureDevice(LPCWSTR pszSymbolicLink) {
|
||||
ComPtr<IMFAttributes> pAttributes;
|
||||
ComPtr<IMFMediaSource> pSource;
|
||||
|
||||
HRESULT hr = MFCreateAttributes(pAttributes.GetAddressOf(), 2);
|
||||
|
||||
// Set the device type to video.
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = pAttributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
|
||||
}
|
||||
|
||||
// Set the symbolic link.
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = pAttributes->SetString(
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
|
||||
pszSymbolicLink);
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = MFCreateDeviceSource(pAttributes.Get(), pSource.GetAddressOf());
|
||||
}
|
||||
|
||||
// No need to check last HR, as the source would be null anyway.
|
||||
return pSource;
|
||||
}
|
||||
|
||||
ComPtr<IMFSourceReader> CreateSourceReader(IMFMediaSource* mediaSource,
|
||||
IMFSourceReaderCallback* callback) {
|
||||
HRESULT hr = S_OK;
|
||||
ComPtr<IMFAttributes> pAttributes;
|
||||
ComPtr<IMFSourceReader> sourceReader;
|
||||
|
||||
hr = MFCreateAttributes(pAttributes.GetAddressOf(), 1);
|
||||
if (FAILED(hr)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
hr = pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, callback);
|
||||
if (FAILED(hr)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
hr = pAttributes->SetUINT32(
|
||||
MF_SOURCE_READER_DISCONNECT_MEDIASOURCE_ON_SHUTDOWN, TRUE);
|
||||
if (FAILED(hr)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MFCreateSourceReaderFromMediaSource(mediaSource, pAttributes.Get(),
|
||||
sourceReader.GetAddressOf());
|
||||
|
||||
// No need to check last HR, as the sourceReader would be null anyway.
|
||||
return sourceReader;
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
58
cscore/src/main/native/windows/COMCreators.h
Normal file
58
cscore/src/main/native/windows/COMCreators.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mfidl.h>
|
||||
#include <mfreadwrite.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "ComPtr.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class UsbCameraImpl;
|
||||
|
||||
// Source callback used by the source reader.
|
||||
// COM object, so it needs a to ref count itself.
|
||||
class SourceReaderCB : public IMFSourceReaderCallback {
|
||||
public:
|
||||
explicit SourceReaderCB(std::weak_ptr<cs::UsbCameraImpl> source,
|
||||
const cs::VideoMode& mode);
|
||||
void SetVideoMode(const VideoMode& mode) { m_mode = mode; }
|
||||
|
||||
STDMETHODIMP QueryInterface(REFIID iid, void** ppv);
|
||||
STDMETHODIMP_(ULONG) AddRef();
|
||||
STDMETHODIMP_(ULONG) Release();
|
||||
|
||||
STDMETHODIMP OnEvent(DWORD, IMFMediaEvent*);
|
||||
STDMETHODIMP OnFlush(DWORD);
|
||||
STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
|
||||
DWORD dwStreamFlags, LONGLONG llTimestamp,
|
||||
IMFSample* pSample // Can be NULL
|
||||
);
|
||||
|
||||
void InvalidateCapture() { m_source = std::weak_ptr<cs::UsbCameraImpl>(); }
|
||||
|
||||
private:
|
||||
// Destructor is private. Caller should call Release.
|
||||
virtual ~SourceReaderCB() {}
|
||||
void NotifyError(HRESULT hr);
|
||||
|
||||
ULONG m_nRefCount;
|
||||
std::weak_ptr<cs::UsbCameraImpl> m_source;
|
||||
cs::VideoMode m_mode;
|
||||
};
|
||||
|
||||
ComPtr<SourceReaderCB> CreateSourceReaderCB(
|
||||
std::weak_ptr<cs::UsbCameraImpl> source, const cs::VideoMode& mode);
|
||||
ComPtr<IMFSourceReader> CreateSourceReader(IMFMediaSource* mediaSource,
|
||||
IMFSourceReaderCallback* callback);
|
||||
ComPtr<IMFMediaSource> CreateVideoCaptureDevice(LPCWSTR pszSymbolicLink);
|
||||
} // namespace cs
|
||||
152
cscore/src/main/native/windows/ComPtr.h
Normal file
152
cscore/src/main/native/windows/ComPtr.h
Normal file
@@ -0,0 +1,152 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <comdef.h>
|
||||
#include <shlwapi.h> // QISearch
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace cs {
|
||||
|
||||
template <typename Interface>
|
||||
class RemoveAddRefRelease : public Interface {
|
||||
ULONG __stdcall AddRef();
|
||||
ULONG __stdcall Release();
|
||||
virtual ~RemoveAddRefRelease();
|
||||
};
|
||||
|
||||
template <typename Interface>
|
||||
class ComPtr {
|
||||
public:
|
||||
template <typename T>
|
||||
friend class ComPtr;
|
||||
|
||||
ComPtr(std::nullptr_t = nullptr) noexcept {} // NOLINT(runtime/explicit)
|
||||
ComPtr(const ComPtr& other) noexcept : m_ptr(other.m_ptr) {
|
||||
InternalAddRef();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ComPtr(const ComPtr<T>& other) noexcept : m_ptr(other.m_ptr) {
|
||||
InternalAddRef();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ComPtr(ComPtr<T>&& other) noexcept : m_ptr(other.m_ptr) {
|
||||
other.m_ptr = nullptr;
|
||||
}
|
||||
|
||||
~ComPtr() noexcept { InternalRelease(); }
|
||||
|
||||
ComPtr& operator=(const ComPtr& other) noexcept {
|
||||
InternalCopy(other.m_ptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ComPtr& operator=(const ComPtr<T>& other) noexcept {
|
||||
InternalCopy(other.m_ptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ComPtr& operator=(ComPtr<T>&& other) noexcept {
|
||||
InternalMove(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Swap(ComPtr& other) noexcept {
|
||||
Interface* temp = m_ptr;
|
||||
m_ptr = other.m_ptr;
|
||||
other.m_ptr = temp;
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept { return nullptr != m_ptr; }
|
||||
|
||||
void Reset() noexcept { InternalRelease(); }
|
||||
|
||||
Interface* Get() const noexcept { return m_ptr; }
|
||||
|
||||
Interface* Detach() noexcept {
|
||||
Interface* temp = m_ptr;
|
||||
m_ptr = nullptr;
|
||||
return temp;
|
||||
}
|
||||
|
||||
void Copy(Interface* other) noexcept { InternalCopy(other); }
|
||||
|
||||
void Attach(Interface* other) noexcept {
|
||||
InternalRelease();
|
||||
m_ptr = other;
|
||||
}
|
||||
|
||||
Interface** GetAddressOf() noexcept {
|
||||
assert(m_ptr == nullptr);
|
||||
return &m_ptr;
|
||||
}
|
||||
|
||||
void CopyTo(Interface** other) const noexcept {
|
||||
InternalAddRef();
|
||||
*other = m_ptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ComPtr<T> As() const noexcept {
|
||||
ComPtr<T> temp;
|
||||
m_ptr->QueryInterface(temp.GetAddressOf());
|
||||
return temp;
|
||||
}
|
||||
|
||||
RemoveAddRefRelease<Interface>* operator->() const noexcept {
|
||||
return static_cast<RemoveAddRefRelease<Interface>*>(m_ptr);
|
||||
}
|
||||
|
||||
private:
|
||||
Interface* m_ptr = nullptr;
|
||||
|
||||
void InternalAddRef() const noexcept {
|
||||
if (m_ptr) {
|
||||
m_ptr->AddRef();
|
||||
}
|
||||
}
|
||||
|
||||
void InternalRelease() noexcept {
|
||||
Interface* temp = m_ptr;
|
||||
if (temp) {
|
||||
m_ptr = nullptr;
|
||||
temp->Release();
|
||||
}
|
||||
}
|
||||
|
||||
void InternalCopy(Interface* other) noexcept {
|
||||
if (m_ptr != other) {
|
||||
InternalRelease();
|
||||
m_ptr = other;
|
||||
InternalAddRef();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void InternalMove(ComPtr<T>& other) noexcept {
|
||||
if (m_ptr != other.m_ptr) {
|
||||
InternalRelease();
|
||||
m_ptr = other.m_ptr;
|
||||
other.m_ptr = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Interface>
|
||||
void swap(
|
||||
ComPtr<Interface>& left,
|
||||
ComPtr<Interface>& right) noexcept { // NOLINT(build/include_what_you_use)
|
||||
left.Swap(right);
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
61
cscore/src/main/native/windows/NetworkListener.cpp
Normal file
61
cscore/src/main/native/windows/NetworkListener.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "NetworkListener.h"
|
||||
|
||||
#include <winsock2.h> // NOLINT(build/include_order)
|
||||
|
||||
#include <windows.h> // NOLINT(build/include_order)
|
||||
|
||||
#include <ws2def.h> // NOLINT(build/include_order)
|
||||
|
||||
#include <ws2ipdef.h> // NOLINT(build/include_order)
|
||||
|
||||
#include <iphlpapi.h> // NOLINT(build/include_order)
|
||||
|
||||
#include <netioapi.h> // NOLINT(build/include_order)
|
||||
|
||||
#include "Instance.h"
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
|
||||
#pragma comment(lib, "Iphlpapi.lib")
|
||||
|
||||
using namespace cs;
|
||||
|
||||
class NetworkListener::Impl {
|
||||
public:
|
||||
Impl(wpi::Logger& logger, Notifier& notifier)
|
||||
: m_logger(logger), m_notifier(notifier) {}
|
||||
wpi::Logger& m_logger;
|
||||
Notifier& m_notifier;
|
||||
HANDLE eventHandle = 0;
|
||||
};
|
||||
|
||||
// Static Callback function for NotifyIpInterfaceChange API.
|
||||
static void WINAPI OnInterfaceChange(PVOID callerContext,
|
||||
PMIB_IPINTERFACE_ROW row,
|
||||
MIB_NOTIFICATION_TYPE notificationType) {
|
||||
Notifier* notifier = reinterpret_cast<Notifier*>(callerContext);
|
||||
notifier->NotifyNetworkInterfacesChanged();
|
||||
}
|
||||
|
||||
NetworkListener::NetworkListener(wpi::Logger& logger, Notifier& notifier)
|
||||
: m_impl(std::make_unique<Impl>(logger, notifier)) {}
|
||||
|
||||
NetworkListener::~NetworkListener() { Stop(); }
|
||||
|
||||
void NetworkListener::Start() {
|
||||
NotifyIpInterfaceChange(AF_INET, OnInterfaceChange, &m_impl->m_notifier, true,
|
||||
&m_impl->eventHandle);
|
||||
}
|
||||
|
||||
void NetworkListener::Stop() {
|
||||
if (m_impl->eventHandle) {
|
||||
CancelMibChangeNotify2(m_impl->eventHandle);
|
||||
}
|
||||
}
|
||||
42
cscore/src/main/native/windows/NetworkUtil.cpp
Normal file
42
cscore/src/main/native/windows/NetworkUtil.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
#pragma comment(lib, "Ws2_32.lib")
|
||||
|
||||
namespace cs {
|
||||
|
||||
std::vector<std::string> GetNetworkInterfaces() {
|
||||
uv_interface_address_t* adrs;
|
||||
int counts = 0;
|
||||
|
||||
std::vector<std::string> addresses{};
|
||||
|
||||
uv_interface_addresses(&adrs, &counts);
|
||||
|
||||
char ip[50];
|
||||
|
||||
for (int i = 0; i < counts; i++) {
|
||||
if (adrs[i].is_internal) continue;
|
||||
InetNtop(PF_INET, &(adrs[i].netmask.netmask4.sin_addr.s_addr), ip,
|
||||
sizeof(ip) - 1);
|
||||
ip[49] = '\0';
|
||||
InetNtop(PF_INET, &(adrs[i].address.address4.sin_addr.s_addr), ip,
|
||||
sizeof(ip) - 1);
|
||||
ip[49] = '\0';
|
||||
addresses.emplace_back(std::string{ip});
|
||||
}
|
||||
|
||||
uv_free_interface_addresses(adrs, counts);
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
1061
cscore/src/main/native/windows/UsbCameraImpl.cpp
Normal file
1061
cscore/src/main/native/windows/UsbCameraImpl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
185
cscore/src/main/native/windows/UsbCameraImpl.h
Normal file
185
cscore/src/main/native/windows/UsbCameraImpl.h
Normal file
@@ -0,0 +1,185 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_USBCAMERAIMPL_H_
|
||||
#define CSCORE_USBCAMERAIMPL_H_
|
||||
|
||||
#include <mfapi.h>
|
||||
#include <mfidl.h>
|
||||
#include <mfreadwrite.h>
|
||||
|
||||
#include <ks.h> // NOLINT(build/include_order)
|
||||
|
||||
#include <ksmedia.h> // NOLINT(build/include_order)
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <Dbt.h>
|
||||
#include <wpi/STLExtras.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/Twine.h>
|
||||
#include <wpi/condition_variable.h>
|
||||
#include <wpi/mutex.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "ComCreators.h"
|
||||
#include "ComPtr.h"
|
||||
#include "SourceImpl.h"
|
||||
#include "UsbCameraProperty.h"
|
||||
#include "WindowsMessagePump.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
class UsbCameraImpl : public SourceImpl,
|
||||
public std::enable_shared_from_this<UsbCameraImpl> {
|
||||
public:
|
||||
UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
|
||||
Telemetry& telemetry, const wpi::Twine& path);
|
||||
UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
|
||||
Telemetry& telemetry, int deviceId);
|
||||
~UsbCameraImpl() override;
|
||||
|
||||
void Start();
|
||||
|
||||
// Property functions
|
||||
void SetProperty(int property, int value, CS_Status* status) override;
|
||||
void SetStringProperty(int property, const wpi::Twine& value,
|
||||
CS_Status* status) override;
|
||||
|
||||
// Standard common camera properties
|
||||
void SetBrightness(int brightness, CS_Status* status) override;
|
||||
int GetBrightness(CS_Status* status) const override;
|
||||
void SetWhiteBalanceAuto(CS_Status* status) override;
|
||||
void SetWhiteBalanceHoldCurrent(CS_Status* status) override;
|
||||
void SetWhiteBalanceManual(int value, CS_Status* status) override;
|
||||
void SetExposureAuto(CS_Status* status) override;
|
||||
void SetExposureHoldCurrent(CS_Status* status) override;
|
||||
void SetExposureManual(int value, CS_Status* status) override;
|
||||
|
||||
bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;
|
||||
bool SetPixelFormat(VideoMode::PixelFormat pixelFormat,
|
||||
CS_Status* status) override;
|
||||
bool SetResolution(int width, int height, CS_Status* status) override;
|
||||
bool SetFPS(int fps, CS_Status* status) override;
|
||||
|
||||
void NumSinksChanged() override;
|
||||
void NumSinksEnabledChanged() override;
|
||||
|
||||
void ProcessFrame(IMFSample* sample, const VideoMode& mode);
|
||||
void PostRequestNewFrame();
|
||||
|
||||
std::string GetPath() { return m_path; }
|
||||
|
||||
// Messages passed to/from camera thread
|
||||
struct Message {
|
||||
enum Kind {
|
||||
kNone = 0,
|
||||
kCmdSetMode,
|
||||
kCmdSetPixelFormat,
|
||||
kCmdSetResolution,
|
||||
kCmdSetFPS,
|
||||
kCmdSetProperty,
|
||||
kCmdSetPropertyStr,
|
||||
kNumSinksChanged, // no response
|
||||
kNumSinksEnabledChanged, // no response
|
||||
// Responses
|
||||
kOk,
|
||||
kError
|
||||
};
|
||||
|
||||
explicit Message(Kind kind_)
|
||||
: kind(kind_), from(std::this_thread::get_id()) {}
|
||||
|
||||
Kind kind;
|
||||
int data[4];
|
||||
std::string dataStr;
|
||||
std::thread::id from;
|
||||
};
|
||||
|
||||
protected:
|
||||
std::unique_ptr<PropertyImpl> CreateEmptyProperty(
|
||||
const wpi::Twine& name) const override;
|
||||
|
||||
// Cache properties. Immediately successful if properties are already cached.
|
||||
// If they are not, tries to connect to the camera to do so; returns false and
|
||||
// sets status to CS_SOURCE_IS_DISCONNECTED if that too fails.
|
||||
bool CacheProperties(CS_Status* status) const override;
|
||||
|
||||
private:
|
||||
// The camera processing thread
|
||||
void CameraThreadMain();
|
||||
|
||||
LRESULT PumpMain(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
bool CheckDeviceChange(WPARAM wParam, DEV_BROADCAST_HDR* pHdr,
|
||||
bool* connected);
|
||||
|
||||
// Functions used by CameraThreadMain()
|
||||
void DeviceDisconnect();
|
||||
bool DeviceConnect();
|
||||
bool DeviceStreamOn();
|
||||
bool DeviceStreamOff();
|
||||
CS_StatusValue DeviceSetMode();
|
||||
void DeviceCacheMode();
|
||||
void DeviceCacheProperty(std::unique_ptr<UsbCameraProperty> rawProp,
|
||||
IMFSourceReader* sourceReader);
|
||||
void DeviceCacheProperties();
|
||||
void DeviceCacheVideoModes();
|
||||
template <typename TagProperty, typename IAM>
|
||||
void DeviceAddProperty(const wpi::Twine& name_, TagProperty tag,
|
||||
IAM* pProcAmp);
|
||||
|
||||
ComPtr<IMFMediaType> DeviceCheckModeValid(const VideoMode& toCheck);
|
||||
|
||||
// Command helper functions
|
||||
CS_StatusValue DeviceProcessCommand(std::unique_lock<wpi::mutex>& lock,
|
||||
Message::Kind msgKind,
|
||||
const Message* msg);
|
||||
CS_StatusValue DeviceCmdSetMode(std::unique_lock<wpi::mutex>& lock,
|
||||
const Message& msg);
|
||||
CS_StatusValue DeviceCmdSetProperty(std::unique_lock<wpi::mutex>& lock,
|
||||
const Message& msg);
|
||||
|
||||
// Property helper functions
|
||||
int RawToPercentage(const UsbCameraProperty& rawProp, int rawValue);
|
||||
int PercentageToRaw(const UsbCameraProperty& rawProp, int percentValue);
|
||||
|
||||
//
|
||||
// Variables only used within camera thread
|
||||
//
|
||||
bool m_streaming{false};
|
||||
bool m_wasStreaming{false};
|
||||
bool m_modeSet{false};
|
||||
int m_connectVerbose{1};
|
||||
bool m_deviceValid{false};
|
||||
|
||||
ComPtr<IMFMediaSource> m_mediaSource;
|
||||
ComPtr<IMFSourceReader> m_sourceReader;
|
||||
ComPtr<SourceReaderCB> m_imageCallback;
|
||||
std::unique_ptr<cs::WindowsMessagePump> m_messagePump;
|
||||
ComPtr<IMFMediaType> m_currentMode;
|
||||
|
||||
//
|
||||
// Path never changes, so not protected by mutex.
|
||||
//
|
||||
std::string m_path;
|
||||
|
||||
std::wstring m_widePath;
|
||||
int m_deviceId;
|
||||
|
||||
std::vector<std::pair<VideoMode, ComPtr<IMFMediaType>>> m_windowsVideoModes;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_USBCAMERAIMPL_H_
|
||||
189
cscore/src/main/native/windows/UsbCameraProperty.cpp
Normal file
189
cscore/src/main/native/windows/UsbCameraProperty.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "UsbCameraProperty.h"
|
||||
|
||||
#include "ComPtr.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
UsbCameraProperty::UsbCameraProperty(const wpi::Twine& name_,
|
||||
tagVideoProcAmpProperty tag, bool autoProp,
|
||||
IAMVideoProcAmp* pProcAmp, bool* isValid)
|
||||
: PropertyImpl{autoProp ? name_ + "_auto" : name_} {
|
||||
this->tagVideoProc = tag;
|
||||
this->isControlProperty = false;
|
||||
this->isAutoProp = autoProp;
|
||||
long paramVal, paramFlag; // NOLINT(runtime/int)
|
||||
HRESULT hr;
|
||||
long minVal, maxVal, stepVal; // NOLINT(runtime/int)
|
||||
hr = pProcAmp->GetRange(tag, &minVal, &maxVal, &stepVal, ¶mVal,
|
||||
¶mFlag); // Unable to get the property, trying to
|
||||
// return default value
|
||||
if (SUCCEEDED(hr)) {
|
||||
minimum = minVal;
|
||||
maximum = maxVal;
|
||||
hasMaximum = true;
|
||||
hasMinimum = true;
|
||||
defaultValue = paramVal;
|
||||
step = stepVal;
|
||||
value = paramVal;
|
||||
propKind = CS_PropertyKind::CS_PROP_INTEGER;
|
||||
*isValid = true;
|
||||
} else {
|
||||
*isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool UsbCameraProperty::DeviceGet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMVideoProcAmp* pProcAmp) {
|
||||
if (!pProcAmp) return true;
|
||||
|
||||
lock.unlock();
|
||||
long newValue = 0, paramFlag = 0; // NOLINT(runtime/int)
|
||||
if (SUCCEEDED(pProcAmp->Get(tagVideoProc, &newValue, ¶mFlag))) {
|
||||
lock.lock();
|
||||
value = newValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMVideoProcAmp* pProcAmp) const {
|
||||
return DeviceSet(lock, pProcAmp, value);
|
||||
}
|
||||
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMVideoProcAmp* pProcAmp,
|
||||
int newValue) const {
|
||||
if (!pProcAmp) return true;
|
||||
|
||||
lock.unlock();
|
||||
if (SUCCEEDED(
|
||||
pProcAmp->Set(tagVideoProc, newValue, VideoProcAmp_Flags_Manual))) {
|
||||
lock.lock();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
UsbCameraProperty::UsbCameraProperty(const wpi::Twine& name_,
|
||||
tagCameraControlProperty tag,
|
||||
bool autoProp, IAMCameraControl* pProcAmp,
|
||||
bool* isValid)
|
||||
: PropertyImpl{autoProp ? name_ + "_auto" : name_} {
|
||||
this->tagCameraControl = tag;
|
||||
this->isControlProperty = true;
|
||||
this->isAutoProp = autoProp;
|
||||
long paramVal, paramFlag; // NOLINT(runtime/int)
|
||||
HRESULT hr;
|
||||
long minVal, maxVal, stepVal; // NOLINT(runtime/int)
|
||||
hr = pProcAmp->GetRange(tag, &minVal, &maxVal, &stepVal, ¶mVal,
|
||||
¶mFlag); // Unable to get the property, trying to
|
||||
// return default value
|
||||
if (SUCCEEDED(hr)) {
|
||||
minimum = minVal;
|
||||
maximum = maxVal;
|
||||
hasMaximum = true;
|
||||
hasMinimum = true;
|
||||
defaultValue = paramVal;
|
||||
step = stepVal;
|
||||
value = paramVal;
|
||||
propKind = CS_PropertyKind::CS_PROP_INTEGER;
|
||||
*isValid = true;
|
||||
} else {
|
||||
*isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool UsbCameraProperty::DeviceGet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMCameraControl* pProcAmp) {
|
||||
if (!pProcAmp) return true;
|
||||
|
||||
lock.unlock();
|
||||
long newValue = 0, paramFlag = 0; // NOLINT(runtime/int)
|
||||
if (SUCCEEDED(pProcAmp->Get(tagCameraControl, &newValue, ¶mFlag))) {
|
||||
lock.lock();
|
||||
value = newValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMCameraControl* pProcAmp) const {
|
||||
return DeviceSet(lock, pProcAmp, value);
|
||||
}
|
||||
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMCameraControl* pProcAmp,
|
||||
int newValue) const {
|
||||
if (!pProcAmp) return true;
|
||||
|
||||
lock.unlock();
|
||||
if (SUCCEEDED(pProcAmp->Set(tagCameraControl, newValue,
|
||||
CameraControl_Flags_Manual))) {
|
||||
lock.lock();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UsbCameraProperty::DeviceGet(std::unique_lock<wpi::mutex>& lock,
|
||||
IMFSourceReader* sourceReader) {
|
||||
if (!sourceReader) return true;
|
||||
|
||||
if (isControlProperty) {
|
||||
ComPtr<IAMCameraControl> pProcAmp;
|
||||
if (SUCCEEDED(sourceReader->GetServiceForStream(
|
||||
(DWORD)MF_SOURCE_READER_MEDIASOURCE, GUID_NULL,
|
||||
IID_PPV_ARGS(pProcAmp.GetAddressOf())))) {
|
||||
return DeviceGet(lock, pProcAmp.Get());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
ComPtr<IAMVideoProcAmp> pProcAmp;
|
||||
if (SUCCEEDED(sourceReader->GetServiceForStream(
|
||||
(DWORD)MF_SOURCE_READER_MEDIASOURCE, GUID_NULL,
|
||||
IID_PPV_ARGS(pProcAmp.GetAddressOf())))) {
|
||||
return DeviceGet(lock, pProcAmp.Get());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IMFSourceReader* sourceReader) const {
|
||||
return DeviceSet(lock, sourceReader, value);
|
||||
}
|
||||
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IMFSourceReader* sourceReader,
|
||||
int newValue) const {
|
||||
if (!sourceReader) return true;
|
||||
|
||||
if (isControlProperty) {
|
||||
ComPtr<IAMCameraControl> pProcAmp;
|
||||
if (SUCCEEDED(sourceReader->GetServiceForStream(
|
||||
(DWORD)MF_SOURCE_READER_MEDIASOURCE, GUID_NULL,
|
||||
IID_PPV_ARGS(pProcAmp.GetAddressOf())))) {
|
||||
return DeviceSet(lock, pProcAmp.Get(), newValue);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
ComPtr<IAMVideoProcAmp> pProcAmp;
|
||||
if (SUCCEEDED(sourceReader->GetServiceForStream(
|
||||
(DWORD)MF_SOURCE_READER_MEDIASOURCE, GUID_NULL,
|
||||
IID_PPV_ARGS(pProcAmp.GetAddressOf())))) {
|
||||
return DeviceSet(lock, pProcAmp.Get(), newValue);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
97
cscore/src/main/native/windows/UsbCameraProperty.h
Normal file
97
cscore/src/main/native/windows/UsbCameraProperty.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mfapi.h>
|
||||
#include <mfidl.h>
|
||||
#include <mfreadwrite.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <Dshow.h>
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
#include "PropertyImpl.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
// Property data
|
||||
class UsbCameraProperty : public PropertyImpl {
|
||||
public:
|
||||
UsbCameraProperty() = default;
|
||||
explicit UsbCameraProperty(const wpi::Twine& name_) : PropertyImpl{name_} {}
|
||||
|
||||
// Software property constructor
|
||||
UsbCameraProperty(const wpi::Twine& name_, unsigned id_,
|
||||
CS_PropertyKind kind_, int minimum_, int maximum_,
|
||||
int step_, int defaultValue_, int value_)
|
||||
: PropertyImpl(name_, kind_, minimum_, maximum_, step_, defaultValue_,
|
||||
value_),
|
||||
device{false},
|
||||
id{id_} {}
|
||||
|
||||
// Normalized property constructor
|
||||
UsbCameraProperty(const wpi::Twine& name_, int rawIndex_,
|
||||
const UsbCameraProperty& rawProp, int defaultValue_,
|
||||
int value_)
|
||||
: PropertyImpl(name_, rawProp.propKind, 1, defaultValue_, value_),
|
||||
percentage{true},
|
||||
propPair{rawIndex_},
|
||||
id{rawProp.id},
|
||||
type{rawProp.type} {
|
||||
hasMinimum = true;
|
||||
minimum = 0;
|
||||
hasMaximum = true;
|
||||
maximum = 100;
|
||||
}
|
||||
|
||||
UsbCameraProperty(const wpi::Twine& name_, tagVideoProcAmpProperty tag,
|
||||
bool autoProp, IAMVideoProcAmp* pProcAmp, bool* isValid);
|
||||
|
||||
UsbCameraProperty(const wpi::Twine& name_, tagCameraControlProperty tag,
|
||||
bool autoProp, IAMCameraControl* pProcAmp, bool* isValid);
|
||||
|
||||
bool DeviceGet(std::unique_lock<wpi::mutex>& lock,
|
||||
IMFSourceReader* sourceReader);
|
||||
bool DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IMFSourceReader* sourceReader) const;
|
||||
bool DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IMFSourceReader* sourceReader, int newValue) const;
|
||||
|
||||
// If this is a device (rather than software) property
|
||||
bool device{true};
|
||||
bool isAutoProp{true};
|
||||
|
||||
bool isControlProperty{false};
|
||||
tagVideoProcAmpProperty tagVideoProc;
|
||||
tagCameraControlProperty tagCameraControl;
|
||||
|
||||
// If this is a percentage (rather than raw) property
|
||||
bool percentage{false};
|
||||
|
||||
// If not 0, index of corresponding raw/percentage property
|
||||
int propPair{0};
|
||||
|
||||
unsigned id{0}; // implementation-level id
|
||||
int type{0}; // implementation type, not CS_PropertyKind!
|
||||
|
||||
private:
|
||||
bool DeviceGet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMCameraControl* pProcAmp);
|
||||
bool DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMCameraControl* pProcAmp) const;
|
||||
bool DeviceSet(std::unique_lock<wpi::mutex>& lock, IAMCameraControl* pProcAmp,
|
||||
int newValue) const;
|
||||
|
||||
bool DeviceGet(std::unique_lock<wpi::mutex>& lock, IAMVideoProcAmp* pProcAmp);
|
||||
bool DeviceSet(std::unique_lock<wpi::mutex>& lock,
|
||||
IAMVideoProcAmp* pProcAmp) const;
|
||||
bool DeviceSet(std::unique_lock<wpi::mutex>& lock, IAMVideoProcAmp* pProcAmp,
|
||||
int newValue) const;
|
||||
};
|
||||
} // namespace cs
|
||||
152
cscore/src/main/native/windows/WindowsMessagePump.cpp
Normal file
152
cscore/src/main/native/windows/WindowsMessagePump.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "WindowsMessagePump.h"
|
||||
|
||||
#include <ks.h>
|
||||
#include <ksmedia.h>
|
||||
#include <mfapi.h>
|
||||
#include <mfidl.h>
|
||||
#include <windows.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <Dbt.h>
|
||||
|
||||
#pragma comment(lib, "Mfplat.lib")
|
||||
#pragma comment(lib, "Mf.lib")
|
||||
#pragma comment(lib, "mfuuid.lib")
|
||||
#pragma comment(lib, "Ole32.lib")
|
||||
#pragma comment(lib, "User32.lib")
|
||||
|
||||
namespace cs {
|
||||
|
||||
static LRESULT CALLBACK pWndProc(HWND hwnd, UINT uiMsg, WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
WindowsMessagePump* pumpContainer;
|
||||
// Our "this" parameter is passed only during WM_CREATE
|
||||
// If it is create, store in our user parameter
|
||||
// Otherwise grab from our user parameter
|
||||
if (uiMsg == WM_CREATE) {
|
||||
CREATESTRUCT* pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
|
||||
pumpContainer =
|
||||
reinterpret_cast<WindowsMessagePump*>(pCreate->lpCreateParams);
|
||||
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pumpContainer);
|
||||
SetWindowPos(hwnd, HWND_MESSAGE, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
|
||||
} else {
|
||||
pumpContainer = reinterpret_cast<WindowsMessagePump*>(
|
||||
GetWindowLongPtr(hwnd, GWLP_USERDATA));
|
||||
}
|
||||
|
||||
// Run the callback
|
||||
bool hasCalledBack = false;
|
||||
LRESULT result;
|
||||
|
||||
if (pumpContainer) {
|
||||
hasCalledBack = true;
|
||||
result = pumpContainer->m_callback(hwnd, uiMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
// Handle a close message
|
||||
if (uiMsg == WM_CLOSE) {
|
||||
return HANDLE_WM_CLOSE(hwnd, 0, 0, [](HWND) { PostQuitMessage(0); });
|
||||
}
|
||||
|
||||
// Return message, otherwise return the base handler
|
||||
if (hasCalledBack) {
|
||||
return result;
|
||||
}
|
||||
return DefWindowProc(hwnd, uiMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct ClassHolder {
|
||||
HINSTANCE current_instance;
|
||||
WNDCLASSEX wx;
|
||||
const char* class_name = "DUMMY_CLASS";
|
||||
ClassHolder() {
|
||||
current_instance = (HINSTANCE)GetModuleHandle(NULL);
|
||||
wx = {};
|
||||
wx.cbSize = sizeof(WNDCLASSEX);
|
||||
wx.lpfnWndProc = pWndProc; // function which will handle messages
|
||||
wx.hInstance = current_instance;
|
||||
wx.lpszClassName = class_name;
|
||||
RegisterClassEx(&wx);
|
||||
}
|
||||
~ClassHolder() { UnregisterClass(class_name, current_instance); }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
static std::shared_ptr<ClassHolder> GetClassHolder() {
|
||||
static std::shared_ptr<ClassHolder> clsHolder =
|
||||
std::make_shared<ClassHolder>();
|
||||
return clsHolder;
|
||||
}
|
||||
|
||||
WindowsMessagePump::WindowsMessagePump(
|
||||
std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)> callback) {
|
||||
m_callback = callback;
|
||||
auto handle = CreateEvent(NULL, true, false, NULL);
|
||||
m_mainThread = std::thread([=]() { ThreadMain(handle); });
|
||||
auto waitResult = WaitForSingleObject(handle, 1000);
|
||||
if (waitResult == WAIT_OBJECT_0) {
|
||||
CloseHandle(handle);
|
||||
}
|
||||
}
|
||||
|
||||
WindowsMessagePump::~WindowsMessagePump() {
|
||||
auto res = SendMessage(hwnd, WM_CLOSE, NULL, NULL);
|
||||
if (m_mainThread.joinable()) m_mainThread.join();
|
||||
}
|
||||
|
||||
void WindowsMessagePump::ThreadMain(HANDLE eventHandle) {
|
||||
// Initialize COM
|
||||
CoInitializeEx(0, COINIT_MULTITHREADED);
|
||||
// Initialize MF
|
||||
MFStartup(MF_VERSION);
|
||||
|
||||
auto classHolder = GetClassHolder();
|
||||
hwnd = CreateWindowEx(0, classHolder->class_name, "dummy_name", 0, 0, 0, 0, 0,
|
||||
HWND_MESSAGE, NULL, NULL, this);
|
||||
|
||||
// Register for device notifications
|
||||
HDEVNOTIFY g_hdevnotify = NULL;
|
||||
HDEVNOTIFY g_hdevnotify2 = NULL;
|
||||
|
||||
DEV_BROADCAST_DEVICEINTERFACE di = {0};
|
||||
di.dbcc_size = sizeof(di);
|
||||
di.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
|
||||
di.dbcc_classguid = KSCATEGORY_CAPTURE;
|
||||
|
||||
g_hdevnotify =
|
||||
RegisterDeviceNotification(hwnd, &di, DEVICE_NOTIFY_WINDOW_HANDLE);
|
||||
|
||||
DEV_BROADCAST_DEVICEINTERFACE di2 = {0};
|
||||
di2.dbcc_size = sizeof(di2);
|
||||
di2.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
|
||||
di2.dbcc_classguid = KSCATEGORY_VIDEO_CAMERA;
|
||||
|
||||
g_hdevnotify2 =
|
||||
RegisterDeviceNotification(hwnd, &di2, DEVICE_NOTIFY_WINDOW_HANDLE);
|
||||
|
||||
SetEvent(eventHandle);
|
||||
|
||||
MSG Msg;
|
||||
while (GetMessage(&Msg, NULL, 0, 0) > 0) {
|
||||
TranslateMessage(&Msg);
|
||||
DispatchMessage(&Msg);
|
||||
}
|
||||
UnregisterDeviceNotification(g_hdevnotify);
|
||||
UnregisterDeviceNotification(g_hdevnotify2);
|
||||
|
||||
MFShutdown();
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
66
cscore/src/main/native/windows/WindowsMessagePump.h
Normal file
66
cscore/src/main/native/windows/WindowsMessagePump.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
namespace cs {
|
||||
class WindowsMessagePump {
|
||||
public:
|
||||
WindowsMessagePump(
|
||||
std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)> callback);
|
||||
~WindowsMessagePump();
|
||||
|
||||
friend LRESULT CALLBACK pWndProc(HWND hwnd, UINT uiMsg, WPARAM wParam,
|
||||
LPARAM lParam);
|
||||
|
||||
template <typename RetVal = LRESULT, typename FirstParam = WPARAM,
|
||||
typename SecondParam = LPARAM>
|
||||
RetVal SendWindowMessage(UINT msg, FirstParam wParam, SecondParam lParam) {
|
||||
static_assert(sizeof(FirstParam) <= sizeof(WPARAM),
|
||||
"First Parameter Does Not Fit");
|
||||
static_assert(sizeof(SecondParam) <= sizeof(LPARAM),
|
||||
"Second Parameter Does Not Fit");
|
||||
static_assert(sizeof(RetVal) <= sizeof(LRESULT),
|
||||
"Return Value Does Not Fit");
|
||||
WPARAM firstToSend = 0;
|
||||
LPARAM secondToSend = 0;
|
||||
std::memcpy(&firstToSend, &wParam, sizeof(FirstParam));
|
||||
std::memcpy(&secondToSend, &lParam, sizeof(SecondParam));
|
||||
LRESULT result = SendMessage(hwnd, msg, firstToSend, secondToSend);
|
||||
RetVal toReturn;
|
||||
std::memset(&toReturn, 0, sizeof(RetVal));
|
||||
std::memcpy(&toReturn, &result, sizeof(RetVal));
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
template <typename FirstParam = WPARAM, typename SecondParam = LPARAM>
|
||||
BOOL PostWindowMessage(UINT msg, FirstParam wParam, SecondParam lParam) {
|
||||
static_assert(sizeof(FirstParam) <= sizeof(WPARAM),
|
||||
"First Parameter Does Not Fit");
|
||||
static_assert(sizeof(SecondParam) <= sizeof(LPARAM),
|
||||
"Second Parameter Does Not Fit");
|
||||
WPARAM firstToSend = 0;
|
||||
LPARAM secondToSend = 0;
|
||||
std::memcpy(&firstToSend, &wParam, sizeof(FirstParam));
|
||||
std::memcpy(&secondToSend, &lParam, sizeof(SecondParam));
|
||||
return PostMessage(hwnd, msg, firstToSend, secondToSend);
|
||||
}
|
||||
|
||||
private:
|
||||
void ThreadMain(HANDLE eventHandle);
|
||||
|
||||
HWND hwnd;
|
||||
std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)> m_callback;
|
||||
|
||||
std::thread m_mainThread;
|
||||
};
|
||||
} // namespace cs
|
||||
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id 'org.ysb33r.doxygen' version '0.4'
|
||||
id 'java'
|
||||
id "org.ysb33r.doxygen" version "0.5"
|
||||
}
|
||||
|
||||
evaluationDependsOn(':wpiutil')
|
||||
@@ -100,6 +100,7 @@ task generateJavaDocs(type: Javadoc) {
|
||||
options.links("https://docs.oracle.com/javase/8/docs/api/")
|
||||
options.addStringOption "tag", "pre:a:Pre-Condition"
|
||||
options.addStringOption('Xdoclint:accessibility,html,missing,reference,syntax')
|
||||
options.addBooleanOption('html5', true)
|
||||
dependsOn project(':wpilibj').generateJavaVersion
|
||||
dependsOn project(':hal').generateUsageReporting
|
||||
source project(':hal').sourceSets.main.java
|
||||
@@ -111,6 +112,19 @@ task generateJavaDocs(type: Javadoc) {
|
||||
source configurations.javaSource.collect { zipTree(it) }
|
||||
include '**/*.java'
|
||||
failOnError = true
|
||||
|
||||
title = "WPILib API $pubVersion"
|
||||
ext.entryPoint = "$destinationDir/index.html"
|
||||
|
||||
if (JavaVersion.current().isJava11Compatible()) {
|
||||
options.addBooleanOption('-no-module-directories', true)
|
||||
doLast {
|
||||
// This is a work-around for https://bugs.openjdk.java.net/browse/JDK-8211194. Can be removed once that issue is fixed on JDK's side
|
||||
// Since JDK 11, package-list is missing from javadoc output files and superseded by element-list file, but a lot of external tools still need it
|
||||
// Here we generate this file manually
|
||||
new File(destinationDir, 'package-list').text = new File(destinationDir, 'element-list').text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("zipJavaDocs", Zip) {
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
2
gradlew
vendored
2
gradlew
vendored
@@ -28,7 +28,7 @@ APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
2
gradlew.bat
vendored
2
gradlew.bat
vendored
@@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
@@ -22,6 +22,47 @@ endforeach()
|
||||
string(REPLACE ";" "\n" usage_reporting_types_cpp "${usage_reporting_types_cpp}")
|
||||
string(REPLACE ";" "\n" usage_reporting_instances_cpp "${usage_reporting_instances_cpp}")
|
||||
|
||||
file(GLOB
|
||||
hal_shared_native_src src/main/native/cpp/cpp/*.cpp
|
||||
hal_shared_native_src src/main/native/cpp/handles/*.cpp
|
||||
hal_sim_native_src src/main/native/sim/*.cpp
|
||||
hal_sim_native_src src/main/native/sim/mockdata/*.cpp)
|
||||
add_library(hal ${hal_shared_native_src})
|
||||
set_target_properties(hal PROPERTIES DEBUG_POSTFIX "d")
|
||||
|
||||
if(USE_EXTERNAL_HAL)
|
||||
include(${EXTERNAL_HAL_FILE})
|
||||
else()
|
||||
target_sources(hal PRIVATE ${hal_sim_native_src} ${hal_sim_jni_src})
|
||||
endif()
|
||||
|
||||
configure_file(src/generate/FRCUsageReporting.h.in gen/hal/FRCUsageReporting.h)
|
||||
|
||||
set_target_properties(hal PROPERTIES OUTPUT_NAME "wpiHal")
|
||||
|
||||
target_include_directories(hal PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
|
||||
$<INSTALL_INTERFACE:${include_dest}/hal>)
|
||||
|
||||
target_include_directories(hal PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/gen>
|
||||
$<INSTALL_INTERFACE:${include_dest}/hal>)
|
||||
target_link_libraries(hal PUBLIC wpiutil)
|
||||
|
||||
set_property(TARGET hal PROPERTY FOLDER "libraries")
|
||||
|
||||
install(TARGETS hal EXPORT hal DESTINATION "${main_lib_dest}")
|
||||
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/hal")
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/gen DESTINATION "${include_dest}/hal")
|
||||
|
||||
if (MSVC)
|
||||
set (hal_config_dir ${wpilib_dest})
|
||||
else()
|
||||
set (hal_config_dir share/hal)
|
||||
endif()
|
||||
|
||||
install(FILES hal-config.cmake DESTINATION ${hal_config_dir})
|
||||
install(EXPORT hal DESTINATION ${hal_config_dir})
|
||||
|
||||
# Java bindings
|
||||
if (NOT WITHOUT_JAVA)
|
||||
@@ -53,59 +94,32 @@ if (NOT WITHOUT_JAVA)
|
||||
|
||||
set_property(TARGET hal_jar PROPERTY FOLDER "java")
|
||||
|
||||
endif()
|
||||
add_library(haljni ${hal_shared_jni_src})
|
||||
|
||||
file(GLOB
|
||||
hal_shared_native_src src/main/native/cpp/cpp/*.cpp
|
||||
hal_shared_native_src src/main/native/cpp/handles/*.cpp
|
||||
hal_sim_native_src src/main/native/sim/*.cpp
|
||||
hal_sim_native_src src/main/native/sim/MockData/*.cpp)
|
||||
add_library(hal ${hal_shared_native_src} ${hal_shared_jni_src})
|
||||
|
||||
if(USE_EXTERNAL_HAL)
|
||||
include(${EXTERNAL_HAL_FILE})
|
||||
else()
|
||||
target_sources(hal PRIVATE ${hal_sim_native_src} ${hal_sim_jni_src})
|
||||
endif()
|
||||
|
||||
configure_file(src/generate/FRCUsageReporting.h.in gen/hal/FRCUsageReporting.h)
|
||||
|
||||
set_target_properties(hal PROPERTIES OUTPUT_NAME "wpiHal")
|
||||
|
||||
target_include_directories(hal PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
|
||||
$<INSTALL_INTERFACE:${include_dest}/hal>)
|
||||
|
||||
target_include_directories(hal PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/gen>
|
||||
$<INSTALL_INTERFACE:${include_dest}/hal>)
|
||||
target_link_libraries(hal PUBLIC wpiutil)
|
||||
|
||||
set_property(TARGET hal PROPERTY FOLDER "libraries")
|
||||
|
||||
if (NOT WITHOUT_JAVA)
|
||||
if(${CMAKE_VERSION} VERSION_LESS "3.11.0")
|
||||
target_include_directories(hal PRIVATE ${JNI_INCLUDE_DIRS})
|
||||
target_include_directories(hal PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/jniheaders")
|
||||
if(USE_EXTERNAL_HAL)
|
||||
include(${EXTERNAL_HAL_FILE})
|
||||
else()
|
||||
target_link_libraries(hal PRIVATE hal_jni_headers)
|
||||
target_sources(haljni PRIVATE ${hal_sim_jni_src})
|
||||
endif()
|
||||
add_dependencies(hal hal_jar)
|
||||
|
||||
set_target_properties(haljni PROPERTIES OUTPUT_NAME "wpiHaljni")
|
||||
|
||||
target_link_libraries(haljni PUBLIC hal wpiutil)
|
||||
|
||||
set_property(TARGET haljni PROPERTY FOLDER "libraries")
|
||||
|
||||
if(${CMAKE_VERSION} VERSION_LESS "3.11.0")
|
||||
target_include_directories(haljni PRIVATE ${JNI_INCLUDE_DIRS})
|
||||
target_include_directories(haljni PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/jniheaders")
|
||||
else()
|
||||
target_link_libraries(haljni PRIVATE hal_jni_headers)
|
||||
endif()
|
||||
add_dependencies(haljni hal_jar)
|
||||
|
||||
if (MSVC)
|
||||
install(TARGETS haljni RUNTIME DESTINATION "${jni_lib_dest}" COMPONENT Runtime)
|
||||
endif()
|
||||
|
||||
install(TARGETS haljni EXPORT haljni DESTINATION "${main_lib_dest}")
|
||||
|
||||
endif()
|
||||
|
||||
install(TARGETS hal EXPORT hal DESTINATION "${main_lib_dest}")
|
||||
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/hal")
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/gen DESTINATION "${include_dest}/hal")
|
||||
|
||||
if (NOT WITHOUT_JAVA AND MSVC)
|
||||
install(TARGETS hal RUNTIME DESTINATION "${jni_lib_dest}" COMPONENT Runtime)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
set (hal_config_dir ${wpilib_dest})
|
||||
else()
|
||||
set (hal_config_dir share/hal)
|
||||
endif()
|
||||
|
||||
install(FILES hal-config.cmake DESTINATION ${hal_config_dir})
|
||||
install(EXPORT hal DESTINATION ${hal_config_dir})
|
||||
|
||||
@@ -51,11 +51,50 @@ ext {
|
||||
binary.lib project: ':hal', library: 'hal', linkage: shared
|
||||
}
|
||||
|
||||
addHalJniDependency = { binary->
|
||||
binary.tasks.withType(AbstractNativeSourceCompileTask) {
|
||||
it.dependsOn generateUsageReporting
|
||||
}
|
||||
binary.lib project: ':hal', library: 'halJNIShared', linkage: 'shared'
|
||||
}
|
||||
|
||||
nativeName = 'hal'
|
||||
setBaseName = 'wpiHal'
|
||||
devMain = 'DevMain'
|
||||
niLibraries = true
|
||||
generatedHeaders = "$buildDir/generated/headers"
|
||||
jniSplitSetup = {
|
||||
it.tasks.withType(AbstractNativeSourceCompileTask) {
|
||||
it.dependsOn generateUsageReporting
|
||||
}
|
||||
if (it.targetPlatform.architecture.name == 'athena') {
|
||||
it.sources {
|
||||
athenaJniCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs = ["${rootDir}/shared/singlelib", "$buildDir/generated/cpp"]
|
||||
include '**/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDir 'src/main/native/include'
|
||||
srcDir generatedHeaders
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
it.sources {
|
||||
simJniCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/sim'
|
||||
include '**/jni/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDir 'src/main/native/include'
|
||||
srcDir generatedHeaders
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
splitSetup = {
|
||||
it.tasks.withType(AbstractNativeSourceCompileTask) {
|
||||
it.dependsOn generateUsageReporting
|
||||
@@ -64,8 +103,9 @@ ext {
|
||||
it.sources {
|
||||
athenaCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs = ['src/main/native/athena', "$buildDir/generated/cpp"]
|
||||
srcDirs = ['src/main/native/athena']
|
||||
include '**/*.cpp'
|
||||
exclude '**/jni/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDir 'src/main/native/include'
|
||||
@@ -79,6 +119,7 @@ ext {
|
||||
source {
|
||||
srcDirs 'src/main/native/sim'
|
||||
include '**/*.cpp'
|
||||
exclude '**/jni/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDir 'src/main/native/include'
|
||||
@@ -142,7 +183,7 @@ model {
|
||||
x86SymbolFilter = { symbols ->
|
||||
def retList = []
|
||||
symbols.each { symbol ->
|
||||
if (symbol.startsWith('HAL_') || symbol.startsWith('Java_') || symbol.startsWith('JNI_')) {
|
||||
if (symbol.startsWith('HAL_') || symbol.startsWith('HALSIM_')) {
|
||||
retList << symbol
|
||||
}
|
||||
}
|
||||
@@ -151,7 +192,7 @@ model {
|
||||
x64SymbolFilter = { symbols ->
|
||||
def retList = []
|
||||
symbols.each { symbol ->
|
||||
if (symbol.startsWith('HAL_') || symbol.startsWith('Java_') || symbol.startsWith('JNI_')) {
|
||||
if (symbol.startsWith('HAL_') || symbol.startsWith('HALSIM_')) {
|
||||
retList << symbol
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,3 +80,6 @@ kResourceType_Shuffleboard = 78
|
||||
kResourceType_CAN = 79
|
||||
kResourceType_DigilentDMC60 = 80
|
||||
kResourceType_PWMVictorSPX = 81
|
||||
kResourceType_RevSparkMaxPWM = 82
|
||||
kResourceType_RevSparkMaxCAN = 83
|
||||
kResourceType_ADIS16470 = 84
|
||||
|
||||
@@ -25,9 +25,9 @@ public class InterruptJNI extends JNIWrapper {
|
||||
|
||||
public static native void disableInterrupts(int interruptHandle);
|
||||
|
||||
public static native double readInterruptRisingTimestamp(int interruptHandle);
|
||||
public static native long readInterruptRisingTimestamp(int interruptHandle);
|
||||
|
||||
public static native double readInterruptFallingTimestamp(int interruptHandle);
|
||||
public static native long readInterruptFallingTimestamp(int interruptHandle);
|
||||
|
||||
public static native void requestInterrupts(int interruptHandle, int digitalSourceHandle,
|
||||
int analogTriggerType);
|
||||
|
||||
@@ -21,7 +21,7 @@ public class JNIWrapper {
|
||||
static {
|
||||
if (!libraryLoaded) {
|
||||
try {
|
||||
loader = new RuntimeLoader<>("wpiHal", RuntimeLoader.getDefaultExtractionRoot(), JNIWrapper.class);
|
||||
loader = new RuntimeLoader<>("wpiHaljni", RuntimeLoader.getDefaultExtractionRoot(), JNIWrapper.class);
|
||||
loader.loadLibrary();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
|
||||
@@ -57,7 +57,7 @@ public class SPIJNI extends JNIWrapper {
|
||||
public static native int spiReadAutoReceivedData(int port, ByteBuffer buffer, int numToRead,
|
||||
double timeout);
|
||||
|
||||
public static native int spiReadAutoReceivedData(int port, byte[] buffer, int numToRead,
|
||||
public static native int spiReadAutoReceivedData(int port, int[] buffer, int numToRead,
|
||||
double timeout);
|
||||
|
||||
public static native int spiGetAutoDroppedCount(int port);
|
||||
|
||||
@@ -75,6 +75,9 @@ public class DriverStationSim {
|
||||
public void setDsAttached(boolean dsAttached) {
|
||||
DriverStationDataJNI.setDsAttached(dsAttached);
|
||||
}
|
||||
public void notifyNewData() {
|
||||
DriverStationDataJNI.notifyNewData();
|
||||
}
|
||||
|
||||
public void resetData() {
|
||||
DriverStationDataJNI.resetData();
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
package edu.wpi.first.hal.sim;
|
||||
|
||||
public interface SpiReadAutoReceiveBufferCallback {
|
||||
int callback(String name, byte[] buffer, int numToRead);
|
||||
int callback(String name, int[] buffer, int numToRead);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "AnalogInternal.h"
|
||||
#include "HALInitializer.h"
|
||||
#include "hal/AnalogAccumulator.h"
|
||||
@@ -169,6 +171,8 @@ void HAL_CalibrateAnalogGyro(HAL_GyroHandle handle, int32_t* status) {
|
||||
|
||||
HAL_InitAccumulator(gyro->handle, status);
|
||||
if (*status != 0) return;
|
||||
wpi::outs() << "Calibrating analog gyro for " << kCalibrationSampleTime
|
||||
<< " seconds." << '\n';
|
||||
Wait(kCalibrationSampleTime);
|
||||
|
||||
int64_t value;
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "HALInitializer.h"
|
||||
#include "PortsInternal.h"
|
||||
#include "hal/AnalogAccumulator.h"
|
||||
#include "hal/HAL.h"
|
||||
#include "hal/handles/HandlesInternal.h"
|
||||
|
||||
namespace hal {
|
||||
|
||||
@@ -22,7 +22,7 @@ using namespace hal;
|
||||
|
||||
namespace {
|
||||
struct Receives {
|
||||
uint64_t lastTimeStamp;
|
||||
uint32_t lastTimeStamp;
|
||||
uint8_t data[8];
|
||||
uint8_t length;
|
||||
};
|
||||
@@ -40,30 +40,13 @@ struct CANStorage {
|
||||
static UnlimitedHandleResource<HAL_CANHandle, CANStorage, HAL_HandleEnum::CAN>*
|
||||
canHandles;
|
||||
|
||||
static std::atomic_bool HasFixedTime{false};
|
||||
static uint64_t timeSpanDiff;
|
||||
|
||||
static void CheckDeltaTime() {
|
||||
if (HasFixedTime) return;
|
||||
HasFixedTime = true;
|
||||
|
||||
// TODO: Fix locking
|
||||
static uint32_t GetPacketBaseTime() {
|
||||
timespec t;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
|
||||
int32_t status = 0;
|
||||
uint64_t fpgaTime = HAL_GetFPGATime(&status);
|
||||
|
||||
// Convert t to microseconds
|
||||
uint64_t us = t.tv_sec * 1000000 + t.tv_nsec / 1000;
|
||||
|
||||
timeSpanDiff =
|
||||
us - fpgaTime; // This assumes CLOCK_MONOTONIC is greater then FPGA Time.
|
||||
}
|
||||
|
||||
static inline uint64_t ConvertToFPGATime(uint32_t canMs) {
|
||||
uint64_t canMsToUs = canMs * 1000;
|
||||
return canMsToUs - timeSpanDiff;
|
||||
// Convert t to milliseconds
|
||||
uint64_t ms = t.tv_sec * 1000ull + t.tv_nsec / 1000000ull;
|
||||
return ms & 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
namespace hal {
|
||||
@@ -89,7 +72,6 @@ HAL_CANHandle HAL_InitializeCAN(HAL_CANManufacturer manufacturer,
|
||||
int32_t deviceId, HAL_CANDeviceType deviceType,
|
||||
int32_t* status) {
|
||||
hal::init::CheckInit();
|
||||
CheckDeltaTime();
|
||||
auto can = std::make_shared<CANStorage>();
|
||||
|
||||
auto handle = canHandles->Allocate(can);
|
||||
@@ -189,17 +171,16 @@ void HAL_ReadCANPacketNew(HAL_CANHandle handle, int32_t apiId, uint8_t* data,
|
||||
uint32_t ts = 0;
|
||||
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
|
||||
|
||||
uint64_t timestamp = ConvertToFPGATime(ts);
|
||||
|
||||
if (*status == 0) {
|
||||
std::lock_guard<wpi::mutex> lock(can->mapMutex);
|
||||
auto& msg = can->receives[messageId];
|
||||
msg.length = dataSize;
|
||||
msg.lastTimeStamp = timestamp;
|
||||
msg.lastTimeStamp = ts;
|
||||
// The NetComm call placed in data, copy into the msg
|
||||
std::memcpy(msg.data, data, dataSize);
|
||||
}
|
||||
*length = dataSize;
|
||||
*receivedTimestamp = timestamp;
|
||||
*receivedTimestamp = ts;
|
||||
}
|
||||
|
||||
void HAL_ReadCANPacketLatest(HAL_CANHandle handle, int32_t apiId, uint8_t* data,
|
||||
@@ -216,21 +197,21 @@ void HAL_ReadCANPacketLatest(HAL_CANHandle handle, int32_t apiId, uint8_t* data,
|
||||
uint32_t ts = 0;
|
||||
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
|
||||
|
||||
uint64_t timestamp = ConvertToFPGATime(ts);
|
||||
|
||||
std::lock_guard<wpi::mutex> lock(can->mapMutex);
|
||||
if (*status == 0) {
|
||||
// fresh update
|
||||
auto& msg = can->receives[messageId];
|
||||
msg.length = dataSize;
|
||||
*length = dataSize;
|
||||
msg.lastTimeStamp = timestamp;
|
||||
*receivedTimestamp = timestamp;
|
||||
msg.lastTimeStamp = ts;
|
||||
*receivedTimestamp = ts;
|
||||
// The NetComm call placed in data, copy into the msg
|
||||
std::memcpy(msg.data, data, dataSize);
|
||||
} else {
|
||||
auto i = can->receives.find(messageId);
|
||||
if (i != can->receives.end()) {
|
||||
std::memcpy(i->second.data, data, i->second.length);
|
||||
// Read the data from the stored message into the output
|
||||
std::memcpy(data, i->second.data, i->second.length);
|
||||
*length = i->second.length;
|
||||
*receivedTimestamp = i->second.lastTimeStamp;
|
||||
*status = 0;
|
||||
@@ -253,29 +234,28 @@ void HAL_ReadCANPacketTimeout(HAL_CANHandle handle, int32_t apiId,
|
||||
uint32_t ts = 0;
|
||||
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
|
||||
|
||||
uint64_t timestamp = ConvertToFPGATime(ts);
|
||||
|
||||
std::lock_guard<wpi::mutex> lock(can->mapMutex);
|
||||
if (*status == 0) {
|
||||
// fresh update
|
||||
auto& msg = can->receives[messageId];
|
||||
msg.length = dataSize;
|
||||
*length = dataSize;
|
||||
msg.lastTimeStamp = timestamp;
|
||||
*receivedTimestamp = timestamp;
|
||||
msg.lastTimeStamp = ts;
|
||||
*receivedTimestamp = ts;
|
||||
// The NetComm call placed in data, copy into the msg
|
||||
std::memcpy(msg.data, data, dataSize);
|
||||
} else {
|
||||
auto i = can->receives.find(messageId);
|
||||
if (i != can->receives.end()) {
|
||||
// Found, check if new enough
|
||||
uint64_t now = HAL_GetFPGATime(status);
|
||||
if (now - i->second.lastTimeStamp >
|
||||
static_cast<uint64_t>(timeoutMs) * 1000) {
|
||||
uint32_t now = GetPacketBaseTime();
|
||||
if (now - i->second.lastTimeStamp > static_cast<uint32_t>(timeoutMs)) {
|
||||
// Timeout, return bad status
|
||||
*status = HAL_CAN_TIMEOUT;
|
||||
return;
|
||||
}
|
||||
std::memcpy(i->second.data, data, i->second.length);
|
||||
// Read the data from the stored message into the output
|
||||
std::memcpy(data, i->second.data, i->second.length);
|
||||
*length = i->second.length;
|
||||
*receivedTimestamp = i->second.lastTimeStamp;
|
||||
*status = 0;
|
||||
@@ -299,14 +279,15 @@ void HAL_ReadCANPeriodicPacket(HAL_CANHandle handle, int32_t apiId,
|
||||
std::lock_guard<wpi::mutex> lock(can->mapMutex);
|
||||
auto i = can->receives.find(messageId);
|
||||
if (i != can->receives.end()) {
|
||||
uint64_t now = HAL_GetFPGATime(status);
|
||||
// Found, check if new enough
|
||||
if (now - i->second.lastTimeStamp <
|
||||
static_cast<uint64_t>(periodMs) * 1000) {
|
||||
*status = 0;
|
||||
std::memcpy(i->second.data, data, i->second.length);
|
||||
uint32_t now = GetPacketBaseTime();
|
||||
if (now - i->second.lastTimeStamp < static_cast<uint32_t>(periodMs)) {
|
||||
// Read the data from the stored message into the output
|
||||
std::memcpy(data, i->second.data, i->second.length);
|
||||
*length = i->second.length;
|
||||
*receivedTimestamp = i->second.lastTimeStamp;
|
||||
*status = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,29 +296,28 @@ void HAL_ReadCANPeriodicPacket(HAL_CANHandle handle, int32_t apiId,
|
||||
uint32_t ts = 0;
|
||||
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
|
||||
|
||||
uint64_t timestamp = ConvertToFPGATime(ts);
|
||||
|
||||
std::lock_guard<wpi::mutex> lock(can->mapMutex);
|
||||
if (*status == 0) {
|
||||
// fresh update
|
||||
auto& msg = can->receives[messageId];
|
||||
msg.length = dataSize;
|
||||
*length = dataSize;
|
||||
msg.lastTimeStamp = timestamp;
|
||||
*receivedTimestamp = timestamp;
|
||||
msg.lastTimeStamp = ts;
|
||||
*receivedTimestamp = ts;
|
||||
// The NetComm call placed in data, copy into the msg
|
||||
std::memcpy(msg.data, data, dataSize);
|
||||
} else {
|
||||
auto i = can->receives.find(messageId);
|
||||
if (i != can->receives.end()) {
|
||||
// Found, check if new enough
|
||||
uint64_t now = HAL_GetFPGATime(status);
|
||||
if (now - i->second.lastTimeStamp >
|
||||
static_cast<uint64_t>(timeoutMs) * 1000) {
|
||||
uint32_t now = GetPacketBaseTime();
|
||||
if (now - i->second.lastTimeStamp > static_cast<uint32_t>(timeoutMs)) {
|
||||
// Timeout, return bad status
|
||||
*status = HAL_CAN_TIMEOUT;
|
||||
return;
|
||||
}
|
||||
std::memcpy(i->second.data, data, i->second.length);
|
||||
// Read the data from the stored message into the output
|
||||
std::memcpy(data, i->second.data, i->second.length);
|
||||
*length = i->second.length;
|
||||
*receivedTimestamp = i->second.lastTimeStamp;
|
||||
*status = 0;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user