op25/op25/gr-op25_repeater/lib/dmr_bs_tx_bb_impl.cc

474 lines
13 KiB
C++

/* -*- c++ -*- */
/*
* DMR Encoder (C) Copyright 2017 Max H. Parke KA1RBI
*
* This file is part of OP25
*
* This 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, or (at your option)
* any later version.
*
* This software 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 software; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gnuradio/io_signature.h>
#include "dmr_bs_tx_bb_impl.h"
#include "dmr_const.h"
#include <vector>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#if 0
static void print_result(char title[], const uint8_t r[], int len) {
uint8_t buf[256];
for (int i=0; i<len; i++){
buf[i] = r[i] + '0';
}
buf[len] = 0;
printf("%s: %s\n", title, buf);
}
#endif
static uint8_t crc8(const uint8_t bits[], unsigned int len) {
uint8_t crc=0;
static const unsigned int K = 8;
static const uint8_t poly[K+1] = {1,0,0,0,0,0,1,1,1}; // crc8 poly
uint8_t buf[256];
if (len+K > sizeof(buf)) {
fprintf (stderr, "crc8: buffer length %u exceeds maximum %lu\n", len+K, sizeof(buf));
return 0;
}
memset (buf, 0, sizeof(buf));
for (int i=0; i<len; i++){
buf[i] = bits[i];
}
for (int i=0; i<len; i++)
if (buf[i])
for (int j=0; j<K+1; j++)
buf[i+j] ^= poly[j];
for (int i=0; i<K; i++){
crc = (crc << 1) + buf[len + i];
}
return crc;
}
static bool crc8_ok(const uint8_t bits[], unsigned int len) {
uint16_t crc = 0;
for (int i=0; i < 8; i++) {
crc = (crc << 1) + bits[len+i];
}
return (crc == crc8(bits,len));
}
static inline void store_i(int reg, uint8_t val[], int len) {
for (int i=0; i<len; i++){
val[i] = (reg >> (len-1-i)) & 1;
}
}
static void bits_to_dibits(uint8_t* dest, const uint8_t* src, int n_dibits) {
for (int i=0; i<n_dibits; i++) {
dest[i] = src[i*2] * 2 + src[i*2+1];
}
}
static inline int load_i(const uint8_t val[], int len) {
int acc = 0;
for (int i=0; i<len; i++){
acc = (acc << 1) + (val[i] & 1);
}
return acc;
}
static void encode_emb(int cc, int pi, int lcss, uint8_t result[16]) {
int acc = (cc << 3) | (pi << 2) | lcss;
acc = hamming_16_7[acc];
for (int i=0; i<16; i++) {
result[i] = (acc >> (15-i)) & 1;
}
}
static void encode_embedded(const uint8_t lc[72], uint8_t result[32*4]) {
uint8_t encode[16*8];
static const int lengths[] = {11, 11, 10, 10, 10, 10, 10};
int s_index = 0;
uint16_t csum = 0;
for (int i=0; i<9; i++) {
csum += load_i(&lc[i*8], 8);
}
csum = csum % 31;
for (int i=0; i<7; i++) {
memcpy(&encode[16*i], &lc[s_index], lengths[i]);
s_index += lengths[i];
}
for (int i=0; i<5; i++) {
encode[(i+2)*16+10] = (csum >> (4-i)) & 1;
}
for (int i=0; i<7; i++) {
int acc = load_i(&encode[16*i], 11);
acc = hamming_16_11[acc];
for (int j=0; j<5; j++){
encode[i*16+j+11] = (acc >> (4-j)) & 1;
}
}
for (int i=0; i<16; i++) {
encode[7*16+i] = (encode[0*16+i] + encode[1*16+i] + encode[2*16+i] + \
encode[3*16+i] + encode[4*16+i] + encode[5*16+i] + \
encode[6*16+i]) & 1;
}
int resultp = 0;
for (int i=0; i<16; i++) {
for (int j=0; j<8; j++){
result[resultp++] = encode[i+16*j];
}
}
}
static void encode_shortlc(const uint8_t lc[28], uint8_t result[17*4]) {
uint8_t buffer[36];
uint8_t encode[17*4];
memcpy(buffer, lc, 28);
uint8_t crc = crc8(buffer, 28);
for (int i=0; i<8; i++) {
buffer[28+i] = (crc >> (7-i)) & 1;
}
//print_result("buffer", buffer, 36);
for (int i=0; i<3; i++) {
//print_result("buffer-d", &buffer[i*12], 12);
int acc = load_i(&buffer[i*12], 12);
acc = hamming_17_12[acc];
memcpy(&encode[i*17], &buffer[i*12], 12);
for (int j=0; j<5; j++){
encode[i*17+j+12] = (acc >> (4-j)) & 1;
}
}
//print_result("encode-0", &encode[0*17], 17);
//print_result("encode-1", &encode[1*17], 17);
//print_result("encode-2", &encode[2*17], 17);
for (int i=0; i<17; i++) {
encode[3*17+i] = (encode[0*17+i] + encode[1*17+i] + encode[2*17+i]) & 1;
}
//print_result("encode-3", &encode[3*17], 17);
int resultp = 0;
for (int i=0; i<17; i++) {
for (int j=0; j<4; j++){
result[resultp++] = encode[i+17*j];
}
}
}
static void generate_cach(uint8_t at, uint8_t tc, uint8_t lcss, const uint8_t cach_bits[17], uint8_t result[24]) {
int tact = hamming_7_4[ (at << 3) | (tc << 2) | lcss ];
//printf ("tact %d %x\n", tact, tact);
//print_result("cach_payload_bits", cach_bits, 17);
for (int i=0; i<7; i++) {
result[cach_tact_bits[i]] = (tact >> (6-i)) & 1;
}
for (int i=0; i<17; i++) {
result[cach_payload_bits[i]] = cach_bits[i];
}
}
namespace gr {
namespace op25_repeater {
dmr_bs_tx_bb::sptr
dmr_bs_tx_bb::make(int verbose_flag, const char * config_file)
{
return gnuradio::get_initial_sptr
(new dmr_bs_tx_bb_impl(verbose_flag, config_file));
}
//////////////////////////////////////////////////////////////////////////
// accepts ambe codewords (36 dibits, char format) on two input channels
// input channel 0 --> DMR slot 1
// input channel 1 --> DMR slot 2
// outputs dibits (char format) in range 0-3
// symbols are output in bursts of 144 dibits (288 bits) (1 burst per 30 msec.):
// CACH (24)
// voice (108)
// SYNC or embedded signalling (48)
// voice (108)
// reads three ambe codewords (216 bits = 108 dibits) for each output burst
// ref. sec. 5.1.5.1
// output dibit map(144 dibits):
// 00 - 11 CACH(12)
// 12 - 47 VC1(36)
// 48 - 65 VC2 part1(18)
// 66 - 89 sync(24)
// 90 -107 VC2 part2(18)
// 108-143 VC3 (36)
static const int MIN_IN = 2;
static const int MAX_IN = 2;
static const int MIN_OUT = 1;
static const int MAX_OUT = 1;
/*
* The private constructor
*/
dmr_bs_tx_bb_impl::dmr_bs_tx_bb_impl(int verbose_flag, const char * config_file)
: gr::block("dmr_bs_tx_bb",
gr::io_signature::make (MIN_IN, MAX_IN, 36),
gr::io_signature::make (MIN_OUT, MAX_OUT, sizeof(char))),
d_verbose_flag(verbose_flag),
d_config_file(config_file),
d_next_burst(0),
d_next_slot(0)
{
d_ts[0] = 0;
d_ts[1] = 0;
d_cc[0] = 0;
d_cc[1] = 0;
d_so[0] = 0;
d_so[1] = 0;
d_ga[0] = 0;
d_ga[1] = 0;
d_sa[0] = 0;
d_sa[1] = 0;
d_en[0] = 0;
d_en[1] = 0;
set_output_multiple(144);
set_history(3);
config();
}
/*
* Our virtual destructor.
*/
dmr_bs_tx_bb_impl::~dmr_bs_tx_bb_impl()
{
}
void
dmr_bs_tx_bb_impl::config()
{
FILE * fp1 = fopen(d_config_file, "r");
char line[256];
char * cp;
if (!fp1) {
fprintf(stderr, "dmr_bs_tx_bb_impl:config: failed to open %s\n", d_config_file);
return;
}
for (;;) {
cp = fgets(line, sizeof(line) - 2, fp1);
if (!cp) break;
if (line[0] == '#') continue;
// cach: hashed addr ts1(8) and hashed addr ts2(8)
// emb: (one per ch) cc(4)
// group voice channel user PDU: service options (8), group address(24), source address(24)
// (one per ch)
if (memcmp(line, "ts1=", 4) == 0)
sscanf(&line[4], "%d", &d_ts[0]);
else if (memcmp(line, "ts2=", 4) == 0)
sscanf(&line[4], "%d", &d_ts[1]);
else if (memcmp(line, "cc1=", 4) == 0)
sscanf(&line[4], "%d", &d_cc[0]);
else if (memcmp(line, "cc2=", 4) == 0)
sscanf(&line[4], "%d", &d_cc[1]);
else if (memcmp(line, "so1=", 4) == 0)
sscanf(&line[4], "%d", &d_so[0]);
else if (memcmp(line, "so2=", 4) == 0)
sscanf(&line[4], "%d", &d_so[1]);
else if (memcmp(line, "ga1=", 4) == 0)
sscanf(&line[4], "%d", &d_ga[0]);
else if (memcmp(line, "ga2=", 4) == 0)
sscanf(&line[4], "%d", &d_ga[1]);
else if (memcmp(line, "sa1=", 4) == 0)
sscanf(&line[4], "%d", &d_sa[0]);
else if (memcmp(line, "sa2=", 4) == 0)
sscanf(&line[4], "%d", &d_sa[1]);
else if (memcmp(line, "en1=", 4) == 0)
sscanf(&line[4], "%d", &d_en[0]);
else if (memcmp(line, "en2=", 4) == 0)
sscanf(&line[4], "%d", &d_en[1]);
}
fclose(fp1);
d_ts[0] &= 0xff;
d_ts[1] &= 0xff;
d_cc[0] &= 0xf;
d_cc[1] &= 0xf;
d_so[0] &= 0xff;
d_so[1] &= 0xff;
d_ga[0] &= 0xffffff;
d_ga[1] &= 0xffffff;
d_sa[0] &= 0xffffff;
d_sa[1] &= 0xffffff;
d_en[0] &= 0x1;
d_en[1] &= 0x1;
#if 0
fprintf(stderr, "dmr_bs_tx_bb_impl:config: ts[0] %d ts[1] %d cc[0] %d cc[1] %d\n", d_ts[0], d_ts[1], d_cc[0], d_cc[1]);
fprintf(stderr, "dmr_bs_tx_bb_impl:config: so[0] %d so[1] %d ga[0] %d ga[1] %d\n", d_so[0], d_so[1], d_ga[0], d_ga[1]);
fprintf(stderr, "dmr_bs_tx_bb_impl:config: sa[0] %d sa[1] %d\n", d_sa[0], d_sa[1]);
#endif
// TODO: allow cach and sync data to be reconfigured at runtime
static uint8_t lc[28]={
// activity update vol 2 #7.1.3.2
0,0,0,1, // opcode
1,0,0,0, // group voice on slot 1
1,0,0,0, // group voice on slot 2
0,0,0,0,0,0,0,0, // ts[0]
0,0,0,0,0,0,0,0}; // ts[1]
store_i(d_ts[0], &lc[12], 8);
store_i(d_ts[1], &lc[20], 8);
uint8_t shortlc_result[17*4];
uint8_t cach_result[24];
encode_shortlc(lc, shortlc_result);
generate_cach(1, 0, 1, &shortlc_result[0*17], cach_result);
bits_to_dibits(d_cach[0], cach_result, 12);
generate_cach(1, 1, 3, &shortlc_result[1*17], cach_result);
bits_to_dibits(d_cach[1], cach_result, 12);
generate_cach(1, 0, 3, &shortlc_result[2*17], cach_result);
bits_to_dibits(d_cach[2], cach_result, 12);
generate_cach(1, 1, 2, &shortlc_result[3*17], cach_result);
bits_to_dibits(d_cach[3], cach_result, 12);
uint8_t full_lc[2][72]; // group voice channel user lc pdu vol 1 #7.1.1.1
memset(full_lc[0], 0, 72);
memset(full_lc[1], 0, 72);
store_i(d_so[0], &full_lc[0][16], 8);
store_i(d_so[1], &full_lc[1][16], 8);
store_i(d_ga[0], &full_lc[0][24], 24);
store_i(d_ga[1], &full_lc[1][24], 24);
store_i(d_sa[0], &full_lc[0][48], 24);
store_i(d_sa[1], &full_lc[1][48], 24);
uint8_t embedded_result[2][32*4];
encode_embedded(full_lc[0], embedded_result[0]);
encode_embedded(full_lc[1], embedded_result[1]);
uint8_t emb_result[2][16*4];
encode_emb(d_cc[0], 0, 1, &emb_result[0][0*16]);
encode_emb(d_cc[0], 0, 3, &emb_result[0][1*16]);
encode_emb(d_cc[0], 0, 3, &emb_result[0][2*16]);
encode_emb(d_cc[0], 0, 2, &emb_result[0][3*16]);
encode_emb(d_cc[1], 0, 1, &emb_result[1][0*16]);
encode_emb(d_cc[1], 0, 3, &emb_result[1][1*16]);
encode_emb(d_cc[1], 0, 3, &emb_result[1][2*16]);
encode_emb(d_cc[1], 0, 2, &emb_result[1][3*16]);
uint8_t emb_buf[48];
for (int i=0; i<2; i++) {
for (int j=0; j<4; j++) {
memcpy(&emb_buf[0], &emb_result[i][j*16], 8);
memcpy(&emb_buf[8], &embedded_result[i][j*32], 32);
memcpy(&emb_buf[40], &emb_result[i][j*16+8], 8);
bits_to_dibits(&d_embedded[i][j*24], emb_buf, 24);
}
}
}
void
dmr_bs_tx_bb_impl::forecast(int nof_output_items, gr_vector_int &nof_input_items_reqd)
{
// reads three ambe codewords (216 bits = 108 dibits) for each 144 dibit output burst
// the three codewords though are only read from one of the two inputs per burst
const size_t nof_inputs = nof_input_items_reqd.size();
const int nof_bursts = nof_output_items / 144.0;
const int nof_samples_reqd = 3 * ((nof_bursts+1)>>1);
std::fill(&nof_input_items_reqd[0], &nof_input_items_reqd[nof_inputs], nof_samples_reqd);
}
int
dmr_bs_tx_bb_impl::general_work (int noutput_items,
gr_vector_int &ninput_items,
gr_vector_const_void_star &input_items,
gr_vector_void_star &output_items)
{
static const int burst_schedule[2][6] = {
// -3: idle SYNC
// -2: voice SYNC
// -1: RC
// 0|1|2|3: segment no. of embedded signalling
{-2, 0, 1, -1, 2, 3},
{-1, 2, 3, -2, 0, 1}
};
int nconsumed[2] = {0,0};
uint8_t *in[2];
in[0] = (uint8_t *) input_items[0];
in[1] = (uint8_t *) input_items[1];
uint8_t *out = reinterpret_cast<uint8_t*>(output_items[0]);
int nframes=0;
for (int n=0;n < (noutput_items/144);n++) {
// need (at least) three voice codewords
if ((ninput_items[d_next_slot] - nconsumed[d_next_slot]) < 3) break;
int cach_id = ((d_next_burst & 1) << 1) + d_next_slot;
memcpy(&out[0], d_cach[cach_id], 12);
memcpy(&out[12], &in[d_next_slot][0], 36);
memcpy(&out[48], &in[d_next_slot][36], 18);
int schedule = burst_schedule[d_next_slot][d_next_burst];
if (schedule == -3 || !d_en[d_next_slot])
memcpy(&out[66], dmr_bs_idle_sync, 24);
else if (schedule == -2)
memcpy(&out[66], dmr_bs_voice_sync, 24);
else if (schedule == -1)
memset(&out[66], 0, 24);
else
memcpy(&out[66], &d_embedded[d_next_slot][schedule*24], 24);
memcpy(&out[90], &in[d_next_slot][36+18], 18);
memcpy(&out[108], &in[d_next_slot][36+36], 36);
in[d_next_slot] += 108;
out += 144;
nconsumed[d_next_slot] += 3;
d_next_slot = (d_next_slot + 1) & 1;
if (d_next_slot == 0) {
d_next_burst = (d_next_burst + 1) % 6;
}
nframes++;
}
// Tell runtime system how many input items we consumed on
// each input stream.
if (nconsumed[0])
consume (0, nconsumed[0]);
if (nconsumed[1])
consume (1, nconsumed[1]);
// Tell runtime system how many output items we produced.
return (nframes * 144);
}
} /* namespace op25_repeater */
} /* namespace gr */