Commit bb99fd8afdb08083c90876ab5f0d6d0e32f0c6e8
Merge pull request #30 from biometrics/stream_processing
some updates to buffer management
Showing
1 changed file
with
194 additions
and
68 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 = 200) : 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 = 20) : 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,61 +87,176 @@ 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 | |
| 116 | -// Interface for sequentially getting data from some data source. | |
| 117 | -// Initialized off of a template, can represent a video file (stored in the template's filename) | |
| 118 | -// or a set of images already loaded into memory stored as multiple matrices in an input template. | |
| 119 | -class DataSource | |
| 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 | |
| 120 | 107 | { |
| 121 | 108 | public: |
| 122 | - DataSource() {} | |
| 123 | - virtual ~DataSource() {} | |
| 109 | + DoubleBuffer() | |
| 110 | + { | |
| 111 | + inputBuffer = &buffer1; | |
| 112 | + outputBuffer = &buffer2; | |
| 113 | + } | |
| 124 | 114 | |
| 125 | - virtual FrameData * getNext() = 0; | |
| 126 | - virtual void close() = 0; | |
| 127 | - virtual bool open(Template & input) = 0; | |
| 128 | - virtual bool isOpen() = 0; | |
| 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; | |
| 129 | 195 | }; |
| 130 | 196 | |
| 131 | -// Read a video frame by frame using cv::VideoCapture | |
| 132 | -class VideoDataSource : public DataSource | |
| 197 | + | |
| 198 | +// Interface for sequentially getting data from some data source. | |
| 199 | +// Initialized off of a template, can represent a video file (stored in the template's filename) | |
| 200 | +// or a set of images already loaded into memory stored as multiple matrices in an input template. | |
| 201 | +class DataSource | |
| 133 | 202 | { |
| 134 | 203 | public: |
| 135 | - VideoDataSource() {} | |
| 136 | - | |
| 137 | - FrameData * getNext() | |
| 204 | + DataSource(int maxFrames=100) | |
| 138 | 205 | { |
| 139 | - if (!isOpen()) | |
| 140 | - return NULL; | |
| 206 | + for (int i=0; i < maxFrames;i++) | |
| 207 | + { | |
| 208 | + allFrames.addItem(new FrameData()); | |
| 209 | + } | |
| 210 | + allFrames.startInput(); | |
| 211 | + } | |
| 141 | 212 | |
| 142 | - FrameData * output = new FrameData(); | |
| 143 | - output->data.append(Template(basis.file)); | |
| 144 | - output->data.last().append(cv::Mat()); | |
| 213 | + virtual ~DataSource() | |
| 214 | + { | |
| 215 | + allFrames.stoppedInput(); | |
| 216 | + while (true) | |
| 217 | + { | |
| 218 | + FrameData * frame = allFrames.getItem(); | |
| 219 | + if (frame == NULL) | |
| 220 | + break; | |
| 221 | + delete frame; | |
| 222 | + } | |
| 223 | + } | |
| 145 | 224 | |
| 146 | - output->sequenceNumber = next_idx; | |
| 147 | - next_idx++; | |
| 225 | + FrameData * getFrame() | |
| 226 | + { | |
| 227 | + FrameData * aFrame = allFrames.getItem(); | |
| 228 | + aFrame->data.clear(); | |
| 229 | + aFrame->sequenceNumber = -1; | |
| 148 | 230 | |
| 149 | - bool res = video.read(output->data.last().last()); | |
| 231 | + bool res = getNext(*aFrame); | |
| 150 | 232 | if (!res) { |
| 151 | - delete output; | |
| 233 | + allFrames.addItem(aFrame); | |
| 152 | 234 | return NULL; |
| 153 | 235 | } |
| 154 | - return output; | |
| 236 | + return aFrame; | |
| 155 | 237 | } |
| 156 | 238 | |
| 239 | + void returnFrame(FrameData * inputFrame) | |
| 240 | + { | |
| 241 | + allFrames.addItem(inputFrame); | |
| 242 | + } | |
| 243 | + | |
| 244 | + virtual void close() = 0; | |
| 245 | + virtual bool open(Template & output) = 0; | |
| 246 | + virtual bool isOpen() = 0; | |
| 247 | + | |
| 248 | + virtual bool getNext(FrameData & input) = 0; | |
| 249 | + | |
| 250 | +protected: | |
| 251 | + DoubleBuffer allFrames; | |
| 252 | +}; | |
| 253 | + | |
| 254 | +// Read a video frame by frame using cv::VideoCapture | |
| 255 | +class VideoDataSource : public DataSource | |
| 256 | +{ | |
| 257 | +public: | |
| 258 | + VideoDataSource(int maxFrames) : DataSource(maxFrames) {} | |
| 259 | + | |
| 157 | 260 | bool open(Template &input) |
| 158 | 261 | { |
| 159 | 262 | next_idx = 0; |
| ... | ... | @@ -167,6 +270,24 @@ public: |
| 167 | 270 | void close() { video.release(); } |
| 168 | 271 | |
| 169 | 272 | private: |
| 273 | + bool getNext(FrameData & output) | |
| 274 | + { | |
| 275 | + if (!isOpen()) | |
| 276 | + return false; | |
| 277 | + | |
| 278 | + output.data.append(Template(basis.file)); | |
| 279 | + output.data.last().append(cv::Mat()); | |
| 280 | + | |
| 281 | + output.sequenceNumber = next_idx; | |
| 282 | + next_idx++; | |
| 283 | + | |
| 284 | + bool res = video.read(output.data.last().last()); | |
| 285 | + if (!res) { | |
| 286 | + return false; | |
| 287 | + } | |
| 288 | + return true; | |
| 289 | + } | |
| 290 | + | |
| 170 | 291 | cv::VideoCapture video; |
| 171 | 292 | Template basis; |
| 172 | 293 | int next_idx; |
| ... | ... | @@ -177,21 +298,9 @@ private: |
| 177 | 298 | class TemplateDataSource : public DataSource |
| 178 | 299 | { |
| 179 | 300 | public: |
| 180 | - TemplateDataSource() { current_idx = INT_MAX; } | |
| 181 | - | |
| 182 | - FrameData * getNext() | |
| 301 | + TemplateDataSource(int maxFrames) : DataSource(maxFrames) | |
| 183 | 302 | { |
| 184 | - if (!isOpen()) | |
| 185 | - return NULL; | |
| 186 | - | |
| 187 | - FrameData * output = new FrameData(); | |
| 188 | - output->data.append(basis[current_idx]); | |
| 189 | - current_idx++; | |
| 190 | - | |
| 191 | - output->sequenceNumber = next_sequence; | |
| 192 | - next_sequence++; | |
| 193 | - | |
| 194 | - return output; | |
| 303 | + current_idx = INT_MAX; | |
| 195 | 304 | } |
| 196 | 305 | |
| 197 | 306 | bool open(Template &input) |
| ... | ... | @@ -211,6 +320,20 @@ public: |
| 211 | 320 | } |
| 212 | 321 | |
| 213 | 322 | private: |
| 323 | + bool getNext(FrameData & output) | |
| 324 | + { | |
| 325 | + if (!isOpen()) | |
| 326 | + return false; | |
| 327 | + | |
| 328 | + output.data.append(basis[current_idx]); | |
| 329 | + current_idx++; | |
| 330 | + | |
| 331 | + output.sequenceNumber = next_sequence; | |
| 332 | + next_sequence++; | |
| 333 | + | |
| 334 | + return true; | |
| 335 | + } | |
| 336 | + | |
| 214 | 337 | Template basis; |
| 215 | 338 | int current_idx; |
| 216 | 339 | int next_sequence; |
| ... | ... | @@ -232,12 +355,6 @@ public: |
| 232 | 355 | close(); |
| 233 | 356 | } |
| 234 | 357 | |
| 235 | - FrameData * getNext() | |
| 236 | - { | |
| 237 | - if (!isOpen()) return NULL; | |
| 238 | - return actualSource->getNext(); | |
| 239 | - } | |
| 240 | - | |
| 241 | 358 | void close() |
| 242 | 359 | { |
| 243 | 360 | if (actualSource) { |
| ... | ... | @@ -253,13 +370,13 @@ public: |
| 253 | 370 | bool open_res = false; |
| 254 | 371 | // Input has no matrices? Its probably a video that hasn't been loaded yet |
| 255 | 372 | if (input.empty()) { |
| 256 | - actualSource = new VideoDataSource(); | |
| 373 | + actualSource = new VideoDataSource(0); | |
| 257 | 374 | open_res = actualSource->open(input); |
| 258 | 375 | qDebug("created video resource status %d", open_res); |
| 259 | 376 | } |
| 260 | 377 | else { |
| 261 | 378 | // create frame dealer |
| 262 | - actualSource = new TemplateDataSource(); | |
| 379 | + actualSource = new TemplateDataSource(0); | |
| 263 | 380 | open_res = actualSource->open(input); |
| 264 | 381 | } |
| 265 | 382 | if (!isOpen()) { |
| ... | ... | @@ -274,6 +391,11 @@ public: |
| 274 | 391 | |
| 275 | 392 | protected: |
| 276 | 393 | DataSource * actualSource; |
| 394 | + bool getNext(FrameData & output) | |
| 395 | + { | |
| 396 | + return actualSource->getNext(output); | |
| 397 | + } | |
| 398 | + | |
| 277 | 399 | }; |
| 278 | 400 | |
| 279 | 401 | class ProcessingStage : public QRunnable |
| ... | ... | @@ -347,7 +469,8 @@ public: |
| 347 | 469 | { |
| 348 | 470 | forever |
| 349 | 471 | { |
| 350 | - FrameData * aFrame = dataSource.getNext(); | |
| 472 | + //FrameData * aFrame = dataSource.getNext(); | |
| 473 | + FrameData * aFrame = dataSource.getFrame(); | |
| 351 | 474 | if (aFrame == NULL) |
| 352 | 475 | break; |
| 353 | 476 | outputBuffer->addItem(aFrame); |
| ... | ... | @@ -367,6 +490,7 @@ public: |
| 367 | 490 | private: |
| 368 | 491 | TemplateList collectedOutput; |
| 369 | 492 | public: |
| 493 | + DataSource * data; | |
| 370 | 494 | void run() |
| 371 | 495 | { |
| 372 | 496 | forever |
| ... | ... | @@ -377,7 +501,8 @@ public: |
| 377 | 501 | break; |
| 378 | 502 | // Just put the item on collectedOutput |
| 379 | 503 | collectedOutput.append(frame->data); |
| 380 | - delete frame; | |
| 504 | + // Return the frame to the input frame buffer | |
| 505 | + data->returnFrame(frame); | |
| 381 | 506 | } |
| 382 | 507 | this->markStop(); |
| 383 | 508 | } |
| ... | ... | @@ -483,7 +608,7 @@ public: |
| 483 | 608 | } |
| 484 | 609 | |
| 485 | 610 | // buffer 0 -- output buffer for the read stage |
| 486 | - sharedBuffers.append(new SingleBuffer()); | |
| 611 | + sharedBuffers.append(new DoubleBuffer()); | |
| 487 | 612 | readStage.outputBuffer = sharedBuffers.last(); |
| 488 | 613 | readStage.stage_id = 0; |
| 489 | 614 | |
| ... | ... | @@ -499,12 +624,13 @@ public: |
| 499 | 624 | processingStages.last()->inputBuffer = sharedBuffers[lastBufferIdx]; |
| 500 | 625 | lastBufferIdx++; |
| 501 | 626 | |
| 502 | - sharedBuffers.append(new SingleBuffer()); | |
| 627 | + sharedBuffers.append(new DoubleBuffer()); | |
| 503 | 628 | processingStages.last()->outputBuffer = sharedBuffers.last(); |
| 504 | 629 | processingStages.last()->transform = transforms[i]; |
| 505 | 630 | } |
| 506 | 631 | |
| 507 | 632 | collectionStage.inputBuffer = sharedBuffers.last(); |
| 633 | + collectionStage.data = &readStage.dataSource; | |
| 508 | 634 | collectionStage.stage_id = next_stage_id; |
| 509 | 635 | } |
| 510 | 636 | ... | ... |