在后台捕获相机帧 (Android)
Capture camera frames in background (Android)
我的问题是:我想要一个后台服务,它会实时从摄像头获取帧,以便我可以分析它们。我在这里看到了很多类似的主题,据说可以解决这个问题,但 none 对我来说确实有效。
我的第一次尝试是创建一个 Activity,它启动了一个服务,并在该服务中创建了一个 surfaceView,从中我得到了一个 holder 并实现了一个回调,我在其中准备了相机和所有。然后,在 previewCallback 上,我可以创建一个新线程,它可以分析我从 PreviewCallback 的 onPreviewFrame 方法获取的数据。
效果很好,虽然我在前台有该服务,但是当我打开另一个应用程序(服务仍在后台 运行ning 时),我意识到预览是在那里,所以我无法从中获取框架。
在网上搜索,我发现我也许可以用 SurfaceTexture 解决这个问题。所以,我创建了一个 Activity 来启动我的服务,如下所示:
public class SurfaceTextureActivity extends Activity {
public static TextureView mTextureView;
public static Vibrator mVibrator;
public static GLSurfaceView mGLView;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGLView = new GLSurfaceView(this);
mTextureView = new TextureView(this);
setContentView(mTextureView);
try {
Intent intent = new Intent(SurfaceTextureActivity.this, RecorderService.class);
intent.putExtra(RecorderService.INTENT_VIDEO_PATH, "/folder-path/");
startService(intent);
Log.i("ABC", "Start Service "+this.toString()+" + "+mTextureView.toString()+" + "+getWindowManager().toString());
}
catch (Exception e) {
Log.i("ABC", "Exc SurfaceTextureActivity: "+e.getMessage());
}
}
}
然后我让 RecorderService 实现 SurfaceTextureListener,这样我就可以打开相机并做其他准备工作,然后可能会捕获帧。我的 RecorderService 目前看起来像这样:
public class RecorderService extends Service implements TextureView.SurfaceTextureListener, SurfaceTexture.OnFrameAvailableListener {
private Camera mCamera = null;
private TextureView mTextureView;
private SurfaceTexture mSurfaceTexture;
private float[] mTransformMatrix;
private static IMotionDetection detector = null;
public static Vibrator mVibrator;
@Override
public void onCreate() {
try {
mTextureView = SurfaceTextureActivity.mTextureView;
mTextureView.setSurfaceTextureListener(this);
Log.i("ABC","onCreate");
// startForeground(START_STICKY, new Notification()); - doesn't work
} catch (Exception e) {
Log.i("ABC","onCreate exception "+e.getMessage());
e.printStackTrace();
}
}
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture)
{
//How do I obtain frames?!
// SurfaceTextureActivity.mGLView.queueEvent(new Runnable() {
//
// @Override
// public void run() {
// mSurfaceTexture.updateTexImage();
//
// }
// });
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width,
int height) {
mSurfaceTexture = surface;
mSurfaceTexture.setOnFrameAvailableListener(this);
mVibrator = (Vibrator)this.getSystemService(VIBRATOR_SERVICE);
detector = new RgbMotionDetection();
int cameraId = 0;
Camera.CameraInfo info = new Camera.CameraInfo();
for (cameraId = 0; cameraId < Camera.getNumberOfCameras(); cameraId++) {
Camera.getCameraInfo(cameraId, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
break;
}
mCamera = Camera.open(cameraId);
Matrix transform = new Matrix();
Camera.Size previewSize = mCamera.getParameters().getPreviewSize();
int rotation = ((WindowManager)(getSystemService(Context.WINDOW_SERVICE))).getDefaultDisplay()
.getRotation();
Log.i("ABC", "onSurfaceTextureAvailable(): CameraOrientation(" + cameraId + ")" + info.orientation + " " + previewSize.width + "x" + previewSize.height + " Rotation=" + rotation);
try {
switch (rotation) {
case Surface.ROTATION_0:
mCamera.setDisplayOrientation(90);
mTextureView.setLayoutParams(new FrameLayout.LayoutParams(
previewSize.height, previewSize.width, Gravity.CENTER));
transform.setScale(-1, 1, previewSize.height/2, 0);
break;
case Surface.ROTATION_90:
mCamera.setDisplayOrientation(0);
mTextureView.setLayoutParams(new FrameLayout.LayoutParams(
previewSize.width, previewSize.height, Gravity.CENTER));
transform.setScale(-1, 1, previewSize.width/2, 0);
break;
case Surface.ROTATION_180:
mCamera.setDisplayOrientation(270);
mTextureView.setLayoutParams(new FrameLayout.LayoutParams(
previewSize.height, previewSize.width, Gravity.CENTER));
transform.setScale(-1, 1, previewSize.height/2, 0);
break;
case Surface.ROTATION_270:
mCamera.setDisplayOrientation(180);
mTextureView.setLayoutParams(new FrameLayout.LayoutParams(
previewSize.width, previewSize.height, Gravity.CENTER));
transform.setScale(-1, 1, previewSize.width/2, 0);
break;
}
mCamera.setPreviewTexture(mSurfaceTexture);
Log.i("ABC", "onSurfaceTextureAvailable(): Transform: " + transform.toString());
mCamera.startPreview();
// mTextureView.setVisibility(0);
mCamera.setPreviewCallback(new PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
if (data == null) return;
Camera.Size size = mCamera.getParameters().getPreviewSize();
if (size == null) return;
//This is where I start my thread that analyzes images
DetectionThread thread = new DetectionThread(data, size.width, size.height);
thread.start();
}
});
}
catch (Exception t) {
Log.i("ABC", "onSurfaceTextureAvailable Exception: "+ t.getMessage());
}
}
然而,与其他情况类似,因为我的分析线程在 onSurfaceTextureAvailable 内部启动,这仅在纹理 存在 时启动,而不是在我打开另一个应用程序时,打开其他东西就不会继续抓帧了。
Some 想法表明这是可能的,但我只是不知道如何实现。有一个想法,我可以实现 SurfaceTexture.onFrameAvailable 然后一旦新帧可用,触发 运行nable to be 运行 on render thread (GLSurfaceView.queueEvent(..) ) 最后 运行 一个 运行 可调用 SurfaceTexture.updateTexImage()。这是我尝试过的方法(它在我的代码中被注释掉了),但它不起作用,如果我这样做,应用程序会崩溃。
我还能做什么?我知道这可以以某种方式工作,因为我已经看到它在 SpyCameraOS 等应用程序中使用(是的,我知道它是开源的,我已经看过代码,但我无法制定一个可行的解决方案),而且我感觉好像我在某处遗漏了一小块,但我不知道我做错了什么。我过去 3 天一直在做这个,但没有成功。
不胜感激。
总结评论:将 Camera 的输出定向到未绑定到 View 对象的 SurfaceTexture。当 activity 暂停时,TextureView 将被销毁,释放其 SurfaceTexture,但如果您创建一个单独的 SurfaceTexture(或从 TextureView 中分离它),那么它不会受到 Activity 中更改的影响状态。纹理可以渲染到离屏表面,从中可以读取像素。
可以在 Grafika 中找到各种示例。
我的问题是:我想要一个后台服务,它会实时从摄像头获取帧,以便我可以分析它们。我在这里看到了很多类似的主题,据说可以解决这个问题,但 none 对我来说确实有效。
我的第一次尝试是创建一个 Activity,它启动了一个服务,并在该服务中创建了一个 surfaceView,从中我得到了一个 holder 并实现了一个回调,我在其中准备了相机和所有。然后,在 previewCallback 上,我可以创建一个新线程,它可以分析我从 PreviewCallback 的 onPreviewFrame 方法获取的数据。
效果很好,虽然我在前台有该服务,但是当我打开另一个应用程序(服务仍在后台 运行ning 时),我意识到预览是在那里,所以我无法从中获取框架。
在网上搜索,我发现我也许可以用 SurfaceTexture 解决这个问题。所以,我创建了一个 Activity 来启动我的服务,如下所示:
public class SurfaceTextureActivity extends Activity {
public static TextureView mTextureView;
public static Vibrator mVibrator;
public static GLSurfaceView mGLView;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGLView = new GLSurfaceView(this);
mTextureView = new TextureView(this);
setContentView(mTextureView);
try {
Intent intent = new Intent(SurfaceTextureActivity.this, RecorderService.class);
intent.putExtra(RecorderService.INTENT_VIDEO_PATH, "/folder-path/");
startService(intent);
Log.i("ABC", "Start Service "+this.toString()+" + "+mTextureView.toString()+" + "+getWindowManager().toString());
}
catch (Exception e) {
Log.i("ABC", "Exc SurfaceTextureActivity: "+e.getMessage());
}
}
}
然后我让 RecorderService 实现 SurfaceTextureListener,这样我就可以打开相机并做其他准备工作,然后可能会捕获帧。我的 RecorderService 目前看起来像这样:
public class RecorderService extends Service implements TextureView.SurfaceTextureListener, SurfaceTexture.OnFrameAvailableListener {
private Camera mCamera = null;
private TextureView mTextureView;
private SurfaceTexture mSurfaceTexture;
private float[] mTransformMatrix;
private static IMotionDetection detector = null;
public static Vibrator mVibrator;
@Override
public void onCreate() {
try {
mTextureView = SurfaceTextureActivity.mTextureView;
mTextureView.setSurfaceTextureListener(this);
Log.i("ABC","onCreate");
// startForeground(START_STICKY, new Notification()); - doesn't work
} catch (Exception e) {
Log.i("ABC","onCreate exception "+e.getMessage());
e.printStackTrace();
}
}
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture)
{
//How do I obtain frames?!
// SurfaceTextureActivity.mGLView.queueEvent(new Runnable() {
//
// @Override
// public void run() {
// mSurfaceTexture.updateTexImage();
//
// }
// });
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width,
int height) {
mSurfaceTexture = surface;
mSurfaceTexture.setOnFrameAvailableListener(this);
mVibrator = (Vibrator)this.getSystemService(VIBRATOR_SERVICE);
detector = new RgbMotionDetection();
int cameraId = 0;
Camera.CameraInfo info = new Camera.CameraInfo();
for (cameraId = 0; cameraId < Camera.getNumberOfCameras(); cameraId++) {
Camera.getCameraInfo(cameraId, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
break;
}
mCamera = Camera.open(cameraId);
Matrix transform = new Matrix();
Camera.Size previewSize = mCamera.getParameters().getPreviewSize();
int rotation = ((WindowManager)(getSystemService(Context.WINDOW_SERVICE))).getDefaultDisplay()
.getRotation();
Log.i("ABC", "onSurfaceTextureAvailable(): CameraOrientation(" + cameraId + ")" + info.orientation + " " + previewSize.width + "x" + previewSize.height + " Rotation=" + rotation);
try {
switch (rotation) {
case Surface.ROTATION_0:
mCamera.setDisplayOrientation(90);
mTextureView.setLayoutParams(new FrameLayout.LayoutParams(
previewSize.height, previewSize.width, Gravity.CENTER));
transform.setScale(-1, 1, previewSize.height/2, 0);
break;
case Surface.ROTATION_90:
mCamera.setDisplayOrientation(0);
mTextureView.setLayoutParams(new FrameLayout.LayoutParams(
previewSize.width, previewSize.height, Gravity.CENTER));
transform.setScale(-1, 1, previewSize.width/2, 0);
break;
case Surface.ROTATION_180:
mCamera.setDisplayOrientation(270);
mTextureView.setLayoutParams(new FrameLayout.LayoutParams(
previewSize.height, previewSize.width, Gravity.CENTER));
transform.setScale(-1, 1, previewSize.height/2, 0);
break;
case Surface.ROTATION_270:
mCamera.setDisplayOrientation(180);
mTextureView.setLayoutParams(new FrameLayout.LayoutParams(
previewSize.width, previewSize.height, Gravity.CENTER));
transform.setScale(-1, 1, previewSize.width/2, 0);
break;
}
mCamera.setPreviewTexture(mSurfaceTexture);
Log.i("ABC", "onSurfaceTextureAvailable(): Transform: " + transform.toString());
mCamera.startPreview();
// mTextureView.setVisibility(0);
mCamera.setPreviewCallback(new PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
if (data == null) return;
Camera.Size size = mCamera.getParameters().getPreviewSize();
if (size == null) return;
//This is where I start my thread that analyzes images
DetectionThread thread = new DetectionThread(data, size.width, size.height);
thread.start();
}
});
}
catch (Exception t) {
Log.i("ABC", "onSurfaceTextureAvailable Exception: "+ t.getMessage());
}
}
然而,与其他情况类似,因为我的分析线程在 onSurfaceTextureAvailable 内部启动,这仅在纹理 存在 时启动,而不是在我打开另一个应用程序时,打开其他东西就不会继续抓帧了。
Some 想法表明这是可能的,但我只是不知道如何实现。有一个想法,我可以实现 SurfaceTexture.onFrameAvailable 然后一旦新帧可用,触发 运行nable to be 运行 on render thread (GLSurfaceView.queueEvent(..) ) 最后 运行 一个 运行 可调用 SurfaceTexture.updateTexImage()。这是我尝试过的方法(它在我的代码中被注释掉了),但它不起作用,如果我这样做,应用程序会崩溃。
我还能做什么?我知道这可以以某种方式工作,因为我已经看到它在 SpyCameraOS 等应用程序中使用(是的,我知道它是开源的,我已经看过代码,但我无法制定一个可行的解决方案),而且我感觉好像我在某处遗漏了一小块,但我不知道我做错了什么。我过去 3 天一直在做这个,但没有成功。
不胜感激。
总结评论:将 Camera 的输出定向到未绑定到 View 对象的 SurfaceTexture。当 activity 暂停时,TextureView 将被销毁,释放其 SurfaceTexture,但如果您创建一个单独的 SurfaceTexture(或从 TextureView 中分离它),那么它不会受到 Activity 中更改的影响状态。纹理可以渲染到离屏表面,从中可以读取像素。
可以在 Grafika 中找到各种示例。