libvisiontransfer  9.0.3
asynctransfer.cpp
1 /*******************************************************************************
2  * Copyright (c) 2021 Nerian Vision GmbH
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *******************************************************************************/
14 
15 #if __GNUC__ == 4 && __GNUC_MINOR__ < 9
16 // This is a very ugly workaround for GCC bug 54562. If omitted,
17 // passing timeouts to collectReceivedImage() is broken.
18 #include <bits/c++config.h>
19 #undef _GLIBCXX_USE_CLOCK_MONOTONIC
20 #endif
21 
22 #include <iostream>
23 #include <functional>
24 #include <stdexcept>
25 #include <thread>
26 #include <condition_variable>
27 #include <chrono>
28 #include <mutex>
29 #include <vector>
30 #include <cstring>
31 #include <algorithm>
32 #include "visiontransfer/asynctransfer.h"
33 #include "visiontransfer/alignedallocator.h"
34 
35 using namespace std;
36 using namespace visiontransfer;
37 using namespace visiontransfer::internal;
38 
39 namespace visiontransfer {
40 
41 /*************** Pimpl class containing all private members ***********/
42 
43 class AsyncTransfer::Pimpl {
44 public:
45  Pimpl(const char* address, const char* service,
46  ImageProtocol::ProtocolType protType, bool server,
47  int bufferSize, int maxUdpPacketSize);
48  ~Pimpl();
49 
50  // Redeclaration of public members
51  void sendImageSetAsync(const ImageSet& imageSet, bool deleteData);
52  bool collectReceivedImageSet(ImageSet& imageSet, double timeout);
53  int getNumDroppedFrames() const;
54  bool isConnected() const;
55  void disconnect();
56  std::string getRemoteAddress() const;
57  bool tryAccept();
58 
59 private:
60  static constexpr int NUM_BUFFERS = 6;
61  static constexpr int SEND_THREAD_SHORT_WAIT_MS = 1;
62  static constexpr int SEND_THREAD_LONG_WAIT_MS = 10;
63 
64  // The encapsulated image transfer object
65  ImageTransfer imgTrans;
66 
67  // Variable for controlling thread termination
68  volatile bool terminate;
69 
70  // There are two threads, one for sending and one for receiving.
71  // Each has a mutex and condition variable for synchronization.
72  std::thread sendThread;
73  std::mutex sendMutex;
74  std::condition_variable sendCond;
75  std::condition_variable sendWaitCond;
76 
77  std::thread receiveThread;
78  std::timed_mutex receiveMutex;
79  std::condition_variable_any receiveCond;
80  std::condition_variable_any receiveWaitCond;
81 
82  // Objects for exchanging images with the send and receive threads
83  ImageSet receivedSet;
84  std::vector<unsigned char, AlignedAllocator<unsigned char> > receivedData[NUM_BUFFERS];
85  volatile bool newDataReceived;
86 
87  ImageSet sendImageSet;
88  bool sendSetValid;
89  bool deleteSendData;
90 
91  // Exception occurred in one of the threads
92  std::exception_ptr receiveException;
93  std::exception_ptr sendException;
94 
95  bool sendThreadCreated;
96  bool receiveThreadCreated;
97 
98  // Main loop for sending thread
99  void sendLoop();
100 
101  // Main loop for receiving;
102  void receiveLoop();
103 
104  void createSendThread();
105 };
106 
107 /******************** Stubs for all public members ********************/
108 
109 AsyncTransfer::AsyncTransfer(const char* address, const char* service,
110  ImageProtocol::ProtocolType protType, bool server,
111  int bufferSize, int maxUdpPacketSize)
112  : pimpl(new Pimpl(address, service, protType, server, bufferSize, maxUdpPacketSize)) {
113 }
114 
115 AsyncTransfer::AsyncTransfer(const DeviceInfo& device, int bufferSize, int maxUdpPacketSize)
116  : pimpl(new Pimpl(device.getIpAddress().c_str(), "7681", static_cast<ImageProtocol::ProtocolType>(device.getNetworkProtocol()),
117  false, bufferSize, maxUdpPacketSize)) {
118 }
119 
120 AsyncTransfer::~AsyncTransfer() {
121  delete pimpl;
122 }
123 
124 void AsyncTransfer::sendImageSetAsync(const ImageSet& imageSet, bool deleteData) {
125  pimpl->sendImageSetAsync(imageSet, deleteData);
126 }
127 
128 bool AsyncTransfer::collectReceivedImageSet(ImageSet& imageSet, double timeout) {
129  return pimpl->collectReceivedImageSet(imageSet, timeout);
130 }
131 
133  return pimpl->getNumDroppedFrames();
134 }
135 
137  return pimpl->isConnected();
138 }
139 
141  return pimpl->disconnect();
142 }
143 
144 std::string AsyncTransfer::getRemoteAddress() const {
145  return pimpl->getRemoteAddress();
146 }
147 
149  return pimpl->tryAccept();
150 }
151 
152 /******************** Implementation in pimpl class *******************/
153 
154 AsyncTransfer::Pimpl::Pimpl(const char* address, const char* service,
155  ImageProtocol::ProtocolType protType, bool server,
156  int bufferSize, int maxUdpPacketSize)
157  : imgTrans(address, service, protType, server, bufferSize, maxUdpPacketSize),
158  terminate(false), newDataReceived(false), sendSetValid(false),
159  deleteSendData(false), sendThreadCreated(false),
160  receiveThreadCreated(false) {
161 
162  if(server) {
163  createSendThread();
164  }
165 }
166 
167 AsyncTransfer::Pimpl::~Pimpl() {
168  terminate = true;
169 
170  sendCond.notify_all();
171  receiveCond.notify_all();
172  sendWaitCond.notify_all();
173  receiveWaitCond.notify_all();
174 
175  if(sendThreadCreated && sendThread.joinable()) {
176  sendThread.join();
177  }
178 
179  if(receiveThreadCreated && receiveThread.joinable()) {
180  receiveThread.join();
181  }
182 
183  if(sendSetValid && deleteSendData) {
184  delete[] sendImageSet.getPixelData(0);
185  delete[] sendImageSet.getPixelData(1);
186  }
187 }
188 
189 void AsyncTransfer::Pimpl::createSendThread() {
190  if(!sendThreadCreated) {
191  // Lazy initialization of the send thread as it is not always needed
192  unique_lock<mutex> lock(sendMutex);
193  sendThread = thread(bind(&AsyncTransfer::Pimpl::sendLoop, this));
194  sendThreadCreated = true;
195  }
196 }
197 
198 void AsyncTransfer::Pimpl::sendImageSetAsync(const ImageSet& imageSet, bool deleteData) {
199  createSendThread();
200 
201  while(true) {
202  unique_lock<mutex> lock(sendMutex);
203 
204  // Test for errors
205  if(sendException) {
206  std::rethrow_exception(sendException);
207  }
208 
209  if(!sendSetValid) {
210  sendImageSet = imageSet;
211  sendSetValid = true;
212  deleteSendData = deleteData;
213 
214  // Wake up the sender thread
215  sendCond.notify_one();
216 
217  return;
218  } else {
219  // Wait for old data to be processed first
220  sendWaitCond.wait(lock);
221  }
222  }
223 }
224 
225 bool AsyncTransfer::Pimpl::collectReceivedImageSet(ImageSet& imageSet, double timeout) {
226  if(!receiveThreadCreated) {
227  // Lazy initialization of receive thread
228  unique_lock<timed_mutex> lock(receiveMutex);
229  receiveThreadCreated = true;
230  receiveThread = thread(bind(&AsyncTransfer::Pimpl::receiveLoop, this));
231  }
232 
233  // Acquire mutex
234  unique_lock<timed_mutex> lock(receiveMutex, std::defer_lock);
235  if(timeout < 0) {
236  lock.lock();
237  } else {
238  std::chrono::steady_clock::time_point lockStart =
239  std::chrono::steady_clock::now();
240  if(!lock.try_lock_for(std::chrono::microseconds(static_cast<unsigned int>(timeout*1e6)))) {
241  // Timed out
242  return false;
243  }
244 
245  // Update timeout
246  unsigned int lockDuration = static_cast<unsigned int>(std::chrono::duration_cast<std::chrono::microseconds>(
247  std::chrono::steady_clock::now() - lockStart).count());
248  timeout = std::max(0.0, timeout - lockDuration*1e-6);
249  }
250 
251  // Test for errors
252  if(receiveException) {
253  std::rethrow_exception(receiveException);
254  }
255 
256  if(timeout == 0 && !newDataReceived) {
257  // No image has been received and we are not blocking
258  return false;
259  }
260 
261  // If there is no data yet then keep on waiting
262  if(!newDataReceived) {
263  if(timeout < 0) {
264  while(!terminate && !receiveException && !newDataReceived) {
265  receiveCond.wait(lock);
266  }
267  } else {
268  receiveCond.wait_for(lock, std::chrono::microseconds(static_cast<unsigned int>(timeout*1e6)));
269  }
270  }
271 
272  // Test for errors again
273  if(receiveException) {
274  std::rethrow_exception(receiveException);
275  }
276 
277  if(newDataReceived) {
278  // Get the received image
279  imageSet = receivedSet;
280 
281  newDataReceived = false;
282  receiveWaitCond.notify_one();
283 
284  return true;
285  } else {
286  return false;
287  }
288 }
289 
290 void AsyncTransfer::Pimpl::sendLoop() {
291  {
292  // Delay the thread start
293  unique_lock<mutex> lock(sendMutex);
294  }
295 
296  ImageSet imgSet;
297  bool deleteSet = false;
298 
299  try {
300  while(!terminate) {
301  // Wait for next image
302  {
303  unique_lock<mutex> lock(sendMutex);
304  // Wait for next frame to be queued
305  bool firstWait = true;
306  while(!terminate && !sendSetValid) {
307  imgTrans.transferData();
308  sendCond.wait_for(lock, std::chrono::milliseconds(
309  firstWait ? SEND_THREAD_SHORT_WAIT_MS : SEND_THREAD_LONG_WAIT_MS));
310  firstWait = false;
311  }
312  if(!sendSetValid) {
313  continue;
314  }
315 
316  imgSet = sendImageSet;
317  deleteSet = deleteSendData;
318  sendSetValid = false;
319 
320  sendWaitCond.notify_one();
321  }
322 
323  imgTrans.setTransferImageSet(imgSet);
324  while(!terminate) {
325  ImageTransfer::TransferStatus status = imgTrans.transferData();
327  break;
328  }
329  std::this_thread::sleep_for(std::chrono::milliseconds(SEND_THREAD_LONG_WAIT_MS));
330  }
331 
332  if(deleteSet) {
333  for (int i=0; i<imgSet.getNumberOfImages(); ++i) {
334  delete[] imgSet.getPixelData(i);
335  }
336  deleteSet = false;
337  }
338  }
339  } catch(...) {
340  // Store the exception for later
341  if(!sendException) {
342  sendException = std::current_exception();
343  }
344  sendWaitCond.notify_all();
345 
346  // Don't forget to free the memory
347  if(deleteSet) {
348  for (int i=0; i<imgSet.getNumberOfImages(); ++i) {
349  delete[] imgSet.getPixelData(i);
350  }
351  deleteSet = false;
352  }
353  }
354 }
355 
356 void AsyncTransfer::Pimpl::receiveLoop() {
357  {
358  // Delay the thread start
359  unique_lock<timed_mutex> lock(receiveMutex);
360  }
361 
362  try {
363  ImageSet currentSet;
364  int bufferIndex = 0;
365 
366  while(!terminate) {
367  // Receive new image
368  if(!imgTrans.receiveImageSet(currentSet)) {
369  // No image available
370  continue;
371  }
372 
373  // Copy the pixel data
374  for(int i=0;i<currentSet.getNumberOfImages();i++) {
375  int bytesPerPixel = currentSet.getBytesPerPixel(i);
376  int newStride = currentSet.getWidth() * bytesPerPixel;
377  int totalSize = currentSet.getHeight() * newStride;
378  if(static_cast<int>(receivedData[i + bufferIndex].size()) < totalSize) {
379  receivedData[i + bufferIndex].resize(totalSize);
380  }
381  if(newStride == currentSet.getRowStride(i)) {
382  memcpy(&receivedData[i + bufferIndex][0], currentSet.getPixelData(i),
383  newStride*currentSet.getHeight());
384  } else {
385  for(int y = 0; y<currentSet.getHeight(); y++) {
386  memcpy(&receivedData[i + bufferIndex][y*newStride],
387  &currentSet.getPixelData(i)[y*currentSet.getRowStride(i)],
388  newStride);
389  }
390  currentSet.setRowStride(i, newStride);
391  }
392  currentSet.setPixelData(i, &receivedData[i + bufferIndex][0]);
393  }
394 
395  {
396  unique_lock<timed_mutex> lock(receiveMutex);
397 
398  // Wait for previously received data to be processed
399  while(newDataReceived) {
400  receiveWaitCond.wait_for(lock, std::chrono::milliseconds(100));
401  if(terminate) {
402  return;
403  }
404  }
405 
406  // Notify that a new image set has been received
407  newDataReceived = true;
408  receivedSet = currentSet;
409  receiveCond.notify_one();
410  }
411 
412  // Increment index for data buffers
413  bufferIndex = (bufferIndex + currentSet.getNumberOfImages()) % NUM_BUFFERS;
414  }
415  } catch(...) {
416  // Store the exception for later
417  if(!receiveException) {
418  receiveException = std::current_exception();
419  }
420  receiveCond.notify_all();
421  }
422 }
423 
424 bool AsyncTransfer::Pimpl::isConnected() const {
425  return imgTrans.isConnected();
426 }
427 
428 void AsyncTransfer::Pimpl::disconnect() {
429  imgTrans.disconnect();
430 }
431 
432 std::string AsyncTransfer::Pimpl::getRemoteAddress() const {
433  return imgTrans.getRemoteAddress();
434 }
435 
436 int AsyncTransfer::Pimpl::getNumDroppedFrames() const {
437  return imgTrans.getNumDroppedFrames();
438 }
439 
440 bool AsyncTransfer::Pimpl::tryAccept() {
441  return imgTrans.tryAccept();
442 }
443 
444 constexpr int AsyncTransfer::Pimpl::NUM_BUFFERS;
445 constexpr int AsyncTransfer::Pimpl::SEND_THREAD_SHORT_WAIT_MS;
446 constexpr int AsyncTransfer::Pimpl::SEND_THREAD_LONG_WAIT_MS;
447 
448 } // namespace
449 
int getRowStride(int imageNumber) const
Returns the row stride for the pixel data of one image.
Definition: imageset.h:218
void setPixelData(int imageNumber, unsigned char *pixelData)
Sets the pixel data for the given image.
Definition: imageset.h:134
The operation would block and blocking as been disabled.
Definition: imagetransfer.h:51
AsyncTransfer(const char *address, const char *service="7681", ImageProtocol::ProtocolType protType=ImageProtocol::PROTOCOL_UDP, bool server=false, int bufferSize=16 *1048576, int maxUdpPacketSize=1472)
Creates a new transfer object.
bool tryAccept()
Tries to accept a client connection.
int getNumberOfImages() const
Returns the number of images in this set.
Definition: imageset.h:404
Class for synchronous transfer of image sets.
Definition: imagetransfer.h:36
int getHeight() const
Returns the height of each image.
Definition: imageset.h:207
A lightweight protocol for transferring image sets.
Definition: imageprotocol.h:40
bool isConnected() const
Returns true if a remote connection is established.
void disconnect()
Terminates the current connection.
int getBytesPerPixel(int imageNumber) const
Returns the number of bytes that are required to store one image pixel.
Definition: imageset.h:372
int getNumDroppedFrames() const
Returns the number of frames that have been dropped since connecting to the current remote host...
ProtocolType
Supported network protocols.
Definition: imageprotocol.h:43
std::string getRemoteAddress() const
Returns the address of the remote host.
void setRowStride(int imageNumber, int stride)
Sets a new row stride for the pixel data of one image.
Definition: imageset.h:104
Aggregates information about a discovered device.
Definition: deviceinfo.h:47
unsigned char * getPixelData(int imageNumber) const
Returns the pixel data for the given image.
Definition: imageset.h:272
A set of one to three images, but usually two (the left camera image and the disparity map)...
Definition: imageset.h:38
int getWidth() const
Returns the width of each image.
Definition: imageset.h:202
bool collectReceivedImageSet(ImageSet &imageSet, double timeout=-1)
Collects the asynchronously received image.
void sendImageSetAsync(const ImageSet &imageSet, bool deleteData=false)
Starts an asynchronous transmission of the given image set.
TransferStatus
The result of a partial image transfer.
Definition: imagetransfer.h:39
Nerian Vision Technologies