2013-02-11 15 views
13

Dopo diversi giorni di ricerche su SO e Google, sto iniziando a rinunciare, quindi ho pensato che avrei potuto postare anche qui.Android: streaming della fotocamera come mjpeg

Sto creando un'app per Android che dovrebbe offrire qualche tipo di chat video. Dato che questo dovrebbe essere il più vicino possibile al tempo reale, ho letto su vari protocolli e ho deciso di provare MJPEG per i principianti (per ora non riguarda l'audio).

In questo momento lo streaming dei dati mi sta facendo impazzire. La connessione viene stabilita, l'app inizia a scrivere i frame di anteprima della videocamera nello stream, ma né VLC né mplayer iniziano a riprodurre video. Il monitoraggio della connessione rivela che i dati stanno arrivando.

Collegamento Questo codice viene eseguito da un compito asincrona, un ascoltatore viene notificato in caso di successo:

try 
    { 
     ServerSocket server = new ServerSocket(8080); 

     socket = server.accept(); 

     server.close(); 

     Log.i(TAG, "New connection to :" + socket.getInetAddress()); 

     stream = new DataOutputStream(socket.getOutputStream()); 
     prepared = true; 
    } 
    catch (IOException e) 
    { 
     Log.e(TAG, e.getMessage(); 
    } 

Sul mio PC eseguo 'mplayer http://tabletIP:8080' e la tavoletta registra una connessione (e quindi inizia il mio streamer e l'anteprima della fotocamera). Funziona anche con VLC.

Streaming Questo scrive l'intestazione al flusso:

if (stream != null) 
{ 
    try 
    { 
     // send the header 
     stream.write(("HTTP/1.0 200 OK\r\n" + 
         "Server: iRecon\r\n" + 
         "Connection: close\r\n" + 
         "Max-Age: 0\r\n" + 
         "Expires: 0\r\n" + 
         "Cache-Control: no-cache, private\r\n" + 
         "Pragma: no-cache\r\n" + 
         "Content-Type: multipart/x-mixed-replace; " + 
         "boundary=--" + boundary + 
         "\r\n\r\n").getBytes()); 

     stream.flush(); 

     streaming = true; 
    } 
    catch (IOException e) 
    { 
     notifyOnEncoderError(this, "Error while writing header: " + e.getMessage()); 
     stop(); 
    } 
} 

Successivamente streaming è attivato attraverso la richiamata Camera.onPreviewFrame():

@Override 
public void onPreviewFrame(byte[] data, Camera camera) 
{ 
    frame = data; 

    if (streaming) 
     mHandler.post(this); 
} 

@Override 
public void run() 
{ 
    // TODO: cache not filling? 
    try 
    { 
        // buffer is a ByteArrayOutputStream 
     buffer.reset(); 

     switch (imageFormat) 
     { 
      case ImageFormat.JPEG: 
       // nothing to do, leave it that way 
       buffer.write(frame); 
       break; 

      case ImageFormat.NV16: 
      case ImageFormat.NV21: 
      case ImageFormat.YUY2: 
      case ImageFormat.YV12: 
       new YuvImage(frame, imageFormat, w, h, null).compressToJpeg(area, 100, buffer); 
       break; 

      default: 
       throw new IOException("Error while encoding: unsupported image format"); 
     } 

     buffer.flush(); 

     // write the content header 
     stream.write(("--" + boundary + "\r\n" + 
         "Content-type: image/jpg\r\n" + 
         "Content-Length: " + buffer.size() + 
         "\r\n\r\n").getBytes()); 

     // Should omit the array copy 
     buffer.writeTo(stream); 

     stream.write("\r\n\r\n".getBytes()); 
     stream.flush(); 
    } 
    catch (IOException e) 
    { 
     stop(); 
     notifyOnEncoderError(this, e.getMessage()); 
    } 
} 

Non c'è eccezione generata. L'mHandler gira nel proprio HandlerThread. Solo per essere sicuro di aver provato ad usare un AsyncTask, senza alcun risultato (btw, è meglio?).

I frame codificati vanno bene sul lato Android, li ho salvati in file jpg e li ho potuto aprire.

La mia ipotesi è che devo raggruppare i dati in qualche modo o dover impostare alcune opzioni per il socket o qualcosa del genere, ma .... beh, sono bloccato.

tl; dr: VLC non giocare flusso, mplayer dice 'cache non riempire', problema probabilmente ultimo segmento di codice, hanno bisogno di aiuto ~ :)

Grazie gentilmente!

risposta

11

Ho capito. Sembra, come se le mie intestazioni http/content fossero incasinate. Le intestazioni corrette dovrebbero essere:

stream.write(("HTTP/1.0 200 OK\r\n" + 
          "Server: iRecon\r\n" + 
          "Connection: close\r\n" + 
          "Max-Age: 0\r\n" + 
          "Expires: 0\r\n" + 
          "Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0\r\n" + 
          "Pragma: no-cache\r\n" + 
          "Content-Type: multipart/x-mixed-replace; " + 
          "boundary=" + boundary + "\r\n" + 
          "\r\n" + 
          "--" + boundary + "\r\n").getBytes()); 

e

stream.write(("Content-type: image/jpeg\r\n" + 
         "Content-Length: " + buffer.size() + "\r\n" + 
         "X-Timestamp:" + timestamp + "\r\n" + 
         "\r\n").getBytes()); 

buffer.writeTo(stream); 
stream.write(("\r\n--" + boundary + "\r\n").getBytes()); 

Naturalmente, dove mettere il confine è la vostra scelta. Inoltre ci sono probabilmente alcuni campi che sono opzionali (ad esempio la maggior parte in Cache-Control), ma questo funziona e fino ad ora ero troppo pigro per spogliarli. La parte importante è ricordare le interruzioni di riga (\r\n thingies) ...

+1

Potrebbe inviare il codice per ottenere MJPEG dalla fotocamera. – Sourav301

+0

@Sourav La classe deve implementare Camera.PreviewCallback. Dopo aver inizializzato la videocamera, chiama camera.setPreviewCallback (...). Il resto per il recupero di JPEGS è già nella mia domanda. – Managarm

+1

Per coloro che si chiedono quale limite dovrebbe essere, può essere qualsiasi stringa java (composta da alfabeti, non sono sicuro che i numeri e caratteri speciali funzioneranno). –