Commit f43bd978 authored by 姜天宇's avatar 姜天宇

feat(v1.0.2): 1.android相机改为UvcCamera

parent f8cd3366
......@@ -29,7 +29,7 @@ android {
buildTypes {
debug {
buildConfigField 'String', 'POS_URL', '"https://se.wmdigit.com/"'
buildConfigField 'String', 'POS_URL', '"https://se-test.frp.wmdigit.com/"'
resValue "string", "app_name", "餐饮服务-debug"
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
......
......@@ -31,9 +31,10 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
implementation project(path: ":common")
implementation project(path: ":data-local")
// CameraX core library using the camera2 implementation
def camerax_version = "1.2.0-beta02"
def camerax_version = "1.3.0-beta01"
// The following line is optional, as the core library is included indirectly by camera-camera2
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
......@@ -44,7 +45,7 @@ dependencies {
// If you want to additionally use the CameraX View class
implementation "androidx.camera:camera-view:${camerax_version}"
// If you want to additionally add CameraX ML Kit Vision Integration
implementation "androidx.camera:camera-mlkit-vision:${camerax_version}"
// implementation "androidx.camera:camera-mlkit-vision:${camerax_version}"
// If you want to additionally use the CameraX Extensions library
implementation "androidx.camera:camera-extensions:${camerax_version}"
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
package com.jiangdg.utils;
/*
* libcommon
* utility/helper classes for myself
*
* Copyright (c) 2014-2018 saki t_saki@serenegiant.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class HandlerThreadHandler extends Handler {
private static final String TAG = "HandlerThreadHandler";
public static final HandlerThreadHandler createHandler() {
return createHandler(TAG);
}
public static final HandlerThreadHandler createHandler(final String name) {
final HandlerThread thread = new HandlerThread(name);
thread.start();
return new HandlerThreadHandler(thread.getLooper());
}
public static final HandlerThreadHandler createHandler(@Nullable final Callback callback) {
return createHandler(TAG, callback);
}
public static final HandlerThreadHandler createHandler(final String name, @Nullable final Callback callback) {
final HandlerThread thread = new HandlerThread(name);
thread.start();
return new HandlerThreadHandler(thread.getLooper(), callback);
}
private HandlerThreadHandler(@NonNull final Looper looper) {
super(looper);
}
private HandlerThreadHandler(@NonNull final Looper looper, @Nullable final Callback callback) {
super(looper, callback);
}
}
/*
* UVCCamera
* library and sample to access to UVC web camera on non-rooted Android device
*
* Copyright (c) 2014-2017 saki t_saki@serenegiant.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* All files in the folder are under this Apache License, Version 2.0.
* Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder
* may have a different license, see the respective files.
*/
package com.jiangdg.utils;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Locale;
public class Size implements Parcelable {
//
/**
* native側のuvc_raw_format_tの値, こっちは主にlibuvc用
* 9999 is still image
*/
public int type;
/**
* native側のraw_frame_tの値, androusb用,
* libuvcは対応していない
*/
public int frame_type;
public int index;
public int width;
public int height;
public int frameIntervalType;
public int frameIntervalIndex;
public int[] intervals;
// ここ以下はframeIntervalTypeとintervalsから#updateFrameRateで計算する
public float[] fps;
private String frameRates;
/**
* コンストラクタ
* @param _type native側のraw_format_tの値, ただし9999は静止画
* @param _frame_type native側のraw_frame_tの値
* @param _index
* @param _width
* @param _height
*/
public Size(final int _type, final int _frame_type, final int _index, final int _width, final int _height) {
type = _type;
frame_type = _frame_type;
index = _index;
width = _width;
height = _height;
frameIntervalType = -1;
frameIntervalIndex = 0;
intervals = null;
updateFrameRate();
}
/**
* コンストラクタ
* @param _type native側のraw_format_tの値, ただし9999は静止画
* @param _frame_type native側のraw_frame_tの値
* @param _index
* @param _width
* @param _height
* @param _min_intervals
* @param _max_intervals
*/
public Size(final int _type, final int _frame_type, final int _index, final int _width, final int _height, final int _min_intervals, final int _max_intervals, final int _step) {
type = _type;
frame_type = _frame_type;
index = _index;
width = _width;
height = _height;
frameIntervalType = 0;
frameIntervalIndex = 0;
intervals = new int[3];
intervals[0] = _min_intervals;
intervals[1] = _max_intervals;
intervals[2] = _step;
updateFrameRate();
}
/**
* コンストラクタ
* @param _type native側のraw_format_tの値, ただし9999は静止画
* @param _frame_type native側のraw_frame_tの値
* @param _index
* @param _width
* @param _height
* @param _intervals
*/
public Size(final int _type, final int _frame_type, final int _index, final int _width, final int _height, final int[] _intervals) {
type = _type;
frame_type = _frame_type;
index = _index;
width = _width;
height = _height;
final int n = _intervals != null ? _intervals.length : -1;
if (n > 0) {
frameIntervalType = n;
intervals = new int[n];
System.arraycopy(_intervals, 0, intervals, 0, n);
} else {
frameIntervalType = -1;
intervals = null;
}
frameIntervalIndex = 0;
updateFrameRate();
}
/**
* コピーコンストラクタ
* @param other
*/
public Size(final Size other) {
type = other.type;
frame_type = other.frame_type;
index = other.index;
width = other.width;
height = other.height;
frameIntervalType = other.frameIntervalType;
frameIntervalIndex = other.frameIntervalIndex;
final int n = other.intervals != null ? other.intervals.length : -1;
if (n > 0) {
intervals = new int[n];
System.arraycopy(other.intervals, 0, intervals, 0, n);
} else {
intervals = null;
}
updateFrameRate();
}
private Size(final Parcel source) {
// 読み取り順はwriteToParcelでの書き込み順と同じでないとダメ
type = source.readInt();
frame_type = source.readInt();
index = source.readInt();
width = source.readInt();
height = source.readInt();
frameIntervalType = source.readInt();
frameIntervalIndex = source.readInt();
if (frameIntervalType >= 0) {
if (frameIntervalType > 0) {
intervals = new int[frameIntervalType];
} else {
intervals = new int[3];
}
source.readIntArray(intervals);
} else {
intervals = null;
}
updateFrameRate();
}
public Size set(final Size other) {
if (other != null) {
type = other.type;
frame_type = other.frame_type;
index = other.index;
width = other.width;
height = other.height;
frameIntervalType = other.frameIntervalType;
frameIntervalIndex = other.frameIntervalIndex;
final int n = other.intervals != null ? other.intervals.length : -1;
if (n > 0) {
intervals = new int[n];
System.arraycopy(other.intervals, 0, intervals, 0, n);
} else {
intervals = null;
}
updateFrameRate();
}
return this;
}
public float getCurrentFrameRate() throws IllegalStateException {
final int n = fps != null ? fps.length : 0;
if ((frameIntervalIndex >= 0) && (frameIntervalIndex < n)) {
return fps[frameIntervalIndex];
}
throw new IllegalStateException("unknown frame rate or not ready");
}
public void setCurrentFrameRate(final float frameRate) {
// 一番近いのを選ぶ
int index = -1;
final int n = fps != null ? fps.length : 0;
for (int i = 0; i < n; i++) {
if (fps[i] <= frameRate) {
index = i;
break;
}
}
frameIntervalIndex = index;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeInt(type);
dest.writeInt(frame_type);
dest.writeInt(index);
dest.writeInt(width);
dest.writeInt(height);
dest.writeInt(frameIntervalType);
dest.writeInt(frameIntervalIndex);
if (intervals != null) {
dest.writeIntArray(intervals);
}
}
public void updateFrameRate() {
final int n = frameIntervalType;
if (n > 0) {
fps = new float[n];
for (int i = 0; i < n; i++) {
final float _fps = fps[i] = 10000000.0f / intervals[i];
}
} else if (n == 0) {
try {
final int min = Math.min(intervals[0], intervals[1]);
final int max = Math.max(intervals[0], intervals[1]);
final int step = intervals[2];
if (step > 0) {
int m = 0;
for (int i = min; i <= max; i+= step) { m++; }
fps = new float[m];
m = 0;
for (int i = min; i <= max; i+= step) {
final float _fps = fps[m++] = 10000000.0f / i;
}
} else {
final float max_fps = 10000000.0f / min;
int m = 0;
for (float fps = 10000000.0f / min; fps <= max_fps; fps += 1.0f) { m++; }
fps = new float[m];
m = 0;
for (float fps = 10000000.0f / min; fps <= max_fps; fps += 1.0f) {
this.fps[m++] = fps;
}
}
} catch (final Exception e) {
// ignore, なんでかminとmaxが0になってるんちゃうかな
fps = null;
}
}
final int m = fps != null ? fps.length : 0;
final StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < m; i++) {
sb.append(String.format(Locale.US, "%4.1f", fps[i]));
if (i < m-1) {
sb.append(",");
}
}
sb.append("]");
frameRates = sb.toString();
if (frameIntervalIndex > m) {
frameIntervalIndex = 0;
}
}
@Override
public String toString() {
float frame_rate = 0.0f;
try {
frame_rate = getCurrentFrameRate();
} catch (final Exception e) {
}
return String.format(Locale.US, "Size(%dx%d@%4.1f,type:%d,frame:%d,index:%d,%s)", width, height, frame_rate, type, frame_type, index, frameRates);
}
public static final Creator<Size> CREATOR = new Creator<Size>() {
@Override
public Size createFromParcel(final Parcel source) {
return new Size(source);
}
@Override
public Size[] newArray(final int size) {
return new Size[size];
}
};
}
package com.jiangdg.utils;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import com.elvishew.xlog.LogConfiguration;
import com.elvishew.xlog.LogLevel;
import com.elvishew.xlog.XLog;
import com.elvishew.xlog.flattener.PatternFlattener;
import com.elvishew.xlog.printer.AndroidPrinter;
import com.elvishew.xlog.printer.file.FilePrinter;
import com.elvishew.xlog.printer.file.naming.FileNameGenerator;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
/**
* X Log wrapper
*
* @author Created by jiangdg on 2022/7/19
*/
public class XLogWrapper {
private static final String TAG = "AUSBC";
private static final String FLATTERER = "{d yyyy-MM-dd HH:mm:ss.SSS} {l}/{t}: {m}";
private static boolean mHasInit;
public static void init(Application application, String folderPath) {
AndroidPrinter androidPrinter = new AndroidPrinter(1);
LogConfiguration config = new LogConfiguration.Builder().logLevel(LogLevel.ALL)
.tag(TAG)
// .disableStackTrace()
.build();
String path = TextUtils.isEmpty(folderPath) ? application.getExternalFilesDir(null).getPath() : folderPath;
path = TextUtils.isEmpty(path) ? application.getFilesDir().getPath(): path;
FilePrinter filePrinter = new FilePrinter.Builder(path)
.fileNameGenerator(new MyFileNameGenerator(application))
.flattener(new MyFlatterer(FLATTERER))
.build();
XLog.init(config, androidPrinter, filePrinter);
mHasInit = true;
}
public static void v(String tag, String msg) {
if (mHasInit) {
XLog.v( "[" + tag + "] " +msg);
return;
}
Log.v(tag, "" +msg);
}
public static void i(String tag, String msg) {
if (mHasInit) {
XLog.i( "[" + tag + "] " +msg);
return;
}
Log.i(tag, "" +msg);
}
public static void d(String tag, String msg) {
if (mHasInit) {
XLog.d( "[" + tag + "] " +msg);
return;
}
Log.d(tag, "" +msg);
}
public static void w(String tag, String msg) {
if (mHasInit) {
XLog.w( "[" + tag + "] " +msg);
return;
}
Log.w(tag, "" +msg);
}
public static void w(String tag, String msg, Throwable throwable) {
if (mHasInit) {
XLog.w( "[" + tag + "] " +msg, throwable);
return;
}
Log.w(tag, msg, throwable);
}
public static void w(String tag, Throwable throwable) {
if (mHasInit) {
XLog.w("[" + tag, throwable);
return;
}
Log.w(tag,"" , throwable);
}
public static void e(String tag, String msg) {
if (mHasInit) {
XLog.e("[" + tag + "] " +msg);
return;
}
Log.e(tag, "" +msg);
}
public static void e(String tag, String msg, Throwable throwable) {
if (mHasInit) {
XLog.e("[" + tag + "] " +msg, throwable);
return;
}
Log.e(tag, "" +msg, throwable);
}
static class MyFileNameGenerator implements FileNameGenerator {
private final Context mCtx;
public MyFileNameGenerator(Context context) {
this.mCtx = context;
}
private final ThreadLocal<SimpleDateFormat> mLocalDateFormat = new ThreadLocal<SimpleDateFormat>() {
@NonNull
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd", Locale.US);
}
};
@Override
public boolean isFileNameChangeable() {
return true;
}
@Override
public String generateFileName(int logLevel, long timestamp) {
SimpleDateFormat sdf = mLocalDateFormat.get();
sdf.setTimeZone(TimeZone.getDefault());
String dateStr = sdf.format(new Date(timestamp));
return "AUSBC_v" + getVerName() + "_" + dateStr + ".log";
}
private String getVerName() {
String verName = "";
try {
verName = mCtx.getPackageManager().
getPackageInfo(mCtx.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return verName;
}
}
static class MyFlatterer extends PatternFlattener {
public MyFlatterer(String pattern) {
super(pattern);
}
}
}
package com.jiangdg.uvc;
public interface IButtonCallback {
void onButton(int button, int state);
}
/*
* UVCCamera
* library and sample to access to UVC web camera on non-rooted Android device
*
* Copyright (c) 2014-2017 saki t_saki@serenegiant.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* All files in the folder are under this Apache License, Version 2.0.
* Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder
* may have a different license, see the respective files.
*/
package com.jiangdg.uvc;
import java.nio.ByteBuffer;
/**
* Callback interface for UVCCamera class
* If you need frame data as ByteBuffer, you can use this callback interface with UVCCamera#setFrameCallback
*/
public interface IFrameCallback {
/**
* This method is called from native library via JNI on the same thread as UVCCamera#startCapture.
* You can use both UVCCamera#startCapture and #setFrameCallback
* but it is better to use either for better performance.
* You can also pass pixel format type to UVCCamera#setFrameCallback for this method.
* Some frames may drops if this method takes a time.
* When you use some color format like NV21, this library never execute color space conversion,
* just execute pixel format conversion. If you want to get same result as on screen, please try to
* consider to get images via texture(SurfaceTexture) and read pixel buffer from it using OpenGL|ES2/3
* instead of using IFrameCallback(this way is much efficient in most case than using IFrameCallback).
* @param frame this is direct ByteBuffer from JNI layer and you should handle it's byte order and limitation.
*/
public void onFrame(ByteBuffer frame);
}
package com.jiangdg.uvc;
import java.nio.ByteBuffer;
public interface IStatusCallback {
void onStatus(int statusClass, int event, int selector, int statusAttribute, ByteBuffer data);
}
This diff is collapsed.
This diff is collapsed.
......@@ -3,30 +3,25 @@ package com.wmdigit.camera;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CaptureRequest;
import android.opengl.GLES11Ext;
import android.util.Range;
import android.hardware.usb.UsbDevice;
import android.util.Size;
import androidx.camera.camera2.interop.Camera2Interop;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.CameraProvider;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.CameraState;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.Preview;
import androidx.camera.core.UseCaseGroup;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;
import com.elvishew.xlog.XLog;
import com.google.common.util.concurrent.ListenableFuture;
import com.jiangdg.usb.USBMonitor;
import com.wmdigit.camera.listener.OnImageAnalyzeListener;
import com.wmdigit.common.utils.YuvToRgbConverter;
import com.wmdigit.common.model.SimpleUsbDevice;
import com.wmdigit.data.mmkv.repository.CameraLocalRepository;
import java.util.List;
import java.util.concurrent.ExecutionException;
......@@ -42,10 +37,6 @@ public class CameraxController {
* 摄像头预览分辨率
*/
private final Size CAMERA_PREVIEW_SIZE = new Size(640, 480);
/**
* 帧处理的时间间隔
*/
private final long INTERVAL_FRAMES_ANALYZE = 100L;
private static CameraxController instance;
private Context context;
/**
......@@ -64,10 +55,6 @@ public class CameraxController {
* 图片分析线程
*/
private final ExecutorService executors = Executors.newSingleThreadExecutor();
/**
* YUV转RGB转化器
*/
private YuvToRgbConverter yuvToRgbConverter;
/**
* 图片分析回调
*/
......@@ -81,6 +68,69 @@ public class CameraxController {
*/
private CameraState cameraState = null;
private final Object mSyncUsb = new Object();
private USBMonitor mUSBMonitor;
private boolean isAttached = false;
private final USBMonitor.OnDeviceConnectListener mOnDeviceConnectListener = new USBMonitor.OnDeviceConnectListener() {
@Override
public void onAttach(UsbDevice device) {
synchronized (mSyncUsb) {
XLog.i("检测到USB连接, getDeviceName" + device.getDeviceName()
+ "getProductId" + device.getProductId()
+ "getVendorId" + device.getVendorId()
+ "getDeviceClass" + device.getDeviceClass()
+ "getProductName" + device.getProductName());
if (mUSBMonitor != null) {
if (isAttached || isOpen()){
return;
}
if (device.getDeviceClass() != 239) {
return;
}
SimpleUsbDevice simpleUsbDevice = CameraLocalRepository.getInstance().getCamera();
if (simpleUsbDevice.getPid() == device.getProductId() && simpleUsbDevice.getVid() == device.getVendorId()) {
isAttached = true;
XLog.d("准备绑定相机");
bindCamera();
}
}
}
}
@Override
public void onDetach(UsbDevice device) {
synchronized (mSyncUsb) {
XLog.i("检测到USB断开连接, getDeviceName" + device.getDeviceName()
+ "getProductId" + device.getProductId()
+ "getVendorId" + device.getVendorId()
+ "getDeviceClass" + device.getDeviceClass()
+ "getProductName" + device.getProductName());
SimpleUsbDevice simpleUsbDevice = CameraLocalRepository.getInstance().getCamera();
if (simpleUsbDevice.getPid() == device.getProductId() && simpleUsbDevice.getVid() == device.getVendorId()) {
isAttached = false;
}
}
}
@Override
public void onConnect(UsbDevice device, USBMonitor.UsbControlBlock ctrlBlock, boolean createNew) {
}
@Override
public void onDisconnect(UsbDevice device, USBMonitor.UsbControlBlock ctrlBlock) {
}
@Override
public void onCancel(UsbDevice device) {
}
};
// private SurfaceTexture mColorSurfaceTexture = new SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
public static CameraxController getInstance(Context context) {
......@@ -101,7 +151,8 @@ public class CameraxController {
public CameraxController(Context context) {
this.context = context;
this.cameraProviderFutureListener = ProcessCameraProvider.getInstance(context);
this.yuvToRgbConverter = new YuvToRgbConverter(context);
this.mUSBMonitor = new USBMonitor(context, mOnDeviceConnectListener);
this.mUSBMonitor.register();
}
/**
......@@ -137,7 +188,7 @@ public class CameraxController {
/**
* 相机初始化
*/
public void apply(){
public void bindCamera(){
cameraProviderFutureListener.addListener(() -> {
try{
// 获取cameraProvider
......@@ -193,8 +244,7 @@ public class CameraxController {
private void analyzeImage(ImageProxy imageProxy) {
try {
// 生成Bitmap
Bitmap bitmap = Bitmap.createBitmap(imageProxy.getWidth(), imageProxy.getHeight(), Bitmap.Config.ARGB_8888);
yuvToRgbConverter.yuvToRgb(imageProxy.getImage(), bitmap);
Bitmap bitmap = imageProxy.toBitmap();
// 回调
if (onImageAnalyzeListener != null){
onImageAnalyzeListener.onAnalyzed(bitmap);
......@@ -229,6 +279,10 @@ public class CameraxController {
@SuppressLint("RestrictedApi") CameraSelector cameraSelector = CameraSelector.Builder
.fromSelector(cameraInfo.getCameraSelector())
.build();
// @SuppressLint("UnsafeOptInUsageError")
// CameraSelector cameraSelector = new CameraSelector.Builder()
// .requireLensFacing(CameraSelector.LENS_FACING_EXTERNAL)
// .build();
// 绑定相机、生命周期和图片分析器
return cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, imageAnalysis);
}
......@@ -257,6 +311,7 @@ public class CameraxController {
if (state.getError() != null){
XLog.i("相机异常:%s, %s", state.getError().getType(), state.getError().getCause());
unbindCamera();
}
});
}
......@@ -275,11 +330,18 @@ public class CameraxController {
return false;
}
public void release(){
/**
* 解绑相机资源
* 该方法用于释放相机的生命周期监听和解绑所有相机资源
*/
public void unbindCamera(){
// 初始化相机提供者
ProcessCameraProvider cameraProvider = null;
try {
// 尝试获取相机提供者实例
cameraProvider = cameraProviderFutureListener.get();
} catch (Exception e) {
// 异常处理:记录异常信息
XLog.e(e.toString());
}
if (camera != null){
......@@ -287,11 +349,29 @@ public class CameraxController {
camera.getCameraInfo().getCameraState().removeObservers(lifecycleOwner);
}
if (cameraProvider != null){
// 解绑所有相机资源
cameraProvider.unbindAll();
}
onImageAnalyzeListener = null;
// 释放相机状态资源
cameraState = null;
}
/**
* 销毁相机对象并释放资源
* 该方法调用unbindCamera()释放相机资源,并将其他相关资源置为null以供垃圾回收
*/
public void destroy(){
if (mUSBMonitor != null){
mUSBMonitor.destroy();
mUSBMonitor = null;
}
// 先解绑相机资源
unbindCamera();
// 释放图像分析监听器资源
onImageAnalyzeListener = null;
// 停止执行器并释放其资源
executors.shutdown();
// 将实例对象置为null以供垃圾回收
instance = null;
}
}
package com.wmdigit.common.model;
import androidx.annotation.NonNull;
/**
* @author dizi
*/
public class SimpleUsbDevice {
private int pid;
private int vid;
private String name;
public SimpleUsbDevice() {
}
public SimpleUsbDevice(int pid, int vid) {
this.pid = pid;
this.vid = vid;
this.name = "";
}
public SimpleUsbDevice(int pid, int vid, String name) {
this.pid = pid;
this.vid = vid;
this.name = name;
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public int getVid() {
return vid;
}
public void setVid(int vid) {
this.vid = vid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@NonNull
@Override
public String toString() {
return name + "(pid:" + pid + ", vid:" + vid + ")";
}
}
package com.wmdigit.common.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.ScriptIntrinsicYuvToRGB;
import android.renderscript.Type;
public class NV21ToBitmap {
private RenderScript rs;
private ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic;
private Type.Builder yuvType, rgbaType;
private Allocation in, out;
public NV21ToBitmap(Context context) {
rs = RenderScript.create(context);
yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
}
public Bitmap nv21ToBitmap(byte[] nv21, int width, int height) {
if (yuvType == null) {
yuvType = new Type.Builder(rs, Element.U8(rs)).setX(nv21.length);
in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);
rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
}
in.copyFrom(nv21);
yuvToRgbIntrinsic.setInput(in);
yuvToRgbIntrinsic.forEach(out);
Bitmap bmpout = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
out.copyTo(bmpout);
return bmpout;
}
public void destroy() {
if (rs != null) {
rs.destroy();
rs = null;
}
if (in != null) {
in.destroy();
in = null;
}
if (out != null) {
out.destroy();
out = null;
}
}
}
\ No newline at end of file
......@@ -10,6 +10,7 @@ import android.widget.ArrayAdapter;
import android.widget.ListPopupWindow;
import android.widget.PopupWindow;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
......@@ -99,6 +100,10 @@ public class MySpinner extends ConstraintLayout {
}
public void setAdapter(SpinnerAdapter adapter){
spinner.setAdapter(adapter);
}
public void setSelectedIndex(int index){
spinner.setSelection(index, true);
}
......
......@@ -61,7 +61,7 @@ add_library(clsretri SHARED IMPORTED)
set_target_properties(
clsretri
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libclsretri.a
${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libretri_dishes.a
)
# 餐盘分类、特征提取库
......
......@@ -54,10 +54,12 @@ Java_com_wmdigit_core_catering_dish_DishDetection_process(JNIEnv *env, jobject t
for (int i = 0; i < rows; ++i) {
rect_array[i] = (int *) malloc(cols * sizeof (int));
}
// 声明一个row * 160的数组,存储每个框的特征向量
// 声明一个row * 128的数组,存储每个框的特征向量
auto** feature_array = (float **) malloc(rows * sizeof (float *));
// 遍历识别结果
for (size_t i = 0; i < output.output_list.size(); ++i) {
LOGD("size: %d", output.output_list.size());
int size = output.output_list.size();
for (size_t i = 0; i < size; ++i) {
// 根据阈值,筛选出有效的框
if (output.output_list[i].prob >= 0.8){
// 记录框的左上、右下点坐标
......@@ -90,8 +92,8 @@ Java_com_wmdigit_core_catering_dish_DishDetection_process(JNIEnv *env, jobject t
// float** 转java float[][]
jobjectArray j_feature_array = env->NewObjectArray(rows, env->FindClass("[F"), nullptr);
for (int i = 0; i < rows; i++) {
jfloatArray sub_feature = env->NewFloatArray(DETFEA_FEAT_DIM);
env->SetFloatArrayRegion(sub_feature, 0, DETFEA_FEAT_DIM, feature_array[i]);
jfloatArray sub_feature = env->NewFloatArray(LENGTH_FEATURE);
env->SetFloatArrayRegion(sub_feature, 0, LENGTH_FEATURE, feature_array[i]);
env->SetObjectArrayElement(j_feature_array, i, sub_feature);
}
// 释放feature_array
......
......@@ -9,4 +9,6 @@
#include "image_tools.h"
//#include <OpenCL/opencl.h>
#define LENGTH_FEATURE 128
#endif //CATERINGDETECT_CATERING_DISH_DETECTION_H
......@@ -10,15 +10,16 @@
#define DETFEA_FEAT_DIM 160
#define DETFEA_ALL_OK 0x00000000 //
#define DETFEA_HANDLE_NULL 0x10050001 // 输入句柄为空
#define DETFEA_INPUT_IMAGE_EMPTY 0x10050002 // 输入图像为空
#define DETFEA_OUTPUT_NULL 0x10050003 // 输入的输出结构体为空
#define DETFEA_HANDLE_NULL 0x10050001 // ��������
#define DETFEA_INPUT_IMAGE_EMPTY 0x10050002 // ����ͼ��Ϊ��
#define DETFEA_OUTPUT_NULL 0x10050003 // ���������ṹ��Ϊ��
typedef struct _DETFEA_INPUT_
{
cv::Mat img;
bool dump_debug_crop_image=false; // �Ƿ��dump debugͼ
unsigned long int pts=0; // ����ʱ���������dumpͼʱ������ͬ������ͼƬ
}DETFEA_INPUT;
......@@ -40,7 +41,7 @@ typedef struct _DETFEA_OUTPUT_
}DETFEA_OUTPUT;
// 定义算法的识别设
// �����㷨��ʶ���豸
typedef enum _DETFEA_DEVICE_
{
DETFEA_CPU = 0x0000, // CPU
......@@ -50,12 +51,12 @@ typedef enum _DETFEA_DEVICE_
/***************************************************************************************************
* 功 能: 初始化
* 参 数:
* const char* model_path - I 模型路径(这个可以设为NULL,表示用内置的模型)
* DETFEA_DEVICE device_name - I 设备类型
* void** detfea_handle - O 句柄
* 返回值: 错误码
* �� ��: ��ʼ��
* �� ��:
* const char* model_path - I ģ��·��(���������ΪNULL����ʾ�����õ�ģ��)
* DETFEA_DEVICE device_name - I �豸����
* void** detfea_handle - O ���
* ����ֵ: ������
***************************************************************************************************/
int DETFEA_Init(const char* model_path1,
const char* model_path2,
......@@ -64,24 +65,44 @@ int DETFEA_Init(const char* model_path1,
/***************************************************************************************************
* 功 能: 识别
* 参 数:
* DETFEA_INPUT in_img - I 输入图片
* DETFEA_OUTPUT* detfea_output - O 返回识别结果
* void* handle - I 句柄
* 返回值: 错误码
* �� ��: ʶ��
* �� ��:
* DETFEA_INPUT in_img - I ����ͼƬ
* DETFEA_OUTPUT* detfea_output - O ����ʶ����
* void* handle - I ���
* ����ֵ: ������
***************************************************************************************************/
int DETFEA_Process(DETFEA_INPUT in_img, DETFEA_OUTPUT* detfea_output, void* handle);
/***************************************************************************************************
* 功 能: 释放句柄
* 参 数:
* void** handle - I 句柄
* 返回值: 错误码
* �� ��: �ͷž��
* �� ��:
* void** handle - I ���
* ����ֵ: ������
***************************************************************************************************/
int DETFEA_Release(void** handle);
/***************************************************************************************************
* �� ��: ����/��ȡIOU��ֵ(ֵԽ�󣬿�Խ��)
* �� ��:
* float value - I ֵ
* void* handle - I ���
* ����ֵ: ������
***************************************************************************************************/
int DETFEA_SetDetIOUThres(float value, void* handle);
int DETFEA_GetDetIOUThres(float &value, void* handle);
/***************************************************************************************************
* �� ��: ����/��ȡ���Ŷ���ֵ(ֵԽ�󣬿�Խ��)
* �� ��:
* float value - I ֵ
* void* handle - I ���
* ����ֵ: ������
***************************************************************************************************/
int DETFEA_SetDetConfThres(float value, void* handle);
int DETFEA_GetDetConfThres(float& value, void* handle);
#endif
\ No newline at end of file
......@@ -19,7 +19,7 @@ public class DishDetection implements TargetDetection {
/**
* 输出维度
*/
private final int OUTPUT_DIMENSION = 160;
private final int OUTPUT_DIMENSION = 128;
/**
* JNI初始化目标检测算法
......
......@@ -4,6 +4,7 @@ import android.content.Context;
import com.tencent.mmkv.MMKV;
import com.wmdigit.data.database.AppDatabase;
import com.wmdigit.data.disk.repository.DiskRepository;
/**
* 本地数据存储模块
......@@ -19,6 +20,8 @@ public class LocalDataModule {
AppDatabase.getInstance(appContext);
// 初始化mmkv
MMKV.initialize(context);
// 清空本地识别记录图片
DiskRepository.getInstance().clearIdentifyRecords();
}
}
......
......@@ -10,10 +10,22 @@ import com.wmdigit.data.database.entity.ProductsPO;
import java.io.File;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
/**
* @author dizi
*/
public class DiskRepository {
/**
* 声明一个最终字符串常量,用于存储"IdentifyRecords"文件夹的名称
*/
private final String FOLDER_NAME_IDENTIFY_RECORDS = "IdentifyRecords";
/**
* 声明一个最终字符串常量,用于存储"ProductLearningRecords"文件夹的名称
*/
private final String FOLDER_NAME_PRODUCT_LEARNING_RECORDS = "ProductLearningRecords";
private static DiskRepository instance;
public static DiskRepository getInstance(){
if (instance == null){
......@@ -38,7 +50,7 @@ public class DiskRepository {
public String saveIdentifyRecordToDisk(Bitmap bitmap, int[][] rectArray, List<ProductsPO> products){
Context context = LocalDataModule.getAppContext();
// 识别记录根目录
String identifyRecordsRootPath = context.getExternalFilesDir("IdentifyRecords").getAbsolutePath();
String identifyRecordsRootPath = context.getExternalFilesDir(FOLDER_NAME_IDENTIFY_RECORDS).getAbsolutePath();
String imageFilename = UUID.randomUUID().toString() + ".jpg";
// 保存图片
BitmapUtils.saveBitmap(bitmap, identifyRecordsRootPath + "/" + imageFilename);
......@@ -46,7 +58,7 @@ public class DiskRepository {
return identifyRecordsRootPath + "/" + imageFilename;
}
// 商品的学习记录根目录
String productLearningRecordsRootPath = context.getExternalFilesDir("ProductLearningRecords").getAbsolutePath();
String productLearningRecordsRootPath = context.getExternalFilesDir(FOLDER_NAME_PRODUCT_LEARNING_RECORDS).getAbsolutePath();
// 根据框的坐标,拆分原图
for (int i = 0; i < rectArray.length; i++) {
if (products.get(i) == null || TextUtils.isEmpty(products.get(i).getProductCode())){
......@@ -61,4 +73,19 @@ public class DiskRepository {
return identifyRecordsRootPath + "/" + imageFilename;
}
/**
* 删除识别记录文件夹图片
*/
public void clearIdentifyRecords(){
Context context = LocalDataModule.getAppContext();
String identifyRecordsRootPath = context.getExternalFilesDir(FOLDER_NAME_IDENTIFY_RECORDS).getAbsolutePath();
File folder = new File(identifyRecordsRootPath);
if (!folder.exists() || !folder.isDirectory()){
return;
}
for (File file : Objects.requireNonNull(folder.listFiles())){
file.delete();
}
}
}
......@@ -66,4 +66,12 @@ public class MmkvCons {
* 识别模式
*/
public static final String MMKV_KEY_AI_MODE = "MMKV_KEY_AI_MODE";
/**
* 摄像头PID
*/
public static final String MMKV_CAMERA_PID = "MMKV_CAMERA_PID";
/**
* 摄像头VID
*/
public static final String MMKV_CAMERA_VID = "MMKV_CAMERA_VID";
}
package com.wmdigit.data.mmkv.repository;
import com.tencent.mmkv.MMKV;
import com.wmdigit.common.model.SimpleUsbDevice;
import com.wmdigit.data.mmkv.constant.MmkvCons;
/**
* @author dizi
*/
public class CameraLocalRepository {
private static CameraLocalRepository instance;
public static CameraLocalRepository getInstance(){
if (instance == null){
synchronized (CameraLocalRepository.class){
if (instance == null){
instance = new CameraLocalRepository();
}
}
}
return instance;
}
/**
* 保存摄像头设备信息
*
* @param simpleUsbDevice 摄像头设备对象,包含设备的PID和VID
*/
public void saveCamera(SimpleUsbDevice simpleUsbDevice){
// 保存摄像头的PID到MMKV中
MMKV.defaultMMKV().putInt(MmkvCons.MMKV_CAMERA_PID, simpleUsbDevice.getPid());
// 保存摄像头的VID到MMKV中
MMKV.defaultMMKV().putInt(MmkvCons.MMKV_CAMERA_VID, simpleUsbDevice.getVid());
}
/**
* 获取摄像头设备信息
*
* @return 返回一个新的SimpleUsbDevice对象,包含从MMKV中读取的PID和VID
*/
public SimpleUsbDevice getCamera(){
// 从MMKV中读取摄像头的PID和VID,并创建一个新的SimpleUsbDevice对象
return new SimpleUsbDevice(MMKV.defaultMMKV().getInt(MmkvCons.MMKV_CAMERA_PID, 0), MMKV.defaultMMKV().getInt(MmkvCons.MMKV_CAMERA_VID, 0));
}
}
......@@ -15,7 +15,7 @@ android {
buildTypes {
debug {
buildConfigField 'String', 'POS_URL', '"https://se.wmdigit.com/"'
buildConfigField 'String', 'POS_URL', '"https://se-test.frp.wmdigit.com/"'
buildConfigField 'String', 'VERSION_CODE', '"1000200"'
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
......
......@@ -34,15 +34,21 @@ public class CateringIdentifyRecord implements Serializable {
*/
private String identifyTime;
/**
* 记录类型,0:学习记录,1:识别记录
*/
private int recordType;
public CateringIdentifyRecord() {
}
public CateringIdentifyRecord(String posCode, String frameImageUrl, String imageUrl, String smallImageSite, String identifyTime) {
public CateringIdentifyRecord(String posCode, String frameImageUrl, String imageUrl, String smallImageSite, String identifyTime, int recordType) {
this.posCode = posCode;
this.frameImageUrl = frameImageUrl;
this.imageUrl = imageUrl;
this.smallImageSite = smallImageSite;
this.identifyTime = identifyTime;
this.recordType = recordType;
}
public String getPosCode() {
......@@ -84,4 +90,12 @@ public class CateringIdentifyRecord implements Serializable {
public void setIdentifyTime(String identifyTime) {
this.identifyTime = identifyTime;
}
public int getRecordType() {
return recordType;
}
public void setRecordType(int recordType) {
this.recordType = recordType;
}
}
......@@ -62,6 +62,7 @@ public class IdentifyRecordRepository extends BaseMvvmNetworkRepository {
record.setFrameImageUrl((String) url);
// 设置小图片站点信息
record.setSmallImageSite(json);
record.setRecordType(0);
// 异步上传餐饮识别记录到服务器
return ServiceFactory.getServiceFactory().getPosService()
.uploadCateringIdentifyRecord(record)
......
......@@ -15,4 +15,7 @@ v1.0.2 2024/08/06 1.增加系统信息页
12.增加【清空商品数据】、【SN解绑】、【下载远程工具】、【检查更新】功能
13.增加摄像头断线重连
14.todo 增加学习记录管理模块
15.todo 替换LOGO
\ No newline at end of file
15.todo 替换LOGO
16.todo 增加过期数据清理
17.todo 增加轮询job
18.摄像头改为UvcCamera
\ No newline at end of file
......@@ -83,8 +83,8 @@ public class DemoHomeFragment extends BaseMvvmFragment<DemoHomeViewModel, Fragme
// todo 测试,开相机
CameraxController.getInstance(requireContext())
.setLifecycleOwner(this)
.setOnImageAnalyzeListener(this::processBitmap)
.apply();
.setOnImageAnalyzeListener(this::processBitmap);
// .apply();
}
int count = 0;
......
package com.wmdigit.setting.fragment;
import com.elvishew.xlog.XLog;
import com.jiangdg.uvc.USBCameraHelper;
import com.wmdigit.camera.CameraxController;
import com.wmdigit.common.base.mvvm.BaseMvvmFragment;
import com.wmdigit.setting.R;
......@@ -46,18 +47,22 @@ public class CameraCropFragment extends BaseMvvmFragment<CameraCropViewModel, Fr
}
@Override
public void onStart() {
super.onStart();
public void onResume() {
super.onResume();
// 注册相机图片监听
CameraxController.getInstance(requireContext())
/*CameraxController.getInstance(requireContext())
.setFrameInterval(2)
.setOnImageAnalyzeListener(mViewModel.getOnImageAnalyzeListener());*/
USBCameraHelper.getInstance(requireContext())
.setOnImageAnalyzeListener(mViewModel.getOnImageAnalyzeListener());
}
@Override
public void onStop() {
super.onStop();
CameraxController.getInstance().setOnImageAnalyzeListener(null);
public void onPause() {
super.onPause();
// CameraxController.getInstance().setOnImageAnalyzeListener(null);
USBCameraHelper.getInstance(requireContext())
.setOnImageAnalyzeListener(null);
}
@Override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment