最近在鼓捣一个基于socket的项目,功能比较简单,服务端提供页面和socket服务,
客户端(Android)连接后实时传输摄像头数据,可在浏览器查看,在此记录下,备忘!

  • Client代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    public class Client {
    public static void connectSocketServer(){
    new Thread(new Runnable() {
    @Override
    public void run() {
    Map<String,Object> map=netty.getConnectInfo();
    String host = map.get("host").toString();
    int port = Integer.parseInt(map.get("port").toString());
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    try {
    Bootstrap bootstrap = new Bootstrap();
    bootstrap.group(workerGroup);
    bootstrap.channel(NioSocketChannel.class);
    bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
    bootstrap.handler(new ChannelInitializer<SocketChannel>() {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
    ByteBuf delimiter = Unpooled.copiedBuffer("rumeng".getBytes());
    ch.pipeline().addLast("framer", new DelimiterBasedFrameDecoder(Integer.MAX_VALUE, delimiter));
    ch.pipeline().addLast(new ClientHandler());
    }
    });
    // 发起异步连接操作
    netty.setChannelFuture(bootstrap.connect(host, port).sync());
    // ChannelFuture future = bootstrap.connect(host, port).sync();
    netty.getChannelFuture().channel().closeFuture().sync();
    } catch (Exception e) {
    workerGroup.shutdownGracefully();
    }
    }
    }).start();
    }
    }
  • ClientHandler:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    public class ClientHandler extends ChannelInboundHandlerAdapter {
    private final ByteBuf firstMessage;

    public ClientHandler() {
    byte[] req = "query time orderrumeng".getBytes();
    firstMessage = Unpooled.buffer(req.length);
    firstMessage.writeBytes(req);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    netty.setSocketChannel((SocketChannel) ctx.channel());
    //启动监听
    CheckDog();
    // ctx.writeAndFlush(firstMessage);// 将请求消息发送给服务端
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
    throws Exception {
    ctx.close();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
    throws Exception {
    ByteBuf buf = (ByteBuf) msg;
    byte[] req = new byte[buf.readableBytes()];
    buf.readBytes(req);
    String body = new String(req, "UTF-8");

    }

    /**
    * 异常断开重连
    */
    public void CheckDog() {
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
    public void run() {
    if(!netty.getChannelFuture().isSuccess() && netty.getIsStart()){
    //重新连接
    Client.connectSocketServer();
    }
    }
    }, 1, 5, TimeUnit.SECONDS);
    }
    }
  • netty:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    public class netty {
    private static SocketChannel socketChannel=null;
    public static void setSocketChannel(SocketChannel cxy){
    socketChannel=cxy;
    }
    public static SocketChannel getSocketChannel(){
    return socketChannel;
    }
    private static ChannelFuture channelFuture=null;
    public static void setChannelFuture(ChannelFuture cf){
    channelFuture=cf;
    }
    public static ChannelFuture getChannelFuture(){
    return channelFuture;
    }
    //用于判断是否开启预览,断线重连
    private static boolean isStart=false;
    public static void setIsStart(boolean of){
    isStart=of;
    }
    public static boolean getIsStart(){
    return isStart;
    }
    //连接信息
    private static Map<String,Object> hp=new HashMap<String,Object>();
    public static void setConnectInfo(String host,int port){
    hp.put("host",host);
    hp.put("port",port);
    }
    public static Map<String,Object> getConnectInfo(){
    return hp;
    }


    }
  • MainActivity:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    public class MainActivity extends AppCompatActivity {
    private EditText portView;
    private EditText hostView;
    private EditText sendStr;
    SurfaceView surfaceView;
    SurfaceHolder sfh;
    Camera camera;
    boolean isPreview = false; //是否在浏览中
    int screenWidth = 300, screenHeight = 300;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    hostView = (EditText) this.findViewById(R.id.host);
    portView = (EditText) this.findViewById(R.id.port);
    sendStr = (EditText) this.findViewById(R.id.sendStr);
    surfaceView = (SurfaceView) this.findViewById(R.id.surfaceView);
    DisplayMetrics dm = new DisplayMetrics();
    getWindowManager().getDefaultDisplay().getMetrics(dm);
    // screenWidth = dm.widthPixels;// 获取屏幕分辨率宽度
    // screenHeight = dm.heightPixels;
    screenWidth = 480;
    screenHeight = 600;
    sfh = surfaceView.getHolder();
    sfh.setFixedSize(screenWidth, screenHeight / 2);

    sfh.addCallback(new SurfaceHolder.Callback() {

    @Override
    public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2,
    int arg3) {
    // TODO Auto-generated method stub

    }

    @Override
    public void surfaceCreated(SurfaceHolder arg0) {
    // TODO Auto-generated method stub
    initCamera();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder arg0) {
    if (camera != null) {
    if (isPreview)
    camera.stopPreview();
    camera.release();
    camera = null;
    }
    try {
    netty.setIsStart(false);
    netty.getSocketChannel().close().sync();
    netty.setSocketChannel(null);
    }catch (Exception e){
    Toast.makeText(MainActivity.this, "关闭socket失败", Toast.LENGTH_SHORT).show();
    }

    }

    });
    }

    public void onClick(View v) {
    int id = v.getId();
    switch (id) {
    case R.id.connect:
    try {
    netty.setConnectInfo(hostView.getText().toString(),Integer.parseInt(portView.getText().toString()));
    }catch (Exception e){
    Toast.makeText(this, "请检测IP地址和端口", Toast.LENGTH_SHORT).show();
    }
    Client.connectSocketServer();
    netty.setIsStart(true);
    break;
    case R.id.off:
    try {
    netty.setIsStart(false);
    netty.getSocketChannel().close().sync();
    netty.setSocketChannel(null);
    } catch (Exception e) {
    Toast.makeText(this, "关闭异常", Toast.LENGTH_SHORT).show();
    }
    break;
    case R.id.sendBtn:
    Log.i("发送消息", sendStr.getText().toString());
    // netty.getSocketChannel().writeAndFlush(util.getStringByteBuf(sendStr.getText().toString()+"rumeng"));
    break;
    default:
    Log.i("isNull", "------------" + netty.getSocketChannel());
    }
    }

    @SuppressWarnings("deprecation")
    private void initCamera() {
    if (!isPreview) {
    int k = 0;
    if ((k = FindBackCamera()) != -1) {
    camera = Camera.open(k);
    } else {
    camera = Camera.open();
    }
    }
    if (camera != null && !isPreview) {
    try {
    camera.setPreviewDisplay(sfh); // 通过SurfaceView显示取景画面
    Camera.Parameters parameters = camera.getParameters();
    /* 每秒从摄像头捕获5帧画面, */
    parameters.setPreviewFrameRate(3);
    parameters.setPictureFormat(ImageFormat.NV21); // 设置图片格式
    parameters.setPictureSize(screenWidth, screenHeight / 4 * 3); // 设置照片的大小
    camera.setDisplayOrientation(90);
    camera.setPreviewCallback(new Camera.PreviewCallback() {

    @Override
    public void onPreviewFrame(byte[] data, Camera c) {
    // TODO Auto-generated method stub
    Camera.Size size = camera.getParameters().getPreviewSize();
    try {
    //调用image.compressToJpeg()将YUV格式图像数据data转为jpg格式
    YuvImage image = new YuvImage(data, ImageFormat.NV21, size.width, size.height, null);
    if (image != null) {
    ByteArrayOutputStream os = new ByteArrayOutputStream(data.length);
    image.compressToJpeg(new Rect(0, 0, size.width, size.height), 80, os);
    if (netty.getSocketChannel() != null && netty.getSocketChannel().isOpen()) {
    sendMsg(os);
    }
    }
    } catch (Exception ex) {
    Log.e("Sys", "Error:" + ex.getMessage());
    }
    }

    });
    camera.startPreview(); // 开始预览
    camera.autoFocus(null); // 自动对焦
    } catch (Exception e) {
    e.printStackTrace();
    }
    isPreview = true;
    }
    }

    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

    public void sendMsg(final ByteArrayOutputStream byteArrayOutputStream) {
    singleThreadExecutor.execute(new Runnable() {
    public void run() {
    try {
    byte[] tmp = byteArrayOutputStream.toByteArray();
    // String msg=util.bytesToHexString(tmp);
    if (netty.getSocketChannel() != null) {
    netty.getSocketChannel().writeAndFlush(util.getBytesByteBuf(util.byteMerger(tmp, "rumeng".getBytes())));
    }
    Thread.sleep(10);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    });
    }

    //调用前置摄像头
    private int FindFrontCamera() {
    int cameraCount = 0;
    Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
    cameraCount = Camera.getNumberOfCameras(); // get cameras number

    for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
    Camera.getCameraInfo(camIdx, cameraInfo); // get camerainfo
    if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
    // 代表摄像头的方位,目前有定义值两个分别为CAMERA_FACING_FRONT前置和CAMERA_FACING_BACK后置
    return camIdx;
    }
    }
    return -1;
    }

    //调用后置摄像头
    private int FindBackCamera() {
    int cameraCount = 0;
    Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
    cameraCount = Camera.getNumberOfCameras(); // get cameras number

    for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
    Camera.getCameraInfo(camIdx, cameraInfo); // get camerainfo
    if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
    // 代表摄像头的方位,目前有定义值两个分别为CAMERA_FACING_FRONT前置和CAMERA_FACING_BACK后置
    return camIdx;
    }
    }
    return -1;
    }
    }
  • 对应view:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="gq.rumeng.androidremotelycameraapp.MainActivity"
    android:weightSum="1">
    <SurfaceView
    android:id="@+id/surfaceView"
    android:layout_width="match_parent"
    android:layout_height="353dp" />
    <LinearLayout
    android:layout_width="match_parent"
    android:orientation="horizontal"
    android:layout_height="wrap_content">
    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:text="@string/main_ip"
    android:layout_weight="2"/>
    <EditText
    android:id="@+id/host"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_weight="1"/>
    <EditText
    android:id="@+id/port"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_weight="2"/>
    </LinearLayout>
    <LinearLayout
    android:layout_width="match_parent"
    android:orientation="horizontal"
    android:layout_height="wrap_content">
    <TextView
    android:text="@string/main_sendStr"
    android:gravity="center"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_weight="4"/>
    <EditText
    android:id="@+id/sendStr"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_weight="1"/>
    </LinearLayout>
    <LinearLayout
    android:layout_width="match_parent"
    android:orientation="horizontal"
    android:gravity="center"
    android:layout_height="wrap_content">
    <Button
    android:id="@+id/connect"
    android:layout_width="wrap_content"
    android:text="@string/main_startScoket"
    android:onClick="onClick"
    android:layout_height="wrap_content" />
    <Button
    android:id="@+id/off"
    android:layout_width="wrap_content"
    android:text="@string/main_endScoket"
    android:onClick="onClick"
    android:layout_height="wrap_content" />
    <Button
    android:id="@+id/sendBtn"
    android:text="@string/main_send_bth"
    android:onClick="onClick"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
    </LinearLayout>

    </LinearLayout>
  • 所用util:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    public class util {
    /**
    * 字节数组转十六进制字符串
    * @param bArray
    * @return
    */
    public static final String bytesToHexString(byte[] bArray) {
    StringBuffer sb = new StringBuffer(bArray.length);
    String sTemp;
    for (int i = 0; i < bArray.length; i++) {
    sTemp = Integer.toHexString(0xFF & bArray[i]);
    if (sTemp.length() < 2)
    sb.append(0);
    sb.append(sTemp.toUpperCase());
    }
    return sb.toString();
    }
    public static byte[] byteMerger(byte[] byte_1, byte[] byte_2){
    byte[] byte_3 = new byte[byte_1.length+byte_2.length];
    System.arraycopy(byte_1, 0, byte_3, 0, byte_1.length);
    System.arraycopy(byte_2, 0, byte_3, byte_1.length, byte_2.length);
    return byte_3;
    }
    public static ByteBuf getStringByteBuf(String str){
    byte[] bytes=str.getBytes();
    ByteBuf byteBuf= Unpooled.buffer(bytes.length);
    byteBuf.writeBytes(bytes);
    return byteBuf;
    }

    public static ByteBuf getBytesByteBuf(byte[] bytes){
    ByteBuf byteBuf= Unpooled.buffer(bytes.length);
    byteBuf.writeBytes(bytes);
    return byteBuf;
    }
    }

页面效果就不放了,就是手机连接后,浏览器打开可以将手机当做一个摄像头