Fixposition SDK 0.0.0-heads/main-0-g200d201
Collection of c++ libraries and apps for use with Fixposition products on Linux
Loading...
Searching...
No Matches
Camera streaming from PBx-A1 sensor
1/**
2 * \verbatim
3 * ___ ___
4 * \ \ / /
5 * \ \/ / Copyright (c) Fixposition AG (www.fixposition.com) and contributors
6 * / /\ \ License: see the LICENSE file
7 * /__/ \__\
8 * \endverbatim
9 *
10 * @file
11 * @brief Fixposition SDK example: Fixposition SDK example: camera streaming from PBx-A1 sensor
12 *
13 * To build and run:
14 *
15 * make
16 * ./build/fusion_epoch
17 *
18 * This program builds on the "parser_intro" example and details how to collect the FP_A fusion messages into
19 * fusion epochs for further processing.
20 *
21 * This file is both the source code of this example as well as the documentation on how it works.
22 */
23
24/* LIBC/STL */
25#include <array>
26#include <cmath>
27#include <cstdint>
28#include <cstring>
29#include <string>
30
31/* EXTERNAL */
32#include <boost/accumulators/accumulators.hpp>
33#include <boost/accumulators/statistics/count.hpp>
34#include <boost/accumulators/statistics/extended_p_square.hpp>
35#include <boost/accumulators/statistics/max.hpp>
36#include <boost/accumulators/statistics/mean.hpp>
37#include <boost/accumulators/statistics/min.hpp>
38#include <boost/accumulators/statistics/stats.hpp>
39#include <boost/accumulators/statistics/sum.hpp>
40#include <boost/accumulators/statistics/variance.hpp>
41
42/* Fixposition SDK */
43#include <fpsdk_common/app.hpp>
44#include <fpsdk_common/cam.hpp>
47#include <fpsdk_common/time.hpp>
50
51/* PACKAGE */
52
53/* ****************************************************************************************************************** */
54
55using namespace fpsdk::common::app;
56using namespace fpsdk::common::cam;
57using namespace fpsdk::common::logging;
58using namespace fpsdk::common::string;
59using namespace fpsdk::common::time;
60using namespace fpsdk::common::video;
61
62// ---------------------------------------------------------------------------------------------------------------------
63
64// Statistics
65struct Stats
66{
67 // clang-format off
68 static constexpr std::array<double, 4> PROB = {{ 0.5, 0.68, 0.95, 0.997 }};
69 using Accumulator = boost::accumulators::accumulator_set<double, boost::accumulators::stats<
70 boost::accumulators::tag::count,
71 boost::accumulators::tag::mean,
72 boost::accumulators::tag::min,
73 boost::accumulators::tag::max,
74 boost::accumulators::tag::sum,
75 boost::accumulators::tag::variance,
76 boost::accumulators::tag::extended_p_square>>;
77 Accumulator lat { boost::accumulators::extended_p_square_probabilities = PROB }; // Latency receiving the data
78 Accumulator size { boost::accumulators::extended_p_square_probabilities = PROB }; // Size of the data
79 Accumulator exp { boost::accumulators::extended_p_square_probabilities = PROB }; // Exposure duration
80#if FPSDK_USE_FFMPEG
81 Accumulator dec { boost::accumulators::extended_p_square_probabilities = PROB }; // Time decoding video
82#endif
83 // clang-format on
84};
85
86// ---------------------------------------------------------------------------------------------------------------------
87
88// Program options
89class Opts : public ProgramOptions
90{
91 // clang-format off
92 public:
93 Opts() /* clang-format off */ :
94 ProgramOptions("camera_streaming", { { 's', true, "sensor" }, { 'n', true, "camera" },
95 { 'd', true, "data" }, { 'r', true, "rate" }, { 'c', false, "stdout" } }) // clang-format on
96 {};
97
98 // clang-format off
99 std::string sensor_;
100 CamId camera_ = CamId::UNSPECIFIED;
101 CamDataType data_ = CamDataType::UNSPECIFIED;
102 int rate_ = 1;
103 bool stdout_ = false;
104 // clang-format on
105
106 void PrintHelp() override final
107 {
108 // clang-format off
109 std::fputs(
110 "\n"
111 "PBx-A1 camera streaming example\n"
112 "\n"
113 "Usage:\n"
114 "\n"
115 " camera_streaming -s <sensor> -c <camera> -d <data> [-r <rate>] [-c]\n"
116 "\n", stdout);
117 std::fputs(COMMON_FLAGS_HELP, stdout);
118 std::fputs(
119 "\n"
120 " -s, --sensor -- Hostname or IP address of sensor\n"
121 " -n, --camera -- Which camera to use (CAM1, CAM2, ...)\n"
122 " -d, --data -- Which data to stream (HIRES_VID, LORES_IMG, etc.)\n"
123 " -r, --rate -- Throttling rate (default 1, >1 only for _IMG streams)\n"
124 " -c, --stdout -- Pipe raw image/video data to stdout (careful!)\n"
125 "\n"
126 "Example:\n"
127 "\n"
128 " camera_streaming -s 10.0.2.1 -n CAM1 -d HIRES_VID\n"
129 "\n"
130 " camera_streaming -s 10.0.2.1 -n CAM1 -d LORES_IMG\n"
131 "\n"
132 " timeout -s SIGINT 60 camera_streaming ...\n"
133 "\n"
134 " camera_streaming -s 10.0.2.1 -n CAM1 -d HIRES_VID -c | ffmpeg -i - -c copy out.mp4\n"
135 "\n"
136 " camera_streaming -s 10.0.2.1 -n CAM1 -d HIRES_VID -q -c | \\\n"
137 " mpv --cache-secs=0 --profile=low-latency --demuxer-readahead-secs=0 --untimed --cache-pause=no -\n"
138 " camera_streaming -s 10.0.2.1 -n CAM1 -d HIRES_VID -q -c | \\\n"
139 " ffplay -fflags nobuffer -flags low_delay -framedrop -probesize 32 -analyzeduration 0 -\n"
140 "\n"
141 "\n", stdout);
142 // clang-format on
143 }
144
145 bool HandleOption(const Option& option, const std::string& argument) final
146 {
147 bool ok = true;
148 switch (option.flag) { // clang-format off
149 case 's': sensor_ = argument; break;
150 case 'n': camera_ = CamIdFromStrOr(argument.c_str(), CamId::UNSPECIFIED); break;
151 case 'd': data_ = CamDataTypeFromStrOr(argument.c_str(), CamDataType::UNSPECIFIED); break;
152 case 'r': StrToValue(argument, rate_); break;
153 case 'c': stdout_ = true; break;
154 default: ok = false; break;
155 } // clang-format on
156 return ok;
157 }
158
159 bool CheckOptions(const std::vector<std::string>& args) final
160 {
161 bool ok = true;
162 INFO("sensor = %s", sensor_.c_str());
163 INFO("camera = %s", CamIdToStr(camera_));
164 INFO("data = %s", CamDataTypeToStr(data_));
165 INFO("rate = %d", rate_);
166 INFO("stdout = %s", ToStr(stdout_));
167 if (!args.empty() || sensor_.empty() || (camera_ == CamId::UNSPECIFIED) ||
168 (data_ == CamDataType::UNSPECIFIED) || (rate_ < 1)) {
169 ok = false;
170 }
171 return ok;
172 }
173};
174
175// ---------------------------------------------------------------------------------------------------------------------
176
177int main(int argc, char** argv)
178{
179#ifndef NDEBUG
181#endif
182
183#if !FPSDK_USE_FFMPEG
184 WARNING("!!!!! This app is not compiled with FFmpeg support !!!!!");
185#endif
186
187 // We need four things (see CamStreamParams):
188 //
189 // 1. The sensor (hostname, IP address)
190 // 2. Which camera (CAM1, CAM2, ...)
191 // 3. Which data (HIRES_VID, LORES_IMG, ...)
192 // 4. Rate value (must be 1 for ..._VID, can be > 1 for ..._IMG)
193 Opts opts;
194 if (!opts.LoadFromArgv(argc, argv)) {
195 return false;
196 }
197 opts.LogVersion();
198
199 NOTICE("Streaming...");
200
201 // Create stream handle (this formally verifies the arguments)
202 auto stream = CreateCamStream({ "cam", opts.sensor_, opts.camera_, opts.data_, opts.rate_ });
203 if (!stream) {
204 return EXIT_FAILURE;
205 }
206
207 // Connect to sensor
208 if (!stream->Connect()) {
209 return EXIT_FAILURE;
210 }
211
212#if FPSDK_USE_FFMPEG
213 // We'll decode encoded video frames
214 VideoFrameDecoderPtr decoder;
215#endif
216
217 // Statistics
218 Stats stats;
219
220 // Stream data until we get SIGINT (CTRL-c)
221 SigIntHelper sigint;
222 CamData data;
223 std::size_t n_frames = 0;
224 char info[1000];
225 bool ok = true;
226 const auto t_start = Time::FromClockRealtime();
227 while (ok && !sigint.ShouldAbort() && stream->NextFrame(data)) {
228 // We'll calculate the latency from the time we received the data (now) and the data reference time. Note that
229 // this requires the computer that runs this program being timesynced with the sensor (and the sensor being
230 // timesynced, too, ideally with GNSS). Any error in timesync of the sensor or the computer affects the measured
231 // latency (could be to the better or to the worse). Also, we'll use the end of exposure as the reference time
232 // instead of the middle of exposure.
233 const auto t_recv = Time::FromClockRealtime();
234 const auto t_expo = Duration::FromNSec(data.dt_);
235 const auto t_data = Time::FromPosixNs(data.ts_) + (t_expo * 0.5);
236 n_frames++;
237
238 // Generic info
239 std::size_t len = std::snprintf(info, sizeof(info), // clang-format off
240 "Frame %6" PRIuMAX ": %06" PRIuMAX " %-7s %-5s %-10s %-9s %" PRIu32 "x%" PRIu32 " %-7s %6" PRIuMAX " %s %4.1f",
241 n_frames, data.seq_, data.valid_ ? "valid" : "invalid", CamIdToStr(data.cam_id_),
242 CamDataTypeToStr(data.type_), CamDataFmtToStr(data.fmt_), data.width_, data.height_,
243 CamDataFrmToStr(data.frm_), data.data_.size(), t_data.StrIsoTime(3).c_str(),
244 Duration::FromNSec(data.dt_).GetSec() * 1e3); // clang-format on
245
246 // Update statistics
247 stats.size((double)data.data_.size() / 1024.0); // [KiB]
248 stats.exp((double)t_expo.GetSec() * 1e3); // [ms]
249
250 // Latency
251 if (data.valid_) {
252 const double lat = (t_recv - t_data).GetSec() * 1e3; // [ms]
253 len += std::snprintf(&info[len], sizeof(info) - len, " -- latency: %+5.1f", lat);
254 stats.lat(lat);
255 }
256
257 // -------------------------------------------------------
258 // Now we have all the raw data and meta data in "data"...
259 // -------------------------------------------------------
260
261 if (opts.stdout_) {
262 write(fileno(stdout), data.data_.data(), data.data_.size());
263 }
264
265#if FPSDK_USE_FFMPEG
266 // Decode video
267 if (data.fmt_ == CamDataFmt::H265_NAL) {
268 // We can only start decoding from the first I-frame onwards
269 if (!decoder && (data.frm_ == CamDataFrm::I_FRAME)) {
271 }
272 if (decoder) {
273 TicToc tt;
274 const auto img = decoder->DecodeFrame(data.data_);
275
276 // Decoding adds latency
277 if (img) {
278 const auto lat = tt.Toc().GetSec() * 1e3; // [ms]
279 len += std::snprintf(&info[len], sizeof(info) - len, " -- decode: %+5.1f", lat);
280 stats.dec(lat);
281 } else {
282 len += std::snprintf(&info[len], sizeof(info) - len, " -- DECODE FAIL");
283 continue;
284 }
285
286 // ----------------------------------------------------------------------------------------
287 // Now we have the decoded image in "img"... (and all the raw data and meta data in "data")
288 // ----------------------------------------------------------------------------------------
289 }
290 }
291#endif
292
293 INFO("%s", info);
294 }
295 const auto t_end = Time::FromClockRealtime();
296 const auto t_str = (t_end - t_start).GetSec();
297
298 stream->Disconnect();
299
300 // Print stats
301 NOTICE("Streamed %s %s %s %d for %.0fs (%" PRIuMAX " frames)", opts.sensor_.c_str(), CamIdToStr(opts.camera_),
302 CamDataTypeToStr(opts.data_), opts.rate_, t_str, boost::accumulators::count(stats.size));
303 if (boost::accumulators::count(stats.lat) > 10) { // clang-format off
304 INFO("Latency receiving data [ms]: mean %4.1f (std %4.1f) min/0.5/0.68/0.95/0.997/max %4.1f %4.1f %4.1f %4.1f %4.1f %4.1f", // clang-format on
305 boost::accumulators::mean(stats.lat), std::sqrt(boost::accumulators::variance(stats.lat)),
306 boost::accumulators::min(stats.lat), boost::accumulators::extended_p_square(stats.lat)[0],
307 boost::accumulators::extended_p_square(stats.lat)[1], boost::accumulators::extended_p_square(stats.lat)[2],
308 boost::accumulators::extended_p_square(stats.lat)[3], boost::accumulators::max(stats.lat));
309 }
310#if FPSDK_USE_FFMPEG
311 if (boost::accumulators::count(stats.dec) > 10) { // clang-format off
312 INFO("Latency decoding video [ms]: mean %4.1f (std %4.1f) min/0.5/0.68/0.95/0.997/max %4.1f %4.1f %4.1f %4.1f %4.1f %4.1f", // clang-format on
313 boost::accumulators::mean(stats.dec), std::sqrt(boost::accumulators::variance(stats.dec)),
314 boost::accumulators::min(stats.dec), boost::accumulators::extended_p_square(stats.dec)[0],
315 boost::accumulators::extended_p_square(stats.dec)[1], boost::accumulators::extended_p_square(stats.dec)[2],
316 boost::accumulators::extended_p_square(stats.dec)[3], boost::accumulators::max(stats.dec));
317 }
318#endif
319 if (boost::accumulators::count(stats.exp) > 10) { // clang-format off
320 INFO("Exposure duration [ms]: mean %4.1f (std %4.1f) min/0.5/0.68/0.95/0.997/max %4.1f %4.1f %4.1f %4.1f %4.1f %4.1f", // clang-format on
321 boost::accumulators::mean(stats.exp), std::sqrt(boost::accumulators::variance(stats.exp)),
322 boost::accumulators::min(stats.exp), boost::accumulators::extended_p_square(stats.exp)[0],
323 boost::accumulators::extended_p_square(stats.exp)[1], boost::accumulators::extended_p_square(stats.exp)[2],
324 boost::accumulators::extended_p_square(stats.exp)[3], boost::accumulators::max(stats.exp));
325 }
326 if ((boost::accumulators::count(stats.size) > 10) && (t_str > 1.0)) { // clang-format off
327 INFO("Data size per frame [KiB]: mean %4.0f (std %4.0f) min/0.5/0.68/0.95/0.997/max %4.0f %4.0f %4.0f %4.0f %4.0f %4.0f", // clang-format on
328 boost::accumulators::mean(stats.size), std::sqrt(boost::accumulators::variance(stats.size)),
329 boost::accumulators::min(stats.size), boost::accumulators::extended_p_square(stats.size)[0],
330 boost::accumulators::extended_p_square(stats.size)[1],
331 boost::accumulators::extended_p_square(stats.size)[2],
332 boost::accumulators::extended_p_square(stats.size)[3], boost::accumulators::max(stats.size));
333 const double avg = boost::accumulators::sum(stats.size) / t_str;
334 // Assuming GibE link TCP socket ~115MB/s = ~120500 KiB/s
335 INFO("Average data rate [KiB/s]: %.0f (~%.1f%%GigE)", avg, avg / 120500.0 * 1e2);
336 }
337
338 return ok ? EXIT_SUCCESS : EXIT_FAILURE;
339}
340
341/* ****************************************************************************************************************** */
Fixposition SDK: Utilities for apps.
Fixposition SDK: Camera types and utilities.
Helper to catch SIGINT (CTRL-c).
Definition app.hpp:64
bool ShouldAbort()
Check if signal was raised and we should abort.
Helper to print a strack trace on SIGSEGV and SIGABRT.
Definition app.hpp:184
static Duration FromNSec(const int64_t nsec)
Make Duration from nanoseconds.
double GetSec(const int prec=9) const
Get duration as seconds.
Helper to measure wallclock time.
Definition time.hpp:114
Duration Toc(const bool reset=false)
Get elapsed wallclock time.
static Time FromPosixNs(const uint64_t posix_ns)
From POSIX nanoseconds (POSIX).
static Time FromClockRealtime()
From system clock current (now) system time (CLOCK_REALTIME).
Fixposition SDK: Logging.
#define NOTICE(...)
Print a notice message.
Definition logging.hpp:85
#define WARNING(...)
Print a warning message.
Definition logging.hpp:81
#define INFO(...)
Print a info message.
Definition logging.hpp:89
Utilities for apps.
Definition app.hpp:35
Camera types and utilities.
Definition cam.hpp:36
const char * CamIdToStr(const CamId camid)
Stringify camera ID enum.
const char * CamDataFmtToStr(const CamDataFmt fmt)
Stringify camera data format enum.
CamId CamIdFromStrOr(const char *str, const CamId def)
Convert camera ID string to enum.
CamId
Camera ID.
Definition cam.hpp:43
const char * CamDataTypeToStr(const CamDataType type)
Stringify camera data type enum.
CamDataType
Camera data type (type of data).
Definition cam.hpp:86
const char * CamDataFrmToStr(const CamDataFrm frm)
Stringify camera data frame type enum.
@ H265_NAL
H.265 NAL (HEVC) (video/hevc).
Definition cam.hpp:137
@ I_FRAME
I-frame (= Horizon camsys MC_H26[45]_NALU_TYPE_I).
Definition cam.hpp:183
CamStreamPtr CreateCamStream(const CamStreamParams &params)
Create a camera stream instance.
CamDataType CamDataTypeFromStrOr(const char *str, const CamDataType def)
Convert camera data type string to enum.
String utilities.
Definition string.hpp:38
constexpr const char * ToStr(const bool value)
Stringify value (bool).
Definition string.hpp:408
bool StrToValue(const std::string &str, int8_t &value)
Convert string to value (int8_t).
Time utilities.
Definition time.hpp:39
Video frame decoding.
Definition video.hpp:37
@ RGB24
Packed RGB 8:8:8, 24bpp, RGBRGB... (= FFmpeg AV_PIX_FMT_RGB24).
Definition video.hpp:77
@ H265
H.265 High Efficiency Video Coding (HEVC).
Definition video.hpp:48
std::unique_ptr< VideoFrameDecoder > VideoFrameDecoderPtr
Pointer to a VideoFrameDecoder instance, see CreateVideoFrameDecoder().
Definition video.hpp:263
VideoFrameDecoderPtr CreateVideoFrameDecoder(const VideoDecoderParams &params)
Create a video frame decoder.
Fixposition SDK: String utilities.
Camera data from CamStream (or from FPL, see fpl::CamData).
Definition cam.hpp:224
uint64_t ts_
Camera image time (middle of exposure) [CLOCK_REALTIME ns] (0 = invalid).
Definition cam.hpp:232
CamId cam_id_
= meta_.cam_id_ as enum (if value in range)
Definition cam.hpp:227
uint32_t dt_
Exposure time (duration) [ns] (0 = not available).
Definition cam.hpp:233
CamDataFmt fmt_
= meta_.fmt_ as enum (if value in range)
Definition cam.hpp:229
uint64_t seq_
Sequence number.
Definition cam.hpp:231
CamDataType type_
= meta_.type_ as enum (if value in range)
Definition cam.hpp:228
bool valid_
Data valid, successfully extracted from message.
Definition cam.hpp:226
uint32_t height_
Height of the fram [px].
Definition cam.hpp:235
std::vector< uint8_t > data_
Image/frame data, contents depends on fmt_ etc.
Definition cam.hpp:236
uint32_t width_
Width of the frame [px].
Definition cam.hpp:234
CamDataFrm frm_
= meta_.frm_ as enum (if value in range)
Definition cam.hpp:230
Fixposition SDK: Time utilities.
Fixposition SDK: Common types and type helpers.
Fixposition SDK: Video frame decoding.