Changed project structure

This commit is contained in:
AlexandreRouma
2021-10-03 16:50:36 +02:00
parent c36034dbb8
commit 73393e36c6
252 changed files with 52 additions and 52 deletions

View File

@ -0,0 +1,50 @@
cmake_minimum_required(VERSION 3.13)
project(m17_decoder)
if (MSVC)
add_compile_options(/O2 /Ob2 /std:c++17 /EHsc)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup)
else ()
add_compile_options(-O3 -std=c++17)
endif ()
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
include_directories("src/")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native")
add_library(m17_decoder SHARED ${SRC})
target_link_libraries(m17_decoder PRIVATE sdrpp_core)
set_target_properties(m17_decoder PROPERTIES PREFIX "")
if (MSVC)
# Lib path
target_include_directories(m17_decoder PUBLIC "C:/Program Files/codec2/include/")
target_link_directories(sdrpp_core PUBLIC "C:/Program Files/codec2/lib")
target_link_libraries(m17_decoder PRIVATE libcodec2)
else (MSVC)
find_package(PkgConfig)
pkg_check_modules(LIBCODEC2 REQUIRED codec2)
target_include_directories(m17_decoder PUBLIC ${LIBCODEC2_INCLUDE_DIRS})
target_link_directories(m17_decoder PUBLIC ${LIBCODEC2_LIBRARY_DIRS})
target_link_libraries(m17_decoder PUBLIC ${LIBCODEC2_LIBRARIES})
# Include it because for some reason pkgconfig doesn't look here?
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
target_include_directories(m17_decoder PUBLIC "/usr/local/include")
endif()
endif ()
# Install directives
install(TARGETS m17_decoder DESTINATION lib/sdrpp/plugins)

View File

@ -0,0 +1,16 @@
#include <base40.h>
void decode_callsign_base40(uint64_t encoded, char *callsign) {
if (encoded >= 262144000000000) { // 40^9
*callsign = 0;
return;
}
char *p = callsign;
for (; encoded > 0; p++) {
*p = " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/."[encoded % 40];
encoded /= 40;
}
*p = 0;
return;
}

View File

@ -0,0 +1,4 @@
#pragma once
#include <stdint.h>
void decode_callsign_base40(uint64_t encoded, char* callsign);

View File

@ -0,0 +1,72 @@
// Copyright 2020 Mobilinkd LLC.
#pragma once
#include <cstdint>
#include <array>
#include <cstddef>
namespace mobilinkd
{
template <uint16_t Poly = 0x5935, uint16_t Init = 0xFFFF>
struct CRC16
{
static constexpr uint16_t MASK = 0xFFFF;
static constexpr uint16_t LSB = 0x0001;
static constexpr uint16_t MSB = 0x8000;
uint16_t reg_ = Init;
void reset()
{
reg_ = Init;
for (size_t i = 0; i != 16; ++i)
{
auto bit = reg_ & LSB;
if (bit) reg_ ^= Poly;
reg_ >>= 1;
if (bit) reg_ |= MSB;
}
reg_ &= MASK;
}
void operator()(uint8_t byte)
{
reg_ = crc(byte, reg_);
}
uint16_t crc(uint8_t byte, uint16_t reg)
{
for (size_t i = 0; i != 8; ++i)
{
auto msb = reg & MSB;
reg = ((reg << 1) & MASK) | ((byte >> (7 - i)) & LSB);
if (msb) reg ^= Poly;
}
return reg & MASK;
}
uint16_t get()
{
auto reg = reg_;
for (size_t i = 0; i != 16; ++i)
{
auto msb = reg & MSB;
reg = ((reg << 1) & MASK);
if (msb) reg ^= Poly;
}
return reg;
}
std::array<uint8_t, 2> get_bytes()
{
auto crc = get();
std::array<uint8_t, 2> result{uint8_t((crc >> 8) & 0xFF), uint8_t(crc & 0xFF)};
return result;
}
};
} // mobilinkd

View File

@ -0,0 +1,233 @@
// Copyright 2020 Rob Riggs <rob@mobilinkd.com>
// All rights reserved.
#pragma once
#include <array>
#include <cstdint>
#include <algorithm>
#include <utility>
namespace mobilinkd {
// Parts are adapted from:
// http://aqdi.com/articles/using-the-golay-error-detection-and-correction-code-3/
namespace Golay24
{
int popcount(uint32_t n) {
int count = 0;
for (int i = 0; i < 32; i++) {
count += ((n >> i) & 1);
}
return count;
}
namespace detail
{
// Need a constexpr sort.
// https://stackoverflow.com/a/40030044/854133
template<class T>
void swap(T& l, T& r)
{
T tmp = std::move(l);
l = std::move(r);
r = std::move(tmp);
}
template <typename T, size_t N>
struct array
{
constexpr T& operator[](size_t i)
{
return arr[i];
}
constexpr const T& operator[](size_t i) const
{
return arr[i];
}
constexpr const T* begin() const
{
return arr;
}
constexpr const T* end() const
{
return arr + N;
}
T arr[N];
};
template <typename T, size_t N>
void sort_impl(array<T, N> &array, size_t left, size_t right)
{
if (left < right)
{
size_t m = left;
for (size_t i = left + 1; i<right; i++)
if (array[i]<array[left])
swap(array[++m], array[i]);
swap(array[left], array[m]);
sort_impl(array, left, m);
sort_impl(array, m + 1, right);
}
}
template <typename T, size_t N>
array<T, N> sort(array<T, N> array)
{
auto sorted = array;
sort_impl(sorted, 0, N);
return sorted;
}
} // detail
// static constexpr uint16_t POLY = 0xAE3;
constexpr uint16_t POLY = 0xC75;
#pragma pack(push, 1)
struct SyndromeMapEntry
{
uint32_t a{0};
uint16_t b{0};
};
#pragma pack(pop)
/**
* Calculate the syndrome of a [23,12] Golay codeword.
*
* @return the 11-bit syndrome of the codeword in bits [22:12].
*/
uint32_t syndrome(uint32_t codeword)
{
codeword &= 0xffffffl;
for (size_t i = 0; i != 12; ++i)
{
if (codeword & 1)
codeword ^= POLY;
codeword >>= 1;
}
return (codeword << 12);
}
bool parity(uint32_t codeword)
{
return popcount(codeword) & 1;
}
SyndromeMapEntry makeSyndromeMapEntry(uint64_t val)
{
return SyndromeMapEntry{uint32_t(val >> 16), uint16_t(val & 0xFFFF)};
}
uint64_t makeSME(uint64_t syndrome, uint32_t bits)
{
return (syndrome << 24) | (bits & 0xFFFFFF);
}
constexpr size_t LUT_SIZE = 2048;
std::array<SyndromeMapEntry, LUT_SIZE> make_lut()
{
constexpr size_t VECLEN=23;
detail::array<uint64_t, LUT_SIZE> result{};
size_t index = 0;
result[index++] = makeSME(syndrome(0), 0);
for (size_t i = 0; i != VECLEN; ++i)
{
auto v = (1 << i);
result[index++] = makeSME(syndrome(v), v);
}
for (size_t i = 0; i != VECLEN - 1; ++i)
{
for (size_t j = i + 1; j != VECLEN; ++j)
{
auto v = (1 << i) | (1 << j);
result[index++] = makeSME(syndrome(v), v);
}
}
for (size_t i = 0; i != VECLEN - 2; ++i)
{
for (size_t j = i + 1; j != VECLEN - 1; ++j)
{
for (size_t k = j + 1; k != VECLEN; ++k)
{
auto v = (1 << i) | (1 << j) | (1 << k);
result[index++] = makeSME(syndrome(v), v);
}
}
}
result = detail::sort(result);
std::array<SyndromeMapEntry, LUT_SIZE> tmp;
for (size_t i = 0; i != LUT_SIZE; ++i)
{
tmp[i] = makeSyndromeMapEntry(result[i]);
}
return tmp;
}
inline auto LUT = make_lut();
/**
* Calculate [23,12] Golay codeword.
*
* @return checkbits(11)|data(12).
*/
uint32_t encode23(uint16_t data)
{
// data &= 0xfff;
uint32_t codeword = data;
for (size_t i = 0; i != 12; ++i)
{
if (codeword & 1)
codeword ^= POLY;
codeword >>= 1;
}
return codeword | (data << 11);
}
uint32_t encode24(uint16_t data)
{
auto codeword = encode23(data);
return ((codeword << 1) | parity(codeword));
}
bool decode(uint32_t input, uint32_t& output)
{
auto syndrm = syndrome(input >> 1);
auto it = std::lower_bound(LUT.begin(), LUT.end(), syndrm,
[](const SyndromeMapEntry& sme, uint32_t val){
return (sme.a >> 8) < val;
});
if ((it->a >> 8) == syndrm)
{
// Build the correction from the compressed entry.
auto correction = ((((it->a & 0xFF) << 16) | it->b) << 1);
// Apply the correction to the input.
output = input ^ correction;
// Only test parity for 3-bit errors.
return popcount(syndrm) < 3 || !parity(output);
}
return false;
}
} // Golay24
} // mobilinkd

View File

@ -0,0 +1,112 @@
#include <lsf_decode.h>
#include <string.h>
#include <base40.h>
#include <stdio.h>
#include <inttypes.h>
#include <crc16.h>
#include <spdlog/spdlog.h>
bool M17CheckCRC(uint8_t* data, int len) {
// TODO: Implement
return true;
}
const char* M17DataTypesTxt[4] = {
"Unknown",
"Data",
"Voice",
"Voice & Data"
};
const char* M17EncryptionTypesTxt[4] = {
"None",
"AES",
"Scrambler",
"Unknown"
};
M17LSF M17DecodeLSF(uint8_t* _lsf) {
M17LSF lsf;
// Extract CRC
lsf.rawCRC = 0;
for (int i = 0; i < 16; i++) {
lsf.rawCRC |= (((uint16_t)_lsf[(i+48+48+16+112) / 8] >> (7 - (i%8))) & 1) << (15 - i);
}
// Check CRC
mobilinkd::CRC16 crc16;
crc16.reset();
for (int i = 0; i < 28; i++) {
crc16(_lsf[i]);
}
if (crc16.get() != lsf.rawCRC) {
lsf.valid = false;
return lsf;
}
lsf.valid = true;
// Extract DST
lsf.rawDst = 0;
for (int i = 0; i < 48; i++) {
lsf.rawDst |= (((uint64_t)_lsf[i / 8] >> (7 - (i%8))) & 1) << (47 - i);
}
// Extract SRC
lsf.rawSrc = 0;
for (int i = 0; i < 48; i++) {
lsf.rawSrc |= (((uint64_t)_lsf[(i+48) / 8] >> (7 - (i%8))) & 1) << (47 - i);
}
// Extract TYPE
lsf.rawType = 0;
for (int i = 0; i < 16; i++) {
lsf.rawType |= (((uint16_t)_lsf[(i+48+48) / 8] >> (7 - (i%8))) & 1) << (15 - i);
}
// Extract META
memcpy(lsf.meta, &_lsf[14], 14);
// Decode DST
if (lsf.rawDst == 0) {
lsf.dst = "Invalid";
}
else if (lsf.rawDst <= 262143999999999) {
char buf[128];
decode_callsign_base40(lsf.rawDst, buf);
lsf.dst = buf;
}
else if (lsf.rawDst == 0xFFFFFFFFFFFF) {
lsf.dst = "Broadcast";
}
else {
char buf[128];
sprintf(buf, "%" PRIX64, lsf.rawDst);
lsf.dst = buf;
}
// Decode SRC
if (lsf.rawSrc == 0 || lsf.rawSrc == 0xFFFFFFFFFFFF) {
lsf.src = "Invalid";
}
else if (lsf.rawSrc <= 262143999999999) {
char buf[128];
decode_callsign_base40(lsf.rawSrc, buf);
lsf.src = buf;
}
else {
char buf[128];
sprintf(buf, "%" PRIX64, lsf.rawSrc);
lsf.src = buf;
}
// Decode TYPE
lsf.isStream = (lsf.rawType >> 0) & 0b1;
lsf.dataType = (M17DataType)((lsf.rawType >> 1) & 0b11);
lsf.encryptionType = (M17EncryptionType)((lsf.rawType >> 3) & 0b11);
lsf.encryptionSubType = (lsf.rawType >> 5) & 0b11;
lsf.channelAccessNum = (lsf.rawType >> 7) & 0b1111;
return lsf;
}

View File

@ -0,0 +1,42 @@
#pragma once
#include <string>
#include <stdint.h>
enum M17DataType {
M17_DATATYPE_UNKNOWN = 0b00,
M17_DATATYPE_DATA = 0b01,
M17_DATATYPE_VOICE = 0b10,
M17_DATATYPE_DATA_VOICE = 0b11
};
enum M17EncryptionType {
M17_ENCRYPTION_NONE = 0b00,
M17_ENCRYPTION_AES = 0b01,
M17_ENCRYPTION_SCRAMBLE = 0b10,
M17_ENCRYPTION_UNKNOWN = 0b11
};
extern const char* M17DataTypesTxt[4];
extern const char* M17EncryptionTypesTxt[4];
struct M17LSF {
uint64_t rawDst;
uint64_t rawSrc;
uint16_t rawType;
uint8_t meta[14];
uint16_t rawCRC;
std::string dst;
std::string src;
bool isStream;
M17DataType dataType;
M17EncryptionType encryptionType;
uint8_t encryptionSubType;
uint8_t channelAccessNum;
bool valid;
};
bool M17CheckCRC(uint8_t* data, int len);
M17LSF M17DecodeLSF(uint8_t* _lsf);

View File

@ -0,0 +1,667 @@
#pragma once
#include <dsp/block.h>
#include <volk/volk.h>
#include <codec2.h>
#include <dsp/demodulator.h>
#include <golay24.h>
#include <lsf_decode.h>
extern "C" {
#include <correct.h>
}
#define M17_DEVIATION 2400.0f
#define M17_BAUDRATE 4800.0f
#define M17_RRC_ALPHA 0.5f
#define M17_4FSK_HIGH_CUT 0.5f
#define M17_SYNC_SIZE 16
#define M17_LICH_SIZE 96
#define M17_PAYLOAD_SIZE 144
#define M17_ENCODED_PAYLOAD_SIZE 296
#define M17_LSF_SIZE 240
#define M17_ENCODED_LSF_SIZE 488
#define M17_RAW_FRAME_SIZE 384
#define M17_CUT_FRAME_SIZE 368
const uint8_t M17_LSF_SYNC[16] = { 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1 };
const uint8_t M17_STF_SYNC[16] = { 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1 };
const uint8_t M17_PKF_SYNC[16] = { 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
const uint8_t M17_SCRAMBLER[368] = { 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1,
1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0,
1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0,
1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0,
1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1,
0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0,
0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1,
1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1,
1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0,
0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1,
0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0,
0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0,
1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0,
0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1,
1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0,
1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1,
1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1,
0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1 };
const uint16_t M17_INTERLEAVER[368] = { 0, 137, 90, 227, 180, 317, 270, 39, 360, 129, 82, 219, 172, 309, 262, 31,
352, 121, 74, 211, 164, 301, 254, 23, 344, 113, 66, 203, 156, 293, 246, 15,
336, 105, 58, 195, 148, 285, 238, 7, 328, 97, 50, 187, 140, 277, 230, 367,
320, 89, 42, 179, 132, 269, 222, 359, 312, 81, 34, 171, 124, 261, 214, 351,
304, 73, 26, 163, 116, 253, 206, 343, 296, 65, 18, 155, 108, 245, 198, 335,
288, 57, 10, 147, 100, 237, 190, 327, 280, 49, 2, 139, 92, 229, 182, 319,
272, 41, 362, 131, 84, 221, 174, 311, 264, 33, 354, 123, 76, 213, 166, 303,
256, 25, 346, 115, 68, 205, 158, 295, 248, 17, 338, 107, 60, 197, 150, 287,
240, 9, 330, 99, 52, 189, 142, 279, 232, 1, 322, 91, 44, 181, 134, 271,
224, 361, 314, 83, 36, 173, 126, 263, 216, 353, 306, 75, 28, 165, 118, 255,
208, 345, 298, 67, 20, 157, 110, 247, 200, 337, 290, 59, 12, 149, 102, 239,
192, 329, 282, 51, 4, 141, 94, 231, 184, 321, 274, 43, 364, 133, 86, 223,
176, 313, 266, 35, 356, 125, 78, 215, 168, 305, 258, 27, 348, 117, 70, 207,
160, 297, 250, 19, 340, 109, 62, 199, 152, 289, 242, 11, 332, 101, 54, 191,
144, 281, 234, 3, 324, 93, 46, 183, 136, 273, 226, 363, 316, 85, 38, 175,
128, 265, 218, 355, 308, 77, 30, 167, 120, 257, 210, 347, 300, 69, 22, 159,
112, 249, 202, 339, 292, 61, 14, 151, 104, 241, 194, 331, 284, 53, 6, 143,
96, 233, 186, 323, 276, 45, 366, 135, 88, 225, 178, 315, 268, 37, 358, 127,
80, 217, 170, 307, 260, 29, 350, 119, 72, 209, 162, 299, 252, 21, 342, 111,
64, 201, 154, 291, 244, 13, 334, 103, 56, 193, 146, 283, 236, 5, 326, 95,
48, 185, 138, 275, 228, 365, 318, 87, 40, 177, 130, 267, 220, 357, 310, 79,
32, 169, 122, 259, 212, 349, 302, 71, 24, 161, 114, 251, 204, 341, 294, 63,
16, 153, 106, 243, 196, 333, 286, 55, 8, 145, 98, 235, 188, 325, 278, 47 };
const uint8_t M17_PUNCTURING_P1[61] = { 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1 };
const uint8_t M17_PUNCTURING_P2[12] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 };
static const correct_convolutional_polynomial_t correct_conv_m17_polynomial[] = {0b11001, 0b10111};
namespace dsp {
class M17Slice4FSK : public generic_block<M17Slice4FSK> {
public:
M17Slice4FSK() {}
M17Slice4FSK(stream<float>* in) { init(in); }
void init(stream<float>* in) {
_in = in;
generic_block<M17Slice4FSK>::registerInput(_in);
generic_block<M17Slice4FSK>::registerOutput(&out);
generic_block<M17Slice4FSK>::_block_init = true;
}
void setInput(stream<float>* in) {
assert(generic_block<M17Slice4FSK>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<M17Slice4FSK>::ctrlMtx);
generic_block<M17Slice4FSK>::tempStop();
generic_block<M17Slice4FSK>::unregisterInput(_in);
_in = in;
generic_block<M17Slice4FSK>::registerInput(_in);
generic_block<M17Slice4FSK>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
float val;
for (int i = 0; i < count; i++) {
val = _in->readBuf[i];
out.writeBuf[i*2] = (val < 0.0f);
out.writeBuf[(i*2)+1] = (fabsf(val) > M17_4FSK_HIGH_CUT);
}
_in->flush();
if (!out.swap(count * 2)) { return -1; }
return count;
}
stream<uint8_t> out;
private:
stream<float>* _in;
};
class M17FrameDemux : public generic_block<M17FrameDemux> {
public:
M17FrameDemux() {}
M17FrameDemux(stream<uint8_t>* in) { init(in); }
~M17FrameDemux() {
if (!generic_block<M17FrameDemux>::_block_init) { return; }
generic_block<M17FrameDemux>::stop();
delete[] delay;
}
void init(stream<uint8_t>* in) {
_in = in;
delay = new uint8_t[STREAM_BUFFER_SIZE];
generic_block<M17FrameDemux>::registerInput(_in);
generic_block<M17FrameDemux>::registerOutput(&linkSetupOut);
generic_block<M17FrameDemux>::registerOutput(&lichOut);
generic_block<M17FrameDemux>::registerOutput(&streamOut);
generic_block<M17FrameDemux>::registerOutput(&packetOut);
generic_block<M17FrameDemux>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<M17FrameDemux>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<M17FrameDemux>::ctrlMtx);
generic_block<M17FrameDemux>::tempStop();
generic_block<M17FrameDemux>::unregisterInput(_in);
_in = in;
generic_block<M17FrameDemux>::registerInput(_in);
generic_block<M17FrameDemux>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
memcpy(&delay[M17_SYNC_SIZE], _in->readBuf, count);
for (int i = 0; i < count;) {
if (detect) {
if (outCount < M17_SYNC_SIZE) { outCount++; i++; }
else {
int id = M17_INTERLEAVER[outCount - M17_SYNC_SIZE];
if (type == 0) {
linkSetupOut.writeBuf[id] = delay[i++] ^ M17_SCRAMBLER[outCount - M17_SYNC_SIZE];
}
else if ((type == 1 || type == 2) && id < M17_LICH_SIZE) {
lichOut.writeBuf[id] = delay[i++] ^ M17_SCRAMBLER[outCount - M17_SYNC_SIZE];
}
else if (type == 1) {
streamOut.writeBuf[id - M17_LICH_SIZE] = (delay[i++] ^ M17_SCRAMBLER[outCount - M17_SYNC_SIZE]);
}
else if (type == 2) {
packetOut.writeBuf[id - M17_LICH_SIZE] = (delay[i++] ^ M17_SCRAMBLER[outCount - M17_SYNC_SIZE]);
}
outCount++;
}
if (outCount >= M17_RAW_FRAME_SIZE) {
detect = false;
if (type == 0) {
if (!linkSetupOut.swap(M17_CUT_FRAME_SIZE)) { return -1; }
}
else if (type == 1) {
if (!lichOut.swap(M17_LICH_SIZE)) {return -1; }
if (!streamOut.swap(M17_CUT_FRAME_SIZE)) {return -1; }
}
else if (type == 2) {
if (!lichOut.swap(M17_LICH_SIZE)) {return -1; }
if (!packetOut.swap(M17_CUT_FRAME_SIZE)) { return -1; }
}
}
continue;
}
// Check for link setup syncword
if (!memcmp(&delay[i], M17_LSF_SYNC, M17_SYNC_SIZE)) {
detect = true;
outCount = 0;
type = 0;
//spdlog::warn("Found sync frame");
continue;
}
// Check for stream syncword
if (!memcmp(&delay[i], M17_STF_SYNC, M17_SYNC_SIZE)) {
detect = true;
outCount = 0;
type = 1;
//spdlog::warn("Found stream frame");
continue;
}
// Check for packet syncword
if (!memcmp(&delay[i], M17_PKF_SYNC, M17_SYNC_SIZE)) {
detect = true;
outCount = 0;
type = 2;
//spdlog::warn("Found packet frame");
continue;
}
i++;
}
memmove(delay, &delay[count], 16);
_in->flush();
return count;
}
stream<uint8_t> linkSetupOut;
stream<uint8_t> lichOut;
stream<uint8_t> streamOut;
stream<uint8_t> packetOut;
private:
stream<uint8_t>* _in;
uint8_t* delay;
bool detect = false;
int type;
int outCount = 0;
};
class M17LSFDecoder : public generic_block<M17LSFDecoder> {
public:
M17LSFDecoder() {}
M17LSFDecoder(stream<uint8_t>* in, void (*handler)(M17LSF& lsf, void* ctx), void* ctx) { init(in, handler, ctx); }
~M17LSFDecoder() {
if (!generic_block<M17LSFDecoder>::_block_init) { return; }
generic_block<M17LSFDecoder>::stop();
correct_convolutional_destroy(conv);
}
void init(stream<uint8_t>* in, void (*handler)(M17LSF& lsf, void* ctx), void* ctx) {
_in = in;
_handler = handler;
_ctx = ctx;
conv = correct_convolutional_create(2, 5, correct_conv_m17_polynomial);
generic_block<M17LSFDecoder>::registerInput(_in);
generic_block<M17LSFDecoder>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<M17LSFDecoder>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<M17LSFDecoder>::ctrlMtx);
generic_block<M17LSFDecoder>::tempStop();
generic_block<M17LSFDecoder>::unregisterInput(_in);
_in = in;
generic_block<M17LSFDecoder>::registerInput(_in);
generic_block<M17LSFDecoder>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
// Depuncture the data
int inOffset = 0;
for (int i = 0; i < M17_ENCODED_LSF_SIZE; i++) {
if (!M17_PUNCTURING_P1[i % 61]) {
depunctured[i] = 0;
continue;
}
depunctured[i] = _in->readBuf[inOffset++];
}
_in->flush();
// Pack into bytes
memset(packed, 0, 61);
for (int i = 0; i < M17_ENCODED_LSF_SIZE; i++) {
packed[i/8] |= depunctured[i] << (7 - (i%8));
}
// Run through convolutional decoder
correct_convolutional_decode(conv, packed, M17_ENCODED_LSF_SIZE, lsf);
// Decode it and call the handler
M17LSF decLsf = M17DecodeLSF(lsf);
if (decLsf.valid) { _handler(decLsf, _ctx); }
return count;
}
private:
stream<uint8_t>* _in;
void (*_handler)(M17LSF& lsf, void* ctx);
void* _ctx;
uint8_t depunctured[488];
uint8_t packed[61];
uint8_t lsf[30];
correct_convolutional* conv;
};
class M17PayloadFEC : public generic_block<M17PayloadFEC> {
public:
M17PayloadFEC() {}
M17PayloadFEC(stream<uint8_t>* in) { init(in); }
~M17PayloadFEC() {
if (!generic_block<M17PayloadFEC>::_block_init) { return; }
generic_block<M17PayloadFEC>::stop();
correct_convolutional_destroy(conv);
}
void init(stream<uint8_t>* in) {
_in = in;
conv = correct_convolutional_create(2, 5, correct_conv_m17_polynomial);
generic_block<M17PayloadFEC>::registerInput(_in);
generic_block<M17PayloadFEC>::registerOutput(&out);
generic_block<M17PayloadFEC>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<M17PayloadFEC>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<M17PayloadFEC>::ctrlMtx);
generic_block<M17PayloadFEC>::tempStop();
generic_block<M17PayloadFEC>::unregisterInput(_in);
_in = in;
generic_block<M17PayloadFEC>::registerInput(_in);
generic_block<M17PayloadFEC>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
// Depuncture the data
int inOffset = 0;
for (int i = 0; i < M17_ENCODED_PAYLOAD_SIZE; i++) {
if (!M17_PUNCTURING_P2[i % 12]) {
depunctured[i] = 0;
continue;
}
depunctured[i] = _in->readBuf[inOffset++];
}
// Pack into bytes
memset(packed, 0, 37);
for (int i = 0; i < M17_ENCODED_PAYLOAD_SIZE; i++) {
if (!(i%8)) { packed[i/8] = 0; }
packed[i/8] |= depunctured[i] << (7 - (i%8));
}
// Run through convolutional decoder
correct_convolutional_decode(conv, packed, M17_ENCODED_PAYLOAD_SIZE, out.writeBuf);
_in->flush();
if (!out.swap(M17_PAYLOAD_SIZE / 8)) { return -1; }
return count;
}
stream<uint8_t> out;
private:
stream<uint8_t>* _in;
uint8_t depunctured[296];
uint8_t packed[37];
correct_convolutional* conv;
};
class M17Codec2Decode : public generic_block<M17Codec2Decode> {
public:
M17Codec2Decode() {}
M17Codec2Decode(stream<uint8_t>* in) { init(in); }
~M17Codec2Decode() {
if (!generic_block<M17Codec2Decode>::_block_init) { return; }
generic_block<M17Codec2Decode>::stop();
codec2_destroy(codec);
delete[] int16Audio;
delete[] floatAudio;
}
void init(stream<uint8_t>* in) {
_in = in;
codec = codec2_create(CODEC2_MODE_3200);
sampsPerC2Frame = codec2_samples_per_frame(codec);
sampsPerC2FrameDouble = sampsPerC2Frame * 2;
int16Audio = new int16_t[sampsPerC2FrameDouble];
floatAudio = new float[sampsPerC2FrameDouble];
generic_block<M17Codec2Decode>::registerInput(_in);
generic_block<M17Codec2Decode>::registerOutput(&out);
generic_block<M17Codec2Decode>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<M17Codec2Decode>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<M17Codec2Decode>::ctrlMtx);
generic_block<M17Codec2Decode>::tempStop();
generic_block<M17Codec2Decode>::unregisterInput(_in);
_in = in;
generic_block<M17Codec2Decode>::registerInput(_in);
generic_block<M17Codec2Decode>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
// Decode both parts using codec
codec2_decode(codec, int16Audio, &_in->readBuf[2]);
codec2_decode(codec, &int16Audio[sampsPerC2Frame], &_in->readBuf[2+8]);
// Convert to float
volk_16i_s32f_convert_32f(floatAudio, int16Audio, 32768.0f, sampsPerC2FrameDouble);
// Interleave into stereo samples
volk_32f_x2_interleave_32fc((lv_32fc_t*)out.writeBuf, floatAudio, floatAudio, sampsPerC2FrameDouble);
_in->flush();
if (!out.swap(sampsPerC2FrameDouble)) { return -1; }
return count;
}
stream<stereo_t> out;
private:
stream<uint8_t>* _in;
int16_t* int16Audio;
float* floatAudio;
CODEC2* codec;
int sampsPerC2Frame = 0;
int sampsPerC2FrameDouble = 0;
};
class M17LICHDecoder : public generic_block<M17LICHDecoder> {
public:
M17LICHDecoder() {}
M17LICHDecoder(stream<uint8_t>* in, void (*handler)(M17LSF& lsf, void* ctx), void* ctx) { init(in, handler, ctx); }
void init(stream<uint8_t>* in, void (*handler)(M17LSF& lsf, void* ctx), void* ctx) {
_in = in;
_handler = handler;
_ctx = ctx;
generic_block<M17LICHDecoder>::registerInput(_in);
generic_block<M17LICHDecoder>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<M17LICHDecoder>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<M17LICHDecoder>::ctrlMtx);
generic_block<M17LICHDecoder>::tempStop();
generic_block<M17LICHDecoder>::unregisterInput(_in);
_in = in;
generic_block<M17LICHDecoder>::registerInput(_in);
generic_block<M17LICHDecoder>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
// Zero out block
memset(chunk, 0, 6);
// Decode the 4 Golay(24, 12) blocks
uint32_t encodedBlock;
uint32_t decodedBlock;
for (int b = 0; b < 4; b++) {
// Pack the 24bit block into a byte
encodedBlock = 0;
decodedBlock = 0;
for (int i = 0; i < 24; i++) { encodedBlock |= _in->readBuf[(b * 24) + i] << (23 - i); }
// Decode
if (!mobilinkd::Golay24::decode(encodedBlock, decodedBlock)) {
_in->flush();
return count;
}
// Pack the decoded bits into the output
int id = 0;
uint8_t temp;
for (int i = 0; i < 12; i++) {
id = (b * 12) + i;
chunk[id / 8] |= ((decodedBlock >> (23 - i)) & 1) << (7 - (id%8));
}
}
_in->flush();
int partId = chunk[5] >> 5;
// If the ID of the chunk is zero, start a new LSF
if (partId == 0) {
newFrame = true;
lastId = 0;
memcpy(&lsf[partId*5], chunk, 5);
return count;
}
// If we're recording a LSF and a discontinuity shows up, cancel
if (newFrame && partId != lastId + 1) {
newFrame = false;
return count;
}
// If we're recording and there's no discontinuity (see above), add the data to the full frame
if (newFrame) {
lastId = partId;
memcpy(&lsf[partId*5], chunk, 5);
// If the lsf is complete, send it out
if (partId == 5) {
newFrame = false;
M17LSF decLsf = M17DecodeLSF(lsf);
if (decLsf.valid) { _handler(decLsf, _ctx); }
}
}
return count;
}
private:
stream<uint8_t>* _in;
void (*_handler)(M17LSF& lsf, void* ctx);
void* _ctx;
uint8_t chunk[6];
uint8_t lsf[240];
bool newFrame = false;
int lastId = 0;
};
class M17Decoder : public generic_hier_block<M17Decoder> {
public:
M17Decoder() {}
M17Decoder(stream<complex_t>* input, float sampleRate, void (*handler)(M17LSF& lsf, void* ctx), void* ctx) {
init(input, sampleRate, handler, ctx);
}
void init(stream<complex_t>* input, float sampleRate, void (*handler)(M17LSF& lsf, void* ctx), void* ctx) {
_sampleRate = sampleRate;
demod.init(input, _sampleRate, M17_DEVIATION);
rrc.init(31, _sampleRate, M17_BAUDRATE, M17_RRC_ALPHA);
fir.init(&demod.out, &rrc);
recov.init(&fir.out, _sampleRate / M17_BAUDRATE, 1e-6f, 0.01f, 0.01f);
doubler.init(&recov.out);
slice.init(&doubler.outA);
demux.init(&slice.out);
lsfFEC.init(&demux.linkSetupOut, handler, ctx);
payloadFEC.init(&demux.streamOut);
decodeLICH.init(&demux.lichOut, handler, ctx);
decodeAudio.init(&payloadFEC.out);
ns2.init(&demux.packetOut);
diagOut = &doubler.outB;
out = &decodeAudio.out;
generic_hier_block<M17Decoder>::registerBlock(&demod);
generic_hier_block<M17Decoder>::registerBlock(&fir);
generic_hier_block<M17Decoder>::registerBlock(&recov);
generic_hier_block<M17Decoder>::registerBlock(&doubler);
generic_hier_block<M17Decoder>::registerBlock(&slice);
generic_hier_block<M17Decoder>::registerBlock(&demux);
generic_hier_block<M17Decoder>::registerBlock(&lsfFEC);
generic_hier_block<M17Decoder>::registerBlock(&payloadFEC);
generic_hier_block<M17Decoder>::registerBlock(&decodeLICH);
generic_hier_block<M17Decoder>::registerBlock(&decodeAudio);
generic_hier_block<M17Decoder>::registerBlock(&ns2);
generic_hier_block<M17Decoder>::_block_init = true;
}
void setInput(stream<complex_t>* input) {
assert(generic_hier_block<M17Decoder>::_block_init);
demod.setInput(input);
}
stream<float>* diagOut = NULL;
stream<stereo_t>* out = NULL;
private:
FloatFMDemod demod;
RRCTaps rrc;
FIR<float> fir;
MMClockRecovery<float> recov;
StreamDoubler<float> doubler;
M17Slice4FSK slice;
M17FrameDemux demux;
M17LSFDecoder lsfFEC;
M17PayloadFEC payloadFEC;
M17LICHDecoder decodeLICH;
M17Codec2Decode decodeAudio;
NullSink<uint8_t> ns2;
float _sampleRate;
};
}

View File

@ -0,0 +1,315 @@
#include <imgui.h>
#include <config.h>
#include <core.h>
#include <gui/style.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <module.h>
#include <options.h>
#include <filesystem>
#include <dsp/pll.h>
#include <dsp/stream.h>
#include <dsp/demodulator.h>
#include <dsp/window.h>
#include <dsp/resampling.h>
#include <dsp/processing.h>
#include <dsp/routing.h>
#include <dsp/sink.h>
#include <gui/widgets/folder_select.h>
#include <gui/widgets/symbol_diagram.h>
#include <m17dsp.h>
#include <fstream>
#include <chrono>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO {
/* Name: */ "m17_decoder",
/* Description: */ "M17 Digital Voice Decoder for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
ConfigManager config;
#define INPUT_SAMPLE_RATE 14400
class M17DecoderModule : public ModuleManager::Instance {
public:
M17DecoderModule(std::string name) : diag(0.8, 480) {
this->name = name;
lsf.valid = false;
// Load config
config.acquire();
if (!config.conf.contains(name)) {
config.conf[name]["showLines"] = false;
}
showLines = config.conf[name]["showLines"];
if (showLines) {
diag.lines.push_back(-0.75f);
diag.lines.push_back(-0.25f);
diag.lines.push_back(0.25f);
diag.lines.push_back(0.75f);
}
config.release(true);
// Initialize VFO
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 9600, INPUT_SAMPLE_RATE, 9600, 9600, true);
vfo->setSnapInterval(250);
// Initialize DSP here
decoder.init(vfo->output, INPUT_SAMPLE_RATE, lsfHandler, this);
resampWin.init(4000, 4000, audioSampRate);
resamp.init(decoder.out, &resampWin, 8000, audioSampRate);
resampWin.setSampleRate(8000 * resamp.getInterpolation());
resamp.updateWindow(&resampWin);
reshape.init(decoder.diagOut, 480, 0);
diagHandler.init(&reshape.out, _diagHandler, this);
// Start DSO Here
decoder.start();
resamp.start();
reshape.start();
diagHandler.start();
// Setup audio stream
srChangeHandler.ctx = this;
srChangeHandler.handler = sampleRateChangeHandler;
stream.init(&resamp.out, &srChangeHandler, audioSampRate);
sigpath::sinkManager.registerStream(name, &stream);
stream.start();
gui::menu.registerEntry(name, menuHandler, this, this);
}
~M17DecoderModule() {
gui::menu.removeEntry(name);
// Stop DSP Here
stream.stop();
if (enabled) {
decoder.stop();
resamp.stop();
reshape.stop();
diagHandler.stop();
sigpath::vfoManager.deleteVFO(vfo);
}
sigpath::sinkManager.unregisterStream(name);
}
void postInit() {}
void enable() {
double bw = gui::waterfall.getBandwidth();
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw/2.0, bw/2.0), 9600, INPUT_SAMPLE_RATE, 9600, 9600, true);
vfo->setSnapInterval(250);
// Set Input of demod here
decoder.setInput(vfo->output);
// Start DSP here
decoder.start();
resamp.start();
reshape.start();
diagHandler.start();
enabled = true;
}
void disable() {
// Stop DSP here
decoder.stop();
resamp.stop();
reshape.stop();
diagHandler.stop();
sigpath::vfoManager.deleteVFO(vfo);
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
static void menuHandler(void* ctx) {
M17DecoderModule* _this = (M17DecoderModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvailWidth();
if (!_this->enabled) { style::beginDisabled(); }
ImGui::SetNextItemWidth(menuWidth);
_this->diag.draw();
{
std::lock_guard lck(_this->lsfMtx);
auto now = std::chrono::high_resolution_clock::now();
if (std::chrono::duration_cast<std::chrono::milliseconds>(now-_this->lastUpdated).count() > 1000) {
_this->lsf.valid = false;
}
ImGui::BeginTable(CONCAT("##m17_info_tbl_", _this->name), 2, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders);
if (!_this->lsf.valid) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Source");
ImGui::TableSetColumnIndex(1);
ImGui::Text("--");
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Destination");
ImGui::TableSetColumnIndex(1);
ImGui::Text("--");
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Data Type");
ImGui::TableSetColumnIndex(1);
ImGui::Text("--");
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Encryption");
ImGui::TableSetColumnIndex(1);
ImGui::Text("-- (Subtype --)");
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("CAN");
ImGui::TableSetColumnIndex(1);
ImGui::Text("--");
}
else {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Source");
ImGui::TableSetColumnIndex(1);
ImGui::Text(_this->lsf.src.c_str());
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Destination");
ImGui::TableSetColumnIndex(1);
ImGui::Text(_this->lsf.dst.c_str());
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Data Type");
ImGui::TableSetColumnIndex(1);
ImGui::Text(M17DataTypesTxt[_this->lsf.dataType]);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Encryption");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%s (Subtype %d)", M17EncryptionTypesTxt[_this->lsf.encryptionType], _this->lsf.encryptionSubType);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("CAN");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%d", _this->lsf.channelAccessNum);
}
ImGui::EndTable();
}
if (ImGui::Checkbox(CONCAT("Show Reference TEST Lines##m17_showlines_", _this->name), &_this->showLines)) {
if (_this->showLines) {
_this->diag.lines.push_back(-0.75f);
_this->diag.lines.push_back(-0.25f);
_this->diag.lines.push_back(0.25f);
_this->diag.lines.push_back(0.75f);
}
else {
_this->diag.lines.clear();
}
config.acquire();
config.conf[_this->name]["showLines"] = _this->showLines;
config.release(true);
}
if (!_this->enabled) { style::endDisabled(); }
}
static void _diagHandler(float* data, int count, void* ctx) {
M17DecoderModule* _this = (M17DecoderModule*)ctx;
float* buf = _this->diag.acquireBuffer();
memcpy(buf, data, count * sizeof(float));
_this->diag.releaseBuffer();
}
static void sampleRateChangeHandler(float sampleRate, void* ctx) {
M17DecoderModule* _this = (M17DecoderModule*)ctx;
// TODO: If too slow, change all demods here and not when setting
_this->audioSampRate = sampleRate;
_this->resampWin.setCutoff(std::min<float>(sampleRate/2, 4000));
_this->resamp.tempStop();
_this->resamp.setOutSampleRate(sampleRate);
_this->resampWin.setSampleRate(8000 * _this->resamp.getInterpolation());
_this->resamp.updateWindow(&_this->resampWin);
_this->resamp.tempStart();
}
static void lsfHandler(M17LSF& lsf, void* ctx) {
M17DecoderModule* _this = (M17DecoderModule*)ctx;
std::lock_guard lck(_this->lsfMtx);
_this->lastUpdated = std::chrono::high_resolution_clock::now();
_this->lsf = lsf;
}
std::string name;
bool enabled = true;
// DSP Chain
VFOManager::VFO* vfo;
dsp::M17Decoder decoder;
dsp::Reshaper<float> reshape;
dsp::HandlerSink<float> diagHandler;
dsp::filter_window::BlackmanWindow resampWin;
dsp::PolyphaseResampler<dsp::stereo_t> resamp;
ImGui::SymbolDiagram diag;
double audioSampRate = 48000;
EventHandler<float> srChangeHandler;
SinkManager::Stream stream;
bool showLines = false;
M17LSF lsf;
std::mutex lsfMtx;
std::chrono::time_point<std::chrono::high_resolution_clock> lastUpdated;
};
MOD_EXPORT void _INIT_() {
// Create default recording directory
json def = json({});
config.setPath(options::opts.root + "/m17_decoder_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new M17DecoderModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (M17DecoderModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}