Então, descobrimos como tirar fotos e gravar vídeos usando a API Camera2. Resta apenas aprender a transferir o fluxo de vídeo do dispositivo Android para os destinatários que sofrem do lado de fora. O objetivo final, como já foi dito várias vezes, é a intelectualização dos robôs - colocamos um smartphone nele e, por assim dizer, transformamos um macaco em uma pessoa. O Media Codec nos ajudará com isso. E, claro, a nova API Camera2.
Quem se importa, por favor, debaixo do gato.
Detalhes sobre o projeto robótico podem ser encontrados
aqui , mas, por
enquanto , transmitiremos o vídeo diretamente dele (ou melhor, de um smartphone Android anexado a ele) para um computador eletrônico pessoal.
Do que precisamos para isso?
Para transferir um fluxo de vídeo da tela do smartphone para outro lugar, como você sabe, ele (fluxo) deve primeiro ser convertido para um formato reduzido (será muito espesso para transmitir quadro a quadro), colocar carimbos de data / hora (carimbos de hora) e enviar em formato binário para o destinatário . O qual executará a operação de decodificação inversa.
É exatamente com essas ações negras de baixo nível que a classe Media Codec lida desde 2013, a partir da data de lançamento do Android 4.3.

Outra coisa é que a abordagem anterior da codificação de vídeo, ao contrário de hoje, não era tão simples. Para tirar uma foto da câmera, era necessário usar toneladas de um
código misterioso no qual, como nos feitiços dos xamãs Yakut, a única imprecisão poderia levar a uma falha completa do aplicativo. Adicione a isso a API da câmera anterior, onde, em vez de retornos de chamada prontos, você mesmo precisará escrever diferentes canetas sincronizadas, e essa atividade, digamos, não é para os fracos de coração.
E o mais importante, você olha para o
código de trabalho de longe, tudo parece claro em termos gerais. Você começa a transferir partes para o seu projeto - não está claro por que ele está vazando. Mas é impossível corrigir, porque é difícil entender os detalhes.
Sim, e de sólidos
obsoletos de alguma forma à vontade. Em suma, bagunça
Felizmente, para os lentos, os construtores do Google introduziram o conceito mágico do
Surface , trabalhando com o qual você pode evitar detalhes de baixo nível. É difícil para mim, como leigo, entender a que custo e o que o desenvolvedor perde, mas agora podemos dizer quase literalmente: "Android, leve essa superfície para a qual o vídeo da câmera é exibido e não mude nada lá, bem, como está, codifique e enviar. " E o mais surpreendente é que funciona. E com a nova API Camera2, o próprio programa sabe quando enviar dados, novos retornos de chamada apareceram!
Então agora para codificar o vídeo - apenas cuspir. O que faremos agora?
Pegamos o código do
primeiro artigo e, como sempre, jogamos tudo fora dele, exceto os botões e a inicialização da câmera.
Vamos começar com o layout do aplicativo.<?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>
E termine com a conexão do Media Codec
Na última postagem, exibimos a imagem da câmera na superfície e gravamos vídeos usando o MediaRecorder. Para fazer isso, simplesmente especificamos os dois componentes na lista Surface.
(Arrays.asList(surface, mMediaRecorder.getSurface()).
Aqui a mesma coisa, somente em vez de mMediaRecorder especificamos:
(Arrays.asList(surface, mEncoderSurface),
Acontece, algo 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(); } }
O que é mEncoderSurface? E esta é a mesma superfície com a qual o Media Codec funcionará. Apenas para começar, você precisa inicializar os dois aproximadamente dessa maneira.
private void setUpMediaCodec() { try { mCodec = MediaCodec.createEncoderByType("video/avc");
Agora resta registrar um único retorno de chamada. Quando o Media Codec sente repentinamente que os próximos dados para transmissão adicional estão prontos, ele nos notificará através dele:
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); } }
A matriz de bytes outDate é um verdadeiro tesouro. Ele contém partes prontas de um fluxo de vídeo H264 codificado com o qual agora podemos fazer o que quisermos.
Aqui estão eles ...

Algumas peças podem ser grandes demais para serem transmitidas pela rede, mas nada, se necessário, o sistema as triturará sozinho e as enviará ao destinatário.
Mas se for muito assustador, você pode se destruir empurrando um fragmento desse tipo
int count =0; int temp =outDate.length ; do {
Mas, por enquanto, precisamos ver em primeira mão que os dados no buffer são realmente um fluxo de vídeo H264. Portanto, vamos enviá-los para um arquivo:
Vamos escrever na configuração:
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(); }
E no retorno de chamada, onde está o buffer:
try { outputStream.write(outDate, 0, outDate.length);
Abra o aplicativo, pressione o botão: “LIGUE A CÂMERA E O FLUXO”. A gravação começa automaticamente. Esperamos um pouco e pressionamos o botão Parar.
O arquivo salvo normalmente não será perdido, pois o formato não é MP4, mas se você o abrir com um player VLC ou o converter on-line
usando o ONLINE CONVERT , garantiremos que estamos no caminho certo. É verdade que a imagem está de lado, mas é corrigível.
Em geral, para cada evento de gravação, fotografia ou transmissão, é melhor, é claro, abrir uma nova sessão a cada vez e fechar a antiga. Ou seja, primeiro ligamos a câmera e iniciamos a pré-visualização. Em seguida, se você precisar tirar uma foto, feche a visualização e abra a visualização, mas com o Image Reader preso. Se mudarmos para a gravação de vídeo, feche a sessão atual e inicie a sessão com a visualização e o Media Recorder anexados. Eu não fiz isso, para que a visibilidade do código não sofra, e você decide como é mais conveniente para si mesmo.
E aqui está o código inteiro.
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;
E não se esqueça das permissões no manifesto.
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET"/>
Portanto, garantimos que o Media Codec esteja funcionando. Mas usá-lo para gravar vídeo em um arquivo é algo sem espírito. O Media Recorder pode lidar com essa tarefa muito melhor e irá adicionar som. Portanto, jogaremos fora a parte do arquivo novamente e adicionaremos um bloco de código para transmitir vídeo à rede usando o protocolo udp. Também é muito simples.
Primeiro, inicializamos o servidor UDP praticamente.
DatagramSocket udpSocket; String ip_address = "192.168.1.84";
E no mesmo retorno de chamada, onde enviamos dados de prontidão para o fluxo do arquivo, agora os enviaremos na forma de datagramas para nossa rede doméstica (espero que todos tenham?)
try { DatagramPacket packet = new DatagramPacket(outDate, outDate.length, address, port); udpSocket.send(packet); } catch (IOException e) { Log.i(LOG_TAG, " UDP "); }
Isso é tudo?
Parece, mas não. O aplicativo será iluminado na inicialização. Veja bem, o sistema não gosta disso no fluxo principal, enviamos todos os tipos de pacotes de datagramas. Mas não há motivo para pânico. Em primeiro lugar, embora estejamos no segmento principal, ainda trabalhamos de forma assíncrona, ou seja, para acionar um retorno de chamada. Em segundo lugar, o envio de pacotes udp é o mesmo processo assíncrono. Dizemos apenas ao sistema operacional que seria bom enviar um pacote, mas que confiamos totalmente nele nesse assunto. Portanto, para que o Android não se rebele, adicionaremos duas linhas no início do programa:
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy);
Em geral, o seguinte pequeno e
elegante programa de demonstração será exibido:
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;
Não sei como os outros sabem, mas no Red Note 7 você pode ver como os kilobytes são baixados no endereço certo

E existem muitos soquetes udp, quanta largura de banda de rede é suficiente. O principal é que existem endereços onde. Você terá uma transmissão.
Agora vamos procurar o endereço desejado no computador
Devo dizer que nem todo programa de computador é capaz de absorver e digerir um fluxo de vídeo H264 através de um único canal de UDP, sem qualquer informação adicional. Mas alguns podem. Este é, por exemplo, o extremamente conhecido
reprodutor de mídia
VLC . Isso é uma coisa tão legal que, se você começar a descrever seus recursos, a partir do artigo, receberá um livro inteiro. Certamente você tem. Caso contrário, coloque-o.
E, a julgar pela descrição dos comandos, os pacotes udp podem digerir esse player.
URL syntax: file:
E todos esses endereços de origem e endereços de ligação, em teoria, não são necessários. Somente a porta de escuta é necessária.
E, no entanto, é claro, você não deve esquecer de permitir que essa porta escute (Malvar)
Você sabia que o Windows não permite que você faça uma tela de impressão a partir do monitor de recursos?Ou você pode desativar o firewall (não o recomendo).Assim, superando esses espinhos, lançamos o VLC player com nosso endereço e apreciamos a tela em branco. Nenhum vídeo.Como assim?
E assim Você provavelmente tem a versão mais recente do VLC 3.08 Vetinari? É isso, nesta versão do udp, é declarada obsoleta e, além disso, está fodida.Portanto, a lógica dos desenvolvedores do jogador é clara. Atualmente, poucas pessoas precisam usar o canal udp bare porque:Portanto, as pessoas normais, é claro, usam protocolos de alto nível RTP e outros. Ou seja, nos dedos - você escreve um servidor que usa o udp (para velocidade) de qualquer maneira, mas ao mesmo tempo troca informações de controle com o cliente para quem ele transmite o vídeo. Qual é a sua largura de banda, é necessário aumentar ou diminuir o cache dos dados, que detalhes da imagem são ideais agora e assim por diante. Novamente, às vezes também é necessário som. E ele precisa, você sabe, de sincronização com o vídeo.Olha, os caras de Odnoklassniki até tiveram que arquivar seu protocolo para transmissão. Mas suas tarefas, é claro, são muito mais importantes - enviar vídeos com gatos para dezenas de milhões de donas de casa em todo o mundo. Lá você não gerenciará um canal udp.Mas, de alguma forma, estamos tristes em escrever nosso servidor RTP no Android. Provavelmente, você pode até achar pronto e até gratuito, mas vamos tentar não complicar as entidades por enquanto. Basta levar a versão do VLC player onde o streaming de udp ainda funcionava.Portanto, faça o download aqui do VLC 2.2.6 UmbrellaInstall, em vez de ou ao lado do antigo (ou seja, novo VLC), como desejar.Começamos e vemos uma tela em branco novamente.E tudo isso é porque obviamente não configuramos o uso do codec H264. Portanto, o VLC poderia selecionar o codec automaticamente se tivesse que lidar com o arquivo (nas configurações inicialmente, a seleção automática foi especificada). Mas eles lançam um fluxo de bytes em um único canal e há dezenas de codecs que o VLC suporta. Como ele pode descobrir qual aplicar?Portanto, instalamos o codec à força.
E agora gostamos da transmissão de vídeo "ao vivo". A única coisa é que, por algum motivo, está do lado dele, mas isso já é facilmente corrigido nas configurações do player de vídeo.E você pode simplesmente iniciar o player na linha de comando com esta chave: C:\Program Files\VideoLAN\VLC\vlc udp:
E ele se decodificará e girará.Então, o streaming funciona. Resta apenas integrá-lo à janela JAVA do aplicativo de controle do robô. Lidaremos com isso muito em breve na parte final.