Commit d43c8217fc072cc0e34aae2894902473be314898
1 parent
46985ff5
Update buffer classes slightly
Simplify SingleBuffer since controlling the total amount of frames being processed works quite well. Add a double-buffer scheme, which leads to a really marginal performance improvement (but I think it's cool).
Showing
1 changed file
with
102 additions
and
20 deletions
sdk/plugins/stream.cpp
| 1 | -#define QT_NO_DEBUG_OUTPUT | |
| 2 | - | |
| 3 | 1 | #include <openbr_plugin.h> |
| 4 | 2 | #include <QReadWriteLock> |
| 5 | 3 | #include <QWaitCondition> |
| ... | ... | @@ -29,19 +27,15 @@ public: |
| 29 | 27 | class SharedBuffer |
| 30 | 28 | { |
| 31 | 29 | public: |
| 32 | - SharedBuffer(int _maxItems = 200000) : maxItems(_maxItems) {} | |
| 30 | + SharedBuffer() {} | |
| 33 | 31 | virtual ~SharedBuffer() {} |
| 34 | 32 | |
| 35 | 33 | virtual void addItem(FrameData * input)=0; |
| 36 | 34 | |
| 37 | 35 | virtual FrameData * getItem()=0; |
| 38 | 36 | |
| 39 | - int getMaxItems() { return maxItems; } | |
| 40 | - | |
| 41 | 37 | virtual void stoppedInput() =0; |
| 42 | 38 | virtual void startInput() = 0; |
| 43 | -protected: | |
| 44 | - int maxItems; | |
| 45 | 39 | }; |
| 46 | 40 | |
| 47 | 41 | // For 1 - 1 boundaries, a buffer class with a single shared buffer, a mutex |
| ... | ... | @@ -49,7 +43,7 @@ protected: |
| 49 | 43 | class SingleBuffer : public SharedBuffer |
| 50 | 44 | { |
| 51 | 45 | public: |
| 52 | - SingleBuffer(unsigned _maxItems = 20000) : SharedBuffer(_maxItems) { no_input = false; } | |
| 46 | + SingleBuffer() { no_input = false; } | |
| 53 | 47 | |
| 54 | 48 | void stoppedInput() |
| 55 | 49 | { |
| ... | ... | @@ -70,14 +64,8 @@ public: |
| 70 | 64 | { |
| 71 | 65 | QMutexLocker bufferLock(&bufferGuard); |
| 72 | 66 | |
| 73 | - // If the buffer is too full, wait for space to become available | |
| 74 | - if (buffer.size() >= maxItems) { | |
| 75 | - availableOutputSpace.wait(&bufferGuard); | |
| 76 | - } | |
| 77 | - | |
| 78 | 67 | buffer.append(input); |
| 79 | 68 | |
| 80 | - // Wait for certain # of items? | |
| 81 | 69 | availableInput.wakeOne(); |
| 82 | 70 | } |
| 83 | 71 | |
| ... | ... | @@ -99,20 +87,114 @@ public: |
| 99 | 87 | |
| 100 | 88 | FrameData * output = buffer.first(); |
| 101 | 89 | buffer.removeFirst(); |
| 102 | - if (buffer.size() < maxItems / 2) | |
| 103 | - availableOutputSpace.wakeAll(); | |
| 104 | 90 | return output; |
| 105 | 91 | } |
| 106 | 92 | |
| 107 | 93 | private: |
| 108 | 94 | QMutex bufferGuard; |
| 109 | 95 | QWaitCondition availableInput; |
| 110 | - QWaitCondition availableOutputSpace; | |
| 111 | 96 | bool no_input; |
| 112 | 97 | |
| 113 | 98 | QList<FrameData *> buffer; |
| 114 | 99 | }; |
| 115 | 100 | |
| 101 | +// For 1 - 1 boundaries, a double buffering scheme | |
| 102 | +// Producer/consumer read/write from separate buffers, and switch if their | |
| 103 | +// buffer runs out/overflows. Synchronization is handled by a read/write lock | |
| 104 | +// threads are "reading" if they are adding to/removing from their individual | |
| 105 | +// buffer, and writing if they access or swap with the other buffer. | |
| 106 | +class DoubleBuffer : public SharedBuffer | |
| 107 | +{ | |
| 108 | +public: | |
| 109 | + DoubleBuffer() | |
| 110 | + { | |
| 111 | + inputBuffer = &buffer1; | |
| 112 | + outputBuffer = &buffer2; | |
| 113 | + } | |
| 114 | + | |
| 115 | + void stoppedInput() | |
| 116 | + { | |
| 117 | + QWriteLocker bufferLock(&bufferGuard); | |
| 118 | + no_input = true; | |
| 119 | + // Release anything waiting for input items. | |
| 120 | + availableInput.wakeAll(); | |
| 121 | + } | |
| 122 | + | |
| 123 | + // There will be more input | |
| 124 | + void startInput() | |
| 125 | + { | |
| 126 | + QWriteLocker bufferLock(&bufferGuard); | |
| 127 | + no_input = false; | |
| 128 | + } | |
| 129 | + | |
| 130 | + // called from the producer thread | |
| 131 | + void addItem(FrameData * input) | |
| 132 | + { | |
| 133 | + QReadLocker readLock(&bufferGuard); | |
| 134 | + inputBuffer->append(input); | |
| 135 | + availableInput.wakeOne(); | |
| 136 | + } | |
| 137 | + | |
| 138 | + // Called from the consumer thread | |
| 139 | + FrameData * getItem() { | |
| 140 | + QReadLocker readLock(&bufferGuard); | |
| 141 | + | |
| 142 | + // There is something for us to get | |
| 143 | + if (!outputBuffer->empty()) { | |
| 144 | + FrameData * output = outputBuffer->first(); | |
| 145 | + outputBuffer->removeFirst(); | |
| 146 | + return output; | |
| 147 | + } | |
| 148 | + | |
| 149 | + // Outputbuffer is empty, try to swap with the input buffer, we need a | |
| 150 | + // write lock to do that. | |
| 151 | + readLock.unlock(); | |
| 152 | + QWriteLocker writeLock(&bufferGuard); | |
| 153 | + | |
| 154 | + // Nothing on the input buffer either? | |
| 155 | + if (inputBuffer->empty()) { | |
| 156 | + // If nothing else is coming, return null | |
| 157 | + if (no_input) | |
| 158 | + return NULL; | |
| 159 | + //otherwise, wait on the input buffer | |
| 160 | + availableInput.wait(&bufferGuard); | |
| 161 | + // Did we get woken up because no more input is coming? if so | |
| 162 | + // we're done here | |
| 163 | + if (no_input && inputBuffer->empty()) | |
| 164 | + return NULL; | |
| 165 | + } | |
| 166 | + | |
| 167 | + // input buffer is non-empty, so swap the buffers | |
| 168 | + std::swap(inputBuffer, outputBuffer); | |
| 169 | + | |
| 170 | + // Return a frame | |
| 171 | + FrameData * output = outputBuffer->first(); | |
| 172 | + outputBuffer->removeFirst(); | |
| 173 | + return output; | |
| 174 | + } | |
| 175 | + | |
| 176 | +private: | |
| 177 | + // The read-write lock. The thread adding to this buffer can add | |
| 178 | + // to the current input buffer if it has a read lock. The thread | |
| 179 | + // removing from this buffer can remove things from the current | |
| 180 | + // output buffer if it has a read lock, or swap the buffers if it | |
| 181 | + // has a write lock. | |
| 182 | + // Checking/modifying no_input requires a write lock. | |
| 183 | + QReadWriteLock bufferGuard; | |
| 184 | + QWaitCondition availableInput; | |
| 185 | + bool no_input; | |
| 186 | + | |
| 187 | + // The buffer that is currently being added to | |
| 188 | + QList<FrameData *> * inputBuffer; | |
| 189 | + // The buffer that is currently being removed from | |
| 190 | + QList<FrameData *> * outputBuffer; | |
| 191 | + | |
| 192 | + // The buffers pointed at by inputBuffer/outputBuffer | |
| 193 | + QList<FrameData *> buffer1; | |
| 194 | + QList<FrameData *> buffer2; | |
| 195 | +}; | |
| 196 | + | |
| 197 | + | |
| 116 | 198 | // Interface for sequentially getting data from some data source. |
| 117 | 199 | // Initialized off of a template, can represent a video file (stored in the template's filename) |
| 118 | 200 | // or a set of images already loaded into memory stored as multiple matrices in an input template. |
| ... | ... | @@ -166,7 +248,7 @@ public: |
| 166 | 248 | virtual bool getNext(FrameData & input) = 0; |
| 167 | 249 | |
| 168 | 250 | protected: |
| 169 | - SingleBuffer allFrames; | |
| 251 | + DoubleBuffer allFrames; | |
| 170 | 252 | }; |
| 171 | 253 | |
| 172 | 254 | // Read a video frame by frame using cv::VideoCapture |
| ... | ... | @@ -526,7 +608,7 @@ public: |
| 526 | 608 | } |
| 527 | 609 | |
| 528 | 610 | // buffer 0 -- output buffer for the read stage |
| 529 | - sharedBuffers.append(new SingleBuffer()); | |
| 611 | + sharedBuffers.append(new DoubleBuffer()); | |
| 530 | 612 | readStage.outputBuffer = sharedBuffers.last(); |
| 531 | 613 | readStage.stage_id = 0; |
| 532 | 614 | |
| ... | ... | @@ -542,7 +624,7 @@ public: |
| 542 | 624 | processingStages.last()->inputBuffer = sharedBuffers[lastBufferIdx]; |
| 543 | 625 | lastBufferIdx++; |
| 544 | 626 | |
| 545 | - sharedBuffers.append(new SingleBuffer()); | |
| 627 | + sharedBuffers.append(new DoubleBuffer()); | |
| 546 | 628 | processingStages.last()->outputBuffer = sharedBuffers.last(); |
| 547 | 629 | processingStages.last()->transform = transforms[i]; |
| 548 | 630 | } | ... | ... |