Il lato client viene eseguito su smartphone ed è costituito da due blocchi fondamentali:
All'avvio dell'applicazione vengono inizializzate le strutture che permettono di salvare i punti scaricati dal server, la finestra dove verrà visualizzata
la telecamera e la classe in OpenGL che permette la visualizzazione delle nuove posizioni. Attraverso un menù in alto si può accedere alle impostazioni
riguardanti l'indirizzo IP del server e le porte usate per la comunicazione TCP e UDP.
Si tratta di una classe figlia della classe Thread di java. All'avvio essa preleva dal buffer della preview dello schermo lo stream di byte relativi all'immagine corrente mostrata. Viene fatta quindi una codifica dell'immagine in JPEG, ottenendo lo stream di byte che deve essere inviato al server. E' stato deciso di limitare la dimensione dei pacchetti trasmessi a 4096 byte per evitare problemi di perdite dati eccessive attraverso la rete WLAN: lo stream viene quindi diviso per 4096, viene inviato il numero di datagram che formano l'immagine al server (in modo che riesca a ricostruirla correttamente) ed infine tramite un ciclo viene trasmesso l'intero frame.
while (true) { try{ JPGBytes = mCameraPreview.getFrameJPG(mCameraPreview.getImageBuffer()); Integer totalPack = 1 + (JPGBytes.length - 1) / PACK_SIZE; byte[] iBuf = intToByteArray(totalPack); datagramPacket = new DatagramPacket(iBuf, iBuf.length, inetAddress, mPort); mSocket.send(datagramPacket); // Invio lunghezza pacchetto JPG for (int i = 0; i < totalPack; i++) { toSend = Arrays.copyOfRange(JPGBytes, i * PACK_SIZE, (i + 1) * PACK_SIZE); if (toSend.length <= PACK_SIZE) { mSocket.send(new DatagramPacket(toSend, PACK_SIZE, inetAddress, mPort)); } else { break; } } try { Thread.sleep(FRAME_INTERVAL); } catch (InterruptedException e) { e.printStackTrace(); break; } if (Thread.currentThread().isInterrupted()) break; nextCycle = System.currentTimeMillis(); duration = nextCycle - lastCycle; fps = (1 / duration) * 1000; kbps = PACK_SIZE * totalPack / duration / 1024 * 8; String timeLog = "\teffective FPS: " + fps + " \tkbps: " + kbps; Log.d("CONTR_FPS", timeLog); lastCycle = nextCycle; } catch(IOException e){ e.printStackTrace(); } catch(IllegalArgumentException e){ e.printStackTrace(); } }
Il funzionamento di questo thread ha richiesto un funzionamento asincrono rispetto all'invio immagini della parte UDP, poiché c'era il rischio che uno bloccasse
l'altro nelle fasi più critiche. Per questo motivo la classe del client TCP viene fatta girare in un task asincrono che si avvia alla pressione
del tasto start. L'unico compito di questo task è quello di restare in ascolto sulla porta TCP impostata. Alla pressione del tasto stop questo task viene terminato.
La classe TCP_client implementa il socket, il quale è un semplice ricevitore di stringhe
che vengono passate subito alla classe di gestione delle camere per la determinazione della posizione.
protected class PositionReceiver extends AsyncTask{ private TcpClient tcpThread = null; @Override protected String doInBackground(String... params) { if(isDefaultStart) { //default init tcpThread = new TcpClient(new TcpClient.OnMessageReceived() { @Override //here the messageReceived method is implemented public void messageReceived(String message) { //this method calls the onProgressUpdate publishProgress(message); } }); } else{ tcpThread = new TcpClient(new TcpClient.OnMessageReceived() { @Override public void messageReceived(String message) { publishProgress(message); } },params[0], params[1]); } tcpThread.run(); if(isCancelled()) tcpThread.stopClient(); return null; } @Override protected void onProgressUpdate(String... values) { super.onProgressUpdate(values); //response received from server Log.d("test", "posizione ricevuta: " + values[0]); } }
Per dare un senso grafico alle posizioni della camera sul display dello smartphone, si è deciso di usare le librerie OpenGL ES (un sottoinsieme delle librerie grafiche OpenGL pensato per dispositivi integrati).
Questa scelta si è dimostrata vincente per poter rappresentare facilmente in un spazio 3D degli oggetti, potendo definire agilmente una vista della scena.
La rappresentazione delle camere è stata fatta con dei triangoli orientati nella direzione in cui si guarda con lo smarthphone. L'ultima camera aggiunta definisce le posizioni e le orientazioni relative delle camere precedenti. Delle linee rosse collegano le camere per mostrare più in chiaro un percorso. Un altro motivo per cui è stata scelta OpenGl ES è l'utilizzo dell'accelerazione grafica della Gpu, permettendo fluidità anche in dispositivi obsoleti.