从没有预览的相机拍摄图片

问题描述 投票:57回答:9

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

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

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

我需要使用IPC和bindService()来调用此功能吗?还是有替代方法可以实现这一目标?

android android-camera android-service
9个回答
50
投票

真的很奇怪,Android平台上的相机在提供有效的预览界面之前无法流式传输视频。似乎该平台的架构师根本没有考虑过第三方视频流应用程序。即使在增强现实的情况下,图片也可以呈现为某种视觉替代,而不是实时的相机流。

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

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


38
投票

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

注意:可以在不创建相机的情况下使用MediaRecorder首先预览,然后跳过此过程的前几个步骤。然而,因为用户通常更喜欢在开始观看之前先预览记录,此处不讨论该过程。

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


36
投票

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

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

Update 9/21/11:显然,这不适用于所有Android设备。


34
投票

拍照

先尝试使用此功能,然后再尝试隐藏预览。

  • 正确设置预览
    • 使用SurfaceView(Android 4.0之前的兼容性)或SurfaceTexture(Android 4+,可以设为透明)
    • 在拍照之前进行设置和初始化
    • 在设置和初始化预览之前,等待SurfaceViewSurfaceHolder(通过getHolder()报告surfaceCreated()TextureViewonSurfaceTextureAvailable报告其SurfaceTextureListener
  • 确保预览可见:
    • 将其添加到WindowManager
    • 确保其布局大小至少为1x1像素(您可能需要先将其设置为MATCH_PARENT x MATCH_PARENT进行测试)
    • 确保其可见性为View.VISIBLE(如果未指定,则为默认值)
    • 如果是FLAG_HARDWARE_ACCELERATED,请确保使用LayoutParams中的TextureView
  • 使用takePicture的JPEG回调,因为文档指出并非所有设备都支持其他回调

故障排除

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

隐藏预览

  • 使预览View 1x1大小以使其可见度最小化[or try 8x16 for possibly more reliability

    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; }
}

21
投票

在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);

20
投票

有一种方法可以做到,但是有点棘手。应该做的是从服务中将一个Surfaceholder附加到窗口管理器中。

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是您从表面视图获得的支架。

这样,您可以使用Surfaceview的Alpha,使其完全透明,但相机仍会获得帧。

这就是我的做法。希望对您有所帮助:)


13
投票

我们通过使用虚拟的SurfaceView(未添加到实际的GUI中)解决了这个问题,该版本低于3.0(或者说4.0作为平板电脑上的摄像头服务没有任何意义)。在> = 4.0版本中,这仅在仿真器中有效;(在这里可以使用SurfaceTexture(和setSurfaceTexture())代替SurfaceView(和setSurfaceView())。至少这在Nexus S上有效。


3
投票

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


1
投票

您可以尝试使用此工作代码,此服务单击正面图片,如果要捕获背面的相机图片,请在代码中取消注释背面的相机并注释正面的相机。

© www.soinside.com 2019 - 2024. All rights reserved.