WebcamManager.java
package com.ford.openxc.webcam; import android.app.Service; import android.content.Intent; import android.graphics.Bitmap; import android.os.Binder; import android.os.IBinder; import android.util.Log; public class WebcamManager extends Service { private static String TAG = "WebcamManager"; private IBinder mBinder = new WebcamBinder(); private Webcam mWebcam; public class WebcamBinder extends Binder { public WebcamManager getService() { return WebcamManager.this; } } @Override public void onCreate() { super.onCreate(); Log.i(TAG, "Service starting"); mWebcam = new NativeWebcam("/dev/video0"); // check NativeWebcam.java } @Override public void onDestroy() { super.onDestroy(); Log.i(TAG, "Service being destroyed"); mWebcam.stop(); } @Override public IBinder onBind(Intent intent) { Log.i(TAG, "Service binding in response to " + intent); return mBinder; } public Bitmap getFrame() { if(!mWebcam.isAttached()) { stopSelf(); } return mWebcam.getFrame(); } } =================================================================== NativeWebcam.java ===================================================================
================================================================ Webcam.java ================================================================ |
| package com.ford.openxc.webcam; | |
| import android.graphics.Bitmap; | |
| public interface Webcam { | |
| public Bitmap getFrame(); | |
| public void stop(); | |
| public boolean isAttached(); | |
| } |
==========================================================
WebcamPreview.java
==========================================================
| package com.ford.openxc.webcam; | |
| import android.content.ComponentName; | |
| import android.content.Context; | |
| import android.content.Intent; | |
| import android.content.ServiceConnection; | |
| import android.graphics.Bitmap; | |
| import android.graphics.Canvas; | |
| import android.graphics.Rect; | |
| import android.os.IBinder; | |
| import android.util.AttributeSet; | |
| import android.util.Log; | |
| import android.view.SurfaceHolder; | |
| import android.view.SurfaceView; | |
| public class WebcamPreview extends SurfaceView implements | |
| SurfaceHolder.Callback, Runnable { | |
| private static String TAG = "WebcamPreview"; | |
| private Rect mViewWindow; | |
| private boolean mRunning = true; | |
| private Object mServiceSyncToken = new Object(); | |
| private WebcamManager mWebcamManager; | |
| private SurfaceHolder mHolder; | |
| public WebcamPreview(Context context) { | |
| super(context); | |
| init(); | |
| } | |
| public WebcamPreview(Context context, AttributeSet attrs) { | |
| super(context, attrs); | |
| init(); | |
| } | |
| private void init() { | |
| Log.d(TAG, "WebcamPreview constructed"); | |
| setFocusable(true); | |
| mHolder = getHolder(); | |
| mHolder.addCallback(this); | |
| } | |
| @Override | |
| public void run() { | |
| while(mRunning) { | |
| synchronized(mServiceSyncToken) { | |
| if(mWebcamManager == null) { | |
| try { | |
| mServiceSyncToken.wait(); | |
| } catch(InterruptedException e) { | |
| break; | |
| } | |
| } | |
| Bitmap bitmap = mWebcamManager.getFrame(); | |
| Canvas canvas = mHolder.lockCanvas(); | |
| if(canvas != null) { | |
| drawOnCanvas(canvas, bitmap); | |
| mHolder.unlockCanvasAndPost(canvas); | |
| } | |
| } | |
| } | |
| } | |
| protected void drawOnCanvas(Canvas canvas, Bitmap videoBitmap) { | |
| canvas.drawBitmap(videoBitmap, null, mViewWindow, null); | |
| } | |
| protected Rect getViewingWindow() { | |
| return mViewWindow; | |
| } | |
| @Override | |
| public void surfaceCreated(SurfaceHolder holder) { | |
| Log.d(TAG, "Surface created"); | |
| mRunning = true; | |
| getContext().bindService(new Intent(getContext(), WebcamManager.class), | |
| mConnection, Context.BIND_AUTO_CREATE); | |
| (new Thread(this)).start(); | |
| } | |
| @Override | |
| public void surfaceDestroyed(SurfaceHolder holder) { | |
| Log.d(TAG, "Surface destroyed"); | |
| mRunning = false; | |
| if(mWebcamManager != null) { | |
| Log.i(TAG, "Unbinding from webcam manager"); | |
| getContext().unbindService(mConnection); | |
| mWebcamManager = null; | |
| } | |
| } | |
| @Override | |
| public void surfaceChanged(SurfaceHolder holder, int format, int winWidth, | |
| int winHeight) { | |
| Log.d("WebCam", "surfaceChanged"); | |
| int width, height, dw, dh; | |
| if(winWidth * 3 / 4 <= winHeight) { | |
| dw = 0; | |
| dh = (winHeight - winWidth * 3 / 4) / 2; | |
| width = dw + winWidth - 1; | |
| height = dh + winWidth * 3 / 4 - 1; | |
| } else { | |
| dw = (winWidth - winHeight * 4 / 3) / 2; | |
| dh = 0; | |
| width = dw + winHeight * 4 / 3 - 1; | |
| height = dh + winHeight - 1; | |
| } | |
| mViewWindow = new Rect(dw, dh, width, height); | |
| } | |
| private ServiceConnection mConnection = new ServiceConnection() { | |
| public void onServiceConnected(ComponentName className, | |
| IBinder service) { | |
| Log.i(TAG, "Bound to WebcamManager"); | |
| synchronized(mServiceSyncToken) { | |
| mWebcamManager = ((WebcamManager.WebcamBinder)service).getService(); | |
| mServiceSyncToken.notify(); | |
| } | |
| } | |
| public void onServiceDisconnected(ComponentName className) { | |
| Log.w(TAG, "WebcamManager disconnected unexpectedly"); | |
| synchronized(mServiceSyncToken) { | |
| mRunning = false; | |
| mWebcamManager = null; | |
| mServiceSyncToken.notify(); | |
| } | |
| } | |
| }; | |
| } |
==============================================================
jni/video_device.c
==============================================================
| #include "video_device.h" | |
| #include <sys/stat.h> | |
| #include <sys/mman.h> | |
| #include <sys/ioctl.h> | |
| #include <linux/videodev2.h> | |
| #include "util.h" | |
| #include <errno.h> | |
| #include <string.h> | |
| #include <fcntl.h> | |
| #include <malloc.h> | |
| int open_device(const char* dev_name, int* fd) { | |
| struct stat st; | |
| if(-1 == stat(dev_name, &st)) { | |
| LOGE("Cannot identify '%s': %d, %s", dev_name, errno, strerror(errno)); | |
| return ERROR_LOCAL; | |
| } | |
| if(!S_ISCHR(st.st_mode)) { | |
| LOGE("%s is not a valid device", dev_name); | |
| return ERROR_LOCAL; | |
| } | |
| *fd = open(dev_name, O_RDWR | O_NONBLOCK, 0); | |
| if(-1 == *fd) { | |
| LOGE("Cannot open '%s': %d, %s", dev_name, errno, strerror(errno)); | |
| if(EACCES == errno) { | |
| LOGE("Insufficient permissions on '%s': %d, %s", dev_name, errno, | |
| strerror(errno)); | |
| } | |
| return ERROR_LOCAL; | |
| } | |
| return SUCCESS_LOCAL; | |
| } | |
| int init_mmap(int fd) { | |
| struct v4l2_requestbuffers req; | |
| CLEAR(req); | |
| req.count = 4; | |
| req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | |
| req.memory = V4L2_MEMORY_MMAP; | |
| if(-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { | |
| if(EINVAL == errno) { | |
| LOGE("device does not support memory mapping"); | |
| return ERROR_LOCAL; | |
| } else { | |
| return errnoexit("VIDIOC_REQBUFS"); | |
| } | |
| } | |
| if(req.count < 2) { | |
| LOGE("Insufficient buffer memory"); | |
| return ERROR_LOCAL; | |
| } | |
| FRAME_BUFFERS = calloc(req.count, sizeof(*FRAME_BUFFERS)); | |
| if(!FRAME_BUFFERS) { | |
| LOGE("Out of memory"); | |
| return ERROR_LOCAL; | |
| } | |
| for(BUFFER_COUNT = 0; BUFFER_COUNT < req.count; ++BUFFER_COUNT) { | |
| struct v4l2_buffer buf; | |
| CLEAR(buf); | |
| buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | |
| buf.memory = V4L2_MEMORY_MMAP; | |
| buf.index = BUFFER_COUNT; | |
| if(-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf)) { | |
| return errnoexit("VIDIOC_QUERYBUF"); | |
| } | |
| FRAME_BUFFERS[BUFFER_COUNT].length = buf.length; | |
| FRAME_BUFFERS[BUFFER_COUNT].start = mmap(NULL, buf.length, | |
| PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); | |
| if(MAP_FAILED == FRAME_BUFFERS[BUFFER_COUNT].start) { | |
| return errnoexit("mmap"); | |
| } | |
| } | |
| return SUCCESS_LOCAL; | |
| } | |
| int init_device(int fd, int width, int height) { | |
| struct v4l2_capability cap; | |
| struct v4l2_cropcap cropcap; | |
| struct v4l2_crop crop; | |
| struct v4l2_format fmt; | |
| unsigned int min; | |
| if(-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) { | |
| if(EINVAL == errno) { | |
| LOGE("not a valid V4L2 device"); | |
| return ERROR_LOCAL; | |
| } else { | |
| return errnoexit("VIDIOC_QUERYCAP"); | |
| } | |
| } | |
| if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { | |
| LOGE("device is not a video capture device"); | |
| return ERROR_LOCAL; | |
| } | |
| if(!(cap.capabilities & V4L2_CAP_STREAMING)) { | |
| LOGE("device does not support streaming i/o"); | |
| return ERROR_LOCAL; | |
| } | |
| CLEAR(cropcap); | |
| cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | |
| if(0 == xioctl(fd, VIDIOC_CROPCAP, &cropcap)) { | |
| crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | |
| crop.c = cropcap.defrect; | |
| if(-1 == xioctl(fd, VIDIOC_S_CROP, &crop)) { | |
| switch(errno) { | |
| case EINVAL: | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| CLEAR(fmt); | |
| fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | |
| fmt.fmt.pix.width = width; | |
| fmt.fmt.pix.height = height; | |
| fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; | |
| fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; | |
| if(-1 == xioctl(fd, VIDIOC_S_FMT, &fmt)) { | |
| return errnoexit("VIDIOC_S_FMT"); | |
| } | |
| min = fmt.fmt.pix.width * 2; | |
| if(fmt.fmt.pix.bytesperline < min) { | |
| fmt.fmt.pix.bytesperline = min; | |
| } | |
| min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; | |
| if(fmt.fmt.pix.sizeimage < min) { | |
| fmt.fmt.pix.sizeimage = min; | |
| } | |
| return init_mmap(fd); | |
| } | |
| int uninit_device() { | |
| for(unsigned int i = 0; i < BUFFER_COUNT; ++i) { | |
| if(-1 == munmap(FRAME_BUFFERS[i].start, FRAME_BUFFERS[i].length)) { | |
| return errnoexit("munmap"); | |
| } | |
| } | |
| free(FRAME_BUFFERS); | |
| return SUCCESS_LOCAL; | |
| } | |
| int close_device(int* fd) { | |
| int result = SUCCESS_LOCAL; | |
| if(-1 != *fd && -1 == close(*fd)) { | |
| result = errnoexit("close"); | |
| } | |
| *fd = -1; | |
| return result; | |
| } |
Ref : https://github.com/openxc/android-webcam
沒有留言:
張貼留言