【问题标题】:Taking picture from camera without preview从相机拍照而不预览
【发布时间】:2011-01-24 01:38:55
【问题描述】:

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

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

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

我需要使用IPC和bindService()来调用这个函数吗?或者有其他方法可以实现吗?

【问题讨论】:

标签: android android-camera android-service


【解决方案1】:

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

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

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

【讨论】:

  • 有一个 1×1 表面的问题:在某些设备(例如三星)上可能会很慢,当目标大小不除 4(或可能是 8)时,它无法运行硬件图像转换器
  • 另外在某些设备上它只是不起作用,抛出 RuntimeException(至少据我所知是旧的 Nexus One)
  • 我怀疑谷歌是故意的,否则我无法想象他们的架构师会这样计划。
  • 如果您只有服务(例如,想在应用处于后台时使用相机)则不起作用
  • @mnl,它在服务中对我有用。我将预览添加到窗口管理器作为系统覆盖。
【解决方案2】:

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

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

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

【讨论】:

  • 您是否按照分步说明进行操作?我知道这是评分最低的答案(令人失望),但我遵循了文档,它对我来说很好。
  • @phillip-scott-givens:是的,它有效,但不适用于图片捕捉,这是问题主题。如果您使用 MediaRecorder,则只能跳过预览设置。
  • 呃,我仍然在这方面失去声望。我如何删除这篇文章?如果您使用 RTFM,它可以工作!我发誓。它适用于视频。它适用于图片。我发布了这个,因为我找到了它并且它有效。现在,一年后,我仍然被否决。我尝试了“删除”链接,它只是询问我是否要“投票删除”这篇文章。请不要再对我投反对票了。相反,请在这里回复并告诉我如何删除这个东西。
  • 我觉得这个答案很有帮助 - 在我的应用程序中,拍摄短视频而不是照片似乎更好。
  • 这真的很有趣,非常感谢。会试试这个。不用担心投反对票!
【解决方案3】:

其实是可以的,但是你必须用一个虚拟的 SurfaceView 来伪造预览

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

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

【讨论】:

  • 就是这样。非常感谢!
  • 使用4.0以上的SurfaceTexture和setSurfaceTexture
  • 为了让它在每台设备上都能正常工作,必须在某处添加表面并实际创建,最好是使用持有者的回调。仅当视图可见且大小不为 0x0 时才会调用回调。 setAlpha(0) 似乎没问题,但仅适用于 API 11 及更高版本。
  • 只是为了确认.. 也不适用于 Galaxy Nexus RuntimeException: takePicture failed
  • @3c71,使用透明的 PixelFormatsetAlpha(0) 并没有使它在运行 Android 4.3 的 Sony Xperia M 上变得透明。预览仍然不透明。
【解决方案4】:

拍照

在尝试隐藏预览之前先完成这项工作。

  • 正确设置预览
    • 使用SurfaceView(Android-4.0 之前的兼容性)或SurfaceTexture(Android 4+,可以设为透明)
    • 拍照前设置和初始化
    • 等待SurfaceViewSurfaceHolder(通过getHolder())向surfaceCreated()报告TextureView向其onSurfaceTextureAvailable报告onSurfaceTextureAvailable,然后再设置和初始化预览。
  • 确保预览可见:
    • 将其添加到WindowManager
    • 确保其布局大小至少为 1x1 像素(您可能希望首先将其设为 MATCH_PARENT x MATCH_PARENT 以进行测试)
    • 确保其可见性为View.VISIBLE(如果您不指定,这似乎是默认值)
    • 如果是TextureView,请确保在LayoutParams 中使用FLAG_HARDWARE_ACCELERATED
  • 使用 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; }
}

【讨论】:

  • 这终于是一个关于这个主题的详尽答案,带有一个工作示例!谢谢
  • 此解决方案不再适用于 API 级别 >=23。 Cannot add previewandroid.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@664dc37 -- permission denied for window type 2006
  • @RuchirBaronia,这里有几个选项:1. 使用the technique used by the ML Kit team 2. 切换到camera2 API
【解决方案5】:

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

【讨论】:

  • OP 声明他将在服务中使用它。我也在寻找一种如何从服务中拍照的方法。通过放置FrameLayout 你的意思是把它放在启动服务的活动中?如果服务从后台调用它怎么办?带有此FrameLayout 的 Activity 会弹出吗?
  • @NumberFour:第三段代码与您的情况无关。
  • 如何在服务中使用此代码??我可以从服务中调用 CamPreview 吗??
  • 对回调接口进行了微小的更改,为我工作
【解决方案6】:

有一种方法可以做到这一点,但它有点棘手。 应该做的是,从服务中将 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 是您从表面视图中获得的支架。

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

我就是这样做的。希望对你有帮助:)

【讨论】:

  • 你能给我看更多的源代码吗?它对我不起作用:(您也可以通过电子邮件与我联系。
  • 我添加了这个权限,异常消失了。
  • 哦,我一开始就有这个权限..所以我没有遇到异常:)
  • 这就是我尝试此解决方案并退出我的应用程序时发生的情况:i.imgur.com/g8Fmnj6.png
  • @BVB 窗口管理器不会自动删除您添加的视图.. 所以保存对surfaceview的引用并在退出时添加 wm.removeView(surfaceview)
【解决方案7】:

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

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

【讨论】:

    【解决方案8】:

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

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

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

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

    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    

    【讨论】:

      【解决方案9】:

      你可以试试这个工作代码,这个服务点击前置图片,如果你想捕获后置摄像头图片,那么在代码中取消注释后置摄像头并评论前置摄像头。

      注意:- 允许从 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();
                  }
          }
      }
      

      【讨论】:

      • 投了反对票,因为您实际上是从另一个 SO 帖子中复制了答案,修改了日志文本并将其发布,就好像它是您自己的答案一样。 stackoverflow.com/a/24027066/596841
      • 但是相机返回null!
      猜你喜欢
      • 2016-02-20
      • 1970-01-01
      • 1970-01-01
      • 2018-06-17
      • 2012-06-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多