/******************************************************************************* * Copyright (c) 2021 Nerian Vision GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. *******************************************************************************/ #include #include #include #include #include #include #include "visiontransfer/imageprotocol.h" #include "visiontransfer/alignedallocator.h" #include "visiontransfer/datablockprotocol.h" #include "visiontransfer/exceptions.h" #include "visiontransfer/bitconversions.h" #include "visiontransfer/internalinformation.h" // Network headers #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX #endif #include #else #include #endif #define LOG_WARN(expr) //#define LOG_WARN(expr) std::cerr << "DataBlockProtocol: " << expr << std::endl using namespace std; using namespace visiontransfer; using namespace visiontransfer::internal; namespace visiontransfer { /*************** Pimpl class containing all private members ***********/ class ImageProtocol::Pimpl { public: static const int IMAGE_HEADER_OFFSET = sizeof(DataBlockProtocol::HeaderPreamble) + 10; Pimpl(bool server, ProtocolType protType, int maxUdpPacketSize); // Redeclaration of public members void setTransferImageSet(const ImageSet& imageSet); void setRawTransferData(const ImageSet& metaData, const std::vector& rawData, int firstTileWidth = 0, int middleTilesWidth = 0, int lastTileWidth = 0, int validBytes = 0x7FFFFFFF); void setRawValidBytes(const std::vector& validBytesVec); const unsigned char* getTransferMessage(int& length); bool transferComplete(); void resetTransfer(); bool getReceivedImageSet(ImageSet& imageSet); bool getPartiallyReceivedImageSet(ImageSet& imageSet, int& validRows, bool& complete); bool imagesReceived() const; unsigned char* getNextReceiveBuffer(int& maxLength); void processReceivedMessage(int length); int getProspectiveMessageSize(); int getNumDroppedFrames() const; void resetReception(); bool isConnected() const; const unsigned char* getNextControlMessage(int& length); bool newClientConnected(); std::string statusReport(); private: unsigned short MAGIC_SEQUECE = 0x3D15; // Header data transferred in the first packet #pragma pack(push,1) struct HeaderDataLegacy { unsigned short magic; unsigned char protocolVersion; unsigned char isRawImagePair_OBSOLETE; unsigned short width; unsigned short height; unsigned short firstTileWidth; unsigned short lastTileWidth; unsigned char format0; unsigned char format1; unsigned short minDisparity; unsigned short maxDisparity; unsigned char subpixelFactor; unsigned int seqNum; int timeSec; int timeMicrosec; float q[16]; unsigned short middleTilesWidth; }; // Header data v2: extensible and forwards-compatible struct HeaderDataV2: public HeaderDataLegacy { unsigned short totalHeaderSize; unsigned short flags; unsigned char numberOfImages; unsigned char format2; enum FlagBits { NEW_STYLE_TRANSFER = 1, HEADER_V3 = 2, HEADER_V4 = 4, // future protocol extensions should mark a new bit here }; }; // Header data v3, adds arbitrary image channel assignments struct HeaderDataV3: public HeaderDataV2 { // HEADER_V3 bit implies that this extension is present, // declaring arbitrary channel roles for each of numberOfImages active channels. // If not present, is it an old sender that always sends two images // (channel 0: left, channel 1: right or disparity (if active)) unsigned char imageTypes[8]; }; // Header data v4, adds exposure time and sync pulse struct HeaderData: public HeaderDataV3 { int exposureTime; // exposure time in microseconds int lastSyncPulseSec; int lastSyncPulseMicrosec; }; #pragma pack(pop) // Underlying protocol for data transfers DataBlockProtocol dataProt; ProtocolType protType; // Transfer related variables std::vector headerBuffer; // Reception related variables std::vector >decodeBuffer[ImageSet::MAX_SUPPORTED_IMAGES]; bool receiveHeaderParsed; HeaderData receiveHeader; int lastReceivedPayloadBytes[ImageSet::MAX_SUPPORTED_IMAGES]; bool receptionDone; // Copies the transmission header to the given buffer void copyHeaderToBuffer(const ImageSet& imageSet, int firstTileWidth, int middleTilesWidth, int lastTileWidth, unsigned char* buffer); // Decodes header information from the received data void tryDecodeHeader(const unsigned char* receivedData, int receivedBytes); // Decodes a received image from a non-interleaved buffer unsigned char* decodeNoninterleaved(int imageNumber, int numImages, int receivedBytes, unsigned char* data, int& validRows, int& rowStride); // Decodes a received image from an interleaved buffer unsigned char* decodeInterleaved(int imageNumber, int numImages, int receivedBytes, unsigned char* data, int& validRows, int& rowStride); int getNumTiles(int width, int firstTileWidth, int middleTilesWidth, int lastTileWidth); int getFrameSize(int width, int height, int firstTileWidth, int middleTilesWidth, int lastTileWidth, int totalBits); int getFormatBits(ImageSet::ImageFormat format, bool afterDecode); void decodeTiledImage(int imageNumber, int lastReceivedPayloadBytes, int receivedPayloadBytes, const unsigned char* data, int firstTileStride, int middleTilesStride, int lastTileStride, int& validRows, ImageSet::ImageFormat format, bool dataIsInterleaved); void decodeRowsFromTile(int startRow, int stopRow, unsigned const char* src, unsigned char* dst, int srcStride, int dstStride, int tileWidth); void allocateDecodeBuffer(int imageNumber); }; /******************** Stubs for all public members ********************/ ImageProtocol::ImageProtocol(bool server, ProtocolType protType, int maxUdpPacketSize) : pimpl(new Pimpl(server, protType, maxUdpPacketSize)) { // All initializations are done by the Pimpl class } ImageProtocol::~ImageProtocol() { delete pimpl; } void ImageProtocol::setTransferImageSet(const ImageSet& imageSet) { pimpl->setTransferImageSet(imageSet); } void ImageProtocol::setRawTransferData(const ImageSet& metaData, const std::vector& imageData, int firstTileWidth, int middleTilesWidth, int lastTileWidth, int validBytes) { pimpl->setRawTransferData(metaData, imageData, firstTileWidth, middleTilesWidth, lastTileWidth, validBytes); } void ImageProtocol::setRawValidBytes(const std::vector& validBytesVec) { pimpl->setRawValidBytes(validBytesVec); } const unsigned char* ImageProtocol::getTransferMessage(int& length) { return pimpl->getTransferMessage(length); } bool ImageProtocol::transferComplete() { return pimpl->transferComplete(); } void ImageProtocol::resetTransfer() { pimpl->resetTransfer(); } bool ImageProtocol::getReceivedImageSet(ImageSet& imageSet) { return pimpl->getReceivedImageSet(imageSet); } bool ImageProtocol::getPartiallyReceivedImageSet( ImageSet& imageSet, int& validRows, bool& complete) { return pimpl->getPartiallyReceivedImageSet(imageSet, validRows, complete); } bool ImageProtocol::imagesReceived() const { return pimpl->imagesReceived(); } unsigned char* ImageProtocol::getNextReceiveBuffer(int& maxLength) { return pimpl->getNextReceiveBuffer(maxLength); } void ImageProtocol::processReceivedMessage(int length) { pimpl->processReceivedMessage(length); } int ImageProtocol::getNumDroppedFrames() const { return pimpl->getNumDroppedFrames(); } void ImageProtocol::resetReception() { pimpl->resetReception(); } bool ImageProtocol::isConnected() const { return pimpl->isConnected(); } const unsigned char* ImageProtocol::getNextControlMessage(int& length) { return pimpl->getNextControlMessage(length); } bool ImageProtocol::newClientConnected() { return pimpl->newClientConnected(); } /******************** Implementation in pimpl class *******************/ ImageProtocol::Pimpl::Pimpl(bool server, ProtocolType protType, int maxUdpPacketSize) :dataProt(server, (DataBlockProtocol::ProtocolType)protType, maxUdpPacketSize), protType(protType), receiveHeaderParsed(false), lastReceivedPayloadBytes{0}, receptionDone(false) { headerBuffer.resize(sizeof(HeaderData) + 128); memset(&headerBuffer[0], 0, sizeof(headerBuffer.size())); memset(&receiveHeader, 0, sizeof(receiveHeader)); } void ImageProtocol::Pimpl::setTransferImageSet(const ImageSet& imageSet) { for (int i=0; i encodingBuffer[ImageSet::MAX_SUPPORTED_IMAGES]; for(int i = 0; i(pixelData[i])); // these are always reserved memory or untile buffers } } void ImageProtocol::Pimpl::setRawTransferData(const ImageSet& metaData, const std::vector& rawData, int firstTileWidth, int middleTilesWidth, int lastTileWidth, int validBytes) { if(static_cast(rawData.size()) != metaData.getNumberOfImages()) { throw ProtocolException("Mismatch between metadata and number of image buffers!"); } // Set header as first piece of data copyHeaderToBuffer(metaData, firstTileWidth, middleTilesWidth, lastTileWidth, &headerBuffer[IMAGE_HEADER_OFFSET]); dataProt.resetTransfer(); int numTransferBlocks = metaData.getNumberOfImages(); dataProt.setTransferHeader(&headerBuffer[IMAGE_HEADER_OFFSET], sizeof(HeaderData), numTransferBlocks); // Now set the size per channel (replaces old final size argument to setTransferHeader() for (int i=0; i& validBytesVec) { for (int i=0; i(validBytesVec.size()); ++i) { dataProt.setTransferValidBytes(i, validBytesVec[i]); } } const unsigned char* ImageProtocol::Pimpl::getTransferMessage(int& length) { const unsigned char* msg = dataProt.getTransferMessage(length); if(msg == nullptr) { msg = dataProt.getTransferMessage(length); } return msg; } bool ImageProtocol::Pimpl::transferComplete() { return dataProt.transferComplete(); } int ImageProtocol::Pimpl::getNumTiles(int width, int firstTileWidth, int middleTilesWidth, int lastTileWidth) { if(lastTileWidth == 0) { return 1; } else if(middleTilesWidth == 0) { return 2; } else { int tileWidth = firstTileWidth + lastTileWidth - middleTilesWidth; return (width - 2*tileWidth + firstTileWidth + lastTileWidth) / (firstTileWidth + lastTileWidth - tileWidth); } } int ImageProtocol::Pimpl::getFrameSize(int width, int height, int firstTileWidth, int middleTilesWidth, int lastTileWidth, int totalBits) { return (width * height * totalBits) /8; } int ImageProtocol::Pimpl::getFormatBits(ImageSet::ImageFormat format, bool afterDecode) { if(afterDecode) { return ImageSet::getBytesPerPixel(format)*8; } else { switch(format) { case ImageSet::FORMAT_8_BIT_MONO: return 8; case ImageSet::FORMAT_12_BIT_MONO: return 12; case ImageSet::FORMAT_8_BIT_RGB: return 24; default: throw ProtocolException("Illegal pixel format!"); } } } void ImageProtocol::Pimpl::copyHeaderToBuffer(const ImageSet& imageSet, int firstTileWidth, int middleTilesWidth, int lastTileWidth, unsigned char* buffer) { int timeSec = 0, timeMicrosec = 0; HeaderData* transferHeader = reinterpret_cast(buffer); memset(transferHeader, 0, sizeof(*transferHeader)); transferHeader->magic = htons(MAGIC_SEQUECE); transferHeader->protocolVersion = InternalInformation::CURRENT_PROTOCOL_VERSION; transferHeader->isRawImagePair_OBSOLETE = 0; transferHeader->width = htons(imageSet.getWidth()); transferHeader->height = htons(imageSet.getHeight()); transferHeader->firstTileWidth = htons(firstTileWidth); transferHeader->lastTileWidth = htons(lastTileWidth); transferHeader->middleTilesWidth = htons(middleTilesWidth); transferHeader->format0 = static_cast(imageSet.getPixelFormat(0)); transferHeader->format1 = (imageSet.getNumberOfImages() <= 1) ? 0 : static_cast(imageSet.getPixelFormat(1)); transferHeader->seqNum = static_cast(htonl(imageSet.getSequenceNumber())); transferHeader->format2 = (imageSet.getNumberOfImages() <= 2) ? 0 : static_cast(imageSet.getPixelFormat(2)); transferHeader->numberOfImages = static_cast(imageSet.getNumberOfImages()); transferHeader->exposureTime = htonl(imageSet.getExposureTime()); imageSet.getLastSyncPulse(timeSec, timeMicrosec); transferHeader->lastSyncPulseSec = htonl(timeSec); transferHeader->lastSyncPulseMicrosec = htonl(timeMicrosec); transferHeader->totalHeaderSize = htons(sizeof(HeaderData)); transferHeader->flags = htons(HeaderData::FlagBits::NEW_STYLE_TRANSFER | HeaderData::FlagBits::HEADER_V3 | HeaderData::FlagBits::HEADER_V4); int minDisp = 0, maxDisp = 0; imageSet.getDisparityRange(minDisp, maxDisp); transferHeader->minDisparity = minDisp; transferHeader->maxDisparity = maxDisp; transferHeader->subpixelFactor = imageSet.getSubpixelFactor(); imageSet.getTimestamp(timeSec, timeMicrosec); transferHeader->timeSec = static_cast(htonl(static_cast(timeSec))); transferHeader->timeMicrosec = static_cast(htonl(static_cast(timeMicrosec))); int numImageChannels = 0; for (int i=0; i<(int) sizeof(transferHeader->imageTypes); ++i) { transferHeader->imageTypes[i] = static_cast(ImageSet::ImageType::IMAGE_UNDEFINED); } int idx = imageSet.getIndexOf(ImageSet::ImageType::IMAGE_LEFT); if (idx>=0) { transferHeader->imageTypes[idx] = static_cast(ImageSet::ImageType::IMAGE_LEFT); numImageChannels++; } idx = imageSet.getIndexOf(ImageSet::ImageType::IMAGE_RIGHT); if (idx>=0) { transferHeader->imageTypes[idx] = static_cast(ImageSet::ImageType::IMAGE_RIGHT); numImageChannels++; } idx = imageSet.getIndexOf(ImageSet::ImageType::IMAGE_DISPARITY); if (idx>=0) { transferHeader->imageTypes[idx] = static_cast(ImageSet::ImageType::IMAGE_DISPARITY); numImageChannels++; } if (numImageChannels != imageSet.getNumberOfImages()) { throw std::runtime_error("Mismatch between reported number of images and enabled channel selection!"); } if(imageSet.getQMatrix() != nullptr) { memcpy(transferHeader->q, imageSet.getQMatrix(), sizeof(float)*16); } } void ImageProtocol::Pimpl::resetTransfer() { dataProt.resetTransfer(); } unsigned char* ImageProtocol::Pimpl::getNextReceiveBuffer(int& maxLength) { maxLength = dataProt.getMaxReceptionSize(); return dataProt.getNextReceiveBuffer(maxLength); } void ImageProtocol::Pimpl::processReceivedMessage(int length) { receptionDone = false; // Add the received message dataProt.processReceivedMessage(length, receptionDone); if(!dataProt.wasHeaderReceived() && receiveHeaderParsed) { // Something went wrong. We need to reset! LOG_WARN("Resetting image protocol!"); resetReception(); return; } int receivedBytes = 0; dataProt.getReceivedData(receivedBytes); // Immediately try to decode the header if(!receiveHeaderParsed) { int headerLen = 0; unsigned char* headerData = dataProt.getReceivedHeader(headerLen); if(headerData != nullptr) { tryDecodeHeader(headerData, headerLen); } } } void ImageProtocol::Pimpl::tryDecodeHeader(const unsigned char* receivedData, int receivedBytes) { // Extra data fields that have been added to the header. Must be // removed when the protocol version number is updated constexpr int optionalDataSize = sizeof(receiveHeader.middleTilesWidth); constexpr int mandatoryDataSize = static_cast(sizeof(HeaderDataLegacy)) - optionalDataSize; constexpr int fullyExtensibleHeaderSize = static_cast(sizeof(HeaderDataV2)); bool isCompleteHeader = false; if(receivedBytes >= mandatoryDataSize) { if (receivedBytes < fullyExtensibleHeaderSize) { *(static_cast(&receiveHeader)) = *reinterpret_cast(receivedData); } else { memcpy(&receiveHeader, receivedData, std::min((size_t)receivedBytes, sizeof(HeaderData))); receiveHeader = *reinterpret_cast(receivedData); isCompleteHeader = true; } if(receiveHeader.magic != htons(MAGIC_SEQUECE)) { // Let's not call this an error. Perhaps it's just not a header // packet return; } if(receiveHeader.protocolVersion != InternalInformation::CURRENT_PROTOCOL_VERSION) { throw ProtocolException("Protocol version mismatch!"); } // Convert byte order receiveHeader.width = ntohs(receiveHeader.width); receiveHeader.height = ntohs(receiveHeader.height); receiveHeader.firstTileWidth = ntohs(receiveHeader.firstTileWidth); receiveHeader.lastTileWidth = ntohs(receiveHeader.lastTileWidth); receiveHeader.timeSec = static_cast( ntohl(static_cast(receiveHeader.timeSec))); receiveHeader.timeMicrosec = static_cast( ntohl(static_cast(receiveHeader.timeMicrosec))); receiveHeader.seqNum = ntohl(receiveHeader.seqNum); // Optional data items if(receivedBytes >= mandatoryDataSize + optionalDataSize) { receiveHeader.middleTilesWidth = ntohs(receiveHeader.middleTilesWidth); } else { receiveHeader.middleTilesWidth = 0; } if (isCompleteHeader) { // This is a header of v2 or above, which self-reports its extension level in the flags field receiveHeader.totalHeaderSize = ntohs(receiveHeader.totalHeaderSize); receiveHeader.flags = ntohs(receiveHeader.flags); receiveHeader.exposureTime = ntohl(receiveHeader.exposureTime); receiveHeader.lastSyncPulseSec = htonl(receiveHeader.lastSyncPulseSec); receiveHeader.lastSyncPulseMicrosec = htonl(receiveHeader.lastSyncPulseMicrosec); } else { // Infer missing fields for legacy compatibility transfers receiveHeader.totalHeaderSize = (receivedBytes <= mandatoryDataSize) ? mandatoryDataSize : static_cast(sizeof(HeaderDataLegacy)); receiveHeader.flags = 0; receiveHeader.numberOfImages = 2; receiveHeader.format2 = 0; receiveHeader.exposureTime = 0; receiveHeader.lastSyncPulseSec = 0; receiveHeader.lastSyncPulseMicrosec = 0; } receiveHeaderParsed = true; } } bool ImageProtocol::Pimpl::imagesReceived() const { return receptionDone && receiveHeaderParsed; } bool ImageProtocol::Pimpl::getReceivedImageSet(ImageSet& imageSet) { bool complete = false; int validRows; bool ok = getPartiallyReceivedImageSet(imageSet, validRows, complete); return (ok && complete); } bool ImageProtocol::Pimpl::getPartiallyReceivedImageSet(ImageSet& imageSet, int& validRows, bool& complete) { imageSet.setWidth(0); imageSet.setHeight(0); complete = false; if(!receiveHeaderParsed) { // We haven't even received the image header yet return false; } else { // We received at least some pixel data imageSet.setNumberOfImages(receiveHeader.numberOfImages); bool flaggedDisparityPair = (receiveHeader.isRawImagePair_OBSOLETE == 0); // only meaningful in headers <=V2 bool isInterleaved = (receiveHeader.flags & HeaderData::FlagBits::NEW_STYLE_TRANSFER) == 0; bool arbitraryChannels = (receiveHeader.flags & HeaderData::FlagBits::HEADER_V3) > 0; bool hasExposureTime = (receiveHeader.flags & HeaderData::FlagBits::HEADER_V4) > 0; // Forward compatibility check: mask out all known flag bits and see what remains unsigned short unaccountedFlags = receiveHeader.flags & ~(HeaderData::FlagBits::NEW_STYLE_TRANSFER | HeaderData::FlagBits::HEADER_V3 | HeaderData::FlagBits::HEADER_V4); if (unaccountedFlags != 0) { // Newer protocol (unknown flag present) - we will try to continue // since connection has not been refused earlier static bool warnedOnceForward = false; if (!warnedOnceForward) { LOG_WARN("Warning: forward-compatible mode; will attempt to process image stream with unknown extra flags. Consider upgrading the client software."); warnedOnceForward = true; } } imageSet.setWidth(receiveHeader.width); imageSet.setHeight(receiveHeader.height); imageSet.setPixelFormat(0, static_cast(receiveHeader.format0)); if (imageSet.getNumberOfImages() > 1) imageSet.setPixelFormat(1, static_cast(receiveHeader.format1)); if (imageSet.getNumberOfImages() > 2) imageSet.setPixelFormat(2, static_cast(receiveHeader.format2)); int rowStrideArr[ImageSet::MAX_SUPPORTED_IMAGES] = {0}; int validRowsArr[ImageSet::MAX_SUPPORTED_IMAGES] = {0}; unsigned char* pixelArr[ImageSet::MAX_SUPPORTED_IMAGES] = {nullptr}; if (isInterleaved) { // OLD transfer (forced to interleaved 2 images mode) static bool warnedOnceBackward = false; if (!warnedOnceBackward) { LOG_WARN("Info: backward-compatible mode; the device is sending with a legacy protocol. Consider upgrading its firmware."); warnedOnceBackward = true; } unsigned char* data = dataProt.getBlockReceiveBuffer(0); int validBytes = dataProt.getBlockValidSize(0); for (int i=0; i < 2; ++i) { pixelArr[i] = decodeInterleaved(i, imageSet.getNumberOfImages(), validBytes, data, validRowsArr[i], rowStrideArr[i]); } // Legacy sender with mode-dependent channel selection imageSet.setIndexOf(ImageSet::ImageType::IMAGE_LEFT, 0); imageSet.setIndexOf(ImageSet::ImageType::IMAGE_RIGHT, flaggedDisparityPair ? -1 : 1); imageSet.setIndexOf(ImageSet::ImageType::IMAGE_DISPARITY, flaggedDisparityPair ? 1 : -1); } else { // NEW transfer try { for (int i=0; i(typ); imageSet.setIndexOf(imgtype, i); } } else { static bool warnedOnceV2 = false; if (!warnedOnceV2) { LOG_WARN("Info: received a transfer with header v2"); warnedOnceV2 = true; } // Older v2 header; accessing imageTypes is not valid // Two-image sender with mode-dependent channel selection imageSet.setIndexOf(ImageSet::ImageType::IMAGE_LEFT, 0); imageSet.setIndexOf(ImageSet::ImageType::IMAGE_RIGHT, flaggedDisparityPair ? -1 : 1); imageSet.setIndexOf(ImageSet::ImageType::IMAGE_DISPARITY, flaggedDisparityPair ? 1 : -1); } if(hasExposureTime) { imageSet.setExposureTime(receiveHeader.exposureTime); imageSet.setLastSyncPulse(receiveHeader.lastSyncPulseSec, receiveHeader.lastSyncPulseMicrosec); } } for (int i=0; i(receiveHeader.format0); break; } case 1: { format = static_cast(receiveHeader.format1); break; } case 2: { format = static_cast(receiveHeader.format2); break; } default: throw ProtocolException("Not implemented: decodeNoninterleaved with image index > 2"); } bits = getFormatBits(static_cast(format), false); int totalBits = bits; unsigned char* ret = nullptr; if(receiveHeader.lastTileWidth == 0) { int bufferOffset0 = 0; int bufferRowStride = receiveHeader.width*(totalBits) / 8; if(format == ImageSet::FORMAT_8_BIT_MONO || format == ImageSet::FORMAT_8_BIT_RGB) { // No decoding is necessary. We can just pass through the // data pointer ret = &data[bufferOffset0]; rowStride = bufferRowStride; validRows = receivedBytes / bufferRowStride; } else { // Perform 12-bit => 16 bit decoding allocateDecodeBuffer(imageNumber); validRows = receivedBytes / bufferRowStride; rowStride = 2*receiveHeader.width; int lastRow = lastReceivedPayloadBytes[imageNumber] / bufferRowStride; BitConversions::decode12BitPacked(lastRow, validRows, &data[bufferOffset0], &decodeBuffer[imageNumber][0], bufferRowStride, rowStride, receiveHeader.width); ret = &decodeBuffer[imageNumber][0]; } } else { // Decode the tiled transfer decodeTiledImage(imageNumber, lastReceivedPayloadBytes[imageNumber], receivedBytes, data, receiveHeader.firstTileWidth * (totalBits) / 8, receiveHeader.middleTilesWidth * (totalBits) / 8, receiveHeader.lastTileWidth * (totalBits) / 8, validRows, format, false); ret = &decodeBuffer[imageNumber][0]; rowStride = receiveHeader.width*getFormatBits( static_cast(format), true)/8; } lastReceivedPayloadBytes[imageNumber] = receivedBytes; return ret; } unsigned char* ImageProtocol::Pimpl::decodeInterleaved(int imageNumber, int numImages, int receivedBytes, unsigned char* data, int& validRows, int& rowStride) { ImageSet::ImageFormat format = static_cast( imageNumber == 0 ? receiveHeader.format0 : receiveHeader.format1); int bits0 = getFormatBits(static_cast(receiveHeader.format0), false); int bits1 = getFormatBits(static_cast(receiveHeader.format1), false); int bits2 = getFormatBits(static_cast(receiveHeader.format2), false); int totalBits = (numImages<3)?(bits0 + bits1):(bits0 + bits1 + bits2); unsigned char* ret = nullptr; if(receiveHeader.lastTileWidth == 0) { int bufferOffset; switch (imageNumber) { case 0: { bufferOffset = 0; break; } case 1: { bufferOffset = receiveHeader.width * bits0/8; break; } case 2: { bufferOffset = receiveHeader.width * (bits0 + bits1)/8; break; } default: throw ProtocolException("Not implemented: image index > 2"); } int bufferRowStride = receiveHeader.width*(totalBits) / 8; if(format == ImageSet::FORMAT_8_BIT_MONO || format == ImageSet::FORMAT_8_BIT_RGB) { // No decoding is necessary. We can just pass through the // data pointer ret = &data[bufferOffset]; rowStride = bufferRowStride; validRows = receivedBytes / bufferRowStride; } else { // Perform 12-bit => 16 bit decoding allocateDecodeBuffer(imageNumber); validRows = std::min(receivedBytes / bufferRowStride, (int)receiveHeader.height); rowStride = 2*receiveHeader.width; int lastRow = lastReceivedPayloadBytes[imageNumber] / bufferRowStride; BitConversions::decode12BitPacked(lastRow, validRows, &data[bufferOffset], &decodeBuffer[imageNumber][0], bufferRowStride, rowStride, receiveHeader.width); ret = &decodeBuffer[imageNumber][0]; } } else { // Decode the tiled transfer decodeTiledImage(imageNumber, lastReceivedPayloadBytes[imageNumber], receivedBytes, data, receiveHeader.firstTileWidth * (totalBits) / 8, receiveHeader.middleTilesWidth * (totalBits) / 8, receiveHeader.lastTileWidth * (totalBits) / 8, validRows, format, true); ret = &decodeBuffer[imageNumber][0]; rowStride = receiveHeader.width*getFormatBits( static_cast(format), true)/8; } lastReceivedPayloadBytes[imageNumber] = receivedBytes; return ret; } void ImageProtocol::Pimpl::allocateDecodeBuffer(int imageNumber) { ImageSet::ImageFormat format; switch (imageNumber) { case 0: { format = static_cast(receiveHeader.format0); break; } case 1: { format = static_cast(receiveHeader.format1); break; } case 2: { format = static_cast(receiveHeader.format2); break; } default: throw ProtocolException("Not implemented: allocateDecodeBuffer with image index > 2"); } int bitsPerPixel = getFormatBits(format, true); int bufferSize = receiveHeader.width * receiveHeader.height * bitsPerPixel / 8; if(decodeBuffer[imageNumber].size() != static_cast(bufferSize)) { decodeBuffer[imageNumber].resize(bufferSize); } } void ImageProtocol::Pimpl::decodeTiledImage(int imageNumber, int lastReceivedPayloadBytes, int receivedPayloadBytes, const unsigned char* data, int firstTileStride, int middleTilesStride, int lastTileStride, int& validRows, ImageSet::ImageFormat format, bool dataIsInterleaved) { // Allocate a decoding buffer allocateDecodeBuffer(imageNumber); // Get beginning and end of first tile int numTiles = getNumTiles(receiveHeader.width, receiveHeader.firstTileWidth, receiveHeader.middleTilesWidth, receiveHeader.lastTileWidth); int payloadOffset = 0; int decodeXOffset = 0; int prevTileStrides = 0; for(int i = 0; i < numTiles; i++) { // Get relevant parameters int tileWidth = 0; int tileStride = 0; if(i == 0) { tileStride = firstTileStride; tileWidth = receiveHeader.firstTileWidth; } else if(i == numTiles-1) { tileStride = lastTileStride; tileWidth = receiveHeader.lastTileWidth; } else { tileStride = middleTilesStride; tileWidth = receiveHeader.middleTilesWidth; } int tileStart = std::max(0, (lastReceivedPayloadBytes - payloadOffset) / tileStride); int tileStop = std::min(std::max(0, (receivedPayloadBytes - payloadOffset) / tileStride), (int)receiveHeader.height); int tileOffset; if (dataIsInterleaved) { switch (imageNumber) { case 0: { tileOffset = 0; break; } case 1: { tileOffset = tileWidth * ( getFormatBits(static_cast(receiveHeader.format0), false) )/8; break; } case 2: { tileOffset = tileWidth * ( getFormatBits(static_cast(receiveHeader.format0), false) + getFormatBits(static_cast(receiveHeader.format1), false) )/8; break; } default: throw ProtocolException("Not implemented: image index > 2"); } } else { tileOffset = 0; } if(i > 0) { tileOffset += receiveHeader.height * prevTileStrides; } // Decode int bytesPixel; if(format == ImageSet::FORMAT_12_BIT_MONO) { bytesPixel = 2; BitConversions::decode12BitPacked(tileStart, tileStop, &data[tileOffset], &decodeBuffer[imageNumber][decodeXOffset], tileStride, 2*receiveHeader.width, tileWidth); } else { bytesPixel = (format == ImageSet::FORMAT_8_BIT_RGB ? 3 : 1); decodeRowsFromTile(tileStart, tileStop, &data[tileOffset], &decodeBuffer[imageNumber][decodeXOffset], tileStride, receiveHeader.width*bytesPixel, tileWidth*bytesPixel); } payloadOffset += receiveHeader.height * tileStride; decodeXOffset += tileWidth * bytesPixel; prevTileStrides += tileStride; if(i == numTiles-1) { validRows = tileStop; } } } void ImageProtocol::Pimpl::decodeRowsFromTile(int startRow, int stopRow, unsigned const char* src, unsigned char* dst, int srcStride, int dstStride, int tileWidth) { for(int y = startRow; y < stopRow; y++) { memcpy(&dst[y*dstStride], &src[y*srcStride], tileWidth); } } void ImageProtocol::Pimpl::resetReception() { receiveHeaderParsed = false; for (int i=0; istatusReport(); } std::string ImageProtocol::Pimpl::statusReport() { return dataProt.statusReport(); } } // namespace