視頻聊天的應用可以從下面的框圖示意。
所以需要從camera獲取視頻數據(YUV420sp),壓縮成H264/MPEG4/H263的包,再傳遞到對方。接收對方的壓縮包,解壓出來顯示到LCD上。
Android里通過給camera設定 previewcallback函數可以獲取每一個Peview幀的yuv數據。
我們現在看看如何按照你想要求的預覽尺寸打開camera的並且獲取視頻數據的。
下面是打開camera的代碼片斷,他包在一個VideoCameraView類里面。
1 public class VideoCameraView extends SurfaceView implements SurfaceHolder.Callback, 2 android.hardware.Camera.PreviewCallback { 3 4 ... 5 6 7 8 private android.hardware.Camera mCamera = null ; 9 10 private double mAspectRatio = 3.0 / 3.0; 11 12 private int preview_w ; 13 private int preview_h ; 14 private int preview_yuvbytes ; 15 private byte[] bu ; 16 17 private boolean buffFilled = false ; 18 19 private boolean mRec = false ; 20 21 public void openCamera(int w , int h){ 22 23 mRec = false ; 24 25 if( surfaceHolder == null ) 26 return ; 27 mCamera = android.hardware.Camera.open() ; 28 try { 29 mCamera.setPreviewDisplay(surfaceHolder); 30 }catch(IOException e ){ 31 Log.e(TAG,"mCamera.setPreviewDisplay( " + surfaceHolder +") fail" ) ; 32 return ; 33 } 34 35 android.hardware.Camera.Parameters p = mCamera.getParameters() ; 36 37 ////得到最接近要求的尺寸 38 List<android.hardware.Camera.Size> listPreview = p.getSupportedPreviewSizes() ; 39 Log.v(TAG, "preview size is "+listPreview) ; 40 int ii = -1 ; 41 int delta = 0x7fffff ; 42 for( int i = 0 ; i < listPreview.size() ; i ++) { 43 android.hardware.Camera.Size size = listPreview.get(i) ; 44 String ws = Integer.toString(size.width); 45 String hs = Integer.toString(size.height) ; 46 Log.v(TAG, "elements "+i+":"+ws+"x"+hs) ; 47 if( java.lang.Math.abs(size.width - w ) < delta ) { 48 delta = java.lang.Math.abs(size.width - w ) ; 49 ii = i ; 50 } 51 } 52 preview_w = listPreview.get(ii).width ; 53 preview_h = listPreview.get(ii).height ; 54 preview_yuvbytes = preview_w*preview_h*3/2 ; 55 56 57 mAspectRatio = (double)preview_w / preview_h; 58 p.setPreviewSize( preview_w , preview_h ) ; 59 60 List<int[]> fpRange = p.getSupportedPreviewFpsRange() ; 61 int max = 100 ; 62 int min = 0 ; 63 for(int i = 0 ; i < fpRange.size() ; i ++ ) { 64 int[] fpr = fpRange.get(i) ; 65 Log.v(TAG, "min "+ fpr[0]+ " max " + fpr[1]) ; 66 } 67 68 mCamera.setParameters(p); 69 bu = new byte[preview_yuvbytes] ; 70 71 mCamera.setPreviewCallbackWithBuffer( this ) ; 72 73 android.hardware.Camera.CameraInfo cameraInfo = new android.hardware.Camera.CameraInfo() ; 74 mCamera.getCameraInfo( 0 , cameraInfo ) ; 75 rotateAngle = cameraInfo.orientation ; 76 Log.v(TAG,"Camera.CameraInfo.orientation="+ cameraInfo.orientation ); 77 //mCamera.setDisplayOrientation(cameraInfo.orientation) ; 78 //prepareCapture(); 79 requestLayout() ; 80 timeStart = System.currentTimeMillis() ; 81 onPreviewCalled = 0 ; 82 mCamera.startPreview(); 83 } 84 85 }
這里有幾個問題需要說明一下:
1 你傳進來的尺寸可能不是camera支持的,所以要找一個最靠近你要求的尺寸。
2 預覽的長寬比可能和你開始布局的長寬比不一致,這樣預覽到的畫面就會變形,所以需要requestLayout() ,並且要重寫onMeasure函數,如下:
1 protected void onMeasure(int widthSpec, int heightSpec) { 2 int previewWidth = MeasureSpec.getSize(widthSpec); 3 int previewHeight = MeasureSpec.getSize(heightSpec); 4 5 if (previewWidth > previewHeight * mAspectRatio) { 6 previewWidth = (int) (previewHeight * mAspectRatio + .5); 7 } else { 8 previewHeight = (int) (previewWidth / mAspectRatio + .5); 9 } 10 11 // Ask children to follow the new preview dimension. 12 super.onMeasure(MeasureSpec.makeMeasureSpec(previewWidth, MeasureSpec.EXACTLY), 13 MeasureSpec.makeMeasureSpec(previewHeight, MeasureSpec.EXACTLY)); 14 }
請注意:mAspectRatio 是我們在openCamera時計算得到的。
3 需要在應用層new一個preview_yuvbytes大小的內存通過 addCallbackBuffer 傳到android系統里去,然后使用setPreviewCallbackWithBuffer來設定回調函數。要是setPreviewCallback來設回調函數的話,那么GC會被頻繁啟動,因為回調送來的內存塊是每次都重新分配的,很容易到達需要垃圾處理的門檻,性能會大大降低。而我們采用setPreviewCallbackWithBuffer並且在openCamera時分配這塊內存,每次把這塊內存壓縮使用之后,又重新addCallbackBuffer 到系統里去,就不會大量分配內存,GC也不會啟動。請看下面的代碼片:
1 public void startRec() { 2 mRec = true ; 3 mCamera.addCallbackBuffer( bu ) ; 4 } 5 6 public void onPreviewFrame (byte[] data, android.hardware.Camera camera){ 7 if( mRec ) 8 buffFilled = true ; 9 } 10 11 12 13 public int encodeOneFrame(byte[] bitstream , int bitStreamLength){ 14 int i = 0 ; 15 while( (i++ < 10) && (buffFilled == false) ) { 16 try { 17 Thread.sleep(10) ; 18 }catch( InterruptedException e) { 19 20 } 21 } 22 if( buffFilled == false ) 23 return 0 ; 24 int nn = nativeEncodeOneFrameH264( bu , bitstream , bitStreamLength ,..... ) ; 25 buffFilled = false ; 26 mCamera.addCallbackBuffer( bu ) ; 27 return nn ; 28 }
demo鏈接:http://nchc.dl.sourceforge.net/project/avccodecdemo/avccodecDemo-src-apk.zip

