// ###################################################################### void KeyBoard::setBlocking(const bool block) { int flags = fcntl(0, F_GETFL, 0); if (flags == -1) PLERROR("Cannot get flags"); if (block) flags &= (~O_NONBLOCK); else flags |= O_NONBLOCK; if (fcntl(0, F_SETFL, flags) == -1) PLERROR("Cannot set flags"); else blocking = block; // remember setting }
// ###################################################################### KeyBoard::KeyBoard() { // switch keyboard to non-canonical mode: struct termios new_settings; if (tcgetattr(0, &stored_settings) == -1) PLERROR("Cannot tcgetattr"); new_settings = stored_settings; new_settings.c_lflag &= (~ICANON); new_settings.c_cc[VTIME] = 0; new_settings.c_cc[VMIN] = 1; if (tcsetattr(0, TCSANOW, &new_settings) == -1) PLERROR("Cannot tcsetattr"); // by default, assume keyboard is blocking: blocking = true; }
// ###################################################################### KeyBoard:: ~KeyBoard()//Destructor { // restore blocking mode: setBlocking(true); // restore previous keyboard attributes: if (tcsetattr(0, TCSANOW, &stored_settings) == -1) PLERROR("Cannot tcsetattr"); }
// ############################################################################################################## jevois::Gadget::~Gadget() { JEVOIS_TRACE(1); streamOff(); // Tell run() thread to finish up: itsRunning.store(false); // Will block until the run() thread completes: if (itsRunFuture.valid()) try { itsRunFuture.get(); } catch (...) { jevois::warnAndIgnoreException(); } if (close(itsFd) == -1) PLERROR("Error closing UVC gadget -- IGNORED"); }
// ############################################################################################################## jevois::Camera::~Camera() { JEVOIS_TRACE(1); // Turn off streaming if it was on: try { streamOff(); } catch (...) { jevois::warnAndIgnoreException(); } // Block until the run() thread completes: itsRunning.store(false); if (itsRunFuture.valid()) try { itsRunFuture.get(); } catch (...) { jevois::warnAndIgnoreException(); } if (itsBuffers) delete itsBuffers; if (close(itsFd) == -1) PLERROR("Error closing V4L2 camera"); }
// ############################################################################################################## void jevois::Camera::run() { JEVOIS_TRACE(1); fd_set rfds; // For new images captured fd_set efds; // For errors struct timeval tv; // Switch to running state: itsRunning.store(true); // We may have to wait until the device is opened: while (itsFd == -1) std::this_thread::sleep_for(std::chrono::milliseconds(1)); LDEBUG("run() thread ready"); // NOTE: The flow is a little complex here, the goal is to minimize latency between a frame being captured and us // dequeueing it from the driver and making it available to get(). To achieve low latency, we thus need to be polling // the driver most of the time, and we need to prevent other threads from doing various ioctls while we are polling, // as the SUNXI-VFE driver does not like that. Thus, there is high contention on itsMtx which we lock most of the // time. For this reason we do a bit of sleeping with itsMtx unlocked at places where we know it will not increase our // captured image delivery latency. std::vector<size_t> doneidx; // Wait for event from the gadget kernel driver and process them: while (itsRunning.load()) try { // Requeue any done buffer. To avoid having to use a double lock on itsOutputMtx (for itsDoneIdx) and itsMtx (for // itsBuffers->qbuf()), we just swap itsDoneIdx into a local variable here, and invalidate it, with itsOutputMtx // locked, then we will do the qbuf() later, if needed, while itsMtx is locked: { std::lock_guard<std::mutex> _(itsOutputMtx); if (itsDoneIdx.empty() == false) itsDoneIdx.swap(doneidx); } std::unique_lock<std::timed_mutex> lck(itsMtx); // Do the actual qbuf of any done buffer, ignoring any exception: for (size_t idx : doneidx) try { itsBuffers->qbuf(idx); } catch (...) { jevois::warnAndIgnoreException(); } doneidx.clear(); // SUNXI-VFE does not like to be polled when not streaming; if indeed we are not streaming, unlock and then sleep // a bit to avoid too much contention on itsMtx: if (itsStreaming.load() == false) { lck.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(5)); continue; } // Poll the device to wait for any new captured video frame: FD_ZERO(&rfds); FD_ZERO(&efds); FD_SET(itsFd, &rfds); FD_SET(itsFd, &efds); tv.tv_sec = 0; tv.tv_usec = 5000; int ret = select(itsFd + 1, &rfds, nullptr, &efds, &tv); if (ret == -1) { PLERROR("Select error"); if (errno == EINTR) continue; else break; } else if (ret > 0) // NOTE: ret == 0 would mean timeout { if (FD_ISSET(itsFd, &efds)) LFATAL("Camera device error"); if (FD_ISSET(itsFd, &rfds)) { // A new frame has been captured. Dequeue a buffer from the camera driver: struct v4l2_buffer buf; itsBuffers->dqbuf(buf); // Create a RawImage from that buffer: jevois::RawImage img; img.width = itsFormat.fmt.pix.width; img.height = itsFormat.fmt.pix.height; img.fmt = itsFormat.fmt.pix.pixelformat; img.fps = itsFps; img.buf = itsBuffers->get(buf.index); img.bufindex = buf.index; // Unlock itsMtx: lck.unlock(); // We want to never block waiting for people to consume our grabbed frames here, hence we just overwrite our // output image here, it just always contains the latest grabbed image: { std::lock_guard<std::mutex> _(itsOutputMtx); itsOutputImage = img; } LDEBUG("Captured image " << img.bufindex << " ready for processing"); // Let anyone trying to get() our image know it's here: itsOutputCondVar.notify_all(); // This is also a good time to sleep a bit since it will take a while for the next frame to arrive, this // should allow people who had been trying to get a lock on itsMtx to get it now: std::this_thread::sleep_for(std::chrono::milliseconds(5)); } } } catch (...) { jevois::warnAndIgnoreException(); } // Switch out of running state in case we did interrupt the loop here by a break statement: itsRunning.store(false); }
// ###################################################################### void VisualObject::deleteImageFile() const { if (Raster::fileExists(itsImageFname, RASFMT_PNG)) if (unlink(itsImageFname.c_str()) == -1) PLERROR("Could not delete '%s' -- IGNORING", itsImageFname.c_str()); }
// ############################################################################################################## void jevois::Gadget::run() { JEVOIS_TRACE(1); fd_set wfds; // For UVC video streaming fd_set efds; // For UVC events struct timeval tv; // Switch to running state: itsRunning.store(true); // We may have to wait until the device is opened: while (itsFd == -1) std::this_thread::sleep_for(std::chrono::milliseconds(1)); // Wait for event from the gadget kernel driver and process them: while (itsRunning.load()) { // Wait until we either receive an event or we are ready to send the next buffer over: FD_ZERO(&wfds); FD_ZERO(&efds); FD_SET(itsFd, &wfds); FD_SET(itsFd, &efds); tv.tv_sec = 0; tv.tv_usec = 10000; int ret = select(itsFd + 1, nullptr, &wfds, &efds, &tv); if (ret == -1) { PLERROR("Select error"); if (errno == EINTR) continue; else break; } else if (ret > 0) // We have some events, handle them right away: { // Note: we may have more than one event, so here we try processEvents() several times to be sure: if (FD_ISSET(itsFd, &efds)) { // First event, we will report error if any: try { processEvents(); } catch (...) { jevois::warnAndIgnoreException(); } // Let's try to dequeue one more, in most cases it should throw: while (true) try { processEvents(); } catch (...) { break; } } if (FD_ISSET(itsFd, &wfds)) try { processVideo(); } catch (...) { jevois::warnAndIgnoreException(); } } // We timed out // Sometimes we miss events in the main loop, likely because more events come while we are unlocked in the USB UDC // driver and processing here. So let's try to dequeue one more, in most cases it should throw: while (true) try { processEvents(); } catch (...) { break; } // While the driver is not busy in select(), queue at most one buffer that is ready to send off: try { JEVOIS_TIMED_LOCK(itsMtx); if (itsDoneImgs.size()) { LDEBUG("Queuing image " << itsDoneImgs.front() << " for sending over USB"); // We need to prepare a legit v4l2_buffer, including bytesused: struct v4l2_buffer buf = { }; buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; buf.memory = V4L2_MEMORY_MMAP; buf.index = itsDoneImgs.front(); buf.length = itsBuffers->get(buf.index)->length(); if (itsFormat.fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) buf.bytesused = itsBuffers->get(buf.index)->bytesUsed(); else buf.bytesused = buf.length; buf.field = V4L2_FIELD_NONE; buf.flags = 0; gettimeofday(&buf.timestamp, nullptr); // Queue it up so it can be sent to the host: itsBuffers->qbuf(buf); // This one is done: itsDoneImgs.pop_front(); } } catch (...) { jevois::warnAndIgnoreException(); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } // Switch out of running state in case we did interrupt the loop here by a break statement: itsRunning.store(false); }