Съемка с камеры без предварительного просмотра

Я пишу приложение Android 1.5, которое запускается сразу после загрузки. Это Service и должен делать снимок без предварительного просмотра. Это приложение будет регистрировать плотность света в некоторых областях. Я смог сделать снимок, но картина была черной.

После долгого исследования я наткнулся на эту проблему. Если вы не создадите предварительный просмотр, изображение будет черным, так как камера Android нуждается в предварительном просмотре для настройки экспозиции и фокусировки. Я создал SurfaceView и слушателя, но событие onSurfaceCreated() никогда не запускается.

Я предполагаю, что причина в том, что поверхность не создается визуально. Я также видел несколько примеров статического вызова камеры с помощью MediaStore.CAPTURE_OR_SOMETHING, которая делает снимок и сохраняет в нужную папку с двумя строками кода, но также не делает снимок.

Нужно ли использовать IPC и bindService() для вызова этой функции? Или есть альтернативный метод для достижения этого?

Ответ 1

действительно странно, что камера на платформе Android не может транслировать видео до тех пор, пока она не получит правильную поверхность предварительного просмотра. кажется, что архитекторы платформы вообще не думали о сторонних приложениях для потоковой передачи видео. даже для случая расширенной реальности картина может быть представлена ​​как своего рода визуальная замена, а не поток в реальном времени.

в любом случае вы можете просто изменить размер области предварительного просмотра до 1x1 пикселей и поместить ее где-нибудь в угол виджета (визуальный элемент). обратите внимание - измените размер поверхности предварительного просмотра, а не размер камеры.

конечно, такой трюк не устраняет нежелательную передачу данных (для предварительного просмотра), которая потребляет некоторые системные ресурсы и аккумулятор.

Ответ 2

Я нашел ответ на это в Документах для камеры Android.

Примечание. Можно использовать MediaRecorder без создания камеры предварительно просмотрите и пропустите первые несколько шагов этого процесса. Однако, поскольку пользователи обычно предпочитают просматривать предварительный просмотр перед запуском запись, этот процесс здесь не обсуждается.

Вы можете найти пошаговые инструкции по ссылке выше. После инструкций он укажет приведенную выше цитату.

Ответ 3

На самом деле это возможно, но вы должны подделать предварительный просмотр с помощью фиктивного SurfaceView

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

Подробнее в разделе в блоге.

Обновление 9/21/11: По-видимому, это не работает для каждого Android-устройства.

Ответ 4

Взятие фото

Сначала выполните эту работу, прежде чем пытаться скрыть предварительный просмотр.

  • Правильно настройте предварительный просмотр
    • Используйте SurfaceView (до Android 4.0) или SurfaceTexture (Android 4+, можно сделать прозрачным)
    • Установите и инициализируйте его перед съемкой
    • Подождите, пока SurfaceView SurfaceHolder (через getHolder()) сообщит surfaceCreated() или TextureView, чтобы сообщить onSurfaceTextureAvailable его 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, чтобы свести к минимуму его видимость (или попробуйте 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; }
}

Ответ 5

В Android 4.0 и выше (уровень API >= 14) вы можете использовать TextureView для предварительного просмотра потока камеры и сделать его невидимым чтобы не показывать его пользователю. Вот как:

Сначала создайте класс для реализации SurfaceTextureListener, который получит обратные вызовы create/update для поверхности предварительного просмотра. Этот класс также принимает объект камеры как входной сигнал, так что он может вызвать функцию 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);

Ответ 6

Есть способ сделать это, но это несколько сложно. то, что должно быть сделано, прикрепляет поверхностного владельца к оконному менеджеру из службы

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 - это держатель, который вы получаете с поверхности.

Таким образом, вы можете играть с альфа-альфа-поверхностью, сделать его полностью прозрачным, но камера все равно будет получать кадры.

как я это делаю. надеюсь, что это поможет:)

Ответ 7

Мы решили эту проблему, используя фиктивный SurfaceView (не добавленный к фактическому графическому интерфейсу) в версиях ниже 3.0 (или, скажем, 4.0 как услуга камеры на планшете, на самом деле не имеет смысла). В версиях >= 4.0 это работало только в эмуляторе ( Здесь использовалось использование SurfaceTexture (и setSurfaceTexture()) вместо SurfaceView (и setSurfaceView()). По крайней мере, это работает на Nexus S.

Я думаю, что это действительно является недостатком платформы Android.

Ответ 8

В "Рабочем примере Сэма" (Спасибо, Сэм...)

if at istruction "wm.addView(предварительный просмотр, параметры);"

получить исключение "Невозможно добавить окно android.view.ViewRoot - разрешено для этого типа окна"

разрешить с помощью этого разрешения в AndroidManifest:

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

Ответ 9

Вы можете попробовать этот рабочий код. Этот сервис щелкнуть передним изображением, если вы хотите захватить снимок камеры, а затем раскомментировать кадр в коде и прокомментировать FrontCamera.

Примечание. - Разрешить использование камеры и хранилища для приложения и запускать службу из Activity или в любом месте.

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