API Android Camera2 depuis la bouilloire



Il n'y a pas si longtemps, j'étais occupé avec ma machine robotique, essayant d'y mettre un smartphone ANDROID. Ma tâche, c'était de faire évoluer le chariot. Pour qu'elle puisse, pour ainsi dire, sentir le monde avec ses capteurs, le regarder avec son œil (caméra), entendre avec un microphone et jurer sur le haut-parleur. Les ressources AVR, bien sûr, n'étaient pas suffisantes pour cela, et donc le microcontrôleur qui était sur le chariot est passé à un niveau inférieur, quelque part dans la moelle épinière, pour contrôler les moteurs et divers réflexes inconditionnés.

Mais une chose étrange, quand j'ai commencé à écrire une application pour un smartphone, un mauvais IDE ANDROID STUDIO a commencé à biffer constamment mon code et à l'appeler obsolète.

camera = Camera. open ();

Surtout, comme vous pouvez le voir, dans les parties où j'ai essayé de travailler avec l'appareil photo. C'était très décevant car j'ai lu sur Internet et j'ai appris de nombreuses leçons sur le travail avec Android et la caméra ici , ici , ici et même ici . Rien n'a été barré là-bas. Et cela s'appelait l'API Camera. Tout y était simple et logique. Mais Google m'a obstinément poussé vers une sorte d' API Camera2 .

J'ai regardé là-bas et j'ai simplement eu des problèmes avec le nombre de rappels, de constructeurs, de gestionnaires et de boucleurs différents pour chaque ligne du code de démonstration. Il était complètement incompréhensible de savoir comment aborder cela si vous êtes un amateur ordinaire, pas un développeur Android. De plus, il y a même quelques articles sur l'API Camera2 sur le réseau aujourd'hui, bien que cette mise à jour semble être comme il y a quatre ans. Mais tout ce que j'ai trouvé était un article dans Hacker en 2016, un article en trois parties des frères ukrainiens de la même année, un double article sur Habré en 2017 et un article Understanding Camera2 du mégaphone japonais Tomoaki Imai. Et cela, je veux aussi dire des informations structurées et formalisées, et non des extraits de code comme «voir comment je peux» et des feuilles dans le style «regardez le code pliz, rien ne fonctionne pour moi» dispersés sur Internet.

Et maintenant, si vous vous demandez toujours pourquoi j'avais besoin de couper mon message sur ce sujet
déjà en 2019, alors bienvenue au chat.

En fait, j'ai aimé l'article du Hacker parce qu'il m'a donné au moins un aperçu des raisons pour lesquelles le nouveau paradigme de travailler avec la caméra a été introduit. Il est également devenu clair pourquoi de nouvelles méthodes étaient nécessaires qui n'étaient pas dans l'API précédente. Mais écrire un programme de travail pour cet article était complètement impossible, car les auteurs, apparemment, comme de vrais hackers, ne citaient que les noms des méthodes, eh bien, il y a peut-être encore quelques lignes de code supplémentaire. Mais cela ne me suffisait pas catégoriquement.

Le message sur Habré était bon, je ne conteste pas, et j'ai même compris les premiers paragraphes, mais c'était le point, parce que l'auteur du message a vaincu la caméra en utilisant RxJava2, ce qui m'a automatiquement exclu du nombre de lecteurs supplémentaires. Je traiterais normalement avec OOP, mais voici une sorte de programmation réactive.

L'article japonais était encore meilleur, même s'il n'écrivait pas dans sa langue maternelle. Mais je ne connais pas non plus Kotlin, même si, bien sûr, j'étais content pour les développeurs nationaux et appréciais la brièveté de sa syntaxe, contrairement au JAVA-vermicelli qui était sur la page des développeurs de Google (à propos, avec cette page, il était également clair pour moi qu'il y avait clairement pas beaucoup).

Le dernier article qui m'a apporté des avantages pratiques était un article en trois parties de l'Ukraine fraternelle. J'ai même lancé quelque chose là-bas et y ai vu quelque chose. Mais malheureusement, dans la troisième partie, l'auteur de l'article était très fatigué et a également commencé à publier du code en fragments qui ne correspondaient pas à mon travail. De plus, à la fin, l'auteur était complètement bouleversé, car il avait l'image de la mauvaise palette de couleurs pour laquelle il comptait.



Disons, c'est parce que la version de Lollipop est 5.0, et il y a un bug. Vous devez passer à Lollipop 5.1 et tout ira bien. Mais en quelque sorte pas encore. Et aussi des Ukrainiens nuisibles attachés à l'article JAVA SCRIPT, et lors de la copie du code, une quantité sauvage de déchets a été versée dans le texte du programme. Les gars, eh bien, vous ne pouvez pas faire ça ... J'ai dû installer un plug-in spécial dans Firefox.

Par conséquent, j'ai dû ramasser la bannière tombée et terminer le travail jusqu'à la fin, ce qui a bien sûr nécessité un effort mental titanesque. Et comme il était en quelque sorte dommage de se contenter de réaliser qu'un résultat de travail avait été obtenu, je voulais le partager avec des théières amateurs. De plus, c'est loin d'être la fin de l'histoire. J'ai encore besoin d'apprendre à transférer des vidéos en direct de la caméra Android vers l'ordinateur (le robot robot doit se développer, c'est la loi de l'évolution) et pas en morceaux, comme je l'aveugle, mais en douceur. Et il y a de tels obstacles au Mont Blanc sous la forme de codecs multimédias et d'autres choses qui fermentent juste.

Mais d'un autre côté, le tout peut se terminer dans une déception totale. Et pas parce qu'ils ne pourront pas gravir le Mont Blanc.

Et parce que maintenant les fabricants de smartphones développent des approches complètement nouvelles pour la fabrication d'appareils photo.
Il y a un mois, une rumeur a filtré sur les smartphones Nokia avec cinq appareils photo principaux. Comment s'y rapporter? Une rumeur intéressante et prometteuse ou une autre chose étrange? Quel que soit le caractère unique d'une telle conception, Nokia ne sera certainement pas en mesure de devenir un pionnier en introduisant un nombre inhabituel d'objectifs et de capteurs dans les appareils compacts. La caméra Light L16 était déjà équipée de 16 objectifs en 2015, et l'entreprise a évidemment un nouveau prototype en service. Ci-dessus, vous pouvez voir à quoi cela pourrait ressembler.

Après l'apparition de la triple caméra dans le Huawei P20 Pro, la transition vers un smartphone avec cinq caméras ne semble plus aussi comique qu'elle aurait pu l'être il y a quelques années. Cependant, la question principale demeure - quel est le point?
Que faire avec autant de lentilles?

La première chose qui me vient à l'esprit est la variété des types de capteurs de caméra disponibles sur le marché des smartphones modernes et la possibilité d'en ajouter d'autres. Pourquoi choisir entre grand angle, téléobjectif, portrait avec bokeh ou monochrome, si vous pouvez obtenir tout cela dans un seul appareil?

Étant théoriquement possible, une telle conception serait assez délicate à utiliser. Le logiciel devra basculer automatiquement entre les modes ou offrir un ensemble complexe d'options à l'utilisateur. De plus, le développement d'une telle conception serait très coûteux pour tous les avantages douteux d'une telle solution. Chaque appareil photo fonctionnerait pour la plupart de manière indépendante, et il était peu probable que les acheteurs utilisent un grand nombre de modes. Et il n'est pas clair combien ils seraient prêts à payer pour une telle fonctionnalité. Les caméras avec plusieurs modules devraient donc pouvoir faire plus pour attirer l'utilisateur.

Huawei P20 Pro propose déjà sa version de la façon dont plusieurs modules de caméra peuvent fonctionner ensemble pour donner un résultat intéressant. Nous parlons de technologies de Huawei, telles que le zoom monochrome et hybride. Le premier améliore la plage dynamique des images standard en combinant des données RVB normales avec un capteur photosensible noir et blanc. Et le zoom hybride promet encore plus: il combine les données de plusieurs caméras pour augmenter la résolution de l'image pour un meilleur zoom. Par conséquent, dans le P20 Pro 8 MP, le capteur de téléobjectif vous permet de prendre des photos en résolution 10 MP avec un zoom 3x et 5x.
Résolution plus élevée - plus de flexibilité

La première caméra Light L16 a fonctionné de la même manière, utilisant des miroirs périscopiques pour adapter les modules de la caméra dans un corps compact. La caméra a pris des données de plusieurs modules à 28, 70 et 150 mm, selon le niveau de zoom. Le résultat a été une grande prise de vue de 52 MP prise sous 10 angles légèrement différents disponibles à des niveaux d'agrandissement jusqu'à 5x. Le concept du nouveau modèle, développé pour les smartphones, fonctionne avec 5-9 lentilles. Un tel module de caméra est capable de prendre de grandes photos de 64 mégapixels.

Cette idée de plusieurs prises de vue ajoute également des avantages lors de la prise de vue en basse lumière et en HDR avec plusieurs ouvertures. L'effet de haute qualité de la profondeur de la trame est également disponible en raison de l'émulation logicielle simultanée et de l'utilisation de plusieurs longueurs focales.

Le Light L16 a déçu, mais l'idée elle-même était prometteuse. Et la prochaine génération avec succès pourrait s'avérer utile. La société affirme qu'un smartphone sera annoncé à la fin de l'année, où sa dernière solution avec plusieurs objectifs sera installée.

La même idée remonte à l'expérience de Nokia dans la mise en œuvre de plusieurs caméras, étant donné la vieille histoire d'investissement dans Pelican Imaging en 2013. Contrairement à la lumière, le capteur est ici beaucoup plus petit. Et même ainsi, la technologie promet des avantages très similaires, y compris le changement de mise au point du logiciel, la mesure de la profondeur et l'augmentation de la taille de l'image finale. Malheureusement, Tessera a acheté la société en 2016, mais l'idée elle-même n'a peut-être pas quitté l'esprit des ingénieurs de Nokia.

Zeiss, le partenaire photo actuel de Nokia, a un brevet pour le zoom commutable, mais nous n'avons rien entendu de leur part à propos de la conception multi-objectifs. Peut-être que l'autre partenaire de Nokia, FIH Mobile, semble plus prometteur. Cette société appartient à Foxconn, lance des téléphones Nokia et a également investi dans Light en 2015, lui accordant une licence d'utilisation de la technologie.

Et si vous pensez que la fuite de Nokia et le prototype Light ont quelque chose en commun, ce n'est pas un hasard. Connecte les deux sociétés Foxconn. Le smartphone Nokia sera-t-il le premier à utiliser la technologie Light?
Alors, c'est ça l'avenir?

L'ultra-haute résolution n'est pas un nouveau concept. En 2014, Oppo Find 7 a utilisé un principe similaire et le zoom hybride de Huawei a permis à la technologie de fonctionner avec plusieurs caméras. Historiquement, le principal problème de la technologie a été les exigences de performances élevées, la qualité des algorithmes et la consommation d'énergie. Mais du côté des smartphones modernes, des processeurs de traitement du signal plus puissants, des puces DSP écoénergétiques et même des capacités améliorées des réseaux de neurones, ce qui réduit progressivement l'importance du problème.

Des détails élevés, des capacités de zoom optique et un effet bokeh personnalisé en tête de liste des exigences de l'appareil photo pour un smartphone moderne, et la technologie multi-caméras peut aider à atteindre cet objectif. Au lieu d'exécuter différentes fonctions avec des appareils photo séparés, l'avenir de la photographie mobile est de combiner plusieurs appareils photo pour offrir des capacités plus avancées et plus flexibles.

Il y a encore des questions sur la technologie Light, en particulier sur le collage d'images avec différentes focales. Nous ne pouvons qu'attendre - nous verrons ce qui changera pour le mieux dans la deuxième génération de technologie.



Il ne sera pas facile de configurer un tel assemblage à partir de caméras avec poignées dans le programme. Peut-être que le cercle sera fermé et pour prendre une photo, nous devrons à nouveau écrire une intention explicite, avec le contenu, tel que "appareil photo, veuillez prendre la photo vous-même comme vous le pouvez, mais magnifiquement, et me la rendre dans mon activité".

Mais pour l'instant, même si nous en avons encore un peu. Par conséquent, nous procédons immédiatement.

Pourquoi tout ce google avait-il besoin?

Il semble que le tout soit multithread sûr et approprié (et il est également possible de faire toutes sortes d'effets et de filtres directement). Maintenant, si dans JAVA vanille, si nécessaire, vous devez pousser les mutex, les synchronisations et les sémaphores avec compétence partout, alors ici, Google prend en charge presque tout. Il vous suffit d'enregistrer les rappels dans le texte du programme, qui sera appelé si nécessaire. C'est-à-dire, par exemple, que vous soumettez une demande pour allumer l'appareil photo:

mCameraManager.openCamera() 

Mais ce n'est pas une équipe, c'est une demande. Vous ne pouvez pas immédiatement commencer à travailler avec l'appareil photo. Premièrement, elle a besoin de temps pour s'allumer, et deuxièmement, l'androïde a tellement de choses importantes à faire, et votre souhait est placé dans une file d'attente assez grande. Mais nous n'avons pas besoin d'attendre que la caméra s'ouvre en boucle dans le thread principal, suspendant l'application entière (nous nous souvenons toujours de ce qui peut être fait dans le flux d'interface utilisateur et ce qui ne l'est pas). Par conséquent, nous envoyons notre souhait et pendant que nous allons de l'avant, ouvrons les vues, écrivons «bonjour le monde», configurons les gestionnaires de boutons et autres.

Pendant ce temps, après quelques dizaines et centaines de millisecondes, le système d'exploitation atteint enfin les mains de la caméra et il l'initialise. Et dès que la caméra est prête, le même rappel est déclenché (à moins bien sûr de l'avoir enregistré à l'avance)

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

C'est-à-dire que la caméra est maintenant ouverte et peut y faire quelque chose: afficher l'image de la caméra sur la vue, la transmettre davantage pour la sauvegarde, etc.

En conséquence, tous fonctionnent avec l'appareil photo, en fait, se résume à prescrire toutes sortes de rappels. Mais dans leur noble désir, les développeurs de Google sont légèrement allés trop loin et, comme l'a correctement noté le camarade japonais:
L'une des raisons pour lesquelles Camera2 est perplexe est le nombre de rappels que vous devez utiliser pour prendre une seule photo.

Mais cela ne suffit pas. Le schéma complet de la caméra a ce look génial



Mais heureusement, pour commencer, il peut être réduit à une image beaucoup plus attrayante.



Bien que tout y soit en anglais, mais comme on dit, c'est clair sans mots. Pour commencer, nous en avons assez de cette conception. Ouvrez l'appareil photo, affichez l'image sur l'écran du smartphone, prenez une photo et dès qu'elle est prête (rappels à nouveau), l'auditeur de cet événement va travailler et écrire l'image dans un fichier.

Commencer à créer du code

Nous allons créer un nouveau projet dans l'IDE Android Studio, sélectionner la version minimale du SDK 22 pour éviter les images vertes et commander une activité vide (ou encore mieux, prendre la version 23, sinon des problèmes peuvent survenir avec les autorisations). Assez pour commencer. Même les autorisations dans le manifeste n'ont pas encore besoin d'être effectuées.

Nous commençons par créer une instance de la classe CameraManager. Il s'agit d'un gestionnaire de services système qui vous permet de trouver les caméras disponibles, d'obtenir leurs caractéristiques dont vous avez besoin pour le travail et de définir les paramètres de prise de vue pour les caméras.

Et nous verrons les caractéristiques suivantes:

identifiant de la caméra (0, 1, 2 ....)
direction dans laquelle la caméra est dirigée (avant, arrière)
résolution de la caméra

Tout d'abord, nous obtenons la liste des caméras sous la forme d'un tableau de chaînes, puis imprimons les caractéristiques requises dans une boucle et les écrivons dans le journal.

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

Le journal obtiendra quelque chose comme ceci:
 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 


Nous, en principe, n'avons pas vraiment besoin de ces informations, aucune théière, même par l'ancienne API, sait déjà que les caméras ont des identifiants de zéro et au-delà, que vous ne surprendrez personne avec une résolution de 1920 par 1080, et encore plus avec le format JPEG. En fait, ces données sont déjà nécessaires à une application «adulte» prête à la production et qui, sur sa base, fera des menus de choix pour l'utilisateur, etc. Dans notre cas le plus simple, en général, tout est clair. Mais puisque tous les articles commencent par cela, nous allons commencer.

Après avoir vérifié que la caméra frontale porte le numéro d'identification «1» et l'arrière «0» (pour une raison quelconque, ils sont spécifiés au format String), et que la résolution de 1920 x 1080 et l'enregistrement du fichier JPG sont à notre disposition, nous continuerons l'attaque.

Nous obtenons les autorisations nécessaires

Au départ, nous devons nous soucier d'un certain nombre d'autorisations. Pour ce faire, vous devrez écrire ce qui suit dans le manifeste:

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

La première est compréhensible pour l’appareil photo, la seconde sert à écrire des images dans un fichier (et ce n’est pas une carte mémoire externe, comme cela peut sembler d'après le sens du mot EXTERNE, mais tout à fait la mémoire d'un smartphone)

Mais Android se soucie également de nous, donc, à partir de la version Lollipop, les autorisations spécifiées dans le manifeste ne seront plus suffisantes. Désormais, l'utilisateur doit approuver son consentement pour ouvrir la caméra et écrire des données dans la mémoire.

Pour ce faire, dans le cas le plus simple, vous devez ajouter ceci:

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

Pourquoi le plus simple? Parce qu'il n'est pas nécessaire de faire de telles choses dans un flux d'interface utilisateur. Premièrement, le fil se bloque pendant que l'utilisateur touche l'écran avec ses doigts maladroits, et deuxièmement, si vous avez initialisé l'appareil photo, l'application peut généralement se bloquer. Dans ce cas de démonstration, tout va bien, mais il est généralement recommandé d'utiliser le type de rappel souhaité pour ce cas:

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

Bien que je ne savais pas tout cela auparavant, j'ai lancé l'activité souhaitée via AsyncTask, et même plus tôt, je viens de sculpter un nouveau thread, comme en Java.

Cuisson de l'appareil photo

Pour plus de commodité, nous retirerons tout ce qui concerne les caméras dans une classe séparée sur les conseils de personnes intelligentes et créerons la classe CameraService. Là, nous placerons l'initialisation des caméras, puis noterons tous les rappels nécessaires.

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

Dans le thread principal, créez une instance de mCameraManager et utilisez-la pour remplir le tableau d'objets myCameras. Dans ce cas, il n'y en a que deux - les caméras avant et 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(); } 

Dans la méthode open void openCamera () publique, vous pouvez voir la ligne:

  mCameraManager.openCamera(mCameraID,mCameraCallback,null); 

c'est d'ici que le chemin mène au premier rappel de l' état de la caméra CameraDevice. StateCallback. Il nous dira si la caméra est ouverte, fermée ou peut-être qu'il n'y a rien du tout et donnera une erreur. Nous l'écrirons dans les méthodes de la 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); } }; 

Par conséquent, si la caméra est disponible pour le fonctionnement (la méthode public void onOpened (CameraDevice camera) {} a fonctionné), nous y écrivons nos autres actions, par exemple, en appelant la méthode createCameraPreviewSession (). Cela nous aidera à nous apporter l'image de la caméra sur la vue et à travailler avec elle plus loin.

CreateCameraPreviewSession

Ici, nous essayons d'afficher une image (flux de données) sur la texture mImageView, qui est déjà définie dans la mise en page. Vous pouvez même déterminer avec quelle résolution en 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(); } } 

Et lorsque cette même session est prête, le rappel précité est appelé et nous commençons par l'expression des googloders: «afficher l'aperçu de la caméra». Ici, ceux qui le souhaitent peuvent régler les paramètres de mise au point automatique et de flash, mais pour l'instant, nous nous en sortirons avec les paramètres par défaut.

Créer une mise en page

Maintenant, nous devons, pour ainsi dire, esquisser les couleurs sur la toile et créer une image brillante dans le style de
"Trois boutons d'écran et une vue."
 <?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 



Le processus est assez trivial et n'importe qui peut se retourner ici comme il veut. Mais écrire les noms des boutons directement dans la mise en page est également un mauvais langage et donc dans les versions de travail, vous n'avez pas besoin de le faire.

En conséquence, dans l'activité elle-même, nous créons des écouteurs, c'est-à-dire des écouteurs pour les boutons et les vues.

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

Les affectations des boutons sont claires d'après les noms, nous laisserons le troisième bouton pour la future photo.

Et si vous rassemblez maintenant tous les morceaux de code,

obtenez ce qui suit:
 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(); } } 


Nous chargeons, commençons, travaillons!

En général, si cela ne vous suffit pas, le processus de prise de vue lui-même peut être très diversifié. Notre Tomoaki japonais montre et explique comment, apportant un beau diagramme.



Tout d'abord, la caméra doit se concentrer. Cela ne pose généralement pas de problèmes, mais cela nécessite parfois plusieurs tentatives, qui sont également implémentées via un rappel.

 CameraCaptureSession.StateCallback(). 

L'appareil photo passe ensuite en mode d'aperçu PRECAPTURE. À ce stade, l'appareil photo calcule l'exposition, la balance des blancs et l'ouverture (je savais ce que c'était dans l'enfance, mais maintenant cette connaissance est perdue). Parfois, un rappel peut renvoyer une demande CONTROL_AE_STATE_FLASH_REQUIRED, ce qui signifie «ce serait bien d'activer le flash». Il peut être activé automatiquement en passant - setAutoFlash (mPreviewRequestBuilder).

Lorsque tous les paramètres nécessaires à la prise de vue sont définis, le rappel renvoie l'état CONTROL_AE_STATE_CONVERGED nous signalant que la caméra est prête à prendre une photo.

Sur la page googloid, tout cela est déjà dans les exemples, et si vous avez la patience de percer ces champs de mines et ces barrières, alors l'honneur et les éloges sont pour vous.

Prenez une photo et enregistrez-la dans un fichier

C'est exactement là que j'ai commencé à avoir des problèmes.Non, à en juger par le schéma de principe de ce qui précède (pas japonais, mais le précédent), tout n'est pas très compliqué. Nous attendons que la caméra capture l'image. Après le traitement avec la classe CamerCaptureSession, il sera disponible en tant qu'objet Surface, qui à son tour peut être traité à l'aide de la classe ImageReader.

La vérité est que pour recréer un objet ImageReader, cela prend du temps. Nous attendons cette heure dans le prochain écouteur appelé OnImageAvailableListener. Et enfin, à l'aide de l'instance de la dernière classe ImageSaver, nous enregistrons l'image dans un fichier et bien sûr nous le faisons également de manière asynchrone, car ImageSaver est exécutable ici.

Le problème était que je ne pouvais pas attacher cet ImageReader n'importe où, car le rappel de CameraCaptureSession.StateCallback () était déjà occupé à diffuser la vidéo à la vue du smartphone. Et si j'ai fait une nouvelle session, Android a maudit et écrasé l'application de manière prévisible. En conséquence (ne me demandez pas comment) j'ai réussi à croiser le cheval et la biche tremblante dans une méthode createCameraPreviewSession (), qui affichait l'image de la caméra uniquement sur la vue.

Voici ce morceau de code avant:

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

Et le voici:

  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 différence, à part la définition d'instance ImageReader en haut, est presque insaisissable. Juste ajouté à la surface, mImageReader.getSurface () séparé par des virgules et c'est tout. Mais jusqu'à ce que vous y arriviez ...

À partir de ce moment, les choses se sont amusées et vous pouvez utiliser le troisième bouton d'écran "Prendre une photo". Quand elle est pressée, la méthode makePhoto () est appelée (enfin, qui aurait pensé):

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

Et immédiatement après, nous écrivons l'écouteur OnImageAvailableListener:

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

Bien qu'il ne fasse rien, il signale simplement avec un toast qu'ils disent que tout est en ordre, vous pouvez enregistrer l'image dans un fichier.

Et pour cela, nous avons besoin du fichier lui-même:

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

Ainsi qu'un objet spécial de la classe ImageSaver, qui transfère rapidement les données d'une image vers un tampon d'octets, et de là vers un fichier binaire.

Cette classe est statique et exécutable. Par conséquent, nous le plaçons dans MainActivity lui-même:

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

Et pour que cela fonctionne, nous écrivons également dans l'écouteur OnImageAvailableListener:

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

ATTENDEZ! OH MERDE !!

Quoi d'autre est mBackgroundHandler ??? Pourtant, cela fonctionnait parfaitement sans lui.

Mais en fait, la bonne question est - comment cela a-t-il même fonctionné sans lui? Parce que, comme indiqué dans les exemples de Google, BackgroundHandler fournit BackgroundThread, qui à son tour est un fil qui travaille en arrière-plan et qui est en fait responsable de l'activité de la caméra. Et en fait, nous devons nous inscrire au tout début de notre activité:

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

Non seulement cela, vous devez également ajouter le début et la fin de BackgroundThread ici:

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

Quant à mBackgroundHandler, il doit être ajouté à tous nos rappels qui nécessitent un gestionnaire et où nous, sans vous inquiéter, avons écrit null à la place.

La chose la plus intrigante est que nous ne démarrons PAS ce fil d'arrière-plan lorsque nous ouvrons l'application, car il est facile à voir dans le texte du programme. Autrement dit, cela commence implicitement et sans notre aide. Mais nous devons l'arrêter et l'exécuter en modes onPause () et onResume (). Une sorte de contradiction se pose ici.

Mais maintenant, l'image est enregistrée avec succès dans un fichier. Ceci est facile à vérifier en exécutant l'application. Comme le dit le dicton, la couronne du travail est avant tout une récompense.



Certes, l'image se situe en quelque sorte de son côté, mais résoudre ce problème est la tâche des générations futures.

Liste complète du programme
 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/fr468083/


All Articles