1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-25 17:28:50 -05:00

DATV Demod: Add support for LDPC on Windows. Use Qt worker thread instead of external ldpc_tool process.

This commit is contained in:
Jon Beniston 2022-07-18 16:40:00 +01:00
parent a65c9458ed
commit ff26ece347
10 changed files with 353 additions and 73 deletions

View File

@ -25,6 +25,7 @@ set(datv_SOURCES
set(ldpc_SOURCES
ldpctool/tables_handler.cpp
ldpctool/ldpcworker.cpp
)
set(datv_HEADERS
@ -55,6 +56,7 @@ set(ldpc_HEADERS
ldpctool/dvb_s2_tables.h
ldpctool/dvb_s2x_tables.h
ldpctool/dvb_t2_tables.h
ldpctool/ldpcworker.h
)
include_directories(
@ -69,16 +71,10 @@ include_directories(
set(TARGET_NAME demoddatv)
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
if (LINUX)
add_library(${TARGET_NAME} SHARED
${datv_SOURCES}
${ldpc_SOURCES}
)
else()
add_library(${TARGET_NAME} SHARED
${datv_SOURCES}
)
endif()
add_library(${TARGET_NAME} SHARED
${datv_SOURCES}
${ldpc_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
@ -94,13 +90,11 @@ target_link_libraries(${TARGET_NAME}
${SWRESAMPLE_LIBRARIES}
)
if (LINUX)
add_executable(ldpctool
ldpctool/ldpc_tool.cpp
ldpctool/tables_handler.cpp
)
install(TARGETS ldpctool DESTINATION ${INSTALL_BIN_DIR})
endif()
add_executable(ldpctool
ldpctool/ldpc_tool.cpp
ldpctool/tables_handler.cpp
)
install(TARGETS ldpctool DESTINATION ${INSTALL_BIN_DIR})
if(FFMPEG_EXTERNAL)
add_dependencies(${TARGET_NAME} ffmpeg)

View File

@ -300,13 +300,8 @@ DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Ba
CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute);
connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect()));
#ifdef LINUX
CRightClickEnabler *ldpcToolRightClickEnabler = new CRightClickEnabler(ui->softLDPC);
connect(ldpcToolRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(ldpcToolSelect()));
#else
ui->softLDPC->setEnabled(false);
ui->softLDPC->setStyleSheet("QCheckBox { color: gray }");
#endif
ui->playerIndicator->setStyleSheet("QLabel { background-color: gray; border-radius: 8px; }");
ui->udpIndicator->setStyleSheet("QLabel { background-color: gray; border-radius: 8px; }");
@ -377,7 +372,6 @@ void DATVDemodGUI::displaySettings()
ui->maxBitflipsLabel->setStyleSheet("QLabel { color: white }");
}
#ifdef LINUX
if (m_settings.m_standard == DATVDemodSettings::dvb_version::DVB_S)
{
ui->softLDPC->setEnabled(false);
@ -388,7 +382,6 @@ void DATVDemodGUI::displaySettings()
ui->softLDPC->setEnabled(true);
ui->softLDPC->setStyleSheet("QCheckBox { color: white }");
}
#endif
if (m_settings.m_standard == DATVDemodSettings::dvb_version::DVB_S)
{
@ -658,7 +651,6 @@ void DATVDemodGUI::on_cmbStandard_currentIndexChanged(int index)
ui->maxBitflipsLabel->setStyleSheet("QLabel { color: white }");
}
#ifdef LINUX
if (m_settings.m_standard == DATVDemodSettings::dvb_version::DVB_S)
{
ui->softLDPC->setEnabled(false);
@ -669,7 +661,6 @@ void DATVDemodGUI::on_cmbStandard_currentIndexChanged(int index)
ui->softLDPC->setEnabled(true);
ui->softLDPC->setStyleSheet("QCheckBox { color: white }");
}
#endif
if (m_settings.m_standard == DATVDemodSettings::dvb_version::DVB_S)
{
@ -710,18 +701,14 @@ void DATVDemodGUI::on_cmbFEC_currentIndexChanged(int arg1)
void DATVDemodGUI::on_softLDPC_clicked()
{
#ifdef LINUX
m_settings.m_softLDPC = ui->softLDPC->isChecked();
applySettings();
#endif
}
void DATVDemodGUI::on_maxBitflips_valueChanged(int value)
{
#ifdef LINUX
m_settings.m_maxBitflips = value;
applySettings();
#endif
}
void DATVDemodGUI::on_chkViterbi_clicked()

View File

@ -27,6 +27,12 @@
#include "datvdemodsettings.h"
#ifdef _MSC_VER
#define DEFAULT_LDPCTOOLPATH "C:/Program Files/SDRangel/ldpctool.exe"
#else
#define DEFAULT_LDPCTOOLPATH "/opt/install/sdrangel/bin/ldpctool"
#endif
DATVDemodSettings::DATVDemodSettings() :
m_channelMarker(nullptr),
m_rollupState(nullptr)
@ -44,7 +50,7 @@ void DATVDemodSettings::resetToDefaults()
m_modulation = BPSK;
m_fec = FEC12;
m_softLDPC = false;
m_softLDPCToolPath = "/opt/install/sdrangel/bin/ldpctool";
m_softLDPCToolPath = DEFAULT_LDPCTOOLPATH;
m_softLDPCMaxTrials = 8;
m_maxBitflips = 0;
m_symbolRate = 250000;
@ -208,7 +214,7 @@ bool DATVDemodSettings::deserialize(const QByteArray& data)
d.readBool(32, &m_softLDPC, false);
d.readS32(33, &m_maxBitflips, 0);
d.readString(34, &m_softLDPCToolPath, "/opt/install/sdrangel/bin/ldpctool");
d.readString(34, &m_softLDPCToolPath, DEFAULT_LDPCTOOLPATH);
d.readS32(35, &tmp, 8);
m_softLDPCMaxTrials = tmp < 1 ? 1 : tmp > m_softLDPCMaxMaxTrials ? m_softLDPCMaxMaxTrials : tmp;
d.readBool(36, &m_playerEnable, true);

View File

@ -397,7 +397,6 @@ void DATVDemodSink::CleanUpDATVFramework()
delete (leansdr::s2_fecdec<bool, leansdr::hard_sb>*) r_fecdec;
}
#ifdef LINUX
if (r_fecdecsoft != nullptr) {
delete (leansdr::s2_fecdec_soft<leansdr::llr_t,leansdr::llr_sb>*) r_fecdecsoft;
}
@ -405,7 +404,6 @@ void DATVDemodSink::CleanUpDATVFramework()
if (r_fecdechelper != nullptr) {
delete (leansdr::s2_fecdec_helper<leansdr::llr_t,leansdr::llr_sb>*) r_fecdechelper;
}
#endif
if (p_deframer != nullptr) {
delete (leansdr::s2_deframer*) p_deframer;
@ -515,10 +513,8 @@ void DATVDemodSink::ResetDATVFrameworkPointers()
p_bbframes = nullptr;
p_s2_deinterleaver = nullptr;
r_fecdec = nullptr;
#ifdef LINUX
r_fecdecsoft = nullptr;
r_fecdechelper = nullptr;
#endif
p_deframer = nullptr;
r_scope_symbols_dvbs2 = nullptr;
}
@ -1101,7 +1097,6 @@ void DATVDemodSink::InitDATVS2Framework()
p_vbitcount= new leansdr::pipebuf<int>(m_objScheduler, "Bits processed", BUF_S2PACKETS);
p_verrcount = new leansdr::pipebuf<int>(m_objScheduler, "Bits corrected", BUF_S2PACKETS);
#ifdef LINUX
bool commandFileValid = false;
if (QFileInfo::exists(m_settings.m_softLDPCToolPath))
@ -1110,7 +1105,7 @@ void DATVDemodSink::InitDATVS2Framework()
commandFileValid = fileInfo.isExecutable();
}
if (m_settings.m_softLDPC && commandFileValid)
if (m_settings.m_softLDPC /*&& commandFileValid*/)
{
#if 0
// Doesn't work...
@ -1178,25 +1173,6 @@ void DATVDemodSink::InitDATVS2Framework()
leansdr::s2_fecdec<bool, leansdr::hard_sb> *fecdec = (leansdr::s2_fecdec<bool, leansdr::hard_sb> * ) r_fecdec;
fecdec->bitflips=m_settings.m_maxBitflips;
}
#else
// Bit-flipping mode.
// Deinterleave into hard bits.
p_fecframes = new leansdr::pipebuf< leansdr::fecframe<leansdr::hard_sb> >(m_objScheduler, "FEC frames", BUF_FRAMES);
p_s2_deinterleaver = new leansdr::s2_deinterleaver<leansdr::llr_ss,leansdr::hard_sb>(
m_objScheduler,
*(leansdr::pipebuf< leansdr::plslot<leansdr::llr_ss> > *) p_slots_dvbs2,
*(leansdr::pipebuf< leansdr::fecframe<leansdr::hard_sb> > * ) p_fecframes
);
r_fecdec = new leansdr::s2_fecdec<bool, leansdr::hard_sb>(
m_objScheduler,
*(leansdr::pipebuf< leansdr::fecframe<leansdr::hard_sb> > * ) p_fecframes,
*(leansdr::pipebuf<leansdr::bbframe> *) p_bbframes,
p_vbitcount,
p_verrcount
);
leansdr::s2_fecdec<bool, leansdr::hard_sb> *fecdec = (leansdr::s2_fecdec<bool, leansdr::hard_sb> * ) r_fecdec;
fecdec->bitflips=m_settings.m_maxBitflips;
#endif
// Deframe BB frames to TS packets
p_lock = new leansdr::pipebuf<int> (m_objScheduler, "lock", BUF_SLOW);

View File

@ -58,7 +58,11 @@ void DatvDvbS2LdpcDialog::on_showFileDialog_clicked(bool checked)
QFileDialog fileDialog(this, "Select LDPC tool");
fileDialog.setOption(QFileDialog::DontUseNativeDialog, true);
#ifdef _MSC_VER
fileDialog.setNameFilter("*.exe");
#else
fileDialog.setFilter(QDir::Executable | QDir::Files);
#endif
fileDialog.selectFile(m_fileName);
if (fileDialog.exec() == QDialog::Accepted)

View File

@ -8,10 +8,39 @@ Copyright 2018 Ahmet Inan <xdsopl@gmail.com>
#define LAYERED_DECODER_HH
#include <stdlib.h>
#ifdef _MSC_VER
#include <malloc.h>
#endif
#include "ldpc.h"
namespace ldpctool {
class LDPCUtil
{
public:
#ifndef _MSC_VER
static void *aligned_malloc(size_t alignment, size_t size)
{
return aligned_alloc(alignment, size);
}
static void aligned_free(void *mem)
{
free(mem);
}
#else
static void *aligned_malloc(size_t alignment, size_t size)
{
return _aligned_malloc(size, alignment);
}
static void aligned_free(void *mem)
{
_aligned_free(mem);
}
#endif
};
template <typename TYPE, typename ALG>
class LDPCDecoder
{
@ -113,8 +142,8 @@ public:
}
LT = ldpc->links_total();
delete ldpc;
bnl = reinterpret_cast<TYPE *>(aligned_alloc(sizeof(TYPE), sizeof(TYPE) * LT));
pty = reinterpret_cast<TYPE *>(aligned_alloc(sizeof(TYPE), sizeof(TYPE) * R));
bnl = reinterpret_cast<TYPE *>(LDPCUtil::aligned_malloc(sizeof(TYPE), sizeof(TYPE) * LT));
pty = reinterpret_cast<TYPE *>(LDPCUtil::aligned_malloc(sizeof(TYPE), sizeof(TYPE) * R));
uint16_t *tmp = new uint16_t[R * CNL];
for (int i = 0; i < q; ++i)
for (int j = 0; j < M; ++j)
@ -139,8 +168,8 @@ public:
~LDPCDecoder()
{
if (initialized) {
free(bnl);
free(pty);
LDPCUtil::aligned_free(bnl);
LDPCUtil::aligned_free(pty);
delete[] cnc;
delete[] pos;
delete[] inp;

View File

@ -7,7 +7,14 @@ Copyright 2019 <pabr@pabr.org>
*/
#include <stdlib.h>
#ifndef _MSC_VER
#include <unistd.h>
#else
#include <BaseTsd.h>
typedef SSIZE_T ssize_t;
#include <io.h>
#include <malloc.h>
#endif
#include <iostream>
#include <iomanip>
#include <random>
@ -21,6 +28,7 @@ Copyright 2019 <pabr@pabr.org>
#include "algorithms.h"
#include "ldpc.h"
#if 0
#include "flooding_decoder.h"
static const int DEFAULT_TRIALS = 50;
@ -129,7 +137,7 @@ int main(int argc, char **argv)
int BLOCKS = batch_size;
ldpctool::code_type *code = new ldpctool::code_type[BLOCKS * CODE_LEN];
void *aligned_buffer = aligned_alloc(sizeof(ldpctool::simd_type), sizeof(ldpctool::simd_type) * CODE_LEN);
void *aligned_buffer = ldpctool::LDPCUtil::aligned_malloc(sizeof(ldpctool::simd_type), sizeof(ldpctool::simd_type) * CODE_LEN);
ldpctool::simd_type *simd = reinterpret_cast<ldpctool::simd_type *>(aligned_buffer);
// Expect LLR values in int8_t format.
@ -212,7 +220,7 @@ int main(int argc, char **argv)
delete ldpc;
free(aligned_buffer);
ldpctool::LDPCUtil::aligned_free(aligned_buffer);
delete[] code;
return 0;

View File

@ -4,6 +4,8 @@ LDPC testbench
Copyright 2018 Ahmet Inan <xdsopl@gmail.com>
*/
#pragma once
#include <cstdint>
#include <complex>
#include "simd.h"

View File

@ -54,13 +54,18 @@
#include "ldpc.h"
#include "sdr.h"
#ifdef LINUX
#include <signal.h>
#ifdef LINUX
#include <sys/wait.h>
#endif
#ifdef _MSC_VER
#include <BaseTsd.h>
typedef SSIZE_T ssize_t;
#endif
#include "ldpctool/layered_decoder.h"
#include "ldpctool/testbench.h"
#include "ldpctool/algorithms.h"
#endif
#include "ldpctool/ldpcworker.h"
namespace leansdr
{
@ -3041,8 +3046,6 @@ struct s2_fecdec : runnable
pipewriter<int> *bitcount, *errcount;
}; // s2_fecdec
#ifdef LINUX
// Soft LDPC decoder
// Internally implemented LDPC tool. Replaces external LDPC decoder
@ -3230,6 +3233,8 @@ private:
T q[SIZE];
};
#if defined(USE_LDPC_TOOL) && !defined(_MSC_VER)
template <typename SOFTBIT, typename SOFTBYTE>
struct s2_fecdec_helper : runnable
{
@ -3610,7 +3615,280 @@ struct s2_fecdec_helper : runnable
std::deque<int> errcount_q;
pipewriter<int> *bitcount, *errcount;
}; // s2_fecdec_helper
#else // USE_LDPC_TOOL
template <typename SOFTBIT, typename SOFTBYTE>
struct s2_fecdec_helper : runnable
{
int batch_size;
int nhelpers;
bool must_buffer;
int max_trials;
s2_fecdec_helper(
scheduler *sch,
pipebuf<fecframe<SOFTBYTE>> &_in,
pipebuf<bbframe> &_out,
const char *_command,
pipebuf<int> *_bitcount = nullptr,
pipebuf<int> *_errcount = nullptr
) :
runnable(sch, "S2 fecdec io"),
batch_size(16),
nhelpers(1),
must_buffer(false),
max_trials(8),
in(_in),
out(_out),
bitcount(opt_writer(_bitcount, 1)),
errcount(opt_writer(_errcount, 1))
{
command = strdup(_command);
for (int mc = 0; mc < 32; ++mc) {
for (int sf = 0; sf < 2; ++sf) {
pools[mc][sf].procs = nullptr;
}
}
}
~s2_fecdec_helper()
{
free(command);
killall(); // also deletes pools[mc][sf].procs if necessary
}
void run()
{
// Send work until all helpers block.
while (in.readable() >= 1 && !jobs.full())
{
if ((bbframe_q.size() != 0) && (out.writable() >= 1))
{
bbframe *pout = out.wr();
pout->pls = bbframe_q.front().pls;
std::copy(bbframe_q.front().bytes, bbframe_q.front().bytes + (58192 / 8), pout->bytes);
bbframe_q.pop_front();
out.written(1);
}
if ((bitcount_q.size() != 0) && opt_writable(bitcount, 1))
{
opt_write(bitcount, bitcount_q.front());
bitcount_q.pop_front();
}
if ((errcount_q.size() != 0) && opt_writable(errcount, 1))
{
opt_write(errcount, errcount_q.front());
errcount_q.pop_front();
}
if (!jobs.empty() && jobs.peek()->h->b_out) {
receive_frame(jobs.get());
}
send_frame(in.rd());
in.read(1);
}
}
private:
struct helper_instance
{
QThread *m_thread;
LDPCWorker *m_worker;
int batch_size;
int b_in; // Jobs in input queue
int b_out; // Jobs in output queue
};
struct pool
{
helper_instance *procs; // nullptr or [nprocs]
int nprocs;
int shift;
} pools[32][2]; // [modcod][sf]
struct helper_job
{
s2_pls pls;
helper_instance *h;
};
simplequeue<helper_job, 1024> jobs;
// Try to send a frame. Return false if helper was busy.
bool send_frame(fecframe<SOFTBYTE> *pin)
{
pool *p = get_pool(&pin->pls);
for (int j = 0; j < p->nprocs; ++j)
{
int i = (p->shift + j) % p->nprocs;
helper_instance *h = &p->procs[i];
int iosize = (pin->pls.framebits() / 8) * sizeof(SOFTBYTE);
if (h->m_worker->busy()) {
continue;
}
QByteArray data((char *)pin->bytes, iosize);
QMetaObject::invokeMethod(h->m_worker, "process", Qt::QueuedConnection, Q_ARG(QByteArray, data));
p->shift = i;
helper_job *job = jobs.put();
job->pls = pin->pls;
job->h = h;
++h->b_in;
if (h->b_in >= h->batch_size)
{
h->b_in -= h->batch_size;
h->b_out += h->batch_size;
}
return true; // done sent to worker
}
fprintf(stderr, "s2_fecdec_helper::send_frame: WARNING: all %d workers were busy: modcod=%d sf=%d)\n",
p->nprocs, pin->pls.modcod, pin->pls.sf);
return false; // all workers were busy
}
// Return a pool of running helpers for a given modcod.
pool *get_pool(const s2_pls *pls)
{
pool *p = &pools[pls->modcod][pls->sf];
if (!p->procs)
{
fprintf(stderr, "s2_fecdec_helper::get_pool: allocate %d workers: modcod=%d sf=%d\n",
nhelpers, pls->modcod, pls->sf);
p->procs = new helper_instance[nhelpers];
for (int i = 0; i < nhelpers; ++i) {
spawn_helper(&p->procs[i], pls);
}
p->nprocs = nhelpers;
p->shift = 0;
}
return p;
}
void killall()
{
qDebug() << "s2_fecdec_helper::killall";
for (int i = 0; i < 32; i++) // all MODCODs
{
for (int j = 0; j < 2; j++) // long and short frames
{
pool *p = &pools[i][j];
if (p->procs)
{
for (int i = 0; i < p->nprocs; ++i)
{
helper_instance *h = &p->procs[i];
h->m_thread->quit();
h->m_thread->wait();
delete h->m_thread;
h->m_thread = nullptr;
delete h->m_worker;
h->m_worker = nullptr;
}
delete p->procs;
p->procs = nullptr;
p->nprocs = 0;
}
} // long and short frames
} // all MODCODs
}
// Spawn a helper thread.
void spawn_helper(helper_instance *h, const s2_pls *pls)
{
qDebug() << "s2_fecdec_helper: Spawning LDPC thread: modcod=" << pls->modcod << " sf=" << pls->sf;
h->m_thread = new QThread();
h->m_worker = new LDPCWorker(pls->modcod, max_trials, batch_size, pls->sf);
h->m_worker->moveToThread(h->m_thread);
h->batch_size = batch_size;
h->b_in = h->b_out = 0;
h->m_thread->start();
}
// Receive a finished job.
void receive_frame(const helper_job *job)
{
// Read corrected frame from helper
const s2_pls *pls = &job->pls;
int iosize = (pls->framebits() / 8) * sizeof(ldpc_buf[0]);
// Blocking read - do we need to return faster?
// If so, call m_worker->dataAvailable()
QByteArray data = job->h->m_worker->data();
memcpy(ldpc_buf, data.data(), data.size());
--job->h->b_out;
// Decode BCH.
const modcod_info *mcinfo = check_modcod(job->pls.modcod);
const fec_info *fi = &fec_infos[job->pls.sf][mcinfo->rate];
uint8_t *hardbytes = softbytes_harden(ldpc_buf, fi->kldpc / 8, bch_buf);
size_t cwbytes = fi->kldpc / 8;
//size_t msgbytes = fi->Kbch / 8;
//size_t chkbytes = cwbytes - msgbytes;
bch_interface *bch = s2bch.bchs[job->pls.sf][mcinfo->rate];
int ncorr = bch->decode(hardbytes, cwbytes);
if (sch->debug2) {
fprintf(stderr, "BCHCORR = %d\n", ncorr);
}
bool corrupted = (ncorr < 0);
// Report VBER
bitcount_q.push_back(fi->Kbch);
//opt_write(bitcount, fi->Kbch);
errcount_q.push_front((ncorr >= 0) ? ncorr : fi->Kbch);
//opt_write(errcount, (ncorr >= 0) ? ncorr : fi->Kbch);
#if 0
// TBD Some decoders want the bad packets.
if ( corrupted ) {
fprintf(stderr, "Passing bad frame\n");
corrupted = false;
}
#endif
if (!corrupted)
{
// Descramble and output
bbframe_q.emplace_back();
//bbframe *pout = out.wr();
bbframe_q.back().pls = job->pls;
bbscrambling.transform(hardbytes, fi->Kbch / 8, bbframe_q.back().bytes);
//out.written(1);
}
if (sch->debug) {
fprintf(stderr, "%c", corrupted ? '!' : ncorr ? '.' : '_');
}
}
pipereader<fecframe<SOFTBYTE>> in;
pipewriter<bbframe> out;
char *command;
SOFTBYTE ldpc_buf[64800 / 8];
uint8_t bch_buf[64800 / 8]; // Temp storage for hardening before BCH
s2_bch_engines s2bch;
s2_bbscrambling bbscrambling;
std::deque<bbframe> bbframe_q;
std::deque<int> bitcount_q;
std::deque<int> errcount_q;
pipewriter<int> *bitcount, *errcount;
}; // s2_fecdec_helper
#endif // USE_LDPC_TOOL
// S2 FRAMER
// EN 302 307-1 section 5.1 Mode adaptation
@ -3854,7 +4132,6 @@ private:
{
handle_ts(data, dfl, syncd, sync);
}
#ifdef LINUX
else if (streamtype == 1)
{
if (fd_gse >= 0)
@ -3874,7 +4151,6 @@ private:
fprintf(stderr, "Unrecognized bbframe\n");
}
}
#endif
}
void handle_ts(uint8_t *data, uint16_t dfl, uint16_t syncd, uint8_t sync)

View File

@ -183,13 +183,11 @@ The controls specific to DVB-S are disabled and greyed out. These are: Fast Lock
<h5>B.2b.6: DVB-S2 specific - Soft LDPC decoder</h5>
This is for experimenters only working in Linux. It can be used to decode signals lower that ~10 db MER which is the limit of LDPC hard decoding as explained next (B.2b.7). Video degrades progressively down to about 7.5 dB MER and drops below this limit.
Runs the `ldpctool` program for soft LDPC decoding. Frames are sent on its standard input and decoded frames retrieved from its standard output. Two processes executing `ldpctool` are spawned but so far it seems that only one is effectively used.
It can be used to decode signals lower that ~10 db MER which is the limit of LDPC hard decoding as explained next (B.2b.7). Video degrades progressively down to about 7.5 dB MER and drops below this limit.
Right clicking on this control opens a dialog where you can choose:
- The `ldpctool` executable. You have to use the `ldpctool` binary produced by the build of SDRangel.
- The `ldpctool` executable. Obsolete.
- The maximum of retries in LDPC decoding from 1 to 8.
<h5>B.2b.7: DVB-S2 specific - LDPC maximum number of bit flips allowed</h5>