/* videoport.c - uncompressed video */ #include "videoport.h" /* buffer management calls ----------------------------------------------- */ #define SPEW(x) /* see the comment in struct _videoport in videoport.h * for info on these calls */ void _videoport_open(videoport me) { int i; me->nlocs = me->buf_fields+1; me->q = malloc(sizeof(videofield) * me->nlocs); me->vhead = 0; me->uhead = 0; me->tail = 0; for(i=0; i < me->nlocs; i++) { me->q[i].idx = i; /* no need to initialize me->q[i].info to NULL */ } /* we'll use the frontier MSC to compute the MSC of each field */ VC(me->expected_fmsc = vlGetFrontierMSC(me->svr, me->path, me->mem) ); me->maxbuffered = -1; } void _videoport_close(videoport me) { free(me->q); me->q = NULL; } int _videoport_nfilled(videoport me, int head, int tail) { int i = tail - head; if (i < 0) i += me->nlocs; return i; } int _videoport_nfillable(videoport me, int head, int tail) { int i = head - tail - 1; if (i < 0) i += me->nlocs; return i; } void _videoport_spew(videoport me) { int i; int guard; char *buf1 = malloc(me->nlocs + 1); char *buf2 = malloc(me->nlocs + 1); buf1[me->nlocs] = 0; buf2[me->nlocs] = 0; guard = me->vhead - 1; if (guard < 0) guard += me->nlocs; i = me->vhead; while (i != me->uhead) { buf1[i] = 'v'; buf2[i] = (me->q[i].info ? 'U' : '.'); i++; if (i == me->nlocs) i=0; } while (i != me->tail) { buf1[i] = 'u'; buf2[i] = '.'; i++; if (i == me->nlocs) i=0; } while (i != guard) { buf1[i] = '.'; buf2[i] = '.'; i++; if (i == me->nlocs) i=0; } buf1[i] = 'g'; buf2[i] = 'g'; printf("%s vfilled=%d\n" "%s ufilled=%d\n", buf1, _videoport_nfilled(me, me->vhead, me->tail), buf2, _videoport_nfilled(me, me->uhead, me->tail)); free(buf1); free(buf2); } void _videoport_poll_vlbuffer(videoport me) { USTMSCpair p; p.ust = -1; /* process all the VL events available right now */ /* XXX inefficient: should limit this check to every 15ms or so */ while (vlPending(me->svr)) { VLEvent ev; VC(vlNextEvent(me->svr, &ev)); switch (ev.reason) { case VLTransferFailed: error_exit("video transfer failed.\n"); break; } } /* process all the data (VM) or space (MV) available now */ for(;;) { VLInfoPtr info; stamp_t info_msc; void *data; stamp_t ust; /* grab the MSC of the next field we're about to read/write. */ info_msc = me->expected_fmsc; if (me->direction == VIDEOPORT_DIRECTION_VM) { /* get the next field from the video input */ if (NULL == (info = vlGetNextValid(me->svr, me->buffer))) break; /* no more data yet */ /* vlGetNextValid() makes the frontier MSC go up */ me->expected_fmsc++; } else /* me->direction == VIDEOPORT_DIRECTION_MV */ { /* get the next free space to put data for video output */ if (NULL == (info = vlGetNextFree(me->svr, me->buffer, me->vl_xfrsize))) break; /* no more space yet */ } /* get a pointer to the actual pixels */ VNC(data = vlGetActiveRegion(me->svr, me->buffer, info)); /* get a UST/MSC pair if we haven't already */ if (p.ust != -1) VC(vlGetUSTMSCPair(me->svr, me->path, me->vid, VL_ANY, me->mem, &p)); /* compute the UST of this field (when it came in or went out * the jack of the machine). * * we want the UST of 'info'. info_msc is the MSC of 'info.' * we use our UST/MSC pair to extrapolate the pair's UST to * info_msc. for more information, see the Lurker's Guide page: * "Introduction to UST and UST/MSC" */ ust = p.ust + (info_msc - p.msc)*me->ust_per_msc; /* now we have: * data, a pointer to the field's pixels * ust, a time when the field hit/will hit the jack of the machine * me->current_field_type, which is F1 or F2 * * add this videofield to the queue at me->tail */ SPEW(printf("videoport get to tail=%d\n", me->tail)); assert(_videoport_nfillable(me,me->vhead,me->tail) > 0); me->q[me->tail].pixels = data; me->q[me->tail].ust = ust; me->q[me->tail].msc = info_msc; me->q[me->tail].fieldtype = me->current_field_type; me->q[me->tail].info = info; me->tail++; if (me->tail >= me->nlocs) me->tail -= me->nlocs; /* field type always alternates with VL_CAPTURE_NONINTERLEAVED */ me->current_field_type = (me->current_field_type == FIELD_F1) ? FIELD_F2 : FIELD_F1; } /* check for video underflow/overflow */ { stamp_t fmsc; VC(fmsc = vlGetFrontierMSC(me->svr, me->path, me->mem)); if (fmsc != me->expected_fmsc) { printf("video error: we dropped/padded %lld fields\n", fmsc-me->expected_fmsc); } me->expected_fmsc = fmsc; } /* * keep track of the max # of VL fields are are tying up * (this is for debugging and performance monitoring) */ { int buffered = _videoport_nfilled(me, me->vhead, me->tail); if (buffered > me->maxbuffered) me->maxbuffered = buffered; } } /* used for debugging and performance monitoring */ int videoport_max_VL_buffers_used(videoport me) { int maxbuffered = me->maxbuffered; me->maxbuffered = -1; return maxbuffered; } int videoport_available_field_count(videoport me) { _videoport_poll_vlbuffer(me); return _videoport_nfilled(me, me->uhead, me->tail); } videofield *videoport_get_one_field(videoport me) { videofield *f; _videoport_poll_vlbuffer(me); if (_videoport_nfilled(me, me->uhead, me->tail) <= 0) return NULL; assert(_videoport_nfilled(me, me->vhead, me->tail) > 0); assert(me->q[me->vhead].info != NULL); SPEW(printf("app get from uh=%d\n", me->uhead)); f = &me->q[me->uhead]; me->uhead++; if (me->uhead >= me->nlocs) me->uhead -= me->nlocs; SPEW(_videoport_spew(me)); return f; } void videoport_put_one_field(videoport me, videofield *field) { int i; assert(_videoport_nfilled(me, me->vhead, me->tail) > 0); assert(me->q[me->vhead].info != NULL); assert(field); i = field->idx; assert(me->q[field->idx].info); /* i must be >= vhead and < uhead */ assert(((i -me->vhead+me->nlocs)%me->nlocs) >= 0); assert(((i -me->vhead+me->nlocs)%me->nlocs) < ((me->uhead-me->vhead+me->nlocs)%me->nlocs)); SPEW(printf("app put idx=%d\n", i)); /* first punch out the info that the user specified */ me->q[field->idx].info = NULL; /* then see how many _real_ put's we can do */ while (me->q[me->vhead].info == NULL && me->vhead != me->tail) { if (me->direction == VIDEOPORT_DIRECTION_VM) { /* don't need this buffer any more. */ vlPutFree(me->svr, me->buffer); } else /* me->direction == VIDEOPORT_DIRECTION_MV */ { /* done filling in this buffer: send it to the video output */ vlPutValid(me->svr, me->buffer); /* vlPutValid() makes the frontier MSC go up */ me->expected_fmsc++; } SPEW(printf("videoport put from vh=%d\n", me->vhead)); me->vhead++; if (me->vhead >= me->nlocs) me->vhead -= me->nlocs; } SPEW(_videoport_spew(me)); } /* other API calls ---------------------------------------------------- */ videoport videoport_open(int direction, int buf_fields, int flags) { VLControlValue val; videoport me = malloc(sizeof(struct _videoport)); int rc; if (!me) { setoserror(VIDEOPORT_MALLOC_FAILED); return NULL; } me->buf_fields = buf_fields; me->direction = direction; if (me->direction == VIDEOPORT_DIRECTION_VM) { /* * open video input */ VNC(me->svr = vlOpenVideo("")); VC(me->src = vlGetNode(me->svr, VL_SRC, VL_VIDEO, VL_ANY)); VC(me->drn = vlGetNode(me->svr, VL_DRN, VL_MEM, VL_ANY)); VC(me->path = vlCreatePath(me->svr, VL_ANY, me->src, me->drn)); rc = vlSetupPaths(me->svr, (VLPathList)&me->path, 1, VL_LOCK,VL_LOCK); me->vid = me->src; me->mem = me->drn; } else /* me->direction == VIDEOPORT_DIRECTION_MV */ { /* * open video output */ VNC(me->svr = vlOpenVideo("")); VC(me->src = vlGetNode(me->svr, VL_SRC, VL_MEM, VL_ANY)); VC(me->drn = vlGetNode(me->svr, VL_DRN, VL_VIDEO, VL_ANY)); VC(me->path = vlCreatePath(me->svr, VL_ANY, me->src, me->drn)); rc = vlSetupPaths(me->svr, (VLPathList)&me->path, 1, VL_LOCK,VL_LOCK); me->mem = me->src; me->vid = me->drn; } /* check if video device was busy */ if (rc < 0) { if (vlErrno == VLPathInUse) { vlCloseVideo(me->svr); free(me); return VIDEOPORT_DEVICE_BUSY; } else error_exit("VL error (%s) for call to vlSetupPaths()", vlStrError(vlErrno), #call); } /* each buffer will contain one field. */ val.intVal = VL_CAPTURE_NONINTERLEAVED; VC(vlSetControl(me->svr, me->path, me->mem, VL_CAP_TYPE, &val)); /* set colorspace and packing */ val.intVal = VL_PACKING_YVYU_422_8; VC(vlSetControl (me->svr, me->path, me->mem, VL_PACKING, &val)); me->bytes_per_pixel = 2; /* get size of each field in pixels */ VC(vlGetControl(me->svr, me->path, me->mem, VL_SIZE, &val)); printf("video field size is %dx%d\n", val.xyVal.x, val.xyVal.y); me->xsize = val.xyVal.x; me->ysize = val.xyVal.y; /* get time spacing of each MSC (in this case field) in nanoseconds */ VC(me->ust_per_msc = vlGetUSTPerMSC(me->svr, me->path, me->mem)); /* get size of each field in bytes */ me->vl_xfrsize = vlGetTransferSize(me->svr, me->path); /* currently VL guarantees at least 1 page padding is valid */ me->max_valid_bytes_per_field = roundup(me->vl_xfrsize, getpagesize()); printf("video max valid bytes per field is %d\n", me->max_valid_bytes_per_field); /* create VL buffer with buf_fields items (in this case fields) */ VNC(me->buffer = vlCreateBuffer(me->svr, me->path, me->mem, me->buf_fields)); printf("video buffer holds %d fields\n", me->buf_fields); if (flags & VIDEOPORT_ADVISE_NOACCESS) vlBufferAdvise(me->buffer, VL_BUFFER_ADVISE_NOACCESS); VC(vlRegisterBuffer(me->svr, me->path, me->mem, me->buffer)); /* say what VL events we want */ VC(vlSelectEvents(me->svr, me->path, VLTransferFailedMask|VLSequenceLostMask)); /* get file descriptors which we'll use to select() on */ me->event_fd = vlGetFD(me->svr); me->buffer_fd = vlBufferGetFd(me->buffer); /* XXX this assumes F1 dominance. we should query the device, or set * the device if it is settable, to determine the field dominance. * the field dominance parameter is--of course--device-specific, and * only some devices have one (other devices assume F1 dominance). * * for more information, see the Lurker's Guide pages: * "Definitions: F1/F2, Interleave, Field Dominance, and More" * "Where Does Field Dominance Originate?" * "Hints for Vid-to-Mem Applications" * "Introduction to UST and UST/MSC" */ printf("assuming F1 dominance\n"); me->current_field_type = FIELD_F1; /* begin video transfer */ VC(vlBeginTransfer(me->svr, me->path, 0, NULL)); /* set up the data structures for buffer management */ _videoport_open(me); /* start buffer off in a good condition */ { /* * VIDEOPORT_DIRECTION_VM: * empty the input buffer. discard contents. * * VIDEOPORT_DIRECTION_MV: * pre-stuff the output buffer with some data. doesn't matter * what. * * we do this to take the buffer out of overflow/underflow, * so that we can use UST/MSC to accurately measure the UST * of the first buffer we will dequeue/enqueue later. * * we choose to dequeue/enqueue an even number of fields, so we don't * mess up the F1/F2 ordering. */ int i; int items; if (me->direction == VIDEOPORT_DIRECTION_VM) items = videoport_available_field_count(me); else items = me->buf_fields; items = items & ~0x1; for(i=0; i < items; i++) { videofield *f; f = videoport_get_one_field(me); assert(f); videoport_put_one_field(me, f); } } me->fillpoint = 1; return me; } void videoport_close(videoport me) { /* shut down buffer management code */ _videoport_close(me); /* Stop the data transfer */ vlEndTransfer(me->svr, me->path); /* Disassociate the ring buffer from the path */ vlDeregisterBuffer(me->svr, me->path, me->mem, me->buffer); /* Destroy the ring buffer, free the memory it used */ vlDestroyBuffer(me->svr, me->buffer); /* Destroy the path, free it's memory */ vlDestroyPath(me->svr, me->path); /* Disconnect from the daemon */ vlCloseVideo(me->svr); free(me); } void videoport_setfillpoint(videoport me, int fillpoint) { me->fillpoint = fillpoint; } void videoport_setfdset(videoport me, fd_set *readset, fd_set *writeset, fd_set *exceptset, stamp_t *wakeust) { _videoport_poll_vlbuffer(me); if (videoport_available_field_count(me) >= me->fillpoint) *wakeust = 0; /* data/space available: wake up immedately */ else { FD_SET(me->event_fd, readset); if (me->direction == VIDEOPORT_DIRECTION_VM) FD_SET(me->buffer_fd, readset); else FD_SET(me->buffer_fd, writeset); } } void videoport_checkfdset(videoport me, fd_set *readset, fd_set *writeset, fd_set *exceptset) { /* a _videoport_poll_vlbuffer() call here not strictly necessary. * all other videoport calls which need this info will call * _videoport_poll_vlbuffer(me) themselves. we do it anyway * so that we get VL event notifications (transferfailed etc.) * sooner rather than later. */ _videoport_poll_vlbuffer(me); } void videoport_wait_for_space_or_data(videoport me) { while (videoport_available_field_count(me) < me->fillpoint) { fd_set readset, writeset, exceptset; stamp_t wakeust; struct timeval tv, *tvp; FD_ZERO(&readset); FD_ZERO(&writeset); FD_ZERO(&exceptset); wakeust = LONGLONG_MAX; videoport_setfdset(me, &readset, &writeset, &exceptset, &wakeust); if (wakeust == LONGLONG_MAX) tvp = NULL; else { stamp_t now, time_till_wakeup; dmGetUST((unsigned long long *)(&now)); time_till_wakeup = wakeust - now; if (time_till_wakeup < 0) { tv.tv_sec = 0; tv.tv_usec = 0; } else { tv.tv_sec = (long)(time_till_wakeup / 1000000000LL); time_till_wakeup -= tv.tv_sec * 1000000000LL; tv.tv_usec = (long)(time_till_wakeup / 1000); } tvp = &tv; } OC(select(getdtablehi(), &readset, &writeset, &exceptset, tvp)); videoport_checkfdset(me, &readset, &writeset, &exceptset); } }