Evolucione o cree una base para robots en la plataforma ARDUINO, y manejamos sensores y video a una computadora a través de un teléfono inteligente

Para los queridos lectores de GeekTimes, el próximo (cuarto) artículo tan esperado sobre lo que sucederá si vuelves a mezclar el arduino, ESP8266, WI-FI, sazonarlo con un teléfono inteligente Android y espolvorearlo sobre la aplicación JAVA.

Hablaremos sobre los robots del artículo antes del último, que llegó el momento de al menos ser un poco más inteligentes.

imagen

A quién le importa, bienvenido al gato.

Si no está muy interesado en leer artículos antiguos, entonces brevemente, el punto era que para controlar el carro de cuatro ruedas habitual en la plataforma Arduino, se le agregó un puente inalámbrico UART desarrollado por mí basado en el conocido módulo ESP8266. También por conveniencia (y en general este era el objetivo principal) usando el mismo ESP, escribí un programador para arduinki, que le permite flashearlo usted mismo de forma remota.

imagen

Es decir, el carrito en algún lugar lejano (pero dentro de su red WI-FI) viaja (sí, me gusta escribir esta palabra), envía datos y recibe comandos, y si es necesario, por pedido, también puede cambiar el programa en su microcontrolador AVR. En consecuencia, se ejecutó el programa en JAVA para PC, que puede disfrutar de control y obtener telemetría primitiva en forma de distancia recorrida (interruptor de láminas e imán en la rueda).

imagen

Además, experimenté con éxito en el siguiente artículo con el control del carro usando un teléfono inteligente: botones, inclinaciones e incluso voz. Pero cuando el carro se fue a la habitación de al lado, incluso la voz no pudo devolverlo (a diferencia del gato). Fue allí, golpeó paredes y muebles, se enredó con cables, pero además de información sobre la distancia recorrida, no envió nada.

Por lo tanto, surgió de inmediato la idea de proporcionar al futuro Terminator órganos sensoriales. Una de las opciones más fáciles para esto es el uso del sonar.

imagen

El algoritmo de trabajo para la desgracia es simple, arrancamos el sensor desde un frente y al mismo tiempo algún contador de microcontrolador. HC-SR04 comienza a disparar ultrasonido en la distancia. La señal de respuesta del sensor a través de otro cable señala el final de la medición de distancia, y el intervalo de tiempo entre el inicio y la respuesta es proporcional a la distancia medida. En consecuencia, en este momento, disminuimos la velocidad del mostrador y vemos cuánto entra.

La precisión se obtiene hasta aproximadamente un centímetro, y un rango de metros a dos. No le gustan las superficies de lana y lana (por ejemplo, un gato), donde cualquier señal de eco se ahogó irrevocablemente.

El área afectada del ángulo de visión del HC-SR04 es pequeña, por lo que para saber qué está sucediendo en el marco de un ángulo de al menos 90 grados, es conveniente hacer lo siguiente:

  1. atornille el sensor a la servomáquina y mírelo en diferentes direcciones
  2. pon algunos sensores.

Al principio, implementé la primera opción, colocando el sonar en un servo SG90 barato y el carro se convirtió en un rover. Se requirió tanto tiempo para tomar al menos tres mediciones, por lo que básicamente el carro se puso en rotación con un servo, luego se movió cuidadosamente hacia adelante, pero no muy lejos (y de repente apareció un obstáculo en el costado), y nuevamente sintió el espacio frente a sí mismo. Aún así, el sonido no es la luz para ti.

Por lo tanto, sin más preámbulos, puse tres sistemas de sonda a la vez. El carro adquirió una apariencia de araña ctónica, se detuvo sin brillo frente a los obstáculos y comenzó a rodarlos en el camino. Pero los cerebros al final fueron suficientes para no quedar atrapados en un ambiente amigable. Tuvimos que avanzar - hacia la autonomía y el progreso. Y aquí, sin diferentes sentidos, incluso el gusano nematodo te dirá que no puedes hacerlo.


Además, generalmente los entusiastas comienzan a esculpir en sus creaciones varios sensores nuevos, como giroscopios, acelerómetros, magnetómetros e incluso sensores de FUEGO (todos esos chinos sin nombre producen en millones de cantidades para Arduino). Y yo también, casi comencé por este camino resbaladizo, pero cambié de opinión a tiempo. Y lo hice por este motivo. En la perspectiva más lejana, se suponía que el robot robótico tenía visión en forma de cámara y también entendía lo que ve. Pero el microcontrolador AVR de la placa Arduino le dirá "adiós" en la etapa de recepción del video, sin mencionar el procesamiento. Y de repente mi mirada cayó sobre el viejo teléfono inteligente GALAXY S7, ya maltratado por la vida.

Tal potencia informática, ocho núcleos, 4 gigabytes de memoria, dos cámaras, acceso a la red, ¿qué más se necesita para convertir a un mono en una persona?

Pero solo necesitamos un diseño pequeño para que nuestro teléfono inteligente descanse en un carrito y para que pueda colocarse y quitarse fácilmente.

imagen

Luego me subí al sitio de desarrolladores de Android para averiguar qué otras características puede ofrecernos un teléfono inteligente normal. Resultó que no es pequeño. Teóricamente puedes

acceder a los siguientes 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 dicen, ¡qué hay allí solo! Y, de hecho, no había mucho que fuera específico del GALAXY S7. Por ejemplo, un sensor de humedad. Y la temperatura ambiente (aunque entendí que al estar dentro de la carcasa, mostrará la temperatura del teléfono inteligente). Pero los sensores de presión y luz estaban presentes. Sin mencionar los giroscopios, acelerómetros, con los cuales puede determinar fácilmente su posición en el espacio.

Como resultado, una decisión ha madurado, dejar que el teléfono inteligente reciba y procese toda la información del nivel superior: video y todos estos sensores. Y la plataforma Arduino será responsable, por así decirlo, del inconsciente, de todo lo que ya funciona y no requiere retrabajo, todos estos motores, sonar, interruptores de láminas, etc.

Dado que es difícil depurar el programa directamente en el teléfono inteligente, incluso con UDB, decidí dejar que todo se transfiriera a una computadora personal normal y se procesara allí. Y luego, de alguna manera, cuando haya una versión funcional, devolveremos el cerebro al carro. Debemos comenzar con algo pequeño, y de hecho es interesante observar la transmisión de video desde un carrito frenético.

Los datos de los sensores se pueden enviar en una línea simple, a través de un servidor cliente primitivo, no hay ningún problema con esto. Pero con la transferencia del video, inmediatamente hubo dificultades. En general, necesitaba una transmisión en tiempo real desde la cámara del teléfono inteligente a la ventana de la aplicación en la computadora. Esto es por ahora. En el futuro, no solo yo, sino algún tipo de sistema de reconocimiento de patrones podría mirar esta imagen en la ventana. Por ejemplo JAVA OpenCV. O tal vez incluso una red neuronal desde la nube: D. No sé, esta etapa aún está muy lejos. Pero me gustaría ver el mundo con el "ojo" de un camión robótico.

Todo el mundo conoce numerosas aplicaciones, como una "cámara móvil" de la tienda de Google, donde puede ver una transmisión de video de la cámara de un teléfono inteligente abriendo un navegador con la IP deseada en la computadora. Por lo tanto, al principio pensé que no sería difícil traducirlo desde mi GALAXY (lo cual no fue un error débil), por lo que primero debe verificar cómo será con su recepción en la computadora, dado que puedo escribir de alguna manera solo en JAVA.

Al final resultó que, con la reproducción de video JAVA, por decirlo suavemente, no muy bien. Érase una vez en 1997, se lanzó el llamado Java Media Framework, una biblioteca que facilita el desarrollo de programas que funcionan con audio y video de los creadores de JAVA. Pero, en algún lugar después de 2003, se le puso un gran tornillo y desde entonces ya han pasado 15 años. Después de algunos experimentos, logré ejecutar un archivo en la ventana, no recuerdo cuál ya (parece AVI), pero esta vista parecía bastante miserable. Los archivos con otras extensiones no querían ejecutarse en absoluto, en casos extremos había una pista de audio.

En Internet, encontré dos proyectos alternativos más para trabajar con video: Xuggler y aprica VLCj. El primer proyecto fue atractivo en sus capacidades, pero también murió hace mucho tiempo, pero el segundo resultó ser bastante animado e interesante en su propia idea. Los chicos tomaron y sujetaron a JAVA el conocido reproductor multimedia VLC. Es decir, aprica no usa códecs autoescritos, sino códecs ya hechos. Con él, perderás cualquier archivo. Una decisión acertada, pero lo principal es que ya tiene este reproductor VLC instalado en su computadora. Bueno, ¿quién no lo tiene? Sin embargo, la única advertencia es que tiene las mismas profundidades de bits del jugador y JAVA. Por ejemplo, más tarde, con sorpresa, descubrí que todavía tenía un VLC de 32 bits en mi computadora, en contraste con JAVA de 64 bits. Y medio día de vida se perdió en vano.

Los desarrolladores de Caprica prometen a los usuarios muchas cosas en su sitio web. Y todos los formatos de archivo y el lanzamiento en varias ventanas en una aplicación JAVA, reproducción de video de You-Tube, captura de una transmisión de video "en vivo", etc. Pero la dura realidad puso todo en su lugar. No, no engañaron con archivos: todo se reproduce. Pero ahora el video de YouTube ya no quiere. Al principio no podía entender por qué, pero luego vi una inscripción en el registro que de alguna manera, en algún lugar, era imposible ejecutar un cierto script lua e inmediatamente recordé que:
Este 'raspado de pantalla' de la página web de YouTube es frágil: si YouTube cambia la estructura de sus páginas web, a veces VLC no podrá encontrar la URL de transmisión, cuando esto sucede, debe esperar a que un desarrollador proporcione una nueva LUA script y espere a que se lance una nueva versión de VLC.
En resumen, aparentemente YouTube ha cambiado la estructura de su página web y necesito esperar un nuevo lanzamiento. Por otro lado, necesito una transmisión de video "en vivo" y no reproducir un archivo del sitio. Es decir, incluso si el script lua funcionara, no me ayudaría mucho.

Pero no encontré la transmisión prometida en absoluto, aunque estaba escrito que:

Servidor de transmisión de red (por ejemplo, una estación de radio de red o un servidor de video a pedido);
Cliente de transmisión de red;
Tal vez sea Lista de deseos, o tal vez esté en la versión comercial, es difícil de decir.

Pero los archivos se reproducen, repito, sin ninguna queja. Por ejemplo, puedes crear, aquí hay tanta indecencia


La instalación del paquete en sí no es difícil e incluso se describe en detalle, por ejemplo, aquí . Es cierto, de alguna manera prescribí torpemente las variables de entorno y ahora mi VLC se inicia por primera vez con un retraso de diez segundos, pero luego guarda lo que se necesita en la memoria caché y luego en la sesión actual se inicia sin pausas.

Luego, en su proyecto JAVA, prescribe las dependencias necesarias y puede comenzar a remachar reproductores multimedia en las ventanas JAVA para que funcionen.

Después de haber marcado el plan de trabajo aproximado al costado de la computadora personal, regresé a la fuente de los datos de video, mi teléfono inteligente ANDROID.

Pero aquí esperaba otra gran decepción. Después de haber escaneado bastantes sitios e incluso el manual de ANDROID en línea, descubrí que es imposible organizar la transmisión en tiempo real utilizando medios regulares. Como en Caprica, solo puede leer archivos ya grabados. Es decir, la cámara encendida, MEDIA RECORDER comenzó a grabar cuando era necesario detenerla. Y podemos obtener acceso a los datos (y su transferencia) solo después de una parada.

Encontré confirmación de mis conclusiones en un artículo antiguo. Realmente hubo indicios de que una vez que alguien logró engañar a Android haciéndole creer que estaba escribiendo en un archivo, pero de hecho se deslizó un búfer. Pero en cualquier caso, como ya se explicó anteriormente, no pude captar esa transmisión de video en el lado de la PC en la aplicación JAVA.

Por lo tanto, se tomó una decisión simple, temporal (quiero destacar): enviar una transmisión de video de la cámara en pedazos durante dos segundos. No es un rover, por supuesto, pero el rover ya resulta bastante.

Una vez que me decidí por esta decisión, comencé a dominar las clases CAMERA y MEDIA RECORDER. Hay bastantes ejemplos de código en la red para iniciar la cámara y grabar archivos de video, pero por alguna razón ninguno de ellos funcionó para mí. Ni en el emulador, ni en un dispositivo real. Resultó que la razón radica en los permisos, es decir, en los permisos. Resulta que los ejemplos de código se escribieron en aquellos días en que Android todavía era gratuito y el programador podía hacer lo que quisiera si escribía todos los permisos necesarios en el manifiesto. Pero mi versión actual de OC no permitió esto. Primero, tenía que dar permiso al usuario para escribir el archivo y encender la cámara inmediatamente después de iniciar la aplicación. Esto me costó actividad extra, es decir, en Actividad.

Clase 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); } } 


Después de eso, las cosas salieron bien y apareció el siguiente código de trabajo. La segunda actividad de la clase Camera_Activity es responsable de trabajar con la cámara y grabar archivos de video. Http_server clase para reenviar (el nombre, por supuesto, es incorrecto, pero resultó históricamente). El código es simple, siempre que haya una explicación.



Todo está completamente acostado en el Github. Enlace

Cámara_Actividad
 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); } } } 



La esencia del programa después de iniciar y encender la cámara es la siguiente:
escribe un video por dos segundos en el primer archivo,
escribimos el video durante dos segundos en el segundo archivo y, mientras tanto, enviamos el primer archivo a través de TCP-IP a través de la conexión WI-FI local a la computadora,
vuelva a escribir el primer archivo y, mientras tanto, envíe el segundo,
Y así sucesivamente.

Luego, el ciclo se repite hasta que se presiona el botón "detener" o la batería del teléfono inteligente se agota. En principio, es posible implementar un análogo de presionar botones, usando comandos de una computadora, también a través de TCP, esto no es difícil.

Al principio, el búfer de video, por si acaso, consistía en tres archivos de formato 3GP (escribimos el primero, enviamos el tercero, escribimos el segundo, enviamos el primero, escribimos el tercero, enviamos el segundo), pero luego resultó que los dos archivos son bastante suficientes (grabación y envío mutuo) no interferir).

Con una resolución de cámara de 640 por 480, los archivos se obtienen en algún lugar alrededor de 200-300 kB, lo cual es bastante difícil para mi enrutador. Todavía no me molesté con el sonido, pero todo parece ser simple allí: instalas los codificadores de audio, las tasas de bits, la cantidad de canales y demás.

Un poco más tarde, cuando depuré la transferencia de video, completé el código también transmitiendo información desde los sensores del teléfono inteligente. Todo se transmite trivialmente en una línea, pero no pude transferirlo a través del mismo socket que el video. Aparentemente, las clases para transmitir una cadena PrintWriter y transmitir datos binarios BufferedOutputStream usan diferentes flujos, pero luego tienen un búfer de salida, donde se acoplan con éxito. Como resultado, el video comienza a fallar y desmoronarse. Además, el archivo de video se transmite una vez cada dos segundos, y para los sensores este intervalo es demasiado grande. Por lo tanto, se decidió distribuirlos en diferentes enchufes para que no interfieran entre sí. Por esta razón, ha aparecido una nueva clase Http_server_Sensors.

Entonces, organizamos el despacho, ahora nuevamente volveremos al lado oscuro de recepción.

Como ya hemos visto desde el primer ejemplo, reproducir archivos de video en una aplicación JAVA usando un reproductor VLC ahora no plantea problemas. Lo principal es obtener estos archivos.

El siguiente programa de demostración es responsable de esto.

Reproductor de video
 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) { } } } 


Su esencia es simple. Se inicia un cliente TCP, que comienza a esperar a que el servidor esté listo en el teléfono inteligente. Una vez recibido el primer archivo, comienza a reproducirse de inmediato y se espera que el archivo número dos en paralelo. Además, se espera el final de la recepción del segundo archivo o el final de la reproducción del primero. Lo mejor de todo, por supuesto, tres estrellas , cuando el archivo se descarga más rápido de lo que se reproduce. Si perdió el primer archivo, pero aún no ha recibido el segundo, entonces todo está esperando ... Mostraremos una pantalla en negro. De lo contrario, comience a reproducir rápidamente el segundo archivo y descargue simultáneamente el primero nuevamente.
Tenía una vaga esperanza de que la pausa entre cambiar archivos de reproducción sería menor que el tiempo de reacción del ojo humano, pero no se materializó. Sin prisa, por supuesto, este VLC.



Como resultado, obtenemos una especie de vil video discontinuo (el primer trilobite, aparentemente, vio el mundo así), donde la nitidez todavía se ajusta constantemente. Y debemos tener en cuenta que el video también llega tarde por dos segundos. En resumen, en producción no recomiendo difundirlo. Pero me falta pescado y trilobites, como dicen ...

Resumiendo lo anterior, podemos decir absolutamente que:

El envío de video por partes es esencialmente inoperativo y solo puede ser útil en casos donde el video es lo suficientemente largo y no necesita una segunda reacción a lo que está sucediendo.

La transmisión de video a través de TCP-IP también es una idea incorrecta, independientemente de lo que digan algunas personalidades de Habr sobre la velocidad de transferencia de datos utilizando este protocolo (que supuestamente es incluso más rápido que UDP). Por supuesto, las intranets inalámbricas modernas tienen buenas características para garantizar el apretón de manos continuo de los servidores y clientes TCP, y el propio TCP parece haber sido actualizado para datos largos, pero los enchufes entre la reproducción de videos aparecen periódicamente en la demostración.

Pero, al menos para el futuro, aparecieron los siguientes pensamientos:

  1. envíe cuadro por cuadro (no video, sino fotos) a través de UDP, pero controle la información a través de TCP,
  2. maneje marcos de fotos a través de UDP pero con señales de sincronización en el mismo canal.

Por supuesto, hasta ahora hay más preguntas que respuestas. ¿Hay suficiente velocidad de recepción en JAVA con su nivel de abstracción al trabajar con la red y las imágenes? ¿Es posible hacer 30 disparos normales por segundo en el nivel accesible para mí en Android? ¿Tendré que cosecharlos antes de enviarlos para reducir la tasa de bits? ¿Y entonces JAVA es suficiente para empacar y desempacar? Y si, de repente, algo funciona, ¿será posible pasar por el siguiente paso, atornillar el sistema de visión por computadora JAVA OpenCV aquí? Él mismo, por supuesto, siempre es interesante ver la transmisión de video desde el nivel del piso, pero no debemos olvidar el objetivo más alto: ¡un robot robótico con la inteligencia de una hormiga!

Pero, mientras tengamos lo que tenemos, volveremos al carrito actual. El antiguo programa del artículo anterior para el microcontrolador AVR en la plataforma Arduino no ha cambiado mucho, solo se ha agregado la opción de ramificación: conducción autónoma o controlada por el operador. Los datos que el carro (médula espinal) transmite a través de WIFI son los mismos: la distancia recorrida. Un poco más tarde, también adjunté la transferencia de temperatura de un elemento crítico: el controlador del motor. Todo esto es transmitido y recibido primero por UART, y ya al ingresar a la red a través de UDP. No doy el código, todo el análisis completo en el artículo antes del último .



Para dos motores, el (controlador) sigue siendo más o menos suficiente, pero con cuatro después de un tiempo se sobrecalienta a un estado inoperativo. Al principio intenté usar los sensores de temperatura analógicos más simples basados ​​en diodos zener, como el LM335, pero no salió nada. No tenía una fuente de voltaje de referencia, para abreviar, ION.Y coger milivoltios con poca batería no tiene sentido. Por cierto, sobre la batería: cuando estaba cansado de retirar y volver a colocar constantemente las baterías de litio 14500 para recargar, simplemente tomé una batería de repuesto de un destornillador y el carro comenzó a conducir continuamente durante una hora y media, además tenía un aspecto y peso amenazantes (sí, está en esta batería "Ojos"). Por lo tanto, para medir la temperatura, instalé un giroscopio-acelerómetro defectuoso basado en el L3G4200D. Afortunadamente, también midió la temperatura y la transmitió a través del bus I2C.

Un teléfono inteligente sentado detrás de la batería y sobre el sonar-sonar transmite una transmisión de video, lecturas del acelerómetro (puede usarlo para inclinar el carrito y inclinarlo cuando está parado), rodar y inclinarse en grados en la dinámica ya calculada por el teléfono inteligente (una opción muy conveniente) , aceleración lineal en la dirección de desplazamiento, iluminación. En general, por supuesto, puede transferir todo lo que su teléfono inteligente puede medir desde la presión del aire hacia el norte.

Como resultado, la aplicación en JAVA adquirió la siguiente forma:



Lo curioso es que realmente parece un panel de control de un vehículo lunar soviético, solo que tengo una pantalla de TV en el costado y la tienen en el centro.



Cuando lo encienda, primero conéctese al carro y al teléfono inteligente, seleccione el modo de control manual o la autonomía total, ¡y listo! Hasta que la ventana de temperatura del conductor se vuelva amarilla y luego se vuelva roja. Gráficos!

Algo así se ve en vivo.


No traigo el programa aquí, porque como de costumbre, la construcción de ventanas ocupa el 95% del código. Se puede encontrar aquí.

En el futuro, el cerebro (teléfono inteligente) debería aprender a transferir videos normales (y no lo que es ahora) a una computadora, donde debería ser reconocido de alguna manera (mientras el objetivo es construir un mapa de la habitación a nivel del piso y determinar su ubicación). Bueno, y ya bajo el comunismo, me gustaría portarlo de vuelta a mi teléfono inteligente para que no necesite una computadora.

Si al menos algo funciona, definitivamente lo publicaré. Gracias por su atencion

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


All Articles