/* * Pure Data Packet module. * Copyright (c) by Tom Schouten * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of 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 of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include "pdp.h" #include "pdp_llconv.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // dont open any more after a set number // of failed attempts // this is to prevent locks on auto-open // is reset when manually opened or closed #define PDP_XV_RETRIES 10 //uncomment this for additional philips webcam control //#define HAVE_V4LPWC #ifdef HAVE_V4LPWC #include "pwc-ioctl.h" #endif #define DEVICENO 0 #define NBUF 2 #define COMPOSITEIN 1 typedef struct pdp_v4l_struct { t_object x_obj; t_float x_f; t_outlet *x_outlet0; bool x_initialized; bool x_auto_open; unsigned int x_width; unsigned int x_height; int x_channel; unsigned int x_norm; int x_freq; unsigned int x_framerate; struct video_tuner x_vtuner; struct video_picture x_vpicture; struct video_buffer x_vbuffer; struct video_capability x_vcap; struct video_channel x_vchannel; struct video_audio x_vaudio; struct video_mbuf x_vmbuf; struct video_mmap x_vmmap[NBUF]; struct video_window x_vwin; int x_tvfd; int x_frame; unsigned char *x_videobuf; int x_skipnext; int x_mytopmargin, x_mybottommargin; int x_myleftmargin, x_myrightmargin; t_symbol *x_device; t_symbol *x_image_type; //int x_pdp_image_type; int x_v4l_palette; pthread_t x_thread_id; int x_continue_thread; int x_frame_ready; int x_only_new_frames; int x_last_frame; int x_open_retry; } t_pdp_v4l; static void pdp_v4l_close(t_pdp_v4l *x) { /* close the v4l device and dealloc buffer */ void *dummy; /* terminate thread if there is one */ if(x->x_continue_thread){ x->x_continue_thread = 0; pthread_join (x->x_thread_id, &dummy); } if (x->x_tvfd >= 0) { close(x->x_tvfd); x->x_tvfd = -1; } if (x->x_initialized){ munmap(x->x_videobuf, x->x_vmbuf.size); x->x_initialized = false; } } static void pdp_v4l_close_manual(t_pdp_v4l *x) { x->x_open_retry = PDP_XV_RETRIES; pdp_v4l_close(x); } static void pdp_v4l_close_error(t_pdp_v4l *x) { pdp_v4l_close(x); if(x->x_open_retry) x->x_open_retry--; } static void pdp_v4l_pwc_init(t_pdp_v4l *x) { /* todo add detection code for pwc */ #ifdef HAVE_V4LPWC if(ioctl(x->x_tvfd, VIDIOCPWCRUSER)){ perror("pdp_v4l: pwc: VIDIOCPWCRUSER"); goto closit; } if (ioctl(x->x_tvfd, VIDIOCGWIN, &x->x_vwin)){ perror("pdp_v4l: pwc: VIDIOCGWIN"); goto closit; } if (x->x_vwin.flags & PWC_FPS_MASK){ post("pdp_v4l: pwc: framerate: %d", (x->x_vwin.flags & PWC_FPS_MASK) >> PWC_FPS_SHIFT); post("pdp_v4l: pwc: setting framerate to %d", x->x_framerate); x->x_vwin.flags &= PWC_FPS_MASK; x->x_vwin.flags |= (x->x_framerate << PWC_FPS_SHIFT); if (ioctl(x->x_tvfd, VIDIOCSWIN, &x->x_vwin)){ perror("pdp_v4l: pwc: VIDIOCSWIN"); goto closit; } if (ioctl(x->x_tvfd, VIDIOCGWIN, &x->x_vwin)){ perror("pdp_v4l: pwc: VIDIOCGWIN"); goto closit; } post("pdp_v4l: pwc: framerate: %d", (x->x_vwin.flags & PWC_FPS_MASK) >> PWC_FPS_SHIFT); } return; closit: pdp_v4l_close_error(x); return; #else post("pdp_v4l: additional pwc support disabled"); return; #endif } static void pdp_v4l_sync_frame(t_pdp_v4l* x){ /* grab frame */ if (ioctl(x->x_tvfd, VIDIOCSYNC, &x->x_vmmap[x->x_frame].frame) < 0){ perror("pdp_v4l: VIDIOCSYNC"); pdp_v4l_close(x); return; } } static void pdp_v4l_capture_frame(t_pdp_v4l* x){ if (ioctl(x->x_tvfd, VIDIOCMCAPTURE, &x->x_vmmap[x->x_frame]) < 0){ if (errno == EAGAIN) post("pdp_v4l: can't sync (no video source?)\n"); else perror("pdp_v4l: VIDIOCMCAPTURE"); if (ioctl(x->x_tvfd, VIDIOCMCAPTURE, &x->x_vmmap[x->x_frame]) < 0) perror("pdp_v4l: VIDIOCMCAPTURE2"); post("pdp_v4l: frame %d %d, format %d, width %d, height %d", x->x_frame, x->x_vmmap[x->x_frame].frame, x->x_vmmap[x->x_frame].format, x->x_vmmap[x->x_frame].width, x->x_vmmap[x->x_frame].height); pdp_v4l_close(x); return; } } static void *pdp_v4l_thread(void *voidx) { t_pdp_v4l *x = ((t_pdp_v4l *)voidx); /* flip buffers */ x->x_frame ^= 0x1; /* capture with a double buffering scheme */ while (x->x_continue_thread){ /* schedule capture command for next frame */ pdp_v4l_capture_frame(x); /* wait until previous capture is ready */ x->x_frame ^= 0x1; pdp_v4l_sync_frame(x); /* setup pointers for main thread */ x->x_frame_ready = 1; x->x_last_frame = x->x_frame; } return 0; } static void pdp_v4l_open(t_pdp_v4l *x, t_symbol *name) { /* open a v4l device and allocate a buffer */ unsigned int size; int i; unsigned int width, height; /* if already opened -> close */ if (x->x_initialized) pdp_v4l_close(x); /* exit if retried too much */ if (!x->x_open_retry){ post("pdp_v4l: retry count reached zero for %s", name->s_name); post("pdp_v4l: try to open manually"); return; } post("pdp_v4l: opening %s", name->s_name); x->x_device = name; if ((x->x_tvfd = open(name->s_name, O_RDWR)) < 0) { post("pdp_v4l: error:"); perror(name->s_name); goto closit; } if (ioctl(x->x_tvfd, VIDIOCGCAP, &x->x_vcap) < 0) { perror("get capabilities"); goto closit; } post("pdp_v4l: cap: name %s type %d channels %d maxw %d maxh %d minw %d minh %d", x->x_vcap.name, x->x_vcap.type, x->x_vcap.channels, x->x_vcap.maxwidth, x->x_vcap.maxheight, x->x_vcap.minwidth, x->x_vcap.minheight); if (ioctl(x->x_tvfd, VIDIOCGPICT, &x->x_vpicture) < 0) { perror("VIDIOCGCAP"); goto closit; } post("pdp_v4l: picture: brightness %d depth %d palette %d", x->x_vpicture.brightness, x->x_vpicture.depth, x->x_vpicture.palette); /* get channel info */ for (i = 0; i < x->x_vcap.channels; i++) { x->x_vchannel.channel = i; if (ioctl(x->x_tvfd, VIDIOCGCHAN, &x->x_vchannel) < 0) { perror("VDIOCGCHAN"); goto closit; } post("pdp_v4l: channel %d name %s type %d flags %d", x->x_vchannel.channel, x->x_vchannel.name, x->x_vchannel.type, x->x_vchannel.flags); } /* switch to the desired channel */ if (x->x_channel < 0) x->x_channel = 0; if (x->x_channel >= x->x_vcap.channels) x->x_channel = x->x_vcap.channels - 1; x->x_vchannel.channel = x->x_channel; if (ioctl(x->x_tvfd, VIDIOCGCHAN, &x->x_vchannel) < 0) { perror("pdp_v4l: warning: VDIOCGCHAN"); post("pdp_v4l: cant change to channel %d",x->x_channel); // ignore error // goto closit; } else{ post("pdp_v4l: switched to channel %d", x->x_channel); } x->x_vchannel.norm = x->x_norm; if (ioctl(x->x_tvfd, VIDIOCSCHAN, &x->x_vchannel) < 0) { perror("pdp_v4l: warning: VDIOCSCHAN"); post("pdp_v4l: cant change to norm %d",x->x_norm); // ignore error // goto closit; } if (x->x_freq > 0){ if (ioctl(x->x_tvfd, VIDIOCSFREQ, &x->x_freq) < 0) perror ("couldn't set frequency :"); } /* get mmap numbers */ if (ioctl(x->x_tvfd, VIDIOCGMBUF, &x->x_vmbuf) < 0) { perror("pdp_v4l: VIDIOCGMBUF"); goto closit; } post("pdp_v4l: buffer size %d, frames %d, offset %d %d", x->x_vmbuf.size, x->x_vmbuf.frames, x->x_vmbuf.offsets[0], x->x_vmbuf.offsets[1]); if (!(x->x_videobuf = (unsigned char *) mmap(0, x->x_vmbuf.size, PROT_READ|PROT_WRITE, MAP_SHARED, x->x_tvfd, 0))) { perror("pdp_v4l: mmap"); goto closit; } width = (x->x_width > (unsigned int)x->x_vcap.minwidth) ? x->x_width : (unsigned int)x->x_vcap.minwidth; width = (width > (unsigned int)x->x_vcap.maxwidth) ?(unsigned int) x->x_vcap.maxwidth : width; height = (x->x_height > (unsigned int)x->x_vcap.minheight) ? x->x_height :(unsigned int) x->x_vcap.minheight; height = (height > (unsigned int)x->x_vcap.maxheight) ? (unsigned int)x->x_vcap.maxheight : height; for (i = 0; i < NBUF; i++) { //x->x_vmmap[i].format = VIDEO_PALETTE_YUV420P; //x->x_vmmap[i].format = VIDEO_PALETTE_UYVY; x->x_vmmap[i].width = width; x->x_vmmap[i].height = height; x->x_vmmap[i].frame = i; } //goto test; //try yuv planar format x->x_v4l_palette = VIDEO_PALETTE_YUV420P; for (i = 0; i < NBUF; i++) x->x_vmmap[i].format = x->x_v4l_palette; if (ioctl(x->x_tvfd, VIDIOCMCAPTURE, &x->x_vmmap[x->x_frame]) < 0) { if (errno == EAGAIN) post("pdp_v4l: can't sync (no video source?)"); } else{ post("pdp_v4l: using VIDEO_PALETTE_YUV420P"); goto capture_ok; } //try VIDEO_PALETTE_YUV422 format x->x_v4l_palette = VIDEO_PALETTE_YUV422; for (i = 0; i < NBUF; i++) x->x_vmmap[i].format = x->x_v4l_palette; if (ioctl(x->x_tvfd, VIDIOCMCAPTURE, &x->x_vmmap[x->x_frame]) < 0) { if (errno == EAGAIN) post("pdp_v4l: can't sync (no video source?)"); } else{ post("pdp_v4l: using VIDEO_PALETTE_YUV422"); goto capture_ok; } test: //try rgb packed format x->x_v4l_palette = VIDEO_PALETTE_RGB24; for (i = 0; i < NBUF; i++) x->x_vmmap[i].format = x->x_v4l_palette; if (ioctl(x->x_tvfd, VIDIOCMCAPTURE, &x->x_vmmap[x->x_frame]) < 0) { if (errno == EAGAIN) post("pdp_v4l: can't sync (no video source?)"); } else{ post("pdp_v4l: using VIDEO_PALETTE_RGB24"); goto capture_ok; } // none of the formats are supported perror("pdp_v4l: VIDIOCMCAPTURE: format not supported"); goto closit; capture_ok: post("pdp_v4l: frame %d %d, format %d, width %d, height %d", x->x_frame, x->x_vmmap[x->x_frame].frame, x->x_vmmap[x->x_frame].format, x->x_vmmap[x->x_frame].width, x->x_vmmap[x->x_frame].height); x->x_width = width; x->x_height = height; post("pdp_v4l: Opened video connection (%dx%d)",x->x_width,x->x_height); /* do some pwc specific inits */ pdp_v4l_pwc_init(x); x->x_initialized = true; /* create thread */ x->x_continue_thread = 1; x->x_frame_ready = 0; pthread_create(&x->x_thread_id, 0, pdp_v4l_thread, x); return; closit: pdp_v4l_close_error(x); } static void pdp_v4l_open_manual(t_pdp_v4l *x, t_symbol *name) { x->x_open_retry = PDP_XV_RETRIES; pdp_v4l_open(x, name); } static void pdp_v4l_channel(t_pdp_v4l *x, t_float f) { int channel = (float)f; if (x->x_initialized){ pdp_v4l_close(x); x->x_channel = channel; pdp_v4l_open(x, x->x_device); } else x->x_channel = channel; } static void pdp_v4l_freq(t_pdp_v4l *x, t_float f) { int freq = (int)f; x->x_freq = freq; if (x->x_freq > 0){ if (ioctl(x->x_tvfd, VIDIOCSFREQ, &x->x_freq) < 0) perror ("couldn't set frequency :"); else {post("pdp_v4l: tuner frequency: %f MHz", f / 16.0f);} } } static void pdp_v4l_freqMHz(t_pdp_v4l *x, t_float f) { pdp_v4l_freq(x, f*16.0f); } static void pdp_v4l_bang(t_pdp_v4l *x) { /* if initialized, grab a frame and output it */ unsigned int w,h,nbpixels,packet_size,plane1,plane2; unsigned char *newimage; int object,length,pos,i,encoding; t_pdp* header; short int * data; static short int gain[4] = {0x7fff, 0x7fff, 0x7fff, 0x7fff}; if (!(x->x_initialized)){ post("pdp_v4l: no device opened"); if (x->x_auto_open){ post("pdp_v4l: attempting auto open"); pdp_v4l_open(x, x->x_device); if (!(x->x_initialized)){ post("pdp_v4l: auto open failed"); return; } } else return; } /* do nothing if there is no frame ready */ if((!x->x_frame_ready) && (x->x_only_new_frames)) return; x->x_frame_ready = 0; /* get the address of the "other" frame */ newimage = x->x_videobuf + x->x_vmbuf.offsets[x->x_last_frame]; /* create new packet */ w = x->x_width; h = x->x_height; nbpixels = w * h; /* switch(x->x_pdp_image_type){ case PDP_IMAGE_GREY: packet_size = nbpixels << 1; break; case PDP_IMAGE_YV12: packet_size = (nbpixels + (nbpixels >> 1)) << 1; break; default: packet_size = 0; post("pdp_v4l: internal error"); } */ packet_size = (nbpixels + (nbpixels >> 1)) << 1; plane1 = nbpixels; plane2 = nbpixels + (nbpixels>>2); object = pdp_packet_new(PDP_IMAGE, packet_size); header = pdp_packet_header(object); data = (short int *) pdp_packet_data(object); //header->info.image.encoding = x->x_pdp_image_type; header->info.image.encoding = PDP_IMAGE_YV12; header->info.image.width = w; header->info.image.height = h; /* read from the "other" frame */ newimage = x->x_videobuf + x->x_vmbuf.offsets[x->x_frame ^ 0x1]; /* convert data to pdp packet */ switch(x->x_v4l_palette){ case VIDEO_PALETTE_YUV420P: pdp_llconv(newimage, RIF_YUV__P411_U8, data, RIF_YVU__P411_S16, w, h); break; case VIDEO_PALETTE_RGB24: pdp_llconv(newimage, RIF_RGB__P____U8, data, RIF_YVU__P411_S16, w, h); break; case VIDEO_PALETTE_YUV422: pdp_llconv(newimage, RIF_YUYV_P____U8, data, RIF_YVU__P411_S16, w, h); break; default: post("pdp_v4l: unsupported palette"); break; } /* if (PDP_IMAGE_YV12 == x->x_pdp_image_type){ pixel_unpack_u8s16_y(&newimage[0], data, nbpixels>>3, x->x_state_data->gain); pixel_unpack_u8s16_uv(&newimage[plane1], &data[plane2], nbpixels>>5, x->x_state_data->gain); pixel_unpack_u8s16_uv(&newimage[plane2], &data[plane1], nbpixels>>5, x->x_state_data->gain); } */ //x->x_v4l_palette = VIDEO_PALETTE_YUV420P; //x->x_v4l_palette = VIDEO_PALETTE_RGB24; /* else if(PDP_IMAGE_GREY == x->x_pdp_image_type){ pixel_unpack_u8s16_y(&newimage[0], data, nbpixels>>3, x->x_state_data->gain); } */ //post("pdp_v4l: mark unused %d", object); pdp_packet_mark_unused(object); outlet_pdp(x->x_outlet0, object); } static void pdp_v4l_dim(t_pdp_v4l *x, t_floatarg xx, t_floatarg yy) { unsigned int w,h; xx = (xx < 0.0f) ? 0.0f : xx; yy = (yy < 0.0f) ? 0.0f : yy; w = (unsigned int)xx; h = (unsigned int)yy; if (x->x_initialized){ pdp_v4l_close(x); x->x_width = w; x->x_height = h; pdp_v4l_open(x, x->x_device); } else{ x->x_width = w; x->x_height = h; } } static void pdp_v4l_free(t_pdp_v4l *x) { pdp_v4l_close(x); } t_class *pdp_v4l_class; void *pdp_v4l_new(void) { t_pdp_v4l *x = (t_pdp_v4l *)pd_new(pdp_v4l_class); x->x_outlet0 = outlet_new(&x->x_obj, &s_anything); x->x_initialized = false; x->x_tvfd = -1; x->x_frame = 0; x->x_last_frame = 0; x->x_framerate = 27; x->x_auto_open = true; x->x_device = gensym("/dev/video0"); x->x_continue_thread = 0; x->x_only_new_frames = 1; x->x_width = 320; x->x_height = 240; // pdp_v4l_type(x, gensym("yv12")); x->x_open_retry = PDP_XV_RETRIES; x->x_channel = 0; x->x_freq = -1; //don't set freq by default return (void *)x; } #ifdef __cplusplus extern "C" { #endif void pdp_v4l_setup(void) { pdp_v4l_class = class_new(gensym("pdp_v4l"), (t_newmethod)pdp_v4l_new, (t_method)pdp_v4l_free, sizeof(t_pdp_v4l), 0, A_NULL); class_addmethod(pdp_v4l_class, (t_method)pdp_v4l_bang, gensym("bang"), A_NULL); class_addmethod(pdp_v4l_class, (t_method)pdp_v4l_close_manual, gensym("close"), A_NULL); class_addmethod(pdp_v4l_class, (t_method)pdp_v4l_open_manual, gensym("open"), A_SYMBOL, A_NULL); class_addmethod(pdp_v4l_class, (t_method)pdp_v4l_channel, gensym("channel"), A_FLOAT, A_NULL); class_addmethod(pdp_v4l_class, (t_method)pdp_v4l_dim, gensym("dim"), A_FLOAT, A_FLOAT, A_NULL); class_addmethod(pdp_v4l_class, (t_method)pdp_v4l_freq, gensym("freq"), A_FLOAT, A_NULL); class_addmethod(pdp_v4l_class, (t_method)pdp_v4l_freqMHz, gensym("freqMHz"), A_FLOAT, A_NULL); } #ifdef __cplusplus } #endif