首页 > 代码库 > 把Android手机变成远程监控摄像头

把Android手机变成远程监控摄像头

要买新手机了旧手机怎么办?我们可以废物利用下,把旧的手机变成一个远程监控摄像头。这里使用Java创建手机camera客户端和远程服务上的监控界面。

实现方法考虑几点:

  1. 创建一个Android自定义的摄像头应用

  2. 把preview的数据发送到服务端

  3. preview的NV21数据解码

  4. 把图像画出来

Android摄像头,Socket链接,服务端图像显示

创建preview回调函数:

private Camera.PreviewCallback mPreviewCallback = new PreviewCallback() {
 
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            // TODO Auto-generated method stub
            synchronized (mQueue) {
                if (mQueue.size() == MAX_BUFFER) {
                    mQueue.poll();
                }
                mQueue.add(data);
            }
        }
    };
 

注册回调函数:

try {
            mCamera.setPreviewCallback(mPreviewCallback);
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();
 
        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
 

停止释放摄像头:

public void onPause() {
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
        }
        resetBuff();
    }
 

使用AlertDialog来设置服务器IP地址:

private void setting() {
        LayoutInflater factory = LayoutInflater.from(this);
        final View textEntryView = factory.inflate(R.layout.server_setting, null);
        AlertDialog dialog =  new AlertDialog.Builder(IPCamera.this)
            .setIconAttribute(android.R.attr.alertDialogIcon)
            .setTitle(R.string.setting_title)
            .setView(textEntryView)
            .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
 
                    EditText ipEdit = (EditText)textEntryView.findViewById(R.id.ip_edit);
                    EditText portEdit = (EditText)textEntryView.findViewById(R.id.port_edit);
                    mIP = ipEdit.getText().toString();
                    mPort = Integer.parseInt(portEdit.getText().toString());
 
                    Toast.makeText(IPCamera.this, "New address: " + mIP + ":" + mPort, Toast.LENGTH_LONG).show();
                }
            })
            .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
 
                    /* User clicked cancel so do some stuff */
                }
            })
            .create();
        dialog.show();
    }
 

启动线程来创建socket链接,发送JSON数据和每一帧的图像数据:

mSocket = new Socket();
mSocket.connect(new InetSocketAddress(mIP, mPort), 10000);
BufferedOutputStream outputStream = new BufferedOutputStream(mSocket.getOutputStream());
BufferedInputStream inputStream = new BufferedInputStream(mSocket.getInputStream());
 
JsonObject jsonObj = new JsonObject();
    jsonObj.addProperty("type", "data");
    jsonObj.addProperty("length", mCameraPreview.getPreviewLength());
    jsonObj.addProperty("width", mCameraPreview.getPreviewWidth());
    jsonObj.addProperty("height", mCameraPreview.getPreviewHeight());
 
    byte[] buff = new byte[256];
    int len = 0;
    String msg = null;
    outputStream.write(jsonObj.toString().getBytes());
    outputStream.flush();
 
    while ((len = inputStream.read(buff)) != -1) {
        msg = new String(buff, 0, len);
 
        // JSON analysis
        JsonParser parser = new JsonParser();
        boolean isJSON = true;
        JsonElement element = null;
        try {
            element =  parser.parse(msg);
        }
        catch (JsonParseException e) {
            Log.e(TAG, "exception: " + e);
            isJSON = false;
        }
        if (isJSON && element != null) {
            JsonObject obj = element.getAsJsonObject();
            element = obj.get("state");
            if (element != null && element.getAsString().equals("ok")) {
                // send data
                while (true) {
                    outputStream.write(mCameraPreview.getImageBuffer());
                    outputStream.flush();
 
                    if (Thread.currentThread().isInterrupted())
                        break;
                }
 
                break;
            }
        }
        else {
            break;
        }
    }
 
    outputStream.close();
    inputStream.close();
 

服务端接收数据:

public int fillBuffer(byte[] data, int off, int len, LinkedList<byte[]> YUVQueue) {
        mTotalLength += len;
        mByteArrayOutputStream.write(data, off, len);
 
        if (mTotalLength == mFrameLength) {
 
            synchronized (YUVQueue) {
                YUVQueue.add(mByteArrayOutputStream.toByteArray());
                mByteArrayOutputStream.reset();
            }
 
            mTotalLength = 0;         
            System.out.println("received file");
        }
 
        return 0;
    }
 

NV21解码:

public static int[] convertYUVtoRGB(byte[] yuv, int width, int height)
            throws NullPointerException, IllegalArgumentException {        
        int[] out = new int[width * height];
        int sz = width * height;
 
        int i, j;
        int Y, Cr = 0, Cb = 0;
        for (j = 0; j < height; j++) {
            int pixPtr = j * width;
            final int jDiv2 = j >> 1;
            for (i = 0; i < width; i++) {
                Y = yuv[pixPtr];
                if (Y < 0)
                    Y += 255;
                if ((i & 0x1) != 1) {
                    final int cOff = sz + jDiv2 * width + (i >> 1) * 2;
                    Cb = yuv[cOff];
                    if (Cb < 0)
                        Cb += 127;
                    else
                        Cb -= 128;
                    Cr = yuv[cOff + 1];
                    if (Cr < 0)
                        Cr += 127;
                    else
                        Cr -= 128;
                }
                int R = Y + Cr + (Cr >> 2) + (Cr >> 3) + (Cr >> 5);
                if (R < 0)
                    R = 0;
                else if (R > 255)
                    R = 255;
                int G = Y - (Cb >> 2) + (Cb >> 4) + (Cb >> 5) - (Cr >> 1)
                        + (Cr >> 3) + (Cr >> 4) + (Cr >> 5);
                if (G < 0)
                    G = 0;
                else if (G > 255)
                    G = 255;
                int B = Y + Cb + (Cb >> 1) + (Cb >> 2) + (Cb >> 6);
                if (B < 0)
                    B = 0;
                else if (B > 255)
                    B = 255;
                out[pixPtr++] = 0xff000000 + (B << 16) + (G << 8) + R;
            }
        }
 
        return out;
    }
 

使用Swing绘制BufferedImage

BufferedImage bufferedImage = null;
int[] rgbArray = Utils.convertYUVtoRGB(data, mWidth, mHeight);
bufferedImage = new BufferedImage(mWidth, mHeight, BufferedImage.TYPE_USHORT_565_RGB);
bufferedImage.setRGB(0, 0, mWidth, mHeight, rgbArray, 0, mWidth);
 
 
       synchronized (mQueue) {
            if (mQueue.size() > 0) {
                mLastFrame = mQueue.poll();
            }   
        }
        if (mLastFrame != null) {
            g.drawImage(mLastFrame, 0, 0, null);
        }
        else if (mImage != null) {
            g.drawImage(mImage, 0, 0, null);
        }
    }

源码

https://github.com/DynamsoftRD/Android-IP-Camera


把Android手机变成远程监控摄像头