Java Development Kit (JDK) does not provide a built-in API for camera access. A common workaround is to use OpenCV with Java bindings (such as JavaCV). However, OpenCV is a fairly large library, which may not be suitable for all applications.
In this tutorial, we will demonstrate how to build a Java Camera SDK by wrapping the LiteCam C++ SDK, and then integrate it with ZXing and Dynamsoft Barcode Reader to create a complete barcode scanning solution — from setup to deployment.
Demo: Java Barcode Scanner & Reader
Prerequisites
System Requirements
- Java JDK 8+ and Maven 3.6+
- A camera device (for scanning)
- Platform dependencies: Windows (Visual Studio), Linux (
libx11-dev libv4l-dev
), macOS (Xcode) - A 30-day free trial license for Dynamsoft Barcode Reader
Platform-Specific Requirements
Windows
- Visual Studio 2019 or later (for building from source)
- Media Foundation (included with Windows)
- Windows 10/11 recommended
Linux
sudo apt update sudo apt install libx11-dev libv4l-dev
macOS
- Xcode development tools (for building from source)
- AVFoundation framework (included with macOS)
Project Overview
This project consists of two main components:
- LiteCam SDK: A lightweight, cross-platform Java camera capture library
-
Maven Barcode Scanner: A full-featured barcode scanning application with dual detection engines
Key Features
- Real-time Camera Feed: High-performance camera capture using native JNI
- Dual Barcode Engines: Switch between ZXing (open-source) and Dynamsoft (enterprise)
- Visual Overlays: Real-time barcode highlighting with coordinates
- File Mode Support: Drag-and-drop image processing
- Cross-Platform: Windows, macOS, and Linux support
- Convenience Scripts: Cross-platform build and run scripts for easy development
LiteCam SDK Overview
LiteCam is a lightweight C++ camera SDK. A JNI bridge turns it into a Java-compatible library.
Core Features
- Cross-Platform Video Capture: Uses platform-native APIs (Media Foundation, V4L2, AVFoundation)
- RGB Frame Access: Direct access to uncompressed RGB data
- JNI Integration: Optimized native bridge for Java applications
- Resolution Control: Support for multiple resolutions and frame rates
Architecture
┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐ │ Java App │────│ LiteCam │────│ Native Camera │ │ │ │ JNI Bridge │ │ APIs │ └─────────────────┘ └──────────────┘ └─────────────────┘
Supported Platforms
Platform | Camera API | Display |
---|---|---|
Windows | Media Foundation | GDI/DirectX |
Linux | Video4Linux (V4L2) | X11 |
macOS | AVFoundation | Cocoa |
Barcode Scanner Application
The Maven Barcode Scanner application demonstrates advanced integration of camera capture with multiple barcode detection engines.
Architecture Overview
┌──────────────────┐ │ Swing GUI │ ├──────────────────┤ │ Camera Panel │ ← Live preview with overlays │ Controls Panel │ ← Engine selection, modes │ Results Panel │ ← Detection history └──────────────────┘ │ ├────────────────────┤ │ Core Engine │ ├────────────────────┤ │ ┌────────────────┐ │ │ │ LiteCam SDK │ │ ← Camera capture │ └────────────────┘ │ │ ┌────────────────┐ │ │ │ ZXing Engine │ │ ← Open-source detection │ └────────────────┘ │ │ ┌────────────────┐ │ │ │ Dynamsoft DBR │ │ ← Enterprise detection │ └────────────────┘ │ └────────────────────┘
Detection Engines Comparison
Feature | ZXing | Dynamsoft DBR |
---|---|---|
Cost | Free (Apache 2.0) | Commercial license |
Accuracy | Good | Excellent |
Speed | Fast | Very Fast |
Damaged Codes | Limited | Advanced |
Multi-detection | Basic | Advanced |
Project Structure
The project is organized into two main components:
├── README.md # Project overview and quick start ├── build-jar.ps1/.sh # Scripts to build LiteCam SDK ├── run-litecam.ps1/.sh # Scripts to test LiteCam SDK ├── litecam.jar # Pre-built Camera SDK with natives ├── include/ # C++ headers for camera implementation ├── src/ # C++ camera implementation (cross-platform) ├── java-src/ # Basic LiteCam Java SDK source │ └── com/example/litecam/ │ ├── LiteCam.java # Main camera API │ └── LiteCamViewer.java # Simple camera viewer test └── maven-example/ # Complete Barcode Scanner Application ├── pom.xml # Maven dependencies and build config ├── build.ps1/.sh # Build scripts for barcode scanner ├── run.ps1/.sh # Run scripts for barcode scanner ├── src/main/java/com/example/litecam/ │ └── BarcodeScanner.java # Main barcode scanning application └── target/ # Maven build output └── litecam-barcode-scanner-1.0.0.jar
Java Camera SDK Development
Step 1: JNI for LiteCam C++ Integration
Create a LiteCamJNI.cpp
file to wrap the LiteCam C++ SDK, enabling access from Java:
#include "Camera.h" #include <jni.h> #include <vector> #include <mutex> #include <string> struct CameraEntry { int id; Camera *cam; }; static std::mutex g_mutex; static std::vector<CameraEntry> g_cameras; static int g_nextId = 1; static Camera *getCamera(int handle) { std::lock_guard<std::mutex> lock(g_mutex); for (auto &e : g_cameras) if (e.id == handle) return e.cam; return nullptr; } static int registerCamera(Camera *c) { std::lock_guard<std::mutex> lock(g_mutex); int id = g_nextId++; g_cameras.push_back({id, c}); return id; } static void unregisterCamera(int handle) { std::lock_guard<std::mutex> lock(g_mutex); for (auto it = g_cameras.begin(); it != g_cameras.end(); ++it) { if (it->id == handle) { delete it->cam; g_cameras.erase(it); return; } } } static jclass findAndGlobalRef(JNIEnv *env, const char *name) { jclass local = env->FindClass(name); return (jclass)env->NewGlobalRef(local); } extern "C" { JNIEXPORT jobjectArray JNICALL Java_com_example_litecam_LiteCam_listDevices(JNIEnv *env, jclass) { auto devices = ListCaptureDevices(); jclass stringClass = env->FindClass("java/lang/String"); jobjectArray arr = env->NewObjectArray((jsize)devices.size(), stringClass, nullptr); for (jsize i = 0; i < (jsize)devices.size(); ++i) { #ifdef _WIN32 char buffer[512]; wcstombs_s(nullptr, buffer, devices[i].friendlyName, sizeof(buffer)); env->SetObjectArrayElement(arr, i, env->NewStringUTF(buffer)); #else env->SetObjectArrayElement(arr, i, env->NewStringUTF(devices[i].friendlyName)); #endif } return arr; } JNIEXPORT jint JNICALL Java_com_example_litecam_LiteCam_open(JNIEnv *env, jobject self, jint deviceIndex) { auto cam = new Camera(); if (!cam->Open(deviceIndex)) { delete cam; return 0; } return registerCamera(cam); } JNIEXPORT void JNICALL Java_com_example_litecam_LiteCam_nativeClose(JNIEnv *, jobject, jint handle) { unregisterCamera(handle); } JNIEXPORT jintArray JNICALL Java_com_example_litecam_LiteCam_listSupportedResolutions(JNIEnv *env, jobject, jint handle) { Camera *cam = getCamera(handle); if (!cam) return nullptr; auto mts = cam->ListSupportedMediaTypes(); // Flatten as width,height pairs sequentially. jintArray arr = env->NewIntArray((jsize)(mts.size() * 2)); std::vector<jint> tmp; tmp.reserve(mts.size() * 2); for (auto &m : mts) { tmp.push_back((jint)m.width); tmp.push_back((jint)m.height); } env->SetIntArrayRegion(arr, 0, (jsize)tmp.size(), tmp.data()); return arr; } JNIEXPORT jboolean JNICALL Java_com_example_litecam_LiteCam_setResolution(JNIEnv *, jobject, jint handle, jint w, jint h) { Camera *cam = getCamera(handle); if (!cam) return JNI_FALSE; return cam->SetResolution(w, h) ? JNI_TRUE : JNI_FALSE; } JNIEXPORT jboolean JNICALL Java_com_example_litecam_LiteCam_captureFrame(JNIEnv *env, jobject, jint handle, jobject byteBuffer) { Camera *cam = getCamera(handle); if (!cam) return JNI_FALSE; FrameData frame = cam->CaptureFrame(); if (!frame.rgbData) return JNI_FALSE; unsigned char *dst = (unsigned char *)env->GetDirectBufferAddress(byteBuffer); if (!dst) { ReleaseFrame(frame); return JNI_FALSE; } size_t expected = (size_t)(frame.width * frame.height * 3); memcpy(dst, frame.rgbData, expected < frame.size ? expected : frame.size); ReleaseFrame(frame); return JNI_TRUE; } JNIEXPORT jint JNICALL Java_com_example_litecam_LiteCam_getFrameWidth(JNIEnv *, jobject, jint handle) { Camera *cam = getCamera(handle); if (!cam) return 0; return (jint)cam->frameWidth; } JNIEXPORT jint JNICALL Java_com_example_litecam_LiteCam_getFrameHeight(JNIEnv *, jobject, jint handle) { Camera *cam = getCamera(handle); if (!cam) return 0; return (jint)cam->frameHeight; } } // extern C
Step 2: CMake for JNI Build
The following CMakeLists.txt
file is used to build the JNI shared library:
cmake_minimum_required(VERSION 3.15) # Project name and version project(CameraProject VERSION 1.0 LANGUAGES CXX) # Set C++ standard set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) # Build type if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() # Define include directories set(INCLUDE_DIR ${CMAKE_SOURCE_DIR}/include) # Platform detection if(WIN32) set(PLATFORM_NAME "windows") elseif(APPLE) set(PLATFORM_NAME "macos") elseif(UNIX) set(PLATFORM_NAME "linux") else() set(PLATFORM_NAME "unknown") endif() # Architecture detection if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(ARCH_NAME "x86_64") else() set(ARCH_NAME "x86") endif() # Compiler-specific settings if(MSVC) # Set runtime library for Windows if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDebug") else() set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded") endif() # Enable parallel compilation add_compile_options(/MP) # Disable specific warnings add_compile_options(/wd4251 /wd4275) # Enable UTF-8 encoding add_compile_options(/utf-8) endif() # Define source files for the Camera library based on platform if (WIN32) set(LIBRARY_SOURCES src/CameraWindows.cpp src/CameraPreviewWindows.cpp ) elseif (UNIX AND NOT APPLE) set(LIBRARY_SOURCES src/CameraLinux.cpp src/CameraPreviewLinux.cpp ) elseif (APPLE) # Support universal binaries on macOS set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64") # Ensure that Objective-C++ source files are compiled as Objective-C++ set(LIBRARY_SOURCES src/CameraMacOS.mm src/CameraPreviewMacOS.mm ) set_source_files_properties(src/CameraMacOS.mm src/CameraPreviewMacOS.mm PROPERTIES COMPILE_FLAGS "-x objective-c++") # Set main.cpp to be treated as Objective-C++ for macOS set_source_files_properties(src/main.cpp PROPERTIES COMPILE_FLAGS "-x objective-c++") endif() # Add JNI wrapper source (common for all platforms) list(APPEND LIBRARY_SOURCES src/LiteCamJNI.cpp ) # Define source files for the executable set(EXECUTABLE_SOURCES src/main.cpp ) # Add the Camera shared library add_library(litecam SHARED ${LIBRARY_SOURCES}) # Set library properties set_target_properties(litecam PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} OUTPUT_NAME "litecam" ) # Platform-specific library naming if(WIN32) set_target_properties(litecam PROPERTIES PREFIX "" SUFFIX ".dll" ) elseif(APPLE) set_target_properties(litecam PROPERTIES PREFIX "lib" SUFFIX ".dylib" ) else() set_target_properties(litecam PROPERTIES PREFIX "lib" SUFFIX ".so" ) endif() # Set include directories for the Camera library target_include_directories(litecam PUBLIC $<BUILD_INTERFACE:${INCLUDE_DIR}> $<INSTALL_INTERFACE:include> ) # Define the CAMERA_EXPORTS macro for the shared library target_compile_definitions(litecam PRIVATE CAMERA_EXPORTS LITECAM_VERSION_MAJOR=${PROJECT_VERSION_MAJOR} LITECAM_VERSION_MINOR=${PROJECT_VERSION_MINOR} LITECAM_VERSION_PATCH=${PROJECT_VERSION_PATCH} ) # Platform-specific dependencies for the Camera library if (UNIX AND NOT APPLE) # Linux dependencies find_package(X11 REQUIRED) find_package(PkgConfig REQUIRED) # Check for Video4Linux2 pkg_check_modules(V4L2 libv4l2) if (X11_FOUND) target_include_directories(litecam PUBLIC ${X11_INCLUDE_DIR}) target_link_libraries(litecam PRIVATE ${X11_LIBRARIES} pthread) endif() if (V4L2_FOUND) target_include_directories(litecam PRIVATE ${V4L2_INCLUDE_DIRS}) target_link_libraries(litecam PRIVATE ${V4L2_LIBRARIES}) else() message(WARNING "Video4Linux2 not found - camera functionality may be limited") endif() elseif (APPLE) # macOS dependencies find_library(COCOA_LIBRARY Cocoa REQUIRED) find_library(AVFOUNDATION_LIBRARY AVFoundation REQUIRED) find_library(COREMEDIA_LIBRARY CoreMedia REQUIRED) find_library(COREVIDEO_LIBRARY CoreVideo REQUIRED) find_library(OBJC_LIBRARY objc REQUIRED) target_link_libraries(litecam PRIVATE ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${COREMEDIA_LIBRARY} ${COREVIDEO_LIBRARY} ${OBJC_LIBRARY} ) elseif (WIN32) # Windows dependencies target_link_libraries(litecam PRIVATE ole32 uuid mfplat mf mfreadwrite mfuuid ) endif() # JNI support - enhanced detection find_package(JNI) if (JNI_FOUND) target_include_directories(litecam PRIVATE ${JNI_INCLUDE_DIRS}) target_compile_definitions(litecam PRIVATE LITECAM_JNI_ENABLED) # Add JNI libraries on some platforms if(WIN32) # Windows doesn't typically need to link JNI libraries elseif(APPLE) # macOS typically has JNI in the framework else() # Linux might need explicit JNI library linking if(JNI_LIBRARIES) target_link_libraries(litecam PRIVATE ${JNI_LIBRARIES}) endif() endif() endif() # Optional: Add position independent code for shared library set_property(TARGET litecam PROPERTY POSITION_INDEPENDENT_CODE ON) # Add the camera_capture executable add_executable(camera_capture ${EXECUTABLE_SOURCES}) # Set executable properties set_target_properties(camera_capture PROPERTIES OUTPUT_NAME "camera_capture" ) # Link the Camera library to the executable target_link_libraries(camera_capture PRIVATE litecam) # Include the shared library's headers in the executable target_include_directories(camera_capture PRIVATE ${INCLUDE_DIR}) # For macOS, link against the frameworks for the executable too if (APPLE) target_link_libraries(camera_capture PRIVATE ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${COREMEDIA_LIBRARY} ${COREVIDEO_LIBRARY} ${OBJC_LIBRARY} ) endif() # Installation rules (optional) install(TARGETS litecam camera_capture EXPORT CameraProjectTargets LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin INCLUDES DESTINATION include ) install(DIRECTORY ${INCLUDE_DIR}/ DESTINATION include FILES_MATCHING PATTERN "*.h" ) # Export targets for find_package support install(EXPORT CameraProjectTargets FILE CameraProjectTargets.cmake NAMESPACE CameraProject:: DESTINATION lib/cmake/CameraProject ) # Generate and install package config files include(CMakePackageConfigHelpers) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CameraProjectConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/CameraProjectConfig.cmake" INSTALL_DESTINATION lib/cmake/CameraProject ) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/CameraProjectConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion )
Step 3: The Java Camera Class with JNI
package com.example.litecam; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; public class LiteCam implements AutoCloseable { static { boolean loaded = false; try { loaded = loadBundled(); } catch (Throwable t) { } if (!loaded) { System.loadLibrary("litecam"); } } private static boolean loadBundled() throws Exception { String os = System.getProperty("os.name").toLowerCase(); String arch = System.getProperty("os.arch").toLowerCase(); String osToken; if (os.contains("win")) osToken = "windows"; else if (os.contains("mac") || os.contains("darwin")) osToken = "macos"; else if (os.contains("nux") || os.contains("linux")) osToken = "linux"; else return false; String archToken; if (arch.contains("aarch64") || arch.contains("arm64")) archToken = "arm64"; else if (arch.contains("64")) archToken = "x86_64"; else archToken = arch; // fallback String libBase = "litecam"; String ext = osToken.equals("windows") ? ".dll" : (osToken.equals("macos") ? ".dylib" : ".so"); String resourcePath = "/natives/" + osToken + "-" + archToken + "/" + (osToken.equals("windows") ? libBase + ext : "lib" + libBase + ext); try (java.io.InputStream in = LiteCam.class.getResourceAsStream(resourcePath)) { if (in == null) return false; java.nio.file.Path tempFile = java.nio.file.Files.createTempFile(libBase + "-", ext); try (java.io.OutputStream out = java.nio.file.Files.newOutputStream(tempFile)) { byte[] buf = new byte[8192]; int r; while ((r = in.read(buf)) != -1) out.write(buf, 0, r); } tempFile.toFile().deleteOnExit(); System.load(tempFile.toAbsolutePath().toString()); return true; } } private int handle = 0; // Native methods public static native String[] listDevices(); private native int open(int deviceIndex); private native void nativeClose(int handle); public native int[] listSupportedResolutions(int handle); public native boolean setResolution(int handle, int width, int height); public native boolean captureFrame(int handle, ByteBuffer rgbOut); public native int getFrameWidth(int handle); public native int getFrameHeight(int handle); public void openDevice(int index) { if (handle != 0) throw new IllegalStateException("Already opened"); handle = open(index); if (handle == 0) throw new RuntimeException("Failed to open camera index " + index); } public void closeDevice() { if (handle != 0) { nativeClose(handle); handle = 0; } } @Override public void close() { closeDevice(); } public List<int[]> getSupportedResolutions() { int[] flat = listSupportedResolutions(handle); List<int[]> list = new ArrayList<>(); if (flat != null) { for (int i=0;i+1<flat.length;i+=2) { list.add(new int[]{flat[i], flat[i+1]}); } } return list; } public boolean setResolution(int w, int h) { return setResolution(handle, w, h); } public int getWidth() { return getFrameWidth(handle); } public int getHeight() { return getFrameHeight(handle); } public boolean grabFrame(ByteBuffer dst) { return captureFrame(handle, dst); } public boolean isOpen() { return handle != 0; } }
Step 4: Build JNI Shared Library and JAR Package
-
Build native library with CMake:
mkdir build cd build cmake .. -DCMAKE_BUILD_TYPE=Release cmake --build . --config Release
-
Compile Java sources:
cd .. javac -d build -h include java-src/com/example/litecam/*.java
-
Create JAR with native library:
jar cf litecam.jar -C build com jar uf litecam.jar build/litecam.dll # or .dylib on macOS, .so on Linux
Java Barcode Scanner Development
The following code snippet demonstrates the basic usage of LiteCam, ZXing, and Dynamsoft Barcode Reader APIs.
LiteCam
LiteCam cam = new LiteCam(); String[] devices = LiteCam.listDevices(); for (int i = 0; i < devices.length; i++) { System.out.println(i + ": " + devices[i]); } cam.openDevice(0); cam.setResolution(640, 480); ByteBuffer buffer = ByteBuffer.allocateDirect(640 * 480 * 3); if (cam.grabFrame(buffer)) { byte[] frameData = new byte[buffer.remaining()]; buffer.get(frameData); } cam.close();
ZXing
import com.google.zxing.*; import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.common.HybridBinarizer; import com.google.zxing.multi.GenericMultipleBarcodeReader; public class ZXingDetector { private MultiFormatReader reader; private GenericMultipleBarcodeReader multiReader; public void initialize() { reader = new MultiFormatReader(); multiReader = new GenericMultipleBarcodeReader(reader); } public List<Result> detectBarcodes(BufferedImage image) { List<Result> results = new ArrayList<>(); try { LuminanceSource source = new BufferedImageLuminanceSource(image); BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); try { Result[] multiResults = multiReader.decodeMultiple(bitmap); results.addAll(Arrays.asList(multiResults)); } catch (NotFoundException e) { try { Result singleResult = reader.decode(bitmap); results.add(singleResult); } catch (NotFoundException ignored) { } } } catch (Exception e) { logger.debug("ZXing detection failed: {}", e.getMessage()); } return results; } }
Dynamsoft Barcode Reader
import com.dynamsoft.dbr.*; import com.dynamsoft.core.basic_structures.ImageData; public class DynamsoftDetector { private CaptureVisionRouter cvRouter; public void initialize() throws Exception { LicenseManager.initLicense("LICENSE-KEY"); cvRouter = new CaptureVisionRouter(); } public List<BarcodeResultItem> detectBarcodes(BufferedImage image) { List<BarcodeResultItem> results = new ArrayList<>(); try { ImageData imageData = createImageData(image); CapturedResult result = cvRouter.capture(imageData, EnumPresetTemplate.PT_READ_BARCODES); DecodedBarcodesResult barcodeResult = result.getDecodedBarcodesResult(); if (barcodeResult != null) { BarcodeResultItem[] items = barcodeResult.getItems(); if (items != null) { results.addAll(Arrays.asList(items)); } } } catch (Exception e) { logger.error("Dynamsoft detection failed: {}", e.getMessage()); } return results; } private ImageData createImageData(BufferedImage image) { } }
Source Code
https://github.com/yushulx/java-jni-barcode-qrcode-reader/tree/main/examples/barcode-scanner
Top comments (0)