Squashed 'third_party/allwpilib_2019/' changes from 99e4f7dd2..c36bbcc9a

936627bd9 wpilibc: Remove direct CameraServer dependency (#1989)
8e333c0aa Use FPGA Time instead of wall clock time for odometry (#1996)
d4430b765 Gearsbot example: Use standard argument order (#1995)
75438ab2c Add RamseteCommand (#1951)
989df1b46 Bump Native Utils and OpenCV dependencies (#1993)
dbc33b61e Fix Timer usage in TrapezoidProfileCommand (#1992)
79f8c5644 Add TrapezoidProfileCommand (#1962)
9440edf2b Refactor TrajectoryGenerator (#1972)
73a30182c Add frc2::Timer (#1968)
36ea865ed Add toString for geometry and trajectory classes (#1991)
cbe05e7e8 Update ProfiledPIDController API (#1967)
d04eb3546 Deprecate old PID classes (#1964)
02264db69 Add JNI dependencies to myRobotCpp (#1980)
2a76c996e Use VID/PID detection for PS3Eye (#1977)
a3820bbdf Remove HAL_BaseInitialize (#1981)
a83fb4793 Update to 2020v5 image (#1983)
4b0ed910e Make SwerveDriveKinematics.toChassisSpeeds() public (#1976)
103c1b121 Remove DS caching from the HAL level (#1971)
6635ea75e Fix NPE in SendableRegistry.foreachLiveWindow() (#1974)
cfe23c5cd Fix grammar error in comment for configureButtonBindings (#1969)
4bde2654e Fix mac azure build (#1973)
4f034e6c1 generateTrajectory: default reversed param to false (#1953)
acf960f72 Sim GUI: Add option to disable outputs on DS disable
2d3dac99f Sim GUI: Handle low resolutions and scale default window positions
07c86e0cd Sim GUI: Support High DPI monitors
46ad95512 SimDeviceData: Add missing null check
5bce489b9 Add ProggyDotted font to imgui (both cmake and gradle)
55af553ac Simulation GUI: Map gamepad the same way as DS
c59f9cea5 CameraServer: Add VID/PID support for Linux USB devices (#1960)
3fc89c84d Make splinePointsFromSplines public (#1963)
2c5093797 Fix implicitly deleted move constructors (#1954)
f3ad927f4 Update Java SmartDashboard and LiveWindow to match C++
05c25deb7 Fix move handling of C++ Sendable in SmartDashboard and LiveWindow
d726591ce Fix Gazebo sim plugin build (#1959)
2ff694fa4 Unbreak gradle build when other compilers installed (#1958)
53816155b Improve command decorator names (#1945)
a38f183a9 Fix GenResources.cmake so it's usable in a submodule (#1956)
b3398dca3 Set gradlebase correctly for all examples (#1950)
2c311013d Add Aarch64Bionic platform detection (#1922)
c10f2003c Add generateTrajectory overload (#1944)
63cfa64fb Add getters for pose in odometry classes (#1943)
2402c2bad Fix C++ command group recursive constructor bug (#1941)
f4eedf597 Fix ConcurrentModificationException in CommandScheduler (#1938)
bb0b207d2 Fix array out of bounds exception caused by parallel race group (#1935)
7bd69e591 Fix typo in temperature (#1940)
ec9738245 Bump to 2020 v4 image (#1931)
46303a822 Add messaging to extension loading in the HAL (#1926)
d169d6be9 Set extract_static for Doxygen config so that static members show up (#1930)
4e183eb10 Bump to 2020 v3 image (#1929)
84c185803 LiveWindow: catch errors in callback/builder functions (#1921)
0e3b0f3da Remove deprecated free() calls (#1925)
7f839b87c Remove timeouts from azure builds (#1924)
45b766a5d Fix main thread ID being potentially incorrect for simulation (#1923)
56d782b16 Add secondary camera name for PS3Eye (#1920)
2b4894038 Add simulation GUI plugin
f97d16073 Add imgui build to cmake
55a844a3e HAL sim: Add encoder channel B access
10deba854 Remove sendables from SendableRegistry when close() is called (#1917)
a9f0e4668 Implement sim devices for ADXL345, ADXL362, ADXRS450, Ultrasonic
aa9064586 Add ability to associate other devices with a SimDevice
81c2c8a7d Add simulation generic device/value support
e8d6f8a2c Move mockdata/HAL_Value.h to hal/Value.h
1b266717a Add simulation module support to cmake build (#1906)
fb8f3bd06 Add testbench yaml file (#1915)
846d8def0 Update to 2020 v2 image (#1913)
d6ac6e512 Fix PortForwarder package declaration (#1912)
227157086 Fix PS3Eye exposure setting (#1911)
885744d7e Add myRobot C++ version to cmake build (#1907)
366091fa8 Document that ConditionalCommand requires all subsystems (#1909)
c58b072c8 Fix Drive usage reporting order (#1908)
762c88adb Update compiler versions in readme (#1905)
af8ce568d Add Ramsete unicycle controller (#1790)
b2c2934d0 Fix javadoc warnings about invalid usage of ">" (#1904)
cce26ec78 Replace CRLF line endings with LF (#1902)
cb54602d4 Add support for writing RTR CAN Frames to the CAN API (#1900)
9f740e590 Use OS for serial port instead of NI VISA (#1875)
b23baf611 Add ability to run robot main loop in a separate thread (#1895)
457f94ba2 Add trajectory generation using hermite splines (#1843)
fd612052f Update native utils to use new frchome directory (#1884)
8858ec55c Remove periodic can read call (#1868)
41efb8015 Update CANAPITypes.h (#1860)
c93be1b2d Remove LabVIEW HAL support (#1901)
680f8919e Remove eigen, units and libuv from doxygen generation (#1898)
c5812524f Bump GradleJNI plugin version (#1899)
971303da8 Add PortForwarder class (#1890)
50db77bf2 Fix wpiutil cmake eigen install source directory (#1891)
85d42c199 C++ PIDCommand: Add GetMeasurement() and UseOutput() (#1892)
2dfbb855d wpilibj: Fix SwerveDriveKinematics ctor parameter name (#1889)
471f375a3 Simplify Sendable interface (#1864)
1d8c4d016 Replace ::value and ::type with _v and _t suffixes (#1885)
a5650b943 Add Units Utility class for Java (#1829)
904479ad4 Deprecate GearTooth class for removal (#1878)
86b666bba Add equality comparator to geometry classes (#1882)
62f07c182 Make one-arg Rotation2d constructor implicit (#1883)
f405582f8 Add kinematics suite (#1787)
561cbbd14 Deprecate Filter class for removal (#1876)
84e2973aa Remove unused include from Filesystem.h (#1877)
f49859ebf Remove NI VISA headers, as they are now included in NI Libraries (#1879)
bc59db5e6 Rename DEBUG macro to DEBUG0 (#1871)
dd928b4cb Remove JNI logging (#1872)
3e0f7d099 Use units for new NotifierCommand (#1869)
5ffe15d5f Remove ability to build all combined artifacts (#1867)
516cbef2c  Remove RoboRIO ifdef from simulation headers (#1859)
9b6ffc201 Replace SetOutputRange() with SetIntegratorRange()
ff8b8f0a8 Remove percent tolerance from PID controller
0ca8d667d Clean up AutoCloseable and other Java warnings (#1866)
7112add67 Watchdog: use units::second_t instead of double (#1863)
761bc3ef8 Change C++ WaitCommand to use units (#1865)
1fb301123 Add MathUtils.clamp() for Java (#1861)
eb3e0c9c9 Fix cmake Eigen include directory (#1862)
2250b7fbe Rename GearsBotNew example to GearsBot
c9f9feff1 Replace deprecated API usage in C++ examples
d6b9c7e14 CONTRIBUTING.md: Point to frc-docs instead of screensteps (#1858)
d10a1a797 Fix eigen build in vcpkg (#1856)
2bdb44325 Add frc2 includes to list of "other lib" regexes (#1855)
4b2b21d24 Replace outdated Java collections (#508)
8993ce5bf Move Eigen headers out of main include folder (#1854)
0f532a117 Add PWMSparkMax (#1751)
f7ad363d8 Add jni cross compile options for aarch64 (#1853)
9afea3340 Add support for aarch64 jetson bionic builds (#1844)
d787b5d60 Add more items to .gitignore (#1850)
5dd0d1b7d Use units in SPI
07ac711b3 Fix units deprecated warning in IterativeRobot
decfd858b Correctly report -1 for POV on disconnected joystick (#1852)
076ed7770 Add new C++ Command framework (#1785)
a0be07c37 Refactor HAL handle move construction/assignment (#1845)
558c38308 Add new Java Command framework (#1682)
1379735af Delete RobotState and SensorUtil constructors (#1847)
e3d86fee4 Move circular buffer class from wpilib to wpiutil (#1840)
4cd8a5667 TimedRobot.cpp: Fix deprecation warning (#1846)
b2861f894 Use 2020 artifacts and artifactory server (#1838)
98cc32703 Update to use artifactory to publish artifacts (#1833)
fa0640300 Move drive integration tests into wpilibj/src/test (#1836)
e716c36b8 Fix Nat.java generation to be incremental (#1831)
9fd2b5e3f Fix MSVC builds on cmake windows in vcpkg (#1835)
7e95010a2 Add compile-time EJML matrix wrapper to wpiutil (#1804)
3ebc5a6d3 Add ProfiledPIDController
fc98a79db Clean up PIDController interface in preparation for ProfiledPIDController
fdc098267 Fix compilation error in elevator trapezoid profile example (#1826)
a3dd84e85 Make XBoxController Button enum public (#1823)
a216b9e9e Add TrapezoidProfile example (#1814)
8f386f6bb wpilibc: Add unit-safety to C++ geometry classes (#1811)
c07ac2353 wpilibc: Add overloads for units (#1815)
f1d71da8a Move GetStackTrace and Demangle to wpiutil, add Windows support (#1819)
ef037457e Make LinearFilter copyable and moveable (#1789)
76930250c Remove objective-cpp support (#1816)
1c246418f Move TrapezoidProfileTest to trajectory folder (#1812)
95a54a0f2 Add java arcade drive example (#1810)
a4530243e HAL sim: Fix incorrectly setting dio port to initialized on cleanup (#1813)
09d00a622 Update Java examples to use new PIDController (#1809)
ba9b51742 Add missing Java examples (#841)
6411bd79c InterruptableSensorBase: Fix callback function deletion (#1807)
810e58ea8 I2C: Add tip about writeBulk() to transaction() (#1806)
607d6c148 Fix wpilibj integration tests jar name (#1808)
c9873e81b Remove PIDControllerRunner and mutex from new PIDController (#1795)
98d0706de Fix cscore build with OpenCV 4 (#1803)
fbe67c90c Make Sendable setters synchronous (#1799)
c67a488a0 Format SendableBuilderImpl javadocs (#1802)
8e93ce892 Fix PIDControllerRunner member destruction order (#1801)
c98ca7310 Add EJML dependency to wpiutil (#1769)
3b12276bc SendableBase: remove unnecessary synchronization (#1797)
e6d348f38 Fix missing default name in Java PIDController (#1792)
df12fc2a8 Java cleanups (#1776)
39561751f Update GradleVSCode version (#1786)
37d316aa0 Add C++20 std::math constants shim (#1788)
dd4310959 Deprecate frc/WPILib.h (#1779)
823174f30 Update native utils to 2020.0.4 (#1783)
37c695266 Squelch -Wdeprecated-copy for Eigen with GCC >= 9
04c9b000f Revert "Fix build of Eigen 3.3.7 with GCC 9"
ca3e71e21 wpiutil: Fix Process::Spawn() (#1778)
d946d5a2b Fix Eigen compilation errors and add tests (#1777)
8b1b9ac75 Fix build of Eigen 3.3.7 with GCC 9
2f680ba99 Add Eigen linear algebra library
a885db7d4 Make MotorEncoderTest use LinearFilter (#1775)
ee2410169 Add geometry classes (#1766)
48fe54271 Add HALSIM_SetSendError implementation (#1773)
dff58c87f Fix unused warning in release build (#1771)
dde61aad3 Remove TimerEventHandler typedef from Notifier class (#1767)
0f6ef80ab Add RobotState#IsEStopped and DriverStation#IsEStopped (#952)
e48886187 Move unit tests from integration test suite (#1170)
dffa1a5cb Make null checks more descriptive (#1688)
fe59d854d Notifier: add null check (#1684)
10731f3d6 Update uv Udp wrapper for latest features
89f7b72b6 Update libuv to 1.30.1 release
85f2f8740 wpiutil: Add unique_function (#1761)
73ec94078 Remove SampleRobot (#1658)
62be0392b Replace std::lock_guard and std::lock with std::scoped_lock (#1758)
24d31df55 Make sure move constructor is generated for TrapezoidProfile (#1757)
841ef5d73 Remove template types from lock RAII wrapper usages (#1756)
e582518ba Fix some move constructors (#1754)
8757bc471 Remove pre-C++17 shims (#1752)
ea9512977 Add replacement PIDController class (#1300)
9b798d228 Add TrapezoidProfile class (#1673)
804926fb5 Unconditionally skip athena builds for sim (#1748)
118e9d29d Add C++14 units library (#1749)
c705953d7 Add usage reporting to LinearFilter (#1750)
852d1b9ca Don't cross-build gazebo for raspbian (#1747)
eedb3a1ad Fix GCC 9 warnings (#1730)
60dce66a4 Remove wpi::ArrayRef std::initializer_list constructor (#1745)
9e19b29c3 Use base azure image for primary wpilib build (#1744)
299425071 Update jni library, fix cross builds of the jni symbol check (#1742)
a6b0e9b85 Only disable execution of cross compile google tests (#1741)
3c2093119 Use docker container to run wpiformat (#1740)
5fe2eebce Revert "Don't build halsim_gazebo on raspbian (#1737)" (#1743)
4b1b92bb7 Replace wpi::optional with C++17 std::optional (#1732)
0fbb0d989 Update to 2020 compilers (#1733)
2dc94e605 Disable google tests on cross compilers (#1738)
d9cb57a42 Don't build halsim_gazebo on raspbian (#1737)
f7cfdd7ce Replace crlf line endings with lf (#1731)
b6d5d90d9 Add JaCoCo Support (#1734)
c7ab2baa6 Add way to disable the jni check tasks from a property (#1736)
0c45c5b7e Fix skip athena and skip raspbian flags (#1735)
3dfb01d45 Update to new Native Utils (#1696)
30e936837 Clean up LinearDigitalFilter class (#782)
311e2de4c Remove deprecated Joystick constants (#1715)
c08fd6682 Update CAN manufacturer list (#1706)
258bba0c2 ErrorBase and WPIError improvements (#1727)
372ca4f45 cmake: Enable googletest unit tests (#1720)
223d47af0 HALSIM: support mocking of HAL_SendError() (#1728)
55cb683db Change compiler flags to C++17 (#1723)
ee8a33c56 wpiutil: SafeThread: Add thread id, support getting shared_ptr (#1722)
61426d08d wpiutil: Signal: make operator() const (#1721)
b630b63ef Remove functions in LiveWindow deprecated since 2018 (#1716)
1d0c05d4f Styleguide fixes for #1718 (#1719)
f07569df1 Fix newer GCC/clang compiler warnings (#1718)
0120f3124 C++ SPI: Fix SetClockRate to take int (#1717)
c2829ed98 Configure gradle to ignore unresolved headers (#1711)
221e66f46 Allow disabling static init of JNI libraries (#1672)
738852e11 cmake: Add cross toolchain files for Rio and Pi (#1710)
27b697b08 Remove frc directory include shims (#1714)
9e45373a7 Remove functions and classes deprecated for 2018 season (#1059)
eeb1025ac SPI: Report port as instance for usage reporting (#1704)
bc6f1e246 Windows compiler options improvements (#1709)
bb48ae391 cmake: Move example programs into folders (#1654)
221011494 Update for C++17 and fix MSVC warnings (#1694)
fb1239a2a Add raw sources and sinks to cscore (#1670)
7de947734 Add lambda overloads for interrupts (#1636)
90957aeea Move libuv to its own subfolder in build (#1661)
47aae502a Styleguide fixes (#1702)
0bff98b5e Correct DifferentialDrive::ArcadeDrive param docs (#1698)
b52e40b80 Allow widgets to be added by passing value suppliers (#1690)
4a00cd77b Add usage reporting for the Shuffleboard API (#1685)
e25e515f2  Publish artifacts on azure (#1678)
322ef9b96 Force Java 11, fix javadoc generation (#1695)
d42ef5df0 Fix Watchdog print formatting (#1693)
f432f65be Update copyright year in license to 2019 (#1524)
1726b77ac wpiutil: uv: Remove copy from SimpleBufferPool (#1680)
620bec9ca wpiutil: uv: Add LoopClosing status to Handle (#1647)
7cd6e2e7f UsbCamera: Solve race in windows initialization (#1638)
7732836bd Completely disable watchdog tests on mac (#1679)
698edfda9 Remove framework load, disable mac timeout test (#1676)
1c454b000 Add Shuffleboard calls to IterativeRobotBase in C++ (#1607)
f42905b32 Include missing headers in HAL.h (#1660)
bdc822fad Only generate passthrough URLs for RoboRIO (#1624)
d3affb16b Make failure of HAL_GetFPGATime() more descriptive (#1633)
2de3bf7f5 Update LLVM from stable upstream (#1653)
3cf4f38f5 Fix build on macos10.14.4 (#1648)
4e0c10f48 Fix CAN Clean using wrong ID (#1668)
3b0631324 Fix Gray to BGR conversion in CameraServer (#1665)
6cd1c73ef Fix GUID comparison creating weird symbol (#1659)
063bbab6f MavenArtifacts.md: update links to HTTPS (#1674)
aab4c494d Fix type in build.gradle (#1604)
bf46af260 Disable extraneous data warnings in libjpeg (#1630)
655763a9a Limit length of message sent to DS SendError call (#1618)
a095ec2d8 Fix linker errors with free functions in Threads.h (#1625)
12ab035aa Fix receive side of LabVIEW USB streams (#1621)

Change-Id: Ibd382e1a48925c200850cf90a8121e35c0fcffe3
git-subtree-dir: third_party/allwpilib_2019
git-subtree-split: c36bbcc9a9095489fc078229db4fba3ecd0f9b78
diff --git a/cscore/CMakeLists.txt b/cscore/CMakeLists.txt
index 52bcb54..a87ab7d 100644
--- a/cscore/CMakeLists.txt
+++ b/cscore/CMakeLists.txt
@@ -1,6 +1,8 @@
 project(cscore)
 
 include(SubDirList)
+include(CompileWarnings)
+include(AddTest)
 
 find_package( OpenCV REQUIRED )
 
@@ -21,14 +23,15 @@
     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)
+    target_compile_definitions(cscore PUBLIC -DNOMINMAX)
+    target_compile_definitions(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)
+wpilib_target_warnings(cscore)
 target_link_libraries(cscore PUBLIC wpiutil ${OpenCV_LIBS})
 
 set_property(TARGET cscore PROPERTY FOLDER "libraries")
@@ -36,13 +39,14 @@
 install(TARGETS cscore EXPORT cscore DESTINATION "${main_lib_dest}")
 install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/cscore")
 
-if (MSVC)
+if (MSVC OR FLAT_INSTALL_WPILIB)
     set (cscore_config_dir ${wpilib_dest})
 else()
     set (cscore_config_dir share/cscore)
 endif()
 
-install(FILES cscore-config.cmake DESTINATION ${cscore_config_dir})
+configure_file(cscore-config.cmake.in ${CMAKE_BINARY_DIR}/cscore-config.cmake )
+install(FILES ${CMAKE_BINARY_DIR}/cscore-config.cmake DESTINATION ${cscore_config_dir})
 install(EXPORT cscore DESTINATION ${cscore_config_dir})
 
 SUBDIR_LIST(cscore_examples "${CMAKE_CURRENT_SOURCE_DIR}/examples")
@@ -50,7 +54,9 @@
     file(GLOB cscore_example_src examples/${example}/*.cpp)
     if(cscore_example_src)
         add_executable(cscore_${example} ${cscore_example_src})
+        wpilib_target_warnings(cscore_${example})
         target_link_libraries(cscore_${example} cscore)
+        set_property(TARGET cscore_${example} PROPERTY FOLDER "examples")
     endif()
 endforeach()
 
@@ -101,6 +107,7 @@
     set_property(TARGET cscore_jar PROPERTY FOLDER "java")
 
     add_library(cscorejni ${cscore_jni_src})
+    wpilib_target_warnings(cscorejni)
     target_link_libraries(cscorejni PUBLIC cscore wpiutil ${OpenCV_LIBS})
 
     set_property(TARGET cscorejni PROPERTY FOLDER "libraries")
@@ -120,3 +127,8 @@
     install(TARGETS cscorejni EXPORT cscorejni DESTINATION "${main_lib_dest}")
 
 endif()
+
+if (WITH_TESTS)
+    wpilib_add_test(cscore src/test/native/cpp)
+    target_link_libraries(cscore_test cscore gmock)
+endif()
diff --git a/cscore/build.gradle b/cscore/build.gradle
index 3162c88..4514b95 100644
--- a/cscore/build.gradle
+++ b/cscore/build.gradle
@@ -5,9 +5,13 @@
     devMain = 'edu.wpi.cscore.DevMain'
 }
 
-if (OperatingSystem.current().isMacOsX()) {
-    apply plugin: 'objective-cpp'
-}
+// Removed because having the objective-cpp plugin added breaks
+// embedded tools and its toolchain check. It causes an obj-cpp
+// source set to be added to all binaries, even cross binaries
+// with no support.
+// if (OperatingSystem.current().isMacOsX()) {
+//     apply plugin: 'objective-cpp'
+// }
 
 apply from: "${rootDir}/shared/jni/setupBuild.gradle"
 
@@ -20,18 +24,18 @@
     useJava = true
     useCpp = true
     splitSetup = {
-        if (it.targetPlatform.operatingSystem.name == 'osx') {
+        if (it.targetPlatform.operatingSystem.isMacOsX()) {
             it.sources {
-                macObjCpp(ObjectiveCppSourceSet) {
-                    source {
-                        srcDirs = ['src/main/native/objcpp']
-                        include '**/*.mm'
-                    }
-                    exportedHeaders {
-                        srcDirs 'src/main/native/include'
-                        include '**/*.h'
-                    }
-                }
+                // macObjCpp(ObjectiveCppSourceSet) {
+                //     source {
+                //         srcDirs = ['src/main/native/objcpp']
+                //         include '**/*.mm'
+                //     }
+                //     exportedHeaders {
+                //         srcDirs 'src/main/native/include'
+                //         include '**/*.h'
+                //     }
+                // }
                 cscoreMacCpp(CppSourceSet) {
                     source {
                         srcDirs 'src/main/native/osx'
@@ -43,7 +47,7 @@
                     }
                 }
             }
-        } else if (it.targetPlatform.operatingSystem.name == 'linux') {
+        } else if (it.targetPlatform.operatingSystem.isLinux()) {
             it.sources {
                 cscoreLinuxCpp(CppSourceSet) {
                     source {
@@ -56,7 +60,7 @@
                     }
                 }
             }
-        } else if (it.targetPlatform.operatingSystem.name == 'windows') {
+        } else if (it.targetPlatform.operatingSystem.isWindows()) {
             it.sources {
                 cscoreWindowsCpp(CppSourceSet) {
                     source {
@@ -88,43 +92,28 @@
 
 apply from: "${rootDir}/shared/opencv.gradle"
 
-model {
-    // Exports config is a utility to enable exporting all symbols in a C++ library on windows to a DLL.
-    // This removes the need for DllExport on a library. However, the gradle C++ builder has a bug
-    // where some extra symbols are added that cannot be resolved at link time. This configuration
-    // lets you specify specific symbols to exlude from exporting.
-    exportsConfigs {
-        cscore(ExportsConfig) {
-            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', '==']
-            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', '==']
+nativeUtils.exportsConfigs {
+    cscore {
+        x86ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
+                            '_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
+                            '_TI5?AVfailure', '_CT??_R0?AVout_of_range', '_CTA3?AVout_of_range',
+                            '_TI3?AVout_of_range', '_CT??_R0?AVbad_cast']
+        x64ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
+                            '_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
+                            '_TI5?AVfailure', '_CT??_R0?AVout_of_range', '_CTA3?AVout_of_range',
+                            '_TI3?AVout_of_range', '_CT??_R0?AVbad_cast']
+    }
+    cscoreJNI {
+        x86SymbolFilter = { symbols ->
+            symbols.removeIf({ !it.startsWith('CS_') })
         }
-        cscoreJNI(ExportsConfig) {
-            x86SymbolFilter = { symbols ->
-                def retList = []
-                symbols.each { symbol ->
-                    if (symbol.startsWith('CS_')) {
-                        retList << symbol
-                    }
-                }
-                return retList
-            }
-            x64SymbolFilter = { symbols ->
-                def retList = []
-                symbols.each { symbol ->
-                    if (symbol.startsWith('CS_')) {
-                        retList << symbol
-                    }
-                }
-                return retList
-            }
+        x64SymbolFilter = { symbols ->
+            symbols.removeIf({ !it.startsWith('CS_') })
         }
     }
+}
+
+model {
     components {
         examplesMap.each { key, value ->
             "${key}"(NativeExecutableSpec) {
diff --git a/cscore/cscore-config.cmake b/cscore/cscore-config.cmake
deleted file mode 100644
index 790633b..0000000
--- a/cscore/cscore-config.cmake
+++ /dev/null
@@ -1,6 +0,0 @@
-include(CMakeFindDependencyMacro)

-find_dependency(wpiutil)

-find_dependency(OpenCV)

-

-get_filename_component(SELF_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)

-include(${SELF_DIR}/cscore.cmake)

diff --git a/cscore/cscore-config.cmake.in b/cscore/cscore-config.cmake.in
new file mode 100644
index 0000000..da85e8b
--- /dev/null
+++ b/cscore/cscore-config.cmake.in
@@ -0,0 +1,6 @@
+include(CMakeFindDependencyMacro)
+@FILENAME_DEP_REPLACE@
+@WPIUTIL_DEP_REPLACE@
+find_dependency(OpenCV)
+
+include(${SELF_DIR}/cscore.cmake)
diff --git a/cscore/examples/httpcvstream/httpcvstream.cpp b/cscore/examples/httpcvstream/httpcvstream.cpp
index db8e0d7..90d61d5 100644
--- a/cscore/examples/httpcvstream/httpcvstream.cpp
+++ b/cscore/examples/httpcvstream/httpcvstream.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2017-2019 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.                                                               */
@@ -11,6 +11,7 @@
 #include <opencv2/core/core.hpp>
 
 #include "cscore.h"
+#include "cscore_cv.h"
 
 int main() {
   cs::HttpCamera camera{"httpcam", "http://localhost:8081/?action=stream"};
diff --git a/cscore/examples/usbcvstream/usbcvstream.cpp b/cscore/examples/usbcvstream/usbcvstream.cpp
index 68796b4..9a4ab06 100644
--- a/cscore/examples/usbcvstream/usbcvstream.cpp
+++ b/cscore/examples/usbcvstream/usbcvstream.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2017-2019 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.                                                               */
@@ -11,6 +11,7 @@
 #include <opencv2/core/core.hpp>
 
 #include "cscore.h"
+#include "cscore_cv.h"
 
 int main() {
   cs::UsbCamera camera{"usbcam", 0};
diff --git a/cscore/java-examples/RawCVMatSink.java b/cscore/java-examples/RawCVMatSink.java
new file mode 100644
index 0000000..d9557f7
--- /dev/null
+++ b/cscore/java-examples/RawCVMatSink.java
@@ -0,0 +1,96 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018-2019 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.                                                               */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.cscore;
+
+import java.nio.ByteBuffer;
+
+import org.opencv.core.CvType;
+import org.opencv.core.Mat;
+
+import edu.wpi.cscore.VideoMode.PixelFormat;
+import edu.wpi.cscore.raw.RawFrame;
+
+public class RawCVMatSink extends ImageSink {
+  RawFrame frame = new RawFrame();
+  Mat tmpMat;
+  ByteBuffer origByteBuffer;
+  int width;
+  int height;
+  int pixelFormat;
+  int bgrValue = PixelFormat.kBGR.getValue();
+
+  private int getCVFormat(PixelFormat pixelFormat) {
+    int type = 0;
+    switch (pixelFormat) {
+    case kYUYV:
+    case kRGB565:
+      type = CvType.CV_8UC2;
+      break;
+    case kBGR:
+      type = CvType.CV_8UC3;
+      break;
+    case kGray:
+    case kMJPEG:
+    default:
+      type = CvType.CV_8UC1;
+      break;
+    }
+    return type;
+  }
+
+  /**
+   * Create a sink for accepting OpenCV images.
+   * WaitForFrame() must be called on the created sink to get each new
+   * image.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   */
+  public RawCVMatSink(String name) {
+    super(CameraServerJNI.createRawSink(name));
+  }
+
+  /**
+   * Wait for the next frame and get the image.
+   * Times out (returning 0) after 0.225 seconds.
+   * The provided image will have three 3-bit channels stored in BGR order.
+   *
+   * @return Frame time, or 0 on error (call GetError() to obtain the error
+   *         message)
+   */
+  public long grabFrame(Mat image) {
+    return grabFrame(image, 0.225);
+  }
+
+  /**
+   * Wait for the next frame and get the image.
+   * Times out (returning 0) after timeout seconds.
+   * The provided image will have three 3-bit channels stored in BGR order.
+   *
+   * @return Frame time, or 0 on error (call GetError() to obtain the error
+   *         message); the frame time is in 1 us increments.
+   */
+  public long grabFrame(Mat image, double timeout) {
+    frame.setWidth(0);
+    frame.setHeight(0);
+    frame.setPixelFormat(bgrValue);
+    long rv = CameraServerJNI.grabSinkFrameTimeout(m_handle, frame, timeout);
+    if (rv <= 0) {
+      return rv;
+    }
+
+    if (frame.getDataByteBuffer() != origByteBuffer || width != frame.getWidth() || height != frame.getHeight() || pixelFormat != frame.getPixelFormat()) {
+      origByteBuffer = frame.getDataByteBuffer();
+      height = frame.getHeight();
+      width = frame.getWidth();
+      pixelFormat = frame.getPixelFormat();
+      tmpMat = new Mat(frame.getHeight(), frame.getWidth(), getCVFormat(VideoMode.getPixelFormatFromInt(pixelFormat)), origByteBuffer);
+    }
+    tmpMat.copyTo(image);
+    return rv;
+  }
+}
diff --git a/cscore/java-examples/RawCVMatSource.java b/cscore/java-examples/RawCVMatSource.java
new file mode 100644
index 0000000..65bd7d2
--- /dev/null
+++ b/cscore/java-examples/RawCVMatSource.java
@@ -0,0 +1,59 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018-2019 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.                                                               */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.cscore;
+
+import org.opencv.core.Mat;
+
+import edu.wpi.cscore.VideoMode.PixelFormat;
+
+public class RawCVMatSource extends ImageSource {
+  /**
+   * Create an OpenCV source.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   * @param mode Video mode being generated
+   */
+  public RawCVMatSource(String name, VideoMode mode) {
+    super(CameraServerJNI.createRawSource(name,
+        mode.pixelFormat.getValue(),
+        mode.width,
+        mode.height,
+        mode.fps));
+  }
+
+  /**
+   * Create an OpenCV source.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   * @param pixelFormat Pixel format
+   * @param width width
+   * @param height height
+   * @param fps fps
+   */
+  public RawCVMatSource(String name, VideoMode.PixelFormat pixelFormat, int width, int height, int fps) {
+    super(CameraServerJNI.createRawSource(name, pixelFormat.getValue(), width, height, fps));
+  }
+
+  /**
+   * Put an OpenCV image and notify sinks.
+   *
+   * <p>Only 8-bit single-channel or 3-channel (with BGR channel order) images
+   * are supported. If the format, depth or channel order is different, use
+   * Mat.convertTo() and/or cvtColor() to convert it first.
+   *
+   * @param image OpenCV image
+   */
+  public void putFrame(Mat image) {
+    int channels = image.channels();
+    if (channels != 1 && channels != 3) {
+      throw new VideoException("Unsupported Image Type");
+    }
+    int imgType = channels == 1 ? PixelFormat.kGray.getValue() : PixelFormat.kBGR.getValue();
+    CameraServerJNI.putRawSourceFrame(m_handle, image.dataAddr(), image.width(), image.height(), imgType, (int)image.total() * channels);
+  }
+}
diff --git a/cscore/src/dev/java/edu/wpi/cscore/DevMain.java b/cscore/src/dev/java/edu/wpi/cscore/DevMain.java
index e7fd516..51bfd26 100644
--- a/cscore/src/dev/java/edu/wpi/cscore/DevMain.java
+++ b/cscore/src/dev/java/edu/wpi/cscore/DevMain.java
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2017-2019 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.                                                               */
diff --git a/cscore/src/dev/native/cpp/main.cpp b/cscore/src/dev/native/cpp/main.cpp
index b95de47..f27f61f 100644
--- a/cscore/src/dev/native/cpp/main.cpp
+++ b/cscore/src/dev/native/cpp/main.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2017-2019 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.                                                               */
diff --git a/cscore/src/main/java/edu/wpi/cscore/CameraServerCvJNI.java b/cscore/src/main/java/edu/wpi/cscore/CameraServerCvJNI.java
new file mode 100644
index 0000000..78b4ff8
--- /dev/null
+++ b/cscore/src/main/java/edu/wpi/cscore/CameraServerCvJNI.java
@@ -0,0 +1,72 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018-2019 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.                                                               */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.cscore;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.opencv.core.Core;
+
+import edu.wpi.first.wpiutil.RuntimeLoader;
+
+public class CameraServerCvJNI {
+  static boolean libraryLoaded = false;
+
+  static RuntimeLoader<Core> loader = null;
+
+  public static class Helper {
+    private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true);
+
+    public static boolean getExtractOnStaticLoad() {
+      return extractOnStaticLoad.get();
+    }
+
+    public static void setExtractOnStaticLoad(boolean load) {
+      extractOnStaticLoad.set(load);
+    }
+  }
+
+  static {
+    String opencvName = Core.NATIVE_LIBRARY_NAME;
+    if (Helper.getExtractOnStaticLoad()) {
+      try {
+        CameraServerJNI.forceLoad();
+        loader = new RuntimeLoader<>(opencvName, RuntimeLoader.getDefaultExtractionRoot(), Core.class);
+        loader.loadLibraryHashed();
+      } catch (IOException ex) {
+        ex.printStackTrace();
+        System.exit(1);
+      }
+      libraryLoaded = true;
+    }
+  }
+
+  /**
+   * Force load the library.
+   */
+  public static synchronized void forceLoad() throws IOException {
+    if (libraryLoaded) {
+      return;
+    }
+    CameraServerJNI.forceLoad();
+    loader = new RuntimeLoader<>(Core.NATIVE_LIBRARY_NAME, RuntimeLoader.getDefaultExtractionRoot(), Core.class);
+    loader.loadLibrary();
+    libraryLoaded = true;
+  }
+
+  public static native int createCvSource(String name, int pixelFormat, int width, int height, int fps);
+
+  public static native void putSourceFrame(int source, long imageNativeObj);
+
+  public static native int createCvSink(String name);
+  //public static native int createCvSinkCallback(String name,
+  //                            void (*processFrame)(long time));
+
+  public static native long grabSinkFrame(int sink, long imageNativeObj);
+  public static native long grabSinkFrameTimeout(int sink, long imageNativeObj, double timeout);
+}
diff --git a/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java b/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java
index cb6202d..ac18408 100644
--- a/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java
+++ b/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -8,21 +8,32 @@
 package edu.wpi.cscore;
 
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 
-import org.opencv.core.Core;
-
+import edu.wpi.cscore.raw.RawFrame;
 import edu.wpi.first.wpiutil.RuntimeLoader;
 
 public class CameraServerJNI {
   static boolean libraryLoaded = false;
-  static boolean cvLibraryLoaded = false;
 
   static RuntimeLoader<CameraServerJNI> loader = null;
-  static RuntimeLoader<Core> cvLoader = null;
+
+  public static class Helper {
+    private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true);
+
+    public static boolean getExtractOnStaticLoad() {
+      return extractOnStaticLoad.get();
+    }
+
+    public static void setExtractOnStaticLoad(boolean load) {
+      extractOnStaticLoad.set(load);
+    }
+  }
 
   static {
-    if (!libraryLoaded) {
+    if (Helper.getExtractOnStaticLoad()) {
       try {
         loader = new RuntimeLoader<>("cscorejni", RuntimeLoader.getDefaultExtractionRoot(), CameraServerJNI.class);
         loader.loadLibrary();
@@ -32,21 +43,19 @@
       }
       libraryLoaded = true;
     }
-
-    String opencvName = Core.NATIVE_LIBRARY_NAME;
-    if (!cvLibraryLoaded) {
-      try {
-        cvLoader = new RuntimeLoader<>(opencvName, RuntimeLoader.getDefaultExtractionRoot(), Core.class);
-        cvLoader.loadLibraryHashed();
-      } catch (IOException ex) {
-        ex.printStackTrace();
-        System.exit(1);
-      }
-      cvLibraryLoaded = true;
-    }
   }
 
-  public static void forceLoad() {}
+  /**
+   * Force load the library.
+   */
+  public static synchronized void forceLoad() throws IOException {
+    if (libraryLoaded) {
+      return;
+    }
+    loader = new RuntimeLoader<>("cscorejni", RuntimeLoader.getDefaultExtractionRoot(), CameraServerJNI.class);
+    loader.loadLibrary();
+    libraryLoaded = true;
+  }
 
   //
   // Property Functions
@@ -70,7 +79,7 @@
   public static native int createUsbCameraPath(String name, String path);
   public static native int createHttpCamera(String name, String url, int kind);
   public static native int createHttpCameraMulti(String name, String[] urls, int kind);
-  public static native int createCvSource(String name, int pixelFormat, int width, int height, int fps);
+  public static native int createRawSource(String name, int pixelFormat, int width, int height, int fps);
 
   //
   // Source Functions
@@ -122,9 +131,13 @@
   public static native String[] getHttpCameraUrls(int source);
 
   //
-  // OpenCV Source Functions
+  // Image Source Functions
   //
-  public static native void putSourceFrame(int source, long imageNativeObj);
+  public static native void putRawSourceFrameBB(int source, ByteBuffer data, int width, int height, int pixelFormat, int totalData);
+  public static native void putRawSourceFrame(int source, long data, int width, int height, int pixelFormat, int totalData);
+  public static void putRawSourceFrame(int source, RawFrame raw) {
+    putRawSourceFrame(source, raw.getDataPtr(), raw.getWidth(), raw.getHeight(), raw.getPixelFormat(), raw.getTotalData());
+  }
   public static native void notifySourceError(int source, String msg);
   public static native void setSourceConnected(int source, boolean connected);
   public static native void setSourceDescription(int source, String description);
@@ -135,9 +148,8 @@
   // Sink Creation Functions
   //
   public static native int createMjpegServer(String name, String listenAddress, int port);
-  public static native int createCvSink(String name);
-  //public static native int createCvSinkCallback(String name,
-  //                            void (*processFrame)(long time));
+
+  public static native int createRawSink(String name);
 
   //
   // Sink Functions
@@ -162,11 +174,19 @@
   public static native int getMjpegServerPort(int sink);
 
   //
-  // OpenCV Sink Functions
+  // Image Sink Functions
   //
   public static native void setSinkDescription(int sink, String description);
-  public static native long grabSinkFrame(int sink, long imageNativeObj);
-  public static native long grabSinkFrameTimeout(int sink, long imageNativeObj, double timeout);
+
+  private static native long grabRawSinkFrameImpl(int sink, RawFrame rawFrame, long rawFramePtr, ByteBuffer byteBuffer, int width, int height, int pixelFormat);
+  private static native long grabRawSinkFrameTimeoutImpl(int sink, RawFrame rawFrame, long rawFramePtr, ByteBuffer byteBuffer, int width, int height, int pixelFormat, double timeout);
+
+  public static long grabSinkFrame(int sink, RawFrame rawFrame) {
+    return grabRawSinkFrameImpl(sink, rawFrame, rawFrame.getFramePtr(), rawFrame.getDataByteBuffer(), rawFrame.getWidth(), rawFrame.getHeight(), rawFrame.getPixelFormat());
+  }
+  public static long grabSinkFrameTimeout(int sink, RawFrame rawFrame, double timeout) {
+    return grabRawSinkFrameTimeoutImpl(sink, rawFrame, rawFrame.getFramePtr(), rawFrame.getDataByteBuffer(), rawFrame.getWidth(), rawFrame.getHeight(), rawFrame.getPixelFormat(), timeout);
+  }
   public static native String getSinkError(int sink);
   public static native void setSinkEnabled(int sink, boolean enabled);
 
@@ -228,4 +248,8 @@
   public static native String getHostname();
 
   public static native String[] getNetworkInterfaces();
+
+  public static native long allocateRawFrame();
+
+  public static native void freeRawFrame(long frame);
 }
diff --git a/cscore/src/main/java/edu/wpi/cscore/CvSink.java b/cscore/src/main/java/edu/wpi/cscore/CvSink.java
index c2c7d0e..f12dcc7 100644
--- a/cscore/src/main/java/edu/wpi/cscore/CvSink.java
+++ b/cscore/src/main/java/edu/wpi/cscore/CvSink.java
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -11,8 +11,11 @@
 
 /**
  * A sink for user code to accept video frames as OpenCV images.
+ * These sinks require the WPILib OpenCV builds.
+ * For an alternate OpenCV, see the documentation how to build your own
+ * with RawSink.
  */
-public class CvSink extends VideoSink {
+public class CvSink extends ImageSink {
   /**
    * Create a sink for accepting OpenCV images.
    * WaitForFrame() must be called on the created sink to get each new
@@ -21,7 +24,7 @@
    * @param name Source name (arbitrary unique identifier)
    */
   public CvSink(String name) {
-    super(CameraServerJNI.createCvSink(name));
+    super(CameraServerCvJNI.createCvSink(name));
   }
 
   /// Create a sink for accepting OpenCV images in a separate thread.
@@ -38,15 +41,6 @@
   //}
 
   /**
-   * Set sink description.
-   *
-   * @param description Description
-   */
-  public void setDescription(String description) {
-    CameraServerJNI.setSinkDescription(m_handle, description);
-  }
-
-  /**
    * Wait for the next frame and get the image.
    * Times out (returning 0) after 0.225 seconds.
    * The provided image will have three 3-bit channels stored in BGR order.
@@ -67,7 +61,7 @@
    *         message); the frame time is in 1 us increments.
    */
   public long grabFrame(Mat image, double timeout) {
-    return CameraServerJNI.grabSinkFrameTimeout(m_handle, image.nativeObj, timeout);
+    return CameraServerCvJNI.grabSinkFrameTimeout(m_handle, image.nativeObj, timeout);
   }
 
   /**
@@ -78,24 +72,6 @@
    *         message); the frame time is in 1 us increments.
    */
   public long grabFrameNoTimeout(Mat image) {
-    return CameraServerJNI.grabSinkFrame(m_handle, image.nativeObj);
-  }
-
-  /**
-   * Get error string.  Call this if WaitForFrame() returns 0 to determine
-   * what the error is.
-   */
-  public String getError() {
-    return CameraServerJNI.getSinkError(m_handle);
-  }
-
-  /**
-   * Enable or disable getting new frames.
-   * Disabling will cause processFrame (for callback-based CvSinks) to not
-   * be called and WaitForFrame() to not return.  This can be used to save
-   * processor resources when frames are not needed.
-   */
-  public void setEnabled(boolean enabled) {
-    CameraServerJNI.setSinkEnabled(m_handle, enabled);
+    return CameraServerCvJNI.grabSinkFrame(m_handle, image.nativeObj);
   }
 }
diff --git a/cscore/src/main/java/edu/wpi/cscore/CvSource.java b/cscore/src/main/java/edu/wpi/cscore/CvSource.java
index 47cd409..c04d197 100644
--- a/cscore/src/main/java/edu/wpi/cscore/CvSource.java
+++ b/cscore/src/main/java/edu/wpi/cscore/CvSource.java
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -11,8 +11,11 @@
 
 /**
  * A source that represents a video camera.
+ * These sources require the WPILib OpenCV builds.
+ * For an alternate OpenCV, see the documentation how to build your own
+ * with RawSource.
  */
-public class CvSource extends VideoSource {
+public class CvSource extends ImageSource {
   /**
    * Create an OpenCV source.
    *
@@ -20,7 +23,7 @@
    * @param mode Video mode being generated
    */
   public CvSource(String name, VideoMode mode) {
-    super(CameraServerJNI.createCvSource(name,
+    super(CameraServerCvJNI.createCvSource(name,
         mode.pixelFormat.getValue(),
         mode.width,
         mode.height,
@@ -37,7 +40,7 @@
    * @param fps fps
    */
   public CvSource(String name, VideoMode.PixelFormat pixelFormat, int width, int height, int fps) {
-    super(CameraServerJNI.createCvSource(name, pixelFormat.getValue(), width, height, fps));
+    super(CameraServerCvJNI.createCvSource(name, pixelFormat.getValue(), width, height, fps));
   }
 
   /**
@@ -50,154 +53,7 @@
    * @param image OpenCV image
    */
   public void putFrame(Mat image) {
-    CameraServerJNI.putSourceFrame(m_handle, image.nativeObj);
+    CameraServerCvJNI.putSourceFrame(m_handle, image.nativeObj);
   }
 
-  /**
-   * Signal sinks that an error has occurred.  This should be called instead
-   * of NotifyFrame when an error occurs.
-   */
-  public void notifyError(String msg) {
-    CameraServerJNI.notifySourceError(m_handle, msg);
-  }
-
-  /**
-   * Set source connection status.  Defaults to true.
-   *
-   * @param connected True for connected, false for disconnected
-   */
-  public void setConnected(boolean connected) {
-    CameraServerJNI.setSourceConnected(m_handle, connected);
-  }
-
-  /**
-   * Set source description.
-   *
-   * @param description Description
-   */
-  public void setDescription(String description) {
-    CameraServerJNI.setSourceDescription(m_handle, description);
-  }
-
-  /**
-   * Create a property.
-   *
-   * @param name Property name
-   * @param kind Property kind
-   * @param minimum Minimum value
-   * @param maximum Maximum value
-   * @param step Step value
-   * @param defaultValue Default value
-   * @param value Current value
-   * @return Property
-   */
-  public VideoProperty createProperty(String name,
-                                      VideoProperty.Kind kind,
-                                      int minimum,
-                                      int maximum,
-                                      int step,
-                                      int defaultValue,
-                                      int value) {
-    return new VideoProperty(
-        CameraServerJNI.createSourceProperty(m_handle,
-            name,
-            kind.getValue(),
-            minimum,
-            maximum,
-            step,
-            defaultValue,
-            value));
-  }
-
-  /**
-   * Create an integer property.
-   *
-   * @param name Property name
-   * @param minimum Minimum value
-   * @param maximum Maximum value
-   * @param step Step value
-   * @param defaultValue Default value
-   * @param value Current value
-   * @return Property
-   */
-  public VideoProperty createIntegerProperty(String name,
-                                             int minimum,
-                                             int maximum,
-                                             int step,
-                                             int defaultValue,
-                                             int value) {
-    return new VideoProperty(
-        CameraServerJNI.createSourceProperty(m_handle,
-            name,
-            VideoProperty.Kind.kInteger.getValue(),
-            minimum,
-            maximum,
-            step,
-            defaultValue,
-            value));
-  }
-
-  /**
-   * Create a boolean property.
-   *
-   * @param name Property name
-   * @param defaultValue Default value
-   * @param value Current value
-   * @return Property
-   */
-  public VideoProperty createBooleanProperty(String name, boolean defaultValue, boolean value) {
-    return new VideoProperty(
-        CameraServerJNI.createSourceProperty(m_handle,
-            name,
-            VideoProperty.Kind.kBoolean.getValue(),
-            0,
-            1,
-            1,
-            defaultValue ? 1 : 0,
-            value ? 1 : 0));
-  }
-
-  /**
-   * Create a string property.
-   *
-   * @param name Property name
-   * @param value Current value
-   * @return Property
-   */
-  public VideoProperty createStringProperty(String name, String value) {
-    VideoProperty prop = new VideoProperty(
-        CameraServerJNI.createSourceProperty(m_handle,
-            name,
-            VideoProperty.Kind.kString.getValue(),
-            0,
-            0,
-            0,
-            0,
-            0));
-    prop.setString(value);
-    return prop;
-  }
-
-  /**
-   * Configure enum property choices.
-   *
-   * @param property Property
-   * @param choices Choices
-   */
-  public void setEnumPropertyChoices(VideoProperty property, String[] choices) {
-    CameraServerJNI.setSourceEnumPropertyChoices(m_handle, property.m_handle, choices);
-  }
-
-  /**
-   * Configure enum property choices.
-   *
-   * @param property Property
-   * @param choices Choices
-   * @deprecated Use {@code setEnumPropertyChoices} instead.
-   */
-  @Deprecated
-  @SuppressWarnings("MethodName")
-  public void SetEnumPropertyChoices(VideoProperty property, String[] choices) {
-    setEnumPropertyChoices(property, choices);
-  }
 }
diff --git a/cscore/src/main/java/edu/wpi/cscore/ImageSink.java b/cscore/src/main/java/edu/wpi/cscore/ImageSink.java
new file mode 100644
index 0000000..f755fb6
--- /dev/null
+++ b/cscore/src/main/java/edu/wpi/cscore/ImageSink.java
@@ -0,0 +1,41 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018-2019 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.                                                               */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.cscore;
+
+public abstract class ImageSink extends VideoSink {
+  protected ImageSink(int handle) {
+    super(handle);
+  }
+
+  /**
+   * Set sink description.
+   *
+   * @param description Description
+   */
+  public void setDescription(String description) {
+    CameraServerJNI.setSinkDescription(m_handle, description);
+  }
+
+  /**
+   * Get error string.  Call this if WaitForFrame() returns 0 to determine
+   * what the error is.
+   */
+  public String getError() {
+    return CameraServerJNI.getSinkError(m_handle);
+  }
+
+  /**
+   * Enable or disable getting new frames.
+   * Disabling will cause processFrame (for callback-based CvSinks) to not
+   * be called and WaitForFrame() to not return.  This can be used to save
+   * processor resources when frames are not needed.
+   */
+  public void setEnabled(boolean enabled) {
+    CameraServerJNI.setSinkEnabled(m_handle, enabled);
+  }
+}
diff --git a/cscore/src/main/java/edu/wpi/cscore/ImageSource.java b/cscore/src/main/java/edu/wpi/cscore/ImageSource.java
new file mode 100644
index 0000000..3787516
--- /dev/null
+++ b/cscore/src/main/java/edu/wpi/cscore/ImageSource.java
@@ -0,0 +1,162 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018-2019 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.                                                               */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.cscore;
+
+public abstract class ImageSource extends VideoSource {
+  protected ImageSource(int handle) {
+    super(handle);
+  }
+
+  /**
+   * Signal sinks that an error has occurred.  This should be called instead
+   * of NotifyFrame when an error occurs.
+   */
+  public void notifyError(String msg) {
+    CameraServerJNI.notifySourceError(m_handle, msg);
+  }
+
+  /**
+   * Set source connection status.  Defaults to true.
+   *
+   * @param connected True for connected, false for disconnected
+   */
+  public void setConnected(boolean connected) {
+    CameraServerJNI.setSourceConnected(m_handle, connected);
+  }
+
+  /**
+   * Set source description.
+   *
+   * @param description Description
+   */
+  public void setDescription(String description) {
+    CameraServerJNI.setSourceDescription(m_handle, description);
+  }
+
+  /**
+   * Create a property.
+   *
+   * @param name Property name
+   * @param kind Property kind
+   * @param minimum Minimum value
+   * @param maximum Maximum value
+   * @param step Step value
+   * @param defaultValue Default value
+   * @param value Current value
+   * @return Property
+   */
+  public VideoProperty createProperty(String name,
+                                      VideoProperty.Kind kind,
+                                      int minimum,
+                                      int maximum,
+                                      int step,
+                                      int defaultValue,
+                                      int value) {
+    return new VideoProperty(
+        CameraServerJNI.createSourceProperty(m_handle,
+            name,
+            kind.getValue(),
+            minimum,
+            maximum,
+            step,
+            defaultValue,
+            value));
+  }
+
+  /**
+   * Create an integer property.
+   *
+   * @param name Property name
+   * @param minimum Minimum value
+   * @param maximum Maximum value
+   * @param step Step value
+   * @param defaultValue Default value
+   * @param value Current value
+   * @return Property
+   */
+  public VideoProperty createIntegerProperty(String name,
+                                             int minimum,
+                                             int maximum,
+                                             int step,
+                                             int defaultValue,
+                                             int value) {
+    return new VideoProperty(
+        CameraServerJNI.createSourceProperty(m_handle,
+            name,
+            VideoProperty.Kind.kInteger.getValue(),
+            minimum,
+            maximum,
+            step,
+            defaultValue,
+            value));
+  }
+
+  /**
+   * Create a boolean property.
+   *
+   * @param name Property name
+   * @param defaultValue Default value
+   * @param value Current value
+   * @return Property
+   */
+  public VideoProperty createBooleanProperty(String name, boolean defaultValue, boolean value) {
+    return new VideoProperty(
+        CameraServerJNI.createSourceProperty(m_handle,
+            name,
+            VideoProperty.Kind.kBoolean.getValue(),
+            0,
+            1,
+            1,
+            defaultValue ? 1 : 0,
+            value ? 1 : 0));
+  }
+
+  /**
+   * Create a string property.
+   *
+   * @param name Property name
+   * @param value Current value
+   * @return Property
+   */
+  public VideoProperty createStringProperty(String name, String value) {
+    VideoProperty prop = new VideoProperty(
+        CameraServerJNI.createSourceProperty(m_handle,
+            name,
+            VideoProperty.Kind.kString.getValue(),
+            0,
+            0,
+            0,
+            0,
+            0));
+    prop.setString(value);
+    return prop;
+  }
+
+  /**
+   * Configure enum property choices.
+   *
+   * @param property Property
+   * @param choices Choices
+   */
+  public void setEnumPropertyChoices(VideoProperty property, String[] choices) {
+    CameraServerJNI.setSourceEnumPropertyChoices(m_handle, property.m_handle, choices);
+  }
+
+  /**
+   * Configure enum property choices.
+   *
+   * @param property Property
+   * @param choices Choices
+   * @deprecated Use {@code setEnumPropertyChoices} instead.
+   */
+  @Deprecated
+  @SuppressWarnings("MethodName")
+  public void SetEnumPropertyChoices(VideoProperty property, String[] choices) {
+    setEnumPropertyChoices(property, choices);
+  }
+}
diff --git a/cscore/src/main/java/edu/wpi/cscore/MjpegServer.java b/cscore/src/main/java/edu/wpi/cscore/MjpegServer.java
index fbbddf8..4c00aed 100644
--- a/cscore/src/main/java/edu/wpi/cscore/MjpegServer.java
+++ b/cscore/src/main/java/edu/wpi/cscore/MjpegServer.java
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
diff --git a/cscore/src/main/java/edu/wpi/cscore/UsbCameraInfo.java b/cscore/src/main/java/edu/wpi/cscore/UsbCameraInfo.java
index 42ef466..c3a8309 100644
--- a/cscore/src/main/java/edu/wpi/cscore/UsbCameraInfo.java
+++ b/cscore/src/main/java/edu/wpi/cscore/UsbCameraInfo.java
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -18,13 +18,18 @@
    * @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
+   * @param vendorId USB vendor id
+   * @param productId USB product id
    */
   @SuppressWarnings("PMD.ArrayIsStoredDirectly")
-  public UsbCameraInfo(int dev, String path, String name, String[] otherPaths) {
+  public UsbCameraInfo(int dev, String path, String name, String[] otherPaths, int vendorId,
+      int productId) {
     this.dev = dev;
     this.path = path;
     this.name = name;
     this.otherPaths = otherPaths;
+    this.vendorId = vendorId;
+    this.productId = productId;
   }
 
   /**
@@ -50,4 +55,16 @@
    */
   @SuppressWarnings("MemberName")
   public String[] otherPaths;
+
+  /**
+   * USB vendor id.
+   */
+  @SuppressWarnings("MemberName")
+  public int vendorId;
+
+  /**
+   * USB product id.
+   */
+  @SuppressWarnings("MemberName")
+  public int productId;
 }
diff --git a/cscore/src/main/java/edu/wpi/cscore/VideoListener.java b/cscore/src/main/java/edu/wpi/cscore/VideoListener.java
index 8dd62fa..2a35505 100644
--- a/cscore/src/main/java/edu/wpi/cscore/VideoListener.java
+++ b/cscore/src/main/java/edu/wpi/cscore/VideoListener.java
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -27,11 +27,6 @@
     m_handle = CameraServerJNI.addListener(listener, eventMask, immediateNotify);
   }
 
-  @Deprecated
-  public void free() {
-    close();
-  }
-
   @Override
   public synchronized void close() {
     if (m_handle != 0) {
diff --git a/cscore/src/main/java/edu/wpi/cscore/VideoSink.java b/cscore/src/main/java/edu/wpi/cscore/VideoSink.java
index a59a952..107f6d9 100644
--- a/cscore/src/main/java/edu/wpi/cscore/VideoSink.java
+++ b/cscore/src/main/java/edu/wpi/cscore/VideoSink.java
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -14,7 +14,7 @@
  */
 public class VideoSink implements AutoCloseable {
   public enum Kind {
-    kUnknown(0), kMjpeg(2), kCv(4);
+    kUnknown(0), kMjpeg(2), kCv(4), kRaw(8);
 
     @SuppressWarnings("MemberName")
     private final int value;
@@ -46,11 +46,6 @@
     m_handle = handle;
   }
 
-  @Deprecated
-  public void free() {
-    close();
-  }
-
   @Override
   public synchronized void close() {
     if (m_handle != 0) {
@@ -186,7 +181,7 @@
    * @return Connected source; nullptr if no source connected.
    */
   public VideoSource getSource() {
-    // While VideoSource.free() will call releaseSource(), getSinkSource()
+    // While VideoSource.close() will call releaseSource(), getSinkSource()
     // increments the internal reference count so this is okay to do.
     return new VideoSource(CameraServerJNI.getSinkSource(m_handle));
   }
diff --git a/cscore/src/main/java/edu/wpi/cscore/VideoSource.java b/cscore/src/main/java/edu/wpi/cscore/VideoSource.java
index cac344c..51d3821 100644
--- a/cscore/src/main/java/edu/wpi/cscore/VideoSource.java
+++ b/cscore/src/main/java/edu/wpi/cscore/VideoSource.java
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -14,7 +14,7 @@
  */
 public class VideoSource implements AutoCloseable {
   public enum Kind {
-    kUnknown(0), kUsb(1), kHttp(2), kCv(4);
+    kUnknown(0), kUsb(1), kHttp(2), kCv(4), kRaw(8);
 
     @SuppressWarnings("MemberName")
     private final int value;
@@ -81,11 +81,6 @@
     m_handle = handle;
   }
 
-  @Deprecated
-  public void free() {
-    close();
-  }
-
   @Override
   public synchronized void close() {
     if (m_handle != 0) {
diff --git a/cscore/src/main/java/edu/wpi/cscore/raw/RawFrame.java b/cscore/src/main/java/edu/wpi/cscore/raw/RawFrame.java
new file mode 100644
index 0000000..0e7a9ce
--- /dev/null
+++ b/cscore/src/main/java/edu/wpi/cscore/raw/RawFrame.java
@@ -0,0 +1,130 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018-2019 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.                                                               */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.cscore.raw;
+
+import java.nio.ByteBuffer;
+
+import edu.wpi.cscore.CameraServerJNI;
+
+/**
+ * Class for storing raw frame data between image read call.
+ *
+ * <p>Data is reused for each frame read, rather then reallocating every frame.
+ */
+public class RawFrame implements AutoCloseable {
+  private final long m_framePtr;
+  private ByteBuffer m_dataByteBuffer;
+  private long m_dataPtr;
+  private int m_totalData;
+  private int m_width;
+  private int m_height;
+  private int m_pixelFormat;
+
+  /**
+   * Construct a new RawFrame.
+   */
+  public RawFrame() {
+    m_framePtr = CameraServerJNI.allocateRawFrame();
+  }
+
+  /**
+   * Close the RawFrame, releasing native resources.
+   * Any images currently using the data will be invalidated.
+   */
+  @Override
+  public void close() {
+    CameraServerJNI.freeRawFrame(m_framePtr);
+  }
+
+  /**
+   * Called from JNI to set data in class.
+   */
+  public void setData(ByteBuffer dataByteBuffer, long dataPtr, int totalData,
+                      int width, int height, int pixelFormat) {
+    m_dataByteBuffer = dataByteBuffer;
+    m_dataPtr = dataPtr;
+    m_totalData = totalData;
+    m_width = width;
+    m_height = height;
+    m_pixelFormat = pixelFormat;
+  }
+
+  /**
+   * Get the pointer to native representation of this frame.
+   */
+  public long getFramePtr() {
+    return m_framePtr;
+  }
+
+  /**
+   * Get a ByteBuffer pointing to the frame data.
+   * This ByteBuffer is backed by the frame directly. Its lifetime is controlled by
+   * the frame. If a new frame gets read, it will overwrite the current one.
+   */
+  public ByteBuffer getDataByteBuffer() {
+    return m_dataByteBuffer;
+  }
+
+  /**
+   * Get a long (is a char* in native code) pointing to the frame data.
+   * This pointer is backed by the frame directly. Its lifetime is controlled by
+   * the frame. If a new frame gets read, it will overwrite the current one.
+   */
+  public long getDataPtr() {
+    return m_dataPtr;
+  }
+
+  /**
+   * Get the total length of the data stored in the frame.
+   */
+  public int getTotalData() {
+    return m_totalData;
+  }
+
+  /**
+   * Get the width of the frame.
+   */
+  public int getWidth() {
+    return m_width;
+  }
+
+  /**
+   * Set the width of the frame.
+   */
+  public void setWidth(int width) {
+    this.m_width = width;
+  }
+
+  /**
+   * Get the height of the frame.
+   */
+  public int getHeight() {
+    return m_height;
+  }
+
+  /**
+   * Set the height of the frame.
+   */
+  public void setHeight(int height) {
+    this.m_height = height;
+  }
+
+  /**
+   * Get the PixelFormat of the frame.
+   */
+  public int getPixelFormat() {
+    return m_pixelFormat;
+  }
+
+  /**
+   * Set the PixelFormat of the frame.
+   */
+  public void setPixelFormat(int pixelFormat) {
+    this.m_pixelFormat = pixelFormat;
+  }
+}
diff --git a/cscore/src/main/java/edu/wpi/cscore/raw/RawSink.java b/cscore/src/main/java/edu/wpi/cscore/raw/RawSink.java
new file mode 100644
index 0000000..535f356
--- /dev/null
+++ b/cscore/src/main/java/edu/wpi/cscore/raw/RawSink.java
@@ -0,0 +1,68 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018-2019 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.                                                               */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.cscore.raw;
+
+import edu.wpi.cscore.CameraServerJNI;
+import edu.wpi.cscore.ImageSink;
+
+/**
+ * A sink for user code to accept video frames as raw bytes.
+ *
+ * <p>This is a complex API, most cases should use CvSink.
+ */
+public class RawSink extends ImageSink {
+  /**
+   * Create a sink for accepting raw images.
+   *
+   * <p>grabFrame() must be called on the created sink to get each new
+   * image.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   */
+  public RawSink(String name) {
+    super(CameraServerJNI.createRawSink(name));
+  }
+
+  /**
+   * Wait for the next frame and get the image.
+   * Times out (returning 0) after 0.225 seconds.
+   * The provided image will have three 8-bit channels stored in BGR order.
+   *
+   * @return Frame time, or 0 on error (call getError() to obtain the error
+   *         message); the frame time is in the same time base as wpi::Now(),
+   *         and is in 1 us increments.
+   */
+  protected long grabFrame(RawFrame frame) {
+    return grabFrame(frame, 0.225);
+  }
+
+  /**
+   * Wait for the next frame and get the image.
+   * Times out (returning 0) after timeout seconds.
+   * The provided image will have three 8-bit channels stored in BGR order.
+   *
+   * @return Frame time, or 0 on error (call getError() to obtain the error
+   *         message); the frame time is in the same time base as wpi::Now(),
+   *         and is in 1 us increments.
+   */
+  protected long grabFrame(RawFrame frame, double timeout) {
+    return CameraServerJNI.grabSinkFrameTimeout(m_handle, frame, timeout);
+  }
+
+  /**
+   * Wait for the next frame and get the image.  May block forever.
+   * The provided image will have three 8-bit channels stored in BGR order.
+   *
+   * @return Frame time, or 0 on error (call getError() to obtain the error
+   *         message); the frame time is in the same time base as wpi::Now(),
+   *         and is in 1 us increments.
+   */
+  protected long grabFrameNoTimeout(RawFrame frame) {
+    return CameraServerJNI.grabSinkFrame(m_handle, frame);
+  }
+}
diff --git a/cscore/src/main/java/edu/wpi/cscore/raw/RawSource.java b/cscore/src/main/java/edu/wpi/cscore/raw/RawSource.java
new file mode 100644
index 0000000..9dfb3f3
--- /dev/null
+++ b/cscore/src/main/java/edu/wpi/cscore/raw/RawSource.java
@@ -0,0 +1,85 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018-2019 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.                                                               */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.cscore.raw;
+
+import edu.wpi.cscore.CameraServerJNI;
+import edu.wpi.cscore.ImageSource;
+import edu.wpi.cscore.VideoMode;
+
+/**
+ * A source for user code to provide video frames as raw bytes.
+ *
+ * <p>This is a complex API, most cases should use CvSource.
+ */
+public class RawSource extends ImageSource {
+  /**
+   * Create a raw frame source.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   * @param mode Video mode being generated
+   */
+  public RawSource(String name, VideoMode mode) {
+    super(CameraServerJNI.createRawSource(name,
+        mode.pixelFormat.getValue(),
+        mode.width, mode.height,
+        mode.fps));
+  }
+
+  /**
+   * Create a raw frame source.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   * @param pixelFormat Pixel format
+   * @param width width
+   * @param height height
+   * @param fps fps
+   */
+  public RawSource(String name, VideoMode.PixelFormat pixelFormat, int width, int height, int fps) {
+    super(CameraServerJNI.createRawSource(name,
+        pixelFormat.getValue(),
+        width, height,
+        fps));
+  }
+
+  /**
+   * Put a raw image and notify sinks.
+   *
+   * @param image raw frame image
+   */
+  protected void putFrame(RawFrame image) {
+    CameraServerJNI.putRawSourceFrame(m_handle, image);
+  }
+
+  /**
+   * Put a raw image and notify sinks.
+   *
+   * @param data raw frame data pointer
+   * @param width frame width
+   * @param height frame height
+   * @param pixelFormat pixel format
+   * @param totalData length of data in total
+   */
+  protected void putFrame(long data, int width, int height, int pixelFormat, int totalData) {
+    CameraServerJNI.putRawSourceFrame(m_handle, data, width, height, pixelFormat, totalData);
+  }
+
+  /**
+   * Put a raw image and notify sinks.
+   *
+   * @param data raw frame data pointer
+   * @param width frame width
+   * @param height frame height
+   * @param pixelFormat pixel format
+   * @param totalData length of data in total
+   */
+  protected void putFrame(long data, int width, int height, VideoMode.PixelFormat pixelFormat,
+                          int totalData) {
+    CameraServerJNI.putRawSourceFrame(m_handle, data, width, height, pixelFormat.getValue(),
+                                      totalData);
+  }
+}
diff --git a/cscore/src/main/native/cpp/ConfigurableSourceImpl.cpp b/cscore/src/main/native/cpp/ConfigurableSourceImpl.cpp
new file mode 100644
index 0000000..b974169
--- /dev/null
+++ b/cscore/src/main/native/cpp/ConfigurableSourceImpl.cpp
@@ -0,0 +1,109 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018-2019 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 "ConfigurableSourceImpl.h"
+
+#include <wpi/timestamp.h>
+
+#include "Handle.h"
+#include "Instance.h"
+#include "Log.h"
+#include "Notifier.h"
+
+using namespace cs;
+
+ConfigurableSourceImpl::ConfigurableSourceImpl(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);
+}
+
+ConfigurableSourceImpl::~ConfigurableSourceImpl() {}
+
+void ConfigurableSourceImpl::Start() {
+  m_notifier.NotifySource(*this, CS_SOURCE_CONNECTED);
+  m_notifier.NotifySource(*this, CS_SOURCE_VIDEOMODES_UPDATED);
+  m_notifier.NotifySourceVideoMode(*this, m_mode);
+}
+
+bool ConfigurableSourceImpl::SetVideoMode(const VideoMode& mode,
+                                          CS_Status* status) {
+  {
+    std::scoped_lock lock(m_mutex);
+    m_mode = mode;
+    m_videoModes[0] = mode;
+  }
+  m_notifier.NotifySourceVideoMode(*this, mode);
+  return true;
+}
+
+void ConfigurableSourceImpl::NumSinksChanged() {
+  // ignore
+}
+
+void ConfigurableSourceImpl::NumSinksEnabledChanged() {
+  // ignore
+}
+
+void ConfigurableSourceImpl::NotifyError(const wpi::Twine& msg) {
+  PutError(msg, wpi::Now());
+}
+
+int ConfigurableSourceImpl::CreateProperty(const wpi::Twine& name,
+                                           CS_PropertyKind kind, int minimum,
+                                           int maximum, int step,
+                                           int defaultValue, int value) {
+  std::scoped_lock lock(m_mutex);
+  int ndx = CreateOrUpdateProperty(name,
+                                   [=] {
+                                     return std::make_unique<PropertyImpl>(
+                                         name, kind, minimum, maximum, step,
+                                         defaultValue, value);
+                                   },
+                                   [&](PropertyImpl& prop) {
+                                     // update all but value
+                                     prop.propKind = kind;
+                                     prop.minimum = minimum;
+                                     prop.maximum = maximum;
+                                     prop.step = step;
+                                     prop.defaultValue = defaultValue;
+                                     value = prop.value;
+                                   });
+  m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, name, ndx,
+                                  kind, value, wpi::Twine{});
+  return ndx;
+}
+
+int ConfigurableSourceImpl::CreateProperty(
+    const wpi::Twine& name, CS_PropertyKind kind, int minimum, int maximum,
+    int step, int defaultValue, int value,
+    std::function<void(CS_Property property)> onChange) {
+  // TODO
+  return 0;
+}
+
+void ConfigurableSourceImpl::SetEnumPropertyChoices(
+    int property, wpi::ArrayRef<std::string> choices, CS_Status* status) {
+  std::scoped_lock lock(m_mutex);
+  auto prop = GetProperty(property);
+  if (!prop) {
+    *status = CS_INVALID_PROPERTY;
+    return;
+  }
+  if (prop->propKind != CS_PROP_ENUM) {
+    *status = CS_WRONG_PROPERTY_TYPE;
+    return;
+  }
+  prop->enumChoices = choices;
+  m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED,
+                                  prop->name, property, CS_PROP_ENUM,
+                                  prop->value, wpi::Twine{});
+}
diff --git a/cscore/src/main/native/cpp/ConfigurableSourceImpl.h b/cscore/src/main/native/cpp/ConfigurableSourceImpl.h
new file mode 100644
index 0000000..a10f27e
--- /dev/null
+++ b/cscore/src/main/native/cpp/ConfigurableSourceImpl.h
@@ -0,0 +1,56 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2016-2019 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_CONFIGURABLESOURCEIMPL_H_
+#define CSCORE_CONFIGURABLESOURCEIMPL_H_
+
+#include <atomic>
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <wpi/ArrayRef.h>
+#include <wpi/Twine.h>
+
+#include "SourceImpl.h"
+
+namespace cs {
+
+class ConfigurableSourceImpl : public SourceImpl {
+ protected:
+  ConfigurableSourceImpl(const wpi::Twine& name, wpi::Logger& logger,
+                         Notifier& notifier, Telemetry& telemetry,
+                         const VideoMode& mode);
+
+ public:
+  ~ConfigurableSourceImpl() override;
+
+  void Start() override;
+
+  bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;
+
+  void NumSinksChanged() override;
+  void NumSinksEnabledChanged() override;
+
+  // OpenCV-specific functions
+  void NotifyError(const wpi::Twine& msg);
+  int CreateProperty(const wpi::Twine& name, CS_PropertyKind kind, int minimum,
+                     int maximum, int step, int defaultValue, int value);
+  int CreateProperty(const wpi::Twine& name, CS_PropertyKind kind, int minimum,
+                     int maximum, int step, int defaultValue, int value,
+                     std::function<void(CS_Property property)> onChange);
+  void SetEnumPropertyChoices(int property, wpi::ArrayRef<std::string> choices,
+                              CS_Status* status);
+
+ private:
+  std::atomic_bool m_connected{true};
+};
+
+}  // namespace cs
+
+#endif  // CSCORE_CONFIGURABLESOURCEIMPL_H_
diff --git a/cscore/src/main/native/cpp/CvSinkImpl.cpp b/cscore/src/main/native/cpp/CvSinkImpl.cpp
index 17dbd18..7ba505a 100644
--- a/cscore/src/main/native/cpp/CvSinkImpl.cpp
+++ b/cscore/src/main/native/cpp/CvSinkImpl.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -138,10 +138,12 @@
                                                inst.telemetry, processFrame));
 }
 
+static constexpr unsigned SinkMask = CS_SINK_CV | CS_SINK_RAW;
+
 void SetSinkDescription(CS_Sink sink, const wpi::Twine& description,
                         CS_Status* status) {
   auto data = Instance::GetInstance().GetSink(sink);
-  if (!data || data->kind != CS_SINK_CV) {
+  if (!data || (data->kind & SinkMask) == 0) {
     *status = CS_INVALID_HANDLE;
     return;
   }
@@ -169,7 +171,7 @@
 
 std::string GetSinkError(CS_Sink sink, CS_Status* status) {
   auto data = Instance::GetInstance().GetSink(sink);
-  if (!data || data->kind != CS_SINK_CV) {
+  if (!data || (data->kind & SinkMask) == 0) {
     *status = CS_INVALID_HANDLE;
     return std::string{};
   }
@@ -179,7 +181,7 @@
 wpi::StringRef GetSinkError(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
                             CS_Status* status) {
   auto data = Instance::GetInstance().GetSink(sink);
-  if (!data || data->kind != CS_SINK_CV) {
+  if (!data || (data->kind & SinkMask) == 0) {
     *status = CS_INVALID_HANDLE;
     return wpi::StringRef{};
   }
@@ -188,7 +190,7 @@
 
 void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) {
   auto data = Instance::GetInstance().GetSink(sink);
-  if (!data || data->kind != CS_SINK_CV) {
+  if (!data || (data->kind & SinkMask) == 0) {
     *status = CS_INVALID_HANDLE;
     return;
   }
@@ -215,6 +217,7 @@
   return cs::SetSinkDescription(sink, description, status);
 }
 
+#if CV_VERSION_MAJOR < 4
 uint64_t CS_GrabSinkFrame(CS_Sink sink, struct CvMat* image,
                           CS_Status* status) {
   auto mat = cv::cvarrToMat(image);
@@ -226,6 +229,7 @@
   auto mat = cv::cvarrToMat(image);
   return cs::GrabSinkFrameTimeout(sink, mat, timeout, status);
 }
+#endif  // CV_VERSION_MAJOR < 4
 
 uint64_t CS_GrabSinkFrameCpp(CS_Sink sink, cv::Mat* image, CS_Status* status) {
   return cs::GrabSinkFrame(sink, *image, status);
diff --git a/cscore/src/main/native/cpp/CvSourceImpl.cpp b/cscore/src/main/native/cpp/CvSourceImpl.cpp
index 449a9ee..49b9d28 100644
--- a/cscore/src/main/native/cpp/CvSourceImpl.cpp
+++ b/cscore/src/main/native/cpp/CvSourceImpl.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -25,37 +25,10 @@
 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);
-}
+    : ConfigurableSourceImpl{name, logger, notifier, telemetry, mode} {}
 
 CvSourceImpl::~CvSourceImpl() {}
 
-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) {
-  {
-    std::lock_guard<wpi::mutex> lock(m_mutex);
-    m_mode = mode;
-    m_videoModes[0] = mode;
-  }
-  m_notifier.NotifySourceVideoMode(*this, mode);
-  return true;
-}
-
-void CvSourceImpl::NumSinksChanged() {
-  // ignore
-}
-
-void CvSourceImpl::NumSinksEnabledChanged() {
-  // ignore
-}
-
 void CvSourceImpl::PutFrame(cv::Mat& image) {
   // We only support 8-bit images; convert if necessary.
   cv::Mat finalImage;
@@ -89,61 +62,6 @@
   SourceImpl::PutFrame(std::move(dest), wpi::Now());
 }
 
-void CvSourceImpl::NotifyError(const wpi::Twine& msg) {
-  PutError(msg, wpi::Now());
-}
-
-int CvSourceImpl::CreateProperty(const wpi::Twine& name, CS_PropertyKind kind,
-                                 int minimum, int maximum, int step,
-                                 int defaultValue, int value) {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
-  int ndx = CreateOrUpdateProperty(name,
-                                   [=] {
-                                     return wpi::make_unique<PropertyImpl>(
-                                         name, kind, minimum, maximum, step,
-                                         defaultValue, value);
-                                   },
-                                   [&](PropertyImpl& prop) {
-                                     // update all but value
-                                     prop.propKind = kind;
-                                     prop.minimum = minimum;
-                                     prop.maximum = maximum;
-                                     prop.step = step;
-                                     prop.defaultValue = defaultValue;
-                                     value = prop.value;
-                                   });
-  m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, name, ndx,
-                                  kind, value, wpi::Twine{});
-  return ndx;
-}
-
-int CvSourceImpl::CreateProperty(
-    const wpi::Twine& name, CS_PropertyKind kind, int minimum, int maximum,
-    int step, int defaultValue, int value,
-    std::function<void(CS_Property property)> onChange) {
-  // TODO
-  return 0;
-}
-
-void CvSourceImpl::SetEnumPropertyChoices(int property,
-                                          wpi::ArrayRef<std::string> choices,
-                                          CS_Status* status) {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
-  auto prop = GetProperty(property);
-  if (!prop) {
-    *status = CS_INVALID_PROPERTY;
-    return;
-  }
-  if (prop->propKind != CS_PROP_ENUM) {
-    *status = CS_WRONG_PROPERTY_TYPE;
-    return;
-  }
-  prop->enumChoices = choices;
-  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,
@@ -163,10 +81,12 @@
   static_cast<CvSourceImpl&>(*data->source).PutFrame(image);
 }
 
+static constexpr unsigned SourceMask = CS_SINK_CV | CS_SINK_RAW;
+
 void NotifySourceError(CS_Source source, const wpi::Twine& msg,
                        CS_Status* status) {
   auto data = Instance::GetInstance().GetSource(source);
-  if (!data || data->kind != CS_SOURCE_CV) {
+  if (!data || (data->kind & SourceMask) == 0) {
     *status = CS_INVALID_HANDLE;
     return;
   }
@@ -175,7 +95,7 @@
 
 void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) {
   auto data = Instance::GetInstance().GetSource(source);
-  if (!data || data->kind != CS_SOURCE_CV) {
+  if (!data || (data->kind & SourceMask) == 0) {
     *status = CS_INVALID_HANDLE;
     return;
   }
@@ -185,7 +105,7 @@
 void SetSourceDescription(CS_Source source, const wpi::Twine& description,
                           CS_Status* status) {
   auto data = Instance::GetInstance().GetSource(source);
-  if (!data || data->kind != CS_SOURCE_CV) {
+  if (!data || (data->kind & SourceMask) == 0) {
     *status = CS_INVALID_HANDLE;
     return;
   }
@@ -197,7 +117,7 @@
                                  int step, int defaultValue, int value,
                                  CS_Status* status) {
   auto data = Instance::GetInstance().GetSource(source);
-  if (!data || data->kind != CS_SOURCE_CV) {
+  if (!data || (data->kind & SourceMask) == 0) {
     *status = CS_INVALID_HANDLE;
     return -1;
   }
@@ -212,7 +132,7 @@
     int maximum, int step, int defaultValue, int value,
     std::function<void(CS_Property property)> onChange, CS_Status* status) {
   auto data = Instance::GetInstance().GetSource(source);
-  if (!data || data->kind != CS_SOURCE_CV) {
+  if (!data || (data->kind & SourceMask) == 0) {
     *status = CS_INVALID_HANDLE;
     return -1;
   }
@@ -226,7 +146,7 @@
                                   wpi::ArrayRef<std::string> choices,
                                   CS_Status* status) {
   auto data = Instance::GetInstance().GetSource(source);
-  if (!data || data->kind != CS_SOURCE_CV) {
+  if (!data || (data->kind & SourceMask) == 0) {
     *status = CS_INVALID_HANDLE;
     return;
   }
@@ -258,11 +178,13 @@
                             status);
 }
 
+#if CV_VERSION_MAJOR < 4
 void CS_PutSourceFrame(CS_Source source, struct CvMat* image,
                        CS_Status* status) {
   auto mat = cv::cvarrToMat(image);
   return cs::PutSourceFrame(source, mat, status);
 }
+#endif  // CV_VERSION_MAJOR < 4
 
 void CS_PutSourceFrameCpp(CS_Source source, cv::Mat* image, CS_Status* status) {
   return cs::PutSourceFrame(source, *image, status);
diff --git a/cscore/src/main/native/cpp/CvSourceImpl.h b/cscore/src/main/native/cpp/CvSourceImpl.h
index e1a9cd2..978d012 100644
--- a/cscore/src/main/native/cpp/CvSourceImpl.h
+++ b/cscore/src/main/native/cpp/CvSourceImpl.h
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -18,33 +18,19 @@
 #include <wpi/ArrayRef.h>
 #include <wpi/Twine.h>
 
+#include "ConfigurableSourceImpl.h"
 #include "SourceImpl.h"
 
 namespace cs {
 
-class CvSourceImpl : public SourceImpl {
+class CvSourceImpl : public ConfigurableSourceImpl {
  public:
   CvSourceImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
                Telemetry& telemetry, const VideoMode& mode);
   ~CvSourceImpl() override;
 
-  void Start() override;
-
-  bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;
-
-  void NumSinksChanged() override;
-  void NumSinksEnabledChanged() override;
-
   // OpenCV-specific functions
   void PutFrame(cv::Mat& image);
-  void NotifyError(const wpi::Twine& msg);
-  int CreateProperty(const wpi::Twine& name, CS_PropertyKind kind, int minimum,
-                     int maximum, int step, int defaultValue, int value);
-  int CreateProperty(const wpi::Twine& name, CS_PropertyKind kind, int minimum,
-                     int maximum, int step, int defaultValue, int value,
-                     std::function<void(CS_Property property)> onChange);
-  void SetEnumPropertyChoices(int property, wpi::ArrayRef<std::string> choices,
-                              CS_Status* status);
 
  private:
   std::atomic_bool m_connected{true};
diff --git a/cscore/src/main/native/cpp/Frame.cpp b/cscore/src/main/native/cpp/Frame.cpp
index 15f9b2d..466cb52 100644
--- a/cscore/src/main/native/cpp/Frame.cpp
+++ b/cscore/src/main/native/cpp/Frame.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -36,7 +36,7 @@
 
 Image* Frame::GetNearestImage(int width, int height) const {
   if (!m_impl) return nullptr;
-  std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+  std::scoped_lock lock(m_impl->mutex);
   Image* found = nullptr;
 
   // Ideally we want the smallest image at least width/height in size
@@ -60,7 +60,7 @@
                               VideoMode::PixelFormat pixelFormat,
                               int jpegQuality) const {
   if (!m_impl) return nullptr;
-  std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+  std::scoped_lock lock(m_impl->mutex);
   Image* found = nullptr;
 
   // We want the smallest image at least width/height (or the next largest),
@@ -253,7 +253,7 @@
   // Save the result
   Image* rv = newImage.release();
   if (m_impl) {
-    std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+    std::scoped_lock lock(m_impl->mutex);
     m_impl->images.push_back(rv);
   }
   return rv;
@@ -274,7 +274,7 @@
   // Save the result
   Image* rv = newImage.release();
   if (m_impl) {
-    std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+    std::scoped_lock lock(m_impl->mutex);
     m_impl->images.push_back(rv);
   }
   return rv;
@@ -294,7 +294,7 @@
   // Save the result
   Image* rv = newImage.release();
   if (m_impl) {
-    std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+    std::scoped_lock lock(m_impl->mutex);
     m_impl->images.push_back(rv);
   }
   return rv;
@@ -314,7 +314,7 @@
   // Save the result
   Image* rv = newImage.release();
   if (m_impl) {
-    std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+    std::scoped_lock lock(m_impl->mutex);
     m_impl->images.push_back(rv);
   }
   return rv;
@@ -334,7 +334,7 @@
   // Save the result
   Image* rv = newImage.release();
   if (m_impl) {
-    std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+    std::scoped_lock lock(m_impl->mutex);
     m_impl->images.push_back(rv);
   }
   return rv;
@@ -354,14 +354,14 @@
   // Save the result
   Image* rv = newImage.release();
   if (m_impl) {
-    std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+    std::scoped_lock lock(m_impl->mutex);
     m_impl->images.push_back(rv);
   }
   return rv;
 }
 
 Image* Frame::ConvertGrayToBGR(Image* image) {
-  if (!image || image->pixelFormat != VideoMode::kBGR) return nullptr;
+  if (!image || image->pixelFormat != VideoMode::kGray) return nullptr;
 
   // Allocate a BGR image
   auto newImage =
@@ -374,7 +374,7 @@
   // Save the result
   Image* rv = newImage.release();
   if (m_impl) {
-    std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+    std::scoped_lock lock(m_impl->mutex);
     m_impl->images.push_back(rv);
   }
   return rv;
@@ -383,7 +383,7 @@
 Image* Frame::ConvertBGRToMJPEG(Image* image, int quality) {
   if (!image || image->pixelFormat != VideoMode::kBGR) return nullptr;
   if (!m_impl) return nullptr;
-  std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+  std::scoped_lock lock(m_impl->mutex);
 
   // Allocate a JPEG image.  We don't actually know what the resulting size
   // will be; while the destination will automatically grow, doing so will
@@ -397,7 +397,7 @@
 
   // Compress
   if (m_impl->compressionParams.empty()) {
-    m_impl->compressionParams.push_back(CV_IMWRITE_JPEG_QUALITY);
+    m_impl->compressionParams.push_back(cv::IMWRITE_JPEG_QUALITY);
     m_impl->compressionParams.push_back(quality);
   } else {
     m_impl->compressionParams[1] = quality;
@@ -414,7 +414,7 @@
 Image* Frame::ConvertGrayToMJPEG(Image* image, int quality) {
   if (!image || image->pixelFormat != VideoMode::kGray) return nullptr;
   if (!m_impl) return nullptr;
-  std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+  std::scoped_lock lock(m_impl->mutex);
 
   // Allocate a JPEG image.  We don't actually know what the resulting size
   // will be; while the destination will automatically grow, doing so will
@@ -428,7 +428,7 @@
 
   // Compress
   if (m_impl->compressionParams.empty()) {
-    m_impl->compressionParams.push_back(CV_IMWRITE_JPEG_QUALITY);
+    m_impl->compressionParams.push_back(cv::IMWRITE_JPEG_QUALITY);
     m_impl->compressionParams.push_back(quality);
   } else {
     m_impl->compressionParams[1] = quality;
@@ -446,7 +446,7 @@
                            VideoMode::PixelFormat pixelFormat,
                            int requiredJpegQuality, int defaultJpegQuality) {
   if (!m_impl) return nullptr;
-  std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+  std::scoped_lock lock(m_impl->mutex);
   Image* cur = GetNearestImage(width, height, pixelFormat, requiredJpegQuality);
   if (!cur || cur->Is(width, height, pixelFormat, requiredJpegQuality))
     return cur;
diff --git a/cscore/src/main/native/cpp/Frame.h b/cscore/src/main/native/cpp/Frame.h
index b977f24..07fa24f 100644
--- a/cscore/src/main/native/cpp/Frame.h
+++ b/cscore/src/main/native/cpp/Frame.h
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -80,42 +80,42 @@
 
   int GetOriginalWidth() const {
     if (!m_impl) return 0;
-    std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+    std::scoped_lock lock(m_impl->mutex);
     if (m_impl->images.empty()) return 0;
     return m_impl->images[0]->width;
   }
 
   int GetOriginalHeight() const {
     if (!m_impl) return 0;
-    std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+    std::scoped_lock lock(m_impl->mutex);
     if (m_impl->images.empty()) return 0;
     return m_impl->images[0]->height;
   }
 
   int GetOriginalPixelFormat() const {
     if (!m_impl) return 0;
-    std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+    std::scoped_lock lock(m_impl->mutex);
     if (m_impl->images.empty()) return 0;
     return m_impl->images[0]->pixelFormat;
   }
 
   int GetOriginalJpegQuality() const {
     if (!m_impl) return 0;
-    std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+    std::scoped_lock lock(m_impl->mutex);
     if (m_impl->images.empty()) return 0;
     return m_impl->images[0]->jpegQuality;
   }
 
   Image* GetExistingImage(size_t i = 0) const {
     if (!m_impl) return nullptr;
-    std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+    std::scoped_lock lock(m_impl->mutex);
     if (i >= m_impl->images.size()) return nullptr;
     return m_impl->images[i];
   }
 
   Image* GetExistingImage(int width, int height) const {
     if (!m_impl) return nullptr;
-    std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+    std::scoped_lock lock(m_impl->mutex);
     for (auto i : m_impl->images) {
       if (i->Is(width, height)) return i;
     }
@@ -125,7 +125,7 @@
   Image* GetExistingImage(int width, int height,
                           VideoMode::PixelFormat pixelFormat) const {
     if (!m_impl) return nullptr;
-    std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+    std::scoped_lock lock(m_impl->mutex);
     for (auto i : m_impl->images) {
       if (i->Is(width, height, pixelFormat)) return i;
     }
@@ -136,7 +136,7 @@
                           VideoMode::PixelFormat pixelFormat,
                           int jpegQuality) const {
     if (!m_impl) return nullptr;
-    std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
+    std::scoped_lock lock(m_impl->mutex);
     for (auto i : m_impl->images) {
       if (i->Is(width, height, pixelFormat, jpegQuality)) return i;
     }
diff --git a/cscore/src/main/native/cpp/HttpCameraImpl.cpp b/cscore/src/main/native/cpp/HttpCameraImpl.cpp
index f15fc5f..5bf7a8a 100644
--- a/cscore/src/main/native/cpp/HttpCameraImpl.cpp
+++ b/cscore/src/main/native/cpp/HttpCameraImpl.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -7,9 +7,8 @@
 
 #include "HttpCameraImpl.h"
 
-#include <wpi/STLExtras.h>
+#include <wpi/MemAlloc.h>
 #include <wpi/TCPConnector.h>
-#include <wpi/memory.h>
 #include <wpi/timestamp.h>
 
 #include "Handle.h"
@@ -38,7 +37,7 @@
 
   // Close file if it's open
   {
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     if (m_streamConn) m_streamConn->stream->close();
     if (m_settingsConn) m_settingsConn->stream->close();
   }
@@ -65,7 +64,7 @@
 
 void HttpCameraImpl::MonitorThreadMain() {
   while (m_active) {
-    std::unique_lock<wpi::mutex> lock(m_mutex);
+    std::unique_lock lock(m_mutex);
     // sleep for 1 second between checks
     m_monitorCond.wait_for(lock, std::chrono::seconds(1),
                            [=] { return !m_active; });
@@ -96,7 +95,7 @@
 
     // disconnect if not enabled
     if (!IsEnabled()) {
-      std::unique_lock<wpi::mutex> lock(m_mutex);
+      std::unique_lock lock(m_mutex);
       if (m_streamConn) m_streamConn->stream->close();
       // Wait for enable
       m_sinkEnabledCond.wait(lock, [=] { return !m_active || IsEnabled(); });
@@ -118,7 +117,7 @@
     // stream
     DeviceStream(conn->is, boundary);
     {
-      std::unique_lock<wpi::mutex> lock(m_mutex);
+      std::unique_lock lock(m_mutex);
       m_streamConn = nullptr;
     }
   }
@@ -132,7 +131,7 @@
   // Build the request
   wpi::HttpRequest req;
   {
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     if (m_locations.empty()) {
       SERROR("locations array is empty!?");
       std::this_thread::sleep_for(std::chrono::seconds(1));
@@ -149,12 +148,12 @@
 
   if (!m_active || !stream) return nullptr;
 
-  auto connPtr = wpi::make_unique<wpi::HttpConnection>(std::move(stream), 1);
+  auto connPtr = std::make_unique<wpi::HttpConnection>(std::move(stream), 1);
   wpi::HttpConnection* conn = connPtr.get();
 
   // update m_streamConn
   {
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     m_frameCount = 1;  // avoid a race with monitor thread
     m_streamConn = std::move(connPtr);
   }
@@ -162,7 +161,7 @@
   std::string warn;
   if (!conn->Handshake(req, &warn)) {
     SWARNING(GetName() << ": " << warn);
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     m_streamConn = nullptr;
     return nullptr;
   }
@@ -174,7 +173,7 @@
   if (mediaType != "multipart/x-mixed-replace") {
     SWARNING("\"" << req.host << "\": unrecognized Content-Type \"" << mediaType
                   << "\"");
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     m_streamConn = nullptr;
     return nullptr;
   }
@@ -189,6 +188,9 @@
     std::tie(key, value) = keyvalue.split('=');
     if (key.trim() == "boundary") {
       value = value.trim().trim('"');  // value may be quoted
+      if (value.startswith("--")) {
+        value = value.substr(2);
+      }
       boundary.append(value.begin(), value.end());
     }
   }
@@ -196,7 +198,7 @@
   if (boundary.empty()) {
     SWARNING("\"" << req.host
                   << "\": empty multi-part boundary or no Content-Type");
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     m_streamConn = nullptr;
     return nullptr;
   }
@@ -219,11 +221,16 @@
     if (!FindMultipartBoundary(is, boundary, nullptr)) break;
 
     // Read the next two characters after the boundary (normally \r\n)
+    // Handle just \n for LabVIEW however
     char eol[2];
-    is.read(eol, 2);
+    is.read(eol, 1);
     if (!m_active || is.has_error()) break;
-    // End-of-stream is indicated with trailing --
-    if (eol[0] == '-' && eol[1] == '-') break;
+    if (eol[0] != '\n') {
+      is.read(eol + 1, 1);
+      if (!m_active || is.has_error()) break;
+      // End-of-stream is indicated with trailing --
+      if (eol[0] == '-' && eol[1] == '-') break;
+    }
 
     if (!DeviceStreamFrame(is, imageBuf))
       ++numErrors;
@@ -291,7 +298,7 @@
   for (;;) {
     wpi::HttpRequest req;
     {
-      std::unique_lock<wpi::mutex> lock(m_mutex);
+      std::unique_lock lock(m_mutex);
       m_settingsCond.wait(lock, [=] {
         return !m_active || (m_prefLocation != -1 && !m_settings.empty());
       });
@@ -314,12 +321,12 @@
 
   if (!m_active || !stream) return;
 
-  auto connPtr = wpi::make_unique<wpi::HttpConnection>(std::move(stream), 1);
+  auto connPtr = std::make_unique<wpi::HttpConnection>(std::move(stream), 1);
   wpi::HttpConnection* conn = connPtr.get();
 
   // update m_settingsConn
   {
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     m_settingsConn = std::move(connPtr);
   }
 
@@ -331,7 +338,7 @@
 }
 
 CS_HttpCameraKind HttpCameraImpl::GetKind() const {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   return m_kind;
 }
 
@@ -349,7 +356,7 @@
     }
   }
 
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   m_locations.swap(locations);
   m_nextLocation = 0;
   m_streamSettingsUpdated = true;
@@ -357,7 +364,7 @@
 }
 
 std::vector<std::string> HttpCameraImpl::GetUrls() const {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   std::vector<std::string> urls;
   for (const auto& loc : m_locations) urls.push_back(loc.url);
   return urls;
@@ -368,8 +375,8 @@
                                     bool viaSettings, CS_PropertyKind kind,
                                     int minimum, int maximum, int step,
                                     int defaultValue, int value) const {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
-  m_propertyData.emplace_back(wpi::make_unique<PropertyData>(
+  std::scoped_lock lock(m_mutex);
+  m_propertyData.emplace_back(std::make_unique<PropertyData>(
       name, httpParam, viaSettings, kind, minimum, maximum, step, defaultValue,
       value));
 
@@ -382,8 +389,8 @@
 void HttpCameraImpl::CreateEnumProperty(
     const wpi::Twine& name, const wpi::Twine& httpParam, bool viaSettings,
     int defaultValue, int value, std::initializer_list<T> choices) const {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
-  m_propertyData.emplace_back(wpi::make_unique<PropertyData>(
+  std::scoped_lock lock(m_mutex);
+  m_propertyData.emplace_back(std::make_unique<PropertyData>(
       name, httpParam, viaSettings, CS_PROP_ENUM, 0, choices.size() - 1, 1,
       defaultValue, value));
 
@@ -401,11 +408,11 @@
 
 std::unique_ptr<PropertyImpl> HttpCameraImpl::CreateEmptyProperty(
     const wpi::Twine& name) const {
-  return wpi::make_unique<PropertyData>(name);
+  return std::make_unique<PropertyData>(name);
 }
 
 bool HttpCameraImpl::CacheProperties(CS_Status* status) const {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
 
   // Pretty typical set of video modes
   m_videoModes.clear();
@@ -461,7 +468,7 @@
 
 bool HttpCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
   if (mode.pixelFormat != VideoMode::kMJPEG) return false;
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   m_mode = mode;
   m_streamSettingsUpdated = true;
   return true;
@@ -490,7 +497,7 @@
                  true, CS_PROP_INTEGER, 0, 100, 1, 50, 50);
 
   // TODO: get video modes from device
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   m_videoModes.clear();
   m_videoModes.emplace_back(VideoMode::kMJPEG, 640, 480, 30);
   m_videoModes.emplace_back(VideoMode::kMJPEG, 480, 360, 30);
@@ -603,7 +610,7 @@
 char** CS_GetHttpCameraUrls(CS_Source source, int* count, CS_Status* status) {
   auto urls = cs::GetHttpCameraUrls(source, status);
   char** out =
-      static_cast<char**>(wpi::CheckedMalloc(urls.size() * sizeof(char*)));
+      static_cast<char**>(wpi::safe_malloc(urls.size() * sizeof(char*)));
   *count = urls.size();
   for (size_t i = 0; i < urls.size(); ++i) out[i] = cs::ConvertToC(urls[i]);
   return out;
diff --git a/cscore/src/main/native/cpp/Log.h b/cscore/src/main/native/cpp/Log.h
index 9ca0e1b..b9fd9ab 100644
--- a/cscore/src/main/native/cpp/Log.h
+++ b/cscore/src/main/native/cpp/Log.h
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -17,7 +17,7 @@
 #define WARNING(x) WPI_WARNING(m_logger, x)
 #define INFO(x) WPI_INFO(m_logger, x)
 
-#define DEBUG(x) WPI_DEBUG(m_logger, x)
+#define DEBUG0(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)
@@ -27,7 +27,7 @@
 #define SWARNING(x) WARNING(GetName() << ": " << x)
 #define SINFO(x) INFO(GetName() << ": " << x)
 
-#define SDEBUG(x) DEBUG(GetName() << ": " << x)
+#define SDEBUG(x) DEBUG0(GetName() << ": " << x)
 #define SDEBUG1(x) DEBUG1(GetName() << ": " << x)
 #define SDEBUG2(x) DEBUG2(GetName() << ": " << x)
 #define SDEBUG3(x) DEBUG3(GetName() << ": " << x)
diff --git a/cscore/src/main/native/cpp/MjpegServerImpl.cpp b/cscore/src/main/native/cpp/MjpegServerImpl.cpp
index f548f9c..e30b745 100644
--- a/cscore/src/main/native/cpp/MjpegServerImpl.cpp
+++ b/cscore/src/main/native/cpp/MjpegServerImpl.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -105,18 +105,18 @@
   wpi::StringRef GetName() { return m_name; }
 
   std::shared_ptr<SourceImpl> GetSource() {
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     return m_source;
   }
 
   void StartStream() {
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     if (m_source) m_source->EnableSink();
     m_streaming = true;
   }
 
   void StopStream() {
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     if (m_source) m_source->DisableSink();
     m_streaming = false;
   }
@@ -865,7 +865,7 @@
 
 // worker thread for clients that connected to this server
 void MjpegServerImpl::ConnThread::Main() {
-  std::unique_lock<wpi::mutex> lock(m_mutex);
+  std::unique_lock lock(m_mutex);
   while (m_active) {
     while (!m_stream) {
       m_cond.wait(lock);
@@ -898,7 +898,7 @@
 
     auto source = GetSource();
 
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     // Find unoccupied worker thread, or create one if necessary
     auto it = std::find_if(m_connThreads.begin(), m_connThreads.end(),
                            [](const wpi::SafeThreadOwner<ConnThread>& owner) {
@@ -937,7 +937,7 @@
 }
 
 void MjpegServerImpl::SetSourceImpl(std::shared_ptr<SourceImpl> source) {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   for (auto& connThread : m_connThreads) {
     if (auto thr = connThread.GetThread()) {
       if (thr->m_source != source) {
diff --git a/cscore/src/main/native/cpp/Notifier.cpp b/cscore/src/main/native/cpp/Notifier.cpp
index 45bd050..e4645c9 100644
--- a/cscore/src/main/native/cpp/Notifier.cpp
+++ b/cscore/src/main/native/cpp/Notifier.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2015-2019 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.                                                               */
@@ -102,7 +102,7 @@
 void Notifier::Thread::Main() {
   if (m_on_start) m_on_start();
 
-  std::unique_lock<wpi::mutex> lock(m_mutex);
+  std::unique_lock lock(m_mutex);
   while (m_active) {
     while (m_notifications.empty()) {
       m_cond.wait(lock);
diff --git a/cscore/src/main/native/cpp/PropertyContainer.cpp b/cscore/src/main/native/cpp/PropertyContainer.cpp
index 46ea77d..17bf94b 100644
--- a/cscore/src/main/native/cpp/PropertyContainer.cpp
+++ b/cscore/src/main/native/cpp/PropertyContainer.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -18,7 +18,7 @@
   // We can't fail, so instead we create a new index if caching fails.
   CS_Status status = 0;
   if (!m_properties_cached) CacheProperties(&status);
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   wpi::SmallVector<char, 64> nameBuf;
   int& ndx = m_properties[name.toStringRef(nameBuf)];
   if (ndx == 0) {
@@ -33,7 +33,7 @@
     wpi::SmallVectorImpl<int>& vec, CS_Status* status) const {
   if (!m_properties_cached && !CacheProperties(status))
     return wpi::ArrayRef<int>{};
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   for (int i = 0; i < static_cast<int>(m_propertyData.size()); ++i) {
     if (m_propertyData[i]) vec.push_back(i + 1);
   }
@@ -43,7 +43,7 @@
 CS_PropertyKind PropertyContainer::GetPropertyKind(int property) const {
   CS_Status status = 0;
   if (!m_properties_cached && !CacheProperties(&status)) return CS_PROP_NONE;
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   auto prop = GetProperty(property);
   if (!prop) return CS_PROP_NONE;
   return prop->propKind;
@@ -52,7 +52,7 @@
 wpi::StringRef PropertyContainer::GetPropertyName(
     int property, wpi::SmallVectorImpl<char>& buf, CS_Status* status) const {
   if (!m_properties_cached && !CacheProperties(status)) return wpi::StringRef{};
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   auto prop = GetProperty(property);
   if (!prop) {
     *status = CS_INVALID_PROPERTY;
@@ -64,7 +64,7 @@
 
 int PropertyContainer::GetProperty(int property, CS_Status* status) const {
   if (!m_properties_cached && !CacheProperties(status)) return 0;
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   auto prop = GetProperty(property);
   if (!prop) {
     *status = CS_INVALID_PROPERTY;
@@ -80,7 +80,7 @@
 
 void PropertyContainer::SetProperty(int property, int value,
                                     CS_Status* status) {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   auto prop = GetProperty(property);
   if (!prop) {
     *status = CS_INVALID_PROPERTY;
@@ -101,7 +101,7 @@
 
 int PropertyContainer::GetPropertyMin(int property, CS_Status* status) const {
   if (!m_properties_cached && !CacheProperties(status)) return 0;
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   auto prop = GetProperty(property);
   if (!prop) {
     *status = CS_INVALID_PROPERTY;
@@ -112,7 +112,7 @@
 
 int PropertyContainer::GetPropertyMax(int property, CS_Status* status) const {
   if (!m_properties_cached && !CacheProperties(status)) return 0;
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   auto prop = GetProperty(property);
   if (!prop) {
     *status = CS_INVALID_PROPERTY;
@@ -123,7 +123,7 @@
 
 int PropertyContainer::GetPropertyStep(int property, CS_Status* status) const {
   if (!m_properties_cached && !CacheProperties(status)) return 0;
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   auto prop = GetProperty(property);
   if (!prop) {
     *status = CS_INVALID_PROPERTY;
@@ -135,7 +135,7 @@
 int PropertyContainer::GetPropertyDefault(int property,
                                           CS_Status* status) const {
   if (!m_properties_cached && !CacheProperties(status)) return 0;
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   auto prop = GetProperty(property);
   if (!prop) {
     *status = CS_INVALID_PROPERTY;
@@ -147,7 +147,7 @@
 wpi::StringRef PropertyContainer::GetStringProperty(
     int property, wpi::SmallVectorImpl<char>& buf, CS_Status* status) const {
   if (!m_properties_cached && !CacheProperties(status)) return wpi::StringRef{};
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   auto prop = GetProperty(property);
   if (!prop) {
     *status = CS_INVALID_PROPERTY;
@@ -164,7 +164,7 @@
 
 void PropertyContainer::SetStringProperty(int property, const wpi::Twine& value,
                                           CS_Status* status) {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   auto prop = GetProperty(property);
   if (!prop) {
     *status = CS_INVALID_PROPERTY;
@@ -186,7 +186,7 @@
     int property, CS_Status* status) const {
   if (!m_properties_cached && !CacheProperties(status))
     return std::vector<std::string>{};
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   auto prop = GetProperty(property);
   if (!prop) {
     *status = CS_INVALID_PROPERTY;
@@ -201,7 +201,7 @@
 
 std::unique_ptr<PropertyImpl> PropertyContainer::CreateEmptyProperty(
     const wpi::Twine& name) const {
-  return wpi::make_unique<PropertyImpl>(name);
+  return std::make_unique<PropertyImpl>(name);
 }
 
 bool PropertyContainer::CacheProperties(CS_Status* status) const {
diff --git a/cscore/src/main/native/cpp/PropertyContainer.h b/cscore/src/main/native/cpp/PropertyContainer.h
index 19197b2..9bbb9c7 100644
--- a/cscore/src/main/native/cpp/PropertyContainer.h
+++ b/cscore/src/main/native/cpp/PropertyContainer.h
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
diff --git a/cscore/src/main/native/cpp/RawSinkImpl.cpp b/cscore/src/main/native/cpp/RawSinkImpl.cpp
new file mode 100644
index 0000000..986378f
--- /dev/null
+++ b/cscore/src/main/native/cpp/RawSinkImpl.cpp
@@ -0,0 +1,199 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018-2019 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 "RawSinkImpl.h"
+
+#include "Instance.h"
+#include "cscore.h"
+#include "cscore_raw.h"
+
+using namespace cs;
+
+RawSinkImpl::RawSinkImpl(const wpi::Twine& name, wpi::Logger& logger,
+                         Notifier& notifier, Telemetry& telemetry)
+    : SinkImpl{name, logger, notifier, telemetry} {
+  m_active = true;
+  // m_thread = std::thread(&RawSinkImpl::ThreadMain, this);
+}
+
+RawSinkImpl::RawSinkImpl(const wpi::Twine& name, wpi::Logger& logger,
+                         Notifier& notifier, Telemetry& telemetry,
+                         std::function<void(uint64_t time)> processFrame)
+    : SinkImpl{name, logger, notifier, telemetry} {}
+
+RawSinkImpl::~RawSinkImpl() { Stop(); }
+
+void RawSinkImpl::Stop() {
+  m_active = false;
+
+  // wake up any waiters by forcing an empty frame to be sent
+  if (auto source = GetSource()) source->Wakeup();
+
+  // join thread
+  if (m_thread.joinable()) m_thread.join();
+}
+
+uint64_t RawSinkImpl::GrabFrame(CS_RawFrame& image) {
+  SetEnabled(true);
+
+  auto source = GetSource();
+  if (!source) {
+    // Source disconnected; sleep for one second
+    std::this_thread::sleep_for(std::chrono::seconds(1));
+    return 0;
+  }
+
+  auto frame = source->GetNextFrame();  // blocks
+  if (!frame) {
+    // Bad frame; sleep for 20 ms so we don't consume all processor time.
+    std::this_thread::sleep_for(std::chrono::milliseconds(20));
+    return 0;  // signal error
+  }
+
+  return GrabFrameImpl(image, frame);
+}
+
+uint64_t RawSinkImpl::GrabFrame(CS_RawFrame& image, double timeout) {
+  SetEnabled(true);
+
+  auto source = GetSource();
+  if (!source) {
+    // Source disconnected; sleep for one second
+    std::this_thread::sleep_for(std::chrono::seconds(1));
+    return 0;
+  }
+
+  auto frame = source->GetNextFrame(timeout);  // blocks
+  if (!frame) {
+    // Bad frame; sleep for 20 ms so we don't consume all processor time.
+    std::this_thread::sleep_for(std::chrono::milliseconds(20));
+    return 0;  // signal error
+  }
+
+  return GrabFrameImpl(image, frame);
+}
+
+uint64_t RawSinkImpl::GrabFrameImpl(CS_RawFrame& rawFrame,
+                                    Frame& incomingFrame) {
+  Image* newImage = nullptr;
+
+  if (rawFrame.pixelFormat == CS_PixelFormat::CS_PIXFMT_UNKNOWN) {
+    // Always get incoming image directly on unknown
+    newImage = incomingFrame.GetExistingImage(0);
+  } else {
+    // Format is known, ask for it
+    auto width = rawFrame.width;
+    auto height = rawFrame.height;
+    auto pixelFormat =
+        static_cast<VideoMode::PixelFormat>(rawFrame.pixelFormat);
+    if (width <= 0 || height <= 0) {
+      width = incomingFrame.GetOriginalWidth();
+      height = incomingFrame.GetOriginalHeight();
+    }
+    newImage = incomingFrame.GetImage(width, height, pixelFormat);
+  }
+
+  if (!newImage) {
+    // Shouldn't happen, but just in case...
+    std::this_thread::sleep_for(std::chrono::milliseconds(20));
+    return 0;
+  }
+
+  CS_AllocateRawFrameData(&rawFrame, newImage->size());
+  rawFrame.height = newImage->height;
+  rawFrame.width = newImage->width;
+  rawFrame.pixelFormat = newImage->pixelFormat;
+  rawFrame.totalData = newImage->size();
+  std::copy(newImage->data(), newImage->data() + rawFrame.totalData,
+            rawFrame.data);
+
+  return incomingFrame.GetTime();
+}
+
+// Send HTTP response and a stream of JPG-frames
+void RawSinkImpl::ThreadMain() {
+  Enable();
+  while (m_active) {
+    auto source = GetSource();
+    if (!source) {
+      // Source disconnected; sleep for one second
+      std::this_thread::sleep_for(std::chrono::seconds(1));
+      continue;
+    }
+    SDEBUG4("waiting for frame");
+    Frame frame = source->GetNextFrame();  // blocks
+    if (!m_active) break;
+    if (!frame) {
+      // Bad frame; sleep for 10 ms so we don't consume all processor time.
+      std::this_thread::sleep_for(std::chrono::milliseconds(10));
+      continue;
+    }
+    // TODO m_processFrame();
+  }
+  Disable();
+}
+
+namespace cs {
+CS_Sink CreateRawSink(const wpi::Twine& name, CS_Status* status) {
+  auto& inst = Instance::GetInstance();
+  return inst.CreateSink(CS_SINK_RAW,
+                         std::make_shared<RawSinkImpl>(
+                             name, inst.logger, inst.notifier, inst.telemetry));
+}
+
+CS_Sink CreateRawSinkCallback(const wpi::Twine& name,
+                              std::function<void(uint64_t time)> processFrame,
+                              CS_Status* status) {
+  auto& inst = Instance::GetInstance();
+  return inst.CreateSink(CS_SINK_RAW, std::make_shared<RawSinkImpl>(
+                                          name, inst.logger, inst.notifier,
+                                          inst.telemetry, processFrame));
+}
+
+uint64_t GrabSinkFrame(CS_Sink sink, CS_RawFrame& image, CS_Status* status) {
+  auto data = Instance::GetInstance().GetSink(sink);
+  if (!data || data->kind != CS_SINK_RAW) {
+    *status = CS_INVALID_HANDLE;
+    return 0;
+  }
+  return static_cast<RawSinkImpl&>(*data->sink).GrabFrame(image);
+}
+
+uint64_t GrabSinkFrameTimeout(CS_Sink sink, CS_RawFrame& image, double timeout,
+                              CS_Status* status) {
+  auto data = Instance::GetInstance().GetSink(sink);
+  if (!data || data->kind != CS_SINK_RAW) {
+    *status = CS_INVALID_HANDLE;
+    return 0;
+  }
+  return static_cast<RawSinkImpl&>(*data->sink).GrabFrame(image, timeout);
+}
+}  // namespace cs
+
+extern "C" {
+CS_Sink CS_CreateRawSink(const char* name, CS_Status* status) {
+  return cs::CreateRawSink(name, status);
+}
+
+CS_Sink CS_CreateRawSinkCallback(const char* name, void* data,
+                                 void (*processFrame)(void* data,
+                                                      uint64_t time),
+                                 CS_Status* status) {
+  return cs::CreateRawSinkCallback(
+      name, [=](uint64_t time) { processFrame(data, time); }, status);
+}
+
+uint64_t CS_GrabRawSinkFrame(CS_Sink sink, struct CS_RawFrame* image,
+                             CS_Status* status) {
+  return cs::GrabSinkFrame(sink, *image, status);
+}
+
+uint64_t CS_GrabRawSinkFrameTimeout(CS_Sink sink, struct CS_RawFrame* image,
+                                    double timeout, CS_Status* status) {
+  return cs::GrabSinkFrameTimeout(sink, *image, timeout, status);
+}
+}  // extern "C"
diff --git a/cscore/src/main/native/cpp/RawSinkImpl.h b/cscore/src/main/native/cpp/RawSinkImpl.h
new file mode 100644
index 0000000..3e69485
--- /dev/null
+++ b/cscore/src/main/native/cpp/RawSinkImpl.h
@@ -0,0 +1,52 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2016-2019 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_RAWSINKIMPL_H_
+#define CSCORE_RAWSINKIMPL_H_
+
+#include <stdint.h>
+
+#include <atomic>
+#include <functional>
+#include <thread>
+
+#include <wpi/Twine.h>
+#include <wpi/condition_variable.h>
+
+#include "Frame.h"
+#include "SinkImpl.h"
+#include "cscore_raw.h"
+
+namespace cs {
+class SourceImpl;
+
+class RawSinkImpl : public SinkImpl {
+ public:
+  RawSinkImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
+              Telemetry& telemetry);
+  RawSinkImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
+              Telemetry& telemetry,
+              std::function<void(uint64_t time)> processFrame);
+  ~RawSinkImpl() override;
+
+  void Stop();
+
+  uint64_t GrabFrame(CS_RawFrame& frame);
+  uint64_t GrabFrame(CS_RawFrame& frame, double timeout);
+
+ private:
+  void ThreadMain();
+
+  uint64_t GrabFrameImpl(CS_RawFrame& rawFrame, Frame& incomingFrame);
+
+  std::atomic_bool m_active;  // set to false to terminate threads
+  std::thread m_thread;
+  std::function<void(uint64_t time)> m_processFrame;
+};
+}  // namespace cs
+
+#endif  // CSCORE_RAWSINKIMPL_H_
diff --git a/cscore/src/main/native/cpp/RawSourceImpl.cpp b/cscore/src/main/native/cpp/RawSourceImpl.cpp
new file mode 100644
index 0000000..e0dba2d
--- /dev/null
+++ b/cscore/src/main/native/cpp/RawSourceImpl.cpp
@@ -0,0 +1,83 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018-2019 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 "RawSourceImpl.h"
+
+#include <wpi/timestamp.h>
+
+#include "Handle.h"
+#include "Instance.h"
+#include "Log.h"
+#include "Notifier.h"
+#include "cscore_raw.h"
+
+using namespace cs;
+
+RawSourceImpl::RawSourceImpl(const wpi::Twine& name, wpi::Logger& logger,
+                             Notifier& notifier, Telemetry& telemetry,
+                             const VideoMode& mode)
+    : ConfigurableSourceImpl{name, logger, notifier, telemetry, mode} {}
+
+RawSourceImpl::~RawSourceImpl() {}
+
+void RawSourceImpl::PutFrame(const CS_RawFrame& image) {
+  int type;
+  switch (image.pixelFormat) {
+    case VideoMode::kYUYV:
+    case VideoMode::kRGB565:
+      type = CV_8UC2;
+      break;
+    case VideoMode::kBGR:
+      type = CV_8UC3;
+      break;
+    case VideoMode::kGray:
+    case VideoMode::kMJPEG:
+    default:
+      type = CV_8UC1;
+      break;
+  }
+  cv::Mat finalImage{image.height, image.width, type, image.data};
+  std::unique_ptr<Image> dest =
+      AllocImage(static_cast<VideoMode::PixelFormat>(image.pixelFormat),
+                 image.width, image.height, image.totalData);
+  finalImage.copyTo(dest->AsMat());
+
+  SourceImpl::PutFrame(std::move(dest), wpi::Now());
+}
+
+namespace cs {
+CS_Source CreateRawSource(const wpi::Twine& name, const VideoMode& mode,
+                          CS_Status* status) {
+  auto& inst = Instance::GetInstance();
+  return inst.CreateSource(CS_SOURCE_RAW, std::make_shared<RawSourceImpl>(
+                                              name, inst.logger, inst.notifier,
+                                              inst.telemetry, mode));
+}
+
+void PutSourceFrame(CS_Source source, const CS_RawFrame& image,
+                    CS_Status* status) {
+  auto data = Instance::GetInstance().GetSource(source);
+  if (!data || data->kind != CS_SOURCE_RAW) {
+    *status = CS_INVALID_HANDLE;
+    return;
+  }
+  static_cast<RawSourceImpl&>(*data->source).PutFrame(image);
+}
+}  // namespace cs
+
+extern "C" {
+CS_Source CS_CreateRawSource(const char* name, const CS_VideoMode* mode,
+                             CS_Status* status) {
+  return cs::CreateRawSource(name, static_cast<const cs::VideoMode&>(*mode),
+                             status);
+}
+
+void CS_PutRawSourceFrame(CS_Source source, const struct CS_RawFrame* image,
+                          CS_Status* status) {
+  return cs::PutSourceFrame(source, *image, status);
+}
+}  // extern "C"
diff --git a/cscore/src/main/native/cpp/RawSourceImpl.h b/cscore/src/main/native/cpp/RawSourceImpl.h
new file mode 100644
index 0000000..1fdc749
--- /dev/null
+++ b/cscore/src/main/native/cpp/RawSourceImpl.h
@@ -0,0 +1,41 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2016-2019 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_RAWSOURCEIMPL_H_
+#define CSCORE_RAWSOURCEIMPL_H_
+
+#include <atomic>
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <wpi/ArrayRef.h>
+#include <wpi/Twine.h>
+
+#include "ConfigurableSourceImpl.h"
+#include "SourceImpl.h"
+#include "cscore_raw.h"
+
+namespace cs {
+
+class RawSourceImpl : public ConfigurableSourceImpl {
+ public:
+  RawSourceImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
+                Telemetry& telemetry, const VideoMode& mode);
+  ~RawSourceImpl() override;
+
+  // Raw-specific functions
+  void PutFrame(const CS_RawFrame& image);
+
+ private:
+  std::atomic_bool m_connected{true};
+};
+
+}  // namespace cs
+
+#endif  // CSCORE_RAWSOURCEIMPL_H_
diff --git a/cscore/src/main/native/cpp/SinkImpl.cpp b/cscore/src/main/native/cpp/SinkImpl.cpp
index 637b407..5d4235a 100644
--- a/cscore/src/main/native/cpp/SinkImpl.cpp
+++ b/cscore/src/main/native/cpp/SinkImpl.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -30,18 +30,18 @@
 }
 
 void SinkImpl::SetDescription(const wpi::Twine& description) {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   m_description = description.str();
 }
 
 wpi::StringRef SinkImpl::GetDescription(wpi::SmallVectorImpl<char>& buf) const {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   buf.append(m_description.begin(), m_description.end());
   return wpi::StringRef{buf.data(), buf.size()};
 }
 
 void SinkImpl::Enable() {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   ++m_enabledCount;
   if (m_enabledCount == 1) {
     if (m_source) m_source->EnableSink();
@@ -50,7 +50,7 @@
 }
 
 void SinkImpl::Disable() {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   --m_enabledCount;
   if (m_enabledCount == 0) {
     if (m_source) m_source->DisableSink();
@@ -59,7 +59,7 @@
 }
 
 void SinkImpl::SetEnabled(bool enabled) {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   if (enabled && m_enabledCount == 0) {
     if (m_source) m_source->EnableSink();
     m_enabledCount = 1;
@@ -73,7 +73,7 @@
 
 void SinkImpl::SetSource(std::shared_ptr<SourceImpl> source) {
   {
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     if (m_source == source) return;
     if (m_source) {
       if (m_enabledCount > 0) m_source->DisableSink();
@@ -89,13 +89,13 @@
 }
 
 std::string SinkImpl::GetError() const {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   if (!m_source) return "no source connected";
   return m_source->GetCurFrame().GetError();
 }
 
 wpi::StringRef SinkImpl::GetError(wpi::SmallVectorImpl<char>& buf) const {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   if (!m_source) return "no source connected";
   // Make a copy as it's shared data
   wpi::StringRef error = m_source->GetCurFrame().GetError();
diff --git a/cscore/src/main/native/cpp/SinkImpl.h b/cscore/src/main/native/cpp/SinkImpl.h
index 147268f..7ad831f 100644
--- a/cscore/src/main/native/cpp/SinkImpl.h
+++ b/cscore/src/main/native/cpp/SinkImpl.h
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -48,7 +48,7 @@
   void SetSource(std::shared_ptr<SourceImpl> source);
 
   std::shared_ptr<SourceImpl> GetSource() const {
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     return m_source;
   }
 
diff --git a/cscore/src/main/native/cpp/SourceImpl.cpp b/cscore/src/main/native/cpp/SourceImpl.cpp
index e646eb6..455b6cd 100644
--- a/cscore/src/main/native/cpp/SourceImpl.cpp
+++ b/cscore/src/main/native/cpp/SourceImpl.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -10,7 +10,6 @@
 #include <algorithm>
 #include <cstring>
 
-#include <wpi/STLExtras.h>
 #include <wpi/json.h>
 #include <wpi/timestamp.h>
 
@@ -45,13 +44,13 @@
 }
 
 void SourceImpl::SetDescription(const wpi::Twine& description) {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   m_description = description.str();
 }
 
 wpi::StringRef SourceImpl::GetDescription(
     wpi::SmallVectorImpl<char>& buf) const {
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   buf.append(m_description.begin(), m_description.end());
   return wpi::StringRef{buf.data(), buf.size()};
 }
@@ -65,24 +64,24 @@
 }
 
 uint64_t SourceImpl::GetCurFrameTime() {
-  std::unique_lock<wpi::mutex> lock{m_frameMutex};
+  std::unique_lock lock{m_frameMutex};
   return m_frame.GetTime();
 }
 
 Frame SourceImpl::GetCurFrame() {
-  std::unique_lock<wpi::mutex> lock{m_frameMutex};
+  std::unique_lock lock{m_frameMutex};
   return m_frame;
 }
 
 Frame SourceImpl::GetNextFrame() {
-  std::unique_lock<wpi::mutex> lock{m_frameMutex};
+  std::unique_lock lock{m_frameMutex};
   auto oldTime = m_frame.GetTime();
   m_frameCv.wait(lock, [=] { return m_frame.GetTime() != oldTime; });
   return m_frame;
 }
 
 Frame SourceImpl::GetNextFrame(double timeout) {
-  std::unique_lock<wpi::mutex> lock{m_frameMutex};
+  std::unique_lock lock{m_frameMutex};
   auto oldTime = m_frame.GetTime();
   if (!m_frameCv.wait_for(
           lock, std::chrono::milliseconds(static_cast<int>(timeout * 1000)),
@@ -94,7 +93,7 @@
 
 void SourceImpl::Wakeup() {
   {
-    std::lock_guard<wpi::mutex> lock{m_frameMutex};
+    std::scoped_lock lock{m_frameMutex};
     m_frame = Frame{*this, wpi::StringRef{}, 0};
   }
   m_frameCv.notify_all();
@@ -135,7 +134,7 @@
 
 VideoMode SourceImpl::GetVideoMode(CS_Status* status) const {
   if (!m_properties_cached && !CacheProperties(status)) return VideoMode{};
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   return m_mode;
 }
 
@@ -381,7 +380,7 @@
     CS_Status* status) const {
   if (!m_properties_cached && !CacheProperties(status))
     return std::vector<VideoMode>{};
-  std::lock_guard<wpi::mutex> lock(m_mutex);
+  std::scoped_lock lock(m_mutex);
   return m_videoModes;
 }
 
@@ -389,7 +388,7 @@
     VideoMode::PixelFormat pixelFormat, int width, int height, size_t size) {
   std::unique_ptr<Image> image;
   {
-    std::lock_guard<wpi::mutex> lock{m_poolMutex};
+    std::scoped_lock lock{m_poolMutex};
     // find the smallest existing frame that is at least big enough.
     int found = -1;
     for (size_t i = 0; i < m_imagesAvail.size(); ++i) {
@@ -441,7 +440,7 @@
 
   // Update frame
   {
-    std::lock_guard<wpi::mutex> lock{m_frameMutex};
+    std::scoped_lock lock{m_frameMutex};
     m_frame = Frame{*this, std::move(image), time};
   }
 
@@ -452,7 +451,7 @@
 void SourceImpl::PutError(const wpi::Twine& msg, Frame::Time time) {
   // Update frame
   {
-    std::lock_guard<wpi::mutex> lock{m_frameMutex};
+    std::scoped_lock lock{m_frameMutex};
     m_frame = Frame{*this, msg, time};
   }
 
@@ -490,7 +489,7 @@
 }
 
 void SourceImpl::ReleaseImage(std::unique_ptr<Image> image) {
-  std::lock_guard<wpi::mutex> lock{m_poolMutex};
+  std::scoped_lock lock{m_poolMutex};
   if (m_destroyFrames) return;
   // Return the frame to the pool.  First try to find an empty slot, otherwise
   // add it to the end.
@@ -512,9 +511,9 @@
 }
 
 std::unique_ptr<Frame::Impl> SourceImpl::AllocFrameImpl() {
-  std::lock_guard<wpi::mutex> lock{m_poolMutex};
+  std::scoped_lock lock{m_poolMutex};
 
-  if (m_framesAvail.empty()) return wpi::make_unique<Frame::Impl>(*this);
+  if (m_framesAvail.empty()) return std::make_unique<Frame::Impl>(*this);
 
   auto impl = std::move(m_framesAvail.back());
   m_framesAvail.pop_back();
@@ -522,7 +521,7 @@
 }
 
 void SourceImpl::ReleaseFrameImpl(std::unique_ptr<Frame::Impl> impl) {
-  std::lock_guard<wpi::mutex> lock{m_poolMutex};
+  std::scoped_lock lock{m_poolMutex};
   if (m_destroyFrames) return;
   m_framesAvail.push_back(std::move(impl));
 }
diff --git a/cscore/src/main/native/cpp/Telemetry.cpp b/cscore/src/main/native/cpp/Telemetry.cpp
index fa6c93c..77130f6 100644
--- a/cscore/src/main/native/cpp/Telemetry.cpp
+++ b/cscore/src/main/native/cpp/Telemetry.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2015-2019 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.                                                               */
@@ -53,7 +53,7 @@
 void Telemetry::Stop() { m_owner.Stop(); }
 
 void Telemetry::Thread::Main() {
-  std::unique_lock<wpi::mutex> lock(m_mutex);
+  std::unique_lock lock(m_mutex);
   auto prevTime = std::chrono::steady_clock::now();
   while (m_active) {
     double period = m_period;
diff --git a/cscore/src/main/native/cpp/UnlimitedHandleResource.h b/cscore/src/main/native/cpp/UnlimitedHandleResource.h
index c297cfa..6c9a538 100644
--- a/cscore/src/main/native/cpp/UnlimitedHandleResource.h
+++ b/cscore/src/main/native/cpp/UnlimitedHandleResource.h
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -78,7 +78,7 @@
 template <typename... Args>
 THandle UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Allocate(
     Args&&... args) {
-  std::lock_guard<TMutex> sync(m_handleMutex);
+  std::scoped_lock sync(m_handleMutex);
   size_t i;
   for (i = 0; i < m_structures.size(); i++) {
     if (m_structures[i] == nullptr) {
@@ -96,7 +96,7 @@
 template <typename THandle, typename TStruct, int typeValue, typename TMutex>
 THandle UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Allocate(
     std::shared_ptr<THandle> structure) {
-  std::lock_guard<TMutex> sync(m_handleMutex);
+  std::scoped_lock sync(m_handleMutex);
   size_t i;
   for (i = 0; i < m_structures.size(); i++) {
     if (m_structures[i] == nullptr) {
@@ -117,7 +117,7 @@
   auto index =
       handle.GetTypedIndex(static_cast<typename THandle::Type>(typeValue));
   if (index < 0) return nullptr;
-  std::lock_guard<TMutex> sync(m_handleMutex);
+  std::scoped_lock sync(m_handleMutex);
   if (index >= static_cast<int>(m_structures.size())) return nullptr;
   return m_structures[index];
 }
@@ -129,7 +129,7 @@
   auto index =
       handle.GetTypedIndex(static_cast<typename THandle::Type>(typeValue));
   if (index < 0) return nullptr;
-  std::lock_guard<TMutex> sync(m_handleMutex);
+  std::scoped_lock sync(m_handleMutex);
   if (index >= static_cast<int>(m_structures.size())) return nullptr;
   auto rv = std::move(m_structures[index]);
   m_structures[index].reset();
@@ -148,7 +148,7 @@
 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);
+  std::scoped_lock sync(m_handleMutex);
   auto rv = std::move(m_structures);
   m_structures.clear();
   return rv;
@@ -158,7 +158,7 @@
 template <typename F>
 inline void
 UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::ForEach(F func) {
-  std::lock_guard<TMutex> sync(m_handleMutex);
+  std::scoped_lock sync(m_handleMutex);
   for (size_t i = 0; i < m_structures.size(); i++) {
     if (m_structures[i] != nullptr) func(MakeHandle(i), *(m_structures[i]));
   }
@@ -168,7 +168,7 @@
 template <typename F>
 inline std::pair<THandle, std::shared_ptr<TStruct>>
 UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::FindIf(F func) {
-  std::lock_guard<TMutex> sync(m_handleMutex);
+  std::scoped_lock sync(m_handleMutex);
   for (size_t i = 0; i < m_structures.size(); i++) {
     auto& structure = m_structures[i];
     if (structure != nullptr && func(*structure))
diff --git a/cscore/src/main/native/cpp/UsbCameraImplCommon.cpp b/cscore/src/main/native/cpp/UsbCameraImplCommon.cpp
index 082e398..2b3fb08 100644
--- a/cscore/src/main/native/cpp/UsbCameraImplCommon.cpp
+++ b/cscore/src/main/native/cpp/UsbCameraImplCommon.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -17,10 +17,12 @@
   out->path = ConvertToC(in.path);
   out->name = ConvertToC(in.name);
   out->otherPaths = static_cast<char**>(
-      wpi::CheckedMalloc(in.otherPaths.size() * sizeof(char*)));
+      wpi::safe_malloc(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]);
+  out->vendorId = in.vendorId;
+  out->productId = in.productId;
 }
 
 static void FreeUsbCameraInfo(CS_UsbCameraInfo* info) {
@@ -50,7 +52,7 @@
   auto info = cs::GetUsbCameraInfo(source, status);
   if (*status != CS_OK) return nullptr;
   CS_UsbCameraInfo* out = static_cast<CS_UsbCameraInfo*>(
-      wpi::CheckedMalloc(sizeof(CS_UsbCameraInfo)));
+      wpi::safe_malloc(sizeof(CS_UsbCameraInfo)));
   ConvertToC(out, info);
   return out;
 }
@@ -58,7 +60,7 @@
 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)));
+      wpi::safe_malloc(cameras.size() * sizeof(CS_UsbCameraInfo)));
   *count = cameras.size();
   for (size_t i = 0; i < cameras.size(); ++i) ConvertToC(&out[i], cameras[i]);
   return out;
diff --git a/cscore/src/main/native/cpp/c_util.h b/cscore/src/main/native/cpp/c_util.h
index 6264e1f..f985fe2 100644
--- a/cscore/src/main/native/cpp/c_util.h
+++ b/cscore/src/main/native/cpp/c_util.h
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -11,13 +11,13 @@
 #include <cstdlib>
 #include <cstring>
 
+#include <wpi/MemAlloc.h>
 #include <wpi/StringRef.h>
-#include <wpi/memory.h>
 
 namespace cs {
 
 inline char* ConvertToC(wpi::StringRef in) {
-  char* out = static_cast<char*>(wpi::CheckedMalloc(in.size() + 1));
+  char* out = static_cast<char*>(wpi::safe_malloc(in.size() + 1));
   std::memmove(out, in.data(), in.size());
   out[in.size()] = '\0';
   return out;
diff --git a/cscore/src/main/native/cpp/cscore_c.cpp b/cscore/src/main/native/cpp/cscore_c.cpp
index 1819d13..321572e 100644
--- a/cscore/src/main/native/cpp/cscore_c.cpp
+++ b/cscore/src/main/native/cpp/cscore_c.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -11,11 +11,12 @@
 #include <cstdlib>
 
 #include <opencv2/core/core.hpp>
+#include <wpi/MemAlloc.h>
 #include <wpi/SmallString.h>
-#include <wpi/memory.h>
 
 #include "c_util.h"
 #include "cscore_cpp.h"
+#include "cscore_raw.h"
 
 extern "C" {
 
@@ -70,7 +71,7 @@
                                  CS_Status* status) {
   auto choices = cs::GetEnumPropertyChoices(property, status);
   char** out =
-      static_cast<char**>(wpi::CheckedMalloc(choices.size() * sizeof(char*)));
+      static_cast<char**>(wpi::safe_malloc(choices.size() * sizeof(char*)));
   *count = choices.size();
   for (size_t i = 0; i < choices.size(); ++i)
     out[i] = cs::ConvertToC(choices[i]);
@@ -123,7 +124,7 @@
   wpi::SmallVector<CS_Property, 32> buf;
   auto vec = cs::EnumerateSourceProperties(source, buf, status);
   CS_Property* out = static_cast<CS_Property*>(
-      wpi::CheckedMalloc(vec.size() * sizeof(CS_Property)));
+      wpi::safe_malloc(vec.size() * sizeof(CS_Property)));
   *count = vec.size();
   std::copy(vec.begin(), vec.end(), out);
   return out;
@@ -183,7 +184,7 @@
                                            CS_Status* status) {
   auto vec = cs::EnumerateSourceVideoModes(source, status);
   CS_VideoMode* out = static_cast<CS_VideoMode*>(
-      wpi::CheckedMalloc(vec.size() * sizeof(CS_VideoMode)));
+      wpi::safe_malloc(vec.size() * sizeof(CS_VideoMode)));
   *count = vec.size();
   std::copy(vec.begin(), vec.end(), out);
   return out;
@@ -193,8 +194,8 @@
                                  CS_Status* status) {
   wpi::SmallVector<CS_Sink, 32> buf;
   auto handles = cs::EnumerateSourceSinks(source, buf, status);
-  CS_Sink* sinks = static_cast<CS_Sink*>(
-      wpi::CheckedMalloc(handles.size() * sizeof(CS_Sink)));
+  CS_Sink* sinks =
+      static_cast<CS_Sink*>(wpi::safe_malloc(handles.size() * sizeof(CS_Sink)));
   *count = handles.size();
   std::copy(handles.begin(), handles.end(), sinks);
   return sinks;
@@ -271,7 +272,7 @@
   wpi::SmallVector<CS_Property, 32> buf;
   auto vec = cs::EnumerateSinkProperties(sink, buf, status);
   CS_Property* out = static_cast<CS_Property*>(
-      wpi::CheckedMalloc(vec.size() * sizeof(CS_Property)));
+      wpi::safe_malloc(vec.size() * sizeof(CS_Property)));
   *count = vec.size();
   std::copy(vec.begin(), vec.end(), out);
   return out;
@@ -372,7 +373,7 @@
   wpi::SmallVector<CS_Source, 32> buf;
   auto handles = cs::EnumerateSourceHandles(buf, status);
   CS_Source* sources = static_cast<CS_Source*>(
-      wpi::CheckedMalloc(handles.size() * sizeof(CS_Source)));
+      wpi::safe_malloc(handles.size() * sizeof(CS_Source)));
   *count = handles.size();
   std::copy(handles.begin(), handles.end(), sources);
   return sources;
@@ -390,8 +391,8 @@
 CS_Sink* CS_EnumerateSinks(int* count, CS_Status* status) {
   wpi::SmallVector<CS_Sink, 32> buf;
   auto handles = cs::EnumerateSinkHandles(buf, status);
-  CS_Sink* sinks = static_cast<CS_Sink*>(
-      wpi::CheckedMalloc(handles.size() * sizeof(CS_Sink)));
+  CS_Sink* sinks =
+      static_cast<CS_Sink*>(wpi::safe_malloc(handles.size() * sizeof(CS_Sink)));
   *count = handles.size();
   std::copy(handles.begin(), handles.end(), sinks);
   return sinks;
@@ -426,8 +427,8 @@
 
 char** CS_GetNetworkInterfaces(int* count) {
   auto interfaces = cs::GetNetworkInterfaces();
-  char** out = static_cast<char**>(
-      wpi::CheckedMalloc(interfaces.size() * sizeof(char*)));
+  char** out =
+      static_cast<char**>(wpi::safe_malloc(interfaces.size() * sizeof(char*)));
   *count = interfaces.size();
   for (size_t i = 0; i < interfaces.size(); ++i)
     out[i] = cs::ConvertToC(interfaces[i]);
@@ -440,4 +441,23 @@
   std::free(interfaces);
 }
 
+void CS_AllocateRawFrameData(CS_RawFrame* frame, int requestedSize) {
+  if (frame->dataLength >= requestedSize) return;
+  if (frame->data) {
+    frame->data =
+        static_cast<char*>(wpi::safe_realloc(frame->data, requestedSize));
+  } else {
+    frame->data = static_cast<char*>(wpi::safe_malloc(requestedSize));
+  }
+  frame->dataLength = requestedSize;
+}
+
+void CS_FreeRawFrameData(CS_RawFrame* frame) {
+  if (frame->data) {
+    std::free(frame->data);
+    frame->data = nullptr;
+    frame->dataLength = 0;
+  }
+}
+
 }  // extern "C"
diff --git a/cscore/src/main/native/cpp/cscore_cpp.cpp b/cscore/src/main/native/cpp/cscore_cpp.cpp
index 83b4e87..d49fb04 100644
--- a/cscore/src/main/native/cpp/cscore_cpp.cpp
+++ b/cscore/src/main/native/cpp/cscore_cpp.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
diff --git a/cscore/src/main/native/cpp/cscore_oo.cpp b/cscore/src/main/native/cpp/cscore_oo.cpp
index f455273..f42ed5a 100644
--- a/cscore/src/main/native/cpp/cscore_oo.cpp
+++ b/cscore/src/main/native/cpp/cscore_oo.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
diff --git a/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp b/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp
index 528d5e4..c50a7db 100644
--- a/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp
+++ b/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -10,8 +10,14 @@
 #include <wpi/raw_ostream.h>
 
 #include "cscore_cpp.h"
+#include "cscore_cv.h"
+#include "cscore_raw.h"
 #include "edu_wpi_cscore_CameraServerJNI.h"
 
+namespace cv {
+class Mat;
+}  // namespace cv
+
 using namespace wpi::java;
 
 //
@@ -23,6 +29,7 @@
 static JClass usbCameraInfoCls;
 static JClass videoModeCls;
 static JClass videoEventCls;
+static JClass rawFrameCls;
 static JException videoEx;
 static JException nullPointerEx;
 static JException unsupportedEx;
@@ -32,7 +39,8 @@
 static const JClassInit classes[] = {
     {"edu/wpi/cscore/UsbCameraInfo", &usbCameraInfoCls},
     {"edu/wpi/cscore/VideoMode", &videoModeCls},
-    {"edu/wpi/cscore/VideoEvent", &videoEventCls}};
+    {"edu/wpi/cscore/VideoEvent", &videoEventCls},
+    {"edu/wpi/cscore/raw/RawFrame", &rawFrameCls}};
 
 static const JExceptionInit exceptions[] = {
     {"edu/wpi/cscore/VideoException", &videoEx},
@@ -186,13 +194,14 @@
 static jobject MakeJObject(JNIEnv* env, const cs::UsbCameraInfo& info) {
   static jmethodID constructor = env->GetMethodID(
       usbCameraInfoCls, "<init>",
-      "(ILjava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V");
+      "(ILjava/lang/String;Ljava/lang/String;[Ljava/lang/String;II)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(),
-                        otherPaths.obj());
+                        otherPaths.obj(), static_cast<jint>(info.vendorId),
+                        static_cast<jint>(info.productId));
 }
 
 static jobject MakeJObject(JNIEnv* env, const cs::VideoMode& videoMode) {
@@ -506,12 +515,12 @@
 }
 
 /*
- * Class:     edu_wpi_cscore_CameraServerJNI
+ * Class:     edu_wpi_cscore_CameraServerCvJNI
  * Method:    createCvSource
  * Signature: (Ljava/lang/String;IIII)I
  */
 JNIEXPORT jint JNICALL
-Java_edu_wpi_cscore_CameraServerJNI_createCvSource
+Java_edu_wpi_cscore_CameraServerCvJNI_createCvSource
   (JNIEnv* env, jclass, jstring name, jint pixelFormat, jint width, jint height,
    jint fps)
 {
@@ -532,6 +541,31 @@
 
 /*
  * Class:     edu_wpi_cscore_CameraServerJNI
+ * Method:    createRawSource
+ * Signature: (Ljava/lang/String;IIII)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_cscore_CameraServerJNI_createRawSource
+  (JNIEnv* env, jclass, jstring name, jint pixelFormat, jint width, jint height,
+   jint fps)
+{
+  if (!name) {
+    nullPointerEx.Throw(env, "name cannot be null");
+    return 0;
+  }
+  CS_Status status = 0;
+  auto val = cs::CreateRawSource(
+      JStringRef{env, name}.str(),
+      cs::VideoMode{static_cast<cs::VideoMode::PixelFormat>(pixelFormat),
+                    static_cast<int>(width), static_cast<int>(height),
+                    static_cast<int>(fps)},
+      &status);
+  CheckStatus(env, status);
+  return val;
+}
+
+/*
+ * Class:     edu_wpi_cscore_CameraServerJNI
  * Method:    getSourceKind
  * Signature: (I)I
  */
@@ -1054,12 +1088,12 @@
 }
 
 /*
- * Class:     edu_wpi_cscore_CameraServerJNI
+ * Class:     edu_wpi_cscore_CameraServerCvJNI
  * Method:    putSourceFrame
  * Signature: (IJ)V
  */
 JNIEXPORT void JNICALL
-Java_edu_wpi_cscore_CameraServerJNI_putSourceFrame
+Java_edu_wpi_cscore_CameraServerCvJNI_putSourceFrame
   (JNIEnv* env, jclass, jint source, jlong imageNativeObj)
 {
   cv::Mat& image = *((cv::Mat*)imageNativeObj);
@@ -1068,6 +1102,51 @@
   CheckStatus(env, status);
 }
 
+// int width, int height, int pixelFormat, int totalData
+
+/*
+ * Class:     edu_wpi_cscore_CameraServerJNI
+ * Method:    putRawSourceFrameBB
+ * Signature: (ILjava/lang/Object;IIII)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_cscore_CameraServerJNI_putRawSourceFrameBB
+  (JNIEnv* env, jclass, jint source, jobject byteBuffer, jint width,
+   jint height, jint pixelFormat, jint totalData)
+{
+  CS_RawFrame rawFrame;
+  rawFrame.data =
+      reinterpret_cast<char*>(env->GetDirectBufferAddress(byteBuffer));
+  rawFrame.totalData = totalData;
+  rawFrame.pixelFormat = pixelFormat;
+  rawFrame.width = width;
+  rawFrame.height = height;
+  CS_Status status = 0;
+  cs::PutSourceFrame(source, rawFrame, &status);
+  CheckStatus(env, status);
+}
+
+/*
+ * Class:     edu_wpi_cscore_CameraServerJNI
+ * Method:    putRawSourceFrame
+ * Signature: (IJIIII)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_cscore_CameraServerJNI_putRawSourceFrame
+  (JNIEnv* env, jclass, jint source, jlong ptr, jint width, jint height,
+   jint pixelFormat, jint totalData)
+{
+  CS_RawFrame rawFrame;
+  rawFrame.data = reinterpret_cast<char*>(static_cast<intptr_t>(ptr));
+  rawFrame.totalData = totalData;
+  rawFrame.pixelFormat = pixelFormat;
+  rawFrame.width = width;
+  rawFrame.height = height;
+  CS_Status status = 0;
+  cs::PutSourceFrame(source, rawFrame, &status);
+  CheckStatus(env, status);
+}
+
 /*
  * Class:     edu_wpi_cscore_CameraServerJNI
  * Method:    notifySourceError
@@ -1192,12 +1271,12 @@
 }
 
 /*
- * Class:     edu_wpi_cscore_CameraServerJNI
+ * Class:     edu_wpi_cscore_CameraServerCvJNI
  * Method:    createCvSink
  * Signature: (Ljava/lang/String;)I
  */
 JNIEXPORT jint JNICALL
-Java_edu_wpi_cscore_CameraServerJNI_createCvSink
+Java_edu_wpi_cscore_CameraServerCvJNI_createCvSink
   (JNIEnv* env, jclass, jstring name)
 {
   if (!name) {
@@ -1212,6 +1291,25 @@
 
 /*
  * Class:     edu_wpi_cscore_CameraServerJNI
+ * Method:    createRawSink
+ * Signature: (Ljava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_cscore_CameraServerJNI_createRawSink
+  (JNIEnv* env, jclass, jstring name)
+{
+  if (!name) {
+    nullPointerEx.Throw(env, "name cannot be null");
+    return 0;
+  }
+  CS_Status status = 0;
+  auto val = cs::CreateRawSink(JStringRef{env, name}.str(), &status);
+  CheckStatus(env, status);
+  return val;
+}
+
+/*
+ * Class:     edu_wpi_cscore_CameraServerJNI
  * Method:    getSinkKind
  * Signature: (I)I
  */
@@ -1449,12 +1547,12 @@
 }
 
 /*
- * Class:     edu_wpi_cscore_CameraServerJNI
+ * Class:     edu_wpi_cscore_CameraServerCvJNI
  * Method:    grabSinkFrame
  * Signature: (IJ)J
  */
 JNIEXPORT jlong JNICALL
-Java_edu_wpi_cscore_CameraServerJNI_grabSinkFrame
+Java_edu_wpi_cscore_CameraServerCvJNI_grabSinkFrame
   (JNIEnv* env, jclass, jint sink, jlong imageNativeObj)
 {
   cv::Mat& image = *((cv::Mat*)imageNativeObj);
@@ -1465,12 +1563,12 @@
 }
 
 /*
- * Class:     edu_wpi_cscore_CameraServerJNI
+ * Class:     edu_wpi_cscore_CameraServerCvJNI
  * Method:    grabSinkFrameTimeout
  * Signature: (IJD)J
  */
 JNIEXPORT jlong JNICALL
-Java_edu_wpi_cscore_CameraServerJNI_grabSinkFrameTimeout
+Java_edu_wpi_cscore_CameraServerCvJNI_grabSinkFrameTimeout
   (JNIEnv* env, jclass, jint sink, jlong imageNativeObj, jdouble timeout)
 {
   cv::Mat& image = *((cv::Mat*)imageNativeObj);
@@ -1480,6 +1578,75 @@
   return rv;
 }
 
+static void SetRawFrameData(JNIEnv* env, jobject rawFrameObj,
+                            jobject byteBuffer, bool didChangeDataPtr,
+                            const CS_RawFrame& frame) {
+  static jmethodID setMethod =
+      env->GetMethodID(rawFrameCls, "setData", "(Ljava/nio/ByteBuffer;JIIII)V");
+  jlong framePtr = static_cast<jlong>(reinterpret_cast<intptr_t>(frame.data));
+
+  if (didChangeDataPtr) {
+    byteBuffer = env->NewDirectByteBuffer(frame.data, frame.dataLength);
+  }
+
+  env->CallVoidMethod(
+      rawFrameObj, setMethod, byteBuffer, framePtr,
+      static_cast<jint>(frame.totalData), static_cast<jint>(frame.width),
+      static_cast<jint>(frame.height), static_cast<jint>(frame.pixelFormat));
+}
+
+/*
+ * Class:     edu_wpi_cscore_CameraServerJNI
+ * Method:    grabRawSinkFrameImpl
+ * Signature: (ILjava/lang/Object;JLjava/lang/Object;III)J
+ */
+JNIEXPORT jlong JNICALL
+Java_edu_wpi_cscore_CameraServerJNI_grabRawSinkFrameImpl
+  (JNIEnv* env, jclass, jint sink, jobject rawFrameObj, jlong rawFramePtr,
+   jobject byteBuffer, jint width, jint height, jint pixelFormat)
+{
+  CS_RawFrame* ptr =
+      reinterpret_cast<CS_RawFrame*>(static_cast<intptr_t>(rawFramePtr));
+  auto origDataPtr = ptr->data;
+  ptr->width = width;
+  ptr->height = height;
+  ptr->pixelFormat = pixelFormat;
+  CS_Status status = 0;
+  auto rv = cs::GrabSinkFrame(static_cast<CS_Sink>(sink), *ptr, &status);
+  if (!CheckStatus(env, status)) {
+    return 0;
+  }
+  SetRawFrameData(env, rawFrameObj, byteBuffer, origDataPtr != ptr->data, *ptr);
+  return rv;
+}
+
+/*
+ * Class:     edu_wpi_cscore_CameraServerJNI
+ * Method:    grabRawSinkFrameTimeoutImpl
+ * Signature: (ILjava/lang/Object;JLjava/lang/Object;IIID)J
+ */
+JNIEXPORT jlong JNICALL
+Java_edu_wpi_cscore_CameraServerJNI_grabRawSinkFrameTimeoutImpl
+  (JNIEnv* env, jclass, jint sink, jobject rawFrameObj, jlong rawFramePtr,
+   jobject byteBuffer, jint width, jint height, jint pixelFormat,
+   jdouble timeout)
+{
+  CS_RawFrame* ptr =
+      reinterpret_cast<CS_RawFrame*>(static_cast<intptr_t>(rawFramePtr));
+  auto origDataPtr = ptr->data;
+  ptr->width = width;
+  ptr->height = height;
+  ptr->pixelFormat = pixelFormat;
+  CS_Status status = 0;
+  auto rv = cs::GrabSinkFrameTimeout(static_cast<CS_Sink>(sink), *ptr, timeout,
+                                     &status);
+  if (!CheckStatus(env, status)) {
+    return 0;
+  }
+  SetRawFrameData(env, rawFrameObj, byteBuffer, origDataPtr != ptr->data, *ptr);
+  return rv;
+}
+
 /*
  * Class:     edu_wpi_cscore_CameraServerJNI
  * Method:    getSinkError
@@ -1781,4 +1948,32 @@
       minLevel);
 }
 
+/*
+ * Class:     edu_wpi_cscore_CameraServerJNI
+ * Method:    allocateRawFrame
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL
+Java_edu_wpi_cscore_CameraServerJNI_allocateRawFrame
+  (JNIEnv*, jclass)
+{
+  cs::RawFrame* rawFrame = new cs::RawFrame{};
+  intptr_t rawFrameIntPtr = reinterpret_cast<intptr_t>(rawFrame);
+  return static_cast<jlong>(rawFrameIntPtr);
+}
+
+/*
+ * Class:     edu_wpi_cscore_CameraServerJNI
+ * Method:    freeRawFrame
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_cscore_CameraServerJNI_freeRawFrame
+  (JNIEnv*, jclass, jlong rawFrame)
+{
+  cs::RawFrame* ptr =
+      reinterpret_cast<cs::RawFrame*>(static_cast<intptr_t>(rawFrame));
+  delete ptr;
+}
+
 }  // extern "C"
diff --git a/cscore/src/main/native/include/cscore_c.h b/cscore/src/main/native/include/cscore_c.h
index 182b9b2..24e30b4 100644
--- a/cscore/src/main/native/include/cscore_c.h
+++ b/cscore/src/main/native/include/cscore_c.h
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -20,8 +20,6 @@
 extern "C" {
 #endif
 
-struct CvMat;
-
 /**
  * @defgroup cscore_c_api cscore C API
  *
@@ -128,7 +126,8 @@
   CS_SOURCE_UNKNOWN = 0,
   CS_SOURCE_USB = 1,
   CS_SOURCE_HTTP = 2,
-  CS_SOURCE_CV = 4
+  CS_SOURCE_CV = 4,
+  CS_SOURCE_RAW = 8,
 };
 
 /**
@@ -144,7 +143,12 @@
 /**
  * Sink kinds
  */
-enum CS_SinkKind { CS_SINK_UNKNOWN = 0, CS_SINK_MJPEG = 2, CS_SINK_CV = 4 };
+enum CS_SinkKind {
+  CS_SINK_UNKNOWN = 0,
+  CS_SINK_MJPEG = 2,
+  CS_SINK_CV = 4,
+  CS_SINK_RAW = 8
+};
 
 /**
  * Listener event kinds
@@ -232,6 +236,8 @@
   char* name;
   int otherPathsCount;
   char** otherPaths;
+  int vendorId;
+  int productId;
 } CS_UsbCameraInfo;
 
 /**
@@ -351,8 +357,6 @@
  * @defgroup cscore_opencv_source_cfunc OpenCV Source Functions
  * @{
  */
-void CS_PutSourceFrame(CS_Source source, struct CvMat* image,
-                       CS_Status* status);
 void CS_NotifySourceError(CS_Source source, const char* msg, CS_Status* status);
 void CS_SetSourceConnected(CS_Source source, CS_Bool connected,
                            CS_Status* status);
@@ -415,9 +419,6 @@
  */
 void CS_SetSinkDescription(CS_Sink sink, const char* description,
                            CS_Status* status);
-uint64_t CS_GrabSinkFrame(CS_Sink sink, struct CvMat* image, CS_Status* status);
-uint64_t CS_GrabSinkFrameTimeout(CS_Sink sink, struct CvMat* image,
-                                 double timeout, CS_Status* status);
 char* CS_GetSinkError(CS_Sink sink, CS_Status* status);
 void CS_SetSinkEnabled(CS_Sink sink, CS_Bool enabled, CS_Status* status);
 /** @} */
diff --git a/cscore/src/main/native/include/cscore_cpp.h b/cscore/src/main/native/include/cscore_cpp.h
index a400b78..c1ec024 100644
--- a/cscore/src/main/native/include/cscore_cpp.h
+++ b/cscore/src/main/native/include/cscore_cpp.h
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2015-2019 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.                                                               */
@@ -21,9 +21,11 @@
 
 #include "cscore_c.h"
 
-namespace cv {
-class Mat;
-}  // namespace cv
+#ifdef _WIN32
+// Disable uninitialized variable warnings
+#pragma warning(push)
+#pragma warning(disable : 26495)
+#endif
 
 namespace wpi {
 class json;
@@ -54,6 +56,10 @@
   std::string name;
   /** Other path aliases to device (e.g. '/dev/v4l/by-id/...' etc on Linux) */
   std::vector<std::string> otherPaths;
+  /** USB Vendor Id */
+  int vendorId = -1;
+  /** USB Product Id */
+  int productId = -1;
 };
 
 /**
@@ -286,7 +292,6 @@
  * @defgroup cscore_opencv_source_func OpenCV Source Functions
  * @{
  */
-void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status);
 void NotifySourceError(CS_Source source, const wpi::Twine& msg,
                        CS_Status* status);
 void SetSourceConnected(CS_Source source, bool connected, CS_Status* status);
@@ -312,6 +317,7 @@
 CS_Sink CreateCvSinkCallback(const wpi::Twine& name,
                              std::function<void(uint64_t time)> processFrame,
                              CS_Status* status);
+
 /** @} */
 
 /**
@@ -356,9 +362,6 @@
  */
 void SetSinkDescription(CS_Sink sink, const wpi::Twine& description,
                         CS_Status* status);
-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);
 std::string GetSinkError(CS_Sink sink, CS_Status* status);
 wpi::StringRef GetSinkError(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
                             CS_Status* status);
@@ -429,18 +432,9 @@
 
 }  // namespace cs
 
-/**
- * @defgroup cscore_cpp_opencv_special cscore C functions taking a cv::Mat*
- *
- * These are needed for specific interop implementations.
- * @{
- */
-extern "C" {
-uint64_t CS_GrabSinkFrameCpp(CS_Sink sink, cv::Mat* image, CS_Status* status);
-uint64_t CS_GrabSinkFrameTimeoutCpp(CS_Sink sink, cv::Mat* image,
-                                    double timeout, CS_Status* status);
-void CS_PutSourceFrameCpp(CS_Source source, cv::Mat* image, CS_Status* status);
-}  // extern "C"
-/** @} */
+#ifdef _WIN32
+// Disable uninitialized variable warnings
+#pragma warning(pop)
+#endif
 
 #endif  // CSCORE_CSCORE_CPP_H_
diff --git a/cscore/src/main/native/include/cscore_cv.h b/cscore/src/main/native/include/cscore_cv.h
new file mode 100644
index 0000000..650399b
--- /dev/null
+++ b/cscore/src/main/native/include/cscore_cv.h
@@ -0,0 +1,209 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2019 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_CSCORE_CV_H_
+#define CSCORE_CSCORE_CV_H_
+
+#include "cscore_c.h"
+
+#ifdef CSCORE_CSCORE_RAW_CV_H_
+#error "Cannot include both cscore_cv.h and cscore_raw_cv.h in the same file"
+#endif
+
+#ifdef __cplusplus
+#include "cscore_oo.h"  // NOLINT(build/include_order)
+
+#endif
+
+#if CV_VERSION_MAJOR < 4
+
+#ifdef __cplusplus
+extern "C" {  // NOLINT(build/include_order)
+#endif
+
+struct CvMat;
+
+void CS_PutSourceFrame(CS_Source source, struct CvMat* image,
+                       CS_Status* status);
+
+uint64_t CS_GrabSinkFrame(CS_Sink sink, struct CvMat* image, CS_Status* status);
+uint64_t CS_GrabSinkFrameTimeout(CS_Sink sink, struct CvMat* image,
+                                 double timeout, CS_Status* status);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // CV_VERSION_MAJOR < 4
+
+#ifdef __cplusplus
+
+#include "cscore_oo.h"
+
+namespace cv {
+class Mat;
+}  // namespace cv
+
+namespace cs {
+
+/**
+ * @defgroup cscore_cpp_opencv_special cscore C functions taking a cv::Mat*
+ *
+ * These are needed for specific interop implementations.
+ * @{
+ */
+extern "C" {
+uint64_t CS_GrabSinkFrameCpp(CS_Sink sink, cv::Mat* image, CS_Status* status);
+uint64_t CS_GrabSinkFrameTimeoutCpp(CS_Sink sink, cv::Mat* image,
+                                    double timeout, CS_Status* status);
+void CS_PutSourceFrameCpp(CS_Source source, cv::Mat* image, CS_Status* status);
+}  // extern "C"
+/** @} */
+
+void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status);
+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);
+
+/**
+ * A source for user code to provide OpenCV images as video frames.
+ * These sources require the WPILib OpenCV builds.
+ * For an alternate OpenCV, include "cscore_raw_cv.h" instead, and
+ * include your Mat header before that header.
+ */
+class CvSource : public ImageSource {
+ public:
+  CvSource() = default;
+
+  /**
+   * Create an OpenCV source.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   * @param mode Video mode being generated
+   */
+  CvSource(const wpi::Twine& name, const VideoMode& mode);
+
+  /**
+   * Create an OpenCV source.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   * @param pixelFormat Pixel format
+   * @param width width
+   * @param height height
+   * @param fps fps
+   */
+  CvSource(const wpi::Twine& name, VideoMode::PixelFormat pixelFormat,
+           int width, int height, int fps);
+
+  /**
+   * Put an OpenCV image and notify sinks.
+   *
+   * <p>Only 8-bit single-channel or 3-channel (with BGR channel order) images
+   * are supported. If the format, depth or channel order is different, use
+   * cv::Mat::convertTo() and/or cv::cvtColor() to convert it first.
+   *
+   * @param image OpenCV image
+   */
+  void PutFrame(cv::Mat& image);
+};
+
+/**
+ * A sink for user code to accept video frames as OpenCV images.
+ * These sinks require the WPILib OpenCV builds.
+ * For an alternate OpenCV, include "cscore_raw_cv.h" instead, and
+ * include your Mat header before that header.
+ */
+class CvSink : public ImageSink {
+ public:
+  CvSink() = default;
+
+  /**
+   * Create a sink for accepting OpenCV images.
+   *
+   * <p>WaitForFrame() must be called on the created sink to get each new
+   * image.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   */
+  explicit CvSink(const wpi::Twine& name);
+
+  /**
+   * Create a sink for accepting OpenCV images in a separate thread.
+   *
+   * <p>A thread will be created that calls WaitForFrame() and calls the
+   * processFrame() callback each time a new frame arrives.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   * @param processFrame Frame processing function; will be called with a
+   *        time=0 if an error occurred.  processFrame should call GetImage()
+   *        or GetError() as needed, but should not call (except in very
+   *        unusual circumstances) WaitForImage().
+   */
+  CvSink(const wpi::Twine& name,
+         std::function<void(uint64_t time)> processFrame);
+
+  /**
+   * Wait for the next frame and get the image.
+   * Times out (returning 0) after timeout seconds.
+   * The provided image will have three 8-bit channels stored in BGR order.
+   *
+   * @return Frame time, or 0 on error (call GetError() to obtain the error
+   *         message); the frame time is in the same time base as wpi::Now(),
+   *         and is in 1 us increments.
+   */
+  uint64_t GrabFrame(cv::Mat& image, double timeout = 0.225) const;
+
+  /**
+   * Wait for the next frame and get the image.  May block forever.
+   * The provided image will have three 8-bit channels stored in BGR order.
+   *
+   * @return Frame time, or 0 on error (call GetError() to obtain the error
+   *         message); the frame time is in the same time base as wpi::Now(),
+   *         and is in 1 us increments.
+   */
+  uint64_t GrabFrameNoTimeout(cv::Mat& image) const;
+};
+
+inline CvSource::CvSource(const wpi::Twine& name, const VideoMode& mode) {
+  m_handle = CreateCvSource(name, mode, &m_status);
+}
+
+inline CvSource::CvSource(const wpi::Twine& name, VideoMode::PixelFormat format,
+                          int width, int height, int fps) {
+  m_handle =
+      CreateCvSource(name, VideoMode{format, width, height, fps}, &m_status);
+}
+
+inline void CvSource::PutFrame(cv::Mat& image) {
+  m_status = 0;
+  PutSourceFrame(m_handle, image, &m_status);
+}
+
+inline CvSink::CvSink(const wpi::Twine& name) {
+  m_handle = CreateCvSink(name, &m_status);
+}
+
+inline CvSink::CvSink(const wpi::Twine& name,
+                      std::function<void(uint64_t time)> processFrame) {
+  m_handle = CreateCvSinkCallback(name, processFrame, &m_status);
+}
+
+inline uint64_t CvSink::GrabFrame(cv::Mat& image, double timeout) const {
+  m_status = 0;
+  return GrabSinkFrameTimeout(m_handle, image, timeout, &m_status);
+}
+
+inline uint64_t CvSink::GrabFrameNoTimeout(cv::Mat& image) const {
+  m_status = 0;
+  return GrabSinkFrame(m_handle, image, &m_status);
+}
+
+}  // namespace cs
+
+#endif
+
+#endif  // CSCORE_CSCORE_CV_H_
diff --git a/cscore/src/main/native/include/cscore_oo.h b/cscore/src/main/native/include/cscore_oo.h
index 7e0cb9f..5ab0f70 100644
--- a/cscore/src/main/native/include/cscore_oo.h
+++ b/cscore/src/main/native/include/cscore_oo.h
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2015-2019 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.                                                               */
@@ -28,7 +28,7 @@
  */
 
 // Forward declarations so friend declarations work correctly
-class CvSource;
+class ImageSource;
 class VideoEvent;
 class VideoSink;
 class VideoSource;
@@ -37,7 +37,7 @@
  * A source or sink property.
  */
 class VideoProperty {
-  friend class CvSource;
+  friend class ImageSource;
   friend class VideoEvent;
   friend class VideoSink;
   friend class VideoSource;
@@ -51,7 +51,7 @@
     kEnum = CS_PROP_ENUM
   };
 
-  VideoProperty() : m_handle(0), m_kind(kNone) {}
+  VideoProperty() : m_status(0), m_handle(0), m_kind(kNone) {}
 
   std::string GetName() const;
 
@@ -617,43 +617,13 @@
 };
 
 /**
- * A source for user code to provide OpenCV images as video frames.
+ * A base class for single image providing sources.
  */
-class CvSource : public VideoSource {
+class ImageSource : public VideoSource {
+ protected:
+  ImageSource() = default;
+
  public:
-  CvSource() = default;
-
-  /**
-   * Create an OpenCV source.
-   *
-   * @param name Source name (arbitrary unique identifier)
-   * @param mode Video mode being generated
-   */
-  CvSource(const wpi::Twine& name, const VideoMode& mode);
-
-  /**
-   * Create an OpenCV source.
-   *
-   * @param name Source name (arbitrary unique identifier)
-   * @param pixelFormat Pixel format
-   * @param width width
-   * @param height height
-   * @param fps fps
-   */
-  CvSource(const wpi::Twine& name, VideoMode::PixelFormat pixelFormat,
-           int width, int height, int fps);
-
-  /**
-   * Put an OpenCV image and notify sinks.
-   *
-   * <p>Only 8-bit single-channel or 3-channel (with BGR channel order) images
-   * are supported. If the format, depth or channel order is different, use
-   * cv::Mat::convertTo() and/or cv::cvtColor() to convert it first.
-   *
-   * @param image OpenCV image
-   */
-  void PutFrame(cv::Mat& image);
-
   /**
    * Signal sinks that an error has occurred.  This should be called instead
    * of NotifyFrame when an error occurs.
@@ -979,37 +949,13 @@
 };
 
 /**
- * A sink for user code to accept video frames as OpenCV images.
+ * A base class for single image reading sinks.
  */
-class CvSink : public VideoSink {
+class ImageSink : public VideoSink {
+ protected:
+  ImageSink() = default;
+
  public:
-  CvSink() = default;
-
-  /**
-   * Create a sink for accepting OpenCV images.
-   *
-   * <p>WaitForFrame() must be called on the created sink to get each new
-   * image.
-   *
-   * @param name Source name (arbitrary unique identifier)
-   */
-  explicit CvSink(const wpi::Twine& name);
-
-  /**
-   * Create a sink for accepting OpenCV images in a separate thread.
-   *
-   * <p>A thread will be created that calls WaitForFrame() and calls the
-   * processFrame() callback each time a new frame arrives.
-   *
-   * @param name Source name (arbitrary unique identifier)
-   * @param processFrame Frame processing function; will be called with a
-   *        time=0 if an error occurred.  processFrame should call GetImage()
-   *        or GetError() as needed, but should not call (except in very
-   *        unusual circumstances) WaitForImage().
-   */
-  CvSink(const wpi::Twine& name,
-         std::function<void(uint64_t time)> processFrame);
-
   /**
    * Set sink description.
    *
@@ -1018,27 +964,6 @@
   void SetDescription(const wpi::Twine& description);
 
   /**
-   * Wait for the next frame and get the image.
-   * Times out (returning 0) after timeout seconds.
-   * The provided image will have three 8-bit channels stored in BGR order.
-   *
-   * @return Frame time, or 0 on error (call GetError() to obtain the error
-   *         message); the frame time is in the same time base as wpi::Now(),
-   *         and is in 1 us increments.
-   */
-  uint64_t GrabFrame(cv::Mat& image, double timeout = 0.225) const;
-
-  /**
-   * Wait for the next frame and get the image.  May block forever.
-   * The provided image will have three 8-bit channels stored in BGR order.
-   *
-   * @return Frame time, or 0 on error (call GetError() to obtain the error
-   *         message); the frame time is in the same time base as wpi::Now(),
-   *         and is in 1 us increments.
-   */
-  uint64_t GrabFrameNoTimeout(cv::Mat& image) const;
-
-  /**
    * Get error string.  Call this if WaitForFrame() returns 0 to determine
    * what the error is.
    */
diff --git a/cscore/src/main/native/include/cscore_oo.inl b/cscore/src/main/native/include/cscore_oo.inl
index 5162e1d..ffd2b23 100644
--- a/cscore/src/main/native/include/cscore_oo.inl
+++ b/cscore/src/main/native/include/cscore_oo.inl
@@ -76,7 +76,7 @@
 }
 
 inline VideoProperty::VideoProperty(CS_Property handle, Kind kind)
-  : m_handle(handle), m_kind(kind) {}
+  : m_status(0), m_handle(handle), m_kind(kind) {}
 
 inline VideoSource::VideoSource(const VideoSource& source)
     : m_handle(source.m_handle == 0 ? 0
@@ -378,37 +378,22 @@
                               std::initializer_list<T> hosts)
     : HttpCamera(name, HostToUrl(hosts), kAxis) {}
 
-inline CvSource::CvSource(const wpi::Twine& name, const VideoMode& mode) {
-  m_handle = CreateCvSource(name, mode, &m_status);
-}
-
-inline CvSource::CvSource(const wpi::Twine& name, VideoMode::PixelFormat format,
-                          int width, int height, int fps) {
-  m_handle =
-      CreateCvSource(name, VideoMode{format, width, height, fps}, &m_status);
-}
-
-inline void CvSource::PutFrame(cv::Mat& image) {
-  m_status = 0;
-  PutSourceFrame(m_handle, image, &m_status);
-}
-
-inline void CvSource::NotifyError(const wpi::Twine& msg) {
+inline void ImageSource::NotifyError(const wpi::Twine& msg) {
   m_status = 0;
   NotifySourceError(m_handle, msg, &m_status);
 }
 
-inline void CvSource::SetConnected(bool connected) {
+inline void ImageSource::SetConnected(bool connected) {
   m_status = 0;
   SetSourceConnected(m_handle, connected, &m_status);
 }
 
-inline void CvSource::SetDescription(const wpi::Twine& description) {
+inline void ImageSource::SetDescription(const wpi::Twine& description) {
   m_status = 0;
   SetSourceDescription(m_handle, description, &m_status);
 }
 
-inline VideoProperty CvSource::CreateProperty(const wpi::Twine& name,
+inline VideoProperty ImageSource::CreateProperty(const wpi::Twine& name,
                                               VideoProperty::Kind kind,
                                               int minimum, int maximum,
                                               int step, int defaultValue,
@@ -419,7 +404,7 @@
       minimum, maximum, step, defaultValue, value, &m_status)};
 }
 
-inline VideoProperty CvSource::CreateIntegerProperty(const wpi::Twine& name,
+inline VideoProperty ImageSource::CreateIntegerProperty(const wpi::Twine& name,
                                                     int minimum, int maximum,
                                                     int step, int defaultValue,
                                                     int value) {
@@ -429,7 +414,7 @@
       minimum, maximum, step, defaultValue, value, &m_status)};
 }
 
-inline VideoProperty CvSource::CreateBooleanProperty(const wpi::Twine& name,
+inline VideoProperty ImageSource::CreateBooleanProperty(const wpi::Twine& name,
                                                      bool defaultValue,
                                                      bool value) {
   m_status = 0;
@@ -438,7 +423,7 @@
       0, 1, 1, defaultValue ? 1 : 0, value ? 1 : 0, &m_status)};
 }
 
-inline VideoProperty CvSource::CreateStringProperty(const wpi::Twine& name,
+inline VideoProperty ImageSource::CreateStringProperty(const wpi::Twine& name,
                                                     const wpi::Twine& value) {
   m_status = 0;
   auto prop = VideoProperty{CreateSourceProperty(
@@ -449,14 +434,14 @@
 }
 
 
-inline void CvSource::SetEnumPropertyChoices(
+inline void ImageSource::SetEnumPropertyChoices(
     const VideoProperty& property, wpi::ArrayRef<std::string> choices) {
   m_status = 0;
   SetSourceEnumPropertyChoices(m_handle, property.m_handle, choices, &m_status);
 }
 
 template <typename T>
-inline void CvSource::SetEnumPropertyChoices(const VideoProperty& property,
+inline void ImageSource::SetEnumPropertyChoices(const VideoProperty& property,
                                              std::initializer_list<T> choices) {
   std::vector<std::string> vec;
   vec.reserve(choices.size());
@@ -575,36 +560,17 @@
               quality, &m_status);
 }
 
-inline CvSink::CvSink(const wpi::Twine& name) {
-  m_handle = CreateCvSink(name, &m_status);
-}
-
-inline CvSink::CvSink(const wpi::Twine& name,
-                      std::function<void(uint64_t time)> processFrame) {
-  m_handle = CreateCvSinkCallback(name, processFrame, &m_status);
-}
-
-inline void CvSink::SetDescription(const wpi::Twine& description) {
+inline void ImageSink::SetDescription(const wpi::Twine& description) {
   m_status = 0;
   SetSinkDescription(m_handle, description, &m_status);
 }
 
-inline uint64_t CvSink::GrabFrame(cv::Mat& image, double timeout) const {
-  m_status = 0;
-  return GrabSinkFrameTimeout(m_handle, image, timeout, &m_status);
-}
-
-inline uint64_t CvSink::GrabFrameNoTimeout(cv::Mat& image) const {
-  m_status = 0;
-  return GrabSinkFrame(m_handle, image, &m_status);
-}
-
-inline std::string CvSink::GetError() const {
+inline std::string ImageSink::GetError() const {
   m_status = 0;
   return GetSinkError(m_handle, &m_status);
 }
 
-inline void CvSink::SetEnabled(bool enabled) {
+inline void ImageSink::SetEnabled(bool enabled) {
   m_status = 0;
   SetSinkEnabled(m_handle, enabled, &m_status);
 }
diff --git a/cscore/src/main/native/include/cscore_raw.h b/cscore/src/main/native/include/cscore_raw.h
new file mode 100644
index 0000000..902d90e
--- /dev/null
+++ b/cscore/src/main/native/include/cscore_raw.h
@@ -0,0 +1,234 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2016-2019 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_CSCORE_RAW_H_
+#define CSCORE_CSCORE_RAW_H_
+
+#include "cscore_c.h"
+
+#ifdef __cplusplus
+#include "cscore_oo.h"
+#endif
+
+/**
+ * Raw Frame
+ */
+typedef struct CS_RawFrame {
+  char* data;
+  int dataLength;
+  int pixelFormat;
+  int width;
+  int height;
+  int totalData;
+} CS_RawFrame;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @defgroup cscore_raw_cfunc Raw Image Functions
+ * @{
+ */
+void CS_AllocateRawFrameData(CS_RawFrame* frame, int requestedSize);
+void CS_FreeRawFrameData(CS_RawFrame* frame);
+
+uint64_t CS_GrabRawSinkFrame(CS_Sink sink, struct CS_RawFrame* rawImage,
+                             CS_Status* status);
+uint64_t CS_GrabRawSinkFrameTimeout(CS_Sink sink, struct CS_RawFrame* rawImage,
+                                    double timeout, CS_Status* status);
+
+CS_Sink CS_CreateRawSink(const char* name, CS_Status* status);
+
+CS_Sink CS_CreateRawSinkCallback(const char* name, void* data,
+                                 void (*processFrame)(void* data,
+                                                      uint64_t time),
+                                 CS_Status* status);
+
+void CS_PutRawSourceFrame(CS_Source source, const struct CS_RawFrame* image,
+                          CS_Status* status);
+
+CS_Source CS_CreateRawSource(const char* name, const CS_VideoMode* mode,
+                             CS_Status* status);
+/** @} */
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#ifdef __cplusplus
+namespace cs {
+
+struct RawFrame : public CS_RawFrame {
+  RawFrame() {
+    data = nullptr;
+    dataLength = 0;
+    pixelFormat = CS_PIXFMT_UNKNOWN;
+    width = 0;
+    height = 0;
+    totalData = 0;
+  }
+
+  ~RawFrame() { CS_FreeRawFrameData(this); }
+};
+
+/**
+ * @defgroup cscore_raw_func Raw Image Functions
+ * @{
+ */
+
+CS_Source CreateRawSource(const wpi::Twine& name, const VideoMode& mode,
+                          CS_Status* status);
+
+CS_Sink CreateRawSink(const wpi::Twine& name, CS_Status* status);
+CS_Sink CreateRawSinkCallback(const wpi::Twine& name,
+                              std::function<void(uint64_t time)> processFrame,
+                              CS_Status* status);
+
+void PutSourceFrame(CS_Source source, const CS_RawFrame& image,
+                    CS_Status* status);
+uint64_t GrabSinkFrame(CS_Sink sink, CS_RawFrame& image, CS_Status* status);
+uint64_t GrabSinkFrameTimeout(CS_Sink sink, CS_RawFrame& image, double timeout,
+                              CS_Status* status);
+
+/**
+ * A source for user code to provide video frames as raw bytes.
+ *
+ * This is a complex API, most cases should use CvSource.
+ */
+class RawSource : public ImageSource {
+ public:
+  RawSource() = default;
+
+  /**
+   * Create a raw frame source.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   * @param mode Video mode being generated
+   */
+  RawSource(const wpi::Twine& name, const VideoMode& mode);
+
+  /**
+   * Create a raw frame source.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   * @param pixelFormat Pixel format
+   * @param width width
+   * @param height height
+   * @param fps fps
+   */
+  RawSource(const wpi::Twine& name, VideoMode::PixelFormat pixelFormat,
+            int width, int height, int fps);
+
+ protected:
+  /**
+   * Put a raw image and notify sinks.
+   *
+   * @param image raw frame image
+   */
+  void PutFrame(RawFrame& image);
+};
+
+/**
+ * A sink for user code to accept video frames as raw bytes.
+ *
+ * This is a complex API, most cases should use CvSource.
+ */
+class RawSink : public ImageSink {
+ public:
+  RawSink() = default;
+
+  /**
+   * Create a sink for accepting raw images.
+   *
+   * <p>GrabFrame() must be called on the created sink to get each new
+   * image.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   */
+  explicit RawSink(const wpi::Twine& name);
+
+  /**
+   * Create a sink for accepting raws images in a separate thread.
+   *
+   * <p>A thread will be created that calls WaitForFrame() and calls the
+   * processFrame() callback each time a new frame arrives.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   * @param processFrame Frame processing function; will be called with a
+   *        time=0 if an error occurred.  processFrame should call GetImage()
+   *        or GetError() as needed, but should not call (except in very
+   *        unusual circumstances) WaitForImage().
+   */
+  RawSink(const wpi::Twine& name,
+          std::function<void(uint64_t time)> processFrame);
+
+ protected:
+  /**
+   * Wait for the next frame and get the image.
+   * Times out (returning 0) after timeout seconds.
+   * The provided image will have three 8-bit channels stored in BGR order.
+   *
+   * @return Frame time, or 0 on error (call GetError() to obtain the error
+   *         message); the frame time is in the same time base as wpi::Now(),
+   *         and is in 1 us increments.
+   */
+  uint64_t GrabFrame(RawFrame& image, double timeout = 0.225) const;
+
+  /**
+   * Wait for the next frame and get the image.  May block forever.
+   * The provided image will have three 8-bit channels stored in BGR order.
+   *
+   * @return Frame time, or 0 on error (call GetError() to obtain the error
+   *         message); the frame time is in the same time base as wpi::Now(),
+   *         and is in 1 us increments.
+   */
+  uint64_t GrabFrameNoTimeout(RawFrame& image) const;
+};
+
+inline RawSource::RawSource(const wpi::Twine& name, const VideoMode& mode) {
+  m_handle = CreateRawSource(name, mode, &m_status);
+}
+
+inline RawSource::RawSource(const wpi::Twine& name,
+                            VideoMode::PixelFormat format, int width,
+                            int height, int fps) {
+  m_handle =
+      CreateRawSource(name, VideoMode{format, width, height, fps}, &m_status);
+}
+
+inline void RawSource::PutFrame(RawFrame& image) {
+  m_status = 0;
+  PutSourceFrame(m_handle, image, &m_status);
+}
+
+inline RawSink::RawSink(const wpi::Twine& name) {
+  m_handle = CreateRawSink(name, &m_status);
+}
+
+inline RawSink::RawSink(const wpi::Twine& name,
+                        std::function<void(uint64_t time)> processFrame) {
+  m_handle = CreateRawSinkCallback(name, processFrame, &m_status);
+}
+
+inline uint64_t RawSink::GrabFrame(RawFrame& image, double timeout) const {
+  m_status = 0;
+  return GrabSinkFrameTimeout(m_handle, image, timeout, &m_status);
+}
+
+inline uint64_t RawSink::GrabFrameNoTimeout(RawFrame& image) const {
+  m_status = 0;
+  return GrabSinkFrame(m_handle, image, &m_status);
+}
+
+}  // namespace cs
+
+/** @} */
+
+#endif
+
+#endif  // CSCORE_CSCORE_RAW_H_
diff --git a/cscore/src/main/native/include/cscore_raw_cv.h b/cscore/src/main/native/include/cscore_raw_cv.h
new file mode 100644
index 0000000..ed40006
--- /dev/null
+++ b/cscore/src/main/native/include/cscore_raw_cv.h
@@ -0,0 +1,218 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2019 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_CSCORE_RAW_CV_H_
+#define CSCORE_CSCORE_RAW_CV_H_
+
+#ifdef CSCORE_CSCORE_CV_H_
+#error "Cannot include both cscore_cv.h and cscore_raw_cv.h in the same file"
+#endif
+
+#include "cscore_raw.h"
+
+namespace cs {
+/**
+ * A source for using the raw frame API to provide opencv images.
+ *
+ * If you are using the WPILib OpenCV builds, do not use this, and
+ * instead include "cscore_cv.h" to get a more performant version.
+ *
+ * This is not dependent on any opencv binary ABI, and can be used
+ * with versions of OpenCV that are not 3. If using OpenCV 3, use
+ * CvSource.
+ */
+class RawCvSource : public RawSource {
+ public:
+  RawCvSource() = default;
+
+  /**
+   * Create a Raw OpenCV source.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   * @param mode Video mode being generated
+   */
+  RawCvSource(const wpi::Twine& name, const VideoMode& mode);
+
+  /**
+   * Create a Raw OpenCV source.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   * @param pixelFormat Pixel format
+   * @param width width
+   * @param height height
+   * @param fps fps
+   */
+  RawCvSource(const wpi::Twine& name, VideoMode::PixelFormat pixelFormat,
+              int width, int height, int fps);
+
+  /**
+   * Put an OpenCV image and notify sinks.
+   *
+   * <p>Only 8-bit single-channel or 3-channel (with BGR channel order) images
+   * are supported. If the format, depth or channel order is different, use
+   * cv::Mat::convertTo() and/or cv::cvtColor() to convert it first.
+   *
+   * @param image OpenCV image
+   */
+  void PutFrame(cv::Mat& image);
+
+ private:
+  RawFrame rawFrame;
+};
+
+/**
+ * A sink for user code to accept raw video frames as OpenCV images.
+ *
+ * If you are using the WPILib OpenCV builds, do not use this, and
+ * instead include "cscore_cv.h" to get a more performant version.
+ *
+ * This is not dependent on any opencv binary ABI, and can be used
+ * with versions of OpenCV that are not 3. If using OpenCV 3, use
+ * CvSink.
+ */
+class RawCvSink : public RawSink {
+ public:
+  RawCvSink() = default;
+
+  /**
+   * Create a sink for accepting OpenCV images.
+   *
+   * <p>WaitForFrame() must be called on the created sink to get each new
+   * image.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   */
+  explicit RawCvSink(const wpi::Twine& name);
+
+  /**
+   * Create a sink for accepting OpenCV images in a separate thread.
+   *
+   * <p>A thread will be created that calls WaitForFrame() and calls the
+   * processFrame() callback each time a new frame arrives.
+   *
+   * @param name Source name (arbitrary unique identifier)
+   * @param processFrame Frame processing function; will be called with a
+   *        time=0 if an error occurred.  processFrame should call GetImage()
+   *        or GetError() as needed, but should not call (except in very
+   *        unusual circumstances) WaitForImage().
+   */
+  RawCvSink(const wpi::Twine& name,
+            std::function<void(uint64_t time)> processFrame);
+
+  /**
+   * Wait for the next frame and get the image.
+   * Times out (returning 0) after timeout seconds.
+   * The provided image will have three 8-bit channels stored in BGR order.
+   *
+   * @return Frame time, or 0 on error (call GetError() to obtain the error
+   *         message); the frame time is in the same time base as wpi::Now(),
+   *         and is in 1 us increments.
+   */
+  uint64_t GrabFrame(cv::Mat& image, double timeout = 0.225);
+
+  /**
+   * Wait for the next frame and get the image.  May block forever.
+   * The provided image will have three 8-bit channels stored in BGR order.
+   *
+   * @return Frame time, or 0 on error (call GetError() to obtain the error
+   *         message); the frame time is in the same time base as wpi::Now(),
+   *         and is in 1 us increments.
+   */
+  uint64_t GrabFrameNoTimeout(cv::Mat& image);
+
+  /**
+   * Wait for the next frame and get the image.
+   * Times out (returning 0) after timeout seconds.
+   * The provided image will have three 8-bit channels stored in BGR order.
+   *
+   * @return Frame time, or 0 on error (call GetError() to obtain the error
+   *         message); the frame time is in the same time base as wpi::Now(),
+   *         and is in 1 us increments.
+   */
+  uint64_t GrabFrameDirect(cv::Mat& image, double timeout = 0.225);
+
+  /**
+   * Wait for the next frame and get the image.  May block forever.
+   * The provided image will have three 8-bit channels stored in BGR order.
+   *
+   * @return Frame time, or 0 on error (call GetError() to obtain the error
+   *         message); the frame time is in the same time base as wpi::Now(),
+   *         and is in 1 us increments.
+   */
+  uint64_t GrabFrameNoTimeoutDirect(cv::Mat& image);
+
+ private:
+  RawFrame rawFrame;
+};
+
+inline RawCvSource::RawCvSource(const wpi::Twine& name, const VideoMode& mode)
+    : RawSource{name, mode} {}
+
+inline RawCvSource::RawCvSource(const wpi::Twine& name,
+                                VideoMode::PixelFormat format, int width,
+                                int height, int fps)
+    : RawSource{name, format, width, height, fps} {}
+
+inline void RawCvSource::PutFrame(cv::Mat& image) {
+  m_status = 0;
+  rawFrame.data = reinterpret_cast<char*>(image.data);
+  rawFrame.width = image.cols;
+  rawFrame.height = image.rows;
+  rawFrame.totalData = image.total() * image.channels;
+  rawFrame.pixelFormat = image.channels == 3 ? CS_PIXFMT_BGR : CS_PIXFMT_GRAY;
+  PutSourceFrame(m_handle, rawFrame, &m_status);
+}
+
+inline RawCvSink::RawCvSink(const wpi::Twine& name) : RawSink{name} {}
+
+inline RawCvSink::RawCvSink(const wpi::Twine& name,
+                            std::function<void(uint64_t time)> processFrame)
+    : RawSink{name, processFrame} {}
+
+inline uint64_t RawCvSink::GrabFrame(cv::Mat& image, double timeout) {
+  cv::Mat tmpMat;
+  auto retVal = GrabFrameDirect(tmpMat);
+  if (retVal <= 0) {
+    return retVal;
+  }
+  tmpMat.copyTo(image);
+  return retVal;
+}
+
+inline uint64_t RawCvSink::GrabFrameNoTimeout(cv::Mat& image) {
+  cv::Mat tmpMat;
+  auto retVal = GrabFrameNoTimeoutDirect(tmpMat);
+  if (retVal <= 0) {
+    return retVal;
+  }
+  tmpMat.copyTo(image);
+  return retVal;
+}
+
+inline uint64_t RawCvSink::GrabFrameDirect(cv::Mat& image, double timeout) {
+  rawFrame.height = 0;
+  rawFrame.width = 0;
+  rawFrame.pixelFormat = CS_PixelFormat::CS_PIXFMT_BGR;
+  m_status = RawSink::GrabFrame(rawFrame, timeout);
+  if (m_status <= 0) return m_status;
+  image = cv::Mat{rawFrame.height, rawFrame.width, CV_8UC3, rawFrame.data};
+  return m_status;
+}
+
+inline uint64_t RawCvSink::GrabFrameNoTimeoutDirect(cv::Mat& image) {
+  rawFrame.height = 0;
+  rawFrame.width = 0;
+  rawFrame.pixelFormat = CS_PixelFormat::CS_PIXFMT_BGR;
+  m_status = RawSink::GrabFrameNoTimeout(rawFrame);
+  if (m_status <= 0) return m_status;
+  image = cv::Mat{rawFrame.height, rawFrame.width, CV_8UC3, rawFrame.data};
+  return m_status;
+}
+
+}  // namespace cs
+
+#endif  // CSCORE_CSCORE_RAW_CV_H_
diff --git a/cscore/src/main/native/linux/NetworkListener.cpp b/cscore/src/main/native/linux/NetworkListener.cpp
index ed3d966..6915b30 100644
--- a/cscore/src/main/native/linux/NetworkListener.cpp
+++ b/cscore/src/main/native/linux/NetworkListener.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2015-2019 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.                                                               */
@@ -132,8 +132,9 @@
       break;  // XXX: is this the right thing to do here?
     }
     if (len == 0) continue;  // EOF?
+    unsigned int ulen = static_cast<unsigned int>(len);
     for (struct nlmsghdr* nh = reinterpret_cast<struct nlmsghdr*>(buf);
-         NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) {
+         NLMSG_OK(nh, ulen); nh = NLMSG_NEXT(nh, ulen)) {
       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) {
diff --git a/cscore/src/main/native/linux/UsbCameraImpl.cpp b/cscore/src/main/native/linux/UsbCameraImpl.cpp
index 125c936..6bed486 100644
--- a/cscore/src/main/native/linux/UsbCameraImpl.cpp
+++ b/cscore/src/main/native/linux/UsbCameraImpl.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -25,9 +25,9 @@
 #include <algorithm>
 
 #include <wpi/FileSystem.h>
+#include <wpi/MemAlloc.h>
 #include <wpi/Path.h>
 #include <wpi/SmallString.h>
-#include <wpi/memory.h>
 #include <wpi/raw_ostream.h>
 #include <wpi/timestamp.h>
 
@@ -108,6 +108,11 @@
 static constexpr const int quirkLifeCamHd3000[] = {
     5, 10, 20, 39, 78, 156, 312, 625, 1250, 2500, 5000, 10000, 20000};
 
+static constexpr char const* quirkPS3EyePropExAuto = "auto_exposure";
+static constexpr char const* quirkPS3EyePropExValue = "exposure";
+static constexpr const int quirkPS3EyePropExAutoOn = 0;
+static constexpr const int quirkPS3EyePropExAutoOff = 1;
+
 int UsbCameraImpl::RawToPercentage(const UsbCameraProperty& rawProp,
                                    int rawValue) {
   // LifeCam exposure setting quirk
@@ -138,10 +143,36 @@
          (rawProp.maximum - rawProp.minimum) * (percentValue / 100.0);
 }
 
-static bool GetDescriptionSysV4L(wpi::StringRef path, std::string* desc) {
-  wpi::SmallString<64> ifpath{"/sys/class/video4linux/"};
-  ifpath += path.substr(5);
-  ifpath += "/device/interface";
+static bool GetVendorProduct(int dev, int* vendor, int* product) {
+  wpi::SmallString<64> ifpath;
+  {
+    wpi::raw_svector_ostream oss{ifpath};
+    oss << "/sys/class/video4linux/video" << dev << "/device/modalias";
+  }
+
+  int fd = open(ifpath.c_str(), O_RDONLY);
+  if (fd < 0) return false;
+
+  char readBuf[128];
+  ssize_t n = read(fd, readBuf, sizeof(readBuf));
+  close(fd);
+
+  if (n <= 0) return false;
+  wpi::StringRef readStr{readBuf};
+  if (readStr.substr(readStr.find('v')).substr(1, 4).getAsInteger(16, *vendor))
+    return false;
+  if (readStr.substr(readStr.find('p')).substr(1, 4).getAsInteger(16, *product))
+    return false;
+
+  return true;
+}
+
+static bool GetDescriptionSysV4L(int dev, std::string* desc) {
+  wpi::SmallString<64> ifpath;
+  {
+    wpi::raw_svector_ostream oss{ifpath};
+    oss << "/sys/class/video4linux/video" << dev << "/device/interface";
+  }
 
   int fd = open(ifpath.c_str(), O_RDONLY);
   if (fd < 0) return false;
@@ -187,23 +218,35 @@
   return true;
 }
 
-static std::string GetDescriptionImpl(const char* cpath) {
+static int GetDeviceNum(const char* cpath) {
   wpi::StringRef path{cpath};
-  char pathBuf[128];
-  std::string rv;
+  std::string pathBuf;
 
-  // If trying to get by id or path, follow symlink
-  if (path.startswith("/dev/v4l/by-id/")) {
-    ssize_t n = readlink(cpath, pathBuf, sizeof(pathBuf));
-    if (n > 0) path = wpi::StringRef(pathBuf, n);
-  } else if (path.startswith("/dev/v4l/by-path/")) {
-    ssize_t n = readlink(cpath, pathBuf, sizeof(pathBuf));
-    if (n > 0) path = wpi::StringRef(pathBuf, n);
+  // 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(cpath)) {
+    char* target = ::realpath(cpath, nullptr);
+    if (target) {
+      pathBuf = target;
+      path = pathBuf;
+      std::free(target);
+    }
   }
 
-  if (path.startswith("/dev/video")) {
+  path = wpi::sys::path::filename(path);
+  if (!path.startswith("video")) return -1;
+  int dev = -1;
+  if (path.substr(5).getAsInteger(10, dev)) return -1;
+  return dev;
+}
+
+static std::string GetDescriptionImpl(const char* cpath) {
+  std::string rv;
+
+  int dev = GetDeviceNum(cpath);
+  if (dev >= 0) {
     // Sometimes the /sys tree gives a better name.
-    if (GetDescriptionSysV4L(path, &rv)) return rv;
+    if (GetDescriptionSysV4L(dev, &rv)) return rv;
   }
 
   // Otherwise use an ioctl to query the caps and get the card name
@@ -447,8 +490,7 @@
   if (fd < 0) return;  // already disconnected
 
   // Unmap buffers
-  for (int i = 0; i < kNumBuffers; ++i)
-    m_buffers[i] = std::move(UsbCameraBuffer{});
+  for (int i = 0; i < kNumBuffers; ++i) m_buffers[i] = UsbCameraBuffer{};
 
   // Close device
   close(fd);
@@ -492,7 +534,7 @@
 
     // Restore settings
     SDEBUG3("restoring settings");
-    std::unique_lock<wpi::mutex> lock2(m_mutex);
+    std::unique_lock lock2(m_mutex);
     for (size_t i = 0; i < m_propertyData.size(); ++i) {
       const auto prop =
           static_cast<const UsbCameraProperty*>(m_propertyData[i].get());
@@ -534,11 +576,11 @@
     SDEBUG4("buf " << i << " length=" << buf.length
                    << " offset=" << buf.m.offset);
 
-    m_buffers[i] = std::move(UsbCameraBuffer(fd, buf.length, buf.m.offset));
+    m_buffers[i] = UsbCameraBuffer(fd, buf.length, buf.m.offset);
     if (!m_buffers[i].m_data) {
       SWARNING("could not map buffer " << i);
       // release other buffers
-      for (int j = 0; j < i; ++j) m_buffers[j] = std::move(UsbCameraBuffer{});
+      for (int j = 0; j < i; ++j) m_buffers[j] = UsbCameraBuffer{};
       close(fd);
       m_fd = -1;
       return;
@@ -737,7 +779,7 @@
 }
 
 void UsbCameraImpl::DeviceProcessCommands() {
-  std::unique_lock<wpi::mutex> lock(m_mutex);
+  std::unique_lock lock(m_mutex);
   if (m_commands.empty()) return;
   while (!m_commands.empty()) {
     auto msg = std::move(m_commands.back());
@@ -816,7 +858,7 @@
   vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
   if (DoIoctl(fd, VIDIOC_G_FMT, &vfmt) != 0) {
     SERROR("could not read current video mode");
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     m_mode = VideoMode{VideoMode::kMJPEG, 320, 240, 30};
     return;
   }
@@ -882,7 +924,7 @@
 
   // Save to global mode
   {
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     m_mode.pixelFormat = pixelFormat;
     m_mode.width = width;
     m_mode.height = height;
@@ -908,11 +950,11 @@
   std::unique_ptr<UsbCameraProperty> perProp;
   if (IsPercentageProperty(rawProp->name)) {
     perProp =
-        wpi::make_unique<UsbCameraProperty>(rawProp->name, 0, *rawProp, 0, 0);
+        std::make_unique<UsbCameraProperty>(rawProp->name, 0, *rawProp, 0, 0);
     rawProp->name = "raw_" + perProp->name;
   }
 
-  std::unique_lock<wpi::mutex> lock(m_mutex);
+  std::unique_lock lock(m_mutex);
   int* rawIndex = &m_properties[rawProp->name];
   bool newRaw = *rawIndex == 0;
   UsbCameraProperty* oldRawProp =
@@ -1071,7 +1113,7 @@
   }
 
   {
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     m_videoModes.swap(modes);
   }
   m_notifier.NotifySource(*this, CS_SOURCE_VIDEOMODES_UPDATED);
@@ -1086,14 +1128,14 @@
 
   // Add the message to the command queue
   {
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     m_commands.emplace_back(std::move(msg));
   }
 
   // Signal the camera thread
   if (eventfd_write(fd, 1) < 0) return CS_SOURCE_IS_DISCONNECTED;
 
-  std::unique_lock<wpi::mutex> lock(m_mutex);
+  std::unique_lock lock(m_mutex);
   while (m_active) {
     // Did we get a response to *our* request?
     auto it =
@@ -1121,7 +1163,7 @@
 
   // Add the message to the command queue
   {
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     m_commands.emplace_back(std::move(msg));
   }
 
@@ -1131,7 +1173,7 @@
 
 std::unique_ptr<PropertyImpl> UsbCameraImpl::CreateEmptyProperty(
     const wpi::Twine& name) const {
-  return wpi::make_unique<UsbCameraProperty>(name);
+  return std::make_unique<UsbCameraProperty>(name);
 }
 
 bool UsbCameraImpl::CacheProperties(CS_Status* status) const {
@@ -1150,6 +1192,14 @@
   wpi::StringRef desc = GetDescription(descbuf);
   m_lifecam_exposure =
       desc.endswith("LifeCam HD-3000") || desc.endswith("LifeCam Cinema (TM)");
+
+  int deviceNum = GetDeviceNum(m_path.c_str());
+  if (deviceNum >= 0) {
+    int vendorId, productId;
+    if (GetVendorProduct(deviceNum, &vendorId, &productId)) {
+      m_ps3eyecam_exposure = vendorId == 0x2000 && productId == 0x0145;
+    }
+  }
 }
 
 void UsbCameraImpl::SetProperty(int property, int value, CS_Status* status) {
@@ -1195,21 +1245,41 @@
 
 void UsbCameraImpl::SetExposureAuto(CS_Status* status) {
   // auto; this is an enum value
-  SetProperty(GetPropertyIndex(kPropExAuto), 3, status);
+  if (m_ps3eyecam_exposure) {
+    SetProperty(GetPropertyIndex(quirkPS3EyePropExAuto),
+                quirkPS3EyePropExAutoOn, status);
+
+  } else {
+    SetProperty(GetPropertyIndex(kPropExAuto), 3, status);
+  }
 }
 
 void UsbCameraImpl::SetExposureHoldCurrent(CS_Status* status) {
-  SetProperty(GetPropertyIndex(kPropExAuto), 1, status);  // manual
+  if (m_ps3eyecam_exposure) {
+    SetProperty(GetPropertyIndex(quirkPS3EyePropExAuto),
+                quirkPS3EyePropExAutoOff, status);  // manual
+  } else {
+    SetProperty(GetPropertyIndex(kPropExAuto), 1, status);  // manual
+  }
 }
 
 void UsbCameraImpl::SetExposureManual(int value, CS_Status* status) {
-  SetProperty(GetPropertyIndex(kPropExAuto), 1, status);  // manual
+  if (m_ps3eyecam_exposure) {
+    SetProperty(GetPropertyIndex(quirkPS3EyePropExAuto),
+                quirkPS3EyePropExAutoOff, status);  // manual
+  } else {
+    SetProperty(GetPropertyIndex(kPropExAuto), 1, status);  // manual
+  }
   if (value > 100) {
     value = 100;
   } else if (value < 0) {
     value = 0;
   }
-  SetProperty(GetPropertyIndex(kPropExValue), value, status);
+  if (m_ps3eyecam_exposure) {
+    SetProperty(GetPropertyIndex(quirkPS3EyePropExValue), value, status);
+  } else {
+    SetProperty(GetPropertyIndex(kPropExValue), value, status);
+  }
 }
 
 bool UsbCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
@@ -1292,24 +1362,15 @@
   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);
+  info.dev = GetDeviceNum(keypath.c_str());
 
   // description
   info.name = GetDescriptionImpl(keypath.c_str());
 
+  // vendor/product id
+  GetVendorProduct(info.dev, &info.vendorId, &info.productId);
+
   // look through /dev/v4l/by-id and /dev/v4l/by-path for symlinks to the
   // keypath
   wpi::SmallString<128> path;
@@ -1360,6 +1421,8 @@
       info.name = GetDescriptionImpl(path.c_str());
       if (info.name.empty()) continue;
 
+      GetVendorProduct(dev, &info.vendorId, &info.productId);
+
       if (dev >= retval.size()) retval.resize(info.dev + 1);
       retval[info.dev] = std::move(info);
     }
diff --git a/cscore/src/main/native/linux/UsbCameraImpl.h b/cscore/src/main/native/linux/UsbCameraImpl.h
index eb4a311..3627228 100644
--- a/cscore/src/main/native/linux/UsbCameraImpl.h
+++ b/cscore/src/main/native/linux/UsbCameraImpl.h
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -164,7 +164,8 @@
   std::thread m_cameraThread;
 
   // Quirks
-  bool m_lifecam_exposure{false};  // Microsoft LifeCam exposure
+  bool m_lifecam_exposure{false};    // Microsoft LifeCam exposure
+  bool m_ps3eyecam_exposure{false};  // PS3 Eyecam exposure
 
   //
   // Variables protected by m_mutex
diff --git a/cscore/src/main/native/linux/UsbCameraProperty.cpp b/cscore/src/main/native/linux/UsbCameraProperty.cpp
index 4f51976..4dfa39c 100644
--- a/cscore/src/main/native/linux/UsbCameraProperty.cpp
+++ b/cscore/src/main/native/linux/UsbCameraProperty.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -7,7 +7,6 @@
 
 #include "UsbCameraProperty.h"
 
-#include <wpi/STLExtras.h>
 #include <wpi/SmallString.h>
 #include <wpi/raw_ostream.h>
 
@@ -224,7 +223,7 @@
     *id = qc_ext.id;  // copy back
     // We don't support array types
     if (qc_ext.elems > 1 || qc_ext.nr_of_dims > 0) return nullptr;
-    prop = wpi::make_unique<UsbCameraProperty>(qc_ext);
+    prop = std::make_unique<UsbCameraProperty>(qc_ext);
   }
 #endif
   if (!prop) {
@@ -235,7 +234,7 @@
     rc = TryIoctl(fd, VIDIOC_QUERYCTRL, &qc);
     *id = qc.id;  // copy back
     if (rc != 0) return nullptr;
-    prop = wpi::make_unique<UsbCameraProperty>(qc);
+    prop = std::make_unique<UsbCameraProperty>(qc);
   }
 
   // Cache enum property choices
diff --git a/cscore/src/main/native/linux/UsbCameraProperty.h b/cscore/src/main/native/linux/UsbCameraProperty.h
index d3569cc..32c5e19 100644
--- a/cscore/src/main/native/linux/UsbCameraProperty.h
+++ b/cscore/src/main/native/linux/UsbCameraProperty.h
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
diff --git a/cscore/src/main/native/windows/NetworkUtil.cpp b/cscore/src/main/native/windows/NetworkUtil.cpp
index e994eb8..151ba79 100644
--- a/cscore/src/main/native/windows/NetworkUtil.cpp
+++ b/cscore/src/main/native/windows/NetworkUtil.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
diff --git a/cscore/src/main/native/windows/UsbCameraImpl.cpp b/cscore/src/main/native/windows/UsbCameraImpl.cpp
index e45f361..69c1fcb 100644
--- a/cscore/src/main/native/windows/UsbCameraImpl.cpp
+++ b/cscore/src/main/native/windows/UsbCameraImpl.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Copyright (c) 2018-2019 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.                                                               */
@@ -28,8 +28,8 @@
 #include <opencv2/core/core.hpp>
 #include <opencv2/highgui/highgui.hpp>
 #include <opencv2/imgproc/imgproc.hpp>
+#include <wpi/MemAlloc.h>
 #include <wpi/SmallString.h>
-#include <wpi/memory.h>
 #include <wpi/raw_ostream.h>
 #include <wpi/timestamp.h>
 
@@ -54,6 +54,8 @@
 #pragma comment(lib, "Mfreadwrite.lib")
 #pragma comment(lib, "Shlwapi.lib")
 
+#pragma warning(disable : 4996 4018 26451)
+
 static constexpr int NewImageMessage = 0x0400 + 4488;
 static constexpr int SetCameraMessage = 0x0400 + 254;
 static constexpr int WaitForStartupMessage = 0x0400 + 294;
@@ -76,12 +78,16 @@
     : SourceImpl{name, logger, notifier, telemetry}, m_path{path.str()} {
   std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
   m_widePath = utf8_conv.from_bytes(m_path.c_str());
+  m_deviceId = -1;
+  StartMessagePump();
 }
 
 UsbCameraImpl::UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger,
                              Notifier& notifier, Telemetry& telemetry,
                              int deviceId)
-    : SourceImpl{name, logger, notifier, telemetry}, m_deviceId(deviceId) {}
+    : SourceImpl{name, logger, notifier, telemetry}, m_deviceId(deviceId) {
+  StartMessagePump();
+}
 
 UsbCameraImpl::~UsbCameraImpl() { m_messagePump = nullptr; }
 
@@ -192,11 +198,14 @@
       SetCameraMessage, Message::kNumSinksEnabledChanged, nullptr);
 }
 
-void UsbCameraImpl::Start() {
+void UsbCameraImpl::StartMessagePump() {
   m_messagePump = std::make_unique<WindowsMessagePump>(
       [this](HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam) {
         return this->PumpMain(hwnd, uiMsg, wParam, lParam);
       });
+}
+
+void UsbCameraImpl::Start() {
   m_messagePump->PostWindowMessage(PumpReadyMessage, nullptr, nullptr);
 }
 
@@ -354,6 +363,7 @@
       DeviceConnect();
       break;
     case WaitForStartupMessage:
+      DeviceConnect();
       return CS_OK;
     case WM_DEVICECHANGE: {
       // Device potentially changed
@@ -401,7 +411,7 @@
       {
         Message* msg = reinterpret_cast<Message*>(lParam);
         Message::Kind msgKind = static_cast<Message::Kind>(wParam);
-        std::unique_lock<wpi::mutex> lock(m_mutex);
+        std::unique_lock lock(m_mutex);
         auto retVal = DeviceProcessCommand(lock, msgKind, msg);
         return retVal;
       }
@@ -413,16 +423,16 @@
 
 static cs::VideoMode::PixelFormat GetFromGUID(const GUID& guid) {
   // Compare GUID to one of the supported ones
-  if (guid == MFVideoFormat_NV12) {
+  if (IsEqualGUID(guid, MFVideoFormat_NV12)) {
     // GrayScale
     return cs::VideoMode::PixelFormat::kGray;
-  } else if (guid == MFVideoFormat_YUY2) {
+  } else if (IsEqualGUID(guid, MFVideoFormat_YUY2)) {
     return cs::VideoMode::PixelFormat::kYUYV;
-  } else if (guid == MFVideoFormat_RGB24) {
+  } else if (IsEqualGUID(guid, MFVideoFormat_RGB24)) {
     return cs::VideoMode::PixelFormat::kBGR;
-  } else if (guid == MFVideoFormat_MJPG) {
+  } else if (IsEqualGUID(guid, MFVideoFormat_MJPG)) {
     return cs::VideoMode::PixelFormat::kMJPEG;
-  } else if (guid == MFVideoFormat_RGB565) {
+  } else if (IsEqualGUID(guid, MFVideoFormat_RGB565)) {
     return cs::VideoMode::PixelFormat::kRGB565;
   } else {
     return cs::VideoMode::PixelFormat::kUnknown;
@@ -585,11 +595,11 @@
   std::unique_ptr<UsbCameraProperty> perProp;
   if (IsPercentageProperty(rawProp->name)) {
     perProp =
-        wpi::make_unique<UsbCameraProperty>(rawProp->name, 0, *rawProp, 0, 0);
+        std::make_unique<UsbCameraProperty>(rawProp->name, 0, *rawProp, 0, 0);
     rawProp->name = "raw_" + perProp->name;
   }
 
-  std::unique_lock<wpi::mutex> lock(m_mutex);
+  std::unique_lock lock(m_mutex);
   int* rawIndex = &m_properties[rawProp->name];
   bool newRaw = *rawIndex == 0;
   UsbCameraProperty* oldRawProp =
@@ -672,7 +682,7 @@
   }
 
   NotifyPropertyCreated(*rawIndex, *rawPropPtr);
-  if (perPropPtr) NotifyPropertyCreated(*perIndex, *perPropPtr);
+  if (perPropPtr && perIndex) NotifyPropertyCreated(*perIndex, *perPropPtr);
 }
 
 CS_StatusValue UsbCameraImpl::DeviceProcessCommand(
@@ -808,7 +818,10 @@
 
     m_currentMode = std::move(newModeType);
     m_mode = newMode;
+#pragma warning(push)
+#pragma warning(disable : 26110)
     lock.unlock();
+#pragma warning(pop)
     if (m_sourceReader) {
       DeviceDisconnect();
       DeviceConnect();
@@ -862,10 +875,10 @@
         // Default mode is not supported. Grab first supported image
         auto&& firstSupported = m_windowsVideoModes[0];
         m_currentMode = firstSupported.second;
-        std::lock_guard<wpi::mutex> lock(m_mutex);
+        std::scoped_lock lock(m_mutex);
         m_mode = firstSupported.first;
       } else {
-        std::lock_guard<wpi::mutex> lock(m_mutex);
+        std::scoped_lock lock(m_mutex);
         m_mode = result->first;
       }
     }
@@ -947,7 +960,7 @@
     count++;
   }
   {
-    std::lock_guard<wpi::mutex> lock(m_mutex);
+    std::scoped_lock lock(m_mutex);
     m_videoModes.swap(modes);
   }
   m_notifier.NotifySource(*this, CS_SOURCE_VIDEOMODES_UPDATED);
@@ -962,6 +975,7 @@
   std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
   ComPtr<IMFAttributes> pAttributes;
   IMFActivate** ppDevices = nullptr;
+  UINT32 count = 0;
 
   // Create an attribute store to specify the enumeration parameters.
   HRESULT hr = MFCreateAttributes(pAttributes.GetAddressOf(), 1);
@@ -977,7 +991,6 @@
   }
 
   // Enumerate devices.
-  UINT32 count;
   hr = MFEnumDeviceSources(pAttributes.Get(), &ppDevices, &count);
   if (FAILED(hr)) {
     goto done;
@@ -993,11 +1006,11 @@
     info.dev = i;
     WCHAR buf[512];
     ppDevices[i]->GetString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, buf,
-                            sizeof(buf), NULL);
+                            sizeof(buf) / sizeof(WCHAR), NULL);
     info.name = utf8_conv.to_bytes(buf);
     ppDevices[i]->GetString(
         MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, buf,
-        sizeof(buf), NULL);
+        sizeof(buf) / sizeof(WCHAR), NULL);
     info.path = utf8_conv.to_bytes(buf);
     retval.emplace_back(std::move(info));
   }
@@ -1005,12 +1018,15 @@
 done:
   pAttributes.Reset();
 
-  for (DWORD i = 0; i < count; i++) {
-    if (ppDevices[i]) {
-      ppDevices[i]->Release();
-      ppDevices[i] = nullptr;
+  if (ppDevices) {
+    for (DWORD i = 0; i < count; i++) {
+      if (ppDevices[i]) {
+        ppDevices[i]->Release();
+        ppDevices[i] = nullptr;
+      }
     }
   }
+
   CoTaskMemFree(ppDevices);
   return retval;
 }
diff --git a/cscore/src/main/native/windows/UsbCameraImpl.h b/cscore/src/main/native/windows/UsbCameraImpl.h
index 4013cda..11e07aa 100644
--- a/cscore/src/main/native/windows/UsbCameraImpl.h
+++ b/cscore/src/main/native/windows/UsbCameraImpl.h
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Copyright (c) 2018-2019 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.                                                               */
@@ -98,7 +98,7 @@
     };
 
     explicit Message(Kind kind_)
-        : kind(kind_), from(std::this_thread::get_id()) {}
+        : kind(kind_), data{0}, from(std::this_thread::get_id()) {}
 
     Kind kind;
     int data[4];
@@ -154,6 +154,8 @@
   int RawToPercentage(const UsbCameraProperty& rawProp, int rawValue);
   int PercentageToRaw(const UsbCameraProperty& rawProp, int percentValue);
 
+  void StartMessagePump();
+
   //
   // Variables only used within camera thread
   //
diff --git a/cscore/src/main/native/windows/UsbCameraProperty.cpp b/cscore/src/main/native/windows/UsbCameraProperty.cpp
index ee4198b..ae22436 100644
--- a/cscore/src/main/native/windows/UsbCameraProperty.cpp
+++ b/cscore/src/main/native/windows/UsbCameraProperty.cpp
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Copyright (c) 2018-2019 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.                                                               */
diff --git a/cscore/src/main/native/windows/UsbCameraProperty.h b/cscore/src/main/native/windows/UsbCameraProperty.h
index 68795fc..11888e9 100644
--- a/cscore/src/main/native/windows/UsbCameraProperty.h
+++ b/cscore/src/main/native/windows/UsbCameraProperty.h
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
+/* Copyright (c) 2016-2019 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.                                                               */
@@ -68,8 +68,10 @@
   bool isAutoProp{true};
 
   bool isControlProperty{false};
-  tagVideoProcAmpProperty tagVideoProc;
-  tagCameraControlProperty tagCameraControl;
+  tagVideoProcAmpProperty tagVideoProc{
+      tagVideoProcAmpProperty::VideoProcAmp_Brightness};
+  tagCameraControlProperty tagCameraControl{
+      tagCameraControlProperty::CameraControl_Pan};
 
   // If this is a percentage (rather than raw) property
   bool percentage{false};
diff --git a/cscore/src/test/java/edu/wpi/cscore/UsbCameraTest.java b/cscore/src/test/java/edu/wpi/cscore/UsbCameraTest.java
index b55dc4e..9df3645 100644
--- a/cscore/src/test/java/edu/wpi/cscore/UsbCameraTest.java
+++ b/cscore/src/test/java/edu/wpi/cscore/UsbCameraTest.java
@@ -1,5 +1,5 @@
 /*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
+/* Copyright (c) 2018-2019 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.                                                               */
@@ -25,16 +25,14 @@
 class UsbCameraTest {
   @Nested
   @EnabledOnOs(OS.LINUX)
-  static class ConnectVerbose {
+  class ConnectVerbose {
     @Test
     void setConnectVerboseEnabledTest() {
       try (UsbCamera camera = new UsbCamera("Nonexistant Camera", getNonexistentCameraDev())) {
         camera.setConnectVerbose(1);
 
         CompletableFuture<String> result = new CompletableFuture<>();
-        CameraServerJNI.setLogger((level, file, line, message) -> {
-          result.complete(message);
-        }, 20);
+        CameraServerJNI.setLogger((level, file, line, message) -> result.complete(message), 20);
 
         assertTimeoutPreemptively(Duration.ofSeconds(5),
             () -> assertTrue(result.get().contains("Connecting to USB camera on ")));
@@ -47,9 +45,7 @@
         camera.setConnectVerbose(0);
 
         CompletableFuture<String> result = new CompletableFuture<>();
-        CameraServerJNI.setLogger((level, file, line, message) -> {
-          result.complete(message);
-        }, 20);
+        CameraServerJNI.setLogger((level, file, line, message) -> result.complete(message), 20);
 
         assertThrows(TimeoutException.class,
             () -> result.get(3, TimeUnit.SECONDS));
@@ -60,6 +56,6 @@
   private static int getNonexistentCameraDev() {
     return Arrays.stream(CameraServerJNI.enumerateUsbCameras())
         .mapToInt(info -> info.dev)
-        .max().orElseGet(() -> -1) + 1;
+        .max().orElse(-1) + 1;
   }
 }