从相机拍摄照片,无需预览

我正在编写一个刚开机后启动的Android 1.5应用程序。 这是一个Service ,应该没有预览的照片。 这个应用程序将记录某些地区的光密度。 我能拍照,但照片是黑色的。

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

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

我需要使用IPC和bindService()来调用这个函数吗? 还是有另一种方法来实现这一目标?

Android平台上的摄像头在给出有效的预览面之前不能流式传输视频,这真是奇怪。 似乎这个平台的架构师根本不在考虑第三方视频流应用。 即使对于增强现实情况,图片也可以作为某种视觉替换而不是实时的相机流来呈现。

无论如何,只需将预览表面调整为1×1像素,然后将其放置在小部件(可视元素)的某个角落即可。 请注意 – 调整预览面的大小,而不是相机的框架大小。

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

其实这是可能的,但你必须伪造一个假的SurfaceView预览

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

在我的博客文章中找到更多细节。

11/21/11更新:显然这不适用于每个Android设备。

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

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

您可以在上面的链接中找到一步一步的说明。 在指示后,它会说明我上面提供的报价。

拍照

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

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

故障排除

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

隐藏预览

  • 预览View 1×1大小以最大限度地降低其可见性( 或尝试8×16以获得更高的可靠性 )

     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; 

工作示例(在索尼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; } } 

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

有这样做的方法,但有点棘手。 应该做什么,是从服务附加窗口管理器的窗口管理器

 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,使它完全透明,但相机仍然会得到帧。

我就是这样做的 希望它有助于:)

我们通过在3.0以下的版本中使用虚拟的SurfaceView(不添加到实际的GUI)来解决这个问题(或者说4.0作为平板电脑上的摄像头服务没有任何意义)。 在版本> = 4.0中,这只能在模拟器中工作(使用SurfaceTexture(和setSurfaceTexture())代替SurfaceView(和setSurfaceView())在这里工作。

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

在“Sam的工作实例”(谢谢Sam …)

如果在构造“wm.addView(preview,params);”

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

在AndroidManifest中使用此权限解决:

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