API Camera2 do Android da chaleira



Há pouco tempo, eu estava ocupado com minha máquina robótica, tentando colocar um smartphone ANDROID nela. Minha tarefa, ou seja, a tarefa dele era fazer o carro evoluir evolutivamente. Para que ela, por assim dizer, pudesse sentir o mundo com seus sensores, olhá-lo com os olhos (câmera), ouvir com um microfone e xingar pelo viva-voz. Os recursos de AVR, é claro, não foram suficientes para isso e, portanto, o microcontrolador que estava no carrinho passou para um nível mais baixo, em algum lugar da medula espinhal para controlar motores e vários reflexos não condicionados.

Mas uma coisa estranha, quando comecei a escrever um aplicativo para um smartphone, um IDE ANDROID STUDIO ruim começou a constantemente cruzar meu código e chamar isso de obsoleto.

camera = Camera. open ();

Especialmente, como você pode ver, naquelas partes em que tentei trabalhar com a câmera. Foi muito decepcionante, porque li na Internet e aprendi muitas lições sobre como trabalhar com o Android e a câmera aqui , aqui , aqui e até aqui . Nada foi riscado lá fora. E foi chamado de API da câmera. Tudo lá era simples e lógico. Mas o Google teimosamente me empurrou para algum tipo de API Camera2 .

Eu olhei lá e simplesmente tive problemas com o número de diferentes retornos de chamada, construtores, manipuladores e loopers para cada linha do código de demonstração. Era completamente incompreensível que maneira de abordar isso se você é um amador comum, não um desenvolvedor de Android. Além disso, existem ainda alguns artigos sobre a API Camera2 na rede hoje, embora essa atualização parecesse ocorrer há quatro anos. Mas tudo que eu encontrei foi um artigo no Hacker em 2016, um post em três partes de irmãos ucranianos do mesmo ano, um post duplo sobre Habré em 2017 e um artigo Understanding Camera2 do megafone japonês Tomoaki Imai. E isso também quero dizer com algumas informações estruturadas e formalizadas, e não trechos de código como "veja como eu posso" e folhas no estilo "veja a tabela de códigos, nada funciona para mim" espalhadas na Internet.

E agora, se você ainda está se perguntando por que eu precisava cortar meu post sobre este tópico
já em 2019, então seja bem-vindo ao gato.

Na verdade, gostei do artigo do Hacker, porque me deu pelo menos uma visão geral de por que diabos o novo paradigma de trabalhar com a câmera foi introduzido. Também ficou claro por que eram necessários alguns novos métodos que não estavam na API anterior. Mas escrever um programa de trabalho para este artigo era completamente impossível, porque os autores, aparentemente, como hackers reais, citaram apenas os nomes dos métodos, bem, talvez existam algumas linhas de código adicional. Mas isso categoricamente não foi suficiente para mim.

O post sobre Habré foi bom, não discuto, e até entendi os primeiros parágrafos, mas esse era o ponto, porque o autor do post derrotou a câmera usando o RxJava2, o que automaticamente me excluiu do número de outros leitores. Normalmente lidaria com OOP, mas aqui está algum tipo de programação reativa.

O artigo em japonês era ainda melhor, mesmo que ele não escrevesse em sua língua nativa. Mas também não conheço o Kotlin, embora eu, obviamente, tenha ficado satisfeito com os desenvolvedores domésticos e apreciei a brevidade de sua sintaxe, em contraste com a aletria JAVA que estava na página de desenvolvedores do Google (a propósito, nesta página, também estava claro para mim que havia claramente não muito).

O último artigo que me trouxe benefícios práticos foi um post de três partes da Ucrânia fraterna. Eu até lancei algo lá e vi algo lá. Mas, infelizmente, na terceira parte, o autor do artigo estava muito cansado e também começou a emitir código em fragmentos que não se somavam ao todo do meu trabalho. Além disso, no final, o autor ficou completamente chateado, porque recebeu a imagem da paleta de cores errada para a qual estava contando.



Digamos, isso ocorre porque a versão do Lollipop é 5.0 e há um erro. Você precisa atualizar para o Lollipop 5.1 e tudo ficará bem. Mas de alguma forma ainda não. E ucranianos prejudiciais também se prenderam ao artigo JAVA SCRIPT e, ao copiar o código, uma grande quantidade de lixo foi derramada no texto do programa. Gente, bem, você não pode fazer isso ... Eu tive que instalar um plug-in especial no Firefox.

Portanto, eu tive que pegar a bandeira caída e terminar o trabalho até o fim, o que, é claro, exigia um esforço mental titânico. E como, de alguma forma, era uma pena ficar satisfeito apenas com a percepção de que um resultado de trabalho foi obtido, eu queria compartilhá-lo com bules amadores. Além disso, isso está longe do fim da história. Ainda preciso aprender a transferir vídeo ao vivo da câmera do Android para o computador (o robô robótico deve se desenvolver, essa é a lei da evolução) e não em pedaços, como cegamente, mas sem problemas. E existem obstáculos como o Mont Blanc na forma de codecs de mídia e outras coisas que apenas funcionam com papel alumínio.

Mas, por outro lado, tudo pode terminar em completa decepção. E não porque eles não serão capazes de escalar o Monte Branco.

E porque agora os fabricantes de smartphones estão desenvolvendo abordagens completamente novas para a fabricação de câmeras.
Há um mês, vazou um boato sobre os smartphones Nokia com cinco câmeras principais. Como se relacionar com isso? Um boato interessante e promissor ou outra coisa estranha? Não importa o quão único esse design possa parecer, a Nokia certamente não poderá se tornar pioneira na introdução de um número incomum de lentes e sensores em dispositivos compactos. A câmera Light L16 já estava equipada com 16 lentes em 2015, e a empresa, obviamente, possui um novo protótipo em operação. Acima, você pode ver como ele poderia se parecer.

Depois que a câmera tripla apareceu no Huawei P20 Pro, a transição para um smartphone com cinco câmeras não parece mais tão cômica quanto poderia ser há alguns anos atrás. No entanto, a principal questão permanece - qual é o objetivo?
O que fazer com tantas lentes?

A primeira coisa que vem à mente é a variedade de tipos de sensores de câmera disponíveis no mercado moderno de smartphones e a capacidade de adicionar mais. Por que escolher entre grande angular, telefoto, retrato com bokeh ou monocromático, se você consegue tudo isso em um único dispositivo?

Sendo teoricamente possível, esse design seria bastante complicado de usar. O software terá que alternar entre os modos automaticamente ou oferecer um conjunto complexo de opções ao usuário. Além disso, o desenvolvimento de um projeto desse tipo seria muito caro para todas as vantagens duvidosas de tal solução. Cada câmera funcionaria em sua maioria de forma independente, e os compradores dificilmente usariam um grande número de modos. E não está claro quanto eles estariam dispostos a pagar por essa funcionalidade. Portanto, câmeras com vários módulos devem poder fazer mais para atrair o usuário.

O Huawei P20 Pro já oferece sua versão de como vários módulos de câmera podem trabalhar juntos para fornecer um resultado interessante. Estamos falando de tecnologias da Huawei, como Monochrome e Hybrid Zoom. O primeiro aprimora a faixa dinâmica de quadros padrão, combinando dados RGB regulares com um sensor preto e branco fotossensível. E o Zoom híbrido promete ainda mais: combina dados de várias câmeras para aumentar a resolução da imagem e obter um zoom melhor. Como resultado, no P20 Pro 8 MP, o sensor de lente telefoto permite fotografar em resolução de 10 MP com zoom de 3x e 5x.
Maior resolução - mais flexibilidade

A primeira câmera Light L16 funcionou da mesma forma, usando espelhos periscópicos para encaixar os módulos da câmera em um corpo compacto. A câmera capturou dados de vários módulos em 28, 70 e 150 mm, dependendo do nível de zoom. O resultado foi uma grande captura de 52 MP de 10 ângulos ligeiramente diferentes disponíveis em níveis de ampliação de até 5x. O conceito do novo modelo, desenvolvido para smartphones, funciona com 5-9 lentes. Esse módulo de câmera é capaz de tirar fotos grandes de 64 megapixels.

Essa idéia de várias fotos também traz benefícios ao fotografar com pouca luz e em HDR com várias aberturas. O efeito de alta qualidade da profundidade do quadro também está disponível devido à emulação simultânea de software e ao uso de várias distâncias focais.

A luz L16 trouxe decepção, mas a ideia em si foi promissora. E a próxima geração com sucesso pode ser algo que vale a pena. A empresa afirma que um smartphone será anunciado no final do ano, onde sua solução mais recente com várias lentes será instalada.

A mesma idéia pode ser rastreada até a experiência da Nokia na implementação de várias câmeras, dada a velha história de investimento na Pelican Imaging em 2013. Ao contrário da Light, o sensor aqui é muito menor. E, mesmo assim, a tecnologia promete vantagens muito semelhantes, incluindo mudança de foco do software, medição de profundidade e aumento do tamanho da imagem final. Infelizmente, Tessera comprou a empresa em 2016, mas a ideia em si pode não ter saído da cabeça dos engenheiros da Nokia.

A Zeiss, atual parceira de fotos da Nokia, possui uma patente para zoom comutável, mas ainda não ouvimos nada sobre o design de várias lentes. Talvez o outro parceiro da Nokia, FIH Mobile, pareça mais promissor. Essa empresa é de propriedade da Foxconn, lança telefones Nokia e também investiu na Light em 2015, dando-lhe uma licença para usar a tecnologia.

E se você acha que o vazamento da Nokia e o protótipo Light têm algo em comum, isso não é coincidência. Conecta as duas empresas Foxconn. Então, o smartphone Nokia será o primeiro a usar a tecnologia da Light?
Então este é o futuro?

A resolução ultra-alta não é um conceito novo: em 2014, o Oppo Find 7 usou um princípio semelhante, e o Hybrid Zoom da Huawei permitiu que a tecnologia funcionasse com várias câmeras. Historicamente, o principal problema da tecnologia eram requisitos de alto desempenho, qualidade do algoritmo e consumo de energia. Mas do lado dos smartphones modernos, processadores de processamento de sinal mais potentes, chips DSP com eficiência energética e até recursos aprimorados de redes neurais, o que reduz gradualmente a importância do problema.

Detalhes altos, recursos de zoom óptico e um efeito bokeh personalizado estão no topo da lista de requisitos de câmera para um smartphone moderno, e a tecnologia de várias câmeras pode ajudar a conseguir isso. Em vez de executar funções diferentes com câmeras separadas, o futuro da fotografia móvel é combinar várias câmeras para fornecer recursos mais avançados e flexíveis.

Ainda há dúvidas sobre a tecnologia Light, especialmente sobre a colagem de imagens com diferentes distâncias focais. Só podemos esperar - veremos o que mudará para melhor na segunda geração de tecnologia.



Não será fácil configurar esse conjunto a partir de câmeras com alças no programa. Talvez o círculo esteja fechado e, para tirar uma foto, teremos que escrever novamente uma intenção explícita, com o conteúdo, como “câmera, por favor, tire a foto você mesmo, mas lindamente, e devolva-a na minha atividade”.

Mas, por enquanto, embora um pouco ainda tenhamos. Portanto, procedemos imediatamente.

Por que todo esse google precisava?

Parece que a coisa toda é multithreading segura e adequada (e também é possível fazer todos os tipos de efeitos e filtros diretamente). Agora, se em baunilha JAVA, se necessário, você precisa enviar mutexes, sincronizações e semáforos de forma competente em todos os lugares, então aqui o Google assume quase tudo. Você só precisa registrar retornos de chamada no texto do programa, que será chamado se necessário. Ou seja, por exemplo, você envia uma solicitação para ligar a câmera:

mCameraManager.openCamera() 

Mas isso não é uma equipe, é uma solicitação. Você não pode começar a trabalhar imediatamente com a câmera. Primeiro, ela precisa de tempo para ligar, e segundo, o andróide tem tantas coisas importantes a fazer, e seu desejo é colocado em uma fila bastante grande. Mas não precisamos esperar a câmera abrir em um loop no thread principal, desligando o aplicativo inteiro (ainda lembramos o que pode ser feito no fluxo da interface do usuário e o que não). Portanto, enviamos nosso desejo e, enquanto conduzimos nossos negócios, abrimos as visualizações, escrevemos “olá mundo”, configuramos manipuladores de botão e similares.

Enquanto isso, depois de algumas dezenas e centenas de milissegundos, o sistema operacional finalmente chega às mãos da câmera e a inicializa. E assim que a câmera estiver pronta, o mesmo retorno de chamada é acionado (a menos que você tenha registrado com antecedência)

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

Ou seja, a câmera agora está aberta e pode fazer algo: exibir a imagem da câmera na visualização, encaminhá-la ainda mais para salvar e assim por diante.

Como resultado, todo o trabalho com a câmera se resume a prescrever todos os tipos de retornos de chamada. Mas, no seu desejo nobre, os desenvolvedores do Google foram um pouco longe demais e, como o camarada japonês observou corretamente:
Uma das razões pelas quais a Camera2 está perplexa é quantas chamadas de retorno você precisa usar para tirar uma foto.

Mas isso não é suficiente. O esquema completo da câmera tem esse visual incrível



Mas, felizmente, para iniciantes, pode ser reduzido a uma imagem muito mais atraente.



Embora tudo esteja em inglês lá, mas como eles dizem, é claro sem palavras. Para iniciantes, temos o suficiente desse design. Abriremos a câmera, exibiremos a imagem na tela do smartphone, tiraremos uma foto e, assim que estiver pronta (retornos de chamada novamente), o ouvinte deste evento funcionará e gravará a foto em um arquivo.

Introdução à criação de código

Criaremos um novo projeto no Android Studio IDE, selecionaremos a versão mínima do SDK 22 para evitar imagens verdes e solicitaremos atividades vazias (ou melhor, levar a versão 23, caso contrário, poderão ocorrer problemas com as permissões). O suficiente para começar. Mesmo as permissões no manifesto ainda não precisam ser feitas.

Começamos criando uma instância da classe CameraManager. Este é um gerenciador de serviços do sistema que permite encontrar câmeras disponíveis, obter as características necessárias para o trabalho e definir configurações de fotografia para as câmeras.

E veremos as seguintes características:

identificador da câmera (0, 1, 2 ....)
direção para onde a câmera é direcionada (para frente, para trás)
resolução da câmera

Primeiro, obtemos a lista de câmeras na forma de uma matriz de strings e, em seguida, imprimimos as características necessárias em um loop e as gravamos no log.

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

O log terá algo parecido com isto:
 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 


Nós, em princípio, realmente não precisamos dessas informações, qualquer bule de chá, mesmo pela API anterior, já sabe que as câmeras têm identificadores de zero e além, que você não surpreenderá ninguém com uma resolução de 1920 por 1080 e, ainda mais, com o formato JPEG. De fato, esses dados já são necessários para um aplicativo "adulto" pronto para produção e que, com base em ele, fará menus de escolha para o usuário e assim por diante. No nosso caso mais simples, em geral, tudo está claro. Mas como todos os artigos começam com isso, começaremos.

Depois de garantir que a câmera frontal tenha o número de identificação "1" e a parte traseira "0" (por algum motivo, eles estão especificados no formato String), e também que a resolução de 1920 x 1080 e o salvamento do arquivo JPG estão disponíveis, continuaremos o ataque.

Obtemos as permissões necessárias

Inicialmente, precisamos nos preocupar com várias permissões. Para fazer isso, você precisará escrever o seguinte no manifesto:

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

O primeiro é compreensível para a câmera, o segundo é para gravar imagens em um arquivo (e este não é um cartão de memória externo, como pode parecer pelo significado da palavra EXTERNO, mas uma memória nativa do smartphone)

Mas o Android também se preocupa conosco, portanto, começando com a versão Lollipop, as permissões especificadas no manifesto não serão mais suficientes. Agora é necessário que as canetas do usuário aprove seu consentimento para abrir a câmera e gravar dados na memória.

Para fazer isso, no caso mais simples, você precisa adicionar este:

 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 que no mais simples? Porque não é necessário fazer essas coisas em um fluxo da interface do usuário. Em primeiro lugar, um fio é desligado enquanto o usuário cutuca a tela com os dedos desajeitados e, em segundo lugar, se você tiver mais inicialização da câmera, o aplicativo geralmente pode cair. Neste caso de demonstração, está tudo bem, mas geralmente é prescrito o uso do tipo de retorno de chamada desejado 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(); //     (  ) } } } 

Embora eu não soubesse de tudo isso anteriormente, lancei a Atividade desejada através do AsyncTask e, ainda mais cedo, esculpi um novo Thread, como em Java.

Cozinhando a câmera

Por conveniência, retiraremos tudo relacionado às câmeras em uma classe separada, seguindo as orientações de pessoas inteligentes, e criaremos a classe CameraService. Lá, colocaremos a inicialização das câmeras e, em seguida, anotamos todos os retornos de chamada necessários.

 ….. 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; } } } 

No thread principal, crie uma instância do mCameraManager e use-a para preencher a matriz de objetos myCameras. Nesse caso, existem apenas dois deles - as câmeras frontal e selfie.

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

No método openCamera () do void público, você pode ver a linha:

  mCameraManager.openCamera(mCameraID,mCameraCallback,null); 

é a partir daqui que o caminho leva ao primeiro retorno de chamada do status da câmera CameraDevice. StateCallback. Ele nos dirá se a câmera está aberta, fechada ou talvez não exista nada e cometerá um erro. Vamos escrever nos métodos da classe 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); } }; 

Portanto, se a câmera estiver disponível para operação (o método public void onOpened (CameraDevice camera) {} funcionou), então escreveremos nossas ações adicionais, por exemplo, chamando o método createCameraPreviewSession (). Isso nos ajudará a trazer a imagem da câmera para a visualização e a trabalhar com ela ainda mais.

CreateCameraPreviewSession

Aqui, estamos tentando exibir uma imagem (fluxo de dados) na textura mImageView, que já está definida no layout. Você pode até determinar com qual resolução nos pixels.

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

E quando esta mesma sessão estiver pronta, o retorno de chamada acima é chamado e começamos pela expressão dos googloders: “exibindo a visualização da câmera”. Aqui, aqueles que desejarem podem ajustar as configurações de foco automático e flash, mas, por enquanto, seguiremos com as configurações padrão.

Crie um layout

Agora precisamos, por assim dizer, esboçar as cores na tela e criar uma imagem brilhante no estilo de
"Três botões na tela e uma visualização."
 <?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 



O processo é bastante trivial e qualquer pessoa pode se virar aqui como quiser. Mas escrever os nomes dos botões diretamente no layout também é uma má educação e, portanto, nas versões de trabalho, não é necessário fazê-lo.

Consequentemente, na própria Atividade, criamos ouvintes, ou seja, ouvintes para botões e visualizações.

  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) { //    } }); …….. 

As atribuições dos botões são claras a partir dos nomes, deixaremos o terceiro botão para a imagem futura.

E se você agora reunir todos os pedaços de código,

obtenha o seguinte:
 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(); } } 


Carregamos, começamos, trabalhamos!

Em geral, se isso não for suficiente para você, o próprio processo de filmagem pode ser bastante diversificado. Nosso japonês Tomoaki mostra e explica como, trazendo um belo diagrama.



Primeiro, a câmera precisa focar. Isso geralmente não causa problemas, mas às vezes exige várias tentativas, que também são implementadas por meio de um retorno de chamada.

 CameraCaptureSession.StateCallback(). 

Em seguida, a câmera entra no modo de visualização PRECAPTURE. Nesta fase, a câmera calcula a exposição, o balanço de brancos e a abertura (eu sabia o que era na infância, mas agora esse conhecimento está perdido). Às vezes, um retorno de chamada pode retornar uma solicitação CONTROL_AE_STATE_FLASH_REQUIRED, o que significa "seria bom ligar o flash". Ele pode ser ativado automaticamente a propósito - setAutoFlash (mPreviewRequestBuilder).

Quando todos os parâmetros necessários para o disparo são definidos, o retorno de chamada retorna o estado CONTROL_AE_STATE_CONVERGED nos sinalizando que a câmera está pronta para tirar uma foto.

Na página da googloid, tudo isso já está nos exemplos, e se você tiver paciência para romper esses campos minados e cercas de arame, a honra será sua.

Tire uma foto e salve a foto em um arquivo

Foi exatamente aí que comecei a ter problemas.Não, a julgar pelo diagrama de blocos acima (não japonês, mas o anterior), tudo não é muito complicado. Estamos aguardando a câmera capturar a imagem. Após o processamento com a classe CamerCaptureSession, ele estará disponível como um objeto Surface, que por sua vez pode ser processado usando a classe ImageReader.

A verdade é que, para criar um objeto ImageReader novamente, leva tempo. Aguardamos esse tempo no próximo ouvinte chamado OnImageAvailableListener. E, finalmente, com a ajuda da instância da última classe ImageSaver, salvamos a imagem em um arquivo e, é claro, fazemos de forma assíncrona também, porque o ImageSaver é executável aqui.

O problema era que eu não conseguia anexar este ImageReader em nenhum lugar, porque o retorno de chamada CameraCaptureSession.StateCallback () já estava ocupado transmitindo o vídeo para a visualização do smartphone. E se eu fiz uma nova sessão, o Android previsivelmente amaldiçoou e travou o aplicativo. Como resultado (não me pergunte como), consegui atravessar o cavalo e a corça trêmula em um método createCameraPreviewSession (), que costumava exibir a imagem da câmera apenas na exibição.

Aqui está este pedaço de código antes:

 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() ……. 

E aqui está ele depois:

  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() …… 

A diferença, além da definição da instância do ImageReader na parte superior, é quase ilusória. Apenas adicionado à superfície, separamos vírgulas mImageReader.getSurface () e é isso. Mas até você chegar lá ...

A partir desse momento, as coisas ficaram mais divertidas e você pode usar o terceiro botão da tela "Tirar uma foto". Quando pressionado, o método makePhoto () é chamado (bem, quem teria 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 imediatamente depois, escrevemos o ouvinte OnImageAvailableListener:

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

Enquanto ele não está fazendo nada, ele simplesmente sinaliza com um brinde que eles dizem que está tudo em ordem, você pode salvar a imagem em um arquivo.

E para isso, precisamos do próprio arquivo:

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

Assim como um objeto especial da classe ImageSaver, que transfere rapidamente dados de uma imagem para um buffer de bytes e de lá para um arquivo binário.

Esta classe é estática e executável. Portanto, colocamos na própria 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(); } } } } } 

E para que funcione, escrevemos adicionalmente no ouvinte OnImageAvailableListener:

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

ESPERA! OH MERDA !!

O que mais é mBackgroundHandler ??? Ainda assim, funcionou perfeitamente sem ele.

Mas, de fato, a pergunta certa é: como ela funcionou sem ela? Porque, conforme declarado nos exemplos do Google, o BackgroundHandler fornece o BackgroundThread, que por sua vez é um segmento que trabalha em segundo plano e é realmente responsável pela atividade da câmera. De fato, devemos nos registrar no início de nossa Atividade:

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

Além disso, você também precisa adicionar o início e o fim do BackgroundThread aqui:

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

Quanto ao mBackgroundHandler, ele deve ser adicionado a todos os nossos retornos de chamada que exigem um manipulador e onde, sem se preocupar, escrevemos nulos.

O mais intrigante é que NÃO iniciamos esse segmento de segundo plano quando abrimos o aplicativo, pois é fácil ver o texto do programa. Ou seja, começa implicitamente e sem a nossa ajuda. Mas temos que pará-lo e executá-lo nos modos onPause () e onResume (). Algum tipo de contradição surge aqui.

Mas agora a imagem é salva com sucesso em um arquivo. É fácil verificar isso executando o aplicativo. Como diz o ditado, a coroa do trabalho está acima de todos os prêmios.



É verdade que a imagem de alguma forma está do seu lado, mas resolver esse problema é tarefa das gerações futuras.

Lista completa do programa
 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/pt468083/


All Articles