Implementation of M17 frame decoder

This commit is contained in:
Silvano Seva 2022-01-05 14:12:23 +01:00
parent 49bd8ee2f4
commit 5fbd39959c
6 changed files with 319 additions and 3 deletions

View File

@ -43,6 +43,7 @@ openrtx_src = ['openrtx/src/core/state.c',
'openrtx/src/protocols/M17/M17Callsign.cpp',
'openrtx/src/protocols/M17/M17Modulator.cpp',
'openrtx/src/protocols/M17/M17Demodulator.cpp',
'openrtx/src/protocols/M17/M17FrameDecoder.cpp',
'openrtx/src/protocols/M17/M17Transmitter.cpp',
'openrtx/src/protocols/M17/M17LinkSetupFrame.cpp']

View File

@ -34,9 +34,9 @@ using meta_t = std::array< uint8_t, 14 >; // Data type for LSF metadata fie
using payload_t = std::array< uint8_t, 16 >; // Data type for frame payload field
using lich_t = std::array< uint8_t, 12 >; // Data type for Golay(24,12) encoded LICH data
static constexpr std::array<uint8_t, 2> LSF_SYNC_WORD = {0x55, 0xF7}; // LSF sync word
static constexpr std::array<uint8_t, 2> DATA_SYNC_WORD = {0xFF, 0x5D}; // Stream data sync word
static constexpr std::array<uint8_t, 2> LSF_SYNC_WORD = {0x55, 0xF7}; // LSF sync word
static constexpr std::array<uint8_t, 2> DATA_SYNC_WORD = {0xFF, 0x5D}; // Stream data sync word
static constexpr std::array<uint8_t, 2> PACKET_SYNC_WORD = {0x75, 0xFF}; // Packet data sync word
/**

View File

@ -0,0 +1,131 @@
/***************************************************************************
* Copyright (C) 2022 by Federico Amedeo Izzo IU2NUO, *
* Niccolò Izzo IU2KIN *
* Frederik Saraci IU2NRO *
* Silvano Seva IU2KWO *
* *
* 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; either version 3 of the License, or *
* (at your option) any later version. *
* *
* 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 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, see <http://www.gnu.org/licenses/> *
***************************************************************************/
#ifndef M17FRAMEDECODER_H
#define M17FRAMEDECODER_H
#ifndef __cplusplus
#error This header is C++ only!
#endif
#include <string>
#include <array>
#include "M17LinkSetupFrame.h"
#include "M17Viterbi.h"
#include "M17StreamFrame.h"
enum class M17FrameType : uint8_t
{
PREAMBLE = 0, ///< Frame contains a preamble.
LINK_SETUP = 1, ///< Frame is a Link Setup Frame.
STREAM = 2, ///< Frame is a stream data frame.
PACKET = 3, ///< Frame is a packet data frame.
UNKNOWN = 4 ///< Frame is unknown.
};
/**
* M17 frame decoder.
*/
class M17FrameDecoder
{
public:
/**
* Constructor.
*/
M17FrameDecoder();
/**
* Destructor.
*/
~M17FrameDecoder();
/**
* Clear the internal data structures.
*/
void reset();
/**
* Decode an M17 frame, identifying its type. Frame data must contain the
* sync word in the first two bytes.
*
* @param frame: byte array containg frame data.
* @return the type of frame recognized.
*/
M17FrameType decodeFrame(const std::array< uint8_t, 48 >& frame);
/**
* Get the latest Link Setup Frame decoded. Check of the validity of the
* data contained in the LSF is left to application code.
*
* @return a reference to the latest Link Setup Frame decoded.
*/
const M17LinkSetupFrame& getLsf()
{
return lsf;
}
/**
* Get the latest stream data frame decoded.
*
* @return a reference to the latest stream data frame decoded.
*/
const M17StreamFrame& getStreamFrame()
{
return streamFrame;
}
private:
/**
* Decode Link Setup Frame data and update the internal LSF field with
* the new frame data.
*
* @param data: byte array containg frame data, without sync word.
*/
void decodeLSF(const std::array< uint8_t, 46 >& data);
/**
* Decode stream data and update the internal LSF field with the new
* frame data.
*
* @param data: byte array containg frame data, without sync word.
*/
void decodeStream(const std::array< uint8_t, 46 >& data);
/**
* Decode a LICH block.
*
* @param segment: byte array where to store the decoded Link Setup Frame
* segment. The last byte contains the segment number.
* @param lich: LICH block to be decoded.
* @return true when the LICH block is successfully decoded.
*/
bool decodeLich(std::array< uint8_t, 6 >& segment, const lich_t& lich);
uint8_t lsfSegmentMap; ///< Bitmap for LSF reassembly from LICH
M17LinkSetupFrame lsf; ///< Latest LSF received.
M17LinkSetupFrame lsfFromLich; ///< LSF assembled from LICH segments.
M17StreamFrame streamFrame; ///< Latest stream dat frame received.
M17Viterbi viterbi; ///< Viterbi decoder.
};
#endif /* M17FRAMEDECODER_H */

View File

@ -29,6 +29,8 @@
#include <array>
#include "M17Datatypes.h"
class M17FrameDecoder;
/**
* This class describes and handles an M17 Link Setup Frame.
* By default the frame contains a broadcast destination address, unless a
@ -157,6 +159,9 @@ private:
uint16_t crc; ///< CRC
}
data; ///< Frame data.
// Frame decoder class needs to access raw frame data
friend class M17FrameDecoder;
};
#endif /* M17_LINKSETUPFRAME_H */

View File

@ -29,6 +29,8 @@
#include <string>
#include "M17Datatypes.h"
class M17FrameDecoder;
/**
* This class describes and handles a generic M17 data frame.
*/
@ -131,6 +133,9 @@ private:
///< Frame data.
static constexpr uint16_t EOS_BIT = 0x0080; ///< End Of Stream bit.
static constexpr uint16_t FN_MASK = 0x7FFF; ///< Bitmask for frame number.
// Frame decoder class needs to access raw frame data
friend class M17FrameDecoder;
};
#endif /* M17_FRAME_H */

View File

@ -0,0 +1,174 @@
/***************************************************************************
* Copyright (C) 2022 by Federico Amedeo Izzo IU2NUO, *
* Niccolò Izzo IU2KIN *
* Frederik Saraci IU2NRO *
* Silvano Seva IU2KWO *
* *
* 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; either version 3 of the License, or *
* (at your option) any later version. *
* *
* 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 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, see <http://www.gnu.org/licenses/> *
***************************************************************************/
#include <M17/M17Golay.h>
#include <M17/M17FrameDecoder.h>
#include <M17/M17Interleaver.h>
#include <M17/M17Decorrelator.h>
#include <M17/M17CodePuncturing.h>
#include <algorithm>
M17FrameDecoder::M17FrameDecoder() { }
M17FrameDecoder::~M17FrameDecoder() { }
void M17FrameDecoder::reset()
{
lsfSegmentMap = 0;
lsf.clear();
lsfFromLich.clear();
streamFrame.clear();
}
M17FrameType M17FrameDecoder::decodeFrame(const std::array< uint8_t, 48 >& frame)
{
std::array< uint8_t, 2 > syncWord;
std::array< uint8_t, 46 > data;
std::copy_n(frame.begin(), 2, syncWord.begin());
std::copy(frame.begin() + 2, frame.end(), data.begin());
// Re-correlating data is the same operation as decorrelating
decorrelate(data);
deinterleave(data);
// Preamble
if((syncWord[0] == 0x77) && (syncWord[1] == 0x77))
{
return M17FrameType::PREAMBLE;
}
// Link Setup Frame
if(syncWord == LSF_SYNC_WORD)
{
decodeLSF(data);
return M17FrameType::LINK_SETUP;
}
// Stream data frame
if(syncWord == DATA_SYNC_WORD)
{
decodeStream(data);
return M17FrameType::STREAM;
}
// If we get here, we received an unknown frame
return M17FrameType::UNKNOWN;
}
void M17FrameDecoder::decodeLSF(const std::array< uint8_t, 46 >& data)
{
std::array< uint8_t, sizeof(M17LinkSetupFrame) > tmp;
viterbi.decodePunctured(data, tmp, LSF_puncture);
memcpy(&lsf.data, tmp.data(), tmp.size());
}
void M17FrameDecoder::decodeStream(const std::array< uint8_t, 46 >& data)
{
// Extract and unpack the LICH segment contained at beginning of frame
lich_t lich;
std::array < uint8_t, 6 > lsfSegment;
std::copy_n(data.begin(), lich.size(), lich.begin());
bool decodeOk = decodeLich(lsfSegment, lich);
if(decodeOk)
{
// Append LICH segment
uint8_t segmentNum = lsfSegment[5];
uint8_t *ptr = reinterpret_cast < uint8_t * >(&lsfFromLich.data);
memcpy(ptr + segmentNum, lsfSegment.data(), 5);
// Mark this segment as present
lsfSegmentMap |= 1 << segmentNum;
// Check if we have received all the five LICH segments
if(lsfSegmentMap == 0x1F)
{
if(lsfFromLich.valid()) lsf = lsfFromLich;
lsfSegmentMap = 0;
lsfFromLich.clear();
}
}
// Extract and decode stream data
std::array< uint8_t, 34 > punctured;
std::array< uint8_t, sizeof(M17StreamFrame) > tmp;
auto begin = data.begin();
begin += lich.size();
std::copy(begin, data.end(), punctured.begin());
viterbi.decodePunctured(punctured, tmp, Audio_puncture);
memcpy(&streamFrame.data, tmp.data(), tmp.size());
}
bool M17FrameDecoder::decodeLich(std::array < uint8_t, 6 >& segment,
const lich_t& lich)
{
/*
* Extract and unpack the LICH segment contained in the frame header.
* The LICH segment is composed of four blocks of Golay(24,12) encoded data
* and carries five bytes of the original Link Setup Frame. The sixth byte
* is the segment number, allowing to determine the correct position of the
* segment when reassembling the LSF.
*
* NOTE: LICH data is stored in big-endian format, swap and shift after
* memcpy convert it to little-endian.
*/
segment.fill(0x00);
size_t index = 0;
uint32_t block = 0;
for(size_t i = 0; i < 4; i++)
{
memcpy(&block, lich.data() + 3*i, 3);
block = __builtin_bswap32(block) >> 8;
uint16_t decoded = golay24_decode(block);
// Unrecoverable error, abort decoding
if(decoded == 0xFFFF)
{
segment.fill(0x00);
return false;
}
if(i & 1)
{
segment[index++] |= (decoded >> 8); // upper 4 bits
segment[index++] = (decoded & 0xFF); // lower 8 bits
}
else
{
segment[index++] |= (decoded >> 4); // upper 8 bits
segment[index] = (decoded & 0x0F) << 4; // lower 4 bits
}
}
// Last byte of the segment contains the segment number, shift left
// by five when packing the LICH.
segment[5] >>= 5;
return true;
}