libvisiontransfer  8.1.0
imagetransfer.cpp
1 /*******************************************************************************
2  * Copyright (c) 2020 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 #include <cstdio>
16 #include <iostream>
17 #include <cstring>
18 #include <memory>
19 #include <string>
20 #include <vector>
21 #include <mutex>
22 #include "visiontransfer/imagetransfer.h"
23 #include "visiontransfer/exceptions.h"
24 #include "visiontransfer/datablockprotocol.h"
25 #include "visiontransfer/networking.h"
26 
27 using namespace std;
28 using namespace visiontransfer;
29 using namespace visiontransfer::internal;
30 
31 namespace visiontransfer {
32 
33 /*************** Pimpl class containing all private members ***********/
34 
35 class ImageTransfer::Pimpl {
36 public:
37  Pimpl(const char* address, const char* service, ImageProtocol::ProtocolType protType,
38  bool server, int bufferSize, int maxUdpPacketSize);
39  ~Pimpl();
40 
41  // Redeclaration of public members
42  void setRawTransferData(const ImageSet& metaData, const std::vector<unsigned char*>& rawData,
43  int firstTileWidth = 0, int secondTileWidth = 0, int validBytes = 0x7FFFFFFF);
44  void setRawValidBytes(const std::vector<int>& validBytes);
45  void setTransferImageSet(const ImageSet& imageSet);
46  TransferStatus transferData();
47  bool receiveImageSet(ImageSet& imageSet);
48  bool receivePartialImageSet(ImageSet& imageSet, int& validRows, bool& complete);
49  int getNumDroppedFrames() const;
50  bool isConnected() const;
51  void disconnect();
52  std::string getRemoteAddress() const;
53  bool tryAccept();
54 
55  std::string statusReport();
56 private:
57  // Configuration parameters
59  bool isServer;
60  int bufferSize;
61  int maxUdpPacketSize;
62 
63  // Thread synchronization
64  std::recursive_mutex receiveMutex;
65  std::recursive_mutex sendMutex;
66 
67  // Transfer related members
68  SOCKET clientSocket;
69  SOCKET tcpServerSocket;
70  sockaddr_in remoteAddress;
71 
72  // Object for encoding and decoding the network protocol
73  std::unique_ptr<ImageProtocol> protocol;
74 
75  // Outstanding network message that still has to be transferred
76  int currentMsgLen;
77  int currentMsgOffset;
78  const unsigned char* currentMsg;
79 
80  // Socket configuration
81  void setSocketOptions();
82 
83  // Network socket initialization
84  void initTcpServer(const addrinfo* addressInfo);
85  void initTcpClient(const addrinfo* addressInfo);
86  void initUdp(const addrinfo* addressInfo);
87 
88  // Data reception
89  bool receiveNetworkData(bool block);
90 
91  // Data transmission
92  bool sendNetworkMessage(const unsigned char* msg, int length);
93  void sendPendingControlMessages();
94 
95  bool selectSocket(bool read, bool wait);
96 };
97 
98 /******************** Stubs for all public members ********************/
99 
100 ImageTransfer::ImageTransfer(const char* address, const char* service,
101  ImageProtocol::ProtocolType protType, bool server, int bufferSize, int maxUdpPacketSize):
102  pimpl(new Pimpl(address, service, protType, server, bufferSize, maxUdpPacketSize)) {
103  // All initialization in the pimpl class
104 }
105 
106 ImageTransfer::ImageTransfer(const DeviceInfo& device, int bufferSize, int maxUdpPacketSize):
107  pimpl(new Pimpl(device.getIpAddress().c_str(), "7681", static_cast<ImageProtocol::ProtocolType>(device.getNetworkProtocol()),
108  false, bufferSize, maxUdpPacketSize)) {
109  // All initialization in the pimpl class
110 }
111 
112 ImageTransfer::~ImageTransfer() {
113  delete pimpl;
114 }
115 
116 void ImageTransfer::setRawTransferData(const ImageSet& metaData, const std::vector<unsigned char*>& rawData,
117  int firstTileWidth, int secondTileWidth, int validBytes) {
118  pimpl->setRawTransferData(metaData, rawData, firstTileWidth, secondTileWidth, validBytes);
119 }
120 
121 void ImageTransfer::setRawValidBytes(const std::vector<int>& validBytes) {
122  pimpl->setRawValidBytes(validBytes);
123 }
124 
126  pimpl->setTransferImageSet(imageSet);
127 }
128 
130  return pimpl->transferData();
131 }
132 
134  return pimpl->receiveImageSet(imageSet);
135 }
136 
137 bool ImageTransfer::receivePartialImageSet(ImageSet& imageSet, int& validRows, bool& complete) {
138  return pimpl->receivePartialImageSet(imageSet, validRows, complete);
139 }
140 
142  return pimpl->getNumDroppedFrames();
143 }
144 
146  return pimpl->isConnected();
147 }
148 
150  pimpl->disconnect();
151 }
152 
153 std::string ImageTransfer::getRemoteAddress() const {
154  return pimpl->getRemoteAddress();
155 }
156 
158  return pimpl->tryAccept();
159 }
160 
161 /******************** Implementation in pimpl class *******************/
162 ImageTransfer::Pimpl::Pimpl(const char* address, const char* service,
163  ImageProtocol::ProtocolType protType, bool server, int
164  bufferSize, int maxUdpPacketSize)
165  : protType(protType), isServer(server), bufferSize(bufferSize),
166  maxUdpPacketSize(maxUdpPacketSize),
167  clientSocket(INVALID_SOCKET), tcpServerSocket(INVALID_SOCKET),
168  currentMsgLen(0), currentMsgOffset(0), currentMsg(nullptr) {
169 
170  Networking::initNetworking();
171 #ifndef _WIN32
172  // We don't want to be interrupted by the pipe signal
173  signal(SIGPIPE, SIG_IGN);
174 #endif
175 
176  memset(&remoteAddress, 0, sizeof(remoteAddress));
177 
178  // If address is null we use the any address
179  if(address == nullptr || string(address) == "") {
180  address = "0.0.0.0";
181  }
182 
183  addrinfo* addressInfo = Networking::resolveAddress(address, service);
184 
185  try {
186  if(protType == ImageProtocol::PROTOCOL_UDP) {
187  initUdp(addressInfo);
188  } else if(protType == ImageProtocol::PROTOCOL_TCP && isServer) {
189  initTcpServer(addressInfo);
190  } else {
191  initTcpClient(addressInfo);
192  }
193  } catch(...) {
194  freeaddrinfo(addressInfo);
195  throw;
196  }
197 
198  if(addressInfo != nullptr) {
199  freeaddrinfo(addressInfo);
200  }
201 }
202 
203 ImageTransfer::Pimpl::~Pimpl() {
204  if(clientSocket != INVALID_SOCKET) {
205  Networking::closeSocket(clientSocket);
206  }
207  if(tcpServerSocket != INVALID_SOCKET) {
208  Networking::closeSocket(tcpServerSocket);
209  }
210 }
211 
212 void ImageTransfer::Pimpl::initTcpClient(const addrinfo* addressInfo) {
213  protocol.reset(new ImageProtocol(isServer, ImageProtocol::PROTOCOL_TCP));
214  clientSocket = Networking::connectTcpSocket(addressInfo);
215  memcpy(&remoteAddress, addressInfo->ai_addr, sizeof(remoteAddress));
216 
217  // Set special socket options
218  setSocketOptions();
219 }
220 
221 void ImageTransfer::Pimpl::initTcpServer(const addrinfo* addressInfo) {
222  protocol.reset(new ImageProtocol(isServer, ImageProtocol::PROTOCOL_TCP));
223 
224  // Create socket
225  tcpServerSocket = ::socket(addressInfo->ai_family, addressInfo->ai_socktype,
226  addressInfo->ai_protocol);
227  if (tcpServerSocket == INVALID_SOCKET) {
228  TransferException ex("Error opening socket: " + string(strerror(errno)));
229  throw ex;
230  }
231 
232  // Enable reuse address
233  Networking::enableReuseAddress(tcpServerSocket, true);
234 
235  // Open a server port
236  Networking::bindSocket(tcpServerSocket, addressInfo);
237  clientSocket = INVALID_SOCKET;
238 
239  // Make the server socket non-blocking
240  Networking::setSocketBlocking(tcpServerSocket, false);
241 
242  // Listen on port
243  listen(tcpServerSocket, 1);
244 }
245 
246 void ImageTransfer::Pimpl::initUdp(const addrinfo* addressInfo) {
247  protocol.reset(new ImageProtocol(isServer, ImageProtocol::PROTOCOL_UDP, maxUdpPacketSize));
248  // Create sockets
249  clientSocket = socket(AF_INET, SOCK_DGRAM, 0);
250  if(clientSocket == INVALID_SOCKET) {
251  TransferException ex("Error creating receive socket: " + string(strerror(errno)));
252  throw ex;
253  }
254 
255  // Enable reuse address
256  Networking::enableReuseAddress(clientSocket, true);
257 
258  // Bind socket to port
259  if(isServer && addressInfo != nullptr) {
260  Networking::bindSocket(clientSocket, addressInfo);
261  }
262 
263  if(!isServer) {
264  memcpy(&remoteAddress, addressInfo->ai_addr, sizeof(remoteAddress));
265  }
266 
267  // Set special socket options
268  setSocketOptions();
269 }
270 
271 bool ImageTransfer::Pimpl::tryAccept() {
272  if(protType != ImageProtocol::PROTOCOL_TCP || ! isServer) {
273  throw TransferException("Connections can only be accepted in tcp server mode");
274  }
275 
276  unique_lock<recursive_mutex> recvLock(receiveMutex);
277  unique_lock<recursive_mutex> sendLock(sendMutex);
278 
279  // Accept one connection
280  SOCKET newSocket = Networking::acceptConnection(tcpServerSocket, remoteAddress);
281  if(newSocket == INVALID_SOCKET) {
282  // No connection
283  return false;
284  }
285 
286  if(clientSocket != INVALID_SOCKET) {
287  Networking::closeSocket(clientSocket);
288  }
289  clientSocket = newSocket;
290 
291  // Set special socket options
292  setSocketOptions();
293 
294  // Reset connection data
295  protocol->resetTransfer();
296  protocol->resetReception();
297  currentMsg = nullptr;
298 
299  return true;
300 }
301 
302 std::string ImageTransfer::Pimpl::getRemoteAddress() const {
303  unique_lock<recursive_mutex> lock(const_cast<recursive_mutex&>(sendMutex)); // either mutex will work
304 
305  if(remoteAddress.sin_family != AF_INET) {
306  return "";
307  }
308 
309  char strPort[11];
310  snprintf(strPort, sizeof(strPort), ":%d", remoteAddress.sin_port);
311 
312  return string(inet_ntoa(remoteAddress.sin_addr)) + strPort;
313 }
314 
315 void ImageTransfer::Pimpl::setSocketOptions() {
316  // Set the socket buffer sizes
317  if(bufferSize > 0) {
318  setsockopt(clientSocket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char*>(&bufferSize), sizeof(bufferSize));
319  setsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&bufferSize), sizeof(bufferSize));
320  }
321 
322  Networking::setSocketTimeout(clientSocket, 500);
323  Networking::setSocketBlocking(clientSocket, true);
324 }
325 
326 void ImageTransfer::Pimpl::setRawTransferData(const ImageSet& metaData,
327  const std::vector<unsigned char*>& rawDataVec, int firstTileWidth, int secondTileWidth, int validBytes) {
328  unique_lock<recursive_mutex> sendLock(sendMutex);
329  protocol->setRawTransferData(metaData, rawDataVec, firstTileWidth, secondTileWidth, validBytes);
330  currentMsg = nullptr;
331 }
332 
333 void ImageTransfer::Pimpl::setRawValidBytes(const std::vector<int>& validBytes) {
334  unique_lock<recursive_mutex> sendLock(sendMutex);
335  protocol->setRawValidBytes(validBytes);
336 }
337 
338 void ImageTransfer::Pimpl::setTransferImageSet(const ImageSet& imageSet) {
339  unique_lock<recursive_mutex> sendLock(sendMutex);
340  protocol->setTransferImageSet(imageSet);
341  currentMsg = nullptr;
342 }
343 
344 ImageTransfer::TransferStatus ImageTransfer::Pimpl::transferData() {
345  unique_lock<recursive_mutex> lock(sendMutex);
346 
347  // First receive data in case a control message arrives
348  if(protType == ImageProtocol::PROTOCOL_UDP) {
349  receiveNetworkData(false);
350  }
351 
352  if(remoteAddress.sin_family != AF_INET || !protocol->isConnected()) {
353  return NOT_CONNECTED;
354  }
355 
356  // Get first message to transfer
357  if(currentMsg == nullptr) {
358  currentMsgOffset = 0;
359  currentMsg = protocol->getTransferMessage(currentMsgLen);
360 
361  if(currentMsg == nullptr) {
362  if(protocol->transferComplete()) {
363  return ALL_TRANSFERRED;
364  } else {
365  return NO_VALID_DATA;
366  }
367  }
368  }
369 
370  // Try transferring messages
371  bool dataTransferred = (currentMsg != nullptr);
372  while(currentMsg != nullptr) {
373  int writing = (int)(currentMsgLen - currentMsgOffset);
374 
375  if(sendNetworkMessage(&currentMsg[currentMsgOffset], writing)) {
376  // Get next message
377  currentMsgOffset = 0;
378  currentMsg = protocol->getTransferMessage(currentMsgLen);
379  } else {
380  return WOULD_BLOCK;
381  }
382  }
383 
384  if(dataTransferred && protType == ImageProtocol::PROTOCOL_TCP && protocol->transferComplete()) {
385  // Force a flush for TCP by turning the nagle algorithm off and on
386  int flag = 1;
387  setsockopt(clientSocket, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));
388  flag = 0;
389  setsockopt(clientSocket, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));
390  }
391 
392  // Also check for control messages at the end
393  if(protType == ImageProtocol::PROTOCOL_UDP) {
394  receiveNetworkData(false);
395  }
396 
397  if(protocol->transferComplete()) {
398  return ALL_TRANSFERRED;
399  } else {
400  return PARTIAL_TRANSFER;
401  }
402 }
403 
404 bool ImageTransfer::Pimpl::receiveImageSet(ImageSet& imageSet) {
405  int validRows = 0;
406  bool complete = false;
407 
408  std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now();
409  while(!complete) {
410  if(!receivePartialImageSet(imageSet, validRows, complete)) {
411  return false;
412  }
413 
414  unsigned int time = static_cast<unsigned int>(std::chrono::duration_cast<std::chrono::milliseconds>(
415  std::chrono::steady_clock::now() - startTime).count());
416  if(time > 1000) {
417  return false;
418  }
419  }
420 
421  return true;
422 }
423 
424 bool ImageTransfer::Pimpl::receivePartialImageSet(ImageSet& imageSet,
425  int& validRows, bool& complete) {
426  unique_lock<recursive_mutex> lock(receiveMutex);
427 
428  // Try to receive further image data if needed
429  bool block = true;
430  while(!protocol->imagesReceived() && receiveNetworkData(block)) {
431  block = false;
432  }
433 
434  // Get received image
435  return protocol->getPartiallyReceivedImageSet(imageSet, validRows, complete);
436 }
437 
438 bool ImageTransfer::Pimpl::receiveNetworkData(bool block) {
439  unique_lock<recursive_mutex> lock = block ?
440  unique_lock<recursive_mutex>(receiveMutex) : unique_lock<recursive_mutex>(receiveMutex, std::try_to_lock);
441 
442  if(clientSocket == INVALID_SOCKET) {
443  return false; // Not connected
444  }
445 
446  // First send control messages if necessary
447  sendPendingControlMessages();
448 
449  if(!lock.owns_lock()) {
450  // Waiting for the lock would block this call
451  return false;
452  }
453 
454  // Test if the socket has data available
455  if(!block && !selectSocket(true, false)) {
456  return 0;
457  }
458 
459  int maxLength = 0;
460  char* buffer = reinterpret_cast<char*>(protocol->getNextReceiveBuffer(maxLength));
461 
462  // Receive data
463  sockaddr_in fromAddress;
464  socklen_t fromSize = sizeof(fromAddress);
465 
466  int bytesReceived = recvfrom(clientSocket, buffer, maxLength,
467  0, reinterpret_cast<sockaddr*>(&fromAddress), &fromSize);
468 
469  //std::cout << "recvfrom to addr " << ((long) buffer) << std::endl;
470  //std::cout << "recvfrom returned " << bytesReceived << std::endl;
471  if(bytesReceived == 0 || (protType == ImageProtocol::PROTOCOL_TCP && bytesReceived < 0 && errno == WSAECONNRESET)) {
472  // Connection closed
473  disconnect();
474  } else if(bytesReceived < 0 && errno != EWOULDBLOCK && errno != EINTR &&
475  errno != ETIMEDOUT && errno != WSA_IO_PENDING && errno != WSAECONNRESET) {
476  TransferException ex("Error reading from socket: " + string(strerror(errno)));
477  throw ex;
478  } else if(bytesReceived > 0) {
479  protocol->processReceivedMessage(bytesReceived);
480  if(protocol->newClientConnected()) {
481  // We have just established a new connection
482  memcpy(&remoteAddress, &fromAddress, sizeof(remoteAddress));
483  }
484  }
485 
486  return bytesReceived > 0;
487 }
488 
489 void ImageTransfer::Pimpl::disconnect() {
490  // We just need to forget the remote address in order to
491  // disconnect
492  unique_lock<recursive_mutex> recvLock(receiveMutex);
493  unique_lock<recursive_mutex> sendLock(sendMutex);
494 
495  if(clientSocket != INVALID_SOCKET && protType == ImageProtocol::PROTOCOL_TCP) {
496  Networking::closeSocket(clientSocket);
497  }
498  memset(&remoteAddress, 0, sizeof(remoteAddress));
499 }
500 
501 bool ImageTransfer::Pimpl::isConnected() const {
502  unique_lock<recursive_mutex> lock(const_cast<recursive_mutex&>(sendMutex)); //either mutex will work
503 
504  return remoteAddress.sin_family == AF_INET && protocol->isConnected();
505 }
506 
507 bool ImageTransfer::Pimpl::sendNetworkMessage(const unsigned char* msg, int length) {
508  int written = 0;
509  if(protType == ImageProtocol::PROTOCOL_UDP) {
510  sockaddr_in destAddr;
511  SOCKET destSocket;
512  {
513  unique_lock<recursive_mutex> lock(sendMutex);
514  destAddr = remoteAddress;
515  destSocket = clientSocket;
516  }
517 
518  if(destAddr.sin_family != AF_INET) {
519  return false; // Not connected
520  }
521 
522  written = sendto(destSocket, reinterpret_cast<const char*>(msg), length, 0,
523  reinterpret_cast<sockaddr*>(&destAddr), sizeof(destAddr));
524  } else {
525  SOCKET destSocket;
526  {
527  unique_lock<recursive_mutex> lock(sendMutex);
528  destSocket = clientSocket;
529  }
530  written = send(destSocket, reinterpret_cast<const char*>(msg), length, 0);
531  }
532 
533  unsigned long sendError = errno;
534 
535  if(written < 0) {
536  if(sendError == EAGAIN || sendError == EWOULDBLOCK || sendError == ETIMEDOUT) {
537  // The socket is not yet ready for a new transfer
538  return false;
539  } else if(sendError == EPIPE) {
540  // The connection has been closed
541  disconnect();
542  return false;
543  } else {
544  TransferException ex("Error sending network packet: " + string(strerror(sendError)));
545  throw ex;
546  }
547  } else if(written != length) {
548  if(protType == ImageProtocol::PROTOCOL_UDP) {
549  // The message has been transmitted partially
550  throw TransferException("Unable to transmit complete UDP message");
551  } else {
552  // For TCP we can transmit the remaining data later
553  currentMsgOffset += written;
554  return false;
555  }
556  } else {
557  return true;
558  }
559 }
560 
561 void ImageTransfer::Pimpl::sendPendingControlMessages() {
562  const unsigned char* controlMsgData = nullptr;
563  int controlMsgLen = 0;
564 
565  while(true) {
566  unique_lock<recursive_mutex> lock(sendMutex);
567  if(remoteAddress.sin_family != AF_INET) {
568  return;
569  }
570 
571  controlMsgData = protocol->getNextControlMessage(controlMsgLen);
572 
573  if(controlMsgData != nullptr) {
574  //std::cout << "Sending a control message of size " << controlMsgLen << std::endl;
575  sendNetworkMessage(controlMsgData, controlMsgLen);
576  } else {
577  break;
578  }
579  }
580 }
581 
582 int ImageTransfer::Pimpl::getNumDroppedFrames() const {
583  return protocol->getNumDroppedFrames();
584 }
585 
586 bool ImageTransfer::Pimpl::selectSocket(bool read, bool wait) {
587  SOCKET sock;
588  {
589  unique_lock<recursive_mutex> lock(sendMutex); // Either mutex will do
590  sock = clientSocket;
591  }
592 #ifdef _WIN32
593  fd_set fds;
594  struct timeval tv;
595  FD_ZERO(&fds);
596  FD_SET(sock, &fds);
597  tv.tv_sec = 0;
598  if(wait) {
599  tv.tv_usec = 100000;
600  } else {
601  tv.tv_usec = 0;
602  }
603 
604  if(select(sock+1, (read ? &fds : nullptr), (!read ? &fds : nullptr), nullptr, &tv) <= 0) {
605  // The socket is currently not ready
606  return false;
607  }
608 #else
609  // use poll() on non-Windows platform (glibc select() limitations)
610  constexpr int timeoutMillisec = 100;
611  pollfd pfd;
612  pfd.fd = sock;
613  pfd.events = POLLIN;
614  if (poll(&pfd, 1, wait ? timeoutMillisec: 0) <= 0) {
615  // The socket is currently not ready
616  return false;
617  }
618 #endif
619  // select (or poll) reported an event
620  return true;
621 }
622 
623 std::string ImageTransfer::statusReport() {
624  return pimpl->statusReport();
625 }
626 std::string ImageTransfer::Pimpl::statusReport() {
627  return protocol->statusReport();
628 }
629 
630 } // namespace
631 
std::string getRemoteAddress() const
Returns the address of the remote host.
int getNumDroppedFrames() const
Returns the number of frames that have been dropped since connecting to the current remote host...
bool receivePartialImageSet(ImageSet &imageSet, int &validRows, bool &complete)
Returns the received image set, even if it is not yet complete.
The operation would block and blocking as been disabled.
Definition: imagetransfer.h:51
ImageTransfer(const char *address, const char *service="7681", ImageProtocol::ProtocolType protType=ImageProtocol::PROTOCOL_UDP, bool server=false, int bufferSize=1048576, int maxUdpPacketSize=1472)
Creates a new transfer object by manually specifying the target address.
The connection-less UDP transport protocol.
Definition: imageprotocol.h:48
bool tryAccept()
Tries to accept a client connection.
void disconnect()
Terminates the current connection.
void setRawValidBytes(const std::vector< int > &validBytes)
Updates the number of valid bytes in a partial raw transmission.
bool receiveImageSet(ImageSet &imageSet)
Waits for and receives a new image set.
A lightweight protocol for transferring image sets.
Definition: imageprotocol.h:40
No network connection has been established.
Definition: imagetransfer.h:54
TransferStatus transferData()
Performs a partial (or full) image transmission.
ProtocolType
Supported network protocols.
Definition: imageprotocol.h:43
bool isConnected() const
Returns true if a remote connection is established.
Aggregates information about a discovered device.
Definition: deviceinfo.h:47
There is currently no more data that could be transmitted.
Definition: imagetransfer.h:48
The image set has been transferred completely.
Definition: imagetransfer.h:41
A set of one to three images, but usually two (the left camera image and the disparity map)...
Definition: imageset.h:38
Exception class that is used for all transfer exceptions.
Definition: exceptions.h:33
void setTransferImageSet(const ImageSet &imageSet)
Sets a new image set that shall be transmitted.
void setRawTransferData(const ImageSet &metaData, const std::vector< unsigned char *> &rawData, int firstTileWidth=0, int secondTileWidth=0, int validBytes=0x7FFFFFFF)
Sets the raw pixel data for a partial image transmission.
The connection oriented TCP transport protocol.
Definition: imageprotocol.h:45
TransferStatus
The result of a partial image transfer.
Definition: imagetransfer.h:39
Nerian Vision Technologies