sync.cpp
Go to the documentation of this file.
1 // License: Apache 2.0. See LICENSE file in root directory.
2 // Copyright(c) 2015 Intel Corporation. All Rights Reserved.
3 
5 #include "sync.h"
6 #include "environment.h"
7 
8 namespace librealsense
9 {
10  const int MAX_GAP = 1000;
11 
12 #define LOG_IF_ENABLE( OSTREAM, ENV ) \
13  while( ENV.log ) \
14  { \
15  LOG_DEBUG( OSTREAM ); \
16  break; \
17  }
18 
19 
20  matcher::matcher(std::vector<stream_id> streams_id)
21  : _streams_id(streams_id){}
22 
24  {
25  auto cb = begin_callback();
26  _callback(std::move(f), env);
27  }
28 
30  {
31  _callback = f;
32  }
33 
35  {
37  }
38 
40  {
41  return _name;
42  }
43 
44  bool matcher::get_active() const
45  {
46  return _active;
47  }
48 
49  void matcher::set_active(const bool active)
50  {
51  _active = active;
52  }
53 
54  const std::vector<stream_id>& matcher::get_streams() const
55  {
56  return _streams_id;
57  }
58 
59  const std::vector<rs2_stream>& matcher::get_streams_types() const
60  {
61  return _streams_type;
62  }
63 
65  {
67 
68  auto callbacks_inflight = _callback_inflight.get_size();
69  if (callbacks_inflight > 0)
70  {
71  LOG_WARNING(callbacks_inflight << " callbacks are still running on some other threads. Waiting until all callbacks return...");
72  }
73  // wait until user is done with all the stuff he chose to borrow
75  }
76 
78  :matcher({stream})
79  {
80  _streams_type = {stream_type};
81  _name = "I " + std::string(rs2_stream_to_string(stream_type));
82  }
83 
85  {
86  LOG_IF_ENABLE(_name << "--> " << *f.frame, env);
87 
88  sync(std::move(f), env);
89  }
90 
91  std::string create_composite_name(const std::vector<std::shared_ptr<matcher>>& matchers, const std::string& name)
92  {
93  std::stringstream s;
94  s<<"("<<name;
95 
96  for (auto&& matcher : matchers)
97  {
98  s<<matcher->get_name()<<" ";
99  }
100  s<<")";
101  return s.str();
102  }
103 
104  composite_matcher::composite_matcher(std::vector<std::shared_ptr<matcher>> matchers, std::string name)
105  {
106  for (auto&& matcher : matchers)
107  {
108  for (auto&& stream : matcher->get_streams())
109  {
111  {
112  sync(std::move(f), env);
113  });
114  _matchers[stream] = matcher;
115  _streams_id.push_back(stream);
116  }
117  for (auto&& stream : matcher->get_streams_types())
118  {
119  _streams_type.push_back(stream);
120  }
121  }
122 
123  _name = create_composite_name(matchers, name);
124  }
125 
127  {
128  LOG_IF_ENABLE("DISPATCH " << _name << "--> " <<*f.frame, env);
129 
130  clean_inactive_streams(f);
131  auto matcher = find_matcher(f);
132 
133  if (matcher)
134  {
135  update_last_arrived(f, matcher.get());
136  matcher->dispatch(std::move(f), env);
137  }
138  else
139  {
140  LOG_ERROR("didn't find any matcher for " << *f.frame << " will not be synchronized");
141  _callback(std::move(f), env);
142  }
143 
144  }
145 
146  std::shared_ptr<matcher> composite_matcher::find_matcher(const frame_holder& frame)
147  {
148  std::shared_ptr<matcher> matcher;
149  auto stream_id = frame.frame->get_stream()->get_unique_id();
150  auto stream_type = frame.frame->get_stream()->get_stream_type();
151 
152  auto sensor = frame.frame->get_sensor().get(); //TODO: Potential deadlock if get_sensor() gets a hold of the last reference of that sensor
153 
154  auto dev_exist = false;
155 
156  if (sensor)
157  {
158 
159  const device_interface* dev = nullptr;
160  try
161  {
162  dev = sensor->get_device().shared_from_this().get();
163  }
164  catch (const std::bad_weak_ptr&)
165  {
166  LOG_WARNING("Device destroyed");
167  }
168  if (dev)
169  {
170  dev_exist = true;
171  matcher = _matchers[stream_id];
172  if (!matcher)
173  {
174  std::ostringstream ss;
175  for (auto const & it : _matchers)
176  ss << ' ' << it.first;
177  LOG_DEBUG("stream id " << stream_id << " was not found; trying to create, existing streams=" << ss.str());
178  matcher = dev->create_matcher(frame);
179 
180  matcher->set_callback(
182  {
183  sync(std::move(f), env);
184  });
185 
186  for (auto stream : matcher->get_streams())
187  {
188  if (_matchers[stream])
189  {
190  _frames_queue.erase(_matchers[stream].get());
191  }
192  _matchers[stream] = matcher;
193  _streams_id.push_back(stream);
194  }
195  for (auto stream : matcher->get_streams_types())
196  {
197  _streams_type.push_back(stream);
198  }
199 
200  if (std::find(_streams_type.begin(), _streams_type.end(), stream_type) == _streams_type.end())
201  {
202  LOG_ERROR("Stream matcher not found! stream=" << rs2_stream_to_string(stream_type));
203  }
204  }
205  else if (!matcher->get_active())
206  {
207  matcher->set_active(true);
208  _frames_queue[matcher.get()].start();
209  }
210  }
211  }
212  else
213  {
214  LOG_DEBUG("sensor does not exist");
215  }
216 
217  if (!dev_exist)
218  {
219  matcher = _matchers[stream_id];
220  // We don't know what device this frame came from, so just store it under device NULL with ID matcher
221  if (!matcher)
222  {
223  if (_matchers[stream_id])
224  {
225  _frames_queue.erase(_matchers[stream_id].get());
226  }
227  _matchers[stream_id] = std::make_shared<identity_matcher>(stream_id, stream_type);
228  _streams_id.push_back(stream_id);
229  _streams_type.push_back(stream_type);
230  matcher = _matchers[stream_id];
231 
232  matcher->set_callback([&](frame_holder f, syncronization_environment env)
233  {
234  sync(std::move(f), env);
235  });
236  }
237  }
238  return matcher;
239  }
240 
242  {
243  for (auto& fq : _frames_queue)
244  {
245  fq.second.clear();
246  }
247  }
248 
249  std::string composite_matcher::frames_to_string(std::vector<librealsense::matcher*> matchers)
250  {
252  for (auto m : matchers)
253  {
254  frame_holder* f;
255  if(_frames_queue[m].peek(&f))
256  str += frame_to_string(*f->frame);
257  }
258  return str;
259  }
260 
262  {
263  LOG_IF_ENABLE("SYNC " << _name << "--> " << *f.frame, env);
264 
265  update_next_expected(f);
266  auto matcher = find_matcher(f);
267  if (!matcher)
268  {
269  LOG_ERROR("didn't find any matcher for " << frame_holder_to_string(f) << " will not be synchronized");
270  _callback(std::move(f), env);
271  return;
272  }
273 
274  _frames_queue[matcher.get()].enqueue(std::move(f));
275 
276  std::vector<frame_holder*> frames_arrived;
277  std::vector<librealsense::matcher*> frames_arrived_matchers;
278  std::vector<librealsense::matcher*> synced_frames;
279  std::vector<librealsense::matcher*> missing_streams;
280 
281  do
282  {
283  std::stringstream s;
284  auto old_frames = false;
285 
286  synced_frames.clear();
287  missing_streams.clear();
288  frames_arrived_matchers.clear();
289  frames_arrived.clear();
290 
291 
292  for (auto s = _frames_queue.begin(); s != _frames_queue.end(); s++)
293  {
294  frame_holder* f;
295  if (s->second.peek(&f))
296  {
297  frames_arrived.push_back(f);
298  frames_arrived_matchers.push_back(s->first);
299  }
300  else
301  {
302  missing_streams.push_back(s->first);
303  }
304  }
305 
306  if (frames_arrived.size() == 0)
307  break;
308 
309  frame_holder* curr_sync;
310  if (frames_arrived.size() > 0)
311  {
312  curr_sync = frames_arrived[0];
313  synced_frames.push_back(frames_arrived_matchers[0]);
314  }
315 
316  for (auto i = 1; i < frames_arrived.size(); i++)
317  {
318  if (are_equivalent(*curr_sync, *frames_arrived[i]))
319  {
320  synced_frames.push_back(frames_arrived_matchers[i]);
321  }
322  else if (is_smaller_than(*frames_arrived[i], *curr_sync))
323  {
324  old_frames = true;
325  synced_frames.clear();
326  synced_frames.push_back(frames_arrived_matchers[i]);
327  curr_sync = frames_arrived[i];
328  }
329  else
330  {
331  old_frames = true;
332  }
333  }
334 
335  if (!old_frames)
336  {
337  for (auto i : missing_streams)
338  {
339  if (!skip_missing_stream(synced_frames, i, env))
340  {
341  LOG_IF_ENABLE(" "<<frames_to_string(synced_frames )<<" Wait for missing stream: ", env);
342 
343  for (auto&& stream : i->get_streams())
344  LOG_IF_ENABLE(stream << " next expected " << std::fixed << _next_expected[i]<<" " , env);
345  synced_frames.clear();
346 
347  break;
348  }
349  else
350  {
351  LOG_IF_ENABLE(_name << " " << frames_to_string(synced_frames) << " Skipped missing stream: ", env);
352  for (auto&& stream : i->get_streams())
353  LOG_IF_ENABLE( stream << " next expected " << std::fixed << _next_expected[i] << " ", env);
354 
355  }
356 
357  }
358  }
359  else
360  {
361  LOG_IF_ENABLE(_name << " old frames: ", env);
362  }
363  if (synced_frames.size())
364  {
365  std::vector<frame_holder> match;
366  match.reserve(synced_frames.size());
367 
368  for (auto index : synced_frames)
369  {
371  int timeout_ms = 5000;
372  _frames_queue[index].dequeue(&frame, timeout_ms);
373  if (old_frames)
374  {
375  LOG_IF_ENABLE("--> " << frame_holder_to_string(frame) << " ", env);
376  }
377  match.push_back(std::move(frame));
378  }
379 
380  std::sort(match.begin(), match.end(), [](const frame_holder& f1, const frame_holder& f2)
381  {
382  return ((frame_interface*)f1)->get_stream()->get_unique_id() > ((frame_interface*)f2)->get_stream()->get_unique_id();
383  });
384 
385 
386  frame_holder composite = env.source->allocate_composite_frame(std::move(match));
387  if (composite.frame)
388  {
389  auto cb = begin_callback();
390  _callback(std::move(composite), env);
391  }
392  }
393  } while (synced_frames.size() > 0);
394  }
395 
396  frame_number_composite_matcher::frame_number_composite_matcher(std::vector<std::shared_ptr<matcher>> matchers)
397  :composite_matcher(matchers, "FN: ")
398  {
399  }
400 
402  {
404  }
405 
407  {
408  return a->get_frame_number() == b->get_frame_number();
409  }
411  {
412  return a->get_frame_number() < b->get_frame_number();
413  }
415  {
416  std::vector<stream_id> inactive_matchers;
417  for(auto m: _matchers)
418  {
419  if (_last_arrived[m.second.get()] && (fabs((long long)f->get_frame_number() - (long long)_last_arrived[m.second.get()])) > 5)
420  {
421  std::stringstream s;
422  s << "clean inactive stream in "<<_name;
423  for (auto stream : m.second->get_streams_types())
424  {
425  s << stream << " ";
426  }
427  LOG_DEBUG(s.str());
428 
429  inactive_matchers.push_back(m.first);
430  m.second->set_active(false);
431  }
432  }
433 
434  for(auto id: inactive_matchers)
435  {
436  _frames_queue[_matchers[id].get()].clear();
437  }
438  }
439 
440  bool frame_number_composite_matcher::skip_missing_stream(std::vector<matcher*> synced, matcher* missing, const syncronization_environment& env)
441  {
442  frame_holder* synced_frame;
443 
444  if(!missing->get_active())
445  return true;
446 
447  _frames_queue[synced[0]].peek(&synced_frame);
448 
449  auto next_expected = _next_expected[missing];
450 
451  if((*synced_frame)->get_frame_number() - next_expected > 4 || (*synced_frame)->get_frame_number() < next_expected)
452  {
453  return true;
454  }
455  return false;
456  }
457 
459  {
460  auto matcher = find_matcher(f);
461  if (!matcher)
462  {
463  LOG_ERROR("didn't find any matcher for " << frame_holder_to_string(f) << " will not be synchronized");
464  return;
465  }
466 
467  _next_expected[matcher.get()] = f.frame->get_frame_number()+1.;
468  }
469 
470  std::pair<double, double> extract_timestamps(frame_holder & a, frame_holder & b)
471  {
473  return{ a->get_frame_timestamp(), b->get_frame_timestamp() };
474  else
475  {
478  }
479  }
480 
481  timestamp_composite_matcher::timestamp_composite_matcher(std::vector<std::shared_ptr<matcher>> matchers)
482  :composite_matcher(matchers, "TS: ")
483  {
484  }
486  {
487  auto a_fps = get_fps(a);
488  auto b_fps = get_fps(b);
489 
490  auto min_fps = std::min(a_fps, b_fps);
491 
492  auto ts = extract_timestamps(a, b);
493 
494  return are_equivalent(ts.first, ts.second, min_fps);
495  }
496 
498  {
499  if (!a || !b)
500  {
501  return false;
502  }
503 
504  auto ts = extract_timestamps(a, b);
505 
506  return ts.first < ts.second;
507  }
508 
510  {
513 
514  else
515  _fps[m] = f->get_stream()->get_framerate();
516 
518  }
519 
521  {
522  uint32_t fps = 0;
524  {
526  }
527  LOG_DEBUG("fps " <<fps<<" "<< frame_holder_to_string(const_cast<frame_holder&>(f)));
528  return fps?fps:f.frame->get_stream()->get_framerate();
529  }
530 
532  {
533  auto fps = get_fps(f);
534  auto gap = 1000.f / (float)fps;
535 
536  auto matcher = find_matcher(f);
537  if (!matcher)
538  {
539  LOG_ERROR("didn't find any matcher for " << frame_holder_to_string(f) );
540  return;
541  }
542  _next_expected[matcher.get()] = f.frame->get_frame_timestamp() + gap;
544  LOG_DEBUG(_name << frame_holder_to_string(const_cast<frame_holder&>(f))<<"fps " <<fps<<" gap " <<gap<<" next_expected: "<< _next_expected[matcher.get()]);
545 
546  }
547 
549  {
550  if (f.is_blocking())
551  return;
552  std::vector<stream_id> dead_matchers;
553  auto now = environment::get_instance().get_time_service()->get_time();
554  for(auto m: _matchers)
555  {
556  auto threshold = _fps[m.second.get()] ? (1000 / _fps[m.second.get()]) * 5 : 500; //if frame of a specific stream didn't arrive for time equivalence to 5 frames duration
557  //this stream will be marked as "not active" in order to not stack the other streams
558  if(_last_arrived[m.second.get()] && (now - _last_arrived[m.second.get()]) > threshold)
559  {
560  std::stringstream s;
561  s << "clean inactive stream in "<<_name;
562  for (auto stream : m.second->get_streams_types())
563  {
564  s << stream << " ";
565  }
566  LOG_DEBUG(s.str());
567 
568  dead_matchers.push_back(m.first);
569  m.second->set_active(false);
570  }
571  }
572 
573  for(auto id: dead_matchers)
574  {
575  _frames_queue[_matchers[id].get()].clear();
576  _frames_queue.erase(_matchers[id].get());
577  }
578  }
579 
580  bool timestamp_composite_matcher::skip_missing_stream(std::vector<matcher*> synced, matcher* missing, const syncronization_environment& env)
581  {
582  if(!missing->get_active())
583  return true;
584 
585  frame_holder* synced_frame;
586 
587  _frames_queue[synced[0]].peek(&synced_frame);
588 
589  auto next_expected = _next_expected[missing];
590 
591  auto it = _next_expected_domain.find(missing);
592  if (it != _next_expected_domain.end())
593  {
594  if (it->second != (*synced_frame)->get_frame_timestamp_domain())
595  {
596  return false;
597  }
598  }
599  auto gap = 1000.f/ (float)get_fps(*synced_frame);
600  //next expected of the missing stream didn't updated yet
601  if((*synced_frame)->get_frame_timestamp() > next_expected && abs((*synced_frame)->get_frame_timestamp()- next_expected)<gap*10)
602  {
603  LOG_IF_ENABLE("next expected of the missing stream didn't updated yet", env);
604  return false;
605  }
606 
607  return !are_equivalent((*synced_frame)->get_frame_timestamp(), next_expected, get_fps(*synced_frame));
608  }
609 
611  {
612  auto gap = 1000.f / (float)fps;
613  return abs(a - b) < ((float)gap / (float)2) ;
614  }
615 
616  composite_identity_matcher::composite_identity_matcher(std::vector<std::shared_ptr<matcher>> matchers) :composite_matcher(matchers, "CI: ")
617  {}
618 
620  {
621  LOG_DEBUG("composite_identity_matcher: " << _name << " " << frame_holder_to_string(f));
622 
623  auto composite = dynamic_cast<const composite_frame *>(f.frame);
624  // Syncer have to output composite frame
625  if (!composite)
626  {
627  std::vector<frame_holder> match;
628  match.push_back(std::move(f));
629  frame_holder composite = env.source->allocate_composite_frame(std::move(match));
630  if (composite.frame)
631  {
632  auto cb = begin_callback();
633  _callback(std::move(composite), env);
634  }
635  else
636  {
637  LOG_ERROR("composite_identity_matcher: " << _name << " " << frame_holder_to_string(f) << " faild to create composite_frame, user callback will not be called");
638  }
639  }
640  else
641  _callback(std::move(f), env);
642 
643  }
644 } // namespace librealsense
virtual rs2_metadata_type get_frame_metadata(const rs2_frame_metadata_value &frame_metadata) const =0
timestamp_composite_matcher(std::vector< std::shared_ptr< matcher >> matchers)
Definition: sync.cpp:481
virtual rs2_timestamp_domain get_frame_timestamp_domain() const =0
GLboolean GLboolean GLboolean b
std::map< matcher *, double > _next_expected
Definition: sync.h:147
GLuint const GLchar * name
void clean_inactive_streams(frame_holder &f) override
Definition: sync.cpp:414
GLdouble s
sync_callback _callback
Definition: sync.h:108
std::shared_ptr< platform::time_service > get_time_service()
const std::vector< stream_id > & get_streams() const override
Definition: sync.cpp:54
virtual void dispatch(frame_holder f, const syncronization_environment &env)=0
void sync(frame_holder f, const syncronization_environment &env) override
Definition: sync.cpp:261
const GLfloat * m
Definition: glext.h:6814
#define LOG_WARNING(...)
Definition: src/types.h:241
bool is_smaller_than(frame_holder &a, frame_holder &b) override
Definition: sync.cpp:410
std::function< void(frame_holder, const syncronization_environment &)> sync_callback
Definition: sync.h:74
std::pair< double, double > extract_timestamps(frame_holder &a, frame_holder &b)
Definition: sync.cpp:470
std::vector< rs2_stream > _streams_type
Definition: sync.h:107
GLsizei const GLchar *const * string
identity_matcher(stream_id stream, rs2_stream streams_type)
Definition: sync.cpp:77
GLuint GLuint stream
Definition: glext.h:1790
void sort(sort_type m_sort_type, const std::string &in, const std::string &out)
matcher(std::vector< stream_id > streams_id={})
Definition: sync.cpp:20
GLenum GLuint id
void dispatch(frame_holder f, const syncronization_environment &env) override
Definition: sync.cpp:126
GLuint index
GLboolean GLboolean GLboolean GLboolean a
const std::vector< rs2_stream > & get_streams_types() const override
Definition: sync.cpp:59
callback_invocation_holder begin_callback()
Definition: sync.cpp:34
GLdouble f
virtual ~matcher()
Definition: sync.cpp:64
std::string frames_to_string(std::vector< librealsense::matcher * > matchers)
Definition: sync.cpp:249
std::map< stream_id, std::shared_ptr< matcher > > _matchers
Definition: sync.h:146
callbacks_heap _callback_inflight
Definition: sync.h:109
std::shared_ptr< matcher > find_matcher(const frame_holder &f)
Definition: sync.cpp:146
bool skip_missing_stream(std::vector< matcher * > synced, matcher *missing, const syncronization_environment &env) override
Definition: sync.cpp:440
unsigned int uint32_t
Definition: stdint.h:80
unsigned int get_fps(const frame_holder &f)
Definition: sync.cpp:520
bool is_smaller_than(frame_holder &a, frame_holder &b) override
Definition: sync.cpp:497
bool are_equivalent(frame_holder &a, frame_holder &b) override
Definition: sync.cpp:406
void dispatch(frame_holder f, const syncronization_environment &env) override
Definition: sync.cpp:84
std::string frame_to_string(const frame_interface &f)
Definition: streaming.cpp:14
def find(dir, mask)
Definition: file.py:25
composite_identity_matcher(std::vector< std::shared_ptr< matcher >> matchers)
Definition: sync.cpp:616
std::string _name
Definition: sync.h:110
virtual rs2_time_t get_frame_timestamp() const =0
#define LOG_ERROR(...)
Definition: src/types.h:242
std::string create_composite_name(const std::vector< std::shared_ptr< matcher >> &matchers, const std::string &name)
Definition: sync.cpp:91
bool are_equivalent(frame_holder &a, frame_holder &b) override
Definition: sync.cpp:485
synthetic_source_interface * source
Definition: sync.h:67
std::map< matcher *, single_consumer_frame_queue< frame_holder > > _frames_queue
Definition: sync.h:145
rs2_stream
Streams are different types of data provided by RealSense devices.
Definition: rs_sensor.h:42
virtual std::shared_ptr< sensor_interface > get_sensor() const =0
static environment & get_instance()
void update_next_expected(const frame_holder &f) override
Definition: sync.cpp:458
void update_next_expected(const frame_holder &f) override
Definition: sync.cpp:531
virtual std::shared_ptr< stream_profile_interface > get_stream() const =0
void set_active(const bool active)
Definition: sync.cpp:49
std::map< matcher *, rs2_timestamp_domain > _next_expected_domain
Definition: sync.h:148
frame_interface * frame
Definition: streaming.h:126
std::map< matcher *, unsigned long long > _last_arrived
Definition: sync.h:180
frame_number_composite_matcher(std::vector< std::shared_ptr< matcher >> matchers)
Definition: sync.cpp:396
virtual void update_last_arrived(frame_holder &f, matcher *m) override
Definition: sync.cpp:401
const char * rs2_stream_to_string(rs2_stream stream)
Definition: rs.cpp:1262
int stream_id
Definition: sync.h:17
static auto it
std::map< matcher *, double > _last_arrived
Definition: sync.h:197
virtual std::string get_name() const override
Definition: sync.cpp:39
int min(int a, int b)
Definition: lz4s.c:73
composite_matcher(std::vector< std::shared_ptr< matcher >> matchers, std::string name)
Definition: sync.cpp:104
typename::boost::move_detail::remove_reference< T >::type && move(T &&t) BOOST_NOEXCEPT
void sync(frame_holder f, const syncronization_environment &env) override
Definition: sync.cpp:619
#define LOG_IF_ENABLE(OSTREAM, ENV)
Definition: sync.cpp:12
std::string frame_holder_to_string(const frame_holder &f)
Definition: streaming.cpp:9
virtual void update_last_arrived(frame_holder &f, matcher *m) override
Definition: sync.cpp:509
int i
const int MAX_GAP
Definition: sync.cpp:10
virtual void set_callback(sync_callback f) override
Definition: sync.cpp:29
bool skip_missing_stream(std::vector< matcher * > synced, matcher *missing, const syncronization_environment &env) override
Definition: sync.cpp:580
#define LOG_DEBUG(...)
Definition: src/types.h:239
virtual void stop() override
Definition: sync.cpp:241
void clean_inactive_streams(frame_holder &f) override
Definition: sync.cpp:548
bool is_blocking() const
Definition: streaming.h:157
virtual std::shared_ptr< matcher > create_matcher(const frame_holder &frame) const =0
std::map< matcher *, unsigned int > _fps
Definition: sync.h:198
virtual frame_interface * allocate_composite_frame(std::vector< frame_holder > frames)=0
virtual unsigned long long get_frame_number() const =0
virtual void sync(frame_holder f, const syncronization_environment &env) override
Definition: sync.cpp:23
bool get_active() const
Definition: sync.cpp:44
std::vector< stream_id > _streams_id
Definition: sync.h:106
virtual bool supports_frame_metadata(const rs2_frame_metadata_value &frame_metadata) const =0


librealsense2
Author(s): Sergey Dorodnicov , Doron Hirshberg , Mark Horn , Reagan Lopez , Itay Carpis
autogenerated on Mon May 3 2021 02:50:11