Transmitir vídeo do dispositivo Android pelo aplicativo UDP para JAVA

Então, vá para a linha de chegada. Já aprendemos como transmitir vídeo do Android para um VLC player, agora resta apenas integrar a janela com o vídeo no aplicativo JAVA e começar a dirigir o robô.



O projeto de código aberto VLCJ CAPRICA nos ajudará bastante com isso .
O projeto vlcj fornece uma estrutura Java para permitir que uma instância de um media player VLC nativo seja incorporada a um aplicativo Java.
A idéia dos caras é simples, mas engenhosa (realmente pimenta). Em vez de atormentar com bibliotecas FFmpeg e mais, chame imediatamente um especialista como o núcleo de um media player VLC normal, funcional e profissional. E chame-o diretamente do aplicativo JAVA.

Quem se importa, pedimos um gato.

Como nessa viagem existem armadilhas suficientes, começaremos, como sempre, com uma muito simples e só então passaremos para o trivial.

Instale o pacote VLCJ


Primeiro, verifique sua versão do VLC media player. Não precisamos de uma versão nova, ela corta o que é necessário para o fluxo de udp. Isso já foi mencionado em um post anterior . Portanto, baixamos a versão 2.2.6 do Umbrella e, ao mesmo tempo, verificamos cuidadosamente nosso pacote JAVA. Eles devem corresponder em profundidade de bits. Se o player usar uma arquitetura de 64 bits, o JDK deverá ser o mesmo. E não vai decolar.

Depois disso, você já pode baixar o próprio pacote da biblioteca VLCJ



Observe que precisamos do pacote de distribuição vlcj-3.12.1 (zip) . É ele quem trabalha com os jogadores versão VLC 2.2.x. Você pode descompactá-lo em qualquer lugar, o principal é que ele não está na pasta do VLC, porque dois arquivos coincidem por nome. E se você os reescrever, tudo isso terminará em completo fracasso.

Em seguida, criamos um projeto no IntelliJ IDEA IDE (se você tiver um IDE diferente, não posso ajudar em nada) e escrevemos as dependências necessárias para a integração das bibliotecas VLCJ.



Fazemos exatamente isso para arquivos:

jna-5.2.0

jna-platform-5.2.0

vlcj-3.12.1

Em seguida, criamos a única classe e escrevemos o próximo pequeno programa nela.

import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.SwingUtilities; import uk.co.caprica.vlcj.component.EmbeddedMediaPlayerComponent; import uk.co.caprica.vlcj.discovery.NativeDiscovery; import uk.co.caprica.vlcj.player.MediaPlayerFactory; import uk.co.caprica.vlcj.player.embedded.EmbeddedMediaPlayer; import uk.co.caprica.vlcj.player.embedded.videosurface.CanvasVideoSurface; public class BasicPlayer { public final JFrame frame; public static String mrl; public static MediaPlayerFactory mpf; public static EmbeddedMediaPlayer MediaPlayer; public static CanvasVideoSurface videoSurface; public static Canvas canvas; public static void main(final String[] args) { new NativeDiscovery().discover(); mrl = "D:\\ttt.mp4"; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { BasicPlayer vp = new BasicPlayer(); vp.start(mrl); } }); } public BasicPlayer() { frame = new JFrame("My First Media Player"); frame.setBounds(200, 100, 540, 340); frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { System.out.println(e); MediaPlayer.release(); mpf.release(); System.exit(0); } }); JPanel contentPane = new JPanel(); contentPane.setLayout(new BorderLayout()); canvas = new Canvas(); mpf = new MediaPlayerFactory(); videoSurface = mpf.newVideoSurface(canvas); MediaPlayer = mpf.newEmbeddedMediaPlayer(); MediaPlayer.setVideoSurface(videoSurface); contentPane.add(canvas, BorderLayout.CENTER); //        frame.setContentPane(contentPane); frame.setVisible(true); } public void start(String mrl) { MediaPlayer.playMedia(mrl); } } 

Sim, enquanto estamos tentando reproduzir apenas um arquivo (como visto no código). É melhor não começar com o udp - não vai funcionar. E o arquivo é reproduzido completamente se, é claro, você não esquecer de colocá-lo com o nome correspondente antecipadamente, quando necessário. Eu acho que mesmo para o javista mais novato, não será difícil entender o código acima.

Tudo novo é:

chamada para VLCJ

  new NativeDiscovery().discover(); 

e criando a própria instância do media player

  mpf = new MediaPlayerFactory(); videoSurface = mpf.newVideoSurface(canvas); MediaPlayer = mpf.newEmbeddedMediaPlayer(); MediaPlayer.setVideoSurface(videoSurface); 

Depois, basta adicioná-lo ao painel gráfico desejado:

 contentPane.add(canvas, BorderLayout.CENTER); 

E tudo, o arquivo será reproduzido nesta janela.

Agora tente substituir

 mrl = "D:\\ttt.mp4"; 

em

 mrl = "udp://@:40002"; 

como fizemos com calma no último post para streaming de vídeo via conexão udp.
Este número não funcionará aqui. A janela, é claro, abrirá, mas mostrará um figo, no sentido de uma tela escura. Embora não haja logs de erros. Simplesmente não haverá nada.

Tenho que descobrir


Talvez o codec H264 que selecionamos nas configurações esteja ausente? Pare, como o arquivo ttt.mp4 foi reproduzido? Ele não pode jogar com essa configuração, ele é mp4.

Imediatamente vem o entendimento de que a biblioteca VLCJ executa apenas o núcleo do player. E quais eram as predefinições, ela não sabe e não quer saber. Ou seja, precisamos, de alguma forma, ao iniciar o aplicativo JAVA, passar de alguma forma o VLC player que queremos usar explicitamente o codec H264 ou, digamos, queremos girar a imagem ou outra coisa.

Acontece que isso pode ser feito usando a classe MediaPlayerFactory. Somente nós o lançamos sem argumentos, ou mesmo com eles. No stackoverflow.com, encontrei imediatamente um exemplo simples relacionado à rotação de imagens:

 String[] args = { "--video-filter", "rotate", "rotate-angle", "10" }; mpf = new MediaPlayerFactory(args); 

Ou seja, transferimos algo lá com uma matriz de strings para a fábrica de mídia e ela é armazenada lá para o recurso de mídia usado.

Eu tentei esse método para reproduzir o arquivo e, como de costume, nada funcionou. Acontece que eles esqueceram de adicionar dois traços e espalhá-lo por toda a Internet. Eu tive que adivinhar usando um método de transformação semelhante.

Em suma, deve ser:

 String[] args = { "--video-filter", "rotate", "--rotate-angle", "10" }; 

Agora nosso arquivo de referência foi revertido como deveria!



Além disso, será bastante simples:

Para determinar o codec, de acordo com a linha de comando do VLC, adicionamos a linha à matriz de strings:

 "--demux=h264" 

Tentando o canal udp novamente


mrl = "udp: // @: 40002";

E desta vez tudo funciona, indicando a vitória da mente humana. Agora, nesta janela de vídeo ou em várias dessas janelas, você pode facilmente portar para a interface gráfica do seu aplicativo JAVA.

Parece uma vitória?


Na verdade não. Uma ligeira perplexidade foi causada por atrasos temporários ou, cientificamente, por atrasos. No início, eles são menos aceitáveis, mas se você tiver paciência para assistir ao vídeo até o fim, verá que o atraso no final do primeiro minuto da transmissão chega a cinco segundos. Eu tive paciência suficiente por 10 minutos de filmagem, mas, estranhamente, o atraso não aumentou mais, mas permaneceu dentro dos mesmos limites.

o video


Claro, para assistir a um vídeo da câmera, isso servirá, mas dificilmente controlar um robô. Até o veículo espacial lunar reagiu duas vezes mais rápido!

As suspeitas caíram imediatamente nos processos de armazenamento em cache e elas (suspeitas) acabaram sendo verdadeiras.

O mais arrogante foi:

  caching for network resources 

Ele consome quase tudo por padrão, se não for entregue a ele a tempo.
Pode organizar um atraso e:

 caching for cameras and microphones 

Portanto, para evitar atrasos de vários segundos, é recomendável adicionar as seguintes linhas à mesma matriz de cadeias, separadas por vírgulas:

  "--live-caching=100", "--network-caching=500", 

Os parâmetros são definidos em milissegundos e, portanto, qualquer um pode escolher por si próprio.

Você também pode usar a chave:

 "--clock-jitter=time in milliseconds", 

Em seguida, o media player tentará otimizar o tremor - contraindo a tela. Mas, quanto mais tempo for definido, melhor será otimizado e isso será compreensível. Portanto, resta apenas procurar consenso e, às vezes, ver tal desgraça nos logs:



Ele queria, você sabe, consertar a instabilidade, e você ajustou o intervalo de tempo muito pequeno. Agora a culpa é minha.

Agora tudo parece estar como deveria. O atraso foi reduzido para menos de um segundo (embora um pouco menos).

o video


Como resultado, temos um código de trabalho muito pequeno

 import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.SwingUtilities; import uk.co.caprica.vlcj.discovery.NativeDiscovery; import uk.co.caprica.vlcj.player.MediaPlayerFactory; import uk.co.caprica.vlcj.player.embedded.EmbeddedMediaPlayer; import uk.co.caprica.vlcj.player.embedded.videosurface.CanvasVideoSurface; public class BasicVideoPlayer { public final JFrame frame; public static String mrl; public static MediaPlayerFactory mpf; public static EmbeddedMediaPlayer MediaPlayer; public static CanvasVideoSurface videoSurface; public static Canvas canvas; public static void main(final String[] args) { new NativeDiscovery().discover(); mrl = "udp://@:40002"; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { BasicVideoPlayer vp = new BasicVideoPlayer(); vp.start(mrl); } }); } public BasicVideoPlayer() { frame = new JFrame("My First Media Player"); frame.setBounds(200,100, 540, 340); frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { System.out.println(e); MediaPlayer.release(); mpf.release(); System.exit(0); } }); JPanel contentPane = new JPanel(); contentPane.setLayout(new BorderLayout()); canvas = new Canvas(); String[] args = { "--video-filter", "rotate", "--rotate-angle", "270", "--demux=h264", "--clock-jitter=100", "--live-caching=100", "--network-caching=500", }; mpf = new MediaPlayerFactory(args); videoSurface = mpf.newVideoSurface(canvas); MediaPlayer = mpf.newEmbeddedMediaPlayer(); MediaPlayer.setVideoSurface(videoSurface); contentPane.add(canvas, BorderLayout.CENTER); frame.setContentPane(contentPane); frame.setVisible(true); } public void start(String mrl) { MediaPlayer.playMedia(mrl); } } 

Agora você pode integrar a transmissão de vídeo ao meu programa de controle robótico, onde eu costumava transmitir vídeo em pedaços. E devo dizer que o código foi bastante simplificado e a qualidade melhorou uma ordem de magnitude. Além de tudo no fluxo de vídeo, podemos transmitir leituras

acelerômetros
giroscópios
nível de iluminação
pressão do ar
leituras da bússola
temperatura
e até umidade

Desde que, é claro, todos esses sensores estejam disponíveis no seu smartphone.

E até acender o farol! Automaticamente! Se o nível de iluminação cair.


Quase ninguém está particularmente interessado, mas no caso de um link para um github:

para carrinho
para smartphone

Source: https://habr.com/ru/post/pt473840/


All Articles