Wir haben also herausgefunden, wie man mit der Camera2-API Fotos und Videos aufnimmt. Es bleibt nur zu lernen, wie der Videostream vom Android-Gerät von außen an die leidenden Empfänger übertragen wird. Das ultimative Ziel ist, wie bereits wiederholt gesagt wurde, die Intellektualisierung von Robotern - wir setzen ein Smartphone darauf und verwandeln sozusagen einen Affen in eine Person. Media Codec hilft uns dabei. Und natürlich die neue Camera2-API.
Wen kümmert es bitte unter der Katze.
Details zum Roboterprojekt finden Sie
hier. Im Moment werden wir jedoch Videos direkt von diesem (oder besser gesagt von einem daran angeschlossenen Android-Smartphone) auf einen elektronischen PC streamen.
Was brauchen wir dafür?
Um einen Videostream vom Smartphone-Bildschirm an einen anderen Ort zu übertragen, muss dieser (Stream) zunächst in ein geeignetes verkleinertes Format konvertiert werden (er ist zu dick, um Frame für Frame zu übertragen), Zeitstempel (Zeitstempel) eingefügt und in binärer Form an den Empfänger gesendet werden . Welches wird die inverse Decodierungsoperation ausführen.
Genau diese Low-Level-Schwarzfälle befasst sich die Media Codec-Klasse seit 2013 ab dem Veröffentlichungsdatum von Android 4.3.

Eine andere Sache ist, dass die frühere Annäherung an die Videokodierung im Gegensatz zu heute nicht so einfach war. Um ein Bild von der Kamera zu erhalten, war es notwendig, Tonnen eines
mysteriösen Codes zu verwenden, in dem, wie in den Zaubersprüchen der Jakut-Schamanen, die einzige Ungenauigkeit zu einem vollständigen Absturz der Anwendung führen konnte. Fügen Sie dazu die vorherige Kamera-API hinzu, bei der Sie anstelle von vorgefertigten Rückrufen selbst verschiedene synchronisierte Stifte schreiben mussten, und diese Aktivität ist beispielsweise nichts für schwache Nerven.
Und am wichtigsten ist, dass Sie den
Arbeitscode von weitem betrachten. Alles scheint allgemein klar zu sein. Sie beginnen, Teile in Ihr Projekt zu übertragen - es ist nicht klar, warum es fließt. Eine Korrektur ist jedoch nicht möglich, da die Details schwer zu verstehen sind.
Ja, und von fest
veraltet irgendwie beruhigt. Kurz gesagt, Chaos
Glücklicherweise haben Google-Entwickler das magische Konzept von
Surface eingeführt , mit dem Sie Details auf niedriger Ebene vermeiden können. Zu welchem Preis und was der Entwickler verliert, ist für mich als Laie schwer zu verstehen, aber jetzt können wir fast wörtlich sagen: „Android, nimm diese Oberfläche, auf der das Video von der Kamera angezeigt wird, und ohne dort etwas zu ändern, so wie es ist, Code und weiterleiten. " Und das Erstaunlichste ist, dass es funktioniert. Und mit der neuen Camera2-API weiß das Programm selbst, wann Daten gesendet werden müssen. Neue Rückrufe sind aufgetreten!
Also jetzt das Video kodieren - einfach spucken. Was werden wir jetzt tun?
Wir nehmen den Code aus dem
ersten Artikel und werfen wie üblich alles außer den Tasten und der Kamera-Initialisierung heraus.
Beginnen wir mit dem Layout für die App.<?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/button1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> <Button android:id="@+id/button2" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> <Button android:id="@+id/button3" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
Und beenden Sie mit Media Codec Hooking
Im letzten Beitrag haben wir das Bild von der Kamera auf der Oberfläche angezeigt und mit MediaRecorder ein Video davon geschrieben. Dazu haben wir einfach beide Komponenten in der Oberflächenliste angegeben.
(Arrays.asList(surface, mMediaRecorder.getSurface()).
Hier das gleiche, nur anstelle von mMediaRecorder geben wir an:
(Arrays.asList(surface, mEncoderSurface),
Es stellt sich heraus, so etwas wie:
private void startCameraPreviewSession() { SurfaceTexture texture = mImageView.getSurfaceTexture(); texture.setDefaultBufferSize(320, 240); surface = new Surface(texture); try { mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); mPreviewBuilder.addTarget(surface); mPreviewBuilder.addTarget(mEncoderSurface); mCameraDevice.createCaptureSession(Arrays.asList(surface, mEncoderSurface), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { mSession = session; try { mSession.setRepeatingRequest(mPreviewBuilder.build(), null, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession session) { } }, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } }
Was ist mEncoderSurface? Und dies ist dieselbe Oberfläche, mit der Media Codec arbeiten wird. Nur für den Anfang müssen Sie beide ungefähr auf diese Weise initialisieren.
private void setUpMediaCodec() { try { mCodec = MediaCodec.createEncoderByType("video/avc");
Jetzt muss noch ein einzelner Rückruf registriert werden. Wenn Media Codec plötzlich das Gefühl hat, dass die nächsten Daten für die weitere Ausstrahlung bereit sind, wird er uns über ihn darüber informieren:
private class EncoderCallback extends MediaCodec.Callback { @Override public void onInputBufferAvailable(MediaCodec codec, int index) { } @Override public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) { outPutByteBuffer = mCodec.getOutputBuffer(index); byte[] outDate = new byte[info.size]; outPutByteBuffer.get(outDate); Log.i(LOG_TAG, " outDate.length : " + outDate.length); mCodec.releaseOutputBuffer(index, false); } @Override public void onError(MediaCodec codec, MediaCodec.CodecException e) { Log.i(LOG_TAG, "Error: " + e); } @Override public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { Log.i(LOG_TAG, "encoder output format changed: " + format); } }
Das outDate-Byte-Array ist ein wahrer Schatz. Es enthält fertige Teile eines codierten H264-Videostreams, mit dem wir jetzt tun können, was wir wollen.
Hier sind sie ...

Einige Teile sind möglicherweise zu groß für die Übertragung über das Netzwerk, aber nichts, das System zerlegt sie bei Bedarf selbst und sendet sie an den Empfänger.
Aber wenn es sehr beängstigend ist, können Sie sich selbst zerreißen, indem Sie ein solches Fragment schieben
int count =0; int temp =outDate.length ; do {
Aber jetzt müssen wir aus erster Hand sehen, dass die Daten im Puffer wirklich ein H264-Videostream sind. Senden wir sie daher an eine Datei:
Wir werden im Setup schreiben:
private void setUpMediaCodec() { File mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "test3.h264"); try { outputStream = new BufferedOutputStream(new FileOutputStream(mFile)); Log.i("Encoder", "outputStream initialized"); } catch (Exception e) { e.printStackTrace(); }
Und im Rückruf wo ist der Puffer:
try { outputStream.write(outDate, 0, outDate.length);
Öffnen Sie die Anwendung und drücken Sie die Taste: „KAMERA UND STREAM EINSCHALTEN“. Die Aufnahme startet automatisch. Wir warten ein wenig und drücken den Stoppknopf.
Die gespeicherte Datei geht normalerweise wahrscheinlich nicht verloren, da das Format nicht MP4 ist. Wenn Sie sie jedoch mit einem VLC-Player öffnen oder online
mit ONLINE CONVERT konvertieren , stellen wir sicher, dass wir auf dem richtigen Weg sind. Das Bild liegt zwar auf der Seite, kann aber repariert werden.
Im Allgemeinen ist es natürlich besser, für jedes Ereignis des Aufzeichnens, Fotografierens oder Streamens jedes Mal eine neue Sitzung zu eröffnen und die alte zu schließen. Das heißt, zuerst schalten wir die Kamera ein und starten die nackte Vorschau. Wenn Sie dann ein Bild aufnehmen müssen, schließen Sie die Vorschau und öffnen Sie die Vorschau, jedoch mit befestigtem Bildleser. Wenn wir zur Videoaufzeichnung wechseln, schließen Sie die aktuelle Sitzung und starten Sie die Sitzung mit der angehängten Vorschau und dem daran angeschlossenen Media Recorder. Ich habe dies nicht getan, damit die Sichtbarkeit des Codes nicht beeinträchtigt wird und Sie entscheiden, wie es für Sie bequemer ist.
Und hier ist der ganze Code.
BasicMediaCodec package com.example.basicmediacodec; 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.ActivityInfo; 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.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.StrictMode; 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.BufferedOutputStream; 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"; public static Surface surface = null; CameraService[] myCameras = null; private CameraManager mCameraManager = null; private final int CAMERA1 = 0; private Button mButtonOpenCamera1 = null; private Button mButtonStreamVideo = null; private Button mButtonTStopStreamVideo = null; public static TextureView mImageView = null; private HandlerThread mBackgroundThread; private Handler mBackgroundHandler = null; private MediaCodec mCodec = null;
Und vergessen Sie nicht die Berechtigungen im Manifest.
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET"/>
Also haben wir sichergestellt, dass Media Codec funktioniert. Aber damit Videos in eine Datei zu schreiben, ist irgendwie geistlos. Media Recorder kann diese Aufgabe viel besser bewältigen und fügt Ton hinzu. Daher werden wir den Dateiteil wieder wegwerfen und einen Codeblock zum Streamen von Videos zum Netzwerk unter Verwendung des udp-Protokolls hinzufügen. Es ist auch sehr einfach.
Zunächst initialisieren wir den UDP-Server praktisch.
DatagramSocket udpSocket; String ip_address = "192.168.1.84";
Und im selben Rückruf, in dem wir Bereitschaftsdaten an den Stream für die Datei gesendet haben, werden wir sie jetzt in Form von Datagrammen an unser Heimnetzwerk senden (ich hoffe, jeder hat sie?)
try { DatagramPacket packet = new DatagramPacket(outDate, outDate.length, address, port); udpSocket.send(packet); } catch (IOException e) { Log.i(LOG_TAG, " UDP "); }
Und alle?
Es scheint, aber nein. Die Anwendung wird beim Start aufgehellt. Sie sehen, das System mag es nicht, dass wir im Hauptstrom alle Arten von Datagrammpaketen senden. Aber es gibt keinen Grund zur Panik. Erstens arbeiten wir, obwohl wir uns im Hauptthread befinden, immer noch asynchron, dh, um einen Rückruf auszulösen. Zweitens ist das Senden von udp-Paketen der gleiche asynchrone Prozess. Wir sagen dem Betriebssystem nur, dass es schön wäre, ein Paket zu senden, aber dass wir uns in dieser Angelegenheit vollständig darauf verlassen. Damit Android nicht rebelliert, werden wir zu Beginn des Programms zwei Zeilen hinzufügen:
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy);
Im Allgemeinen wird sich das folgende kleine
elegante Demo-Programm herausstellen:
package com.example.basicmediacodec; 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.ActivityInfo; 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.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.StrictMode; 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.BufferedOutputStream; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.nio.ByteBuffer; import java.util.Arrays; public class MainActivity extends AppCompatActivity { public static final String LOG_TAG = "myLogs"; public static Surface surface = null; CameraService[] myCameras = null; private CameraManager mCameraManager = null; private final int CAMERA1 = 0; private Button mButtonOpenCamera1 = null; private Button mButtonStreamVideo = null; private Button mButtonTStopStreamVideo = null; public static TextureView mImageView = null; private HandlerThread mBackgroundThread; private Handler mBackgroundHandler = null; private MediaCodec mCodec = null;
Ich weiß nicht, wie es anderen geht, aber auf meinem Red Note 7 können Sie sogar sehen, wie Kilobyte an der richtigen Adresse heruntergeladen werden

Und es gibt viele solcher udp-Sockets, wie viel Netzwerkbandbreite ausreicht. Die Hauptsache ist, dass es Adressen gibt, wo. Sie werden eine Sendung haben.
Suchen wir nun auf dem Computer nach der gewünschten Adresse
Ich muss sagen, dass nicht jedes Computerprogramm in der Lage ist, einen H264-Videostream über einen einzigen udp-Kanal ohne zusätzliche Informationen aufzunehmen und zu verarbeiten. Aber manche mögen. Dies ist zum Beispiel der sehr bekannte
VLC Media
Player . Dies ist eine so coole Sache, dass Sie, wenn Sie anfangen, ihre Fähigkeiten zu beschreiben, aus dem Artikel ein ganzes Buch erhalten. Sicher hast du es. Wenn nicht, zieh es an.
Und nach der Beschreibung der Befehle dafür zu urteilen, können udp-Pakete diesen Player verdauen.
URL syntax: file:
Und all diese Quelladressen und Bindungsadressen werden theoretisch nicht benötigt. Es wird nur der Abhörport benötigt.
Und doch dürfen Sie natürlich nicht vergessen, diesen Port abhören zu lassen (Malvar).
Wussten Sie, dass Windows es Ihnen nicht erlaubt, einen Druckbildschirm über den Ressourcenmonitor zu erstellen?Oder Sie können die Firewall überhaupt deaktivieren (ich empfehle es nicht).Nachdem wir diese Dornen überwunden haben, starten wir den VLC-Player mit unserer Adresse und genießen den leeren Bildschirm. Kein Video.Wie so
Und so. Sie haben wahrscheinlich die neueste Version von VLC 3.08 Vetinari? Das war's, in dieser Version von udp wird es für veraltet erklärt und außerdem wurde es getrunken.Die Logik der Entwickler des Players ist also klar. Heutzutage müssen nur wenige Menschen den Bare-UDP-Kanal verwenden, weil:Daher verwenden normale Menschen natürlich übergeordnete Protokolle RTP und andere. Das heißt, an den Fingern - Sie schreiben einen Server, der ohnehin udp (aus Geschwindigkeitsgründen) verwendet, aber gleichzeitig Steuerinformationen mit dem Client austauscht, an den das Video gestreamt wird. Was ist seine Bandbreite, ist es notwendig, den Cache für Daten zu vergrößern oder zu verkleinern, welche Bilddetails jetzt optimal sind und so weiter und so fort. Auch hier wird manchmal Ton benötigt. Und er braucht eine Synchronisation mit dem Video.Schauen Sie, die Jungs von Odnoklassniki mussten sogar ihr Protokoll für das Streaming einreichen . Aber ihre Aufgaben sind natürlich viel wichtiger - Videos mit Katzen an zig Millionen Hausfrauen auf der ganzen Welt zu senden. Dort verwalten Sie keinen einzigen udp-Kanal.Aber wir sind irgendwie traurig, unseren RTP-Server auf Android zu schreiben. Wahrscheinlich finden Sie sogar fertige und sogar kostenlose, aber versuchen wir, Entitäten vorerst nicht zu komplizieren. Nehmen Sie einfach die Version des VLC-Players, in der das UDP-Streaming noch funktioniert hat.Laden Sie also von hier aus VLC 2.2.6 UmbrellaInstall anstelle oder neben dem alten ( dh neuen VLC) herunter , wie Sie möchten.Wir starten und sehen wieder einen leeren Bildschirm.Und das alles, weil wir die Verwendung des H264-Codecs offensichtlich nicht konfiguriert haben. VLC könnte also den Codec automatisch auswählen, wenn es sich um eine Datei handelt (in den Einstellungen wurde zunächst die automatische Auswahl angegeben). Aber sie werfen einen Byte-Stream über einen einzelnen Kanal, und es gibt Dutzende von Codecs, die VLC unterstützt. Wie kann er herausfinden, welche er beantragen soll?Deshalb installieren wir den Codec mit Gewalt.
Und jetzt genießen wir die Übertragung von "Live" -Videos. Das einzige ist, dass es aus irgendeinem Grund auf der Seite liegt, aber dies kann bereits leicht in den Einstellungen des Videoplayers behoben werden.Und Sie können den Player einfach über die Befehlszeile mit dieser Taste starten: C:\Program Files\VideoLAN\VLC\vlc udp:
Und es wird sich selbst entschlüsseln und sich drehen.Streaming funktioniert also. Es bleibt nur die Integration in das JAVA-Fenster der Robotersteuerungsanwendung. Wir werden uns sehr bald im letzten Teil damit befassen.