Fixposition SDK 0.0.0-heads/main-0-g4d935a0
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
30/* EXTERNAL */
31#include <boost/accumulators/accumulators.hpp>
32#include <boost/accumulators/statistics/count.hpp>
33#include <boost/accumulators/statistics/extended_p_square.hpp>
34#include <boost/accumulators/statistics/max.hpp>
35#include <boost/accumulators/statistics/mean.hpp>
36#include <boost/accumulators/statistics/min.hpp>
37#include <boost/accumulators/statistics/stats.hpp>
38#include <boost/accumulators/statistics/sum.hpp>
39#include <boost/accumulators/statistics/variance.hpp>
40
41/* Fixposition SDK */
42#include <fpsdk_common/app.hpp>
43#include <fpsdk_common/cam.hpp>
46#include <fpsdk_common/time.hpp>
49
50/* PACKAGE */
51
52/* ****************************************************************************************************************** */
53
54using namespace fpsdk::common::app;
55using namespace fpsdk::common::cam;
56using namespace fpsdk::common::logging;
57using namespace fpsdk::common::string;
58using namespace fpsdk::common::time;
59using namespace fpsdk::common::video;
60
61// ---------------------------------------------------------------------------------------------------------------------
62
63// Statistics
64struct Stats
65{
66 // clang-format off
67 static constexpr std::array<double, 4> PROB = {{ 0.5, 0.68, 0.95, 0.997 }};
68 using Accumulator = boost::accumulators::accumulator_set<double, boost::accumulators::stats<
69 boost::accumulators::tag::count,
70 boost::accumulators::tag::mean,
71 boost::accumulators::tag::min,
72 boost::accumulators::tag::max,
73 boost::accumulators::tag::sum,
74 boost::accumulators::tag::variance,
75 boost::accumulators::tag::extended_p_square>>;
76 Accumulator lat { boost::accumulators::extended_p_square_probabilities = PROB }; // Latency receiving the data
77 Accumulator size { boost::accumulators::extended_p_square_probabilities = PROB }; // Size of the data
78 Accumulator exp { boost::accumulators::extended_p_square_probabilities = PROB }; // Exposure duration
79 Accumulator dec { boost::accumulators::extended_p_square_probabilities = PROB }; // Time decoding video
80 // clang-format on
81};
82
83// ---------------------------------------------------------------------------------------------------------------------
84
85int main(int argc, char** argv)
86{
87#ifndef NDEBUG
89#endif
90
91 // We need three things (see CamStreamParams):
92 //
93 // 1. The sensor (hostname, IP address)
94 // 2. Which camera (CAM1, CAM2, ...)
95 // 3. Which data (HIRES_VID, LORES_IMG, ...)
96 // 4. Throttle value
97 //
98 if (argc != 5) {
99 ERROR("Missing arguments!");
100 INFO(
101 "Usage: camera_streaming <sensor> <cam> <type> <rate>\n"
102 "Where: <sensor> is the sensor's hostname or IP address, <cam> is CAM1 or CAM2, <type> is\n"
103 "HIRES_VID, LORES_VID, HIRES_IMG or LORES_IMG, and <rate> is the throttling rate (must be\n"
104 "1 for encoded video)\n"
105 "Examples:\n"
106 " camera_streaming 10.0.2.1 CAM1 HIRES_VID 1\n"
107 " camera_streaming 10.0.2.1 CAM1 HIRES_IMG 10\n"
108 " timeout -s SIGINT 60 camera_streaming ...");
109 return EXIT_FAILURE;
110 }
111
112#if 0 // Set to 1 (and build with CMAKE_BUILD_TYPE=Debug) for debugging
114#else
116#endif
117
118 const std::string sensor = argv[1];
119 const CamId cam_id = CamIdFromStrOr(argv[2], CamId::UNSPECIFIED);
121 int rate = 0;
122 StrToValue(argv[4], rate);
123
124 NOTICE("camera_streaming %s %s %s %d", sensor.c_str(), CamIdToStr(cam_id), CamDataTypeToStr(type), rate);
125
126 // Create stream handle (this formally verifies the arguments)
127 auto stream = CreateCamStream({ "cam", sensor, cam_id, type, rate });
128 if (!stream) {
129 return EXIT_FAILURE;
130 }
131
132 // Connect to sensor
133 if (!stream->Connect()) {
134 return EXIT_FAILURE;
135 }
136
137 // We'll decode encoded video frames
138 VideoFrameDecoderPtr decoder;
139
140 // Statistics
141 Stats stats;
142
143 // Stream data until we get SIGINT (CTRL-c)
144 SigIntHelper sigint;
145 CamData data;
146 std::size_t n_frames = 0;
147 char info[1000];
148 bool ok = true;
149 const auto t_start = Time::FromClockRealtime();
150 while (ok && !sigint.ShouldAbort() && stream->NextFrame(data)) {
151 // We'll calculate the latency from the time we received the data (now) and the data reference time. Note that
152 // this requires the computer that runs this program being timesynced with the sensor (and the sensor being
153 // timesynced, too, ideally with GNSS). Any error in timesync of the sensor or the computer affects the measured
154 // latency (could be to the better or to the worse). Also, we'll use the end of exposure as the reference time
155 // instead of the middle of exposure.
156 const auto t_recv = Time::FromClockRealtime();
157 const auto t_expo = Duration::FromNSec(data.dt_);
158 const auto t_data = Time::FromPosixNs(data.ts_) + (t_expo * 0.5);
159 n_frames++;
160
161 // Generic info
162 std::size_t len =
163 std::snprintf(info, sizeof(info), "Frame %6" PRIuMAX ": %-7s %-5s %-10s %-9s %-7s %6" PRIuMAX " %s %4.1f",
164 n_frames, data.valid_ ? "valid" : "invalid", CamIdToStr(data.cam_id_), CamDataTypeToStr(data.type_),
165 CamDataFmtToStr(data.fmt_), CamDataFrmToStr(data.frm_), data.data_.size(), t_data.StrIsoTime(3).c_str(),
166 Duration::FromNSec(data.dt_).GetSec() * 1e3);
167
168 // Update statistics
169 stats.size((double)data.data_.size() / 1024.0); // [KiB]
170 stats.exp((double)t_expo.GetSec() * 1e3); // [ms]
171
172 // Latency
173 if (data.valid_) {
174 const double lat = (t_recv - t_data).GetSec() * 1e3; // [ms]
175 len += std::snprintf(&info[len], sizeof(info) - len, " -- latency: %+5.1f", lat);
176 stats.lat(lat);
177 }
178
179 // Decode video
180 if (data.fmt_ == CamDataFmt::H265_NAL) {
181 // We can only start decoding from the first I-frame onwards
182 if (!decoder && (data.frm_ == CamDataFrm::I_FRAME)) {
184 }
185 if (decoder) {
186 TicToc tt;
187 const auto img = decoder->DecodeFrame(data.data_);
188 if (!img) {
189 ok = false;
190 break;
191 }
192
193 // --------------------------------------------------------------------------------------
194 // Now we have the decoded image in "img" and all the raw data and meta data in "data"...
195 // --------------------------------------------------------------------------------------
196
197 // Decoding adds latency
198 const auto lat = tt.Toc().GetSec() * 1e3; // [ms]
199 len += std::snprintf(&info[len], sizeof(info) - len, " -- decode: %+5.1f", lat);
200 stats.dec(lat);
201 }
202 }
203
204 INFO("%s", info);
205 }
206 const auto t_end = Time::FromClockRealtime();
207 const auto t_str = (t_end - t_start).GetSec();
208
209 stream->Disconnect();
210
211 // Print stats
212 NOTICE("Streamed %s %s %s %d for %.0fs (%" PRIuMAX " frames)", sensor.c_str(), CamIdToStr(cam_id),
213 CamDataTypeToStr(type), rate, t_str, boost::accumulators::count(stats.size));
214 if (boost::accumulators::count(stats.lat) > 10) { // clang-format off
215 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
216 boost::accumulators::mean(stats.lat), std::sqrt(boost::accumulators::variance(stats.lat)),
217 boost::accumulators::min(stats.lat), boost::accumulators::extended_p_square(stats.lat)[0],
218 boost::accumulators::extended_p_square(stats.lat)[1], boost::accumulators::extended_p_square(stats.lat)[2],
219 boost::accumulators::extended_p_square(stats.lat)[3], boost::accumulators::max(stats.lat));
220 }
221 if (boost::accumulators::count(stats.dec) > 10) { // clang-format off
222 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
223 boost::accumulators::mean(stats.dec), std::sqrt(boost::accumulators::variance(stats.dec)),
224 boost::accumulators::min(stats.dec), boost::accumulators::extended_p_square(stats.dec)[0],
225 boost::accumulators::extended_p_square(stats.dec)[1], boost::accumulators::extended_p_square(stats.dec)[2],
226 boost::accumulators::extended_p_square(stats.dec)[3], boost::accumulators::max(stats.dec));
227 }
228 if (boost::accumulators::count(stats.exp) > 10) { // clang-format off
229 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
230 boost::accumulators::mean(stats.exp), std::sqrt(boost::accumulators::variance(stats.exp)),
231 boost::accumulators::min(stats.exp), boost::accumulators::extended_p_square(stats.exp)[0],
232 boost::accumulators::extended_p_square(stats.exp)[1], boost::accumulators::extended_p_square(stats.exp)[2],
233 boost::accumulators::extended_p_square(stats.exp)[3], boost::accumulators::max(stats.exp));
234 }
235 if ((boost::accumulators::count(stats.size) > 10) && (t_str > 1.0)) { // clang-format off
236 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
237 boost::accumulators::mean(stats.size), std::sqrt(boost::accumulators::variance(stats.size)),
238 boost::accumulators::min(stats.size), boost::accumulators::extended_p_square(stats.size)[0],
239 boost::accumulators::extended_p_square(stats.size)[1],
240 boost::accumulators::extended_p_square(stats.size)[2],
241 boost::accumulators::extended_p_square(stats.size)[3], boost::accumulators::max(stats.size));
242 const double avg = boost::accumulators::sum(stats.size) / t_str;
243 // Assuming GibE link TCP socket ~115MB/s = ~120500 KiB/s
244 INFO("Average data rate [KiB/s]: %.0f (~%.1f%%GigE)", avg, avg / 120500.0 * 1e2);
245 }
246
247 return ok ? EXIT_SUCCESS : EXIT_FAILURE;
248}
249
250/* ****************************************************************************************************************** */
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 ERROR(...)
Print a error message.
Definition logging.hpp:77
#define NOTICE(...)
Print a notice message.
Definition logging.hpp:85
#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
@ UNSPECIFIED
Unspecified.
Definition cam.hpp:44
const char * CamDataTypeToStr(const CamDataType type)
Stringify camera data type enum.
CamDataType
Camera data type (type of data).
Definition cam.hpp:86
@ UNSPECIFIED
Unspecified.
Definition cam.hpp:87
const char * CamDataFrmToStr(const CamDataFrm frm)
Stringify camera data frame type enum.
@ H265_NAL
H.265 NAL (HEVC) (video/hevc).
Definition cam.hpp:133
@ I_FRAME
I-frame (= Horizon camsys MC_H26[45]_NALU_TYPE_I).
Definition cam.hpp:179
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.
LoggingParams LoggingSetParams(const LoggingParams &params)
Configure logging.
@ RELATIVE
Relative timestamps (since first logging message, sssss.sss format).
Definition logging.hpp:258
@ TRACE
[7/debug] Extra debugging, only compiled-in in non-Release builds
Definition logging.hpp:206
@ INFO
[6/info] Interesting stuff, the default level (for apps)
Definition logging.hpp:204
@ AUTO
Automatic (default), use colours if stderr is an interactive terminal.
Definition logging.hpp:237
String utilities.
Definition string.hpp:38
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:36
@ RGB24
Packed RGB 8:8:8, 24bpp, RGBRGB... (= FFmpeg AV_PIX_FMT_RGB24).
Definition video.hpp:76
@ H265
H.265 High Efficiency Video Coding (HEVC).
Definition video.hpp:47
std::unique_ptr< VideoFrameDecoder > VideoFrameDecoderPtr
Pointer to a VideoFrameDecoder instance, see CreateVideoFrameDecoder().
Definition video.hpp:261
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:220
uint64_t ts_
Camera image time (middle of exposure) [CLOCK_REALTIME ns] (0 = invalid).
Definition cam.hpp:228
CamId cam_id_
= meta_.cam_id_ as enum (if value in range)
Definition cam.hpp:223
uint32_t dt_
Exposure time (duration) [ns] (0 = not available).
Definition cam.hpp:229
CamDataFmt fmt_
= meta_.fmt_ as enum (if value in range)
Definition cam.hpp:225
CamDataType type_
= meta_.type_ as enum (if value in range)
Definition cam.hpp:224
bool valid_
Data valid, successfully extracted from message.
Definition cam.hpp:222
std::vector< uint8_t > data_
Image/frame data, contents depends on fmt_ etc.
Definition cam.hpp:232
CamDataFrm frm_
= meta_.frm_ as enum (if value in range)
Definition cam.hpp:226
Fixposition SDK: Time utilities.
Fixposition SDK: Common types and type helpers.
Fixposition SDK: Video frame decoding.