summaryrefslogtreecommitdiff
path: root/src/ipa/raspberrypi/controller/pwl.cpp
blob: 130c820b559f2029342c2fe69f0beb7901bfbf6d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * Copyright (C) 2019, Raspberry Pi (Trading) Limited
 *
 * pwl.cpp - piecewise linear functions
 */

#include <cassert>
#include <stdexcept>

#include "pwl.hpp"

using namespace RPiController;

void Pwl::Read(boost::property_tree::ptree const &params)
{
	for (auto it = params.begin(); it != params.end(); it++) {
		double x = it->second.get_value<double>();
		assert(it == params.begin() || x > points_.back().x);
		it++;
		double y = it->second.get_value<double>();
		points_.push_back(Point(x, y));
	}
	assert(points_.size() >= 2);
}

void Pwl::Append(double x, double y, const double eps)
{
	if (points_.empty() || points_.back().x + eps < x)
		points_.push_back(Point(x, y));
}

void Pwl::Prepend(double x, double y, const double eps)
{
	if (points_.empty() || points_.front().x - eps > x)
		points_.insert(points_.begin(), Point(x, y));
}

Pwl::Interval Pwl::Domain() const
{
	return Interval(points_[0].x, points_[points_.size() - 1].x);
}

Pwl::Interval Pwl::Range() const
{
	double lo = points_[0].y, hi = lo;
	for (auto &p : points_)
		lo = std::min(lo, p.y), hi = std::max(hi, p.y);
	return Interval(lo, hi);
}

bool Pwl::Empty() const
{
	return points_.empty();
}

double Pwl::Eval(double x, int *span_ptr, bool update_span) const
{
	int span = findSpan(x, span_ptr && *span_ptr != -1
				       ? *span_ptr
				       : points_.size() / 2 - 1);
	if (span_ptr && update_span)
		*span_ptr = span;
	return points_[span].y +
	       (x - points_[span].x) * (points_[span + 1].y - points_[span].y) /
		       (points_[span + 1].x - points_[span].x);
}

int Pwl::findSpan(double x, int span) const
{
	// Pwls are generally small, so linear search may well be faster than
	// binary, though could review this if large PWls start turning up.
	int last_span = points_.size() - 2;
	// some algorithms may call us with span pointing directly at the last
	// control point
	span = std::max(0, std::min(last_span, span));
	while (span < last_span && x >= points_[span + 1].x)
		span++;
	while (span && x < points_[span].x)
		span--;
	return span;
}

Pwl::PerpType Pwl::Invert(Point const &xy, Point &perp, int &span,
			  const double eps) const
{
	assert(span >= -1);
	bool prev_off_end = false;
	for (span = span + 1; span < (int)points_.size() - 1; span++) {
		Point span_vec = points_[span + 1] - points_[span];
		double t = ((xy - points_[span]) % span_vec) / span_vec.Len2();
		if (t < -eps) // off the start of this span
		{
			if (span == 0) {
				perp = points_[span];
				return PerpType::Start;
			} else if (prev_off_end) {
				perp = points_[span];
				return PerpType::Vertex;
			}
		} else if (t > 1 + eps) // off the end of this span
		{
			if (span == (int)points_.size() - 2) {
				perp = points_[span + 1];
				return PerpType::End;
			}
			prev_off_end = true;
		} else // a true perpendicular
		{
			perp = points_[span] + span_vec * t;
			return PerpType::Perpendicular;
		}
	}
	return PerpType::None;
}

Pwl Pwl::Inverse(bool *true_inverse, const double eps) const
{
	bool appended = false, prepended = false, neither = false;
	Pwl inverse;

	for (Point const &p : points_) {
		if (inverse.Empty())
			inverse.Append(p.y, p.x, eps);
		else if (std::abs(inverse.points_.back().x - p.y) <= eps ||
			 std::abs(inverse.points_.front().x - p.y) <= eps)
			/* do nothing */;
		else if (p.y > inverse.points_.back().x) {
			inverse.Append(p.y, p.x, eps);
			appended = true;
		} else if (p.y < inverse.points_.front().x) {
			inverse.Prepend(p.y, p.x, eps);
			prepended = true;
		} else
			neither = true;
	}

	// This is not a proper inverse if we found ourselves putting points
	// onto both ends of the inverse, or if there were points that couldn't
	// go on either.
	if (true_inverse)
		*true_inverse = !(neither || (appended && prepended));

	return inverse;
}

Pwl Pwl::Compose(Pwl const &other, const double eps) const
{
	double this_x = points_[0].x, this_y = points_[0].y;
	int this_span = 0, other_span = other.findSpan(this_y, 0);
	Pwl result({ { this_x, other.Eval(this_y, &other_span, false) } });
	while (this_span != (int)points_.size() - 1) {
		double dx = points_[this_span + 1].x - points_[this_span].x,
		       dy = points_[this_span + 1].y - points_[this_span].y;
		if (abs(dy) > eps &&
		    other_span + 1 < (int)other.points_.size() &&
		    points_[this_span + 1].y >=
			    other.points_[other_span + 1].x + eps) {
			// next control point in result will be where this
			// function's y reaches the next span in other
			this_x = points_[this_span].x +
				 (other.points_[other_span + 1].x -
				  points_[this_span].y) * dx / dy;
			this_y = other.points_[++other_span].x;
		} else if (abs(dy) > eps && other_span > 0 &&
			   points_[this_span + 1].y <=
				   other.points_[other_span - 1].x - eps) {
			// next control point in result will be where this
			// function's y reaches the previous span in other
			this_x = points_[this_span].x +
				 (other.points_[other_span + 1].x -
				  points_[this_span].y) * dx / dy;
			this_y = other.points_[--other_span].x;
		} else {
			// we stay in the same span in other
			this_span++;
			this_x = points_[this_span].x,
			this_y = points_[this_span].y;
		}
		result.Append(this_x, other.Eval(this_y, &other_span, false),
			      eps);
	}
	return result;
}

void Pwl::Map(std::function<void(double x, double y)> f) const
{
	for (auto &pt : points_)
		f(pt.x, pt.y);
}

void Pwl::Map2(Pwl const &pwl0, Pwl const &pwl1,
	       std::function<void(double x, double y0, double y1)> f)
{
	int span0 = 0, span1 = 0;
	double x = std::min(pwl0.points_[0].x, pwl1.points_[0].x);
	f(x, pwl0.Eval(x, &span0, false), pwl1.Eval(x, &span1, false));
	while (span0 < (int)pwl0.points_.size() - 1 ||
	       span1 < (int)pwl1.points_.size() - 1) {
		if (span0 == (int)pwl0.points_.size() - 1)
			x = pwl1.points_[++span1].x;
		else if (span1 == (int)pwl1.points_.size() - 1)
			x = pwl0.points_[++span0].x;
		else if (pwl0.points_[span0 + 1].x > pwl1.points_[span1 + 1].x)
			x = pwl1.points_[++span1].x;
		else
			x = pwl0.points_[++span0].x;
		f(x, pwl0.Eval(x, &span0, false), pwl1.Eval(x, &span1, false));
	}
}

Pwl Pwl::Combine(Pwl const &pwl0, Pwl const &pwl1,
		 std::function<double(double x, double y0, double y1)> f,
		 const double eps)
{
	Pwl result;
	Map2(pwl0, pwl1, [&](double x, double y0, double y1) {
		result.Append(x, f(x, y0, y1), eps);
	});
	return result;
}

void Pwl::MatchDomain(Interval const &domain, bool clip, const double eps)
{
	int span = 0;
	Prepend(domain.start, Eval(clip ? points_[0].x : domain.start, &span),
		eps);
	span = points_.size() - 2;
	Append(domain.end, Eval(clip ? points_.back().x : domain.end, &span),
	       eps);
}

Pwl &Pwl::operator*=(double d)
{
	for (auto &pt : points_)
		pt.y *= d;
	return *this;
}

void Pwl::Debug(FILE *fp) const
{
	fprintf(fp, "Pwl {\n");
	for (auto &p : points_)
		fprintf(fp, "\t(%g, %g)\n", p.x, p.y);
	fprintf(fp, "}\n");
}
t;id().c_str()), ("libcamera::Camera::acquire() failed: %s", g_strerror(ret))); return false; } cam->requestCompleted.connect(self->state, &GstLibcameraSrcState::requestCompleted); /* No need to lock here, we didn't start our threads yet. */ self->state->cm_ = std::move(cm); self->state->cam_ = cam; return true; } static void gst_libcamera_src_task_run(gpointer user_data) { GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data); GstLibcameraSrcState *state = self->state; std::unique_ptr<Request> request = state->cam_->createRequest(); if (!request) { GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT, ("Failed to allocate request for camera '%s'.", state->cam_->id().c_str()), ("libcamera::Camera::createRequest() failed")); gst_task_stop(self->task); return; } std::unique_ptr<RequestWrap> wrap = std::make_unique<RequestWrap>(std::move(request)); for (GstPad *srcpad : state->srcpads_) { GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad); GstBuffer *buffer; GstFlowReturn ret; ret = gst_buffer_pool_acquire_buffer(GST_BUFFER_POOL(pool), &buffer, nullptr); if (ret != GST_FLOW_OK) { /* * RequestWrap has ownership of the rquest, and we * won't be queueing this one due to lack of buffers. */ wrap.release(); break; } wrap->attachBuffer(buffer); } if (wrap) { GLibLocker lock(GST_OBJECT(self)); GST_TRACE_OBJECT(self, "Requesting buffers"); state->cam_->queueRequest(wrap->request_.get()); state->requests_.push(std::move(wrap)); /* The RequestWrap will be deleted in the completion handler. */ } GstFlowReturn ret = GST_FLOW_OK; gst_flow_combiner_reset(self->flow_combiner); for (GstPad *srcpad : state->srcpads_) { ret = gst_libcamera_pad_push_pending(srcpad); ret = gst_flow_combiner_update_pad_flow(self->flow_combiner, srcpad, ret); } { /* * Here we need to decide if we want to pause or stop the task. This * needs to happen in lock step with the callback thread which may want * to resume the task. */ GLibLocker lock(GST_OBJECT(self)); if (ret != GST_FLOW_OK) { if (ret == GST_FLOW_EOS) { g_autoptr(GstEvent) eos = gst_event_new_eos(); guint32 seqnum = gst_util_seqnum_next(); gst_event_set_seqnum(eos, seqnum); for (GstPad *srcpad : state->srcpads_) gst_pad_push_event(srcpad, gst_event_ref(eos)); } else if (ret != GST_FLOW_FLUSHING) { GST_ELEMENT_FLOW_ERROR(self, ret); } gst_task_stop(self->task); return; } bool do_pause = true; for (GstPad *srcpad : state->srcpads_) { if (gst_libcamera_pad_has_pending(srcpad)) { do_pause = false; break; } } if (do_pause) gst_task_pause(self->task); } } static void gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread, gpointer user_data) { GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data); GLibRecLocker lock(&self->stream_lock); GstLibcameraSrcState *state = self->state; GstFlowReturn flow_ret = GST_FLOW_OK; gint ret; GST_DEBUG_OBJECT(self, "Streaming thread has started"); gint stream_id_num = 0; StreamRoles roles; for (GstPad *srcpad : state->srcpads_) { /* Create stream-id and push stream-start. */ g_autofree gchar *stream_id_intermediate = g_strdup_printf("%i%i", state->group_id_, stream_id_num++); g_autofree gchar *stream_id = gst_pad_create_stream_id(srcpad, GST_ELEMENT(self), stream_id_intermediate); GstEvent *event = gst_event_new_stream_start(stream_id); gst_event_set_group_id(event, state->group_id_); gst_pad_push_event(srcpad, event); /* Collect the streams roles for the next iteration. */ roles.push_back(gst_libcamera_pad_get_role(srcpad)); } /* Generate the stream configurations, there should be one per pad. */ state->config_ = state->cam_->generateConfiguration(roles); if (state->config_ == nullptr) { GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS, ("Failed to generate camera configuration from roles"), ("Camera::generateConfiguration() returned nullptr")); gst_task_stop(task); return; } g_assert(state->config_->size() == state->srcpads_.size()); for (gsize i = 0; i < state->srcpads_.size(); i++) { GstPad *srcpad = state->srcpads_[i]; StreamConfiguration &stream_cfg = state->config_->at(i); /* Retrieve the supported caps. */ g_autoptr(GstCaps) filter = gst_libcamera_stream_formats_to_caps(stream_cfg.formats()); g_autoptr(GstCaps) caps = gst_pad_peer_query_caps(srcpad, filter); if (gst_caps_is_empty(caps)) { flow_ret = GST_FLOW_NOT_NEGOTIATED; break; } /* Fixate caps and configure the stream. */ caps = gst_caps_make_writable(caps); gst_libcamera_configure_stream_from_caps(stream_cfg, caps); } if (flow_ret != GST_FLOW_OK) goto done; /* Validate the configuration. */ if (state->config_->validate() == CameraConfiguration::Invalid) { flow_ret = GST_FLOW_NOT_NEGOTIATED; goto done; } /* * Regardless if it has been modified, create clean caps and push the * caps event. Downstream will decide if the caps are acceptable. */ for (gsize i = 0; i < state->srcpads_.size(); i++) { GstPad *srcpad = state->srcpads_[i]; const StreamConfiguration &stream_cfg = state->config_->at(i); g_autoptr(GstCaps) caps = gst_libcamera_stream_configuration_to_caps(stream_cfg); if (!gst_pad_push_event(srcpad, gst_event_new_caps(caps))) { flow_ret = GST_FLOW_NOT_NEGOTIATED; break; } /* Send an open segment event with time format. */ GstSegment segment; gst_segment_init(&segment, GST_FORMAT_TIME); gst_pad_push_event(srcpad, gst_event_new_segment(&segment)); } ret = state->cam_->configure(state->config_.get()); if (ret) { GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS, ("Failed to configure camera: %s", g_strerror(-ret)), ("Camera::configure() failed with error code %i", ret)); gst_task_stop(task); return; } self->allocator = gst_libcamera_allocator_new(state->cam_, state->config_.get()); if (!self->allocator) { GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT, ("Failed to allocate memory"), ("gst_libcamera_allocator_new() failed.")); gst_task_stop(task); return; } self->flow_combiner = gst_flow_combiner_new(); for (gsize i = 0; i < state->srcpads_.size(); i++) { GstPad *srcpad = state->srcpads_[i]; const StreamConfiguration &stream_cfg = state->config_->at(i); GstLibcameraPool *pool = gst_libcamera_pool_new(self->allocator, stream_cfg.stream()); g_signal_connect_swapped(pool, "buffer-notify", G_CALLBACK(gst_libcamera_resume_task), task); gst_libcamera_pad_set_pool(srcpad, pool); gst_flow_combiner_add_pad(self->flow_combiner, srcpad); } ret = state->cam_->start(); if (ret) { GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS, ("Failed to start the camera: %s", g_strerror(-ret)), ("Camera.start() failed with error code %i", ret)); gst_task_stop(task); return; } done: switch (flow_ret) { case GST_FLOW_NOT_NEGOTIATED: GST_ELEMENT_FLOW_ERROR(self, flow_ret); gst_task_stop(task); break; default: break; } } static void gst_libcamera_src_task_leave([[maybe_unused]] GstTask *task, [[maybe_unused]] GThread *thread, gpointer user_data) { GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data); GstLibcameraSrcState *state = self->state; GST_DEBUG_OBJECT(self, "Streaming thread is about to stop"); state->cam_->stop(); for (GstPad *srcpad : state->srcpads_) gst_libcamera_pad_set_pool(srcpad, nullptr); g_clear_object(&self->allocator); g_clear_pointer(&self->flow_combiner, (GDestroyNotify)gst_flow_combiner_free); } static void gst_libcamera_src_close(GstLibcameraSrc *self) { GstLibcameraSrcState *state = self->state; gint ret; GST_DEBUG_OBJECT(self, "Releasing resources"); state->config_.reset(); ret = state->cam_->release(); if (ret) { GST_ELEMENT_WARNING(self, RESOURCE, BUSY, ("Camera '%s' is still in use.", state->cam_->id().c_str()), ("libcamera::Camera.release() failed: %s", g_strerror(-ret))); } state->cam_.reset(); state->cm_->stop(); state->cm_.reset(); } static void gst_libcamera_src_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GLibLocker lock(GST_OBJECT(object)); GstLibcameraSrc *self = GST_LIBCAMERA_SRC(object); switch (prop_id) { case PROP_CAMERA_NAME: g_free(self->camera_name); self->camera_name = g_value_dup_string(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gst_libcamera_src_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GLibLocker lock(GST_OBJECT(object)); GstLibcameraSrc *self = GST_LIBCAMERA_SRC(object); switch (prop_id) { case PROP_CAMERA_NAME: g_value_set_string(value, self->camera_name); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static GstStateChangeReturn gst_libcamera_src_change_state(GstElement *element, GstStateChange transition) { GstLibcameraSrc *self = GST_LIBCAMERA_SRC(element); GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; GstElementClass *klass = GST_ELEMENT_CLASS(gst_libcamera_src_parent_class); ret = klass->change_state(element, transition); if (ret == GST_STATE_CHANGE_FAILURE) return ret; switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: if (!gst_libcamera_src_open(self)) return GST_STATE_CHANGE_FAILURE; break; case GST_STATE_CHANGE_READY_TO_PAUSED: /* This needs to be called after pads activation.*/ self->state->group_id_ = gst_util_group_id_next(); if (!gst_task_pause(self->task)) return GST_STATE_CHANGE_FAILURE; ret = GST_STATE_CHANGE_NO_PREROLL; break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: gst_task_start(self->task); break; case GST_STATE_CHANGE_PLAYING_TO_PAUSED: ret = GST_STATE_CHANGE_NO_PREROLL; break; case GST_STATE_CHANGE_PAUSED_TO_READY: /* * \todo this might require some thread unblocking in the future * if the streaming thread starts doing any kind of blocking * operations. If this was the case, we would need to do so * before pad deactivation, so before chaining to the parent * change_state function. */ gst_task_join(self->task); break; case GST_STATE_CHANGE_READY_TO_NULL: gst_libcamera_src_close(self); break; default: break; } return ret; } static void gst_libcamera_src_finalize(GObject *object) { GObjectClass *klass = G_OBJECT_CLASS(gst_libcamera_src_parent_class); GstLibcameraSrc *self = GST_LIBCAMERA_SRC(object); g_rec_mutex_clear(&self->stream_lock); g_clear_object(&self->task); g_free(self->camera_name); delete self->state; return klass->finalize(object); } static void gst_libcamera_src_init(GstLibcameraSrc *self) { GstLibcameraSrcState *state = new GstLibcameraSrcState(); GstPadTemplate *templ = gst_element_get_pad_template(GST_ELEMENT(self), "src"); g_rec_mutex_init(&self->stream_lock); self->task = gst_task_new(gst_libcamera_src_task_run, self, nullptr); gst_task_set_enter_callback(self->task, gst_libcamera_src_task_enter, self, nullptr); gst_task_set_leave_callback(self->task, gst_libcamera_src_task_leave, self, nullptr); gst_task_set_lock(self->task, &self->stream_lock); state->srcpads_.push_back(gst_pad_new_from_template(templ, "src")); gst_element_add_pad(GST_ELEMENT(self), state->srcpads_[0]); /* C-style friend. */ state->src_ = self; self->state = state; } static GstPad * gst_libcamera_src_request_new_pad(GstElement *element, GstPadTemplate *templ, const gchar *name, [[maybe_unused]] const GstCaps *caps) { GstLibcameraSrc *self = GST_LIBCAMERA_SRC(element); g_autoptr(GstPad) pad = NULL; GST_DEBUG_OBJECT(self, "new request pad created"); pad = gst_pad_new_from_template(templ, name); g_object_ref_sink(pad); if (gst_element_add_pad(element, pad)) { GLibLocker lock(GST_OBJECT(self)); self->state->srcpads_.push_back(reinterpret_cast<GstPad *>(g_object_ref(pad))); } else { GST_ELEMENT_ERROR(element, STREAM, FAILED, ("Internal data stream error."), ("Could not add pad to element")); return NULL; } return reinterpret_cast<GstPad *>(g_steal_pointer(&pad)); } static void gst_libcamera_src_release_pad(GstElement *element, GstPad *pad) { GstLibcameraSrc *self = GST_LIBCAMERA_SRC(element); GST_DEBUG_OBJECT(self, "Pad %" GST_PTR_FORMAT " being released", pad); { GLibLocker lock(GST_OBJECT(self)); std::vector<GstPad *> &pads = self->state->srcpads_; auto begin_iterator = pads.begin(); auto end_iterator = pads.end(); auto pad_iterator = std::find(begin_iterator, end_iterator, pad); if (pad_iterator != end_iterator) { g_object_unref(*pad_iterator); pads.erase(pad_iterator); } } gst_element_remove_pad(element, pad); } static void gst_libcamera_src_class_init(GstLibcameraSrcClass *klass) { GstElementClass *element_class = GST_ELEMENT_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->set_property = gst_libcamera_src_set_property; object_class->get_property = gst_libcamera_src_get_property; object_class->finalize = gst_libcamera_src_finalize; element_class->request_new_pad = gst_libcamera_src_request_new_pad; element_class->release_pad = gst_libcamera_src_release_pad; element_class->change_state = gst_libcamera_src_change_state; gst_element_class_set_metadata(element_class, "libcamera Source", "Source/Video", "Linux Camera source using libcamera", "Nicolas Dufresne <nicolas.dufresne@collabora.com"); gst_element_class_add_static_pad_template_with_gtype(element_class, &src_template, GST_TYPE_LIBCAMERA_PAD); gst_element_class_add_static_pad_template_with_gtype(element_class, &request_src_template, GST_TYPE_LIBCAMERA_PAD); GParamSpec *spec = g_param_spec_string("camera-name", "Camera Name", "Select by name which camera to use.", nullptr, (GParamFlags)(GST_PARAM_MUTABLE_READY | G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(object_class, PROP_CAMERA_NAME, spec); }