Evolua ou crie uma base para robôs na plataforma ARDUINO, e direcionamos sensores e vídeo para um computador via smartphone

Para os queridos leitores do GeekTimes, o próximo (quarto) artigo aguardado sobre o que acontecerá se você misturar novamente o arduino, ESP8266, WI-FI, tempere com um smartphone Android e salpique sobre o aplicativo JAVA.

Falaremos sobre os robôs do artigo antes da última, que chegou a hora de pelo menos ficar um pouco mais inteligente.

imagem

Quem se importa, bem-vindo ao gato.

Se você não está muito interessado em ler artigos antigos, então brevemente - o ponto era que, para controlar o carrinho de quatro rodas habitual na plataforma Arduino, uma ponte UART sem fio desenvolvida por mim com base no conhecido módulo ESP8266 foi adicionada a ele. Também por conveniência (e em geral esse era o objetivo principal) usando o mesmo ESP, escrevi um programador para o arduinki, que permite que você faça o flash remotamente.

imagem

Ou seja, o carrinho em algum lugar distante (mas dentro da sua rede WI-FI) viaja (sim, eu gosto de escrever essa palavra), envia dados e recebe comandos e, se necessário, por ordem, também pode alterar o programa no microcontrolador AVR. Consequentemente, o programa em JAVA para o PC foi executado, executando o qual você pode ter controle e obter telemetria primitiva na forma da distância percorrida (chave reed e ímã no volante).

imagem

Além disso, experimentei com sucesso no próximo artigo controlar o carrinho usando um smartphone - botões, inclinações e até voz. Mas quando o carrinho partiu para a próxima sala, mesmo a voz não pôde devolvê-lo (ao contrário do gato). Ela foi até lá, bateu nas paredes e nos móveis, enredou-se nos fios, mas além de informações sobre a distância percorrida, ela não enviou nada.

Portanto, surgiu imediatamente a idéia de fornecer ao futuro Terminator órgãos sensoriais. Uma das opções mais fáceis para isso é o uso do sonar.

imagem

O algoritmo de trabalho para a desgraça é simples, iniciamos o sensor de uma frente e, ao mesmo tempo, um contador de microcontrolador. O HC-SR04 começa a balançar o ultrassom à distância. O sinal de resposta do sensor através de outro fio sinaliza o final da medição da distância e o intervalo de tempo entre o início e a resposta é proporcional à distância medida. Assim, nesse momento desaceleramos o balcão e vemos quanto ele entrou.

A precisão é obtida até cerca de um centímetro e um intervalo de metros a dois. Ele não gosta de superfícies felpudas e de lã (por exemplo, um gato), onde qualquer sinal de eco se afoga irrevogavelmente.

A área afetada, o ângulo de visão do HC-SR04, é pequeno; portanto, para saber o que está acontecendo à frente no âmbito de um ângulo de pelo menos 90 graus, é recomendável fazer o seguinte:

  1. parafuse o sensor na máquina servo e olhe-o em direções diferentes
  2. coloque alguns sensores.

No início, implementei a primeira opção, colocando o sonar em um servo barato SG90 e o carrinho se transformou em um veículo espacial. Foi necessário tanto tempo para realizar pelo menos três medições, então, basicamente, o carrinho ficou rodando com um servo, depois avançou cuidadosamente, mas não muito longe (e de repente um obstáculo apareceu ao lado), e novamente sentiu o espaço à sua frente. Ainda assim, o som não é a luz para você.

Portanto, sem mais delongas, coloquei três sistemas de sonar ao mesmo tempo. O carrinho adquiriu uma aparência de aranha crônica, parou embotado na frente de obstáculos e começou a taxiá-los em movimento. Mas, no final, os cérebros eram suficientes para não ficarem presos em um ambiente amigável. Tivemos que seguir em frente - para autonomia e progresso. E aqui, sem sentidos diferentes, até o worm nematóide dirá, você não pode fazê-lo.


Além disso, geralmente os entusiastas começam a esculpir em suas criações vários novos sensores, como giroscópios, acelerômetros-magnetômetros e até sensores FIRE (todos os chineses sem nome produzem em milhões de quantidades para o Arduino). E eu também quase comecei por esse caminho escorregadio, mas mudei de idéia com o tempo. E eu fiz isso por esse motivo. Na perspectiva mais distante, o robô robótico deveria ter visão na forma de uma câmera e também entender o que vê. Mas o microcontrolador AVR da placa Arduino diz adeus a você, mesmo na fase de recebimento do vídeo, sem mencionar o processamento. E de repente meu olhar caiu no velho smartphone GALAXY S7, já agredido pela vida.

Tal poder de computação, oito núcleos, 4 gigabytes de memória, duas câmeras, acesso à rede, o que mais é necessário para transformar um macaco em uma pessoa?

Mas precisamos apenas de um design pequeno para que o smartphone repouse em um carrinho e possa ser facilmente colocado e removido.

imagem

Então subi no site de desenvolvedores do Android para descobrir quais outros recursos um smartphone comum pode nos dar. Acabou que não é pequeno. Teoricamente você pode

acesse os seguintes sensores sensores.
TYPE_ACCELEROMETER

TYPE_AMBIENT_TEMPERATURE

TYPE_GAME_ROTATION_VECTOR

GEOMAGNETIC_ROTATION_VECTOR

TYPE_GRAVITY

TYPE_GYROSCOPE

TYPE_GYROSCOPE_UNCALIBRATED

TYPE_HEART_BEAT

TYPE_HEART_RATE

TYPE_LIGHT

TYPE_LINEAR_ACCELERATION

TYPE_LOW_LATENCY_OFFBODY_DETECT

TYPE_MAGNETIC_FIELD

TYPE_MAGNETIC_FIELD_UNCALIBRATED

TYPE_MOTION_DETECT

TYPE_ORIENTATION

TYPE_POSE_6DOF

TYPE_PRESSURE

TYPE_PROXIMITY

TYPE_RELATIVE_HUMIDITY

TYPE_ROTATION_VECTOR

TYPE_SIGNIFICANT_MOTION

TYPE_STATIONARY_DETECT

TYPE_STEP_COUNTER

TYPE_STEP_DETECTOR


Como se costuma dizer, o que há apenas! E, de fato, não havia muito específico para o GALAXY S7. Por exemplo, um sensor de umidade. E a temperatura ambiente (embora eu entenda que, por estar dentro do gabinete, ele mostrará a temperatura do próprio smartphone). Mas os sensores de pressão e luz estavam presentes. Sem mencionar os giroscópios, acelerômetros, com os quais você pode facilmente determinar sua posição no espaço.

Como resultado, uma decisão amadureceu, deixe o smartphone receber e processar todas as informações de nível superior - vídeo e todos esses vários sensores. E a plataforma Arduino será responsável, por assim dizer, pelo inconsciente - por tudo o que já funciona e não requer retrabalho, todos esses motores, sonar, chaves de palheta e assim por diante.

Como é difícil depurar o programa diretamente no smartphone, mesmo com o UDB, decidi que tudo seria transferido para um computador pessoal normal e processado lá. E então, de alguma forma, quando houver uma versão funcional, retornaremos os cérebros de volta ao carrinho. Temos que começar pequenos e, de fato, é interessante observar a transmissão de vídeo de um carrinho frenético.

Os dados dos sensores podem ser enviados em uma linha simples, através de um servidor cliente primitivo, não há problemas com isso. Mas com a transferência do vídeo, imediatamente houve dificuldades. Em geral, eu precisava de uma transmissão em tempo real da câmera do smartphone para a janela do aplicativo no computador. Isso é por enquanto. No futuro, não apenas eu, mas algum tipo de sistema de reconhecimento de padrões poderia olhar para essa foto na janela. Por exemplo, JAVA OpenCV. Ou talvez até uma rede neural da nuvem: D. Não sei, esse estágio ainda está muito longe. Mas eu gostaria de ver o mundo com o "olho" de um caminhão robótico.

Todo mundo conhece vários aplicativos, como uma "câmera móvel" da loja do Google, onde você captura um fluxo de vídeo da câmera de um smartphone abrindo um navegador com o IP desejado no computador. Portanto, no começo eu pensei que não seria difícil traduzir do meu GALAXY (o que não foi um erro fraco), então primeiro você precisa verificar como será a recepção no computador, já que eu posso escrever de alguma forma apenas em JAVA.

Como se viu, com a reprodução de vídeo JAVA, para dizer o mínimo, não muito bem. Em 1997, foi lançado o chamado Java Media Framework - uma biblioteca que facilita o desenvolvimento de programas que funcionam com áudio e vídeo pelos próprios criadores do JAVA. Mas, em algum lugar depois de 2003, um grande raio foi colocado nele e desde então já faz 15 anos. Após algumas experiências, consegui executar um arquivo na janela, não me lembro qual deles já (parece AVI), mas essa visão parecia bastante infeliz. Arquivos com outras extensões não desejavam ser executados; em casos extremos, havia uma faixa de áudio.

Na Internet, encontrei mais dois projetos alternativos para trabalhar com vídeo: Xuggler e aprica VLCj. O primeiro projeto era atraente em suas capacidades, mas também morreu há muito tempo, mas o segundo acabou sendo bastante animado e interessante em sua própria idéia. Os caras pegaram e prenderam ao JAVA o conhecido popular media player VLC. Ou seja, o aprica não usa codecs auto-escritos, mas usa codecs prontos. Com ele, você perderá qualquer arquivo. Uma decisão sábia, mas o principal é que você já possui esse VLC player instalado no seu computador. Bem, quem não tem? A única ressalva, no entanto, é que você tem as mesmas profundidades de bits do player e do JAVA. Por exemplo, mais tarde, com surpresa, descobri que ainda tinha um VLC de 32 bits no computador, em contraste com o JAVA de 64 bits. E meio dia de vida foi perdido em vão.

Os desenvolvedores de Caprica prometem aos usuários muitas coisas em seu site. E todos os formatos de arquivo e inicialização em várias janelas em um aplicativo JAVA, reproduzindo vídeos do You-Tube, capturando um fluxo de vídeo "ao vivo" e assim por diante. Mas a dura realidade coloca tudo em seu lugar. Não, eles não enganaram com arquivos - tudo é reproduzido. Mas agora o vídeo do YouTube não quer mais. No começo, eu não conseguia entender o porquê, mas então vi uma inscrição no log que, de alguma forma, era impossível executar um determinado script lua e lembrei imediatamente:
Essa "captura de tela" da página da Web do YouTube é quebradiça - se o YouTube alterar a estrutura de suas páginas da Web, o VLC às vezes falha em encontrar o URL de streaming. Quando isso acontece, você precisa esperar que um desenvolvedor forneça uma nova LUA script e aguarde o lançamento de uma nova versão do VLC.
Em suma, aparentemente o YouTube mudou a estrutura de sua página da web e preciso aguardar um novo lançamento. Por outro lado, preciso de uma transmissão de vídeo "ao vivo" e não reproduzir um arquivo do site. Ou seja, mesmo que o script lua funcionasse, isso não me ajudaria muito.

Mas não encontrei o streaming prometido, embora tenha sido escrito que:

Servidor de streaming de rede (por exemplo, uma estação de rádio em rede ou um servidor de vídeo sob demanda);
Cliente de streaming de rede;
Talvez seja uma lista de desejos, ou talvez esteja na versão comercial, é difícil dizer.

Mas os arquivos são reproduzidos, repito, sem queixas. Por exemplo, você pode criar, aqui está essa indecência


A instalação do pacote em si não é difícil e é descrita até em detalhes, por exemplo, aqui . É verdade que, de alguma forma, prescrevi desajeitadamente variáveis ​​de ambiente e agora meu VLC inicia pela primeira vez com um atraso de dez segundos, mas salva o necessário no cache e, posteriormente, na sessão atual, inicia sem pausas.

Então, no seu projeto JAVA, você prescreve as dependências necessárias e pode começar a reproduzir os media players nas janelas JAVA para funcionar.

Depois de marcar o plano de trabalho aproximado na lateral do computador pessoal, voltei à fonte dos dados de vídeo, meu smartphone ANDROID.

Mas aqui eu estava esperando outra decepção bastante grande. Depois de digitalizar alguns sites e até o manual ANDROID on-line, descobri que é impossível organizar a transmissão em tempo real usando meios regulares. Como em Caprica, você só pode ler arquivos já gravados. Ou seja, com a câmera ligada, o MEDIA RECORDER começou a gravar quando precisou ser interrompido. E podemos ter acesso aos dados (e sua transferência) somente após uma parada.

Encontrei confirmação de minhas conclusões em um artigo antigo. Havia realmente dicas de que, uma vez que alguém conseguiu induzir o Android a pensar que ele estava gravando em um arquivo, mas na verdade eles deixaram um buffer. Mas, de qualquer forma, como já foi explicado anteriormente, não foi possível capturar um fluxo de vídeo no lado do PC no aplicativo JAVA.

Portanto, foi tomada uma decisão simples, temporária , em carvalho (quero enfatizar) - enviar um fluxo de vídeo da câmera em pedaços por dois segundos. Não é um rover, é claro, mas o rover já sai bastante.

Tendo decidido sobre essa decisão, comecei a dominar as aulas de CAMERA e MEDIA RECORDER. Existem alguns exemplos de código na rede para iniciar a câmera e gravar arquivos de vídeo, mas por algum motivo, nenhum deles funcionou para mim. Nem no emulador, nem em um dispositivo real. Descobriu-se que o motivo está nas permissões, ou seja, nas permissões. Acontece que exemplos de código foram escritos naqueles dias em que o Android ainda estava livre e o programador poderia fazer o que quisesse se escrevesse todas as permissões necessárias no manifesto. Mas minha versão atual do OC não permitiu isso. Primeiro, você tinha que dar ao usuário permissão para gravar o arquivo e ligar a câmera imediatamente após iniciar o aplicativo. Isso me custou atividade extra, ou seja, em Atividade.

Classe MainActivity.java
import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.hardware.Camera; import android.media.MediaRecorder; import android.os.AsyncTask; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.Toast; import java.io.File; public class MainActivity extends AppCompatActivity { Camera camera; MediaRecorder mediaRecorder; public static MainActivity m; public static boolean Camera_granted; File videoFile; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if ((ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) ||(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) ) //ask for authorisation { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE}, 50); } if ((ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)& (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)) { Toast.makeText(MainActivity.this,"", Toast.LENGTH_SHORT).show();} m=this; MyTask mt = new MyTask(); mt.execute(); } } class MyTask extends AsyncTask<Void, Void, Void> { @Override protected void onPreExecute() { super.onPreExecute(); } @Override protected Void doInBackground(Void... params) { boolean ready = false; while(!ready) { if ((ContextCompat.checkSelfPermission(MainActivity.m, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED)& (ContextCompat.checkSelfPermission(MainActivity.m, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)) { System.out.println(" "); ready=true; } } return null; } @Override protected void onPostExecute(Void result) { Toast.makeText(MainActivity.m,"", Toast.LENGTH_SHORT).show(); Intent intent=new Intent(MainActivity.m,CameraActivity.class); //   : MainActivity.m.startActivity(intent); } } 


Depois disso, tudo correu bem e o seguinte código de funcionamento apareceu. A segunda atividade da classe Camera_Activity é responsável por trabalhar com a câmera e gravar arquivos de vídeo. Classe Http_server para encaminhamento (o nome, é claro, está incorreto, mas acabou historicamente). O código é simples, sempre que houver uma explicação.



Tudo está completamente no Github. Link

Camera_Activity
 import android.hardware.Camera; import android.media.MediaRecorder; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.widget.Button; import android.widget.TextView; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.ServerSocket; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.content.Context; import android.hardware.Sensor; import static android.hardware.Camera.getNumberOfCameras; import java.io.BufferedOutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; /** * Created by m on 01.02.2019. */ public class CameraActivity extends AppCompatActivity implements SensorEventListener { SurfaceView surfaceView; TextView mTextView; Button mStart; Button mStop; Camera camera; MediaRecorder mediaRecorder; public static ServerSocket ss; public static ServerSocket ss2; public static MainActivity m; public static volatile boolean stopCamera=true; public static int count=1; public static File videoFile1; public static File videoFile2; public static File videoFile3; public static volatile byte[] data; public SensorManager mSensorManager; public Sensor mAxeleration, mLight,mRotation,mHumidity,mPressure,mTemperature; public int ax; public int ay; public int az; public double light; public int x; public int y; public int z; public double hum; public double press; public double tempr; public static String Sensors; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); //    mAxeleration = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); //    mSensorManager.registerListener(this, mAxeleration, SensorManager.SENSOR_DELAY_NORMAL); mLight = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); mSensorManager.registerListener(this, mLight, SensorManager.SENSOR_DELAY_NORMAL); mRotation = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); mSensorManager.registerListener(this, mRotation, SensorManager.SENSOR_DELAY_NORMAL); mHumidity = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION); mSensorManager.registerListener(this, mHumidity, SensorManager.SENSOR_DELAY_NORMAL); mPressure = mSensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE); mSensorManager.registerListener(this, mPressure, SensorManager.SENSOR_DELAY_NORMAL); mTemperature = mSensorManager.getDefaultSensor(Sensor.TYPE_AMBIENT_TEMPERATURE); mSensorManager.registerListener(this, mTemperature, SensorManager.SENSOR_DELAY_NORMAL); setContentView(R.layout.camera); // videoFile = new File(Environment.getExternalStorageDirectory() + File.separator+ Environment.DIRECTORY_DCIM + File.separator + "test.3gp"); videoFile1 = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "test1.3gp"); videoFile2 = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "test2.3gp"); videoFile3 = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "test3.3gp"); // videoFile4 = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "testOUT.3gp"); // File file = new File(Environment.getExternalStorageDirectory(),VIDEO_PATH_NAME);//   // "touch" the file try { videoFile1.createNewFile(); videoFile2.createNewFile(); videoFile3.createNewFile(); } catch (IOException e) { } mStart = (Button) findViewById(R.id.btnStartRecord); mStop = (Button) findViewById(R.id.btnStopRecord); surfaceView = (SurfaceView) findViewById(R.id.surfaceView); mTextView = (TextView) findViewById(R.id.textView); SurfaceHolder holder = surfaceView.getHolder(); holder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { try { camera.setPreviewDisplay(holder); camera.startPreview(); } catch (Exception e) { e.printStackTrace(); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { } }); mStart.setOnClickListener(new View.OnClickListener() { public void onClick(View v){ System.out.println("  "); //    WriteVideo WV = new WriteVideo(); WV.start(); mStart.setClickable(false); mStop.setClickable(true); Http_server.File_is_sent=true; new ServerCreation().execute();//      } } ); mStop.setOnClickListener(new View.OnClickListener() { public void onClick(View v){ stopCamera = false; // mTextView.setText(" "); releaseMediaRecorder(); releaseCamera(); mStart.setClickable(true); mStop.setClickable(false); System.out.println("  "); Http_server.File_is_sent=true; } } ); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { //    } @Override public void onSensorChanged(SensorEvent event) { //   switch (event.sensor.getType()) { case Sensor.TYPE_ACCELEROMETER: ax = (int)(event.values[0] * 9); // + -  az = (int)(event.values[2] * 9);//  +  -  // System.out.println(" = "+ ax + "  = " + az); break; case Sensor.TYPE_LIGHT: light = event.values[0]; // System.out.println(" = "+ light); break; case Sensor.TYPE_ORIENTATION: x = (int)event.values[0]; y = (int)event.values[1]+90; //  +  -  z = (int)event.values[2]; // + -  // System.out.println("x = "+ x + " y = " + y+ " z= "+ z); break; case Sensor.TYPE_LINEAR_ACCELERATION: hum = event.values[2]; int k = (int)(hum*100); hum = - (double)k;//   ,    /2 // System.out.println(hum); break; case Sensor.TYPE_PRESSURE: press = event.values[0]*760/10.1325; int i = (int) press; press = (double)i/100; // System.out.println(press); break; case Sensor.TYPE_AMBIENT_TEMPERATURE: tempr = event.values[0]; System.out.println(tempr); break; } Sensors = " tangaz_1 "+ az+ " kren_1 " + ax + " tangaz_2 "+ y + " kren_2 " + z + " forvard_accel "+ hum + " light " + light+ " "; // System.out.println(Sensors); } @Override protected void onResume() { super.onResume(); releaseCamera(); releaseMediaRecorder(); int t = getNumberOfCameras(); mTextView.setText(""+t); if(camera == null) { camera = Camera.open(); // camera.unlock(); } else{ } } @Override protected void onPause() { super.onPause(); } @Override protected void onStop() { super.onStop(); releaseCamera(); releaseMediaRecorder(); } private boolean prepareVideoRecorder() { if (mediaRecorder==null) {mediaRecorder = new MediaRecorder();} camera.unlock(); mediaRecorder.setCamera(camera); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); switch (count) { case 1: mediaRecorder.setOutputFile(videoFile1.getAbsolutePath()); break; case 2: mediaRecorder.setOutputFile(videoFile2.getAbsolutePath()); break; case 3: mediaRecorder.setOutputFile(videoFile3.getAbsolutePath()); break; } mediaRecorder.setPreviewDisplay(surfaceView.getHolder().getSurface()); mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); //mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mediaRecorder.setVideoSize(640,480); mediaRecorder.setOrientationHint(90);//    ,     mediaRecorder.setVideoFrameRate(30); mediaRecorder.setVideoEncoder(1); try { mediaRecorder.prepare(); } catch (Exception e) { e.printStackTrace(); releaseMediaRecorder(); return false; } return true; } private void releaseMediaRecorder() { if (mediaRecorder != null) { mediaRecorder.release(); mediaRecorder = null; if (camera != null) { // camera.lock();//         } } } public void releaseCamera() { if (camera != null) { // camera.setPreviewCallback(null); // SurfaceView.getHolder().removeCallback(SurfaceView); camera.release(); camera = null; } } private class WriteVideo extends Thread{ public void run () { stopCamera=true; do{ // releaseMediaRecorder(); // releaseCamera(); if(camera == null) { camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); } System.out.println(""); if (prepareVideoRecorder()) { mediaRecorder.start(); } else { releaseMediaRecorder(); } try { Thread.sleep(2000);//     } catch (Exception e) { } count++; if(count==3){count=1;}// 4 if (mediaRecorder != null) { releaseMediaRecorder(); } System.out.println(""); File f=null; switch (count) { case 1: f = videoFile2;// 3 break; case 2: f = videoFile1;//1 break; case 3: f = videoFile2; break; } try { BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f)); data = new byte[bis.available()]; bis.read(data); bis.close(); }catch (Exception e) { System.out.println(e); } if(Http_server.File_is_sent)//       { System.out.println("  "); new HTTP_Server_Calling().execute(); Http_server.File_is_sent=false; } } while(stopCamera); if (mediaRecorder != null) { System.out.println(""); } } } } class ServerCreation extends AsyncTask<Void, Void, Void> { @Override protected void onPreExecute() { super.onPreExecute(); } @Override protected Void doInBackground(Void... params) { try { CameraActivity.ss = new ServerSocket(40001);//      System.out.println("  "); new Http_server(CameraActivity.ss.accept()); CameraActivity.ss2 = new ServerSocket(40002);//      }catch (Exception e) { System.out.println(e); System.out.println("   "); } new HTTP_Server_Calling2().start(); return null; } @Override protected void onPostExecute(Void result) { } } class HTTP_Server_Calling extends AsyncTask<Void, Void, Void> { @Override protected void onPreExecute() { super.onPreExecute(); } @Override protected Void doInBackground(Void... params) { try { new Http_server(CameraActivity.ss.accept()); } catch (Exception e) { System.out.println(e); } return null; } @Override protected void onPostExecute(Void result) { } } class HTTP_Server_Calling2 extends Thread//           { public void run() { while (CameraActivity.stopCamera) { try { Thread.sleep(500);//      new Http_server_Sensors(CameraActivity.ss2.accept()); } catch (Exception e) { System.out.println(e); } } } } class Http_server extends Thread { public Socket socket; public static volatile boolean File_is_sent=true; Http_server(Socket s) { System.out.println("  "); socket = s; setPriority(MAX_PRIORITY); start(); } public void run() { try { System.out.println("    "); BufferedOutputStream bos = new BufferedOutputStream((socket.getOutputStream())); bos.write(CameraActivity.data); bos.flush(); bos.close(); socket.close(); } catch (Exception e) { System.out.println(e); } File_is_sent = true; } } public class Http_server_Sensors extends Thread { public Socket socket; PrintWriter pw; Http_server_Sensors(Socket s) { socket = s; setPriority(MAX_PRIORITY); start(); } public void run() { try { pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);//    System.out.println(CameraActivity.Sensors); pw.println(CameraActivity.Sensors);//  pw.flush(); pw.close(); socket.close(); } catch (Exception e) { System.out.println(e); } } } 



A essência do programa após iniciar e ligar a câmera é a seguinte:
escreva um vídeo por dois segundos no primeiro arquivo,
escrevemos o vídeo por dois segundos no segundo arquivo e, enquanto isso, enviamos o primeiro arquivo via TCP-IP via WI-FI local para o computador,
escreva o primeiro arquivo novamente e, enquanto isso, envie o segundo,
e assim por diante.

Em seguida, o ciclo se repete até o botão "parar" ser pressionado ou a bateria do smartphone acabar. Em princípio, é possível implementar um análogo de pressionar botões, usando comandos de um computador, também via TCP, isso não é difícil.

A princípio, o buffer de vídeo consistia em três arquivos no formato 3GP (escrevemos o primeiro, enviamos o terceiro, escrevemos o segundo, enviamos o primeiro, escrevemos o terceiro, enviamos o segundo), mas depois os arquivos são suficientes (gravando e enviando um ao outro) não interfira).

Com uma resolução de câmera de 640 por 480, os arquivos são obtidos, algo em torno de 200-300 kB, o que é bastante difícil para o meu roteador. Ainda não me incomodei com o som, mas tudo parece simples: você instala os codificadores de áudio, as taxas de bits, o número de canais e similares.

Um pouco mais tarde, quando depurei a transferência de vídeo, suplementei o código também transmitindo informações dos sensores do smartphone. Tudo é transmitido trivialmente em uma linha, mas não foi possível transferi-lo pelo mesmo soquete do vídeo. Aparentemente, as classes para a transmissão de uma sequência PrintWriter e a transmissão de dados binários BufferedOutputStream usam fluxos diferentes, mas, em seguida, eles têm um buffer de saída, no qual são bem sucedidos. Como resultado, o vídeo começa a falhar e desmoronar. Além disso, o arquivo de vídeo é transmitido a cada dois segundos e, para sensores, esse intervalo é muito grande. Portanto, foi decidido distribuí-los em diferentes soquetes para que eles não interfiram entre si. Por esse motivo, uma nova classe Http_server_Sensors apareceu.

Assim, organizamos a expedição, agora novamente retornaremos ao lado negro de recebimento.

Como já vimos desde o primeiro exemplo, reproduzir arquivos de vídeo em um aplicativo JAVA usando um player VLC agora não apresenta problemas. O principal é obter esses arquivos.

O seguinte programa de demonstração é responsável por isso.

Video player
 import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JOptionPane; 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.MediaPlayer; import uk.co.caprica.vlcj.player.MediaPlayerEventAdapter; import java.io.*; import java.net.Socket; public class VideoPlayer { public final JFrame frame; public static EmbeddedMediaPlayerComponent mediaPlayerComponent; public final JButton pauseButton; public final JButton rewindButton; public final JButton skipButton; public static String mr1, mr2; public static boolean playing_finished = false; public static boolean File_1_play_starting = false; public static boolean File_1_play_finished = false; public static boolean File_2_play_starting = false; public static boolean File_2_play_finished = false; //192.168.1.128 public static void main(final String[] args) { new NativeDiscovery().discover(); mr1 = "D:\\test1.3gp"; mr2 = "D:\\test2.3gp"; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { VideoPlayer vp = new VideoPlayer(); vp.mediaPlayerComponent.getMediaPlayer().setPlaySubItems(true); VideoPlayer.playing_finished=false; new Control().start();//   // while (!Http_client.File_ready) { // System.out.println(""); try { Thread.sleep(100); } catch (Exception e) { } } // while (true) { playing_finished = false; } // System.out.println("  1"); } }); } public VideoPlayer() { frame = new JFrame("My First Media Player"); frame.setBounds(100, 100, 600, 400); frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { System.out.println(e); mediaPlayerComponent.release(); System.exit(0); } }); JPanel contentPane = new JPanel(); contentPane.setLayout(new BorderLayout()); mediaPlayerComponent = new EmbeddedMediaPlayerComponent(); contentPane.add(mediaPlayerComponent, BorderLayout.CENTER); frame.setContentPane(contentPane); JPanel controlsPane = new JPanel(); pauseButton = new JButton("Pause"); controlsPane.add(pauseButton); rewindButton = new JButton("Rewind"); controlsPane.add(rewindButton); skipButton = new JButton("Skip"); controlsPane.add(skipButton); contentPane.add(controlsPane, BorderLayout.SOUTH); pauseButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { mediaPlayerComponent.getMediaPlayer().pause(); } }); rewindButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { mediaPlayerComponent.getMediaPlayer().skip(-10000); } }); skipButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { mediaPlayerComponent.getMediaPlayer().skip(10000); } }); mediaPlayerComponent.getMediaPlayer().addMediaPlayerEventListener(new MediaPlayerEventAdapter() { @Override public void playing(MediaPlayer mediaPlayer) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { frame.setTitle(String.format( "My First Media Player - %s", mediaPlayerComponent.getMediaPlayer() )); } }); } @Override public void finished(MediaPlayer mediaPlayer) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { playing_finished = true; System.out.println("finished " + playing_finished); //closeWindow(); } }); } @Override public void error(MediaPlayer mediaPlayer) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { JOptionPane.showMessageDialog( frame, "Failed to play media", "Error", JOptionPane.ERROR_MESSAGE ); closeWindow(); } }); } }); frame.setVisible(true); //mediaPlayerComponent.getMediaPlayer(); } public void start(String mrl) { mediaPlayerComponent.getMediaPlayer().setPlaySubItems(true); mediaPlayerComponent.getMediaPlayer().prepareMedia(mrl); //mediaPlayerComponent.getMediaPlayer().parseMedia(); mediaPlayerComponent.getMediaPlayer().playMedia(mrl); // mediaPlayerComponent. } public void closeWindow() { frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING)); } } class PlayFile { public static void run(int number) { if (number==1) { VideoPlayer.mediaPlayerComponent.getMediaPlayer().prepareMedia(VideoPlayer.mr1); System.out.println("  1"); VideoPlayer.mediaPlayerComponent.getMediaPlayer().start(); VideoPlayer.mediaPlayerComponent.getMediaPlayer().playMedia(VideoPlayer.mr1); VideoPlayer.File_1_play_starting = true; VideoPlayer.File_1_play_finished = false; while (!VideoPlayer.playing_finished) {//      try { Thread.sleep(1); } catch (Exception e) { } } VideoPlayer.mediaPlayerComponent.getMediaPlayer().stop(); VideoPlayer.playing_finished = false; VideoPlayer.File_1_play_starting = false; VideoPlayer.File_1_play_finished = true; } { try { Thread.sleep(10); } catch (Exception e) { } } if (number==2) { VideoPlayer.mediaPlayerComponent.getMediaPlayer().prepareMedia(VideoPlayer.mr2); System.out.println("  2"); VideoPlayer.mediaPlayerComponent.getMediaPlayer().start(); VideoPlayer.mediaPlayerComponent.getMediaPlayer().playMedia(VideoPlayer.mr2); VideoPlayer.File_2_play_starting = true; VideoPlayer.File_2_play_finished = false; while (!VideoPlayer.playing_finished) { try { Thread.sleep(1); } catch (Exception e) { } } VideoPlayer.mediaPlayerComponent.getMediaPlayer().stop(); VideoPlayer.playing_finished = false; VideoPlayer.File_2_play_starting = false; VideoPlayer.File_2_play_finished = true; } { try { Thread.sleep(10); } catch (Exception e) { } } } } public class Control extends Thread{ public static boolean P_for_play_1=false; public static boolean P_for_play_2=false; public void run() { // new Http_client(1).start(); while (!Http_client.File1_reception_complete) { try { Thread.sleep(1); } catch (Exception e) { } } while (true) { new Http_client(2).start(); PlayFile.run(1); while (!VideoPlayer.File_1_play_finished) { try { Thread.sleep(1); } catch (Exception e) { } } while (!Http_client.File2_reception_complete) { try { Thread.sleep(1); } catch (Exception e) { } } new Http_client(1).start(); PlayFile.run(2); while (!VideoPlayer.File_2_play_finished) { try { Thread.sleep(1); } catch (Exception e) { } } while (!Http_client.File1_reception_complete) { try { Thread.sleep(1); } catch (Exception e) { } } //PlayFile.run(1); } } } public class Http_client extends Thread { public static boolean File1_starts_writing=false; public static boolean File1_reception_complete=false; public static boolean File2_starts_writing=false; public static boolean File2_reception_complete=false; public int Number; public BufferedOutputStream bos; Http_client(int Number) { this.Number = Number; } public void run(){ try { Socket socket= new Socket("192.168.1.128", 40001); // System.out.println(" "); // PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true); // pw.println("ready");// Greetings with CLIENT // System.out.println(" "); BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); if (Number ==1) { File1_starts_writing=true; File1_reception_complete=false; System.out.println("  1,    2"); bos = new BufferedOutputStream(new FileOutputStream(VideoPlayer.mr1)); byte[] buffer = new byte[32768]; while (true) { //     int readBytesCount = bis.read(buffer); if (readBytesCount == -1) { //   break; } if (readBytesCount > 0) { //    - ,   bos.write(buffer, 0, readBytesCount); } } System.out.println(" "); System.out.println(" "); bos.flush(); bos.close(); File1_starts_writing=false; File1_reception_complete=true; } if (Number==2) { File2_starts_writing=true; File2_reception_complete=false; System.out.println("  2,    1"); bos = new BufferedOutputStream(new FileOutputStream(VideoPlayer.mr2)); byte[] buffer = new byte[32768]; while (true) { //     int readBytesCount = bis.read(buffer); if (readBytesCount == -1) { //   break; } if (readBytesCount > 0) { //    - ,   bos.write(buffer, 0, readBytesCount); } } System.out.println(" "); System.out.println(" "); bos.flush(); bos.close(); File2_starts_writing=false; File2_reception_complete=true; } socket.close(); // pw.close(); bis.close(); } catch(Exception e){ System.out.println(e); System.out.println("gfgfg"); } try { Thread.sleep(10); } catch (Exception e) { } } } 


Sua essência é simples. Um cliente TCP é iniciado, que começa a aguardar o servidor estar pronto no smartphone. Após receber o primeiro arquivo, ele começa a ser reproduzido imediatamente e o arquivo número dois é esperado em paralelo. Além disso, espera-se o final da recepção do segundo arquivo ou o final da reprodução do primeiro. O melhor de tudo, é claro, três estrelas , quando o arquivo é baixado mais rapidamente do que é reproduzido. Se você perdeu o primeiro arquivo, mas ainda não recebeu o segundo, tudo está aguardando ... Demonstraremos uma tela preta. Caso contrário, comece rapidamente a reproduzir o segundo arquivo e baixe o primeiro novamente.
Eu tinha uma vaga esperança de que a pausa entre a troca de arquivos reproduzidos fosse menor que o tempo de reação do olho humano, mas não se concretizou. Lazer, é claro, este VLC.



Como resultado, temos um tipo de vídeo vil e descontínuo (o primeiro trilobita, aparentemente, viu o mundo assim), onde a nitidez ainda está sendo constantemente ajustada. E devemos levar em conta que o vídeo também está atrasado por dois segundos. Em resumo, na produção, eu não recomendo espalhá-lo. Mas eu tenho falta de peixe e trilobita, como eles dizem ...

Resumindo o exposto, podemos absolutamente dizer que:

O envio de vídeo em partes é essencialmente inoperante e só pode ser útil nos casos em que o vídeo é longo o suficiente e você não precisa de uma segunda reação ao que está acontecendo.

A transmissão de vídeo por TCP-IP também é uma idéia incorreta, independentemente do que algumas personalidades de Habr diriam sobre a velocidade de transferência de dados usando esse protocolo (que é supostamente ainda mais rápido que o UDP). Obviamente, as intranets sem fio modernas têm boas características para garantir handshakes contínuos de servidores e clientes TCP, e o próprio TCP parece ter sido atualizado para dados longos, mas ainda assim os plugues entre a reprodução de vídeos aparecem periodicamente na demonstração.

Mas, pelo menos para o futuro, surgiram os seguintes pensamentos:

  1. envie quadro a quadro (não vídeo, mas fotos) via UDP, mas controle as informações via TCP,
  2. conduza molduras por todo o UDP, mas com sinais de sincronização no mesmo canal.

Obviamente, até agora há mais perguntas do que respostas. Existe velocidade de recepção suficiente no JAVA com seu nível de abstração ao trabalhar com a rede e as imagens? É possível fazer 30 fotos normais por segundo no nível acessível para mim no Android? Vou ter que colher antes de enviar para reduzir a taxa de bits? E o JAVA é suficiente para empacotar e descompactar? E se, de repente, algo der certo, será possível seguir o próximo passo, danificar o sistema de visão computacional JAVA OpenCV aqui? Ele mesmo, é claro, sempre é interessante assistir a vídeos em fluxo desde o nível do chão, mas não devemos esquecer o objetivo mais alto - um robô robótico com a inteligência de uma formiga!

Mas, enquanto tivermos o que temos, retornaremos ao carrinho atual. O antigo programa do artigo anterior para o microcontrolador AVR na plataforma Arduino não mudou muito, apenas a opção de ramificação foi adicionada - direção autônoma ou controlada pelo operador. Os dados que o carrinho (medula espinhal) transmite via WIFI são os mesmos - a distância percorrida. Um pouco mais tarde, também anexei a transferência de temperatura de um elemento crítico - o driver do motor. Tudo isso é transmitido e recebido primeiro pelo UART, e já ao entrar na rede via UDP. Não dou o código, toda a análise completa no artigo antes da última .



Para dois motores, o (driver) ainda é suficiente, mais ou menos, mas com quatro depois de um tempo superaquece para um estado inoperante. No começo, tentei usar os sensores analógicos de temperatura mais simples baseados em diodos zener, como o LM335, mas nada resultou disso. Eu não tinha uma fonte de tensão de referência, para abreviar, ION.E pegar milivolts com uma bateria fraca não faz sentido. A propósito, sobre a bateria - quando eu estava cansado de remover e reinserir constantemente 14500 baterias de lítio para recarregar, peguei uma bateria sobressalente de uma chave de fenda e o carrinho começou a funcionar continuamente por uma hora e meia, além de ter uma aparência e peso ameaçadores (sim, está nesta bateria "Olhos"). Portanto, para medir a temperatura, instalei um acelerômetro giroscópio com defeito, baseado no L3G4200D. Felizmente, ele também mediu a temperatura e a transmitiu através do barramento I2C.

Um smartphone sentado atrás da bateria e sobre os olhos do eco-sonar transmite um fluxo de vídeo, leituras do acelerômetro (você pode usá-lo para inclinar o carrinho e a inclinação do carrinho quando está em pé), girar e inclinar em graus na dinâmica já calculada pelo próprio smartphone (uma opção muito conveniente) , aceleração linear na direção da viagem, iluminação. Em geral, é claro, você pode transferir tudo o que seu smartphone pode medir da pressão do ar para a direção norte.

Como resultado, o aplicativo em JAVA adquiriu a seguinte forma:



O engraçado é que ele realmente se parece com um painel de controle de um Lunokhod soviético, só que eu tenho uma tela de TV ao lado e eles a têm no centro.



Ao ligá-lo, primeiro conecte-se ao carrinho e ao smartphone, selecione o modo de controle manual ou autonomia total - e pronto! Até a janela de temperatura do motorista ficar amarela e depois vermelha. Gráficos!

Algo assim parece vivo.


Não trago o programa aqui, porque, como sempre, a construção de janelas ocupa 95% do código. Pode ser encontrado aqui.

No futuro, o cérebro (smartphone) deve aprender a transferir vídeo normal (e não o que é agora) para um computador, onde deve ser reconhecido de alguma maneira de uma maneira boa (enquanto o objetivo é criar um mapa da sala no nível do chão e determinar sua localização). Bem, e já sob o comunismo, gostaria de portá-lo de volta para o meu smartphone para que ele não precisasse de um computador.

Se pelo menos algo der certo, eu definitivamente o publicarei. Obrigado pela atenção.

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


All Articles