原文地址:http://blog.csdn.net/sinat_35845281/article/details/52674946
最近想搞一個新奇的玩意兒~~~
最近一直在在學習通過兩個Android手機通過wifi共享攝像頭的數據。弄了好久有了點頭目。具體有下面幾個步驟:
1.對手機相機的開發,自定義surfaceView來定義自己的相機類。主要是顯示手機攝像頭的畫面。
2.對自定義相機的預覽畫面的數據的獲取。然后對數據進行解析。
3.在兩台Android手機通過wifi建立傳輸數據的連接。
4.將數據的時時的傳輸在兩個手機之間。
一.自定義相機類很簡單定義一個surfaceView,在Activity中,通過實現surfaceHodler.callBack接口重寫的OnSurfaceCreate()中添加打開相機的camera.open即可獲得一個Camera對象。設置Camera的預覽對象。 mCamera.setPreviewDisplay(holder);holder是我們surfaceView的hodler。可以通過surfaceView.getHodler()獲取。這樣就可在surfaceView中顯示我們手機的預覽的畫面。
代碼如下:
package com.weipu.camera;
import java.io.IOException; import android.annotation.TargetApi; import android.app.Activity; import android.graphics.ImageFormat; import android.hardware.Camera; import android.os.Bundle; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.View.OnClickListener; /** * 接口SurfaceHolder.Callback被用來接收攝像頭預覽界面變化的信息。它實現了三個方法: surfaceChanged * 當預覽界面的格式和大小發生改變時,該方法被調用。 surfaceCreated 初次實例化,預覽界面被創建時,該方法被調用。 * surfaceDestroyed 當預覽界面被關閉時,該方法被調用。 * * @author pengqinping */ @TargetApi(9) public class MyCameraActivity extends Activity implements SurfaceHolder.Callback, OnClickListener { private SurfaceHolder mSurfaceHolder = null; private SurfaceView mSurfaceView = null; private Camera mCamera = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // 初始化組件,SurfaceView mSurfaceView = (SurfaceView) findViewById(R.id.SurfaceView01); /** * 通過代碼,我們從surfaceview中獲得了holder,並增加callback功能到“this”。 * 這意味着我們的操作(activity)將可以管理這個surfaceview。 */ mSurfaceHolder = mSurfaceView.getHolder();// 獲取surfaceView的Holder對象 mSurfaceHolder.addCallback(this);// 為surfaceView的holder對象添加回調函數, mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } @Override protected void onStart() { super.onStart(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width1, int height1) { if (mCamera == null) { System.out.println("沒有相機設備"); return; } if (mCamera != null) { //初始化相機。設置參數 Camera.Parameters parameters = mCamera.getParameters(); parameters.setPreviewSize(width1, height1); // 設置預覽照片的大小 parameters.setPreviewFpsRange(20, 30); // 每秒顯示20~30幀 parameters.setPictureFormat(ImageFormat.NV21); // 設置圖片格式 parameters.setPictureSize(width1, height1); // 設置照片的大小 } try { mCamera.setPreviewDisplay(holder); } catch (IOException e) { System.out.println("設置預覽出錯......"); } // 通過SurfaceView顯示取景畫面 mCamera.startPreview(); // 開始預覽 mCamera.autoFocus(null); // 自動對焦 } @Override public void surfaceCreated(SurfaceHolder holder) { try { mCamera = Camera.open(); } catch (Exception e) { System.out.println("000000000"); return; } } @Override public void surfaceDestroyed(SurfaceHolder holder) { mCamera.stopPreview(); mCamera.release(); } @Override public void onClick(View v) { } }
這里在對應的Activity申明的時候需要在AndroidManifest.xml中添加
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="landscape"
例:(這樣是設置他的橫屏和橫豎屏的改變Activity做出的改變)
<activity android:name=".CameraDemophotoneActivity" android:configChanges="keyboardHidden|orientation" android:label="@string/app_name" android:screenOrientation="landscape" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
在權限方面最好是加上網絡權限:
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" />
二.手機預覽數據的獲取,手機相機的數據怎么獲取了?通過回調手機預覽函數: mCamera.setPreviewCallback(streamIt); // streamIt回調的接口的對象。 這樣我們就可以獲取相機預覽的時時的數據。 獲取的是原生態YUV格式的數據。這一步的關鍵是對數據的解析如何解碼。網上有牛人寫的我也是copy過來用的。重要的是根據自己的需要來決定使用的方式,我使用的是第二種,比較不容易出錯。前一種的效果比較差,這里代碼我就不弄出來了。(只用好的.....>>>>哈哈呵呵呵)
代碼如下:
private Camera.PreviewCallback streamIt = new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, Camera camera) { // method1:使用解碼 //method2: 使用系統自帶的類來發送圖片 Size size = camera.getParameters().getPreviewSize();//獲取大小 try { //調用image.compressToJpeg()將YUV格式圖像數據data轉為jpg格式 YuvImage image = new YuvImage(data, ImageFormat.NV21, size.width, size.height, null); //使用handler發送圖片出去 ByteArrayOutputStream outputSteam = new ByteArrayOutputStream(); image.compressToJpeg(new Rect(0, 0, size.width, size.height), 80, outputSteam); } catch (Exception ex) { Log.e("Sys", "Error:" + ex.getMessage()); } } };
三,兩個手機之間建立連接。通過WIFI局域網,不一定是wifi.只要在一個網段就可以,這步我由於設備的原因走了很多彎路。開始我是用的是模擬器來操作的,因為模擬器沒有指定Ip還必須通過端口的映射來完成,好不容易把端口映射弄好了,他兩台模擬器都用端口映射好像不太合適,因為在測試的時候模擬器自己使用的端口是同一個,問題比較多。我失敗了很多次,后來直接使用真機測試,通過借助WIFI,還的感謝我同事提供自己的設備測試。這步需要注意的是Android手機開啟一個端口的時候其中有一個堵塞的方法我們最好放在線程中來開啟,接受數據后我們不能再接線程中更新界面的數據,這是android中比較頭疼的位置。我們還需要通過線程幫助類來完成。
四.數據共享的思路。
手機數據發動端(連接指定的ip端);開啟一個線程來發送數據,因為在android中直接發送會導致主線程出現很多問題。
手機接受數據端(也就是開啟端口等待接受數據的設備)。可以一個線程來接受數據,接受到數據之后通過handler來跟新手機上的顯示的畫面。