在后台捕获相机帧(Android)

Posted

技术标签:

【中文标题】在后台捕获相机帧(Android)【英文标题】:Capture camera frames in background (Android) 【发布时间】:2015-03-29 03:11:24 【问题描述】:

我的问题是:我想要一个后台服务,它会实时从相机获取帧,以便我可以分析它们。我在这里看到了很多类似的主题,据说可以解决这个问题,但它们都没有真正适用于我的案例。

我的第一次尝试是创建一个 Activity,它启动了一个 Service,并在该服务内部创建了一个 SurfaceView,我从中获得了一个持有者并实现了一个回调,我在其中准备了相机和所有东西。然后,在 previewCallback 上,我可以创建一个新线程,它可以分析我从 PreviewCallback 的 onPreviewFrame 方法获得的数据。

效果很好,虽然我在前台有那个服务,但是当我打开另一个应用程序(服务仍在后台运行)时,我意识到预览不存在所以我不能' t 从中获取帧。

在互联网上搜索,我发现我可以用 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,然后一旦有新的帧可用,触发一个 runnable 以在渲染线程 (GLSurfaceView.queueEvent(..)) 上运行,最后运行一个可运行的调用 SurfaceTexture.updateTexImage()。这是我尝试过的(它在我的代码中被注释掉了),但它不起作用,如果我这样做,应用程序就会崩溃。

我还能做什么?我知道这可以以某种方式工作,因为我已经看到它在 SpyCameraOS 之类的应用程序中使用(是的,我知道它是开源的,我已经查看了代码,但我无法制定一个可行的解决方案),而且我感觉就像我在某处丢失了一小部分,但我不知道我做错了什么。过去 3 天我一直在这样做,但没有成功。

我们将不胜感激。

【问题讨论】:

您是要显示帧,还是只是捕获它们?如果您不需要显示它们(我认为您不会为“背景”捕获而显示它们),请删除名称中带有“视图”一词的任何内容。使用屏幕外表面作为从 SurfaceTexture 进行渲染的目标。请参阅 Grafika (github.com/google/grafika) 了解使用相机和 SurfaceTexture 的一些示例(可能是“连续捕获”)。 我查看了 ContinuousCaptureActivity 并将其变成了一项服务,但是……只要我按下 Back 或 Home,就会调用 surfaceDestroyed,因此,连续捕获会停止.即使将程序置于后台,我似乎也无法继续进行。我特别有兴趣了解如何使用屏幕外表面作为您提到的渲染目标。有什么例子吗? 可以在 Grafika 的 TextureUploadActivity.java 中找到一个示例——注意在 runTextureTest() 中如何使用 OffscreenSurface。测试是重复上传和渲染一组纹理,以尝试计算纹理上传的速度,它在屏幕外完成。最后包含通过glReadPixels() 访问像素的代码,它保存了最后一次迭代以进行调试。 这是有道理的,但是当表面不再存在时,我应该如何从 SurfaceTexture 渲染到屏幕外表面?在您的createPixelSources() 中,您自己创建了 ByteBuffer,然后在runTextureTest() 中从中创建了图像纹理,但是当 SurfaceTexture 没有用新的更新时,我如何连续向屏幕外表面提供相机的帧数据帧数据,因为当我按下返回/主页时它被破坏了? SurfaceTexture 不会被销毁,除非它与 TextureView 相关联。创建您自己的 SurfaceTexture。例如,请参阅 Grafika 的 TextureFromCameraActivity(搜索 new SurfaceTexture)。您可能还想查看 DoubleDecodeActivity,它在方向更改期间从 TextureViews 分离和重新附加 SurfaceTextures,以避免停止视频播放......我不认为这是您想要做的,但它确实表明您可以保留 TextureView 的即使 Activity 已暂停且 TextureView 消失,SurfaceTexture 仍处于活动状态。 【参考方案1】:

总结 cmets:将 Camera 的输出定向到未绑定到 View 对象的 SurfaceTexture。当 Activity 暂停时,TextureView 将被销毁,释放其 SurfaceTexture,但如果您创建单独的 SurfaceTexture(或将其从 TextureView 中分离),则它不会受到 Activity 状态更改的影响。纹理可以渲染到屏幕外的 Surface,从中可以读取像素。

各种例子可以在Grafika找到。

【讨论】:

如果它比“看那边”更有帮助的话,我会推荐它... >.> 有没有示例代码可以在后台运行SurfaceTexure? @Dania:我不确定“在后台运行 SurfaceTexture”是什么意思。您应该发布一个新问题,说明您正在尝试做什么以及到目前为止您已经尝试过什么。 @fadden,非常感谢您的回复。我想在后台服务而不是活动中获取相机获得的帧。我发布了一个问题,详细解释了我想要做什么并展示了我尝试过的工作。这是问题,***.com/questions/35183408/…。请帮我解决这个问题,我尝试了很多但没有任何效果。谢谢。

以上是关于在后台捕获相机帧(Android)的主要内容,如果未能解决你的问题,请参考以下文章

Android 相机预览帧时间戳

在android中使用opencv捕获视频帧

如何保持 Android Camera 的帧速率恒定

将视频保存为输入而不是android中的相机

使用OpenGL的Android相机帧处理

以编程方式在 Android 中为视频添加图像帧或水印