android視頻監控的解決方案探討


1. camera YUV420數據直接socket發送到服務器端,轉換成RGB顯示 佔用帶寬較多。

      原理闡述:基於socket,wifi。 android客戶端獲取的幀數據的格式爲yuv420格式,本來將此data數據流直接傳輸到另一android端,由接收端完成圖像的解碼轉換工作,但是發現如果直接傳數據流數據量太大,還不如直接在本地解碼轉換完畢生成圖片,然後將圖片傳到另外一android端進行重繪排列。

     考慮到效率,可將核心的解碼轉換封裝成c實現.so,速度好些

     改進: 項目採用多播協議,即如果在多臺電腦上同時運行服務端,則一臺手機可以同時控制多臺電腦做同樣的事情,同時操作所有開啓服務端電腦的ppt。

    “   談下實現思路:
1 socket實現傳輸,目前網絡用的wifi
2 java端的視頻獲取我採用的是jmf框架
3 邏輯比較簡單:總體實現無非就是android端視頻捕獲發送yuv原格式到pc端進行解碼而後在pc上顯示,
同時pc通過jmf上獲取camera攝像頭的數據,通過socket傳輸到android端進行重繪。


2. RTP SDP結合+

    The following network protocols are supported for audio and video playback:
RTSP (RTP, SDP)
HTTP/HTTPS progressive streaming
HTTP/HTTPS live streaming draft protocol:



+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Android中抓取手機視頻流數據。

目前實時抓取手機視頻數據有2種方法,一種是通過camera的回調獲取源數據,這裏獲取的源數據是沒有編碼的數據。
有的人發送yuv數據然後在那繪製圖片,也說視頻聊天,真是可笑。這種方式是可是實現視頻聊天的,但是需要移植編碼庫

目前可移植的有,android的opencore編碼,參考http://www.shouyanwang.org/thread-184-1-1.html

ffmpeg編碼,這個網上很難找,但是也有人實現哦。(編碼效率好,但是支持不了高清視頻)

編碼完成可以通過rtp協議發送。就可以視頻啦。rtp協議什麼的就不說了哦。

以上方法的代碼,在這個鏈接最好回覆我貼了代碼,不是完整的。鏈接:http://www.eoeandroid.com/thread-51460-1-1.html

第2種方法,也就是我用的方法。

通過MediaRecorder錄製。然後綁定一個localsocket,可以獲取編碼後的視頻數據傳輸;

代碼如下:

[java] view plaincopy
  1. package com.pei;  
  2.   
  3. import java.io.DataInputStream;  
  4. import java.io.IOException;  
  5. import java.io.InputStream;  
  6. import android.app.Activity;  
  7. import android.os.Bundle;  
  8. import android.graphics.PixelFormat;  
  9. import android.media.MediaRecorder;  
  10. import android.net.LocalServerSocket;  
  11. import android.net.LocalSocket;  
  12. import android.net.LocalSocketAddress;  
  13. import android.view.SurfaceHolder;  
  14. import android.view.SurfaceView;  
  15. import android.view.View;  
  16. import android.view.Window;  
  17. import android.view.WindowManager;  
  18.   
  19. /** 
  20.  * class name:VideoCameraActivity<BR> 
  21.  * class description:CATCH THE VIDEODATA SEND TO RED5<BR> 
  22.  * PS: <BR> 
  23.  *  
  24.  * @version 1.00 2011/11/05 
  25.  * @author CODYY)peijiangping 
  26.  */  
  27. public class VideoCameraActivity extends Activity implements  
  28.         SurfaceHolder.Callback, MediaRecorder.OnErrorListener,  
  29.         MediaRecorder.OnInfoListener {  
  30.     private static final int mVideoEncoder = MediaRecorder.VideoEncoder.H264;  
  31.     private LocalSocket receiver, sender;  
  32.     private LocalServerSocket lss;  
  33.     private MediaRecorder mMediaRecorder = null;  
  34.     private boolean mMediaRecorderRecording = false;  
  35.     private SurfaceView mSurfaceView = null;  
  36.     private SurfaceHolder mSurfaceHolder = null;  
  37.     private Thread t;  
  38.   
  39.     @Override  
  40.     public void onCreate(Bundle savedInstanceState) {  
  41.         super.onCreate(savedInstanceState);  
  42.         getWindow().setFormat(PixelFormat.TRANSLUCENT);  
  43.         requestWindowFeature(Window.FEATURE_NO_TITLE);  
  44.         getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,  
  45.                 WindowManager.LayoutParams.FLAG_FULLSCREEN);  
  46.         setContentView(R.layout.main);  
  47.         mSurfaceView = (SurfaceView) this.findViewById(R.id.surface_camera);  
  48.         SurfaceHolder holder = mSurfaceView.getHolder();  
  49.         holder.addCallback(this);  
  50.         holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);  
  51.         mSurfaceView.setVisibility(View.VISIBLE);  
  52.         receiver = new LocalSocket();  
  53.         try {  
  54.             lss = new LocalServerSocket("VideoCamera");  
  55.             receiver.connect(new LocalSocketAddress("VideoCamera"));  
  56.             receiver.setReceiveBufferSize(500000);  
  57.             receiver.setSendBufferSize(500000);  
  58.             sender = lss.accept();  
  59.             sender.setReceiveBufferSize(500000);  
  60.             sender.setSendBufferSize(500000);  
  61.         } catch (IOException e) {  
  62.             finish();  
  63.             return;  
  64.         }  
  65.     }  
  66.   
  67.     @Override  
  68.     public void onPause() {  
  69.         super.onPause();  
  70.         if (mMediaRecorderRecording) {  
  71.             stopVideoRecording();  
  72.             try {  
  73.                 lss.close();  
  74.                 receiver.close();  
  75.                 sender.close();  
  76.             } catch (IOException e) {  
  77.                 e.printStackTrace();  
  78.             }  
  79.         }  
  80.         finish();  
  81.     }  
  82.   
  83.     private void stopVideoRecording() {  
  84.         System.out.println("stopVideoRecording");  
  85.         if (mMediaRecorderRecording || mMediaRecorder != null) {  
  86.             if (t != null)  
  87.                 t.interrupt();  
  88.             releaseMediaRecorder();  
  89.         }  
  90.     }  
  91.   
  92.     private void startVideoRecording() {  
  93.         (t = new Thread() {  
  94.             public void run() {  
  95.                 int frame_size = 20000;  
  96.                 byte[] buffer = new byte[1024 * 64];  
  97.                 int num, number = 0;  
  98.                 InputStream fis = null;  
  99.                 try {  
  100.                     fis = receiver.getInputStream();  
  101.                 } catch (IOException e1) {  
  102.                     return;  
  103.                 }  
  104.                 number = 0;  
  105.                 releaseMediaRecorder();  
  106.                 while (true) {  
  107.                     System.out.println("ok");  
  108.                     try {  
  109.                         num = fis.read(buffer, number, frame_size);  
  110.                         number += num;  
  111.                         if (num < frame_size) {  
  112.                             System.out.println("recoend break");  
  113.                             break;  
  114.                         }  
  115.                     } catch (IOException e) {  
  116.                         System.out.println("exception break");  
  117.                         break;  
  118.                     }  
  119.                 }  
  120.                 initializeVideo();  
  121.                 number = 0;  
  122.                 Consumer consumer = new Publisher();// Publisher繼承了Consumer  
  123.                 Thread consumerThread = new Thread((Runnable) consumer);  
  124.                 consumer.setRecording(true);// 設置線程狀態;  
  125.                 consumerThread.start();// 開始發佈數據流  
  126.                 DataInputStream dis = new DataInputStream(fis);  
  127.                 try {  
  128.                     dis.read(buffer, 032);  
  129.                 } catch (IOException e1) {  
  130.                     e1.printStackTrace();  
  131.                 }  
  132.                 byte[] aa = { 0x010x42, (byte0x800x0A, (byte0xFF,  
  133.                         (byte0xE10x000x120x670x42, (byte0x800x0A,  
  134.                         (byte0xE90x02, (byte0xC10x290x080x000x00,  
  135.                         0x1F0x400x000x04, (byte0xE20x000x200x01,  
  136.                         0x000x040x68, (byte0xCE0x3C, (byte0x80 };  
  137.                 consumer.putData(System.currentTimeMillis(), aa, 33);  
  138.                 while (true) {  
  139.                     try {  
  140.                         int h264length = dis.readInt();  
  141.                         number = 0;  
  142.                         while (number < h264length) {  
  143.                             int lost = h264length - number;  
  144.                             num = fis.read(buffer, 0,  
  145.                                     frame_size < lost ? frame_size : lost);  
  146.                             number += num;  
  147.                             consumer.putData(System.currentTimeMillis(),  
  148.                                     buffer, num);  
  149.                         }  
  150.                     } catch (IOException e) {  
  151.                         break;  
  152.                     }  
  153.                 }  
  154.                 consumer.setRecording(false);// 設置線程狀態;  
  155.             }  
  156.         }).start();  
  157.     }  
  158.   
  159.     private boolean initializeVideo() {  
  160.         System.out.println("initializeVideo");  
  161.         if (mSurfaceHolder == null)  
  162.             return false;  
  163.         mMediaRecorderRecording = true;  
  164.         if (mMediaRecorder == null)  
  165.             mMediaRecorder = new MediaRecorder();  
  166.         else  
  167.             mMediaRecorder.reset();  
  168.         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);  
  169.         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);  
  170.         mMediaRecorder.setVideoFrameRate(20);  
  171.         mMediaRecorder.setVideoSize(352288);  
  172.         mMediaRecorder.setVideoEncoder(mVideoEncoder);  
  173.         mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());  
  174.         mMediaRecorder.setMaxDuration(0);  
  175.         mMediaRecorder.setMaxFileSize(0);  
  176.         mMediaRecorder.setOutputFile(sender.getFileDescriptor());  
  177.         try {  
  178.             mMediaRecorder.setOnInfoListener(this);  
  179.             mMediaRecorder.setOnErrorListener(this);  
  180.             mMediaRecorder.prepare();  
  181.             mMediaRecorder.start();  
  182.         } catch (IOException exception) {  
  183.             releaseMediaRecorder();  
  184.             finish();  
  185.             return false;  
  186.         }  
  187.         return true;  
  188.     }  
  189.   
  190.     private void releaseMediaRecorder() {  
  191.         System.out.println("Releasing media recorder.");  
  192.         if (mMediaRecorder != null) {  
  193.             if (mMediaRecorderRecording) {  
  194.                 try {  
  195.                     mMediaRecorder.setOnErrorListener(null);  
  196.                     mMediaRecorder.setOnInfoListener(null);  
  197.                     mMediaRecorder.stop();  
  198.                 } catch (RuntimeException e) {  
  199.                     System.out.println("stop fail: " + e.getMessage());  
  200.                 }  
  201.                 mMediaRecorderRecording = false;  
  202.             }  
  203.             mMediaRecorder.reset();  
  204.             mMediaRecorder.release();  
  205.             mMediaRecorder = null;  
  206.         }  
  207.     }  
  208.   
  209.     @Override  
  210.     public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {  
  211.         System.out.println("surfaceChanged");  
  212.         mSurfaceHolder = holder;  
  213.         if (!mMediaRecorderRecording) {  
  214.             initializeVideo();  
  215.             startVideoRecording();  
  216.         }  
  217.     }  
  218.   
  219.     @Override  
  220.     public void surfaceCreated(SurfaceHolder holder) {  
  221.         System.out.println("surfaceCreated");  
  222.         mSurfaceHolder = holder;  
  223.     }  
  224.   
  225.     @Override  
  226.     public void surfaceDestroyed(SurfaceHolder holder) {  
  227.         System.out.println("surfaceDestroyed");  
  228.         mSurfaceView = null;  
  229.         mSurfaceHolder = null;  
  230.         mMediaRecorder = null;  
  231.         if (t != null) {  
  232.             t.interrupt();  
  233.         }  
  234.     }  
  235.   
  236.     @Override  
  237.     public void onInfo(MediaRecorder mr, int what, int extra) {  
  238.         switch (what) {  
  239.         case MediaRecorder.MEDIA_RECORDER_INFO_UNKNOWN:  
  240.             System.out.println("MEDIA_RECORDER_INFO_UNKNOWN");  
  241.             break;  
  242.        case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED:  
            System.out.println("MEDIA_RECORDER_INFO_MAX_DURATION_REACHED");  
            break;  
           case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED:  
            System.out.println("MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED");  
            break;  
        }  
    }  
      
    @Override  
    public void onError(MediaRecorder mr, int what, int extra) {  
        if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {  
            System.out.println("MEDIA_RECORDER_ERROR_UNKNOWN");  
            finish();  
        }  
    }  


h264中avc和flv數據的解析

  1. 計算 AVCDecoderConfigurationRecord  得到 CodecPrivateData 數據(只有第一幀需要);
  2. 計算 NALUs 得到幀數據。

 

計算 AVCDecoderConfigurationRecord  得到 CodecPrivateData 數據

H.264 視頻流的CodecPrivateData 實際上就是 AVCDecoderConfigurationRecord 中 SequenceParameterSets(SPS)和 PictureParameterSets(PPS)使用 byte[] {00, 00, 01} 連接的字節數組

注意!FLV 文件中第一個 VIDEOTAG 的 VIDEODATA 的 AVCVIDEOPACKET 的 Data 總是 AVCDecoderConfigurationRecord(在 ISO/IEC 14496-15 中定義),解碼的時候注意跳過這個 VIDOETAG。

 

AVCDecoderConfigurationRecord 結構的定義:

aligned(8) class AVCDecoderConfigurationRecord { 
unsigned int(8) configurationVersion = 1; 
unsigned int(8) AVCProfileIndication; 
unsigned int(8) profile_compatibility; 
unsigned int(8) AVCLevelIndication; 
bit(6) reserved = ‘111111’b; 
unsigned int(2) lengthSizeMinusOne; 
bit(3) reserved = ‘111’b; 
unsigned int(5) numOfSequenceParameterSets; 
for (i=0; i< numOfSequenceParameterSets; i++) { 
unsigned int(16) sequenceParameterSetLength ; 
bit(8*sequenceParameterSetLength) sequenceParameterSetNALUnit; 

unsigned int(8) numOfPictureParameterSets; 
for (i=0; i< numOfPictureParameterSets; i++) { 
unsigned int(16) pictureParameterSetLength; 
bit(8*pictureParameterSetLength) pictureParameterSetNALUnit; 

}

 

下面藍色的部分就是 FLV 文件中的 AVCDecoderConfigurationRecord 部分。

00000130h: 00 00 00 17 00 00 00 00 01 4D 40 15 FF E1 00 0A ; .........M@.?. 
00000140h: 67 4D 40 15 96 53 01 00 4A 20 01 00 05 68 E9 23 ; gM@.朣..J ...h? 
00000150h: 88 00 00 00 00 2A 08 00 00 52 00 00 00 00 00 00 ; ?...*...R......

 

根據 AVCDecoderConfigurationRecord 結構的定義:

  • configurationVersion = 01
  • AVCProfileIndication = 4D
  • profile_compatibility = 40
  • AVCLevelIndication = 15
  • lengthSizeMinusOne = FF <- 非常重要,是 H.264 視頻中 NALU 的長度,計算方法是 1 + (lengthSizeMinusOne & 3)
  • numOfSequenceParameterSets = E1 <- SPS 的個數,計算方法是 numOfSequenceParameterSets & 0x1F
  • sequenceParameterSetLength = 00 0A <- SPS 的長度
  • sequenceParameterSetNALUnits = 67 4D 40 15 96 53 01 00 4A 20 <- SPS
  • numOfPictureParameterSets = 01 <- PPS 的個數
  • pictureParameterSetLength = 00 05 <- PPS 的長度
  • pictureParameterSetNALUnits = 68 E9 23 88 00 <- PPS

 

因此 CodecPrivateData 的字符串表示就是 000001674D4015965301004A2000000168E9238800

 

但是設置 MediaStreamAttributeKeys.CodecPrivateData 是沒用的(只有 H.264 是這樣,其他類型的視頻流仍然需要設置),只有將 CodecPrivateData 寫入 H.264 視頻流第一幀數據的前面 Silverlight 才能正常解碼。

也就是說,Silverlight 的 H.264 解碼器會讀取第一幀前面的 CodecPrivateData 數據來進行配置。

因爲 CodecPrivateData 數據已經包含視頻流的解碼器參數(包括視頻的寬高),所以就不需要設置 MediaStreamAttributeKeys.CodecPrivateData、MediaStreamAttributeKeys.Width 和 MediaStreamAttributeKeys.Height 了。

 

計算 NALU 得到幀數據

FLV 文件中 VIDEOTAG 的 VIDEODATA 的 AVCVIDEOPACKET 的 Data 不是原始視頻幀數據,而是一個或更多個 NALU 數據片段。在這篇文章中,你認爲 H.264 視頻幀數據是由多個 NALU 組成的。當然實際上並不是這樣,關於這部分的概念請自行 Google,本文將不做討論。

 

下面是 FLV 文件中 VIDEOTAG 的 VIDEODATA 的 AVCVIDEOPACKET 的 Data 屬性的數據(第一幀數據)。

  • 紅色的部分是 NALU 數據的長度,而紅色部分的長度則由 lengthSizeMinusOne 決定。
  • 藍色的部分是 NALU 數據部分。
  • 刪除的部分是廢棄的數據。

00000300h: 00 00 00 00 00 17 01 00 00 22 00 00 00 31 65 88 ; ........."...1e? 
00000310h: 80 40 05 B7 95 53 67 FF 84 6C 07 EB 00 F8 45 FB ; €@.窌Sg刲.?鳨? 
00000320h: F9 15 71 0D A4 C5 2C 00 00 03 00 00 03 00 3F 2B ; ?q.づ,.......?+ 
00000330h: 5B 06 57 48 29 F4 08 00 00 0A 10 02 D0 7A FE 00 ; [.WH)?.....衵? 
00000340h: 00 00 38 65 01 22 22 01 00 17 B7 95 53 67 FF 84 ; ..8e.""...窌Sg? 
00000350h: 6C 07 EB 00 F8 45 FB F9 15 71 0D A4 C5 2C 00 E8 ; l.?鳨.q.づ,.? 
00000360h: F3 37 75 43 90 00 00 03 00 15 EF AA A8 53 86 01 ; ?uC?....錸⊿? 
00000370h: DD 57 60 00 00 03 01 59 0C F4 3C 00 00 00 33 65 ; 軼`....Y.?...3e 
00000380h: 00 90 88 80 40 05 B7 95 53 67 FF 84 6C 07 EB 00 ; .悎€@.窌Sg刲.? 
00000390h: F8 45 FB F9 15 71 0D A4 C5 2C 00 00 03 00 00 03 ; 鳨.q.づ,...... 
000003a0h: 00 3F 2B 5B 06 57 48 29 F4 08 00 00 0A 10 02 D0 ; .?+[.WH)?.....? 
000003b0h: 7A FE 00 00 00 38 65 00 D8 88 80 40 05 B7 95 53 ; z?..8e.貓€@.窌S 
000003c0h: 67 FF 84 6C 07 EB 00 F8 45 FB F9 15 71 0D A4 C5 ; g刲.?鳨.q.づ 
000003d0h: 2C 00 E8 F3 37 75 43 90 00 00 03 00 15 EF AA A8 ; ,.梵7uC?....錸? 
000003e0h: 53 86 01 DD 57 60 00 00 03 01 59 0C F4 3C 00 00 ; S?軼`....Y.?.. 
000003f0h: 00 F4 08 00 01 33 00 00 17 00 00 00 00 AF 01 27 ; .?..3.......?'

 

幀數據是將多個 NALU 使用 byte[] {00, 00, 01} 連接的字節數組。

 

byte[] = {

00,00,01,65,88, 
80,40,05,B7,95,53,67,FF,84,6C,07,EB,00,F8,45,FB, 
F9,15,71,0D,A4,C5,2C,00,00,03,00,00,03,00,3F,2B, 
5B,06,57,48,29,F4,08,00,00,0A,10,02,D0,7A,FE,

00,00,01,65,01,22,22,01,00,17,B7,95,53,67,FF,84, 
6C,07,EB,00,F8,45,FB,F9,15,71,0D,A4,C5,2C,00,E8, 
F3,37,75,43,90,00,00,03,00,15,EF,AA,A8,53,86,01, 
DD,57,60,00,00,03,01,59,0C,F4,3C,

00,00,01,65, 
00,90,88,80,40,05,B7,95,53,67,FF,84,6C,07,EB,00, 
F8,45,FB,F9,15,71,0D,A4,C5,2C,00,00,03,00,00,03, 
00,3F,2B,5B,06,57,48,29,F4,08,00,00,0A,10,02,D0, 
7A,FE,

00,00,01,65,00,D8,88,80,40,05,B7,95,53, 
67,FF,84,6C,07,EB,00,F8,45,FB,F9,15,71,0D,A4,C5, 
2C,00,E8,F3,37,75,43,90,00,00,03,00,15,EF,AA,A8, 
53,86,01,DD,57,60,00,00,03,01,59,0C,F4,3C

};


如果是第一幀數據,那麼前面還要加上 CodecPrivateData 數據。

 

byte[] = {

00,00,01,67,4D,40,15,96,53,01,00,4A,20,

00,00,01,68,E9,23,88,00,

00,00,01,65,88, 
80,40,05,B7,95,53,67,FF,84,6C,07,EB,00,F8,45,FB, 
F9,15,71,0D,A4,C5,2C,00,00,03,00,00,03,00,3F,2B, 
5B,06,57,48,29,F4,08,00,00,0A,10,02,D0,7A,FE,

00,00,01,65,01,22,22,01,00,17,B7,95,53,67,FF,84, 
6C,07,EB,00,F8,45,FB,F9,15,71,0D,A4,C5,2C,00,E8, 
F3,37,75,43,90,00,00,03,00,15,EF,AA,A8,53,86,01, 
DD,57,60,00,00,03,01,59,0C,F4,3C,

00,00,01,65, 
00,90,88,80,40,05,B7,95,53,67,FF,84,6C,07,EB,00, 
F8,45,FB,F9,15,71,0D,A4,C5,2C,00,00,03,00,00,03, 
00,3F,2B,5B,06,57,48,29,F4,08,00,00,0A,10,02,D0, 
7A,FE,

00,00,01,65,00,D8,88,80,40,05,B7,95,53, 
67,FF,84,6C,07,EB,00,F8,45,FB,F9,15,71,0D,A4,C5, 
2C,00,E8,F3,37,75,43,90,00,00,03,00,15,EF,AA,A8, 
53,86,01,DD,57,60,00,00,03,01,59,0C,F4,3C

};


關於h264的avc box中數據的提取。sps pps的獲取。

如果你想獲取視頻中的avc數據,而不要音頻的數據,你就需要錄製一段不帶音頻的視頻。

錄製完成後保存視頻,然後用WinHex打開。如果你是手機需要的視頻就錄製3gp格式。

然後CTRL+F搜索avc.可以看到如圖:


把鼠標點擊到avc附近,左邊也就在01這個數據附近了。

好了avc box的內容就是從01開始,比如上圖就是:

{ 0x01, 0x42, (byte) 0x80, 0x0A,(byte) 0xFF,

(byte) 0xE1, 0x00, 0x12, 0x67, 0x42, (byte) 0x80, 0x0A,
(byte) 0xE9, 0x02, (byte) 0xC1, 0x29, 0x08, 0x00, 0x00,
0x1F, 0x40, 0x00, 0x04, (byte) 0xE2, 0x00, 0x20, 0x01,
0x00, 0x04, 0x68, (byte) 0xCE, 0x3C, (byte) 0x80 };

sps信息就是67開頭 67-20  pps就是68-80 具體數據意義可以看上篇博客。



+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Android使用VideoView播放RTSP視頻


昨天經過編譯配置live555MediaServer.exe,然後通過VLC播放rtsp://88.88.88.200:554/1.mp3後,本以爲很簡單的就可以通過Android的VideoView播放音頻。很糾結的提示不支持。

經過一天的搜索,得出了結論。
官方文檔:Android Supported Media Formats提到

The following network protocols are supported for audio and video playback:

RTSP (RTP, SDP)
HTTP/HTTPS progressive streaming
HTTP/HTTPS live streaming draft protocol:
MPEG-2 TS media files only
Protocol version 3 (Android 4.0 and above)
Protocol version 2 (Android 3.x)
Not supported before Android 3.0

第一條說明了支持RTSP後面包含了RTP和SDP流。其他的流可能暫時不支持。

而對於網上一個代碼例子

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />
    <VideoView
        android:id="@+id/videoView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>
package com.jouhu.live;
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.widget.VideoView;
public class Live555RTSPActivity extends Activity {
    /** Called when the activity is first created. */
    VideoView videoView ;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        videoView = (VideoView)findViewById(R.id.videoView1);
    }
    private void PlayRtspStream(String rtspUrl)
    {
        videoView = (VideoView)findViewById(R.id.videoView1);
        videoView.setVideoURI(Uri.parse(rtspUrl));
        videoView.requestFocus();
        videoView.start();
    }
}

從代碼編譯結果,的確可以顯示出來。跟後面使用的SDP應該有關。

然後開始搜索如何生成SDP的問題。
使用FFmpeg可以生成相應的SDP文件,相應的命令行爲

F:\AndroidDevelop\LiveVideoSolution\FFmpeg-FFServer\ffmpeg-git-d049257-win32-static\bin>ffmpeg -fflags +genpts -re -i 1.avi -vcodec copy -an -f rtp rtp://127.0.0.1:10000 -vn -acodec copy -f rtp rtp://127.0.0.1:20000 > 2.sdp

SDP:
v=0
o=- 0 0 IN IP4 127.0.0.1
s=No Name
t=0 0
a=tool:libavformat 53.17.0
m=video 10000 RTP/AVP 96
c=IN IP4 127.0.0.1
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QAM6w07AgDHlkwEQAAAwPpAAC7gI8YMTg=,aO68sA==
m=audio 20000 RTP/AVP 14
c=IN IP4 127.0.0.1
b=AS:48

參考文章:
1 http://www.cuitu.net/content/ffmpegzai-windowsxia-bu-huo-she-xiang-tou-shi-pin
2 http://developer.android.com/guide/appendix/media-formats.html

 

目前在做視頻應用的時候,比較先進的技術就是RTSP流媒體了,那麼如果利用Android的播放控件VideoView來播放RTSP的流呢?

RTSP流媒體鏈接:
http://218.204.223.237:8081/wap/

這個鏈接含有所有的RTSP流媒體的鏈接,現在咱們就用VideoView來播放裏面的RTSP的流,咱們以其中的一個鏈接來測試下好了:

rtsp://218.204.223.237:554/live/1/66251FC11353191F/e7ooqwcfbqjoo80j.sdp.

效果截圖:

VideoView播放RTSP流

核心代碼如下:

  1. package com.video.rtsp;
  2. import android.app.Activity;
  3. import android.net.Uri;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. import android.widget.Button;
  7. import android.widget.EditText;
  8. import android.widget.VideoView;
  9. public class rtspActivity extends Activity {
  10. /** Called when the activity is first created. */
  11. Button playButton ;
  12. VideoView videoView ;
  13. EditText rtspUrl ;
  14. @Override
  15. public void onCreate(Bundle savedInstanceState) {
  16. super.onCreate(savedInstanceState);
  17. setContentView(R.layout.main);
  18. rtspUrl = (EditText)this.findViewById(R.id.url);
  19. playButton = (Button)this.findViewById(R.id.start_play);
  20. playButton.setOnClickListener(new Button.OnClickListener(){
  21. public void onClick(View v) {
  22. PlayRtspStream(rtspUrl.getEditableText().toString());
  23. }
  24. });
  25. videoView = (VideoView)this.findViewById(R.id.rtsp_player);
  26. }
  27. //play rtsp stream
  28. private void PlayRtspStream(String rtspUrl){
  29. videoView.setVideoURI(Uri.parse(rtspUrl));
  30. videoView.requestFocus();
  31. videoView.start();
  32. }
  33. }

複製代碼

在點擊開始播放後,一般要等個10幾秒中才開始播放的,直接的設置需要播放的RTSP的地址:setVideoURI(rtsp的地址)

 VideoViewRtsp.rar (23.4

非常謝謝了,這篇文章給我很大的啓示。爲什麼要使用
rtsp://218.204.223.237:554/live/1/66251FC11353191F/e7ooqwcfbqjoo80j.sdp
而不是rtsp://。。。。.mp4 爲什麼要後綴sdp呢?不能直接調用視頻文件嗎?



+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

都是摸着石頭過河,花了整整一個星期,終於把技術難點給突破了,貌似網上對這個討論的較少。 

主要需要實現的功能是在android手機上實時採集視頻,並在遠程比如PC機上實時顯示出來,也就是以android手機作爲監控攝像頭。 

一開始查到的是smartcam的一個開源項目,看了下源代碼,發現其實現原理是利用android 的camera圖像的預採集,通過實現PreviewCallback類的回調函數onPreviewFrame,獲得camera採集的原始圖像數據之後,壓成jpeg格式傳到pc端。pc端對接收到的jpeg圖像序列進行實時解壓和顯示,就達到了預想的效果。 

雖然這種方式稍微顯得比較笨拙,這個方式還可以接受。但是不可接受的是jpeg只是幀內壓縮,320x280的圖片序列,FPS大概是10上下,網絡流量就到達了100kb/s以上。這個幾乎是無法實際應用的。 

於是必須直接傳視頻流,MPEG4或者H.264格式。貌似我的開發機上(HTC G8)只支持到MPEG4,所以還是選取MPEG4。但是如何實時採集視頻流是一個大問題,畢竟在video方面,android並沒有提供一個類似於OnPreviewFrame的回調函數。 

想到用opencore或者更爲新一點的stagefright,大概看看了其sdk的框架後,馬上泄氣了,這個太龐大了。在http://blog.csdn.net/zblue78/archive/2010/12/18/6083374.aspx的帖子中提到一個很好的解決方案,就是利用MediaRecorder:MediaRecorder的輸出路徑(其實叫file descriptor)除了是本地文件路徑之外,還可以綁定socket端口。也就是說,通過一個socket端口,就可以實時獲得MediaRecorder的視頻流數據。 
(其實上面博客的內容可以在開源項目sipdroid 的 videocamera文件中找到,但是非常感謝博客主人zhangzhenj對網友提問的回答,贊一個。) 

通過socket接收的視頻流與直接寫在本地文件的視頻流數據有點不一樣,因爲是通過socket傳輸,就無法對視頻文件的回寫,通常MediaRecorder結束錄像的時候都會對視頻文件進行回寫處理,這樣纔可以被播放器播放。所以通過socket接受到的數據,保存下來是無法播放的。16進制方式查看了一下其輸出文件,發現其前32byte都是00,緊接着就是mdat。問題就出現在這了:缺少了一個ftyp box 的描述(28 bytes)以及mdat的長度描述(4 bytes).網上已經有人順利解決這樣的問題,在數據中查找moov的起始位置,發現前面會有ftyp的描述,長度剛剛好28bytes。你可以copy這28bytes到文件開始的28byte中。這ftyp的描述是從moov的起始位置 的前32byte開始一直到前4byte(後面4byte是moov的長度描述)。然後mdat的長度就是 moov的起始位置 減去 0x20,道理就不解釋了。然後把這個值寫到mdat的前面4byte。剛剛好填滿32byte,之後就能順利播放了。 

保存好的文件能播放之後,最後一個問題,如何在實時顯示這個視頻流呢?查看一下mpeg4的文件格式,很快就會知道答案,答案就在mdat中。mdat之後緊跟的就是視頻媒體數據,每一幀以 00 00 01 b6 爲開始標誌,貌似沒有結束標誌,分幀的話估計要用這個。開始標誌後緊接着的兩bit就是I、P、B幀的標誌了,分別對應值爲00,01,10,記住是兩bit不是兩byte 

好了,把mdat的一幀數據取出來,可以用ffmpeg解碼,然後顯示,這樣的路子是可行的,不過細節還是有點麻煩,關鍵是ffmpeg在解碼mpeg4的時候一定要先指定width和height,否則解碼失敗。 

發佈了6 篇原創文章 · 獲贊 12 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章