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

jopen 5年前

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

12103551_7vet.png

12102844_vqht.png

实现方法考虑几点:

  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

原文地址:http://www.codepool.biz/tech-frontier/android/making-android-smart-phone-a-remote-ip-camera.html