Donc, nous avons en quelque sorte compris comment prendre des photos et enregistrer des vidéos en utilisant l'API Camera2. Il ne reste plus qu'à apprendre à transférer le flux vidéo de l'appareil Android vers les destinataires souffrant de l'extérieur. Le but ultime, comme cela a été dit à plusieurs reprises, est l'intellectualisation des robots - nous y mettons un smartphone et, pour ainsi dire, transformons un singe en une personne. Media Codec nous y aidera. Et bien sûr, la nouvelle API Camera2.
Peu importe, s'il vous plaît, sous le chat.
Les détails sur le projet robotique peuvent être trouvés
ici , mais pour l'
instant , nous allons directement diffuser la vidéo depuis celui-ci (ou plutôt, depuis un smartphone Android connecté) vers un ordinateur électronique personnel.
De quoi avons-nous besoin pour cela?
Afin de transférer un flux vidéo de l'écran du smartphone vers un autre endroit, comme vous le savez, il (flux) doit d'abord être converti en un format réduit approprié (il sera trop épais pour transmettre image par image), mettre des horodatages (horodatages) et envoyer sous forme binaire au destinataire . Qui effectuera l'opération de décodage inverse.
Ce sont précisément ces actes noirs de bas niveau que la classe Media Codec traite depuis 2013, à partir de la date de sortie d'Android 4.3.

Une autre chose est que l'approche du codage vidéo antérieure, contrairement à aujourd'hui, n'était pas si simple. Pour obtenir une image de la caméra, il a fallu utiliser des tonnes d'un
code mystérieux dans lequel, comme dans les sorts des chamans Yakut, la seule inexactitude pouvait conduire à un crash complet de l'application. Ajoutez à cela l'API Camera précédente, où au lieu de rappels prêts à l'emploi, vous deviez écrire vous-même différents stylets synchronisés, et cette activité, disons, n'est pas pour les âmes sensibles.
Et surtout, vous regardez le
code de travail de loin, tout semble clair en termes généraux. Vous commencez à transférer en parties à votre projet - il n'est pas clair pourquoi il coule. Mais il est impossible de corriger, car il est difficile de comprendre les détails.
Oui, et de solide
obsolète en quelque sorte à l'aise. En bref, le désordre
Heureusement, pour les esprits lents, les constructeurs de Google ont introduit le concept magique de
Surface , avec lequel vous pouvez éviter les détails de bas niveau. C'est difficile pour moi en tant que profane de comprendre à quel prix et ce que le développeur perd, mais maintenant nous pouvons presque littéralement dire: "Android, prenez cette Surface sur laquelle la vidéo de la caméra est affichée et ne changez rien là, eh bien, comme c'est le cas, encodez et envoyer. " Et le plus étonnant, c'est que ça marche. Et avec la nouvelle API Camera2, le programme lui-même sait quand envoyer des données, de nouveaux rappels sont apparus!
Alors maintenant pour encoder la vidéo - il suffit de cracher. Que ferons-nous maintenant.
Nous prenons le code du
premier article et, comme d'habitude, jetons tout hors de lui sauf les boutons et l'initialisation de la caméra.
Commençons par la mise en page de l'application.<?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>
Et terminez avec le raccordement Media Codec
Dans le dernier article, nous avons affiché l'image de la caméra sur la Surface et écrit une vidéo à partir de celle-ci à l'aide de MediaRecorder. Pour ce faire, nous avons simplement spécifié les deux composants dans la liste Surface.
(Arrays.asList(surface, mMediaRecorder.getSurface()).
Ici, la même chose, seulement au lieu de mMediaRecorder, nous spécifions:
(Arrays.asList(surface, mEncoderSurface),
Il s'avère que quelque chose comme:
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(); } }
Qu'est-ce que mEncoderSurface? Et c'est la même surface avec laquelle Media Codec fonctionnera. Pour commencer, vous devez les initialiser tous les deux approximativement de cette façon.
private void setUpMediaCodec() { try { mCodec = MediaCodec.createEncoderByType("video/avc");
Reste maintenant à enregistrer un seul rappel. Lorsque Media Codec sent soudain que les prochaines données pour une diffusion ultérieure sont prêtes, il nous en informe par son intermédiaire:
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); } }
Le tableau d'octets outDate est un véritable trésor. Il contient des éléments prêts à l'emploi d'un flux vidéo H264 encodé avec lequel nous pouvons maintenant faire ce que nous voulons.
Les voici ...

Certaines pièces peuvent être trop volumineuses pour être transmises sur le réseau, mais rien, le système, si nécessaire, les découpera par lui-même et les enverra au destinataire.
Mais si c'est très effrayant, alors vous pouvez vous déchiqueter en poussant un tel fragment
int count =0; int temp =outDate.length ; do {
Mais pour l'instant, nous devons voir de première main que les données dans le tampon sont vraiment un flux vidéo H264. Par conséquent, envoyons-les dans un fichier:
Nous écrirons dans la configuration:
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(); }
Et dans le rappel où est le tampon:
try { outputStream.write(outDate, 0, outDate.length);
Ouvrez l'application, appuyez sur le bouton: "ALLUMER L'APPAREIL PHOTO ET LE FLUX". L'enregistrement démarre automatiquement. Nous attendons un peu et appuyons sur le bouton stop.
Le fichier enregistré ne sera normalement pas perdu, car le format n'est pas MP4, mais si vous l'ouvrez avec un lecteur VLC ou le convertissez en ligne en
utilisant ONLINE CONVERT , nous nous assurerons que nous sommes sur la bonne voie. Certes, l'image se trouve sur le côté, mais elle est réparable.
En général, pour chaque événement d'enregistrement, de photographie ou de streaming, il est bien sûr préférable d'ouvrir une nouvelle session à chaque fois et de fermer l'ancienne. Autrement dit, nous allumons d'abord l'appareil photo et lançons l'aperçu nu. Ensuite, si vous devez prendre une photo, fermez l'aperçu et ouvrez l'aperçu, mais avec Image Reader fixé. Si nous passons à l'enregistrement vidéo, fermez la session en cours et démarrez la session avec l'aperçu et le Media Recorder qui y sont attachés. Je ne l'ai pas fait, pour que la visibilité du code n'en souffre pas, et vous décidez comment cela vous convient plus.
Et voici tout le 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;
Et n'oubliez pas les autorisations dans le manifeste.
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET"/>
Nous nous sommes donc assurés que Media Codec fonctionne. Mais l'utiliser pour écrire une vidéo dans un fichier est en quelque sorte insensé. L'enregistreur multimédia peut gérer cette tâche beaucoup mieux et il ajoutera du son. Par conséquent, nous jetterons à nouveau la partie fichier et ajouterons un bloc de code pour la vidéo en streaming sur le réseau en utilisant le protocole udp. C'est aussi très simple.
Tout d'abord, nous initialisons le serveur UDP pratiquement.
DatagramSocket udpSocket; String ip_address = "192.168.1.84";
Et dans le même rappel, où nous avons envoyé des données de préparation au flux pour le fichier, nous allons maintenant les envoyer sous forme de datagrammes à notre réseau domestique (j'espère que tout le monde les a?)
try { DatagramPacket packet = new DatagramPacket(outDate, outDate.length, address, port); udpSocket.send(packet); } catch (IOException e) { Log.i(LOG_TAG, " UDP "); }
C'est tout?
Il semblerait, mais non. L'application s'éclaircira au démarrage. Vous voyez, le système n'aime pas que dans le flux principal, nous envoyons toutes sortes de paquets de datagrammes. Mais il n'y a aucune raison de paniquer. Premièrement, bien que nous soyons dans le thread principal, nous travaillons toujours de manière asynchrone, c'est-à-dire pour déclencher un rappel. Deuxièmement, l'envoi de paquets udp est le même processus asynchrone. Nous disons seulement au système d'exploitation qu'il serait bien d'envoyer un paquet, mais que nous comptons entièrement sur lui dans cette affaire. Par conséquent, afin qu'Android ne se rebelle pas, nous ajouterons deux lignes au début du programme:
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy);
En général, le petit programme de démonstration
élégant suivant se révélera:
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;
Je ne sais pas comment font les autres, mais sur mon Red Note 7, vous pouvez même voir comment les kilo-octets sont téléchargés à la bonne adresse

Et il y a beaucoup de telles sockets udp, combien de bande passante réseau est suffisante. L'essentiel est qu'il y ait des adresses où. Vous aurez une émission.
Maintenant, allons chercher l'adresse souhaitée sur l'ordinateur
Je dois dire que tous les programmes informatiques ne sont pas capables d'absorber et de digérer un flux vidéo H264 via un seul canal udp sans aucune information supplémentaire. Mais certains le peuvent. Il s'agit par exemple du
lecteur multimédia
VLC extrêmement connu. C'est tellement cool que si vous commencez à décrire ses capacités, vous obtenez un livre entier dans l'article. Vous l'avez sûrement. Sinon, mettez-le.
Et à en juger par la description des commandes, les paquets udp peuvent digérer ce lecteur.
URL syntax: file:
Et toutes ces adresses source et adresse de liaison, en théorie, ne sont pas nécessaires. Seul le port d'écoute est nécessaire.
Et pourtant, bien sûr, vous ne devez pas oublier d'autoriser ce port à écouter (Malvar)
Saviez-vous que Windows ne vous permet pas de faire un écran d'impression à partir du moniteur de ressources?Ou vous pouvez désactiver le pare-feu (je ne le recommande pas)Donc, après avoir surmonté ces épines, nous lançons le lecteur VLC avec notre adresse et profitons de l'écran vide. Pas de vidéo.Comment ça?
Et ainsi. Vous avez probablement la dernière version de VLC 3.08 Vetinari? Voilà, dans cette version d'udp, il est déclaré obsolète et, de plus, il est foutu.La logique des développeurs du joueur est donc claire. Peu de gens ont besoin d'utiliser le canal udp nu aujourd'hui:Par conséquent, les gens normaux, bien sûr, utilisent des protocoles de niveau supérieur RTP et autres. Autrement dit, vous écrivez un serveur qui utilise udp (pour la vitesse) de toute façon, mais en même temps échange des informations de contrôle avec le client auquel il diffuse la vidéo. Quelle est sa bande passante, est-il nécessaire d'augmenter ou de diminuer le cache pour les données, quels détails d'image sont optimaux maintenant, et ainsi de suite. Encore une fois, le son est parfois nécessaire. Et il a besoin, vous savez, de synchronisation avec la vidéo.Écoutez, les gars d'Odnoklassniki ont même dû déposer leur protocole pour le streaming. Mais leurs tâches, bien sûr, sont beaucoup plus importantes - envoyer des vidéos avec des chats à des dizaines de millions de femmes au foyer à travers le monde. Là, vous ne gérerez pas un canal udp.Mais nous sommes en quelque sorte tristes d'écrire notre serveur RTP sur Android. Probablement, vous pouvez même trouver des éléments prêts à l'emploi et même gratuits, mais essayons de ne pas compliquer les entités pour l'instant. Prenez simplement la version du lecteur VLC où le streaming udp fonctionnait toujours.Alors, téléchargez ici VLC 2.2.6 UmbrellaInstall au lieu de ou à côté de l'ancien (c'est-à-dire le nouveau VLC), comme vous le souhaitez.Nous commençons et voyons à nouveau un écran vide.Et tout cela parce que nous n'avons évidemment pas configuré l'utilisation du codec H264. Ainsi, VLC serait en mesure de sélectionner automatiquement le codec s'il devait traiter le fichier (dans les paramètres initialement, la sélection automatique était spécifiée). Mais ils jettent un flux d'octets sur un seul canal, et il existe des dizaines de codecs pris en charge par VLC. Comment peut-il déterminer lequel appliquer?Par conséquent, nous installons le codec de force.
Et maintenant, nous apprécions la diffusion de vidéo "en direct". La seule chose est que pour une raison quelconque, il se trouve sur le côté, mais cela est déjà facilement corrigé dans les paramètres du lecteur vidéo.Et vous pouvez simplement démarrer le lecteur à partir de la ligne de commande avec cette clé: C:\Program Files\VideoLAN\VLC\vlc udp:
Et il va se décoder et tourner.Le streaming fonctionne donc. Il ne reste plus qu'à l'intégrer dans la fenêtre JAVA de l'application de contrôle du robot. Nous en traiterons très prochainement dans la dernière partie.