Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cscore] Sink: add ability to get most recent frame instead of waiting #7572

Merged
merged 4 commits into from
Dec 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cscore/src/main/java/edu/wpi/first/cscore/CvSink.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ private int getCVFormat(PixelFormat pixelFormat) {
* Create a sink for accepting OpenCV images. grabFrame() must be called on the created sink to
* get each new image.
*
* @param name Source name (arbitrary unique identifier)
* @param name Sink name (arbitrary unique identifier)
* @param pixelFormat Source pixel format
*/
public CvSink(String name, PixelFormat pixelFormat) {
Expand Down
28 changes: 27 additions & 1 deletion cscore/src/main/native/cpp/RawSinkImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ uint64_t RawSinkImpl::GrabFrame(WPI_RawFrame& image) {
}

uint64_t RawSinkImpl::GrabFrame(WPI_RawFrame& image, double timeout) {
return GrabFrame(image, timeout, 0);
}

uint64_t RawSinkImpl::GrabFrame(WPI_RawFrame& image, double timeout,
uint64_t lastFrameTime) {
SetEnabled(true);

auto source = GetSource();
Expand All @@ -72,7 +77,7 @@ uint64_t RawSinkImpl::GrabFrame(WPI_RawFrame& image, double timeout) {
return 0;
}

auto frame = source->GetNextFrame(timeout); // blocks
auto frame = source->GetNextFrame(timeout, lastFrameTime); // blocks
if (!frame) {
// Bad frame; sleep for 20 ms so we don't consume all processor time.
std::this_thread::sleep_for(std::chrono::milliseconds(20));
Expand Down Expand Up @@ -183,6 +188,18 @@ uint64_t GrabSinkFrameTimeout(CS_Sink sink, WPI_RawFrame& image, double timeout,
return static_cast<RawSinkImpl&>(*data->sink).GrabFrame(image, timeout);
}

uint64_t GrabSinkFrameTimeoutLastTime(CS_Sink sink, WPI_RawFrame& image,
double timeout, uint64_t lastFrameTime,
CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return 0;
}
return static_cast<RawSinkImpl&>(*data->sink)
.GrabFrame(image, timeout, lastFrameTime);
}

} // namespace cs

extern "C" {
Expand All @@ -209,4 +226,13 @@ uint64_t CS_GrabRawSinkFrameTimeout(CS_Sink sink, struct WPI_RawFrame* image,
return cs::GrabSinkFrameTimeout(sink, *image, timeout, status);
}

uint64_t CS_GrabRawSinkFrameTimeoutWithFrameTime(CS_Sink sink,
struct WPI_RawFrame* image,
double timeout,
uint64_t lastFrameTime,
CS_Status* status) {
return cs::GrabSinkFrameTimeoutLastTime(sink, *image, timeout, lastFrameTime,
status);
}

} // extern "C"
5 changes: 5 additions & 0 deletions cscore/src/main/native/cpp/RawSinkImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,15 @@ class RawSinkImpl : public SinkImpl {

uint64_t GrabFrame(WPI_RawFrame& frame);
uint64_t GrabFrame(WPI_RawFrame& frame, double timeout);
// Wait for a frame with a time other than lastFrameTime
uint64_t GrabFrame(WPI_RawFrame& frame, double timeout,
uint64_t lastFrameTime);

private:
void ThreadMain();

// Copies the image from incomingFrame into rawFrame, converting where
// necessary to the resolution of rawFrame
uint64_t GrabFrameImpl(WPI_RawFrame& rawFrame, Frame& incomingFrame);

std::atomic_bool m_active; // set to false to terminate threads
Expand Down
11 changes: 8 additions & 3 deletions cscore/src/main/native/cpp/SourceImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,17 @@ Frame SourceImpl::GetNextFrame() {
return m_frame;
}

Frame SourceImpl::GetNextFrame(double timeout) {
Frame SourceImpl::GetNextFrame(double timeout, Frame::Time lastFrameTime) {
std::unique_lock lock{m_frameMutex};
auto oldTime = m_frame.GetTime();

if (lastFrameTime == 0) {
lastFrameTime = m_frame.GetTime();
}

// Wait unitl m_frame has a timestamp other than lastFrameTime
if (!m_frameCv.wait_for(
lock, std::chrono::milliseconds(static_cast<int>(timeout * 1000)),
[=, this] { return m_frame.GetTime() != oldTime; })) {
[=, this] { return m_frame.GetTime() != lastFrameTime; })) {
m_frame = Frame{*this, "timed out getting frame", wpi::Now()};
}
return m_frame;
Expand Down
3 changes: 2 additions & 1 deletion cscore/src/main/native/cpp/SourceImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ class SourceImpl : public PropertyContainer {

// Blocking function that waits for the next frame and returns it (with
// timeout in seconds). If timeout expires, returns empty frame.
Frame GetNextFrame(double timeout);
// If lastFrameTime==0, uses m_frame.GetTime() for lastFrameTime
Frame GetNextFrame(double timeout, Frame::Time lastFrameTime = 0);

// Force a wakeup of all GetNextFrame() callers by sending an empty frame.
void Wakeup();
Expand Down
40 changes: 40 additions & 0 deletions cscore/src/main/native/include/cscore_cv.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,27 @@ class CvSink : public ImageSink {
[[nodiscard]]
uint64_t GrabFrameNoTimeoutDirect(cv::Mat& image);

/**
* Wait for the next frame and get the image.
* Times out (returning 0) after timeout seconds.
* The provided image will have the pixelFormat this class was constructed
* with. The data is backed by data in the CvSink. It will be invalidated by
* any grabFrame*() call on the sink.
*
* <p>If lastFrameTime is provided and non-zero, the sink will fill image with
* the first frame from the source that is not equal to lastFrameTime. If
* lastFrameTime is zero, the time of the current frame owned by the CvSource
* is used, and this function will block until the connected CvSource provides
* a new frame.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameDirectLastTime(cv::Mat& image, uint64_t lastFrameTime,
double timeout = 0.225);

private:
constexpr int GetCvFormat(WPI_PixelFormat pixelFormat);

Expand Down Expand Up @@ -365,6 +386,25 @@ inline uint64_t CvSink::GrabFrameNoTimeoutDirect(cv::Mat& image) {
return timestamp;
}

inline uint64_t CvSink::GrabFrameDirectLastTime(cv::Mat& image,
uint64_t lastFrameTime,
double timeout) {
rawFrame.height = 0;
rawFrame.width = 0;
rawFrame.stride = 0;
rawFrame.pixelFormat = pixelFormat;
auto timestamp = GrabSinkFrameTimeoutLastTime(m_handle, rawFrame, timeout,
lastFrameTime, &m_status);
if (m_status != CS_OK) {
return 0;
}
image =
cv::Mat{rawFrame.height, rawFrame.width,
GetCvFormat(static_cast<WPI_PixelFormat>(rawFrame.pixelFormat)),
rawFrame.data, static_cast<size_t>(rawFrame.stride)};
return timestamp;
}

} // namespace cs

#endif // CSCORE_CSCORE_CV_H_
34 changes: 34 additions & 0 deletions cscore/src/main/native/include/cscore_raw.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ uint64_t CS_GrabRawSinkFrame(CS_Sink sink, struct WPI_RawFrame* rawImage,
CS_Status* status);
uint64_t CS_GrabRawSinkFrameTimeout(CS_Sink sink, struct WPI_RawFrame* rawImage,
double timeout, CS_Status* status);
uint64_t CS_GrabRawSinkFrameTimeoutWithFrameTime(CS_Sink sink,
struct WPI_RawFrame* rawImage,
double timeout,
uint64_t lastFrameTime,
CS_Status* status);

CS_Sink CS_CreateRawSink(const struct WPI_String* name, CS_Bool isCv,
CS_Status* status);
Expand Down Expand Up @@ -67,6 +72,9 @@ void PutSourceFrame(CS_Source source, const WPI_RawFrame& image,
uint64_t GrabSinkFrame(CS_Sink sink, WPI_RawFrame& image, CS_Status* status);
uint64_t GrabSinkFrameTimeout(CS_Sink sink, WPI_RawFrame& image, double timeout,
CS_Status* status);
uint64_t GrabSinkFrameTimeoutLastTime(CS_Sink sink, WPI_RawFrame& image,
double timeout, uint64_t lastFrameTime,
CS_Status* status);

/**
* A source for user code to provide video frames as raw bytes.
Expand Down Expand Up @@ -163,6 +171,24 @@ class RawSink : public ImageSink {
*/
[[nodiscard]]
uint64_t GrabFrameNoTimeout(wpi::RawFrame& image) const;

/**
* Wait for the next frame and get the image. May block forever.
* The provided image will have three 8-bit channels stored in BGR order.
*
* <p>If lastFrameTime is provided and non-zero, the sink will fill image with
* the first frame from the source that is not equal to lastFrameTime. If
* lastFrameTime is zero, the time of the current frame owned by the CvSource
* is used, and this function will block until the connected CvSource provides
* a new frame.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameLastTime(wpi::RawFrame& image, uint64_t lastFrameTime,
double timeout = 0.225) const;
};

inline RawSource::RawSource(std::string_view name, const VideoMode& mode) {
Expand Down Expand Up @@ -199,6 +225,14 @@ inline uint64_t RawSink::GrabFrameNoTimeout(wpi::RawFrame& image) const {
m_status = 0;
return GrabSinkFrame(m_handle, image, &m_status);
}

inline uint64_t RawSink::GrabFrameLastTime(wpi::RawFrame& image,
uint64_t lastFrameTime,
double timeout) const {
m_status = 0;
return GrabSinkFrameTimeoutLastTime(m_handle, image, timeout, lastFrameTime,
&m_status);
}
/** @} */

} // namespace cs
Expand Down
Loading