From 2d5cb02421bb4067bacdc0afd8d9442cd8809da5 Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Wed, 15 Jul 2026 10:10:01 +0300 Subject: [PATCH] Init commit --- .gitignore | 28 ++++++++++++++++ CMakeLists.txt | 28 ++++++++++++++++ README.md | 48 ++++++++++++++++++++++++++++ ewsdr_dmr.c | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ ewsdr_dmr.h | 53 +++++++++++++++++++++++++++++++ 5 files changed, 243 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 ewsdr_dmr.c create mode 100644 ewsdr_dmr.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6fefc4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# Out-of-source CMake/Jenkins builds when this directory is used as WORKSPACE. +/build/ +/build-*/ +/install/ +/install-*/ +/deps/ + +# Accidental in-source CMake builds. +/CMakeCache.txt +/CMakeFiles/ +/cmake_install.cmake +/install_manifest.txt +/Makefile +/CTestTestfile.cmake +/compile_commands.json +/.ninja_* +/*.ninja + +# Native build products created directly in this directory. +/*.dll +/*.dll.a +/*.so +/*.so.* +/*.dylib +/*.a +/*.o +/*.obj +/*.pdb diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..00e411c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.10) +project(ewsdr_dmr C) + +find_path(MBELIB_INCLUDE_DIR mbelib.h REQUIRED) +find_library(MBELIB_LIBRARY NAMES mbe REQUIRED) + +add_library(ewsdr_dmr SHARED ewsdr_dmr.c) +target_compile_definitions(ewsdr_dmr PRIVATE EWSDR_DMR_BUILD) +target_include_directories(ewsdr_dmr + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} + PRIVATE ${MBELIB_INCLUDE_DIR}) +target_link_libraries(ewsdr_dmr PRIVATE ${MBELIB_LIBRARY}) +set_target_properties(ewsdr_dmr PROPERTIES + C_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN YES) + +# DMRBindings.pas loads this exact name. MinGW otherwise adds the Unix-style +# "lib" prefix to DLLs (libewsdr_dmr.dll). +if(WIN32) + set_target_properties(ewsdr_dmr PROPERTIES PREFIX "") +endif() + +include(GNUInstallDirs) +install(TARGETS ewsdr_dmr + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(FILES ewsdr_dmr.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/README.md b/README.md new file mode 100644 index 0000000..23dc48c --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# EWSDR DMR native adapter + +This optional shared library isolates `mbelib` and the imported DMR core from +the Lazarus application. It intentionally does not depend on PulseAudio, +ncurses, libsndfile or the DSD-FME command-line frontend. + +Build on Linux: + +```sh +cmake -S native/dmr -B build/dmr -DCMAKE_BUILD_TYPE=Release +cmake --build build/dmr +``` + +The Pascal burst/FEC layer extracts three row-major `char[4][24]` AMBE+2 +frames from each selected voice-slot burst and feeds them through this ABI. +Decoded 8 kHz mono PCM is resampled to EWSDR's 48 kHz speaker/web route. + +For a build-tree run, either copy `libewsdr_dmr.so` beside the EWSDR executable +or add `build/dmr` to `LD_LIBRARY_PATH`. `cmake --install build/dmr` installs the +adapter system-wide. The GUI and daemon remain usable (with metadata but no +voice) when this optional library is absent. + +## Jenkins cross-build for Windows x64 + +Use `jenkins/build-mbelib-win64.sh` in a Jenkins job that checks out mbelib. +It creates `install-win64` with `mbelib.h`, the import library, `libmbe.dll`, +and the MinGW runtime DLLs. + +Configure the mbelib job to archive `install-win64/**`. In the EWSDR job, copy +that artifact to `deps/mbelib` and then run +`native/dmr/jenkins/build-ewsdr-dmr-win64.sh` before `lazbuild`. The script +expects the resulting prefix at +`$WORKSPACE/deps/mbelib/install-win64`, builds from +`$WORKSPACE/native/dmr`, and stages the DLL bundle in +`$WORKSPACE/bin/x86_64-win64` beside the future `ewsdr.exe`. All paths can be +overridden with environment variables. + +Declarative/Scripted Pipeline example for the artifact transfer: + +```groovy +copyArtifacts( + projectName: 'mbelib', + selector: lastSuccessful(), + filter: 'install-win64/**', + target: 'deps/mbelib' +) +sh 'native/dmr/jenkins/build-ewsdr-dmr-win64.sh' +``` diff --git a/ewsdr_dmr.c b/ewsdr_dmr.c new file mode 100644 index 0000000..f60fd76 --- /dev/null +++ b/ewsdr_dmr.c @@ -0,0 +1,86 @@ +#include "ewsdr_dmr.h" + +#include +#include +#include + +struct ewsdr_dmr_mbe { + mbe_parms current; + mbe_parms previous; + mbe_parms previous_enhanced; + int uv_quality; +}; + +uint32_t ewsdr_dmr_abi_version(void) +{ + return EWSDR_DMR_ABI_VERSION; +} + +void ewsdr_dmr_mbe_reset(ewsdr_dmr_mbe *decoder) +{ + if (decoder == NULL) + return; + mbe_initMbeParms(&decoder->current, &decoder->previous, + &decoder->previous_enhanced); +} + +ewsdr_dmr_mbe *ewsdr_dmr_mbe_create(int uv_quality) +{ + ewsdr_dmr_mbe *decoder = calloc(1, sizeof(*decoder)); + if (decoder == NULL) + return NULL; + if (uv_quality < 1) + uv_quality = 1; + if (uv_quality > 64) + uv_quality = 64; + decoder->uv_quality = uv_quality; + ewsdr_dmr_mbe_reset(decoder); + return decoder; +} + +void ewsdr_dmr_mbe_destroy(ewsdr_dmr_mbe *decoder) +{ + if (decoder == NULL) + return; + memset(decoder, 0, sizeof(*decoder)); + free(decoder); +} + +int ewsdr_dmr_mbe_decode(ewsdr_dmr_mbe *decoder, + const uint8_t ambe_bits[EWSDR_DMR_AMBE_BITS], + float pcm_out[EWSDR_DMR_PCM_SAMPLES], + ewsdr_dmr_mbe_result *result) +{ + char frame[4][24]; + char data[49]; + char error_text[64]; + int errors_c0 = 0; + int errors_total = 0; + size_t row; + size_t col; + + if (decoder == NULL || ambe_bits == NULL || pcm_out == NULL) + return -1; + + for (row = 0; row < 4; ++row) { + for (col = 0; col < 24; ++col) { + const uint8_t bit = ambe_bits[row * 24 + col]; + if (bit > 1) + return -2; + frame[row][col] = (char)bit; + } + } + + memset(data, 0, sizeof(data)); + memset(error_text, 0, sizeof(error_text)); + mbe_processAmbe3600x2450Framef( + pcm_out, &errors_c0, &errors_total, error_text, frame, data, + &decoder->current, &decoder->previous, &decoder->previous_enhanced, + decoder->uv_quality); + + if (result != NULL) { + result->errors_c0 = errors_c0; + result->errors_total = errors_total; + } + return 0; +} diff --git a/ewsdr_dmr.h b/ewsdr_dmr.h new file mode 100644 index 0000000..6120f44 --- /dev/null +++ b/ewsdr_dmr.h @@ -0,0 +1,53 @@ +#ifndef EWSDR_DMR_H +#define EWSDR_DMR_H + +#include +#include + +#if defined(_WIN32) +# if defined(EWSDR_DMR_BUILD) +# define EWSDR_DMR_API __declspec(dllexport) +# else +# define EWSDR_DMR_API __declspec(dllimport) +# endif +#else +# define EWSDR_DMR_API __attribute__((visibility("default"))) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define EWSDR_DMR_ABI_VERSION 1u +#define EWSDR_DMR_AMBE_BITS 96u +#define EWSDR_DMR_PCM_SAMPLES 160u + +typedef struct ewsdr_dmr_mbe ewsdr_dmr_mbe; + +typedef struct ewsdr_dmr_mbe_result { + int errors_c0; + int errors_total; +} ewsdr_dmr_mbe_result; + +EWSDR_DMR_API uint32_t ewsdr_dmr_abi_version(void); +EWSDR_DMR_API ewsdr_dmr_mbe *ewsdr_dmr_mbe_create(int uv_quality); +EWSDR_DMR_API void ewsdr_dmr_mbe_destroy(ewsdr_dmr_mbe *decoder); +EWSDR_DMR_API void ewsdr_dmr_mbe_reset(ewsdr_dmr_mbe *decoder); + +/* + * Decode one already deinterleaved DMR AMBE+2 frame. ambe_bits is a row-major + * char[4][24] matrix containing only 0/1 values. pcm_out receives 160 float + * samples at 8 kHz. Frame extraction/deinterleaving remains in the DMR burst + * layer, keeping mbelib and its state behind this stable ABI. + */ +EWSDR_DMR_API int ewsdr_dmr_mbe_decode( + ewsdr_dmr_mbe *decoder, + const uint8_t ambe_bits[EWSDR_DMR_AMBE_BITS], + float pcm_out[EWSDR_DMR_PCM_SAMPLES], + ewsdr_dmr_mbe_result *result); + +#ifdef __cplusplus +} +#endif + +#endif