Skip to content

Commit 88f5c73

Browse files
authored
Add MPFAsyncVideoCapture (#100)
1 parent cbd100e commit 88f5c73

23 files changed

Lines changed: 738 additions & 175 deletions

detection/api/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ set(SOURCE_FILES
5252
include/MPFVideoCapture.h
5353
src/MPFVideoCapture.cpp
5454

55+
include/MPFAsyncVideoCapture.h
56+
src/MPFAsyncVideoCapture.cpp
57+
5558
include/frame_transformers/SearchRegion.h
5659
src/frame_transformers/SearchRegion.cpp
5760

detection/api/include/BlockingQueue.h

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,66 +51,128 @@ class BlockingQueue {
5151

5252
public:
5353
explicit BlockingQueue(int max_size=-1)
54-
: max_size_(max_size), halt_(false)
54+
: max_size_(max_size)
55+
, halt_(false)
56+
, adding_complete_(false)
5557
{
5658
}
5759

60+
/**
61+
* Adds a copy of item to the queue. Blocks if queue is already full.
62+
* @param item object to be copied in to queue
63+
*/
5864
void push(const T& item) {
5965
auto lock = acquire_lock();
60-
await_free_space(lock);
66+
wait_until_can_add(lock);
6167
queue_.push(item);
6268
cond_.notify_all();
6369
}
6470

71+
/**
72+
* Moves item in to the queue. Blocks if queue is already full.
73+
* @param item object to be moved in to queue
74+
*/
6575
void push(T&& item) {
6676
auto lock = acquire_lock();
67-
await_free_space(lock);
77+
wait_until_can_add(lock);
6878
queue_.push(std::move(item));
6979
cond_.notify_all();
7080
}
7181

82+
/**
83+
* Constructs an instance of T in-place in the queue. Blocks if queue is already full.
84+
* @param args Arguments to be passed to T's constructor
85+
*/
7286
template <typename... Args>
7387
void emplace(Args&&... args) {
7488
auto lock = acquire_lock();
75-
await_free_space(lock);
89+
wait_until_can_add(lock);
7690
queue_.emplace(std::forward<Args>(args)...);
7791
cond_.notify_all();
7892
}
7993

94+
95+
/**
96+
* Moves an item out of the queue and returns it. Blocks if the queue is empty.
97+
* @return The object from the front of the queue
98+
*/
8099
T pop() {
81100
auto lock = acquire_lock();
82-
await_non_empty(lock);
101+
wait_until_can_remove(lock);
83102
T result = std::move(queue_.front());
84103
queue_.pop();
85104
cond_.notify_all();
86105
return result;
87106
}
88107

108+
/**
109+
* Indicates that both producers and consumers should stop processing even if there are items
110+
* in the queue. Any future adds or removes will cause a QueueHaltedException to be thrown.
111+
* This can be used to prevent one side from blocking indefinitely when the other side fails.
112+
*/
89113
void halt() {
90114
auto lock = acquire_lock();
91115
halt_ = true;
92116
cond_.notify_all();
93117
}
94118

119+
/**
120+
* Indicates that no more items will be added to the queue. Consumers should finish processing
121+
* items already in the queue. Any attempt to add items after this is called will cause a
122+
* QueueHaltedException to be thrown. This can be used to prevent consumers from blocking
123+
* indefinitely when a producer finishes.
124+
*
125+
* Loosely based on C#'s BlockingCollection<T>.CompleteAdding method
126+
* (https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.blockingcollection-1.completeadding?view=net-5.0#System_Collections_Concurrent_BlockingCollection_1_CompleteAdding).
127+
*/
128+
void complete_adding() {
129+
auto lock = acquire_lock();
130+
adding_complete_ = true;
131+
cond_.notify_all();
132+
}
133+
134+
/**
135+
* @return true if adding complete. Queue may or may not be empty.
136+
*/
137+
bool is_adding_complete() {
138+
auto lock = acquire_lock();
139+
return halt_ || adding_complete_;
140+
}
141+
142+
/**
143+
* The queue is "completed" if it has been halted or if a producer called complete_adding()
144+
* and the remaining items have all been removed from the queue.
145+
* @return true if items should not be added or removed from queue.
146+
*/
147+
bool is_completed() {
148+
auto lock = acquire_lock();
149+
return halt_ || (adding_complete_ && queue_.empty());
150+
}
151+
152+
95153
private:
96154
int max_size_;
97155
std::queue<T> queue_;
98156
std::mutex mutex_;
99157
std::condition_variable cond_;
100158
bool halt_;
159+
bool adding_complete_;
160+
101161

102-
void await_non_empty(std::unique_lock<std::mutex> &lock) {
103-
cond_.wait(lock, [this] { return (halt_ || !queue_.empty()); });
104-
if (halt_) {
162+
void wait_until_can_remove(std::unique_lock<std::mutex> &lock) {
163+
cond_.wait(lock, [this] { return halt_ || adding_complete_ || !queue_.empty(); });
164+
if (halt_ || (queue_.empty() && adding_complete_)) {
105165
throw QueueHaltedException();
106166
}
107167
}
108168

109-
void await_free_space(std::unique_lock<std::mutex> &lock) {
169+
void wait_until_can_add(std::unique_lock<std::mutex> &lock) {
110170
if (max_size_ > 0) {
111-
cond_.wait(lock, [this] { return (halt_ || queue_.size() < max_size_); });
171+
cond_.wait(lock, [this] {
172+
return halt_ || adding_complete_ || queue_.size() < max_size_;
173+
});
112174
}
113-
if (halt_) {
175+
if (halt_ || adding_complete_) {
114176
throw QueueHaltedException();
115177
}
116178
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/******************************************************************************
2+
* NOTICE *
3+
* *
4+
* This software (or technical data) was produced for the U.S. Government *
5+
* under contract, and is subject to the Rights in Data-General Clause *
6+
* 52.227-14, Alt. IV (DEC 2007). *
7+
* *
8+
* Copyright 2021 The MITRE Corporation. All Rights Reserved. *
9+
******************************************************************************/
10+
11+
/******************************************************************************
12+
* Copyright 2021 The MITRE Corporation *
13+
* *
14+
* Licensed under the Apache License, Version 2.0 (the "License"); *
15+
* you may not use this file except in compliance with the License. *
16+
* You may obtain a copy of the License at *
17+
* *
18+
* http://www.apache.org/licenses/LICENSE-2.0 *
19+
* *
20+
* Unless required by applicable law or agreed to in writing, software *
21+
* distributed under the License is distributed on an "AS IS" BASIS, *
22+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
23+
* See the License for the specific language governing permissions and *
24+
* limitations under the License. *
25+
******************************************************************************/
26+
27+
#ifndef OPENMPF_CPP_COMPONENT_SDK_MPFASYNCVIDEOCAPTURE_H
28+
#define OPENMPF_CPP_COMPONENT_SDK_MPFASYNCVIDEOCAPTURE_H
29+
30+
#include <future>
31+
#include <string>
32+
33+
#include <opencv2/core.hpp>
34+
35+
#include "MPFDetectionComponent.h"
36+
#include "MPFVideoCapture.h"
37+
#include "BlockingQueue.h"
38+
39+
namespace MPF { namespace COMPONENT {
40+
41+
struct MPFFrame {
42+
public:
43+
// Include index field because we can't query MPFVideoCapture to get the current frame
44+
// position. This is because MPFVideoCapture will likely be a few frames ahead by the time
45+
// it is queried.
46+
int index;
47+
cv::Mat data;
48+
49+
explicit MPFFrame(int index = -1, cv::Mat data = {});
50+
51+
bool isValid() const;
52+
53+
explicit operator bool() const;
54+
};
55+
56+
57+
/**
58+
* Reads frames from a regular MPFVideoCapture on a background thread. Frames are buffered
59+
* in a fixed sized BlockingQueue. This class intentionally exposes a subset of the
60+
* MPFVideoCapture functionality. Functionality that would impossible or difficult to write in
61+
* a thread-safe manner has been omitted. Frame transformers, frame filters, and feed forward
62+
* are all supported.
63+
*/
64+
class MPFAsyncVideoCapture {
65+
public:
66+
explicit MPFAsyncVideoCapture(const MPFVideoJob &videoJob,
67+
bool enableFrameTransformers=true,
68+
bool enableFrameFiltering=true);
69+
70+
explicit MPFAsyncVideoCapture(std::string videoPath, int frameQueueSize=4);
71+
72+
~MPFAsyncVideoCapture();
73+
74+
MPFFrame Read();
75+
76+
void ReverseTransform(MPFVideoTrack &videoTrack) const;
77+
78+
/**
79+
* @return An object that can do the reverse transform even after MPFAsyncVideoCapture has
80+
* been destroyed
81+
*/
82+
ReverseTransformer GetReverseTransformer() const;
83+
84+
int GetFrameCount() const;
85+
86+
double GetFrameRate() const;
87+
88+
cv::Size GetFrameSize() const;
89+
90+
cv::Size GetOriginalFrameSize() const;
91+
92+
93+
private:
94+
BlockingQueue<MPFFrame> frameQueue_;
95+
96+
// Fields for properties of the video that don't change as it is being read.
97+
// We can't just query the underlying video capture on the fly because it is being used by
98+
// the frameReader thread. These fields get set prior to handing the video capture
99+
// over to the frameReader thread.
100+
int frameCount_;
101+
double frameRate_;
102+
cv::Size frameSize_;
103+
cv::Size originalFrameSize_;
104+
105+
ReverseTransformer reverseTransformer_;
106+
107+
std::shared_future<void> doneReadingFuture_;
108+
109+
MPFAsyncVideoCapture(MPFVideoCapture&& videoCapture, int frameQueueSize);
110+
};
111+
}}
112+
113+
114+
115+
#endif //OPENMPF_CPP_COMPONENT_SDK_MPFASYNCVIDEOCAPTURE_H

0 commit comments

Comments
 (0)