Android Camera2 API desde el hervidor de agua



No hace mucho tiempo, estaba ocupado con mi máquina robótica, tratando de ponerle un teléfono inteligente ANDROID. Mi, es decir, su tarea, era hacer que el carro evolucionara progresivamente. Para que ella, por así decirlo, pudiera sentir el mundo con sus sensores, mirarlo con su ojo (cámara), escuchar con un micrófono y maldecir por el altavoz. Los recursos AVR, por supuesto, no fueron suficientes para esto, por lo que el microcontrolador que estaba en el carrito se movió a un nivel inferior, en algún lugar de la médula espinal para controlar motores y varios reflejos no condicionados.

Pero algo extraño, cuando comencé a escribir una aplicación para un teléfono inteligente, un ESTUDIO IDE ANDROID malo comenzó a tachar constantemente mi código y llamarlo obsoleto.

cámara = cámara. abierto ();

Especialmente, como puedes ver, en aquellas partes donde intenté trabajar con la cámara. Fue muy decepcionante porque leí en Internet y aprendí muchas lecciones sobre trabajar con Android y cámara aquí , aquí , aquí e incluso aquí . Nada fue tachado allí. Y se llamaba la API de la cámara. Todo allí era simple y lógico. Pero Google tercamente me empujó hacia algún tipo de API Camera2 .

Miré allí y simplemente me metí en problemas con la cantidad de diferentes devoluciones de llamada, constructores, controladores y loopers para cada línea del código de demostración. Era completamente incomprensible qué manera de abordar esto si eres un aficionado común, no un desarrollador de Android. Además, hay incluso algunos artículos sobre la API de Camera2 en la red hoy en día, aunque esta actualización parece ser como hace cuatro años. Pero todo lo que encontré fue un artículo en Hacker en 2016, una publicación en tres partes de hermanos ucranianos del mismo año, una publicación doble sobre Habré en 2017 y un artículo Understanding Camera2 del megáfono japonés Tomoaki Imai. Y esto también me refiero a cierta información estructurada y formalizada, y no fragmentos de código como "ver cómo puedo" y hojas en el estilo "mira el código pliz, nada funciona para mí" dispersos en Internet.

Y ahora, si todavía te preguntas por qué necesitaba cortar mi publicación sobre este tema
ya en 2019, luego bienvenidos a cat.

En realidad, me gustó el artículo del Hacker porque me dio al menos una visión general de por qué demonios se introdujo el nuevo paradigma de trabajar con la cámara. También quedó claro por qué se necesitaban algunos métodos nuevos que no estaban en la API anterior. Pero escribir un programa de trabajo para este artículo fue completamente imposible, porque los autores, al parecer, como verdaderos hackers, citaron solo los nombres de los métodos, bueno, tal vez todavía haya un par de líneas de código adicional. Pero esto categóricamente no fue suficiente para mí.

La publicación sobre Habré fue buena, no discuto, e incluso entendí los primeros párrafos, pero ese era el punto, porque el autor de la publicación derrotó a la cámara usando RxJava2, lo que automáticamente me excluyó de la cantidad de lectores adicionales. Normalmente trataría con OOP, pero aquí hay algún tipo de programación reactiva.

El artículo japonés era aún mejor, incluso si no escribía en su idioma nativo. Pero tampoco conozco a Kotlin, aunque, por supuesto, me alegré por los desarrolladores nacionales y aprecié la brevedad de su sintaxis, en contraste con el vermicelli JAVA que estaba en la página de desarrolladores de Google (por cierto, con esta página, también estaba claro para mí que claramente no mucho).

El último artículo que me trajo beneficios prácticos fue un post de tres partes de la fraternal Ucrania. Incluso lancé algo allí y vi algo allí. Pero desafortunadamente, en la tercera parte, el autor del artículo estaba muy cansado y también comenzó a emitir código en fragmentos que no cuadraban con mi conjunto de trabajo. Además, al final, el autor estaba completamente molesto, porque obtuvo la imagen de la paleta de colores incorrecta que estaba contando.



Digamos que esto se debe a que la versión de Lollipop es 5.0 y hay un error. Necesita actualizar a Lollipop 5.1 y luego todo estará bien. Pero de alguna manera todavía no. Y también los ucranianos dañinos se aferraron al artículo de JAVA SCRIPT, y al copiar el código se vertió una gran cantidad de basura en el texto del programa. Chicos, bueno, no pueden hacer eso ... Tuve que instalar un complemento especial en Firefox.

Por lo tanto, tuve que levantar el estandarte caído y terminar el trabajo hasta el final, lo que, por supuesto, requirió un esfuerzo mental titánico. Y dado que de alguna manera fue una pena estar satisfecho con solo darme cuenta de que se obtuvo un resultado de trabajo, quería compartirlo con teteras aficionados. Además, esto está lejos del final de la historia. Todavía necesito aprender cómo transferir video en vivo desde la cámara de Android a la computadora (el robot robótico debe desarrollarse, esta es la ley de la evolución) y no en pedazos, como lo cegué, sino sin problemas. Y existen tales obstáculos de Mont Blanc en forma de códecs de medios y otras cosas que simplemente son de hojalata.

Pero, por otro lado, todo puede terminar en completa decepción. Y no porque no puedan escalar el Mont Blanc.

Y porque ahora los fabricantes de teléfonos inteligentes están desarrollando enfoques completamente nuevos para la fabricación de cámaras.
Hace un mes, se filtró un rumor sobre los teléfonos inteligentes Nokia con cinco cámaras principales. ¿Cómo relacionarse con esto? ¿Un rumor interesante y prometedor u otra cosa extraña? No importa cuán único pueda parecer ese diseño, Nokia ciertamente no podrá convertirse en pionero en la introducción de una cantidad inusual de lentes y sensores en dispositivos compactos. La cámara Light L16 ya estaba equipada con 16 lentes en 2015, y la compañía, obviamente, tiene un nuevo prototipo en funcionamiento. Arriba puede ver cómo podría verse potencialmente.

Después de que apareciera la cámara triple en el Huawei P20 Pro, la transición a un teléfono inteligente con cinco cámaras ya no suena tan cómica como podría haber sido hace un par de años. Sin embargo, la pregunta principal sigue siendo: ¿cuál es el punto?
¿Qué hacer con tantas lentes?

Lo primero que viene a la mente es la variedad de tipos de sensores de cámara disponibles en el mercado moderno de teléfonos inteligentes y la capacidad de agregar más. ¿Por qué elegir entre gran angular, telefoto, retrato con bokeh o monocromo, si puede obtener todo esto en un solo dispositivo?

Siendo teóricamente posible, tal diseño sería bastante incómodo de usar. El software tendrá que cambiar entre modos automáticamente u ofrecer un conjunto complejo de opciones para el usuario. Además, el desarrollo de dicho diseño sería muy costoso por todas las dudosas ventajas de tal solución. Cada cámara funcionaría en su mayor parte de forma independiente, y era poco probable que los compradores usaran una gran cantidad de modos. Y no está claro cuánto estarían dispuestos a pagar por dicha funcionalidad. Por lo tanto, las cámaras con múltiples módulos deberían poder hacer más para atraer al usuario.

Huawei P20 Pro ya ofrece su versión de cómo varios módulos de cámara pueden trabajar juntos para dar un resultado interesante. Estamos hablando de tecnologías de Huawei, como el Monocromo y el Zoom Híbrido. El primero mejora el rango dinámico de cuadros estándar combinando datos RGB regulares con un sensor fotosensible en blanco y negro. Y Zoom híbrido promete aún más: combina datos de múltiples cámaras para aumentar la resolución de la imagen para un mejor zoom. Como resultado, en el P20 Pro 8 MP, el sensor de teleobjetivo le permite disparar con una resolución de 10 MP con zoom de 3x y 5x.
Mayor resolución - Más flexibilidad

La primera cámara Light L16 funcionó de manera similar, utilizando espejos de periscopio para ajustar los módulos de la cámara en un cuerpo compacto. La cámara tomó datos de varios módulos a 28, 70 y 150 mm, dependiendo del nivel de zoom. El resultado fue una gran toma de 52 MP tomada desde 10 ángulos ligeramente diferentes disponibles en niveles de aumento de hasta 5x. El concepto del nuevo modelo, desarrollado para teléfonos inteligentes, funciona con 5-9 lentes. Tal módulo de cámara es capaz de tomar grandes fotografías de 64 megapíxeles.

Esta idea de disparos múltiples también agrega beneficios al disparar con poca luz y en HDR con múltiples aperturas. El efecto de alta calidad de la profundidad del marco también está disponible debido a la emulación de software simultánea y al uso de varias distancias focales.

Light L16 trajo decepción, pero la idea en sí era prometedora. Y la próxima generación con éxito puede ser algo que valga la pena. La compañía afirma que se anunciará un teléfono inteligente a finales de año, donde se instalará su última solución con múltiples lentes.

La misma idea se remonta a la experiencia de Nokia en la implementación de múltiples cámaras, dada la vieja historia de invertir en Pelican Imaging en 2013. A diferencia de Light, el sensor aquí es mucho más pequeño. Y aun así, la tecnología promete ventajas muy similares, incluido el cambio de enfoque del software, la medición de profundidad y el aumento del tamaño de la imagen final. Desafortunadamente, Tessera compró la compañía en 2016, pero la idea en sí misma podría no haber abandonado las mentes de los ingenieros de Nokia.

Zeiss, el actual socio de fotografía de Nokia, tiene una patente para el zoom conmutable, pero no hemos escuchado nada de ellos sobre el diseño de lentes múltiples. Quizás el otro socio de Nokia, FIH Mobile, parece más prometedor. Esta compañía es propiedad de Foxconn, lanza teléfonos Nokia y también invirtió en Light en 2015, otorgándole una licencia para usar la tecnología.

Y si crees que la fuga de Nokia y el prototipo de Light tienen algo en común, no es casualidad. Conecta a las dos compañías de Foxconn. Entonces, ¿el teléfono inteligente Nokia será el primero en usar la tecnología de Light?
Entonces, ¿es este el futuro?

La resolución ultra alta no es un concepto nuevo. En 2014, Oppo Find 7 utilizó un principio similar, y el Zoom Híbrido de Huawei permitió que la tecnología funcionara con múltiples cámaras. Históricamente, el principal problema de la tecnología han sido los requisitos de alto rendimiento, la calidad del algoritmo y el consumo de energía. Pero del lado de los teléfonos inteligentes modernos, procesadores de procesamiento de señales más potentes, chips DSP eficientes en energía e incluso capacidades mejoradas de redes neuronales, lo que gradualmente reduce la importancia del problema.

Los altos detalles, las capacidades de zoom óptico y un efecto bokeh personalizado encabezan la lista de requisitos de la cámara para un teléfono inteligente moderno, y la tecnología de múltiples cámaras puede ayudar a lograr esto. En lugar de realizar diferentes funciones con cámaras separadas, el futuro de la fotografía móvil es combinar varias cámaras para proporcionar capacidades más avanzadas y flexibles.

Todavía hay preguntas sobre la tecnología Light, especialmente sobre el pegado de imágenes con diferentes distancias focales. Solo podemos esperar: veremos qué cambiará para mejor en la segunda generación de tecnología.



No será fácil configurar tal ensamblaje desde cámaras con asas en el programa. Tal vez el círculo se cerrará y, para tomar una foto, nuevamente tendremos que escribir una intención explícita, con el contenido, como "cámara, tome la foto usted mismo como pueda, pero hermosamente, y devuélvamela en mi actividad".

Pero por ahora, aunque todavía nos queda un poco. Por lo tanto, procedemos de inmediato.

¿Por qué necesitaba todo este google?

Parece que todo es multiproceso seguro y adecuado (y también es posible hacer todo tipo de efectos y filtros directamente). Ahora, si en Java vainilla, si es necesario, necesita empujar mutexes, sincronizaciones y semáforos de manera competente en todas partes, entonces aquí Google se hace cargo de casi todo. Solo necesita registrar devoluciones de llamada en el texto del programa, que se llamará si es necesario. Es decir, por ejemplo, envía una solicitud para encender la cámara:

mCameraManager.openCamera() 

Pero este no es un equipo, es una solicitud. No puede comenzar a trabajar de inmediato con la cámara. En primer lugar, necesita tiempo para encenderse, y en segundo lugar, el androide tiene muchas cosas importantes que hacer, y su deseo se pone en una fila bastante grande. Pero luego no tenemos que esperar a que la cámara se abra en un bucle en el hilo principal y cuelgue toda la aplicación (aún recordamos qué se puede hacer en la transmisión de la interfaz de usuario y qué no). Por lo tanto, enviamos nuestro deseo y mientras nos ocupamos de nuestro negocio, abrimos las vistas, escribimos "hola mundo", configuramos controladores de botones y similares.

Mientras tanto, después de unas decenas y cientos de milisegundos, el sistema operativo finalmente llega a las manos de la cámara y lo inicializa. Y tan pronto como la cámara esté lista, se activa la misma devolución de llamada (a menos, por supuesto, que la haya registrado por adelantado)

  private CameraDevice.StateCallback mCameraCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; createCameraPreviewSession(); ….. } 

Es decir, la cámara ahora está abierta y puede hacer algo allí: mostrar la imagen de la cámara en la vista, reenviarla aún más para guardarla, etc.

Como resultado, todo el trabajo con la cámara, de hecho, se reduce a prescribir todo tipo de devoluciones de llamada. Pero en su noble deseo, los desarrolladores de Google fueron un poco demasiado lejos y, como señaló correctamente el camarada japonés:
Una de las razones por las que Camera2 está perplejo es la cantidad de devoluciones de llamada que debe usar para tomar una foto.

Pero esto no es suficiente. El esquema completo de la cámara tiene este aspecto increíble.



Pero afortunadamente, para empezar, se puede reducir a una imagen mucho más atractiva.



Aunque todo está en inglés allí, pero como dicen, está claro sin palabras. Para empezar, tenemos suficiente de este diseño. Abra la cámara, muestre la imagen en la pantalla del teléfono inteligente, tome una foto y tan pronto como esté lista (devoluciones de llamada), el oyente de este evento trabajará y escribirá la imagen en un archivo.

Comenzando a crear código

Crearemos un nuevo proyecto en el IDE de Android Studio, seleccionaremos la versión mínima del SDK 22 para evitar imágenes verdes y pediremos la Actividad vacía (o mejor aún, tome la versión 23, de lo contrario pueden ocurrir problemas con los permisos). Suficiente para empezar. Incluso los permisos en el manifiesto no necesitan hacerse todavía.

Comenzamos creando una instancia de la clase CameraManager. Este es un administrador de servicios del sistema que le permite encontrar cámaras disponibles, obtener las características que necesita para trabajar y establecer configuraciones de disparo para las cámaras.

Y veremos las siguientes características:

identificador de cámara (0, 1, 2 ....)
dirección donde se dirige la cámara (hacia adelante, hacia atrás)
resolución de la cámara

Primero, obtenemos la lista de cámaras en forma de un conjunto de cadenas, y luego imprimimos las características requeridas en un bucle y las escribimos en el registro.

 package com.example.camera; import android.content.Context; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import android.graphics.ImageFormat; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraManager; import android.hardware.camera2.params.StreamConfigurationMap; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.util.Size; public class MainActivity extends AppCompatActivity { public static final String LOG_TAG = "myLogs"; String[] myCameras = null; private CameraManager mCameraManager = null; @RequiresApi(api = Build.VERSION_CODES.M) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try{ //      myCameras = new String[mCameraManager.getCameraIdList().length]; //     for (String cameraID : mCameraManager.getCameraIdList()) { Log.i(LOG_TAG, "cameraID: "+cameraID); int id = Integer.parseInt(cameraID); // e   CameraCharacteristics cc = mCameraManager.getCameraCharacteristics(cameraID); //    ,    StreamConfigurationMap configurationMap = cc.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); //      int Faceing = cc.get(CameraCharacteristics.LENS_FACING); if (Faceing == CameraCharacteristics.LENS_FACING_FRONT) { Log.i(LOG_TAG,"Camera with ID: "+cameraID + " is FRONT CAMERA "); } if (Faceing == CameraCharacteristics.LENS_FACING_BACK) { Log.i(LOG_TAG,"Camera with: ID "+cameraID + " is BACK CAMERA "); } //        jpeg Size[] sizesJPEG = configurationMap.getOutputSizes(ImageFormat.JPEG); if (sizesJPEG != null) { for (Size item:sizesJPEG) { Log.i(LOG_TAG, "w:"+item.getWidth()+" h:"+item.getHeight()); } } else { Log.i(LOG_TAG, "camera don`t support JPEG"); } } } catch(CameraAccessException e){ Log.e(LOG_TAG, e.getMessage()); e.printStackTrace(); } } } 

El registro obtendrá algo como esto:
 2019-09-13 10:56:31.489 11130-11130/? I/myLogs: cameraID: 0 2019-09-13 10:56:31.504 11130-11130/? I/myLogs: Camera with: ID 0 is BACK CAMERA 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:4000 h:3000 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:4000 h:2250 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:3840 h:2160 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:2592 h:1944 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:2592 h:1940 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:2048 h:1536 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1920 h:1080 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1600 h:1200 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1440 h:1080 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1440 h:720 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1280 h:960 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1280 h:768 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1280 h:720 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1280 h:480 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1280 h:400 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:800 h:480 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:720 h:480 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:640 h:480 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:480 h:640 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:480 h:360 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:480 h:320 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:352 h:288 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:320 h:240 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:240 h:320 2019-09-13 10:56:31.507 11130-11130/? I/myLogs: w:176 h:144 2019-09-13 10:56:31.507 11130-11130/? I/myLogs: w:144 h:176 2019-09-13 10:56:31.507 11130-11130/? I/myLogs: cameraID: 1 2019-09-13 10:56:31.514 11130-11130/? I/myLogs: Camera with ID: 1 is FRONT CAMERA 2019-09-13 10:56:31.515 11130-11130/? I/myLogs: w:4224 h:3136 2019-09-13 10:56:31.515 11130-11130/? I/myLogs: w:4224 h:2376 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:4160 h:3120 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:4160 h:2340 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:4000 h:3000 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:4000 h:2250 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:3840 h:2160 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:2592 h:1944 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:2592 h:1940 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:2048 h:1536 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1920 h:1080 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1600 h:1200 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1440 h:1080 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1440 h:720 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1280 h:960 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1280 h:768 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1280 h:720 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1280 h:480 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1280 h:400 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:800 h:480 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:720 h:480 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:640 h:480 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:480 h:640 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:480 h:360 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:480 h:320 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:352 h:288 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:320 h:240 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:240 h:320 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:176 h:144 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:144 h:176 


Nosotros, en principio, realmente no necesitamos esta información, cualquier tetera incluso de la API anterior ya sabe que las cámaras tienen identificadores de cero y más allá, que no sorprenderá a nadie con una resolución de 1920 por 1080, y aún más con el formato JPEG. De hecho, estos datos ya son necesarios para una aplicación "adulta" lista para la producción y que, en base a esto, hará los menús de elección para el usuario, etc. En nuestro caso más simple, en general, todo está claro. Pero como todos los artículos comienzan con esto, entonces comenzaremos.

Después de asegurarnos de que la cámara frontal tenga el número de identificación "1" y la parte trasera "0" (por alguna razón están especificadas en el formato de Cadena), y también que la resolución de 1920 x 1080 y guardar el archivo JPG estén disponibles, continuaremos el ataque.

Obtenemos los permisos necesarios

Inicialmente, debemos preocuparnos por una serie de permisos. Para hacer esto, deberá escribir lo siguiente en el manifiesto:

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

El primero es comprensible para la cámara, el segundo es para escribir imágenes en un archivo (y esto no es una tarjeta de memoria externa, como podría parecer por el significado de la palabra EXTERNO, sino una memoria de teléfono inteligente bastante nativa)

Pero Android también se preocupa por nosotros, por lo tanto, a partir de la versión Lollipop, los permisos especificados en el manifiesto ya no serán suficientes. Ahora se requiere que los usuarios aprueben su consentimiento para abrir la cámara y escribir datos en la memoria.

Para hacer esto, en el caso más simple, debe agregar esto:

 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ……... if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) ) { requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); } ….. 

¿Por qué en lo más simple? Porque no es necesario hacer tales cosas en una transmisión de IU. En primer lugar, el hilo se cuelga mientras el usuario asoma la pantalla con sus dedos torpes y, en segundo lugar, si tiene la cámara aún más inicializada, la aplicación generalmente puede fallar. En este caso de demostración, todo está bien, pero generalmente se prescribe para usar el tipo de devolución de llamada deseado para este caso:

 @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == MY_REQUEST_CODE_FOR_CAMERA) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { startCameraActivity(); //     (  ) } } } 

Aunque anteriormente no sabía todo esto, inicié la actividad deseada a través de AsyncTask, e incluso antes esculpí un nuevo hilo, como en Java.

Cocinando la camara

Por conveniencia, eliminaremos todo lo relacionado con las cámaras en una clase separada por consejo de personas inteligentes y crearemos la clase CameraService. Allí colocaremos la inicialización de las cámaras y luego anotaremos todas las devoluciones de llamada necesarias.

 ….. CameraService[] myCameras = null; private CameraManager mCameraManager = null; private final int CAMERA1 = 0; private final int CAMERA2 = 1; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ….. } public class CameraService { private String mCameraID; private CameraDevice mCameraDevice = null; private CameraCaptureSession mCaptureSession; public CameraService(CameraManager cameraManager, String cameraID) { mCameraManager = cameraManager; mCameraID = cameraID; } public boolean isOpen() { if (mCameraDevice == null) { return false; } else { return true; } } public void openCamera() { try { if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { mCameraManager.openCamera(mCameraID,mCameraCallback,null); } } catch (CameraAccessException e) { Log.i(LOG_TAG,e.getMessage()); } } public void closeCamera() { if (mCameraDevice != null) { mCameraDevice.close(); mCameraDevice = null; } } } 

En el hilo principal, cree una instancia de mCameraManager y úsela para completar la matriz de objetos myCameras. En este caso, solo hay dos: la cámara frontal y la cámara autofoto.

  mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try{ //      myCameras = new CameraService[mCameraManager.getCameraIdList().length]; for (String cameraID : mCameraManager.getCameraIdList()) { Log.i(LOG_TAG, "cameraID: "+cameraID); int id = Integer.parseInt(cameraID); //     myCameras[id] = new CameraService(mCameraManager,cameraID); } } catch(CameraAccessException e){ Log.e(LOG_TAG, e.getMessage()); e.printStackTrace(); } 

En el método público void openCamera (), puede ver la línea:

  mCameraManager.openCamera(mCameraID,mCameraCallback,null); 

es a partir de aquí que la ruta conduce a la primera devolución de llamada del estado de la cámara del CameraDevice. StateCallback. Nos dirá si la cámara está abierta, cerrada o tal vez no haya nada allí y dará un error. Lo escribiremos en los métodos de la clase CameraService.

  private CameraDevice.StateCallback mCameraCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; Log.i(LOG_TAG, "Open camera with id:"+mCameraDevice.getId()); createCameraPreviewSession(); } @Override public void onDisconnected(CameraDevice camera) { mCameraDevice.close(); Log.i(LOG_TAG, "disconnect camera with id:"+mCameraDevice.getId()); mCameraDevice = null; } @Override public void onError(CameraDevice camera, int error) { Log.i(LOG_TAG, "error! camera id:"+camera.getId()+" error:"+error); } }; 

En consecuencia, si la cámara está disponible para funcionar (el método public void onOpened (CameraDevice camera) {} ha funcionado), entonces escribimos nuestras acciones adicionales allí, por ejemplo, llamando al método createCameraPreviewSession (). Ayudará a traernos la imagen de la cámara a la vista y trabajar con ella aún más.

CreateCameraPreviewSession

Aquí estamos tratando de mostrar una imagen (flujo de datos) en la textura mImageView, que ya está definida en el diseño. Incluso puede determinar con qué resolución en los píxeles.

  private void createCameraPreviewSession() { SurfaceTexture texture = mImageView.getSurfaceTexture(); // texture.setDefaultBufferSize(1920,1080); Surface surface = new Surface(texture); try { final CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(surface); mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { mCaptureSession = session; try { mCaptureSession.setRepeatingRequest(builder.build(),null,null); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession session) { }}, null ); } catch (CameraAccessException e) { e.printStackTrace(); } } 

Y cuando esta misma sesión está lista, se llama la devolución de llamada antes mencionada y comenzamos con la expresión de los buscadores de Google: "mostrar la vista previa de la cámara". Aquí, aquellos que lo deseen pueden ajustar la configuración de enfoque automático y flash, pero por ahora nos las arreglaremos con la configuración predeterminada.

Crea un diseño

Ahora necesitamos, por así decirlo, dibujar los colores en el lienzo y crear una imagen brillante al estilo de
"Tres botones de pantalla y una vista".
 <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextureView android:id="@+id/textureView" android:layout_width="356dp" android:layout_height="410dp" android:layout_marginTop="32dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.49" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <LinearLayout android:layout_width="292dp" android:layout_height="145dp" android:layout_marginStart="16dp" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textureView" app:layout_constraintVertical_bias="0.537"> <Button android:id="@+id/button4" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> <Button android:id="@+id/button5" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> <Button android:id="@+id/button6" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> </LinearLayout> </androidx.constr 



El proceso es bastante trivial y cualquiera puede darse la vuelta aquí como quiera. Pero escribir los nombres de los botones directamente en el diseño también es de mala educación y, por lo tanto, en las versiones de trabajo no es necesario hacerlo.

En consecuencia, en la Actividad en sí, creamos oyentes, es decir, oyentes para botones y vistas.

  private Button mButtonOpenCamera1 = null; private Button mButtonOpenCamera2 = null; private Button mButtonToMakeShot = null; private TextureView mImageView = null; @RequiresApi(api = Build.VERSION_CODES.M) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ……... mButtonOpenCamera1 = findViewById(R.id.button1); mButtonOpenCamera2 = findViewById(R.id.button2); mButtonToMakeShot =findViewById(R.id.button3); mImageView = findViewById(R.id.textureView); mButtonOpenCamera1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].closeCamera(); if (myCameras[CAMERA1] != null) { if (!myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].openCamera(); } } }); mButtonOpenCamera2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].closeCamera(); if (myCameras[CAMERA2] != null) { if (!myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].openCamera(); } } }); mButtonToMakeShot.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //    } }); …….. 

Las asignaciones de botones son claras a partir de los nombres, dejaremos el tercer botón para la imagen futura.

Y si ahora reúnes todas las piezas de código, entonces

obtén lo siguiente:
 package com.example.camera; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.Button; import java.util.Arrays; public class MainActivity extends AppCompatActivity { public static final String LOG_TAG = "myLogs"; CameraService[] myCameras = null; private CameraManager mCameraManager = null; private final int CAMERA1 = 0; private final int CAMERA2 = 1; private Button mButtonOpenCamera1 = null; private Button mButtonOpenCamera2 = null; private Button mButtonToMakeShot = null; private TextureView mImageView = null; @RequiresApi(api = Build.VERSION_CODES.M) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(LOG_TAG, " "); if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) ) { requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE},1); } mButtonOpenCamera1 = findViewById(R.id.button1); mButtonOpenCamera2 = findViewById(R.id.button2); mButtonToMakeShot =findViewById(R.id.button3); mImageView = findViewById(R.id.textureView); mButtonOpenCamera1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA2].isOpen()) {myCameras[CAMERA2].closeCamera();} if (myCameras[CAMERA1] != null) { if (!myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].openCamera(); } } }); mButtonOpenCamera2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA1].isOpen()) {myCameras[CAMERA1].closeCamera();} if (myCameras[CAMERA2] != null) { if (!myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].openCamera(); } } }); mButtonToMakeShot.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // if (myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].makePhoto(); // if (myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].makePhoto(); } }); mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try{ //      myCameras = new CameraService[mCameraManager.getCameraIdList().length]; for (String cameraID : mCameraManager.getCameraIdList()) { Log.i(LOG_TAG, "cameraID: "+cameraID); int id = Integer.parseInt(cameraID); //     myCameras[id] = new CameraService(mCameraManager,cameraID); } } catch(CameraAccessException e){ Log.e(LOG_TAG, e.getMessage()); e.printStackTrace(); } } public class CameraService { private String mCameraID; private CameraDevice mCameraDevice = null; private CameraCaptureSession mCaptureSession; public CameraService(CameraManager cameraManager, String cameraID) { mCameraManager = cameraManager; mCameraID = cameraID; } private CameraDevice.StateCallback mCameraCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; Log.i(LOG_TAG, "Open camera with id:"+mCameraDevice.getId()); createCameraPreviewSession(); } @Override public void onDisconnected(CameraDevice camera) { mCameraDevice.close(); Log.i(LOG_TAG, "disconnect camera with id:"+mCameraDevice.getId()); mCameraDevice = null; } @Override public void onError(CameraDevice camera, int error) { Log.i(LOG_TAG, "error! camera id:"+camera.getId()+" error:"+error); } }; private void createCameraPreviewSession() { SurfaceTexture texture = mImageView.getSurfaceTexture(); texture.setDefaultBufferSize(1920,1080); Surface surface = new Surface(texture); try { final CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(surface); mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { mCaptureSession = session; try { mCaptureSession.setRepeatingRequest(builder.build(),null,null); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession session) { }}, null ); } catch (CameraAccessException e) { e.printStackTrace(); } } public boolean isOpen() { if (mCameraDevice == null) { return false; } else { return true; } } public void openCamera() { try { if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { mCameraManager.openCamera(mCameraID,mCameraCallback,null); } } catch (CameraAccessException e) { Log.i(LOG_TAG,e.getMessage()); } } public void closeCamera() { if (mCameraDevice != null) { mCameraDevice.close(); mCameraDevice = null; } } } @Override public void onPause() { if(myCameras[CAMERA1].isOpen()){myCameras[CAMERA1].closeCamera();} if(myCameras[CAMERA2].isOpen()){myCameras[CAMERA2].closeCamera();} super.onPause(); } } 


¡Cargamos, comenzamos, trabajamos!

En general, si esto no es suficiente para usted, el proceso de disparo en sí puede ser muy diversificado. Nuestro japonés Tomoaki muestra y explica cómo, trayendo un hermoso diagrama.



Primero, la cámara necesita enfocar. Esto generalmente no causa problemas, pero a veces requiere varios intentos, que también se implementan a través de una devolución de llamada.

 CameraCaptureSession.StateCallback(). 

Luego la cámara ingresa al modo de previsualización PRECAPTURE. En esta etapa, la cámara calcula la exposición, el balance de blancos y la apertura (solía saber qué era en la infancia, pero ahora este conocimiento se pierde). A veces, una devolución de llamada puede devolver una solicitud CONTROL_AE_STATE_FLASH_REQUIRED, lo que significa "sería bueno encender el flash". Por cierto, se puede activar automáticamente: setAutoFlash (mPreviewRequestBuilder).

Cuando se definen todos los parámetros necesarios para disparar, la devolución de llamada devuelve el estado CONTROL_AE_STATE_CONVERGED que nos indica que la cámara está lista para tomar una foto.

En la página googloid, todo esto ya está en los ejemplos, y si tienes la paciencia para atravesar estos campos minados y cercas de alambre, entonces el honor es para ti.

Tome una foto y guárdela en un archivo

Aquí es exactamente donde comencé a tener problemas. , - ( , ) . , . CamerCaptureSession Surface, ImageReader.

, ImageReader . OnImageAvailableListener. , ImageSaver , ImageSaver Runnable.

, ImageReader, CameraCaptureSession.StateCallback() . Android . ( ) createCameraPreviewSession(), .

:

 private void createCameraPreviewSession() { SurfaceTexture texture = mImageView.getSurfaceTexture(); Surface surface = new Surface(texture); try { final CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(surface); mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() ……. 

:

  private ImageReader mImageReader; private void createCameraPreviewSession() { mImageReader = ImageReader.newInstance(1920,1080,ImageFormat.JPEG,1); mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, null); SurfaceTexture texture = mImageView.getSurfaceTexture(); Surface surface = new Surface(texture); try { final CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(surface); mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() …… 

La diferencia, aparte de la definición de instancia de ImageReader en la parte superior, es casi difícil de alcanzar. Recién agregado a la superficie, mImageReader.getSurface () separado por comas y listo. Pero hasta que lo consigas ...

Desde ese momento las cosas se volvieron más divertidas y podrías usar el tercer botón de pantalla "Tomar una foto". Cuando se presiona, se llama al método makePhoto () (bueno, quién habría pensado):

  mButtonToMakeShot.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].makePhoto(); if (myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].makePhoto(); } }); …… public class CameraService { public void makePhoto (){ try { // This is the CaptureRequest.Builder that we use to take a picture. final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(mImageReader.getSurface()); CameraCaptureSession.CaptureCallback CaptureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { } }; mCaptureSession.stopRepeating(); mCaptureSession.abortCaptures(); mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null); } catch (CameraAccessException e) { e.printStackTrace(); } } 

E inmediatamente después escribimos el oyente OnImageAvailableListener:

  private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { { Toast.makeText(MainActivity.this,"   ", Toast.LENGTH_SHORT).show();} } }; 

Si bien no está haciendo nada, simplemente señala con un brindis que dicen que todo está en orden, puede guardar la imagen en un archivo.

Y para esto necesitamos el archivo en sí:

 public class CameraService { private File mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "test1.jpg");; 

ImageSaver, , .

Runnable. MainActivity:

  private static class ImageSaver implements Runnable { private final File mFile; ImageSaver(Image image, File file) { mImage = image; mFile = file; } @Override public void run() { ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); FileOutputStream output = null; try { output = new FileOutputStream(mFile); output.write(bytes); } catch (IOException e) { e.printStackTrace(); } finally { mImage.close(); if (null != output) { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } } } } 

, OnImageAvailableListener :

 mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile)); 

WAIT! OH, SHIT!!

mBackgroundHandler??? .

— ? , BackgroundHandler BackgroundThread, . Activity :

  private HandlerThread mBackgroundThread; private Handler mBackgroundHandler = null; private void startBackgroundThread() { mBackgroundThread = new HandlerThread("CameraBackground"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } private void stopBackgroundThread() { mBackgroundThread.quitSafely(); try { mBackgroundThread.join(); mBackgroundThread = null; mBackgroundHandler = null; } catch (InterruptedException e) { e.printStackTrace(); } } 

, BackgroundThread :

  public void onPause() { super.onPause(); stopBackgroundThread(); } @Override public void onResume() { super.onResume(); startBackgroundThread(); } 

mBackgroundHandler, , handler , , null.

, , . . onPause() onResume(). - .

. . , .



- , .

 package com.example.camera; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.ImageFormat; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.TotalCaptureResult; import android.media.Image; import android.media.ImageReader; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.Button; import android.widget.Toast; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; public class MainActivity extends AppCompatActivity { public static final String LOG_TAG = "myLogs"; CameraService[] myCameras = null; private CameraManager mCameraManager = null; private final int CAMERA1 = 0; private final int CAMERA2 = 1; private Button mButtonOpenCamera1 = null; private Button mButtonOpenCamera2 = null; private Button mButtonToMakeShot = null; private TextureView mImageView = null; private HandlerThread mBackgroundThread; private Handler mBackgroundHandler = null; private void startBackgroundThread() { mBackgroundThread = new HandlerThread("CameraBackground"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } private void stopBackgroundThread() { mBackgroundThread.quitSafely(); try { mBackgroundThread.join(); mBackgroundThread = null; mBackgroundHandler = null; } catch (InterruptedException e) { e.printStackTrace(); } } @RequiresApi(api = Build.VERSION_CODES.M) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(LOG_TAG, " "); if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) ) { requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE},1); } mButtonOpenCamera1 = findViewById(R.id.button1); mButtonOpenCamera2 = findViewById(R.id.button2); mButtonToMakeShot =findViewById(R.id.button3); mImageView = findViewById(R.id.textureView); mButtonOpenCamera1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA2].isOpen()) {myCameras[CAMERA2].closeCamera();} if (myCameras[CAMERA1] != null) { if (!myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].openCamera(); } } }); mButtonOpenCamera2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA1].isOpen()) {myCameras[CAMERA1].closeCamera();} if (myCameras[CAMERA2] != null) { if (!myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].openCamera(); } } }); mButtonToMakeShot.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].makePhoto(); if (myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].makePhoto(); } }); mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try{ //      myCameras = new CameraService[mCameraManager.getCameraIdList().length]; for (String cameraID : mCameraManager.getCameraIdList()) { Log.i(LOG_TAG, "cameraID: "+cameraID); int id = Integer.parseInt(cameraID); //     myCameras[id] = new CameraService(mCameraManager,cameraID); } } catch(CameraAccessException e){ Log.e(LOG_TAG, e.getMessage()); e.printStackTrace(); } } public class CameraService { private File mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "test1.jpg");; private String mCameraID; private CameraDevice mCameraDevice = null; private CameraCaptureSession mCaptureSession; private ImageReader mImageReader; public CameraService(CameraManager cameraManager, String cameraID) { mCameraManager = cameraManager; mCameraID = cameraID; } public void makePhoto (){ try { // This is the CaptureRequest.Builder that we use to take a picture. final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(mImageReader.getSurface()); CameraCaptureSession.CaptureCallback CaptureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { } }; mCaptureSession.stopRepeating(); mCaptureSession.abortCaptures(); mCaptureSession.capture(captureBuilder.build(), CaptureCallback, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile)); } }; private CameraDevice.StateCallback mCameraCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; Log.i(LOG_TAG, "Open camera with id:"+mCameraDevice.getId()); createCameraPreviewSession(); } @Override public void onDisconnected(CameraDevice camera) { mCameraDevice.close(); Log.i(LOG_TAG, "disconnect camera with id:"+mCameraDevice.getId()); mCameraDevice = null; } @Override public void onError(CameraDevice camera, int error) { Log.i(LOG_TAG, "error! camera id:"+camera.getId()+" error:"+error); } }; private void createCameraPreviewSession() { mImageReader = ImageReader.newInstance(1920,1080, ImageFormat.JPEG,1); mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, null); SurfaceTexture texture = mImageView.getSurfaceTexture(); texture.setDefaultBufferSize(1920,1080); Surface surface = new Surface(texture); try { final CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(surface); mCameraDevice.createCaptureSession(Arrays.asList(surface,mImageReader.getSurface()), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { mCaptureSession = session; try { mCaptureSession.setRepeatingRequest(builder.build(),null,mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession session) { }}, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } public boolean isOpen() { if (mCameraDevice == null) { return false; } else { return true; } } public void openCamera() { try { if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { mCameraManager.openCamera(mCameraID,mCameraCallback,mBackgroundHandler); } } catch (CameraAccessException e) { Log.i(LOG_TAG,e.getMessage()); } } public void closeCamera() { if (mCameraDevice != null) { mCameraDevice.close(); mCameraDevice = null; } } } @Override public void onPause() { if(myCameras[CAMERA1].isOpen()){myCameras[CAMERA1].closeCamera();} if(myCameras[CAMERA2].isOpen()){myCameras[CAMERA2].closeCamera();} stopBackgroundThread(); super.onPause(); } @Override public void onResume() { super.onResume(); startBackgroundThread(); } private static class ImageSaver implements Runnable { /** * The JPEG image */ private final Image mImage; /** * The file we save the image into. */ private final File mFile; ImageSaver(Image image, File file) { mImage = image; mFile = file; } @Override public void run() { ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); FileOutputStream output = null; try { output = new FileOutputStream(mFile); output.write(bytes); } catch (IOException e) { e.printStackTrace(); } finally { mImage.close(); if (null != output) { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } } } } } 

Source: https://habr.com/ru/post/468083/


All Articles