📄 msv4l.c
字号:
/*mediastreamer2 library - modular sound and video processing and streamingCopyright (C) 2006 Simon MORLAT (simon.morlat@linphone.org)This program is free software; you can redistribute it and/ormodify it under the terms of the GNU General Public Licenseas published by the Free Software Foundation; either version 2of the License, or (at your option) any later version.This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See theGNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with this program; if not, write to the Free SoftwareFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.*/#ifdef __linux#include "mediastreamer-config.h"#include <fcntl.h>#include <unistd.h>#include <sys/stat.h>#include <sys/types.h>#include <sys/ioctl.h>#include <errno.h>#include <string.h>#include <sys/mman.h>#include <linux/videodev.h>#ifdef HAVE_LINUX_VIDEODEV2_H#include <linux/videodev2.h>#endif#include "mediastreamer2/msvideo.h"#include "mediastreamer2/msticker.h"#include "mediastreamer2/msv4l.h"#include "mediastreamer2/mswebcam.h"#include "nowebcam.h"/* From: Logitech QuickCam USB driver */#define QC_IOCTLBASE 220/* Get enable workaround for bugs, bitfield */#define VIDIOCQCGCOMPATIBLE _IOR ('v',QC_IOCTLBASE+10,int)/* Set enable workaround for bugs, bitfield */#define VIDIOCQCSCOMPATIBLE _IOWR('v',QC_IOCTLBASE+10,int)#ifndef VIDIOSFPS#define VIDIOSFPS _IOW('v',BASE_VIDIOCPRIVATE+20, int)#endiftypedef struct V4lState{ int fd; ms_thread_t thread; char *dev; char *mmapdbuf; int msize;/*mmapped size*/ MSVideoSize vsize; MSVideoSize got_vsize; int pix_fmt; int int_pix_fmt; /*internal pixel format */ mblk_t *frames[VIDEO_MAX_FRAME]; mblk_t *mire; queue_t rq; ms_mutex_t mutex; int frame_ind; int frame_max; float fps; float start_time; int frame_count; int queued; bool_t run; bool_t usemire; bool_t v4lv2; /*we interface with a V4Lv2 driver */ bool_t force_v1; bool_t auto_started;}V4lState;static void *v4l_thread(void *s);static int v4l_configure(V4lState *s);#ifdef HAVE_LINUX_VIDEODEV2_Hstatic bool_t v4lv2_try_format(V4lState *s, int fmtid){ struct v4l2_format fmt; memset(&fmt,0,sizeof(fmt)); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = s->vsize.width; fmt.fmt.pix.height = s->vsize.height; fmt.fmt.pix.pixelformat = fmtid; fmt.fmt.pix.field = V4L2_FIELD_ANY; if (ioctl (s->fd, VIDIOC_S_FMT, &fmt)<0){ return FALSE; } s->got_vsize.width=s->vsize.width; s->got_vsize.height=s->vsize.height; return TRUE;}static int v4lv2_configure(V4lState *s){ struct v4l2_capability cap; if (ioctl (s->fd, VIDIOC_QUERYCAP, &cap)<0) { ms_message("Not a v4lv2 driver."); return -1; } if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { ms_error("%s is not a video capture device\n",s->dev); return -1; } if (!(cap.capabilities & V4L2_CAP_STREAMING)) { ms_error("%s does not support streaming i/o\n",s->dev); return -1; } if (v4lv2_try_format(s,V4L2_PIX_FMT_YUV420)){ s->pix_fmt=MS_YUV420P; s->int_pix_fmt=V4L2_PIX_FMT_YUV420; ms_message("v4lv2: YUV420P choosen"); }else if (v4lv2_try_format(s,V4L2_PIX_FMT_NV12)){ s->pix_fmt=MS_YUV420P; s->int_pix_fmt=V4L2_PIX_FMT_NV12; ms_message("v4lv2: V4L2_PIX_FMT_NV12 choosen"); }else if (v4lv2_try_format(s,V4L2_PIX_FMT_MJPEG)){ s->pix_fmt=MS_MJPEG; s->int_pix_fmt=V4L2_PIX_FMT_MJPEG; ms_message("v4lv2: MJPEG choosen"); }else if (v4lv2_try_format(s,V4L2_PIX_FMT_YUYV)){ s->pix_fmt=MS_YUYV; s->int_pix_fmt=V4L2_PIX_FMT_YUYV; ms_message("v4lv2: V4L2_PIX_FMT_YUYV choosen"); }else if (v4lv2_try_format(s,V4L2_PIX_FMT_RGB24)){ s->pix_fmt=MS_RGB24; s->int_pix_fmt=V4L2_PIX_FMT_RGB24; ms_message("v4lv2: RGB24 choosen"); }else{ ms_error("Could not find supported pixel format."); return -1; } return 0;}static int v4lv2_do_mmap(V4lState *s){ struct v4l2_requestbuffers req; int i; enum v4l2_buf_type type; memset(&req,0,sizeof(req)); req.count = 4; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; if (ioctl (s->fd, VIDIOC_REQBUFS, &req)<0) { ms_error("Error requesting info on mmap'd buffers: %s",strerror(errno)); return -1; } for (i=0; i<req.count; ++i) { struct v4l2_buffer buf; mblk_t *msg; void *start; memset(&buf,0,sizeof(buf)); buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory=V4L2_MEMORY_MMAP; buf.index=i; if (ioctl (s->fd, VIDIOC_QUERYBUF, &buf)<0){ ms_error("Could not VIDIOC_QUERYBUF : %s",strerror(errno)); return -1; } start=mmap (NULL /* start anywhere */, buf.length, PROT_READ | PROT_WRITE /* required */, MAP_SHARED /* recommended */, s->fd, buf.m.offset); if (start==NULL){ ms_error("Could not mmap: %s",strerror(errno)); } msg=esballoc(start,buf.length,0,NULL); /* adjust to real size of picture*/ if (s->pix_fmt==MS_RGB24) msg->b_wptr+=s->vsize.width*s->vsize.height*3; else msg->b_wptr+=(s->vsize.width*s->vsize.height*3)/2; s->frames[i]=msg; } s->frame_max=req.count; /* for (i = 0; i < s->frame_max; ++i) { struct v4l2_buffer buf; memset(&buf,0,sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; if (-1==ioctl (s->fd, VIDIOC_QBUF, &buf)){ ms_error("VIDIOC_QBUF failed: %s",strerror(errno)); } } */ /*start capture immediately*/ type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 ==ioctl (s->fd, VIDIOC_STREAMON, &type)){ ms_error("VIDIOC_STREAMON failed: %s",strerror(errno)); return -1; } return 0;}static mblk_t * v4lv2_grab_image(V4lState *s){ struct v4l2_buffer buf; unsigned int k; memset(&buf,0,sizeof(buf)); mblk_t *ret=NULL; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (s->queued){ if (ioctl(s->fd, VIDIOC_DQBUF, &buf)<0) { switch (errno) { case EAGAIN: case EIO: /* Could ignore EIO, see spec. */ break; default: ms_warning("VIDIOC_DQBUF failed: %s",strerror(errno)); } }else{ if (buf.index >= s->frame_max){ ms_error("buf.index>=s->max_frames !"); return NULL; } s->queued--; /*decrement ref count of dequeued buffer */ ret=s->frames[buf.index]; ret->b_datap->db_ref--; if (buf.bytesused<=30){ ms_warning("Ignoring empty buffer..."); return NULL; } } } /*queue buffers whose ref count has dropped to 1, because they are not still used anywhere in the filter chain */ for(k=0;k<s->frame_max;++k){ if (s->frames[k]->b_datap->db_ref==1){ buf.index=k; if (-1==ioctl (s->fd, VIDIOC_QBUF, &buf)) ms_warning("VIDIOC_QBUF %i failed: %s",k, strerror(errno)); else { /*increment ref count of queued buffer*/ s->frames[k]->b_datap->db_ref++; s->queued++; } } } return ret;}#endifstatic void v4l_init(MSFilter *f){ V4lState *s=ms_new0(V4lState,1); s->fd=-1; s->run=FALSE; s->v4lv2=FALSE; s->mmapdbuf=NULL; s->vsize.width=MS_VIDEO_SIZE_CIF_W; s->vsize.height=MS_VIDEO_SIZE_CIF_H; s->pix_fmt=MS_RGB24; s->dev=ms_strdup("/dev/video0"); qinit(&s->rq); s->mire=NULL; ms_mutex_init(&s->mutex,NULL); s->start_time=0; s->frame_count=-1; s->fps=15; s->usemire=(getenv("DEBUG")!=NULL); s->queued=0; s->force_v1=FALSE; s->auto_started=FALSE; f->data=s;}/* we try not to close the /dev/videoX device to workaround a bug of linux kernel.The bug is this one:One thread opens /dev/videoX, and mmap()s some pages to get data from the camera.Then several threads are created (using clone) and automatically get a reference to the mmap'd page, thus a reference to the file_struct of the opened file descriptor.Then when the first thread closes and unmap() the file descriptor, the .release method of the driver is not called because there are still some threads owning the reference to the mmap'd pages.As far as I understand.If all threads are correctly pthread_join()ed, then the file descriptor is closed correctly.But unfortunately when using alsa dmix/asym plugins, some threads are started by those plugins and are kept alive (or zombie) for some time after the mmap()/close().*/static int v4l_fd=-1;static bool_t reuse_fd=FALSE;static char *v4l_devname=NULL;static int v4l_start(MSFilter *f, void *arg){ V4lState *s=(V4lState*)f->data; int err=0; if (v4l_fd>=0 && reuse_fd){ if (strcmp(v4l_devname,s->dev)==0 ){ /*use this one!*/ ms_message("v4l_start: reusing previous file descriptor."); s->fd=v4l_fd; }else{ ms_message("closing cached fd"); close(v4l_fd); v4l_fd=-1; ms_free(v4l_devname); v4l_devname=NULL; } } if (s->fd==-1){ s->fd=open(s->dev,O_RDWR); ms_message("v4l_start: open, fd=%i",s->fd); if (s->fd>=0){ v4l_fd=s->fd; v4l_devname=ms_strdup(s->dev); } } if (s->fd<0){ ms_error("MSV4l: cannot open video device (%s): %s.",s->dev,strerror(errno)); if (!s->usemire){ s->pix_fmt=MS_YUV420P; s->fps=1; } return -1; }else{#ifdef HAVE_LINUX_VIDEODEV2_H if (s->force_v1 || v4lv2_configure(s)<0) {/*might not be V4LV2 */#else if (1){#endif struct video_capability vidcap; err=v4l_configure(s); if (err<0) { ms_error("MSV4l: could not get configuration of video device"); close(s->fd); s->fd=-1; return -1; } if (!s->force_v1) reuse_fd=TRUE; err=ioctl(s->fd, VIDIOCGCAP, &vidcap); if (err==0) { ms_message("MSV4l: Webcam is %s.", vidcap.name); if (strcasecmp(vidcap.name, "Logitech QuickCam USB")==0) { int comp_arg=0; err=ioctl(s->fd, VIDIOCQCSCOMPATIBLE, &comp_arg); if (err==0) { ms_message("MSV4l: compatibility mode disabled for %s.", vidcap.name); } } } }else{ ms_message("Device is a video4linux V2 one."); s->v4lv2=TRUE; reuse_fd=FALSE; } } return 0;}static void v4l_start_capture(V4lState *s){ if (s->fd>=0){ s->run=TRUE; ms_thread_create(&s->thread,NULL,v4l_thread,s); }}static int v4l_stop(MSFilter *f, void *arg){ V4lState *s=(V4lState*)f->data; if (s->fd>=0){ if (!reuse_fd){ if (close(s->fd)<0){ ms_warning("MSV4l: Could not close(): %s",strerror(errno)); } ms_message("v4l fd %i closed",s->fd); } s->fd=-1; s->frame_count=-1; } return 0;}static void v4l_stop_capture(V4lState *s){ if (s->run){ s->run=FALSE; ms_thread_join(s->thread,NULL); ms_message("v4l thread has joined."); flushq(&s->rq,0); }}static void v4l_uninit(MSFilter *f){ V4lState *s=(V4lState*)f->data; if (s->fd>=0) v4l_stop(f,NULL); ms_free(s->dev); flushq(&s->rq,0); ms_mutex_destroy(&s->mutex); freemsg(s->mire); ms_free(s);}static bool_t try_format(int fd, struct video_picture *pict, int palette, int depth){ int err; pict->palette=palette; pict->depth=depth; err=ioctl(fd,VIDIOCSPICT,pict); if (err<0){ ms_warning("Could not set picture properties: %s",strerror(errno)); return FALSE; } return TRUE;}static int v4l_do_mmap(V4lState *s){ struct video_mbuf vmbuf; int err,i; memset(&vmbuf,0,sizeof(vmbuf)); /* try to get mmap properties */ err=ioctl(s->fd,VIDIOCGMBUF,&vmbuf); if (err<0){ ms_error("Could not get mmap properties: %s",strerror(errno)); return -1; }else { if (vmbuf.size>0){ /* do the mmap */ s->msize=vmbuf.size; s->frame_max=vmbuf.frames; } else { ms_error("This device cannot support mmap."); return -1; } } s->mmapdbuf=mmap(NULL,s->msize,PROT_READ,MAP_SHARED,s->fd,0); if (s->mmapdbuf==(void*)-1) { /* for non-mmu arch */ s->mmapdbuf=mmap(NULL,s->msize,PROT_READ,MAP_PRIVATE,s->fd,0); if (s->mmapdbuf==(void*)-1) { ms_error("Could not mmap: %s",strerror(errno)); s->mmapdbuf=NULL; return -1; } } /* initialize the mediastreamer buffers */ ms_message("Using %i-frames mmap'd buffer at %p, len %i", s->frame_max, s->mmapdbuf,s->msize); for(i=0;i<s->frame_max;i++){ mblk_t *buf=esballoc((uint8_t*)s->mmapdbuf+vmbuf.offsets[i],vmbuf.offsets[1],0,NULL); /* adjust to real size of picture*/ if (s->pix_fmt==MS_RGB24) buf->b_wptr+=s->vsize.width*s->vsize.height*3; else buf->b_wptr+=(s->vsize.width*s->vsize.height*3)/2; s->frames[i]=buf; } s->frame_ind=0; return 0;}static bool_t try_size(V4lState *s, MSVideoSize vsize){ struct video_window win; int err; memset(&win,0,sizeof(win)); /*set picture size */ win.x=win.y=0; win.width=vsize.width; win.height=vsize.height; win.flags=0; win.clips=NULL; win.clipcount=0; ms_message("Trying to set capture size to %ix%i", vsize.width,vsize.height); err=ioctl(s->fd,VIDIOCSWIN,&win); if (err<0){ ms_warning("Could not set window size: %s",strerror(errno)); return FALSE; } err=ioctl(s->fd, VIDIOCGWIN, &win); if (err<0){ ms_warning("Could not get window size: %s",strerror(errno)); return FALSE; } s->vsize.width=vsize.width; s->vsize.height=vsize.height; if (s->vsize.width!=win.width || s->vsize.height!=win.height){ ms_warning("Capture size is not what we expected: asked for %ix%i and get %ix%i",s->vsize.width,s->vsize.height, win.width, win.height); } s->got_vsize.width=win.width; s->got_vsize.height=win.height; ms_message("Capture size set to %ix%i", s->got_vsize.width,s->got_vsize.height); return TRUE;}static int v4l_configure(V4lState *s){ struct video_channel chan; struct video_picture pict; struct video_capability cap; int err; int i; int fps = 0; int found=0; memset(&chan,0,sizeof(chan)); memset(&pict,0,sizeof(pict)); memset(&cap,0,sizeof(cap)); err=ioctl(s->fd,VIDIOCGCAP,&cap); if (err!=0)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -