Entonces, descubrimos cómo tomar fotos y grabar videos usando la API de Camera2. Solo queda aprender cómo transferir la transmisión de video desde el dispositivo Android a los receptores que sufren desde el exterior. El objetivo final, como se ha dicho repetidamente antes, es la intelectualización de los robots: le ponemos un teléfono inteligente y, por así decirlo, convertimos a un mono en una persona. Media Codec nos ayudará con esto. Y, por supuesto, la nueva API Camera2.
A quién le importa, por favor, debajo del gato.
Los detalles sobre el proyecto robótico se pueden encontrar
aquí , pero por
ahora , transmitiremos directamente el video desde él (o más bien, desde un teléfono inteligente Android conectado a él) a una computadora electrónica personal.
¿Qué necesitamos para esto?
Para transferir un flujo de video desde la pantalla del teléfono inteligente a otro lugar, como ya sabe, primero debe convertirse a un formato reducido adecuado (será demasiado grueso para transmitir cuadro por cuadro), coloque marcas de tiempo (marcas de tiempo) y envíelas en forma binaria al destinatario . Que realizará la operación de decodificación inversa.
Precisamente estos hechos negros de bajo nivel son los que la clase Media Codec ha estado tratando desde 2013, desde la fecha de lanzamiento de Android 4.3.

Otra cosa es que la codificación de video que se aproximaba antes, a diferencia de hoy, no era tan simple. Para obtener una imagen de la cámara, era necesario usar toneladas de un
código misterioso en el que, como en los hechizos de los chamanes Yakut, la única inexactitud podría conducir a un bloqueo completo de la aplicación. Agregue a esto la API de cámara anterior, donde en lugar de devoluciones de llamada ya hechas, tenía que escribir diferentes bolígrafos sincronizados usted mismo, y esta actividad, digamos, no es para los débiles de corazón.
Y lo más importante, si observa el
código de trabajo desde lejos, todo parece estar claro en términos generales. Empiezas a transferir partes a tu proyecto; no está claro por qué se está vertiendo. Pero es imposible de corregir, porque es difícil entender los detalles.
Sí, y de solido
obsoleto de alguna manera a gusto. En resumen, lío
Afortunadamente, para los ingenuos, los creadores de Google han introducido el concepto mágico de
Surface , con el que puedes evitar los detalles de bajo nivel. A qué costo y lo que pierde el desarrollador, es difícil para mí como un laico entender, pero ahora podemos decir casi literalmente: "Android, toma esta Surface en la que se muestra el video de la cámara y sin cambiar nada allí, bueno, como está, código y seguir adelante ". Y lo más sorprendente es que funciona. Y con la nueva API Camera2, el programa mismo sabe cuándo enviar datos, ¡han aparecido nuevas devoluciones de llamadas!
Así que ahora para codificar el video, solo escupe. ¿Qué haremos ahora?
Tomamos el código del
primer artículo y, como siempre, tiramos todo, excepto los botones y la inicialización de la cámara.
Comencemos con el diseño de la aplicación.<?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>
Y termine con el enganche de Media Codec
En la última publicación, mostramos la imagen de la cámara en Surface y escribimos un video usando MediaRecorder. Para hacer esto, simplemente especificamos ambos componentes en la lista de Surface.
(Arrays.asList(surface, mMediaRecorder.getSurface()).
Aquí lo mismo, solo que en lugar de mMediaRecorder especificamos:
(Arrays.asList(surface, mEncoderSurface),
Resulta que algo así como:
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é es mEncoderSurface? Y esta es la misma superficie con la que trabajará Media Codec. Solo para empezar, debe inicializar ambos aproximadamente de esta manera.
private void setUpMediaCodec() { try { mCodec = MediaCodec.createEncoderByType("video/avc");
Ahora queda por registrar una sola devolución de llamada. Cuando Media Codec de repente sienta que los siguientes datos para una transmisión adicional están listos, nos lo notificará a través de él:
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); } }
El conjunto de bytes outDate es un verdadero tesoro. Contiene piezas preparadas de una transmisión de video H264 codificada con la que ahora podemos hacer lo que queramos.
Aquí están ...

Algunas piezas pueden ser demasiado grandes para la transmisión a través de la red, pero nada, el sistema, si es necesario, las cortará por sí solo y las enviará al destinatario.
Pero si da mucho miedo, entonces puedes destrozarte empujando un fragmento
int count =0; int temp =outDate.length ; do {
Pero por ahora, necesitamos ver de primera mano que los datos en el búfer son realmente una transmisión de video H264. Por lo tanto, enviémoslos a un archivo:
Escribiremos en la configuración:
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(); }
Y en la devolución de llamada donde está el búfer:
try { outputStream.write(outDate, 0, outDate.length);
Abra la aplicación, presione el botón: "ENCIENDA LA CÁMARA Y STREAM". La grabación comienza automáticamente. Esperamos un poco y presionamos el botón de parada.
El archivo guardado normalmente probablemente no se perderá, ya que el formato no es MP4, pero si lo abre con un reproductor VLC o lo convierte en línea
usando CONVERTIR EN LÍNEA , nos aseguraremos de que estamos en el camino correcto. Es cierto que la imagen yace de lado, pero es reparable.
En general, para cada evento de grabación, fotografía o transmisión, es mejor, por supuesto, abrir una nueva sesión cada vez y cerrar la anterior. Es decir, primero encendemos la cámara y lanzamos la vista previa. Luego, si necesita tomar una foto, cierre la vista previa y abra la vista previa, pero con Image Reader abrochado. Si cambiamos a la grabación de video, cierre la sesión actual e inicie la sesión con la vista previa y el Grabador de Medios adjunto. No hice esto, para que la visibilidad del código no se vea afectada, y usted decide cómo es más conveniente para usted.
Y aquí está el código completo.
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;
Y no te olvides de los permisos en el manifiesto.
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET"/>
Entonces, nos aseguramos de que Media Codec funcione. Pero usarlo para escribir video en un archivo es de alguna manera sin espíritu. Media Recorder puede manejar esta tarea mucho mejor y agregará sonido. Por lo tanto, volveremos a descartar la parte del archivo y agregaremos un bloque de código para transmitir video a la red utilizando el protocolo udp. También es muy simple.
Primero, inicializamos el servidor UDP prácticamente.
DatagramSocket udpSocket; String ip_address = "192.168.1.84";
Y en la misma devolución de llamada, donde enviamos datos de preparación a la secuencia para el archivo, ahora los enviaremos en forma de datagramas a nuestra red doméstica (¿espero que todos lo tengan?)
try { DatagramPacket packet = new DatagramPacket(outDate, outDate.length, address, port); udpSocket.send(packet); } catch (IOException e) { Log.i(LOG_TAG, " UDP "); }
¿Eso es todo?
Parece, pero no. La aplicación se iluminará al inicio. Verá, al sistema no le gusta que en la transmisión principal enviemos todo tipo de paquetes de datagramas. Pero no hay razón para el pánico. En primer lugar, aunque estamos en el hilo principal, seguimos trabajando de forma asíncrona, es decir, para activar una devolución de llamada. En segundo lugar, el envío de paquetes udp es el mismo proceso asincrónico. Solo le decimos al sistema operativo que sería bueno enviar un paquete, pero que confiamos completamente en él en este asunto. Por lo tanto, para que Android no se rebele, agregaremos dos líneas al comienzo del programa:
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy);
En general, resultará el siguiente pequeño y
elegante programa de demostración:
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;
No sé cómo lo hacen los demás, pero en mi Red Note 7 incluso puedes ver cómo se descargan kilobytes en la dirección correcta

Y hay muchos sockets de udp, cuánto ancho de banda de red es suficiente. Lo principal es que hay direcciones donde. Tendrás una transmisión.
Ahora busquemos la dirección deseada en la computadora
Debo decir que no todos los programas de computadora son capaces de absorber y digerir una transmisión de video H264 a través de un solo canal udp sin ninguna información adicional. Pero algunos pueden. Este es, por ejemplo, el
reproductor multimedia
VLC extremadamente conocido. Esto es algo tan genial que si comienzas a describir sus capacidades, del artículo obtienes un libro completo. Seguramente lo tienes. Si no, póntelo.
Y a juzgar por la descripción de los comandos para él, los paquetes udp pueden digerir este reproductor.
URL syntax: file:
Y todas estas direcciones de origen y direcciones de enlace, en teoría, no son necesarias. Solo se necesita el puerto de escucha.
Y, sin embargo, por supuesto, no debe olvidarse de permitir que este puerto escuche (Malvar)
¿Sabía que Windows no le permite hacer una pantalla de impresión desde el monitor de recursos?O puede deshabilitar el firewall (no lo recomiendo).Entonces, una vez superadas estas espinas, lanzamos el reproductor VLC con nuestra dirección y disfrutamos de la pantalla en blanco. No hay video¿Cómo es eso?
Y asi. ¿Probablemente tenga la última versión de VLC 3.08 Vetinari? Eso es todo, en esta versión de udp, se declara obsoleto y, además, está jodido.Entonces, la lógica de los desarrolladores del jugador es clara. Pocas personas necesitan usar el canal udp desnudo hoy en día porque:Por lo tanto, las personas normales, por supuesto, usan protocolos de nivel superior RTP y otros. Es decir, en los dedos: escribe un servidor que usa udp (para la velocidad) de todos modos, pero al mismo tiempo intercambia información de control con el cliente al que transmite el video. ¿Cuál es su ancho de banda? ¿Es necesario aumentar o disminuir la memoria caché para los datos, qué detalle de imagen es óptimo ahora, y así sucesivamente? Nuevamente, a veces también se necesita sonido. Y él necesita, ya sabes, sincronización con el video.Mira, los chicos de Odnoklassniki incluso tuvieron que presentar su protocolo para la transmisión. Pero sus tareas, por supuesto, son mucho más importantes: enviar videos con gatos a decenas de millones de amas de casa en todo el mundo. Allí no podrá administrar un canal udp.Pero de alguna manera nos entristece escribir nuestro servidor RTP en Android. Probablemente, incluso puede encontrar listas e incluso gratuitas, pero intentemos no complicar las entidades por ahora. Simplemente tome la versión del reproductor VLC donde la transmisión udp todavía funcionaba.Entonces, descargue desde aquí VLC 2.2.6 UmbrellaInstall en lugar de o al lado del antiguo (es decir, nuevo VLC), como lo desee.Comenzamos y vemos una pantalla en blanco nuevamente.Y todo esto es porque obviamente no configuramos el uso del códec H264. Entonces, VLC podría seleccionar el códec automáticamente si tuviera que lidiar con el archivo (en la configuración inicial, se especificó la selección automática). Pero lanzan una secuencia de bytes en un solo canal, y hay docenas de códecs que admite VLC. ¿Cómo puede averiguar cuál aplicar?Por lo tanto, instalamos el códec por la fuerza.
Y ahora disfrutamos la transmisión del video "en vivo". Lo único es que por alguna razón yace de lado, pero esto ya se soluciona fácilmente en la configuración del reproductor de video.Y puede iniciar el reproductor desde la línea de comando con esta tecla: C:\Program Files\VideoLAN\VLC\vlc udp:
Y se decodificará y girará.Así que la transmisión funciona. Solo queda integrarlo en la ventana JAVA de la aplicación de control del robot. Nos ocuparemos de esto muy pronto en la parte final.