58

我正在编写一个 Android 1.5 应用程序,它在启动后立即启动。这是一个Service并且应该在没有预览的情况下拍照。这个应用程序将记录某些区域的光密度。我可以拍照,但照片是黑色的。

经过长时间的研究,我遇到了一个关于它的错误线程。如果您不生成预览,则图像将为黑色,因为 Android 相机需要预览才能设置曝光和对焦。我创建了一个SurfaceView和监听器,但该onSurfaceCreated()事件永远不会被触发。

我猜原因是,表面不是在视觉上创建的。我还看到了一些静态调用相机的例子,用MediaStore.CAPTURE_OR_SOMETHING它拍照并用两行代码保存在所需的文件夹中,但它也不拍照。

我需要使用 IPC 并bindService()调用此函数吗?或者是否有替代方法来实现这一目标?

4

9 回答 9

53

android平台上的相机在给出有效的预览表面之前无法流式传输视频,这真的很奇怪。该平台的架构师似乎根本没有考虑 3rd 方视频流应用程序。即使对于增强现实情况,图片也可以呈现为某种视觉替代,而不是实时摄像机流。

无论如何,您可以简单地将预览表面调整为 1x1 像素,并将其放在小部件(视觉元素)的角落的某个位置。请注意 - 调整预览表面的大小,而不是相机帧的大小。

当然,这种技巧并不能消除不需要的数据流(用于预览),这会消耗一些系统资源和电池。

于 2010-10-07T11:10:32.140 回答
40

我在Android Camera Docs中找到了答案。

注意:可以在MediaRecorder不先创建相机预览的情况下使用并跳过此过程的前几个步骤。但是,由于用户通常更喜欢在开始录制之前查看预览,因此此处不讨论该过程。

您可以在上面的链接中找到分步说明。在说明之后,它将说明我在上面提供的报价。

于 2012-06-06T06:57:41.103 回答
37

实际上这是可能的,但是您必须使用虚拟 SurfaceView 伪造预览

SurfaceView view = new SurfaceView(this);
c.setPreviewDisplay(view.getHolder());
c.startPreview();
c.takePicture(shutterCallback, rawPictureCallback, jpegPictureCallback);

2011 年9 月 21 日更新:显然这不适用于所有 Android 设备。

于 2011-03-21T23:23:27.613 回答
36

拍照

在尝试隐藏预览之前先让这个工作。

  • 正确设置预览
    • 使用SurfaceView(Android-4.0 之前的兼容性)或SurfaceTexture(Android 4+,可设为透明)
    • 拍照前设置和初始化
    • 在设置和初始化预览之前等待SurfaceView's SurfaceHolder(via getHolder()) 报告surfaceCreated()TextureView报告onSurfaceTextureAvailable给它。SurfaceTextureListener
  • 确保预览可见:
    • 将其添加到WindowManager
    • 确保其布局大小至少为 1x1 像素(您可能希望首先将其设为MATCH_PARENTxMATCH_PARENT以进行测试)
    • 确保它的可见性是View.VISIBLE(如果你不指定它似乎是默认值)
    • 确保你FLAG_HARDWARE_ACCELERATEDLayoutParamsif 它是一个TextureView.
  • 使用takePictureJPEG 回调,因为文档说并非所有设备都支持其他回调

故障排除

  • 如果surfaceCreated/onSurfaceTextureAvailable没有被调用,SurfaceView/TextureView可能没有被显示。
  • 如果takePicture失败,首先确保预览工作正常。您可以删除takePicture呼叫并让预览运行以查看它是否显示在屏幕上。
  • 如果图片比应有的暗,您可能需要在调用之前延迟大约一秒钟,takePicture以便在预览开始后相机有时间调整其曝光。

隐藏预览

  • 将预览View设置为 1x1 大小以最小化其可见性(或尝试 8x16 以获得更高的可靠性

    new WindowManager.LayoutParams(1, 1, /*...*/)
    
  • 将预览移出中心以降低其显着性:

    new WindowManager.LayoutParams(width, height,
        Integer.MIN_VALUE, Integer.MIN_VALUE, /*...*/)
    
  • 使预览透明(仅适用于TextureView

    WindowManager.LayoutParams params = new WindowManager.LayoutParams(
        width, height, /*...*/
        PixelFormat.TRANSPARENT);
    params.alpha = 0;
    

工作示例(在 Sony Xperia M、Android 4.3 上测试)

/** Takes a single photo on service start. */
public class PhotoTakingService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        takePhoto(this);
    }

    @SuppressWarnings("deprecation")
    private static void takePhoto(final Context context) {
        final SurfaceView preview = new SurfaceView(context);
        SurfaceHolder holder = preview.getHolder();
        // deprecated setting, but required on Android versions prior to 3.0
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

        holder.addCallback(new Callback() {
            @Override
            //The preview must happen at or after this point or takePicture fails
            public void surfaceCreated(SurfaceHolder holder) {
                showMessage("Surface created");

                Camera camera = null;

                try {
                    camera = Camera.open();
                    showMessage("Opened camera");

                    try {
                        camera.setPreviewDisplay(holder);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }

                    camera.startPreview();
                    showMessage("Started preview");

                    camera.takePicture(null, null, new PictureCallback() {

                        @Override
                        public void onPictureTaken(byte[] data, Camera camera) {
                            showMessage("Took picture");
                            camera.release();
                        }
                    });
                } catch (Exception e) {
                    if (camera != null)
                        camera.release();
                    throw new RuntimeException(e);
                }
            }

            @Override public void surfaceDestroyed(SurfaceHolder holder) {}
            @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
        });

        WindowManager wm = (WindowManager)context
            .getSystemService(Context.WINDOW_SERVICE);
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                1, 1, //Must be at least 1x1
                WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
                0,
                //Don't know if this is a safe default
                PixelFormat.UNKNOWN);

        //Don't set the preview visibility to GONE or INVISIBLE
        wm.addView(preview, params);
    }

    private static void showMessage(String message) {
        Log.i("Camera", message);
    }

    @Override public IBinder onBind(Intent intent) { return null; }
}
于 2014-11-22T23:43:47.623 回答
22

在 Android 4.0 及更高版本(API 级别 >= 14)上,您可以使用TextureView预览相机流并使其不可见,以免向用户显示。就是这样:

首先创建一个类来实现 SurfaceTextureListener,它将获取预览表面的创建/更新回调。该类还接受一个相机对象作为输入,这样它就可以在创建表面后立即调用相机的 startPreview 函数:

public class CamPreview extends TextureView implements SurfaceTextureListener {

  private Camera mCamera;

  public CamPreview(Context context, Camera camera) {
    super(context);
    mCamera = camera;
   }

  @Override
  public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
    Camera.Size previewSize = mCamera.getParameters().getPreviewSize();
    setLayoutParams(new FrameLayout.LayoutParams(
        previewSize.width, previewSize.height, Gravity.CENTER));

    try{
      mCamera.setPreviewTexture(surface);
     } catch (IOException t) {}

    mCamera.startPreview();
    this.setVisibility(INVISIBLE); // Make the surface invisible as soon as it is created
  }

  @Override
  public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
      // Put code here to handle texture size change if you want to
  }

  @Override
  public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
    return true;
  }

  @Override
  public void onSurfaceTextureUpdated(SurfaceTexture surface) {
      // Update your view here!
  }
}

您还需要实现一个回调类来处理预览数据:

public class CamCallback implements Camera.PreviewCallback{
  public void onPreviewFrame(byte[] data, Camera camera){
     // Process the camera data here
  }
}

使用上面的 CamPreview 和 CamCallback 类在你的活动的 onCreate() 或类似的启动函数中设置相机:

// Setup the camera and the preview object
Camera mCamera = Camera.open(0);
CamPreview camPreview = new CamPreview(Context,mCamera);
camPreview.setSurfaceTextureListener(camPreview);

// Connect the preview object to a FrameLayout in your UI
// You'll have to create a FrameLayout object in your UI to place this preview in
FrameLayout preview = (FrameLayout) findViewById(R.id.cameraView); 
preview.addView(camPreview);

// Attach a callback for preview
CamCallback camCallback = new CamCallback();
mCamera.setPreviewCallback(camCallback);
于 2013-01-09T02:43:49.537 回答
20

有一种方法可以做到这一点,但它有点棘手。应该做的是,从服务中将表面保持器附加到窗口管理器

WindowManager wm = (WindowManager) mCtx.getSystemService(Context.WINDOW_SERVICE);
params = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
            WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
            PixelFormat.TRANSLUCENT);        
wm.addView(surfaceview, params);

然后设置

surfaceview.setZOrderOnTop(true);
mHolder.setFormat(PixelFormat.TRANSPARENT);

其中 mHolder 是您从表面视图中获得的支架。

这样,您可以使用表面视图的 alpha,使其完全透明,但相机仍会获取帧。

我就是这样做的。希望能帮助到你 :)

于 2012-04-22T14:12:25.993 回答
13

我们通过在低于 3.0 的版本中使用虚拟 SurfaceView(未添加到实际 GUI)解决了这个问题(或者假设 4.0 作为平板电脑上的摄像头服务并没有真正意义)。在 >= 4.0 的版本中,这只适用于模拟器;(使用 SurfaceTexture(和 setSurfaceTexture())而不是 SurfaceView(和 setSurfaceView())在这里工作。至少这适用于 Nexus S。

我认为这确实是Android框架的一个缺点。

于 2012-04-20T15:05:32.803 回答
3

在“Sam 的工作示例”中(谢谢 Sam...)

如果在指令 "wm.addView(preview, params);"

获取异常“无法添加窗口 android.view.ViewRoot - 此窗口类型的权限被拒绝”

通过在 AndroidManifest 中使用此权限来解决:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
于 2016-01-08T13:28:53.017 回答
1

您可以尝试此工作代码,此服务单击前置图片,如果要捕获后置摄像头图片,请在代码中取消注释后置摄像头并注释前置摄像头。

注意: - 允许从 Activity 或任何地方访问 App 和 startService 的相机和存储权限。

public class MyService extends Service {

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        CapturePhoto();
    }

    private void CapturePhoto() {

        Log.d("kkkk","Preparing to take photo");
        Camera camera = null;

        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();

            int frontCamera = 1;
            //int backCamera=0;

            Camera.getCameraInfo(frontCamera, cameraInfo);

            try {
                camera = Camera.open(frontCamera);
            } catch (RuntimeException e) {
                Log.d("kkkk","Camera not available: " + 1);
                camera = null;
                //e.printStackTrace();
            }
            try {
                if (null == camera) {
                    Log.d("kkkk","Could not get camera instance");
                } else {
                    Log.d("kkkk","Got the camera, creating the dummy surface texture");
                     try {
                         camera.setPreviewTexture(new SurfaceTexture(0));
                        camera.startPreview();
                    } catch (Exception e) {
                        Log.d("kkkk","Could not set the surface preview texture");
                        e.printStackTrace();
                    }
                    camera.takePicture(null, null, new Camera.PictureCallback() {

                        @Override
                        public void onPictureTaken(byte[] data, Camera camera) {
                            File pictureFileDir=new File("/sdcard/CaptureByService");

                            if (!pictureFileDir.exists() && !pictureFileDir.mkdirs()) {
                                pictureFileDir.mkdirs();
                            }
                            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyymmddhhmmss");
                            String date = dateFormat.format(new Date());
                            String photoFile = "ServiceClickedPic_" + "_" + date + ".jpg";
                            String filename = pictureFileDir.getPath() + File.separator + photoFile;
                            File mainPicture = new File(filename);

                            try {
                                FileOutputStream fos = new FileOutputStream(mainPicture);
                                fos.write(data);
                                fos.close();
                                Log.d("kkkk","image saved");
                            } catch (Exception error) {
                                Log.d("kkkk","Image could not be saved");
                            }
                            camera.release();
                        }
                    });
                }
            } catch (Exception e) {
                camera.release();
            }
    }
}
于 2018-01-03T09:31:33.813 回答