How to Connect a USB Camera to Android for Barcode Scanning
Although almost every Android phone is equipped with cameras, some Android devices, like TV boxes and kiosks, may not have cameras built-in. An easy way to add extra camera functionality to these devices is by connecting with a USB camera if they support USB-OTG.
Starting from Android 9, the Android platform supports the use of plug-and-play USB cameras (that is, webcams) using the standard Android Camera2 API and the camera HIDL interface.1 But not all Android 9+ devices have this function enabled2 and old Android devices cannot use it.
There are other approaches. One is by using the video for the Linux kernel module, like this one, but it may have permission problems. Another is by using the USB host API, like this one, which does not require root access.
In this article, we are going to use the latter approach to create an Android barcode scanner using a USB camera. Dynamsoft Barcode Reader is used as the barcode reading SDK.
What you’ll build: An Android app that connects to an external USB camera via USB-OTG and scans barcodes in real time using the UVCCamera library and Dynamsoft Barcode Reader SDK.
Key Takeaways
- Android devices without built-in cameras (TV boxes, kiosks) can scan barcodes by connecting a USB webcam through USB-OTG.
- The UVCCamera library provides root-free USB camera access on Android via the USB Host API, bypassing Camera2 API limitations.
- Dynamsoft Barcode Reader’s
CaptureVisionRouter.capture()method decodes barcodes from bitmap frames captured from the USB camera preview. - On Android 9+, the UVCCamera library requires
targetSdkVersionset to 27 or lower to function correctly.
Common Developer Questions
- How do I connect an external USB camera to an Android device for barcode scanning?
- Why does UVCCamera not work on Android 9+ with targetSdkVersion above 27?
- How do I read barcodes from a USB webcam preview on Android using Dynamsoft Barcode Reader?
Here is a video of the result:

Step 1: Set Up the Android Project and USB Camera Access
Prerequisites
- Android Studio installed
- An Android device with USB-OTG support
- A USB webcam (UVC-compatible)
- Get a 30-day free trial license for Dynamsoft Barcode Reader
Let’s create a new Android project using Android Studio and add functions like access to USB cameras and barcode scanning.
Tips:
Since we need to use USB-OTG to connect the Android device to a USB camera, we may not be able to debug it using a USB cable. We can configure ADB to debug wirelessly.
Connect the device to the computer first and run the following commands:
adb -d shell setprop service.adb.tcp.port 4444
adb -d tcpip 4444
adb connect 192.168.31.215:4444
After the connection is done, we can unplug the device and debug wirelessly.
Display the USB Camera Preview
The UVCCamera library is used to have access to USB cameras.
Add UVCCamera Dependencies
We need to compile the library with NDK by ourselves using the original repo. Here, for convenience, we can use precompiled AARs from this repo.
Download the AARs and put them in the app/libs folder.
In the project’s build.gradle, add the following lines:
allprojects {
repositories {
maven { url 'https://raw.github.com/saki4510t/libcommon/master/repository/' }
flatDir {
dirs 'libs'
}
}
}
In the app’s build.gradle, add the following lines:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar','*.jar'])
implementation("com.serenegiant:common:1.5.20") {
exclude module: 'support-v4'
}
}
Add Camera Preview Controls
Add a UVCCameraTextureView for camera preview and an ImageButton to select and open cameras.
XML:
<com.serenegiant.widget.UVCCameraTextureView
android:id="@+id/camera_view"
android:layout_width="407dp"
android:layout_height="720dp"
android:layout_centerInParent="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/imageButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginBottom="16dp"
android:background="@android:color/transparent"
android:scaleType="fitCenter"
android:src="@android:drawable/ic_menu_camera"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
In MainActivity:
private CameraViewInterface mUVCCameraView;
private ImageButton mCameraButton;
@Override
protected void onCreate(final Bundle savedInstanceState) {
//......
mCameraButton = findViewById(R.id.imageButton);
final View view = findViewById(R.id.camera_view);
mUVCCameraView = (CameraViewInterface)view;
}
Monitor USB Device Events
The library has a wrapper around the USB Host API. We are going to use it to monitor USB events.
-
Initialize a USBMonitor instance.
private USBMonitor mUSBMonitor; @Override protected void onCreate(Bundle savedInstanceState) { //...... mUSBMonitor = new USBMonitor(this, mOnDeviceConnectListener); } private final USBMonitor.OnDeviceConnectListener mOnDeviceConnectListener = new USBMonitor.OnDeviceConnectListener() { @Override public void onAttach(final UsbDevice device) { Toast.makeText(MainActivity.this, "USB_DEVICE_ATTACHED", Toast.LENGTH_SHORT).show(); } @Override public void onConnect(final UsbDevice device, final USBMonitor.UsbControlBlock ctrlBlock, final boolean createNew) { } @Override public void onDisconnect(final UsbDevice device, final USBMonitor.UsbControlBlock ctrlBlock) { } @Override public void onDettach(final UsbDevice device) { Toast.makeText(MainActivity.this, "USB_DEVICE_DETACHED", Toast.LENGTH_SHORT).show(); } @Override public void onCancel(final UsbDevice device) { } }; -
Manage the life cycle.
@Override protected void onStart() { super.onStart(); mUSBMonitor.register(); } @Override protected void onStop() { mUSBMonitor.unregister(); super.onStop(); } @Override public void onDestroy() { if (mUSBMonitor != null) { mUSBMonitor.destroy(); mUSBMonitor = null; } super.onDestroy(); }
Open the USB Camera and Start Preview
The UVCCameraHandler class is used to handle USB cameras.
-
Initialize the handler.
/** * set true if you want to record movie using MediaSurfaceEncoder * (writing frame data into Surface camera from MediaCodec * by almost same way as USBCameratest2) * set false if you want to record movie using MediaVideoEncoder */ private static final boolean USE_SURFACE_ENCODER = false; /** * preview resolution(width) * if your camera does not support specific resolution and mode, * {@link UVCCamera#setPreviewSize(int, int, int)} throw exception */ private static final int PREVIEW_WIDTH = 640; // 640 /** * preview resolution(height) * if your camera does not support specific resolution and mode, * {@link UVCCamera#setPreviewSize(int, int, int)} throw exception */ private static final int PREVIEW_HEIGHT = 480; //480 /** * preview mode * if your camera does not support specific resolution and mode, * {@link UVCCamera#setPreviewSize(int, int, int)} throw exception * 0:YUYV, other:MJPEG */ private static final int PREVIEW_MODE = 0; // YUV private UVCCameraHandler mCameraHandler; @Override protected void onCreate(Bundle savedInstanceState) { //...... mCameraHandler = UVCCameraHandler.createHandler(this, mUVCCameraView, USE_SURFACE_ENCODER ? 0 : 1, PREVIEW_WIDTH, PREVIEW_HEIGHT, PREVIEW_MODE); } -
Add an
OnClickListenerfor the image button to start a camera selection dialog. The preview will be started after a camera is chosen.protected void onCreate(Bundle savedInstanceState) { //...... mCameraButton.setOnClickListener(mOnClickListener); //...... } private final View.OnClickListener mOnClickListener = new View.OnClickListener() { @Override public void onClick(final View view) { if ((mCameraHandler != null) && !mCameraHandler.isOpened()) { CameraDialog.showDialog(MainActivity.this); } else { mCameraHandler.close(); } } }; private final USBMonitor.OnDeviceConnectListener mOnDeviceConnectListener = new USBMonitor.OnDeviceConnectListener() { @Override public void onConnect(final UsbDevice device, final USBMonitor.UsbControlBlock ctrlBlock, final boolean createNew) { if (mCameraHandler != null) { mCameraHandler.open(ctrlBlock); startPreview(); } } } private void startPreview() { if (mCameraHandler != null) { final SurfaceTexture st = mUVCCameraView.getSurfaceTexture(); mCameraHandler.startPreview(new Surface(st)); } }The activity has to be modified as well to extend the library’s
BaseActivity:public class MainActivity extends BaseActivity implements CameraDialog.CameraDialogParentYou may find that the preview is distorted to fill the screen. You can keep its aspect ratio using the following code:
mUVCCameraView.setAspectRatio(PREVIEW_WIDTH / (double)PREVIEW_HEIGHT);
Step 2: Read Barcodes from USB Camera Frames
Now that we can show camera preview, we can use Dynamsoft Barcode Reader to read barcodes.
Add Dynamsoft Barcode Reader Dependencies
In the project’s build.gradle, add the following lines:
allprojects {
repositories {
maven {
url "https://download2.dynamsoft.com/maven/aar"
}
}
}
In the app’s build.gradle, add the following lines:
dependencies {
implementation "com.dynamsoft:dynamsoftlicense:3.4.40"
implementation "com.dynamsoft:dynamsoftcore:3.4.30"
implementation "com.dynamsoft:dynamsoftcapturevisionrouter:2.4.30"
implementation "com.dynamsoft:dynamsoftbarcodereader:10.4.30"
implementation "com.dynamsoft:dynamsoftimageprocessing:2.4.31"
}
Initialize the Barcode Reader
Initialize Dynamsoft Barcode Reader with a license.
private CaptureVisionRouter mRouter; //used to call Dynamsoft Barcode Reader
protected void onCreate(Bundle savedInstanceState) {
//......
if (savedInstanceState == null) {
LicenseManager.initLicense("LICENSE-KEY", this, (isSuccess, error) -> {
if (!isSuccess) {
error.printStackTrace();
}
});
}
mRouter = new CaptureVisionRouter(this);
}
Decode Barcode Frames with a Timer
When a camera is connected, start decoding using a timer.
private Timer timer = null;
private void startDecoding(){
TimerTask task = new TimerTask() {
@Override
public void run() {
Bitmap bmp = mUVCCameraView.captureStillImage();
decode(bmp);
}
};
timer = new Timer();
timer.scheduleAtFixedRate(task, 1000, 100);
}
private void stopDecoding(){
if (timer != null){
timer.cancel();
timer = null;
}
}
private final USBMonitor.OnDeviceConnectListener mOnDeviceConnectListener = new USBMonitor.OnDeviceConnectListener() {
//......
@Override
public void onConnect(final UsbDevice device, final USBMonitor.UsbControlBlock ctrlBlock, final boolean createNew) {
if (mCameraHandler != null) {
mCameraHandler.open(ctrlBlock);
startPreview();
startDecoding(); //newly added
}
}
@Override
public void onDisconnect(final UsbDevice device, final USBMonitor.UsbControlBlock ctrlBlock) {
stopDecoding(); //newly added
}
}
The preview is captured as bitmap for Dynamsoft Barcode Reader to decode. The result will be displayed in a TextView.
private void decode(final Bitmap bitmap){
try {
CapturedResult result = mRouter.capture(bitmap, EnumPresetTemplate.PT_READ_BARCODES);
DecodedBarcodesResult barcodeResult = result.getDecodedBarcodesResult();
runOnUiThread(
new Runnable() {
@Override
public void run() {
if (barcodeResult != null) {
BarcodeResultItem[] results = barcodeResult.getItems();
StringBuilder sb = new StringBuilder();
sb.append("Found ");
sb.append(results.length);
sb.append(" barcode(s):");
sb.append("\n");
for (BarcodeResultItem result : results){
sb.append(result.getText());
sb.append("\n");
}
resultTextView.setText(sb.toString());
}
}
}
);
} catch (Exception e){
e.printStackTrace();
}
}
Common Issues and Edge Cases
- UVCCamera library fails on Android 9+ with
targetSdkVersion> 27. The library relies on APIs that changed in Android 9. SettargetSdkVersionto 27 or lower. See this post for details. - USB camera not detected after plugging in. Ensure the Android device supports USB-OTG. Some devices require an OTG adapter or cable. Also verify the webcam is UVC-compatible.
- Preview appears distorted or stretched. Call
mUVCCameraView.setAspectRatio(PREVIEW_WIDTH / (double)PREVIEW_HEIGHT)to maintain the correct aspect ratio. - Barcode decoding is slow or misses frames. The timer interval (100 ms) may be too aggressive for lower-end devices. Increase the interval or skip frames when the previous decode has not yet completed.
Source Code
https://github.com/tony-xlh/Android-USB-Camera-Barcode-Scanner