Android 在没有预览的情况下从相机拍照

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2386025/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 05:38:21  来源:igfitidea点击:

Taking picture from camera without preview

androidandroid-cameraandroid-service

提问by eyurdakul

I am writing an Android 1.5 application which starts just after boot-up. This is a Serviceand should take a picture without preview. This app will log the light density in some areas whatever. I was able to take a picture but the picture was black.

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

After researching for a long time, I came across a bug thread about it. If you don't generate a preview, the image will be black since Android camera needs preview to setup exposure and focus. I've created a SurfaceViewand the listener, but the onSurfaceCreated()event never gets fired.

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

I guess the reason is, the surface is not being created visually. I've also seen some examples of calling the camera statically with MediaStore.CAPTURE_OR_SOMETHINGwhich takes a picture and saves in the desired folder with two lines of code, but it doesn't take a picture too.

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

Do I need to use IPC and bindService()to call this function? Or is there an alternative method to achieve this?

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

采纳答案by alexander.o.krutyakov

it is really weird that camera on android platform can't stream video until it given valid preview surface. it seems that the architects of the platform was not thinking about 3rd party video streaming applications at all. even for augmented reality case the picture can be presented as some kind of visual substitution, not real time camera stream.

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

anyway, you can simply resize preview surface to 1x1 pixelsand put it somewhere in the corner of the widget (visual element). please pay attention - resize preview surface, not camera frame size.

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

of course such trick does not eliminate unwanted data streaming (for preview) which consumes some system resources and battery.

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

回答by Phillip Scott Givens

I found the answer to this in the Android Camera Docs.

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

Note: It is possible to use MediaRecorderwithout creating a camera preview first and skip the first few steps of this process. However, since users typically prefer to see a preview before starting a recording, that process is not discussed here.

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

You can find the step by step instructions at the link above. After the instructions, it will state the quote that I have provided above.

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

回答by Frank

Actually it is possible, but you have to fake the preview with a dummy SurfaceView

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

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

Update 9/21/11:Apparently this does not work for every Android device.

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

回答by Sam

Taking the Photo

拍照

Get this working first before trying to hide the preview.

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

  • Correctly set up the preview
    • Use a SurfaceView(pre-Android-4.0 compatibility) or SurfaceTexture(Android 4+, can be made transparent)
    • Set and initialise it before taking the photo
    • Wait for the SurfaceView's SurfaceHolder(via getHolder()) to report surfaceCreated()or the TextureViewto report onSurfaceTextureAvailableto its SurfaceTextureListenerbefore setting and initialising the preview.
  • Ensure the preview is visible:
    • Add it to the WindowManager
    • Ensure its layout size is at least 1x1 pixels (you might want to start by making it MATCH_PARENTx MATCH_PARENTfor testing)
    • Ensure its visibility is View.VISIBLE(which seems to be the default if you don't specify it)
    • Ensure you use the FLAG_HARDWARE_ACCELERATEDin the LayoutParamsif it's a TextureView.
  • Use takePicture's JPEG callback since the documentation says the other callbacks aren't supported on all devices
  • 正确设置预览
    • 使用SurfaceView(Android-4.0 之前的兼容性)或SurfaceTexture(Android 4+,可以设为透明)
    • 拍照前设置并初始化
    • 等待SurfaceViewSurfaceHolder(通过getHolder()),以报告surfaceCreated()TextureView对报告onSurfaceTextureAvailableSurfaceTextureListener之前的设置和初始化预览。
  • 确保预览可见:
    • 将其添加到 WindowManager
    • 确保其布局大小至少为 1x1 像素(您可能希望首先将其设为MATCH_PARENTxMATCH_PARENT以进行测试)
    • 确保它的可见性是View.VISIBLE(如果你不指定它似乎是默认值)
    • 确保您使用FLAG_HARDWARE_ACCELERATEDLayoutParams,如果这是一个TextureView
  • 使用takePicture的 JPEG 回调,因为文档说并非所有设备都支持其他回调

Troubleshooting

故障排除

  • If surfaceCreated/onSurfaceTextureAvailabledoesn't get called, the SurfaceView/TextureViewprobably isn't being displayed.
  • If takePicturefails, first ensure the preview is working correctly. You can remove your takePicturecall and let the preview run to see if it displays on the screen.
  • If the picture is darker than it should be, you might need to delay for about a second before calling takePictureso that the camera has time to adjust its exposure once the preview has started.
  • 如果surfaceCreated/onSurfaceTextureAvailable没有被调用,则SurfaceView/TextureView可能没有被显示。
  • 如果takePicture失败,首先确保预览正常工作。您可以删除您的takePicture呼叫并让预览运行以查看它是否显示在屏幕上。
  • 如果图片比应有的暗,您可能需要在调用前延迟大约一秒钟,takePicture以便相机在预览开始后有时间调整其曝光。

Hiding the Preview

隐藏预览

  • Make the preview View1x1 size to minimise its visibility (or try 8x16 for possibly more reliability)

    new WindowManager.LayoutParams(1, 1, /*...*/)
    
  • Move the preview out of the centre to reduce its noticeability:

    new WindowManager.LayoutParams(width, height,
        Integer.MIN_VALUE, Integer.MIN_VALUE, /*...*/)
    
  • Make the preview transparent (only works for TextureView)

    WindowManager.LayoutParams params = new WindowManager.LayoutParams(
        width, height, /*...*/
        PixelFormat.TRANSPARENT);
    params.alpha = 0;
    
  • 使预览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;
    

Working Example (tested on Sony Xperia M, Android 4.3)

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

回答by Varun Gulshan

On Android 4.0 and above (API level >= 14), you can use TextureViewto preview the camera stream and make it invisible so as to not show it to the user. Here's how:

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

First create a class to implement a SurfaceTextureListener that will get the create/update callbacks for the preview surface. This class also takes a camera object as input, so that it can call the camera's startPreview function as soon as the surface is created:

首先创建一个类来实现一个 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!
  }
}

You'll also need to implement a callback class to process the preview data:

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

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

Use the above CamPreview and CamCallback classes to setup the camera in your activity's onCreate() or similar startup function:

使用上面的 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);

回答by Vlad

There is a way of doing this but it's somewhat tricky. what should be done, is attach a surfaceholder to the window manager from the service

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

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

and then set

然后设置

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

where mHolder is the holder you get from the surface view.

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

this way, you can play with the surfaceview's alpha, make it completly transparent, but the camera will still get frames.

这样,您可以使用surfaceview的alpha,使其完全透明,但相机仍将获得帧。

that's how i do it. hope it helps :)

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

回答by mnl

We solved this problem by using a dummy SurfaceView (not added to actual GUI) in versions below 3.0 (or let's say 4.0 as a camera service on a tablet does not really make sense). In versions >= 4.0 this worked in the emulator only ;( The use of SurfaceTexture (and setSurfaceTexture()) instead of SurfaceView (and setSurfaceView()) worked here. At least this works on Nexus S.

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

I think this really is a shortcoming of the Android framework.

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

回答by Giulio

In the "Working Example by Sam" (Thank you Sam... )

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

if at istruction "wm.addView(preview, params);"

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

obtain exception "Unable to add window android.view.ViewRoot -- permission denied for this window type"

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

resolve by using this permission in AndroidManifest:

在 AndroidManifest 中使用此权限解决:

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

回答by kdblue

You can try this working code, This service click front picture, if you want to capture back camera picture then uncomment back camera in code and comment front camera.

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

Note :- Allow Camera and Storage permission to App And startService from Activity or anywhere.

注意:- 允许应用程序的相机和存储权限,并从活动或任何地方启动服务。

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