diff --git a/devices/CMakeLists.txt b/devices/CMakeLists.txt index 0a0e7bd02..e054d9e74 100644 --- a/devices/CMakeLists.txt +++ b/devices/CMakeLists.txt @@ -6,6 +6,7 @@ if (BUILD_DEBIAN) add_subdirectory(bladerf) add_subdirectory(hackrf) add_subdirectory(limesdr) + add_subdirectory(sdrdaemon) else(BUILD_DEBIAN) find_package(LibBLADERF) if(LIBUSB_FOUND AND LIBBLADERF_FOUND) @@ -21,4 +22,10 @@ else(BUILD_DEBIAN) if(LIBUSB_FOUND AND LIMESUITE_FOUND) add_subdirectory(limesdr) endif(LIBUSB_FOUND AND LIMESUITE_FOUND) + + find_package(CM256cc) + if(CM256CC_FOUND) + add_subdirectory(sdrdaemon) + endif(CM256CC_FOUND AND LIBNANOMSG_FOUND) + endif (BUILD_DEBIAN) diff --git a/devices/sdrdaemon/CMakeLists.txt b/devices/sdrdaemon/CMakeLists.txt new file mode 100644 index 000000000..51bc08d8c --- /dev/null +++ b/devices/sdrdaemon/CMakeLists.txt @@ -0,0 +1,55 @@ +project(sdrdaemondevice) + +if (HAS_SSSE3) + message(STATUS "SDRdaemonDevice: use SSSE3 SIMD" ) +elseif (HAS_NEON) + message(STATUS "SDRdaemonDevice: use Neon SIMD" ) +else() + message(STATUS "SDRdaemonDevice: Unsupported architecture") + return() +endif() + +set(sdrdaemondevice_SOURCES + sdrdaemonfecbuffer.cpp +) + +set(sdrdaemondevice_HEADERS + sdrdaemonfecbuffer.h +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_SHARED) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${LIBCM256CCSRC} +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CM256CC_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_library(sdrdaemondevice SHARED + ${sdrdaemondevice_SOURCES} +) + +if (BUILD_DEBIAN) +target_link_libraries(sdrdaemondevice + ${QT_LIBRARIES} + cm256cc +) +else (BUILD_DEBIAN) +target_link_libraries(sdrdaemondevice + ${QT_LIBRARIES} + ${CM256CC_LIBRARIES} +) +endif (BUILD_DEBIAN) + +qt5_use_modules(sdrdaemondevice Core) + +install(TARGETS sdrdaemondevice DESTINATION lib) diff --git a/devices/sdrdaemon/sdrdaemonfecbuffer.cpp b/devices/sdrdaemon/sdrdaemonfecbuffer.cpp new file mode 100644 index 000000000..aa187bad8 --- /dev/null +++ b/devices/sdrdaemon/sdrdaemonfecbuffer.cpp @@ -0,0 +1,453 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +#include "sdrdaemonfecbuffer.h" + + +const int SDRdaemonFECBuffer::m_sampleSize = 2; +const int SDRdaemonFECBuffer::m_iqSampleSize = 2 * m_sampleSize; + +SDRdaemonFECBuffer::SDRdaemonFECBuffer(uint32_t throttlems) : + m_frameHead(0), + m_decoderIndexHead(nbDecoderSlots/2), + m_curNbBlocks(0), + m_minNbBlocks(256), + m_minOriginalBlocks(128), + m_curNbRecovery(0), + m_maxNbRecovery(0), + m_framesDecoded(true), + m_throttlemsNominal(throttlems), + m_readIndex(0), + m_readBuffer(0), + m_readSize(0), + m_bufferLenSec(0.0f), + m_nbReads(0), + m_nbWrites(0), + m_balCorrection(0), + m_balCorrLimit(0), + m_curOriginalBlocks(0) +{ + m_currentMeta.init(); + m_framesNbBytes = nbDecoderSlots * sizeof(BufferFrame); + m_wrDeltaEstimate = m_framesNbBytes / 2; + m_tvOut_sec = 0; + m_tvOut_usec = 0; + m_readNbBytes = 1; + m_paramsCM256.BlockBytes = sizeof(ProtectedBlock); // never changes + m_paramsCM256.OriginalCount = m_nbOriginalBlocks; // never changes + + if (!m_cm256.isInitialized()) { + m_cm256_OK = false; + qDebug() << "SDRdaemonFECBuffer::SDRdaemonFECBuffer: cannot initialize CM256 library"; + } else { + m_cm256_OK = true; + } +} + +SDRdaemonFECBuffer::~SDRdaemonFECBuffer() +{ + if (m_readBuffer) { + delete[] m_readBuffer; + } +} + +void SDRdaemonFECBuffer::initDecodeAllSlots() +{ + for (int i = 0; i < nbDecoderSlots; i++) + { + m_decoderSlots[i].m_blockCount = 0; + m_decoderSlots[i].m_originalCount = 0; + m_decoderSlots[i].m_recoveryCount = 0; + m_decoderSlots[i].m_decoded = false; + m_decoderSlots[i].m_metaRetrieved = false; + resetOriginalBlocks(i); + memset((void *) m_decoderSlots[i].m_recoveryBlocks, 0, m_nbOriginalBlocks * sizeof(ProtectedBlock)); + } +} + +void SDRdaemonFECBuffer::initDecodeSlot(int slotIndex) +{ + // collect stats before voiding the slot + + m_curNbBlocks = m_decoderSlots[slotIndex].m_blockCount; + m_curOriginalBlocks = m_decoderSlots[slotIndex].m_originalCount; + m_curNbRecovery = m_decoderSlots[slotIndex].m_recoveryCount; + m_avgNbBlocks(m_curNbBlocks); + m_avgOrigBlocks(m_curOriginalBlocks); + m_avgNbRecovery(m_curNbRecovery); + m_framesDecoded = m_framesDecoded && m_decoderSlots[slotIndex].m_decoded; + + if (m_curNbBlocks < m_minNbBlocks) { + m_minNbBlocks = m_curNbBlocks; + } + + if (m_curOriginalBlocks < m_minOriginalBlocks) { + m_minOriginalBlocks = m_curOriginalBlocks; + } + + if (m_curNbRecovery > m_maxNbRecovery) { + m_maxNbRecovery = m_curNbRecovery; + } + + // void the slot + + m_decoderSlots[slotIndex].m_blockCount = 0; + m_decoderSlots[slotIndex].m_originalCount = 0; + m_decoderSlots[slotIndex].m_recoveryCount = 0; + m_decoderSlots[slotIndex].m_decoded = false; + m_decoderSlots[slotIndex].m_metaRetrieved = false; + + resetOriginalBlocks(slotIndex); + memset((void *) m_decoderSlots[slotIndex].m_recoveryBlocks, 0, m_nbOriginalBlocks * sizeof(ProtectedBlock)); +} + +void SDRdaemonFECBuffer::initReadIndex() +{ + m_readIndex = ((m_decoderIndexHead + (nbDecoderSlots/2)) % nbDecoderSlots) * sizeof(BufferFrame); + m_wrDeltaEstimate = m_framesNbBytes / 2; + m_nbReads = 0; + m_nbWrites = 0; +} + +void SDRdaemonFECBuffer::rwCorrectionEstimate(int slotIndex) +{ + if (m_nbReads >= 40) // check every ~1s as tick is ~50ms + { + int targetPivotSlot = (slotIndex + (nbDecoderSlots/2)) % nbDecoderSlots; // slot at half buffer opposite of current write slot + int targetPivotIndex = targetPivotSlot * sizeof(BufferFrame); // buffer index corresponding to start of above slot + int normalizedReadIndex = (m_readIndex < targetPivotIndex ? m_readIndex + nbDecoderSlots * sizeof(BufferFrame) : m_readIndex) + - (targetPivotSlot * sizeof(BufferFrame)); // normalize read index so it is positive and zero at start of pivot slot + int dBytes; + int rwDelta = (m_nbReads * m_readNbBytes) - (m_nbWrites * sizeof(BufferFrame)); + + if (normalizedReadIndex < (nbDecoderSlots/ 2) * sizeof(BufferFrame)) // read leads + { + dBytes = - normalizedReadIndex - rwDelta; + } + else // read lags + { + dBytes = (nbDecoderSlots * sizeof(BufferFrame)) - normalizedReadIndex - rwDelta; + } + + m_balCorrection = (m_balCorrection / 4) + (dBytes / (int) (m_iqSampleSize * m_nbReads)); // correction is in number of samples. Alpha = 0.25 + + if (m_balCorrection < -m_balCorrLimit) { + m_balCorrection = -m_balCorrLimit; + } else if (m_balCorrection > m_balCorrLimit) { + m_balCorrection = m_balCorrLimit; + } + + float rwRatio = (float) (m_nbWrites * sizeof(BufferFrame)) / (float) (m_nbReads * m_readNbBytes); + + m_nbReads = 0; + m_nbWrites = 0; + } +} + +void SDRdaemonFECBuffer::checkSlotData(int slotIndex) +{ + int pseudoWriteIndex = slotIndex * sizeof(BufferFrame); + m_wrDeltaEstimate = pseudoWriteIndex - m_readIndex; + m_nbWrites++; + + int rwDelayBytes = (m_wrDeltaEstimate > 0 ? m_wrDeltaEstimate : sizeof(BufferFrame) * nbDecoderSlots + m_wrDeltaEstimate); + int sampleRate = m_currentMeta.m_sampleRate; + + if (sampleRate > 0) + { + int64_t ts = m_currentMeta.m_tv_sec * 1000000LL + m_currentMeta.m_tv_usec; + ts -= (rwDelayBytes * 1000000LL) / (sampleRate * sizeof(Sample)); + m_tvOut_sec = ts / 1000000LL; + m_tvOut_usec = ts - (m_tvOut_sec * 1000000LL); + } + + if (!m_decoderSlots[slotIndex].m_decoded) + { + qDebug() << "SDRdaemonFECBuffer::checkSlotData: incomplete frame:" + << " m_blockCount: " << m_decoderSlots[slotIndex].m_blockCount + << " m_recoveryCount: " << m_decoderSlots[slotIndex].m_recoveryCount; + } +} + +void SDRdaemonFECBuffer::writeData(char *array, uint32_t length) +{ + SuperBlock *superBlock = (SuperBlock *) array; + int frameIndex = superBlock->header.frameIndex; + int decoderIndex = frameIndex % nbDecoderSlots; + + // frame break + + if (m_frameHead == -1) // initial state + { + m_decoderIndexHead = decoderIndex; // new decoder slot head + m_frameHead = frameIndex; + initReadIndex(); // reset read index + initDecodeAllSlots(); // initialize all slots + } + else if (m_frameHead != frameIndex) // frame break => new frame starts + { + m_decoderIndexHead = decoderIndex; // new decoder slot head + m_frameHead = frameIndex; // new frame head + checkSlotData(decoderIndex); // check slot before re-init + rwCorrectionEstimate(decoderIndex); + initDecodeSlot(decoderIndex); // collect stats and re-initialize current slot + } + + // Block processing + + if (m_decoderSlots[decoderIndex].m_blockCount < m_nbOriginalBlocks) // not enough blocks to decode -> store data + { + int blockIndex = superBlock->header.blockIndex; + int blockCount = m_decoderSlots[decoderIndex].m_blockCount; + int recoveryCount = m_decoderSlots[decoderIndex].m_recoveryCount; + m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[blockCount].Index = blockIndex; + + if (blockIndex == 0) // first block with meta + { + m_decoderSlots[decoderIndex].m_metaRetrieved = true; + } + + if (blockIndex < m_nbOriginalBlocks) // original data + { + m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[blockCount].Block = (void *) storeOriginalBlock(decoderIndex, blockIndex, superBlock->protectedBlock); + m_decoderSlots[decoderIndex].m_originalCount++; + } + else // recovery data + { + m_decoderSlots[decoderIndex].m_recoveryBlocks[recoveryCount] = superBlock->protectedBlock; + m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[blockCount].Block = (void *) &m_decoderSlots[decoderIndex].m_recoveryBlocks[recoveryCount]; + m_decoderSlots[decoderIndex].m_recoveryCount++; + } + } + + m_decoderSlots[decoderIndex].m_blockCount++; + + if (m_decoderSlots[decoderIndex].m_blockCount == m_nbOriginalBlocks) // ready to decode + { + m_decoderSlots[decoderIndex].m_decoded = true; + + if (m_cm256_OK && (m_decoderSlots[decoderIndex].m_recoveryCount > 0)) // recovery data used => need to decode FEC + { + m_paramsCM256.BlockBytes = sizeof(ProtectedBlock); // never changes + m_paramsCM256.OriginalCount = m_nbOriginalBlocks; // never changes + + if (m_decoderSlots[decoderIndex].m_metaRetrieved) { + m_paramsCM256.RecoveryCount = m_currentMeta.m_nbFECBlocks; + } else { + m_paramsCM256.RecoveryCount = m_decoderSlots[decoderIndex].m_recoveryCount; + } + + if (m_cm256.cm256_decode(m_paramsCM256, m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks)) // CM256 decode + { + qDebug() << "SDRdaemonFECBuffer::writeData: decode CM256 error:" + << " m_originalCount: " << m_decoderSlots[decoderIndex].m_originalCount + << " m_recoveryCount: " << m_decoderSlots[decoderIndex].m_recoveryCount; + } + else + { + qDebug() << "SDRdaemonFECBuffer::writeData: decode CM256 success:" + << " m_originalCount: " << m_decoderSlots[decoderIndex].m_originalCount + << " m_recoveryCount: " << m_decoderSlots[decoderIndex].m_recoveryCount; + + for (int ir = 0; ir < m_decoderSlots[decoderIndex].m_recoveryCount; ir++) // restore missing blocks + { + int recoveryIndex = m_nbOriginalBlocks - m_decoderSlots[decoderIndex].m_recoveryCount + ir; + int blockIndex = m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[recoveryIndex].Index; + ProtectedBlock *recoveredBlock = (ProtectedBlock *) m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[recoveryIndex].Block; + + if (blockIndex == 0) // first block with meta + { + MetaDataFEC *metaData = (MetaDataFEC *) recoveredBlock; + + boost::crc_32_type crc32; + crc32.process_bytes(metaData, 20); + + if (crc32.checksum() == metaData->m_crc32) + { + m_decoderSlots[decoderIndex].m_metaRetrieved = true; + printMeta("SDRdaemonFECBuffer::writeData: recovered meta", metaData); + } + else + { + qDebug() << "SDRdaemonFECBuffer::writeData: recovered meta: invalid CRC32"; + } + } + + storeOriginalBlock(decoderIndex, blockIndex, *recoveredBlock); + + qDebug() << "SDRdaemonFECBuffer::writeData: recovered block #" << blockIndex; + } // restore missing blocks + } // CM256 decode + } // revovery + + if (m_decoderSlots[decoderIndex].m_metaRetrieved) // block zero with its meta data has been received + { + MetaDataFEC *metaData = getMetaData(decoderIndex); + + if (!(*metaData == m_currentMeta)) + { + int sampleRate = metaData->m_sampleRate; + + if (sampleRate > 0) { + m_bufferLenSec = (float) m_framesNbBytes / (float) (sampleRate * m_iqSampleSize); + m_balCorrLimit = sampleRate / 1000; // +/- 1 ms correction max per read + m_readNbBytes = (sampleRate * m_iqSampleSize) / 20; + } + + printMeta("SDRdaemonFECBuffer::writeData: new meta", metaData); // print for change other than timestamp + } + + m_currentMeta = *metaData; // renew current meta + } // check block 0 + } // decode +} + +void SDRdaemonFECBuffer::writeData0(char *array, uint32_t length) +{ +// Kept as comments for the out of sync blocks algorithms +// assert(length == m_udpPayloadSize); +// +// bool dataAvailable = false; +// SuperBlock *superBlock = (SuperBlock *) array; +// int frameIndex = superBlock->header.frameIndex; +// int decoderIndex = frameIndex % nbDecoderSlots; +// int blockIndex = superBlock->header.blockIndex; +// +//// qDebug() << "SDRdaemonFECBuffer::writeData:" +//// << " frameIndex: " << frameIndex +//// << " decoderIndex: " << decoderIndex +//// << " blockIndex: " << blockIndex; +// +// if (m_frameHead == -1) // initial state +// { +// m_decoderIndexHead = decoderIndex; // new decoder slot head +// m_frameHead = frameIndex; +// initReadIndex(); // reset read index +// initDecodeAllSlots(); // initialize all slots +// } +// else +// { +// int frameDelta = m_frameHead - frameIndex; +// +// if (frameDelta < 0) +// { +// if (-frameDelta < nbDecoderSlots) // new frame head not too new +// { +// //qDebug() << "SDRdaemonFECBuffer::writeData: new frame head (1): " << frameIndex << ":" << frameDelta << ":" << decoderIndex; +// m_decoderIndexHead = decoderIndex; // new decoder slot head +// m_frameHead = frameIndex; +// checkSlotData(decoderIndex); +// dataAvailable = true; +// initDecodeSlot(decoderIndex); // collect stats and re-initialize current slot +// } +// else if (-frameDelta <= 65536 - nbDecoderSlots) // loss of sync start over +// { +// //qDebug() << "SDRdaemonFECBuffer::writeData: loss of sync start over (1)" << frameIndex << ":" << frameDelta << ":" << decoderIndex; +// m_decoderIndexHead = decoderIndex; // new decoder slot head +// m_frameHead = frameIndex; +// initReadIndex(); // reset read index +// initDecodeAllSlots(); // re-initialize all slots +// } +// } +// else +// { +// if (frameDelta > 65536 - nbDecoderSlots) // new frame head not too new +// { +// //qDebug() << "SDRdaemonFECBuffer::writeData: new frame head (2): " << frameIndex << ":" << frameDelta << ":" << decoderIndex; +// m_decoderIndexHead = decoderIndex; // new decoder slot head +// m_frameHead = frameIndex; +// checkSlotData(decoderIndex); +// dataAvailable = true; +// initDecodeSlot(decoderIndex); // collect stats and re-initialize current slot +// } +// else if (frameDelta >= nbDecoderSlots) // loss of sync start over +// { +// //qDebug() << "SDRdaemonFECBuffer::writeData: loss of sync start over (2)" << frameIndex << ":" << frameDelta << ":" << decoderIndex; +// m_decoderIndexHead = decoderIndex; // new decoder slot head +// m_frameHead = frameIndex; +// initReadIndex(); // reset read index +// initDecodeAllSlots(); // re-initialize all slots +// } +// } +// } +// +// // decoderIndex should now be correctly set +// +} + +uint8_t *SDRdaemonFECBuffer::readData(int32_t length) +{ + uint8_t *buffer = (uint8_t *) m_frames; + uint32_t readIndex = m_readIndex; + + m_nbReads++; + + // SEGFAULT FIX: arbitratily truncate so that it does not exceed buffer length + if (length > framesSize) { + length = framesSize; + } + + if (m_readIndex + length < m_framesNbBytes) // ends before buffer bound + { + m_readIndex += length; + return &buffer[readIndex]; + } + else if (m_readIndex + length == m_framesNbBytes) // ends at buffer bound + { + m_readIndex = 0; + return &buffer[readIndex]; + } + else // ends after buffer bound + { + if (length > m_readSize) // reallocate composition buffer if necessary + { + if (m_readBuffer) { + delete[] m_readBuffer; + } + + m_readBuffer = new uint8_t[length]; + m_readSize = length; + } + + std::memcpy((void *) m_readBuffer, (const void *) &buffer[m_readIndex], m_framesNbBytes - m_readIndex); // copy end of buffer + length -= m_framesNbBytes - m_readIndex; + std::memcpy((void *) &m_readBuffer[m_framesNbBytes - m_readIndex], (const void *) buffer, length); // copy start of buffer + m_readIndex = length; + return m_readBuffer; + } +} + +void SDRdaemonFECBuffer::printMeta(const QString& header, MetaDataFEC *metaData) +{ + qDebug() << header << ": " + << "|" << metaData->m_centerFrequency + << ":" << metaData->m_sampleRate + << ":" << (int) (metaData->m_sampleBytes & 0xF) + << ":" << (int) metaData->m_sampleBits + << ":" << (int) metaData->m_nbOriginalBlocks + << ":" << (int) metaData->m_nbFECBlocks + << "|" << metaData->m_tv_sec + << ":" << metaData->m_tv_usec + << "|"; +} diff --git a/devices/sdrdaemon/sdrdaemonfecbuffer.h b/devices/sdrdaemon/sdrdaemonfecbuffer.h new file mode 100644 index 000000000..b4dbe2dc2 --- /dev/null +++ b/devices/sdrdaemon/sdrdaemonfecbuffer.h @@ -0,0 +1,274 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef DEVICES_SDRDAEMON_SDRDAEMONFECBUFFER_H_ +#define DEVICES_SDRDAEMON_SDRDAEMONFECBUFFER_H_ + +#include +#include +#include +#include "cm256.h" +#include "util/movingaverage.h" + + +#define SDRDAEMONFEC_UDPSIZE 512 // UDP payload size +#define SDRDAEMONFEC_NBORIGINALBLOCKS 128 // number of sample blocks per frame excluding FEC blocks +#define SDRDAEMONFEC_NBDECODERSLOTS 16 // power of two sub multiple of uint16_t size. A too large one is superfluous. + +class SDRdaemonFECBuffer +{ +public: +#pragma pack(push, 1) + struct MetaDataFEC + { + uint32_t m_centerFrequency; //!< 4 center frequency in kHz + uint32_t m_sampleRate; //!< 8 sample rate in Hz + uint8_t m_sampleBytes; //!< 9 MSB(4): indicators, LSB(4) number of bytes per sample + uint8_t m_sampleBits; //!< 10 number of effective bits per sample + uint8_t m_nbOriginalBlocks; //!< 11 number of blocks with original (protected) data + uint8_t m_nbFECBlocks; //!< 12 number of blocks carrying FEC + uint32_t m_tv_sec; //!< 16 seconds of timestamp at start time of super-frame processing + uint32_t m_tv_usec; //!< 20 microseconds of timestamp at start time of super-frame processing + uint32_t m_crc32; //!< 24 CRC32 of the above + + bool operator==(const MetaDataFEC& rhs) + { + return (memcmp((const void *) this, (const void *) &rhs, 12) == 0); // Only the 12 first bytes are relevant + } + + void init() + { + memset((void *) this, 0, sizeof(MetaDataFEC)); + } + }; + + struct Sample + { + int16_t i; + int16_t q; + }; + + struct Header + { + uint16_t frameIndex; + uint8_t blockIndex; + uint8_t filler; + }; + + static const int samplesPerBlock = (SDRDAEMONFEC_UDPSIZE - sizeof(Header)) / sizeof(Sample); + static const int framesSize = SDRDAEMONFEC_NBDECODERSLOTS * (SDRDAEMONFEC_NBORIGINALBLOCKS - 1) * (SDRDAEMONFEC_UDPSIZE - sizeof(Header)); + + struct ProtectedBlock + { + Sample samples[samplesPerBlock]; + }; + + struct SuperBlock + { + Header header; + ProtectedBlock protectedBlock; + }; +#pragma pack(pop) + + SDRdaemonFECBuffer(uint32_t throttlems); + ~SDRdaemonFECBuffer(); + + // R/W operations + void writeData(char *array, uint32_t length); //!< Write data into buffer. + void writeData0(char *array, uint32_t length); //!< Write data into buffer. + uint8_t *readData(int32_t length); //!< Read data from buffer + + // meta data + const MetaDataFEC& getCurrentMeta() const { return m_currentMeta; } + + // samples timestamp + uint32_t getTVOutSec() const { return m_tvOut_sec; } + uint32_t getTVOutUsec() const { return m_tvOut_usec; } + + // stats + + int getCurNbBlocks() const { return m_curNbBlocks; } + int getCurOriginalBlocks() const { return m_curOriginalBlocks; } + int getCurNbRecovery() const { return m_curNbRecovery; } + float getAvgNbBlocks() const { return m_avgNbBlocks; } + float getAvgOriginalBlocks() const { return m_avgOrigBlocks; } + float getAvgNbRecovery() const { return m_avgNbRecovery; } + + int getMinNbBlocks() + { + int minNbBlocks = m_minNbBlocks; + m_minNbBlocks = 256; + return minNbBlocks; + } + + int getMinOriginalBlocks() + { + int minOriginalBlocks = m_minOriginalBlocks; + m_minOriginalBlocks = 128; + return minOriginalBlocks; + } + + int getMaxNbRecovery() + { + int maxNbRecovery = m_maxNbRecovery; + m_maxNbRecovery = 0; + return maxNbRecovery; + } + + bool allFramesDecoded() + { + bool framesDecoded = m_framesDecoded; + m_framesDecoded = true; + return framesDecoded; + } + + float getBufferLengthInSecs() const { return m_bufferLenSec; } + int32_t getRWBalanceCorrection() const { return m_balCorrection; } + + /** Get buffer gauge value in % of buffer size ([-50:50]) + * [-50:0] : write leads or read lags + * [0:50] : read leads or write lags + */ + inline int32_t getBufferGauge() const + { + if (m_framesNbBytes) + { + int32_t val = (m_wrDeltaEstimate * 100) / (int32_t) m_framesNbBytes; + int32_t ret = val < 0 ? -val - 50 : 50 -val; + int32_t rp = (m_readIndex * 100) / (int32_t) m_framesNbBytes; + return ret; + } + else + { + return 0; // default position + } + } + + static const int m_udpPayloadSize = SDRDAEMONFEC_UDPSIZE; + static const int m_nbOriginalBlocks = SDRDAEMONFEC_NBORIGINALBLOCKS; + static const int m_sampleSize; + static const int m_iqSampleSize; + +private: + static const int nbDecoderSlots = SDRDAEMONFEC_NBDECODERSLOTS; + +#pragma pack(push, 1) + struct BufferFrame + { + ProtectedBlock m_blocks[m_nbOriginalBlocks - 1]; + }; +#pragma pack(pop) + + struct DecoderSlot + { + ProtectedBlock m_blockZero; //!< First block of a frame. Has meta data. + ProtectedBlock m_originalBlocks[m_nbOriginalBlocks]; //!< Original blocks retrieved directly or by later FEC + ProtectedBlock m_recoveryBlocks[m_nbOriginalBlocks]; //!< Recovery blocks (FEC blocks) with max size + CM256::cm256_block m_cm256DescriptorBlocks[m_nbOriginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes) + int m_blockCount; //!< number of blocks received for this frame + int m_originalCount; //!< number of original blocks received + int m_recoveryCount; //!< number of recovery blocks received + bool m_decoded; //!< true if decoded + bool m_metaRetrieved; //!< true if meta data (block zero) was retrieved + }; + + MetaDataFEC m_currentMeta; //!< Stored current meta data + CM256::cm256_encoder_params m_paramsCM256; //!< CM256 decoder parameters block + DecoderSlot m_decoderSlots[nbDecoderSlots]; //!< CM256 decoding control/buffer slots + BufferFrame m_frames[nbDecoderSlots]; //!< Samples buffer + int m_framesNbBytes; //!< Number of bytes in samples buffer + int m_decoderIndexHead; //!< index of the current head frame slot in decoding slots + int m_frameHead; //!< index of the current head frame sent + int m_curNbBlocks; //!< (stats) instantaneous number of blocks received + int m_minNbBlocks; //!< (stats) minimum number of blocks received since last poll + int m_curOriginalBlocks; //!< (stats) instantanous number of original blocks received + int m_minOriginalBlocks; //!< (stats) minimum number of original blocks received since last poll + int m_curNbRecovery; //!< (stats) instantaneous number of recovery blocks used + int m_maxNbRecovery; //!< (stats) maximum number of recovery blocks used since last poll + MovingAverage m_avgNbBlocks; //!< (stats) average number of blocks received + MovingAverage m_avgOrigBlocks; //!< (stats) average number of original blocks received + MovingAverage m_avgNbRecovery; //!< (stats) average number of recovery blocks used + bool m_framesDecoded; //!< [stats] true if all frames were decoded since last poll + int m_readIndex; //!< current byte read index in frames buffer + int m_wrDeltaEstimate; //!< Sampled estimate of write to read indexes difference + uint32_t m_tvOut_sec; //!< Estimated returned samples timestamp (seconds) + uint32_t m_tvOut_usec; //!< Estimated returned samples timestamp (microseconds) + int m_readNbBytes; //!< Nominal number of bytes per read (50ms) + + uint32_t m_throttlemsNominal; //!< Initial throttle in ms + uint8_t* m_readBuffer; //!< Read buffer to hold samples when looping back to beginning of raw buffer + uint32_t m_readSize; //!< Read buffer size + + float m_bufferLenSec; + + int m_nbReads; //!< Number of buffer reads since start of auto R/W balance correction period + int m_nbWrites; //!< Number of buffer writes since start of auto R/W balance correction period + int m_balCorrection; //!< R/W balance correction in number of samples + int m_balCorrLimit; //!< Correction absolute value limit in number of samples + CM256 m_cm256; //!< CM256 library + bool m_cm256_OK; //!< CM256 library initialized OK + + inline ProtectedBlock* storeOriginalBlock(int slotIndex, int blockIndex, const ProtectedBlock& protectedBlock) + { + if (blockIndex == 0) { + // m_decoderSlots[slotIndex].m_originalBlocks[0] = protectedBlock; + // return &m_decoderSlots[slotIndex].m_originalBlocks[0]; + m_decoderSlots[slotIndex].m_blockZero = protectedBlock; + return &m_decoderSlots[slotIndex].m_blockZero; + } else { + // m_decoderSlots[slotIndex].m_originalBlocks[blockIndex] = protectedBlock; + // return &m_decoderSlots[slotIndex].m_originalBlocks[blockIndex]; + m_frames[slotIndex].m_blocks[blockIndex - 1] = protectedBlock; + return &m_frames[slotIndex].m_blocks[blockIndex - 1]; + } + } + + inline ProtectedBlock& getOriginalBlock(int slotIndex, int blockIndex) + { + if (blockIndex == 0) { + // return m_decoderSlots[slotIndex].m_originalBlocks[0]; + return m_decoderSlots[slotIndex].m_blockZero; + } else { + // return m_decoderSlots[slotIndex].m_originalBlocks[blockIndex]; + return m_frames[slotIndex].m_blocks[blockIndex - 1]; + } + } + + inline MetaDataFEC *getMetaData(int slotIndex) + { + // return (MetaDataFEC *) &m_decoderSlots[slotIndex].m_originalBlocks[0]; + return (MetaDataFEC *) &m_decoderSlots[slotIndex].m_blockZero; + } + + inline void resetOriginalBlocks(int slotIndex) + { + // memset((void *) m_decoderSlots[slotIndex].m_originalBlocks, 0, m_nbOriginalBlocks * sizeof(ProtectedBlock)); + memset((void *) &m_decoderSlots[slotIndex].m_blockZero, 0, sizeof(ProtectedBlock)); + memset((void *) m_frames[slotIndex].m_blocks, 0, (m_nbOriginalBlocks - 1) * sizeof(ProtectedBlock)); + } + + void initDecodeAllSlots(); + void initReadIndex(); + void rwCorrectionEstimate(int slotIndex); + void checkSlotData(int slotIndex); + void initDecodeSlot(int slotIndex); + + static void printMeta(const QString& header, MetaDataFEC *metaData); +}; + + + +#endif /* DEVICES_SDRDAEMON_SDRDAEMONFECBUFFER_H_ */