最近一直在在学习通过两个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来跟新手机上的显示的画面。

 

效果图: