Android Camera2 API aus dem Wasserkocher



Vor nicht allzu langer Zeit war ich mit meinem Roboter beschäftigt und versuchte, ein ANDROID-Smartphone darauf zu setzen. Meine, das heißt seine Aufgabe war es, den Wagen evolutionär weiterzuentwickeln. Damit sie sozusagen die Welt mit seinen Sensoren fühlen, ihn mit ihrem Auge (Kamera) ansehen, mit einem Mikrofon hören und über die Freisprecheinrichtung schwören konnte. AVR-Ressourcen reichten natürlich nicht aus, und so bewegte sich der Mikrocontroller auf dem Wagen auf eine niedrigere Ebene, irgendwo im Rückenmark, um Motoren und verschiedene unkonditionierte Reflexe zu steuern.

Aber eine seltsame Sache, als ich anfing, eine Anwendung für ein Smartphone zu schreiben, begann ein schlechtes IDE ANDROID STUDIO, meinen Code ständig zu streichen und ihn als veraltet zu bezeichnen.

Kamera = Kamera. open ();

Besonders, wie Sie sehen können, in den Bereichen, in denen ich versucht habe, mit der Kamera zu arbeiten. Es war sehr enttäuschend, weil ich im Internet gelesen und hier , hier , hier und sogar hier viele Lektionen über die Arbeit mit Android und Kamera gelernt habe . Da draußen war nichts durchgestrichen. Und es wurde die Kamera-API genannt. Alles dort war einfach und logisch. Aber Google hat mich hartnäckig zu einer Art Camera2-API getrieben .

Ich habe dort nachgesehen und bin einfach in Schwierigkeiten mit der Anzahl der verschiedenen Rückrufe, Builder, Handler und Loopers für jede Zeile des Demo-Codes geraten. Es war völlig unverständlich, wie man das angehen sollte, wenn man ein gewöhnlicher Amateur und kein Android-Entwickler ist. Darüber hinaus gibt es heute sogar einige Artikel über die Camera2-API im Netzwerk, obwohl dieses Update wie vor vier Jahren zu sein schien. Aber alles, was ich fand, war ein Artikel in Hacker im Jahr 2016, ein dreiteiliger Beitrag von ukrainischen Brüdern desselben Jahres, ein doppelter Beitrag über Habré im Jahr 2017 und ein Artikel Understanding Camera2 vom japanischen Megaphon Tomoaki Imai. Und damit meine ich auch einige strukturierte und formalisierte Informationen und keine Codeausschnitte wie „Sehen, wie ich kann“ und Blätter im Stil „Schauen Sie sich das Code-Pliz an, nichts funktioniert für mich“, die im Internet verstreut sind.

Und jetzt, wenn Sie sich immer noch fragen, warum ich meinen Beitrag zu diesem Thema kürzen musste
schon 2019, dann willkommen bei cat.

Eigentlich hat mir der Artikel vom Hacker gefallen, weil er mir zumindest einen Überblick darüber gab, warum zum Teufel das neue Paradigma der Arbeit mit der Kamera eingeführt wurde. Es wurde auch klar, warum einige neue Methoden benötigt wurden, die nicht in der vorherigen API enthalten waren. Es war jedoch völlig unmöglich, ein Arbeitsprogramm für diesen Artikel zu schreiben, da die Autoren anscheinend wie echte Hacker nur die Namen der Methoden zitierten. Vielleicht gibt es noch ein paar Zeilen zusätzlichen Codes. Aber das war mir kategorisch nicht genug.

Der Beitrag auf Habré war gut, ich behaupte nicht, und ich habe sogar die ersten Absätze verstanden, aber das war der Punkt, weil der Autor des Beitrags die Kamera mit RxJava2 besiegt hat, was mich automatisch von der Anzahl weiterer Leser ausgeschlossen hat. Normalerweise würde ich mich mit OOP befassen, aber hier ist eine Art reaktive Programmierung.

Der japanische Artikel war noch besser, auch wenn er nicht in seiner Muttersprache schrieb. Aber ich kenne Kotlin auch nicht, obwohl ich mich natürlich für die einheimischen Entwickler freute und die Kürze seiner Syntax im Gegensatz zu den JAVA-Fadennudeln auf der Google-Entwicklerseite schätzte (mit dieser Seite war mir übrigens auch klar, dass es dort war eindeutig nicht viel).

Der letzte Artikel, der mir praktischen Nutzen brachte, war ein dreiteiliger Beitrag aus der brüderlichen Ukraine. Ich habe dort sogar etwas gestartet und dort etwas gesehen. Leider war der Autor des Artikels im dritten Teil sehr müde und begann auch, Code in Fragmenten herauszugeben, die nicht zu meinem gesamten Arbeitsleben passten. Außerdem war der Autor am Ende völlig verärgert, weil er das Bild der falschen Farbpalette bekam, für die er zählte.



Angenommen, die Version von Lollipop ist 5.0 und es gibt einen Fehler. Sie müssen auf Lollipop 5.1 aktualisieren und dann ist alles in Ordnung. Aber irgendwie noch nicht. Und auch schädliche Ukrainer, die am JAVA SCRIPT-Artikel befestigt waren, und beim Kopieren des Codes wurde eine wilde Menge Müll in den Programmtext gegossen. Leute, das kannst du nicht ... Ich musste ein spezielles Plug-In in Firefox installieren.

Deshalb musste ich das gefallene Banner aufheben und den Job bis zum Ende beenden, was natürlich eine titanische mentale Anstrengung erforderte. Und da es irgendwie schade war, nur mit der Erkenntnis zufrieden zu sein, dass ein Arbeitsergebnis erzielt wurde, wollte ich es mit Amateur-Teekannen teilen. Darüber hinaus ist dies noch lange nicht das Ende der Geschichte. Ich muss noch lernen, wie man Live-Videos von der Android-Kamera auf den Computer überträgt (der Roboter muss sich entwickeln, das ist das Gesetz der Evolution) und nicht in Stücken, wie ich geblendet habe, sondern reibungslos. Und es gibt solche Mont-Blanc-Hindernisse in Form von Mediencodecs und anderen Dingen, die nur Weißblech enthalten.

Andererseits kann das Ganze zu völliger Enttäuschung führen. Und nicht, weil sie den Mont Blanc nicht besteigen können.

Und weil Hersteller von Smartphones jetzt völlig neue Ansätze für die Kameraherstellung entwickeln.
Vor einem Monat gab es ein Gerücht über Nokia-Smartphones mit fünf Hauptkameras. Wie soll man damit umgehen? Ein interessantes und vielversprechendes Gerücht oder eine andere seltsame Sache? Unabhängig davon, wie einzigartig ein solches Design aussehen mag, wird Nokia sicherlich kein Pionier bei der Einführung einer ungewöhnlichen Anzahl von Objektiven und Sensoren in kompakte Geräte sein können. Die Light L16-Kamera war bereits 2015 mit 16 Objektiven ausgestattet, und das Unternehmen hat offensichtlich einen neuen Prototyp in Betrieb. Oben sehen Sie, wie es möglicherweise aussehen könnte.

Nach dem Erscheinen der Dreifachkamera im Huawei P20 Pro klingt der Übergang zu einem Smartphone mit fünf Kameras nicht mehr so ​​komisch wie vor ein paar Jahren. Die Hauptfrage bleibt jedoch: Worum geht es?
Was tun mit so vielen Objektiven?

Das erste, was mir in den Sinn kommt, ist die Vielfalt der auf dem modernen Smartphone-Markt verfügbaren Arten von Kamerasensoren und die Möglichkeit, weitere hinzuzufügen. Warum zwischen Weitwinkel, Tele, Porträt mit Bokeh oder Monochrom wählen, wenn Sie all dies in einem Gerät erhalten können?

Da ein solches Design theoretisch möglich wäre, wäre es ziemlich umständlich, es zu verwenden. Die Software muss automatisch zwischen den Modi wechseln oder dem Benutzer eine Reihe komplexer Optionen anbieten. Darüber hinaus wäre die Entwicklung eines solchen Designs für alle zweifelhaften Vorteile einer solchen Lösung sehr teuer. Jede Kamera würde größtenteils unabhängig voneinander funktionieren, und es war unwahrscheinlich, dass Käufer eine große Anzahl von Modi verwenden würden. Und es ist nicht klar, wie viel sie bereit wären, für solche Funktionen zu bezahlen. Kameras mit mehreren Modulen sollten daher mehr tun können, um den Benutzer anzulocken.

Huawei P20 Pro bietet bereits seine Version an, wie mehrere Kameramodule zusammenarbeiten können, um ein interessantes Ergebnis zu erzielen. Wir sprechen über Technologien von Huawei wie Monochrom und Hybrid Zoom. Der erste verbessert den Dynamikbereich von Standardframes, indem reguläre RGB-Daten mit einem lichtempfindlichen Schwarzweißsensor kombiniert werden. Und Hybrid Zoom verspricht noch mehr: Es kombiniert Daten von mehreren Kameras, um die Auflösung des Bildes für einen besseren Zoom zu erhöhen. Beim P20 Pro 8 MP können Sie mit dem Teleobjektivsensor mit einer Auflösung von 10 MP bei 3- und 5-fachem Zoom aufnehmen.
Höhere Auflösung - mehr Flexibilität

Die erste Light L16-Kamera funktionierte ähnlich und verwendete Periskopspiegel, um die Kameramodule in ein kompaktes Gehäuse einzubauen. Die Kamera nahm je nach Zoomstufe Daten von mehreren Modulen mit 28, 70 und 150 mm auf. Das Ergebnis war eine große 52-MP-Aufnahme aus 10 leicht unterschiedlichen Winkeln bei einer Vergrößerung von bis zu 5x. Das für Smartphones entwickelte Konzept des neuen Modells funktioniert mit 5-9 Objektiven. Ein solches Kameramodul kann große 64-Megapixel-Bilder aufnehmen.

Diese Idee der Mehrfachaufnahme bietet auch Vorteile bei Aufnahmen bei schlechten Lichtverhältnissen und in HDR mit mehreren Blendenöffnungen. Der hochwertige Effekt der Rahmentiefe ist auch durch die gleichzeitige Software-Emulation und die Verwendung mehrerer Brennweiten möglich.

Light L16 brachte Enttäuschung, aber die Idee selbst war vielversprechend. Und die nächste Generation mit Erfolg könnte sich als lohnenswert erweisen. Das Unternehmen behauptet, dass Ende des Jahres ein Smartphone angekündigt wird, auf dem die neueste Lösung mit mehreren Objektiven installiert wird.

Dieselbe Idee lässt sich auf die Erfahrung von Nokia bei der Implementierung mehrerer Kameras zurückführen, wenn man bedenkt, dass 2013 in Pelican Imaging investiert wurde. Im Gegensatz zu Licht ist der Sensor hier viel kleiner. Trotzdem verspricht die Technologie sehr ähnliche Vorteile, einschließlich der Änderung des Softwarefokus, der Tiefenmessung und der Vergrößerung des endgültigen Bildes. Leider hat Tessera das Unternehmen im Jahr 2016 gekauft, aber die Idee selbst hat die Nokia-Ingenieure möglicherweise nicht verlassen.

Zeiss, der derzeitige Fotopartner von Nokia, hat ein Patent für umschaltbaren Zoom, aber wir haben noch nichts von ihnen über das Design mit mehreren Objektiven gehört. Vielleicht sieht der andere Partner von Nokia, FIH Mobile, vielversprechender aus. Dieses Unternehmen gehört Foxconn, bringt Nokia-Handys auf den Markt und investierte 2015 in Light, um eine Lizenz zur Nutzung der Technologie zu erhalten.

Und wenn Sie der Meinung sind, dass das Nokia-Leck und der Light-Prototyp etwas gemeinsam haben, ist dies kein Zufall. Verbindet die beiden Foxconn-Unternehmen. Wird das Nokia-Smartphone das erste sein, das Technologie von Light verwendet?
Also ist das die Zukunft?

Ultrahohe Auflösung ist kein neues Konzept. 2014 verwendete Oppo Find 7 ein ähnliches Prinzip, und der Hybrid Zoom von Huawei ermöglichte es der Technologie, mit mehreren Kameras zu arbeiten. In der Vergangenheit bestand das Hauptproblem der Technologie in hohen Leistungsanforderungen, Algorithmusqualität und Stromverbrauch. Auf der Seite moderner Smartphones stehen jedoch leistungsstärkere Signalverarbeitungsprozessoren, energieeffiziente DSP-Chips und sogar verbesserte Funktionen neuronaler Netze zur Verfügung, was die Bedeutung des Problems allmählich verringert.

Hohe Detailgenauigkeit, optische Zoomfunktionen und ein benutzerdefinierter Bokeh-Effekt stehen ganz oben auf der Liste der Kameraanforderungen für ein modernes Smartphone, und die Multi-Kamera-Technologie kann dazu beitragen. Anstatt verschiedene Funktionen mit separaten Kameras auszuführen, besteht die Zukunft der mobilen Fotografie darin, mehrere Kameras zu kombinieren, um erweiterte und flexiblere Funktionen bereitzustellen.

Es gibt noch Fragen zur Lichttechnologie, insbesondere zum Kleben von Bildern mit unterschiedlichen Brennweiten. Wir können nur warten - wir werden sehen, was sich in der zweiten Generation der Technologie zum Besseren wenden wird.



Es wird nicht einfach sein, eine solche Baugruppe über Kameras mit Griffen im Programm zu konfigurieren. Vielleicht wird der Kreis geschlossen, und um ein Bild aufzunehmen, müssen wir erneut eine explizite Absicht mit dem Inhalt schreiben, z. B. „Kamera, bitte machen Sie das Bild selbst, wie Sie können, aber wunderschön, und geben Sie es mir in meiner Aktivität zurück“.

Aber für den Moment haben wir noch ein wenig. Deshalb fahren wir sofort fort.

Warum brauchte all das Google?

Es scheint, dass das Ganze sicheres und korrektes Multithreading ist (und es wird auch möglich, alle Arten von Effekten und Filtern direkt auszuführen). Wenn Sie in Vanilla JAVA bei Bedarf Mutexe, Synchronisationen und Semaphoren überall kompetent pushen müssen, übernimmt Google hier fast alles. Sie müssen nur Rückrufe im Programmtext registrieren, die bei Bedarf aufgerufen werden. Das heißt, Sie senden beispielsweise eine Anfrage zum Einschalten der Kamera:

mCameraManager.openCamera() 

Aber das ist kein Team, das ist eine Bitte. Sie können nicht sofort mit der Kamera arbeiten. Erstens braucht sie Zeit, um sich einzuschalten, und zweitens hat der Android so viele wichtige Dinge zu tun, und Ihr Wunsch wird in eine ziemlich große Warteschlange gestellt. Aber dann müssen wir nicht warten, bis sich die Kamera in einer Schleife im Hauptthread öffnet und die gesamte Anwendung aufhängt (wir erinnern uns immer noch, was im UI-Stream getan werden kann und was nicht). Daher senden wir unseren Wunsch und öffnen, während wir unserem Geschäft nachgehen, die Ansichten, schreiben „Hallo Welt“, konfigurieren Schaltflächenhandler und dergleichen.

In der Zwischenzeit, nach einigen zehn und hundert Millisekunden, erreicht das Betriebssystem endlich die Hände der Kamera und initialisiert sie. Und sobald die Kamera bereit ist, wird der gleiche Rückruf ausgelöst (es sei denn, Sie haben ihn natürlich im Voraus registriert).

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

Das heißt, die Kamera ist jetzt geöffnet und kann dort etwas tun: Zeigen Sie das Bild von der Kamera in der Ansicht an, leiten Sie es zum Speichern weiter und so weiter.

Infolgedessen läuft die gesamte Arbeit mit der Kamera darauf hinaus, alle Arten von Rückrufen zu verschreiben. In ihrem edlen Wunsch gingen die Entwickler von Google jedoch etwas zu weit und, wie der japanische Genosse richtig bemerkte:
Einer der Gründe, warum Camera2 ratlos ist, ist die Anzahl der Rückrufe, die Sie für eine Aufnahme benötigen.

Das reicht aber nicht. Das vollständige Schema der Kamera hat diesen fantastischen Look



Glücklicherweise kann es für den Anfang auf ein viel attraktiveres Bild reduziert werden.



Dort ist zwar alles auf Englisch, aber wie sie sagen, ist es ohne Worte klar. Für den Anfang haben wir genug von diesem Design. Wir werden die Kamera öffnen, das Bild auf dem Smartphone-Bildschirm anzeigen, ein Bild aufnehmen und sobald es fertig ist (Rückrufe erneut), wird der Hörer dieses Ereignisses arbeiten und das Bild in eine Datei schreiben.

Erste Schritte beim Erstellen von Code

Wir werden ein neues Projekt in der Android Studio-IDE erstellen, die Mindestversion von SDK 22 auswählen, um grüne Bilder zu vermeiden, und leere Aktivitäten bestellen (oder noch besser Version 23, da sonst Probleme mit Berechtigungen auftreten können). Genug für den Anfang. Auch die Berechtigungen im Manifest müssen noch nicht erfüllt werden.

Wir beginnen mit der Erstellung einer Instanz der CameraManager-Klasse. Dies ist ein Systemdienstmanager, mit dem Sie verfügbare Kameras finden, deren für die Arbeit erforderliche Eigenschaften abrufen und Aufnahmeeinstellungen für Kameras festlegen können.

Und wir werden die folgenden Eigenschaften sehen:

Kamera-ID (0, 1, 2 ....)
Richtung, in die die Kamera gerichtet ist (vorwärts, rückwärts)
Kameraauflösung

Zuerst erhalten wir die Liste der Kameras in Form eines String-Arrays, drucken dann die erforderlichen Merkmale in einer Schleife und schreiben sie in das Protokoll.

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

Das Protokoll wird ungefähr so ​​aussehen:
 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 


Wir brauchen diese Informationen im Prinzip nicht wirklich, jede Teekanne weiß bereits von der vorherigen API, dass Kameras Identifikatoren von Null und darüber hinaus haben, dass Sie niemanden mit einer Auflösung von 1920 x 1080 überraschen werden, und noch mehr mit dem JPEG-Format. Tatsächlich werden diese Daten bereits von einer „erwachsenen“ Anwendung benötigt, die für die Produktion bereit ist und auf deren Grundlage Menüs für den Benutzer ausgewählt werden können, und so weiter. In unserem einfachsten Fall ist im Allgemeinen alles klar. Aber da alle Artikel damit beginnen, werden wir beginnen.

Nachdem wir sichergestellt haben, dass die vordere Kamera die Identifikationsnummer „1“ und die hintere „0“ hat (aus irgendeinem Grund sind sie im String-Format angegeben) und dass uns die Auflösung von 1920 x 1080 und das Speichern der JPG-Datei zur Verfügung stehen, werden wir den Angriff fortsetzen.

Wir erhalten die erforderlichen Berechtigungen

Zunächst müssen wir uns um eine Reihe von Berechtigungen kümmern. Dazu müssen Sie Folgendes in das Manifest schreiben:

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

Die erste ist für die Kamera verständlich, die zweite dient zum Schreiben von Bildern in eine Datei (und dies ist keine externe Speicherkarte, wie es aus der Bedeutung des Wortes EXTERN hervorgeht, sondern ein ziemlich nativer Smartphone-Speicher).

Android kümmert sich aber auch um uns. Ab der Lollipop-Version reichen die im Manifest angegebenen Berechtigungen nicht mehr aus. Jetzt ist es erforderlich, dass der Benutzer seine Zustimmung zum Öffnen der Kamera und zum Schreiben von Daten in den Speicher genehmigt.

Dazu müssen Sie im einfachsten Fall Folgendes hinzufügen:

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

Warum im einfachsten? Weil es nicht notwendig ist, solche Dinge in einem UI-Stream zu tun. Erstens hängt der Thread, während der Benutzer mit seinen ungeschickten Fingern auf den Bildschirm stößt, und zweitens kann die Anwendung im Allgemeinen abstürzen, wenn Sie die Kamera weiter initialisiert haben. In diesem Demo-Fall ist alles in Ordnung, es wird jedoch generell vorgeschrieben, den gewünschten Rückruftyp für diesen Fall zu verwenden:

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

Obwohl ich das alles vorher nicht wusste, habe ich die gewünschte Aktivität über AsyncTask gestartet und noch früher einen neuen Thread erstellt, wie in Java.

Kamera kochen

Der Einfachheit halber werden wir auf Anraten kluger Leute alles, was mit Kameras zu tun hat, in einer separaten Klasse herausnehmen und die CameraService-Klasse erstellen. Dort platzieren wir die Initialisierung der Kameras und notieren dann alle erforderlichen Rückrufe.

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

Erstellen Sie im Hauptthread eine Instanz von mCameraManager und füllen Sie damit das Array der myCameras-Objekte. In diesem Fall gibt es nur zwei davon - die Front- und die Selfie-Kamera.

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

In der öffentlichen void openCamera () -Methode sehen Sie die folgende Zeile:

  mCameraManager.openCamera(mCameraID,mCameraCallback,null); 

Von hier aus führt der Pfad zum ersten Rückruf des CameraDevice- Kamerastatus . StateCallback. Er wird uns mitteilen, ob die Kamera geöffnet oder geschlossen ist oder ob überhaupt nichts vorhanden ist, und einen Fehler melden. Wir werden es in die Methoden der CameraService-Klasse schreiben.

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

Wenn die Kamera für den Betrieb verfügbar ist (die Methode public void onOpened (CameraDevice camera) {} hat funktioniert), schreiben wir dort unsere weiteren Aktionen, z. B. indem wir die Methode createCameraPreviewSession () aufrufen. Es wird uns helfen, das Bild von der Kamera auf die Ansicht zu bringen und weiter damit zu arbeiten.

CreateCameraPreviewSession

Hier versuchen wir, ein Bild (Datenstrom) auf der mImageView-Textur anzuzeigen, die bereits im Layout definiert ist. Sie können sogar bestimmen, mit welcher Auflösung in den Pixeln.

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

Und wenn dieselbe Sitzung fertig ist, wird der oben genannte Rückruf aufgerufen und wir beginnen mit dem Ausdruck der Googloder: „Anzeigen der Kameravorschau“. Hier können diejenigen, die dies wünschen, die Autofokus- und Blitzeinstellungen anpassen, aber im Moment werden wir mit den Standardeinstellungen auskommen.

Erstellen Sie ein Layout

Jetzt müssen wir sozusagen die Farben auf der Leinwand skizzieren und ein brillantes Bild im Stil von erstellen
"Drei Bildschirmtasten und eine Ansicht."
 <?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 



Der Prozess ist ziemlich trivial und jeder kann sich hier umdrehen, wie er will. Aber die Namen der Schaltflächen direkt in das Layout zu schreiben, ist ebenfalls schlecht und in Arbeitsversionen ist dies nicht erforderlich.

Dementsprechend erstellen wir in der Aktivität selbst Listener, dh Listener für Schaltflächen und Ansichten.

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

Die Tastenbelegung ergibt sich aus den Namen, wir lassen die dritte Taste für das zukünftige Bild.

Und wenn Sie jetzt alle Codeteile zusammenbringen, dann

Holen Sie sich Folgendes:
 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(); } } 


Wir laden, starten, arbeiten!

Wenn Ihnen dies nicht ausreicht, kann der Aufnahmevorgang im Allgemeinen stark diversifiziert werden. Unser japanischer Tomoaki zeigt und erklärt wie und bringt ein schönes Diagramm.



Zunächst muss die Kamera fokussieren. Dies verursacht normalerweise keine Probleme, erfordert jedoch manchmal mehrere Versuche, die auch über einen Rückruf implementiert werden.

 CameraCaptureSession.StateCallback(). 

Dann wechselt die Kamera in den Vorschau-Modus von PRECAPTURE. Zu diesem Zeitpunkt berechnet die Kamera die Belichtung, den Weißabgleich und die Blende (ich wusste früher, was es in der Kindheit war, aber jetzt geht dieses Wissen verloren). Manchmal kann ein Rückruf eine CONTROL_AE_STATE_FLASH_REQUIRED-Anforderung zurückgeben, was bedeutet, dass es schön wäre, den Flash einzuschalten. Es kann übrigens automatisch eingeschaltet werden - setAutoFlash (mPreviewRequestBuilder).

Wenn alle für die Aufnahme erforderlichen Parameter definiert sind, gibt der Rückruf den Status CONTROL_AE_STATE_CONVERGED zurück und signalisiert uns, dass die Kamera bereit ist, ein Bild aufzunehmen.

Auf der googloid Seite ist all dies bereits in den Beispielen enthalten, und wenn Sie die Geduld haben, diese Minenfelder und Drahtzäune zu durchbrechen, dann ist Ehre für Sie.

Nehmen Sie ein Bild auf und speichern Sie es in einer Datei

Genau hier begann ich Probleme zu haben.Nein, nach dem obigen Blockdiagramm (nicht japanisch, sondern das vorherige) ist alles nicht sehr kompliziert. Wir warten darauf, dass die Kamera das Bild aufnimmt. Nach der Verarbeitung mit der CamerCaptureSession-Klasse steht sie als Surface-Objekt zur Verfügung, das wiederum mit der ImageReader-Klasse verarbeitet werden kann.

Die Wahrheit ist, dass es einige Zeit dauert, ein ImageReader-Objekt erneut zu erstellen. Wir warten auf diese Zeit im nächsten Listener namens OnImageAvailableListener. Und schließlich speichern wir mit Hilfe der Instanz der letzten ImageSaver-Klasse das Bild in einer Datei und natürlich auch asynchron, da ImageSaver hier ausführbar ist.

Das Problem war, dass ich diesen ImageReader nirgendwo anhängen konnte, da der Rückruf von CameraCaptureSession.StateCallback () bereits damit beschäftigt war, das Video in die Ansicht des Smartphones zu übertragen. Und wenn ich eine neue Sitzung gemacht habe, hat Android die Anwendung vorhersehbar verflucht und zum Absturz gebracht. Infolgedessen (fragen Sie mich nicht, wie) gelang es mir, das Pferd und das zitternde Reh in einer createCameraPreviewSession () -Methode zu überqueren, mit der das Kamerabild nur in der Ansicht angezeigt wurde.

Hier ist dieser Code vor:

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

Und hier ist er hinterher:

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

Der Unterschied ist, abgesehen von der ImageReader-Instanzdefinition oben, fast schwer zu erkennen. Gerade zur Oberfläche hinzugefügt, durch Komma getrennt mImageReader.getSurface () und fertig. Aber bis Sie dazu kommen ...

Von diesem Moment an machten die Dinge mehr Spaß und Sie konnten die dritte Bildschirmtaste "Take a Picture" verwenden. Wenn es gedrückt wird, wird die Methode makePhoto () aufgerufen (na ja, wer hätte das gedacht):

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

Und unmittelbar danach schreiben wir den OnImageAvailableListener-Listener:

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

Während er nichts tut, signalisiert er einfach mit einem Toast, dass alles in Ordnung ist. Sie können das Bild in einer Datei speichern.

Und dafür brauchen wir die Datei selbst:

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

Und auch ein spezielles Objekt der ImageSaver-Klasse, das Daten schnell von einem Bild in einen Bytepuffer und von dort in eine Binärdatei überträgt.

Diese Klasse ist statisch und ausführbar. Deshalb platzieren wir es in MainActivity selbst:

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

Und damit es funktioniert, schreiben wir zusätzlich in den OnImageAvailableListener-Listener:

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

WARTEN! OH Scheiße !!

Was ist mBackgroundHandler noch ??? Trotzdem funktionierte es perfekt ohne ihn.

Tatsächlich ist die richtige Frage: Wie hat es überhaupt ohne funktioniert? Denn wie in den Beispielen von Google dargelegt, bietet BackgroundHandler BackgroundThread, einen Thread, der im Hintergrund arbeitet und tatsächlich für die Kamera verantwortlich ist. Und tatsächlich sollten wir uns gleich zu Beginn unserer Aktivität registrieren:

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

Darüber hinaus müssen Sie hier auch den Start und Stopp von BackgroundThread hinzufügen:

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

Der mBackgroundHandler muss zu allen Rückrufen hinzugefügt werden, für die ein Handler erforderlich ist, und bei denen wir, ohne uns Sorgen zu machen, stattdessen null geschrieben haben.

Das Interessanteste ist, dass wir diesen Hintergrund-Thread beim Öffnen der Anwendung NICHT starten, da dies aus dem Programmtext leicht ersichtlich ist. Das heißt, es beginnt implizit und ohne unsere Hilfe. Aber wir müssen es stoppen und in den Modi onPause () und onResume () ausführen. Hier entsteht ein Widerspruch.

Jetzt wird das Bild erfolgreich in einer Datei gespeichert. Dies lässt sich leicht überprüfen, indem Sie die Anwendung ausführen. Wie das Sprichwort sagt, ist die Krone der Arbeit vor allem Auszeichnungen.



Das Bild liegt zwar irgendwie auf der Seite, aber dieses Problem zu lösen, ist die Aufgabe zukünftiger Generationen.

Vollständige Auflistung des Programms
 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/de468083/


All Articles