Android Barcode Scanner using USB Camera
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.
Here is a video of the result:
Creating an Android Barcode Scanner using USB Camera
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.
Show USB Camera Preview
The UVCCamera library is used to have access to USB cameras.
Add 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 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 Events
The library has a wrapper around the USB Host API which we have talked about in the previous post. 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 USB Camera
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
OnClickListener
for 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.CameraDialogParent
You 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);
Read Barcodes from USB Camera Preview
Now that we can show camera preview, we can use Dynamsoft Barcode Reader to read barcodes.
Add 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 Dynamsoft Barcode Reader
Initialize Dynamsoft Barcode Reader with a license. You can apply for a 30-day free trial license here.
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);
}
Create a Timer to decode frames
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();
}
}
Compatibility issue
On Android 9+, the library may not work if the targetSdkVersion
is set larger than 27. You can check out this post to learn more.
Source Code
https://github.com/tony-xlh/Android-USB-Camera-Barcode-Scanner