2013-02-11 139 views
13

經過幾天搜索SO和谷歌我開始放棄,所以我想我不妨在這裏發帖。Android:將相機作爲mjpeg流式傳輸

我正在創建一個應用程序,應該提供某種視頻聊天。因爲這應該儘可能接近實時,所以我閱讀了各種協議,並決定嘗試MJPEG作爲初學者(現在不涉及音頻)。

現在流式傳輸數據讓我瘋狂。連接建立後,應用程序開始將相機預覽幀寫入流,但VLC和mplayer都不開始播放視頻。監視連接顯示數據正在到達。

連接 此代碼是由一個異步任務執行的監聽器通知上的成功:

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

我的電腦上我執行「mplayer的http://tabletIP:8080」和平板登記的連接(於是開始我的流光和相機預覽)。這也適用於VLC。

這寫頭流:

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

之後數據流是通過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()); 
    } 
} 

沒有拋出異常。 mHandler運行在它自己的HandlerThread中。只是爲了確定我嘗試使用AsyncTask,無濟於事(順便說一句,這是更好?)。

編碼的框架在android端很好,我將它們保存到jpg文件並可以打開它們。

我的猜測是我必須以某種方式將數據聚類在一起,或者必須爲套接字或某些東西設置一些選項,但....好吧,我卡住了。

TL;博士: VLC不打流,mplayer的說 '不緩存填充',可能是在最後的代碼段的問題,需要幫助〜:)

太感謝您了!

回答

11

我明白了。似乎,就像我的HTTP /內容標題搞砸了。正確的標題應該是:

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()); 

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()); 

當然,在這裏把邊界是你自己的選擇。也可能有些字段是可選的(例如大多數在緩存控制中),但是這種方法可行,直到現在我都懶得剝奪它們。重要的部分是記住linebreaks(\r\n thingies)...

+1

你可以請發佈代碼從相機獲取MJPEG。 – Sourav301 2014-01-21 09:06:39

+0

@Sourav你的課必須實現Camera.PreviewCallback。初始化相機調用camera.setPreviewCallback(...)後。其餘的檢索JPEGS已經在我的問題。 – Managarm 2014-02-10 13:40:08

+1

對於那些想知道應該是什麼邊界的人來說,它可以是任何Java字符串(由字母組成,不知道數字和特殊字符是否可以工作)。 – 2015-06-10 21:15:51