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

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

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

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

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


person eyurdakul    schedule 05.03.2010    source источник
comment
См. Также Сделать снимок без предварительного просмотра Android   -  person Alex Cohn    schedule 09.10.2014
comment
github.com/kevalpatel2106/android-hidden-camera - ознакомьтесь с этой библиотекой, которая предоставляет фоновая камера.   -  person Keval Patel    schedule 09.12.2016


Ответы (9)


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

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

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

person Community    schedule 07.10.2010
comment
Есть одна загвоздка с поверхностью 1 × 1: это может быть медленным на некоторых устройствах (например, Samsung), которые не могут запустить конвертер изображений HW, когда целевой размер не делится на 4 (или, может быть, 8). - person Alex Cohn; 22.12.2012
comment
Также на некоторых устройствах он просто не работает, выдает исключение RuntimeException (старый Nexus One, по крайней мере, насколько мне известно) - person comodoro; 01.09.2013
comment
Я подозреваю, что Google сделал это намеренно, иначе я не могу представить, чтобы их архитекторы планировали подобный план. - person user1914692; 05.10.2013
comment
Не работает, если у вас есть только служба (например, вы хотите использовать камеру, когда приложение работает в фоновом режиме) - person mnl; 13.01.2014
comment
@mnl, у меня сработало в сервисе. Я добавил предварительный просмотр в оконный менеджер как системный оверлей. - person Sam; 23.11.2014
comment
@comodoro, что именно привело к сбою? Была ли проблема в том, что Nexus One вообще не поддерживает изменение размера предварительного просмотра? Или он просто не поддерживал определенные размеры? Вы меняли размер SurfaceHolder или SurfaceView? Какой код вы использовали для его изменения? - person Sam; 23.11.2014

Я нашел ответ на этот вопрос в Документах по камерам Android.

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

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

person Phillip Scott Givens    schedule 06.06.2012
comment
Вы следовали пошаговой инструкции? Я знаю, что это самый низкий рейтинг (неутешительный), но я следил за документацией, и он мне подходит. - person Phillip Scott Givens; 10.11.2012
comment
@ phillip-scott-givens: да, это работает, но не для захвата изображений, это вопрос subj. Вы можете пропустить настройку предварительного просмотра, только если вы используете MediaRecorder. - person Alex Cohn; 22.12.2012
comment
Ухг, я все еще теряю очки репутации на этом. КАК УДАЛИТЬ ЭТОТ ЗАПИСЬ? Если вы используете RTFM, ЭТО РАБОТАЕТ! Я клянусь. Это работает для видео. Это работает для картинок. Я разместил это, потому что нашел, и он работает. Сейчас, год спустя, меня все еще не голосуют. Я попробовал ссылку для удаления, и она просто спрашивает меня, хочу ли я проголосовать за удаление этого сообщения. Пожалуйста, не голосуйте больше за меня. Вместо этого ответьте здесь и расскажите, как удалить эту вещь. - person Phillip Scott Givens; 20.01.2013
comment
в основном вы не можете, но вы можете отметить это для внимания модератора: meta.stackexchange.com/questions/25088/ - person apanloco; 25.01.2013
comment
Я считаю этот ответ полезным - в моем приложении кажется, что лучше снять короткое видео, а не изображение. - person NumberFour; 18.10.2013
comment
это действительно интересно, большое спасибо. Попробую это. Не беспокойтесь о голосовании против! - person zipped; 11.12.2013
comment
Не могли бы вы помочь, как мне снять видео без предварительного просмотра? Я уже задавал вопрос здесь stackoverflow .com / questions / 33196518 /, но еще не ответили ... спасибо! - person AsfK; 26.10.2015

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

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

Обновление 21.09.11: По-видимому, это работает не на всех устройствах Android.

person Frank    schedule 21.03.2011
comment
вот в чем дело. Большое спасибо! - person eyurdakul; 22.03.2011
comment
Используйте SurfaceTexture и установите SurfaceTexture выше 4.0. - person HannahMitt; 16.03.2013
comment
Чтобы он работал на каждом устройстве, поверхность должна быть где-то добавлена ​​и фактически создана, лучше всего использовать обратные вызовы держателя. Обратные вызовы вызываются только в том случае, если вид виден и не имеет размера 0x0. setAlpha (0) кажется нормальным, но доступен только в API 11 и выше. - person 3c71; 17.08.2013
comment
Просто для подтверждения .. Не работает на Galaxy Nexus ни RuntimeException: takePicture failed - person Jakob; 13.07.2014
comment
@ 3c71, использование прозрачного PixelFormat и setAlpha(0) не сделало его прозрачным на моем Sony Xperia M под управлением Android 4.3. Превью все еще оставалось непрозрачным. - person Sam; 23.11.2014
comment
@ 3c71, на самом деле setAlpha(0) действительно работал после использования setFormat(PixelFormat.TRANSPARENT) на SurfaceHolder и использования того же PixelFormat в конструкторе SurfaceView. - person Sam; 23.11.2014
comment
Это возвращает черное растровое изображение! - person Muhammad Babar; 04.12.2014

Съемка фото

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

  • Correctly set up the preview
    • Use a SurfaceView (pre-Android-4.0 compatibility) or SurfaceTexture (Android 4+, can be made transparent)
    • Установите и инициализируйте его перед фотографированием
    • Подождите, пока SurfaceView SurfaceHolder (через getHolder()) сообщит surfaceCreated() или TextureView сообщит onSurfaceTextureAvailable своему SurfaceTextureListener перед настройкой и инициализацией предварительного просмотра.
  • Ensure the preview is visible:
    • Add it to the WindowManager
    • Убедитесь, что размер макета составляет не менее 1 x 1 пиксель (вы можете начать с MATCH_PARENT x MATCH_PARENT для тестирования).
    • Убедитесь, что его видимость равна View.VISIBLE (кажется, что это значение по умолчанию, если вы его не укажете)
    • Убедитесь, что вы используете FLAG_HARDWARE_ACCELERATED в LayoutParams, если это TextureView.
  • Используйте обратный вызов JPEG takePicture, поскольку в документации говорится, что другие обратные вызовы поддерживаются не на всех устройствах.

Исправление проблем

  • Если _18 _ / _ 19_ не вызывается, вероятно, _20 _ / _ 21_ не отображается.
  • Если 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; }
}
person Sam    schedule 22.11.2014
comment
Это, наконец, подробный ответ на эту тему с рабочим примером! Спасибо - person NumberFour; 25.12.2014
comment
Это решение больше не работает на уровне API ›= 23. Cannot add previewandroid.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@664dc37 -- permission denied for window type 2006 - person Ruchir Baronia; 08.07.2019
comment
@RuchirBaronia, вот несколько вариантов: 1. Используйте метод, используемый командой ML Kit 2. Переключитесь на camera2 API - person Sam; 08.07.2019

На 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);
person Varun Gulshan    schedule 09.01.2013
comment
ОП заявляет, что он собирается использовать это в Сервисе. Я также ищу способ сделать снимок из Сервиса. Размещая FrameLayout, вы имеете в виду его размещение в Activity, запускающей службу? Что, если Сервис вызывает это из фона? Всплывает ли Activity с прикрепленным FrameLayout? - person NumberFour; 18.10.2013
comment
@NumberFour: третий фрагмент кода не имеет отношения к вашему случаю. - person Alex Cohn; 13.11.2013
comment
Как я могу использовать этот код в сервисе ?? я могу позвонить в CamPreview из службы ?? - person someone; 17.07.2014
comment
работал у меня с небольшими изменениями в интерфейсе обратного вызова - person shashi2459; 08.02.2016

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

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, сделав его полностью прозрачным, но камера все равно будет получать кадры.

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

person Vlad    schedule 22.04.2012
comment
Не могли бы вы показать мне еще немного исходного кода? У меня это не сработало :( Вы также можете связаться со мной по электронной почте. - person Valelik; 05.12.2012
comment
Я получаю исключение с отказом в разрешении для wm.addView (surfaceView, params); - person Sathesh; 30.12.2012
comment
Как вы это преодолели? Не могли бы вы помочь? - person Sathesh; 30.12.2012
comment
я не встречал этого исключения: \ - person Vlad; 31.12.2012
comment
Я добавил это разрешение, и исключение исчезло. ‹Uses-permission android: name = android.permission.SYSTEM_ALERT_WINDOW /› - person Sathesh; 31.12.2012
comment
О, у меня было это разрешение для начала ... поэтому я не столкнулся с исключением :) - person Vlad; 03.01.2013
comment
Я также хотел бы увидеть еще немного исходного кода, я новичок в API камеры. Могу ли я получить mHolder от surfaceView.getHolder ()? Как создать SurfaceView? SurfaceView = new SufaceView (это) будет работать? Спасибо. - person NumberFour; 16.08.2013
comment
Вот что произошло, когда я попробовал это решение и вышел из приложения: i.imgur.com/g8Fmnj6.png - person BVB; 04.10.2013
comment
отличный пост @Vlad thx! Подскажите, пожалуйста, как сделать превью прозрачным? Я пытаюсь около часа и пока безуспешно. - person sswierczek; 30.10.2013
comment
У кого-нибудь получилось, что это работает на Nexus 5? Я добился успеха примерно на 5 других устройствах, но не могу заставить его работать на Nexus 5. - person Matt R; 14.12.2013
comment
@BVB Диспетчер окон не удаляет автоматически добавленные вами представления .. поэтому сохраните ссылку на Surfaceview и при выходе добавьте wm.removeView (surfaceview) - person Vlad; 18.12.2013
comment
Спасибо за WindowManager совет. Решение сделать вид поверхности прозрачным для меня не сработало. Есть еще идеи о том, как это сделать? - person Muzikant; 27.04.2014
comment
Действительно ли гарантировано, что превью камеры будет поддерживать прозрачный формат? Из того, что я прочитал, только формат, гарантированно работающий на всех телефонах, - ImageFormat.NV21. - person Sam; 23.11.2014

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

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

person mnl    schedule 20.04.2012

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

если в istruction "wm.addView (preview, params);"

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

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

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
person Giulio    schedule 08.01.2016

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

Примечание: - Разрешить разрешение камеры и хранилища для приложения и startService из 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();
            }
    }
}
person Kuldeep mourya    schedule 03.01.2018
comment
Проголосовали против, потому что вы буквально скопировали ответ из другого сообщения SO, изменили текст журнала и разместили его, как будто это ваш собственный ответ. stackoverflow.com/a/24027066/596841 - person pookie; 04.08.2020