• Main Page
  • Related Pages
  • Namespaces
  • Classes
  • Files
  • File List
  • File Members

src/N900/Daemon.cpp

00001 #include <errno.h>
00002 #include <sys/types.h>
00003 #include <sys/stat.h>
00004 #include <sys/fcntl.h>
00005 #include <sys/ioctl.h>
00006 #include <poll.h>
00007 
00008 #include "FCam/Time.h"
00009 #include "FCam/Frame.h"
00010 #include "FCam/Action.h"
00011 
00012 #include "../Debug.h"
00013 #include "Daemon.h"
00014 #include "linux/omap34xxcam-fcam.h"
00015 
00016 namespace FCam { namespace N900 {
00017 
00018     void *daemon_setter_thread_(void *arg) {
00019         Daemon *d = (Daemon *)arg;
00020         d->runSetter();    
00021         d->setterRunning = false;    
00022         close(d->daemon_fd);
00023         pthread_exit(NULL);
00024     } 
00025 
00026     void *daemon_handler_thread_(void *arg) {
00027         Daemon *d = (Daemon *)arg;
00028         d->runHandler();
00029         d->handlerRunning = false;
00030         pthread_exit(NULL);    
00031     }
00032 
00033     void *daemon_action_thread_(void *arg) {
00034         Daemon *d = (Daemon *)arg;
00035         d->runAction();
00036         d->actionRunning = false;
00037         pthread_exit(NULL);
00038     }
00039 
00040     Daemon::Daemon(Sensor *sensor) :
00041         sensor(sensor),
00042         stop(false), 
00043         frameLimit(128),
00044         dropPolicy(Sensor::DropNewest),
00045         setterRunning(false), 
00046         handlerRunning(false), 
00047         actionRunning(false),
00048         threadsLaunched(false) {
00049         
00050         // tie ourselves to the correct sensor
00051         v4l2Sensor = V4L2Sensor::instance("/dev/video0");
00052 
00053         // make the mutexes for the producer-consumer queues
00054         if ((errno = 
00055              -(pthread_mutex_init(&actionQueueMutex, NULL) ||               
00056                pthread_mutex_init(&cameraMutex, NULL)))) {
00057             error(Event::InternalError, sensor, "Error creating mutexes: %d", errno);
00058         }
00059     
00060         // make the semaphore
00061         sem_init(&actionQueueSemaphore, 0, 0);
00062 
00063         pipelineFlush = true;
00064     }
00065 
00066     void Daemon::launchThreads() {    
00067         if (threadsLaunched) return;
00068         threadsLaunched = true;
00069 
00070         // launch the threads
00071         pthread_attr_t attr;
00072         struct sched_param param;
00073 
00074         // Open the device as a daemon
00075         daemon_fd = open("/dev/video0", O_RDWR, 0);
00076 
00077         if (daemon_fd < 0) {
00078             error(Event::InternalError, sensor, "Error opening /dev/video0: %d", errno);
00079             return;
00080         }
00081 
00082         // Try to register myself as the fcam camera client
00083         if (ioctl(daemon_fd, VIDIOC_FCAM_INSTALL, NULL)) {
00084             if (errno == EBUSY) {
00085                 error(Event::DriverLockedError, sensor,
00086                       "An FCam program is already running");
00087             } else {
00088                 error(Event::DriverMissingError, sensor, 
00089                       "Error %d calling VIDIOC_FCAM_INSTALL: Are the FCam drivers installed?", errno);
00090             }
00091             return;
00092         }
00093         // I should now have CAP_SYS_NICE
00094 
00095         // make the setter thread
00096         param.sched_priority = sched_get_priority_min(SCHED_FIFO)+1;
00097 
00098         pthread_attr_init(&attr);
00099 
00100         if ((errno =
00101              -(pthread_attr_setschedparam(&attr, &param) ||
00102                pthread_attr_setschedpolicy(&attr, SCHED_FIFO) ||
00103                pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED) ||
00104                pthread_create(&setterThread, &attr, daemon_setter_thread_, this)))) {
00105             error(Event::InternalError, sensor, "Error creating daemon setter thread: %d", errno);
00106             return;
00107         } else {
00108             setterRunning = true;
00109         }
00110 
00111         // make the handler thread
00112         param.sched_priority = sched_get_priority_min(SCHED_FIFO);
00113 
00114         if ((errno =
00115              -(pthread_attr_setschedparam(&attr, &param) ||
00116                pthread_attr_setschedpolicy(&attr, SCHED_FIFO) ||
00117                pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED) ||
00118                pthread_create(&handlerThread, &attr, daemon_handler_thread_, this)))) {
00119             error(Event::InternalError, sensor, "Error creating daemon handler thread: %d", errno);
00120             return;
00121         } else {
00122             handlerRunning = true;
00123         }
00124 
00125         // make the actions thread
00126         param.sched_priority = sched_get_priority_max(SCHED_FIFO);
00127 
00128         if ((errno =
00129              -(pthread_attr_setschedparam(&attr, &param) ||
00130                pthread_attr_setschedpolicy(&attr, SCHED_FIFO) ||
00131                pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED) ||
00132                pthread_create(&actionThread, &attr, daemon_action_thread_, this)))) {
00133             error(Event::InternalError, sensor, "Error creating daemon action thread: %d", errno);
00134             return;
00135         } else {
00136             actionRunning = true;
00137         }
00138 
00139         pthread_attr_destroy(&attr);
00140     }
00141 
00142     Daemon::~Daemon() {
00143         stop = true;
00144 
00145         // post a wakeup call to the action thread
00146         sem_post(&actionQueueSemaphore);
00147 
00148         if (setterRunning) 
00149             pthread_join(setterThread, NULL);
00150     
00151         if (handlerRunning)
00152             pthread_join(handlerThread, NULL);
00153 
00154         if (actionRunning)
00155             pthread_join(actionThread, NULL);
00156 
00157         pthread_mutex_destroy(&actionQueueMutex);
00158         pthread_mutex_destroy(&cameraMutex);
00159 
00160         // Clean up all the internal queues
00161         while (inFlightQueue.size()) delete inFlightQueue.pull();        
00162         while (requestQueue.size()) delete requestQueue.pull();
00163         while (frameQueue.size()) delete frameQueue.pull();
00164         while (actionQueue.size()) {
00165             delete actionQueue.top().action;
00166             actionQueue.pop();
00167         }
00168 
00169         v4l2Sensor->stopStreaming();
00170 
00171         v4l2Sensor->close();
00172     }
00173 
00174 
00175     void Daemon::setDropPolicy(Sensor::DropPolicy p, int f) {
00176         dropPolicy = p;
00177         frameLimit = f;
00178         enforceDropPolicy();
00179     }
00180 
00181     void Daemon::enforceDropPolicy() {
00182         if (frameQueue.size() > frameLimit) {
00183             warning(Event::FrameLimitHit, sensor,
00184                     "WARNING: frame limit hit (%d), silently dropping %d frames.\n"
00185                    "You're not draining the frame queue quickly enough. Use longer \n"
00186                    "frame times or drain the frame queue until empty every time you \n"
00187                    "call getFrame()\n", frameLimit, frameQueue.size() - frameLimit);
00188             if (dropPolicy == Sensor::DropOldest) {
00189                 while (frameQueue.size() >= frameLimit) {
00190                     sensor->decShotsPending();
00191                     delete frameQueue.pull();
00192                 }
00193             } else if (dropPolicy == Sensor::DropNewest) {
00194                 while (frameQueue.size() >= frameLimit) {
00195                     sensor->decShotsPending();
00196                     delete frameQueue.pullBack();
00197                 }
00198             } else {
00199                 error(Event::InternalError, sensor, 
00200                       "Unknown drop policy! Not dropping frames.\n");
00201             }
00202         }
00203     }
00204 
00205     void Daemon::runSetter() {
00206         dprintf(2, "Running setter...\n"); fflush(stdout);
00207         tickSetter(Time::now());
00208         while (!stop) {
00209             struct timeval t;
00210             if (ioctl(daemon_fd, VIDIOC_FCAM_WAIT_FOR_HS_VS, &t)) {                
00211                 if (stop) break;
00212                 error(Event::DriverError, sensor, 
00213                       "error in VIDIOC_FCAM_WAIT_FOR_HS_VS: %s", strerror(errno));
00214                 usleep(100000);
00215                 continue;
00216             }
00217             tickSetter(Time(t));
00218         }
00219 
00220     }
00221 
00222     void Daemon::tickSetter(Time hs_vs) {
00223         static _Frame *req = NULL;
00224 
00225         dprintf(3, "Current hs_vs was at %d %d\n", hs_vs.s(), hs_vs.us());
00226 
00227         // how long will the previous frame take to readout
00228         static int lastReadout = 33000;
00229 
00230         static bool ignoreNextHSVS = false;
00231 
00232         dprintf(4, "Setter: got HS_VS\n");
00233 
00234         if (ignoreNextHSVS) {
00235             dprintf(4, "Setter: ignoring it\n");
00236             ignoreNextHSVS = false;
00237             return;
00238         }
00239 
00240         // Is there a request for which I have set resolution and exposure, but not gain and WB?
00241         if (req) {
00242             dprintf(4, "Setter: setting gain and WB\n");
00243             // set the gain and predicted done time on the pending request
00244             // and then push it onto the handler's input queue and the v4l2 buffer queue
00245 
00246             if (req->shot().gain != current._shot.gain) {
00247                 v4l2Sensor->setGain(req->shot().gain);
00248                 current._shot.gain = req->shot().gain;
00249             }
00250             current.gain = req->gain = v4l2Sensor->getGain();
00251 
00252             if (req->shot().whiteBalance != current._shot.whiteBalance) {
00253                 int wb = req->shot().whiteBalance;
00254 
00255                 // Very lenient sanity checks - restricting the range is up to the auto-wb algorithm
00256                 if (wb < 0) wb = 0; // a black-body radiator at absolute zero.
00257                 if (wb > 25000) wb = 25000; // A type 'O' star.
00258 
00259                 float matrix[12];
00260                 sensor->rawToRGBColorMatrix(wb, matrix);
00261                 v4l2Sensor->setWhiteBalance(matrix);
00262 
00263                 current._shot.whiteBalance = req->shot().whiteBalance;
00264                 current.whiteBalance = wb;            
00265             }
00266             req->whiteBalance = current.whiteBalance;
00267 
00268             // Predict when this frame will be done. It should be HS_VS +
00269             // the readout time for the previous frame + the frame time
00270             // for this request + however long the ISP takes to process
00271             // this frame.
00272             req->processingDoneTime = hs_vs;
00273         
00274             // first there's the readout time for the previous frame
00275             req->processingDoneTime += lastReadout;
00276         
00277             // then there's the time to expose and read out this frame
00278             req->processingDoneTime += req->frameTime;
00279 
00280             // then there's some significant time inside the ISP if it's YUV and large
00281             // (this may be N900 specific)
00282             int ispTime = 0;
00283             if (req->image.type() == UYVY) {
00284                 if (req->image.height() > 1024 && req->image.width() > 1024) {
00285                     ispTime = 65000;
00286                 } else {
00287                     ispTime = 10000;
00288                 }
00289             }
00290             req->processingDoneTime += ispTime;               
00291         
00292             // Also compute when the exposure starts and finishes
00293             req->exposureStartTime = hs_vs + req->frameTime - req->exposure;
00294 
00295             // Now updated the readout time for this frame. This formula
00296             // is specific to the toshiba et8ek8 sensor on the n900
00297             lastReadout = (current.image.height() > 1008) ? 76000 : 33000;
00298 
00299             req->exposureEndTime  = req->exposureStartTime + req->exposure + lastReadout;
00300 
00301             // now queue up this request's actions
00302             pthread_mutex_lock(&actionQueueMutex);
00303             for (std::set<FCam::Action*>::const_iterator i = req->shot().actions().begin();
00304                  i != req->shot().actions().end();
00305                  i++) {
00306                 Action a;
00307                 a.time = req->exposureStartTime + (*i)->time - (*i)->latency;
00308                 a.action = (*i)->copy();
00309                 actionQueue.push(a);
00310             }
00311             pthread_mutex_unlock(&actionQueueMutex);
00312             for (size_t i = 0; i < req->shot().actions().size(); i++) {
00313                 sem_post(&actionQueueSemaphore);
00314             }
00315 
00316             // The setter is done with this frame. Push it into the
00317             // in-flight queue for the handler to deal with.
00318             inFlightQueue.push(req);
00319             req = NULL;
00320         } else {
00321             // a bubble!
00322             lastReadout = (current.image.height() > 1008) ? 76000 : 33000;
00323         }
00324 
00325         // grab a new request and set an appropriate exposure and
00326         // frame time for it make sure there's a request ready for us
00327         if (!requestQueue.size()) {
00328             sensor->generateRequest();
00329         }
00330 
00331         // Peek ahead into the request queue to see what request we're
00332         // going to be handling next
00333         if (requestQueue.size()) {
00334             dprintf(4, "Setter: grabbing next request\n");
00335             // There's a real request for us to handle
00336             req = requestQueue.front();
00337         } else {
00338             dprintf(4, "Setter: inserting a bubble\n");
00339             // There are no pending requests, push a bubble into the
00340             // pipeline. The default parameters for a frame work nicely as
00341             // a bubble (as short as possible, no stats generated, output
00342             // unwanted).
00343             req = new _Frame;
00344             req->_shot.wanted = false;
00345 
00346             // bubbles should just run at whatever resolution is going. If
00347             // fast mode switches were possible, it might be nice to run
00348             // at the minimum resolution to go even faster, but they're
00349             // not.
00350             req->_shot.image = Image(current._shot.image.size(), current._shot.image.type(), Image::Discard);
00351 
00352             // generate histograms and sharpness maps if they're going,
00353             // but drop the data.
00354             req->_shot.histogram  = current._shot.histogram;
00355             req->_shot.sharpness  = current._shot.sharpness;
00356 
00357             // push the bubble into the pipe
00358             requestQueue.pushFront(req);
00359         }
00360 
00361         // Check if the next request requires a mode switch
00362         if (req->shot().image.size() != current._shot.image.size() ||
00363             req->shot().image.type() != current._shot.image.type() ||
00364             req->shot().histogram  != current._shot.histogram  ||
00365             req->shot().sharpness  != current._shot.sharpness) {
00366 
00367             // flush the pipeline
00368             dprintf(3, "Setter: Mode switch required - flushing pipe\n");
00369             pipelineFlush = true;
00370 
00371             pthread_mutex_lock(&cameraMutex);
00372             dprintf(3, "Setter: Handler done flushing pipe, passing control back to setter\n");
00373         
00374             // do the mode switch
00375         
00376             if (current.image.width() > 0) {
00377                 dprintf(3, "Setter: Shutting down camera\n");
00378                 v4l2Sensor->stopStreaming();
00379                 v4l2Sensor->close();
00380             }
00381             dprintf(3, "Setter: Starting up camera in new mode\n");
00382             v4l2Sensor->open();
00383         
00384             // set all the params for the new frame
00385             V4L2Sensor::Mode m;
00386             m.width  = req->shot().image.width();
00387             m.height = req->shot().image.height();
00388             m.type   = req->shot().image.type();
00389             v4l2Sensor->startStreaming(m, req->shot().histogram, req->shot().sharpness);
00390             v4l2Sensor->setFrameTime(0);
00391         
00392             dprintf(3, "Setter: Setter done bringing up camera, passing control back "
00393                     "to handler. Expect two mystery frames.\n");
00394             pipelineFlush = false;
00395             pthread_mutex_unlock(&cameraMutex);
00396         
00397             m = v4l2Sensor->getMode();
00398             // Set destination image to new mode settings
00399             req->image = Image(m.width, m.height, m.type, Image::Discard);
00400         
00401             current._shot.image = Image(req->shot().image.size(), req->shot().image.type(), Image::Discard);
00402             current._shot.histogram  = req->shot().histogram;
00403             current._shot.sharpness  = req->shot().sharpness;
00404             
00405             // make sure we set everything else for the next frame,
00406             // because restarting streaming will have nuked it
00407             current._shot.frameTime = -1;
00408             current._shot.exposure = -1;
00409             current._shot.gain = -1.0;
00410             current._shot.whiteBalance = -1;
00411             current.image = Image(m.width, m.height, m.type, Image::Discard);
00412          
00413             req = NULL;
00414         
00415             // Wait for the second HS_VS before proceeding
00416             ignoreNextHSVS = true;
00417         
00418             return;
00419         } else {
00420             // no mode switch required
00421         }
00422 
00423         // pop the request 
00424         requestQueue.pop(); 
00425 
00426         Time next = hs_vs + current.frameTime;
00427         dprintf(3, "The current %d x %d frame has a frametime of %d\n", 
00428                 current.image.width(), current.image.height(), current.frameTime);
00429         dprintf(3, "Predicting that the next HS_VS will be at %d %d\n",
00430                 next.s(), next.us());
00431         
00432         int exposure = req->shot().exposure;
00433         int frameTime = req->shot().frameTime; 
00434 
00435         if (frameTime < exposure + 400) {
00436             frameTime = exposure + 400;
00437         }
00438 
00439         dprintf(4, "Setter: setting frametime and exposure\n");
00440         // Set the exposure
00441         v4l2Sensor->setFrameTime(frameTime);
00442         v4l2Sensor->setExposure(exposure);
00443     
00444         // Tag the request with the actual params. Also store them in
00445         // current to avoid unnecessary I2C.
00446         current._shot.frameTime = frameTime;
00447         current._shot.exposure  = exposure;
00448         current.exposure  = req->exposure  = v4l2Sensor->getExposure();
00449         current.frameTime = req->frameTime = v4l2Sensor->getFrameTime();
00450         req->image = current.image;
00451 
00452         // Exposure and frame time are set. Return and wait for the next
00453         // HS_VS before setting gain for this request-> (and pulling the next request).
00454     
00455         dprintf(4, "Setter: Done with this HS_VS, waiting for the next one\n");
00456 
00457     }
00458 
00459 
00460     void Daemon::runHandler() {
00461         _Frame *req = NULL;       
00462         V4L2Sensor::V4L2Frame *f = NULL;
00463 
00464         pthread_mutex_lock(&cameraMutex);
00465 
00466         while (!stop) {
00467 
00468             // the setter may be waiting for me to finish processing
00469             // outstanding requests
00470             if (!req && pipelineFlush && inFlightQueue.size() == 0) {        
00471                 dprintf(3, "Handler: Handler done flushing pipe, passing control back to setter\n");
00472                 while (pipelineFlush) {
00473                     pthread_mutex_unlock(&cameraMutex);
00474                     // let the setter grab the mutex. It has higher priority,
00475                     // so it should happen instantly. We put this in a while
00476                     // loop just to be sure.
00477                     usleep(10000);
00478                     pthread_mutex_lock(&cameraMutex);
00479                 }
00480                 dprintf(3, "Handler: Setter done bringing up camera, passing control back to handler\n");
00481             
00482             }
00483         
00484             if (pipelineFlush) {
00485                 dprintf(3, "Handler: Setter would like me to flush the pipeline, but I have requests in flight\n");
00486             }
00487         
00488             // wait for a frame
00489             if (!f)
00490                 f = v4l2Sensor->acquireFrame(true);
00491 
00492             if (!f) {                
00493                 error(Event::InternalError, "Handler got a NULL frame\n");
00494                 usleep(300000);
00495                 continue;
00496             } 
00497         
00498             // grab a request to match to it
00499             if (!req) {
00500                 if (inFlightQueue.size()) {
00501                     dprintf(4, "Handler: popping a frame request\n");
00502                     req = inFlightQueue.pull();
00503                 } else {
00504                     // there's no request for this frame - probably coming up
00505                     // from a mode switch or starting up
00506                     dprintf(3, "Handler: Got a frame without an outstanding request,"
00507                             " dropping it.\n");
00508                     v4l2Sensor->releaseFrame(f);
00509                     f = NULL;
00510                     continue;
00511                 }
00512             }
00513         
00514             // at this point we have a frame and a request, now look at
00515             // the time delta between them to see if they're a match
00516             int dt = req->processingDoneTime - f->processingDoneTime;
00517         
00518             dprintf(4, "Handler dt = %d\n", dt);
00519         
00520             if (dt < -25000) { // more than 25 ms late
00521                 dprintf(3, "Handler: Expected a frame at %d %d, but one didn't arrive until %d %d\n",
00522                         req->processingDoneTime.s(), req->processingDoneTime.us(),
00523                         f->processingDoneTime.s(), f->processingDoneTime.us());
00524                 req->image = Image(req->image.size(), req->image.type(), Image::Discard);
00525                 if (!req->shot().wanted) {
00526                     delete req;
00527                 } else {
00528                     // the histogram and sharpness map may still have appeared
00529                     req->histogram = v4l2Sensor->getHistogram(req->exposureEndTime, req->shot().histogram);
00530                     req->sharpness = v4l2Sensor->getSharpnessMap(req->exposureEndTime, req->shot().sharpness);
00531                     frameQueue.push(req);
00532                     enforceDropPolicy();
00533                 }
00534                 req = NULL;
00535             } else if (dt < 10000) {
00536                 // Is this frame wanted or a bubble?
00537                 if (!req->shot().wanted) {
00538                     // it's a bubble - drop it
00539                     dprintf(4, "Handler: discarding a bubble\n");
00540                     delete req;
00541                     v4l2Sensor->releaseFrame(f);
00542                 } else {
00543                 
00544                     // this looks like a match - bag and tag it
00545                     req->processingDoneTime = f->processingDoneTime;
00546 
00547                     size_t bytes = req->image.width()*req->image.height()*2;
00548                     if (f->length < bytes) bytes = f->length;
00549 
00550                     if (req->shot().image.autoAllocate()) {
00551                         req->image = Image(req->image.size(), req->image.type(), f->data).copy();
00552                     } else if (req->shot().image.discard()) {
00553                         req->image = Image(req->image.size(), req->image.type(), Image::Discard);
00554                     } else {
00555                         if (req->image.size() != req->shot().image.size()) {
00556                             error(Event::ResolutionMismatch, sensor, 
00557                                   "Requested image size (%d x %d) "
00558                                   "on an already allocated image does not "
00559                                   "match actual image size (%d x %d). Dropping image data.\n",
00560                                   req->shot().image.width(), req->shot().image.height(),
00561                                   req->image.width(), req->image.height());
00562                             req->image = Image(req->image.size(), req->image.type(), Image::Discard);
00563                             // TODO: crop instead?
00564                         } else { // the size matches
00565                             req->image = req->shot().image;
00566                             // figure out how long I can afford to wait
00567                             // For now, 10000 us should be safe
00568                             Time lockStart = Time::now();
00569                             if (req->image.lock(10000)) {
00570                                 req->image.copyFrom(Image(req->image.size(), req->image.type(), f->data));
00571                                 req->image.unlock();
00572                             } else {
00573                                 warning(Event::ImageTargetLocked, sensor,
00574                                         "Daemon discarding image data (target is still locked, "
00575                                         "waited for %d us)\n", Time::now() - lockStart);
00576                                 req->image = Image(req->image.size(), req->image.type(), Image::Discard);
00577                             }
00578                         }
00579                     }
00580 
00581                     v4l2Sensor->releaseFrame(f);
00582                     req->histogram = v4l2Sensor->getHistogram(req->exposureEndTime, req->shot().histogram);
00583                     req->sharpness = v4l2Sensor->getSharpnessMap(req->exposureEndTime, req->shot().sharpness);
00584                 
00585                     frameQueue.push(req);
00586                     enforceDropPolicy();
00587 
00588                 }
00589             
00590                 req = NULL;
00591                 f = NULL;
00592 
00593             } else { // more than 10ms early. Perhaps there was a mode switch.
00594                 dprintf(3, "Handler: Received an early mystery frame (%d %d) vs (%d %d), dropping it.\n",
00595                         f->processingDoneTime.s(), f->processingDoneTime.us(),
00596                         req->processingDoneTime.s(), req->processingDoneTime.us());
00597                 v4l2Sensor->releaseFrame(f);
00598                 f = NULL;
00599             }
00600         
00601         }
00602         pthread_mutex_unlock(&cameraMutex);
00603     
00604     }
00605 
00606 
00607     void Daemon::runAction() {
00608         dprintf(2, "Action thread running...\n");
00609         while (1) {       
00610             sem_wait(&actionQueueSemaphore);
00611             if (stop) break;
00612             // priority inversion :(
00613             pthread_mutex_lock(&actionQueueMutex);
00614             Action a = actionQueue.top();
00615             actionQueue.pop();
00616             pthread_mutex_unlock(&actionQueueMutex);
00617 
00618             Time t = Time::now();
00619             int delay = (a.time - t) - 500;
00620             if (delay > 0) usleep(delay);
00621             Time before = Time::now();
00622             // busy wait until go time
00623             while (a.time > before) before = Time::now();
00624             a.action->doAction();
00625             //Time after = Time::now();
00626             dprintf(3, "Action thread: Initiated action %d us after scheduled time\n", before - a.time);
00627             delete a.action;
00628         }
00629     }
00630 
00631 }}
00632  

Generated on Thu Jul 22 2010 17:50:33 for FCam by  doxygen 1.7.1