From e55a92f57eb73783011fa1189276d51b90b9fdae Mon Sep 17 00:00:00 2001 From: William Yang Date: Sat, 17 Feb 2024 21:04:34 +1100 Subject: [PATCH] Added data packet channel with reed solomon outer decoding --- examples/basic_radio_app.cpp | 2 + .../gui/basic_radio/render_basic_radio.cpp | 132 +++++---- examples/gui/basic_radio/render_common.cpp | 2 + examples/radio_app.cpp | 2 + src/basic_radio/CMakeLists.txt | 1 + src/basic_radio/basic_audio_channel.cpp | 4 +- src/basic_radio/basic_audio_channel.h | 7 +- src/basic_radio/basic_data_packet_channel.cpp | 83 ++++++ src/basic_radio/basic_data_packet_channel.h | 38 +++ src/basic_radio/basic_msc_runner.h | 10 + src/basic_radio/basic_radio.cpp | 88 ++++-- src/basic_radio/basic_radio.h | 25 +- src/basic_scraper/basic_scraper.cpp | 46 +++- src/basic_scraper/basic_scraper.h | 9 +- src/dab/CMakeLists.txt | 4 +- src/dab/audio/aac_frame_processor.cpp | 8 +- src/dab/database/dab_database_entities.h | 40 ++- src/dab/database/dab_database_types.h | 1 - src/dab/database/dab_database_updater.cpp | 26 +- src/dab/database/dab_database_updater.h | 3 +- src/dab/msc/msc_data_group_processor.cpp | 156 +++++++++++ src/dab/msc/msc_data_group_processor.h | 49 ++++ src/dab/msc/msc_data_packet_processor.cpp | 177 ++++++++++++ src/dab/msc/msc_data_packet_processor.h | 30 +++ ...msc_reed_solomon_data_packet_processor.cpp | 255 ++++++++++++++++++ .../msc_reed_solomon_data_packet_processor.h | 44 +++ src/dab/msc/msc_xpad_processor.cpp | 193 ------------- src/dab/msc/msc_xpad_processor.h | 41 --- src/dab/pad/pad_MOT_processor.cpp | 11 +- src/dab/radio_fig_handler.cpp | 4 +- 30 files changed, 1122 insertions(+), 369 deletions(-) create mode 100644 src/basic_radio/basic_data_packet_channel.cpp create mode 100644 src/basic_radio/basic_data_packet_channel.h create mode 100644 src/basic_radio/basic_msc_runner.h create mode 100644 src/dab/msc/msc_data_group_processor.cpp create mode 100644 src/dab/msc/msc_data_group_processor.h create mode 100644 src/dab/msc/msc_data_packet_processor.cpp create mode 100644 src/dab/msc/msc_data_packet_processor.h create mode 100644 src/dab/msc/msc_reed_solomon_data_packet_processor.cpp create mode 100644 src/dab/msc/msc_reed_solomon_data_packet_processor.h delete mode 100644 src/dab/msc/msc_xpad_processor.cpp delete mode 100644 src/dab/msc/msc_xpad_processor.h diff --git a/examples/basic_radio_app.cpp b/examples/basic_radio_app.cpp index 67e2e30..06d0670 100644 --- a/examples/basic_radio_app.cpp +++ b/examples/basic_radio_app.cpp @@ -13,6 +13,8 @@ #include #include +#include "basic_radio/basic_radio.h" +#include "basic_radio/basic_audio_channel.h" #include "basic_scraper/basic_scraper.h" #include "./app_helpers/app_io_buffers.h" #include "./app_helpers/app_ofdm_blocks.h" diff --git a/examples/gui/basic_radio/render_basic_radio.cpp b/examples/gui/basic_radio/render_basic_radio.cpp index 66d2ed3..27697d8 100644 --- a/examples/gui/basic_radio/render_basic_radio.cpp +++ b/examples/gui/basic_radio/render_basic_radio.cpp @@ -1,6 +1,4 @@ -#include "./render_basic_radio.h" -#include "./basic_radio_view_controller.h" -#include "dab/database/dab_database_entities.h" +#include "./render_common.h" #define IMGUI_DEFINE_MATH_OPERATORS #include @@ -12,12 +10,16 @@ #include #include "../font_awesome_definitions.h" #include "./formatters.h" -#include "./render_common.h" +#include "./render_basic_radio.h" +#include "./basic_radio_view_controller.h" #include "basic_radio/basic_radio.h" #include "basic_radio/basic_slideshow.h" +#include "basic_radio/basic_data_packet_channel.h" +#include "basic_radio/basic_audio_channel.h" #include "basic_radio/basic_dab_plus_channel.h" #include "basic_radio/basic_dab_channel.h" +#include "dab/database/dab_database_entities.h" #include "dab/database/dab_database.h" #include "dab/database/dab_database_updater.h" @@ -34,6 +36,7 @@ static void RenderSimple_Service(BasicRadio& radio, BasicRadioViewController& co static void RenderSimple_ServiceComponentList(BasicRadio& radio, BasicRadioViewController& controller, Service* service); static void RenderSimple_ServiceComponent(BasicRadio& radio, BasicRadioViewController& controller, ServiceComponent& component); static void RenderSimple_Basic_Audio_Channel(BasicRadio& radio, BasicRadioViewController& controller, Basic_Audio_Channel& channel, const subchannel_id_t subchannel_id); +static void RenderSimple_Basic_Data_Channel(BasicRadio& radio, BasicRadioViewController& controller, Basic_Data_Packet_Channel& channel, const subchannel_id_t subchannel_id); static void RenderSimple_BasicSlideshowSelected(BasicRadio& radio, BasicRadioViewController& controller); static void RenderSimple_LinkServices(BasicRadio& radio, BasicRadioViewController& controller, Service* service); static void RenderSimple_LinkService(BasicRadio& radio, BasicRadioViewController& controller, const LinkService& link_service); @@ -267,60 +270,32 @@ void RenderSimple_ServiceComponent(BasicRadio& radio, BasicRadioViewController& auto* audio_channel = radio.Get_Audio_Channel(subchannel_id); if (audio_channel != nullptr) { - if (ImGui::Begin("Audio Channel")) { + const auto ascty = audio_channel->GetType(); + const char* channel_name = "Unknown"; + switch (ascty) { + case AudioServiceType::DAB_PLUS: channel_name = "DAB+"; break; + case AudioServiceType::DAB: channel_name = "DAB"; break; + default: channel_name = "Unknown"; break; + } + auto label = fmt::format("{} Channel###Channel", channel_name); + if (ImGui::Begin(label.c_str())) { RenderSimple_Basic_Audio_Channel(radio, controller, *audio_channel, subchannel_id); } ImGui::End(); - } -} - -void RenderSimple_Basic_Audio_Channel(BasicRadio& radio, BasicRadioViewController& controller, Basic_Audio_Channel& channel, subchannel_id_t subchannel_id) { - // Channel controls - auto& controls = channel.GetControls(); - if (ImGui::Button("Run All")) { - controls.RunAll(); - } - ImGui::SameLine(); - if (ImGui::Button("Stop All")) { - controls.StopAll(); - } - bool v = false; - v = controls.GetIsDecodeAudio(); - ImGui::SameLine(); - if (ImGui::Checkbox("Decode audio", &v)) { - controls.SetIsDecodeAudio(v); - } - v = controls.GetIsDecodeData(); - ImGui::SameLine(); - if (ImGui::Checkbox("Decode data", &v)) { - controls.SetIsDecodeData(v); - } - v = controls.GetIsPlayAudio(); - ImGui::SameLine(); - if (ImGui::Checkbox("Play audio", &v)) { - controls.SetIsPlayAudio(v); + return; } - const auto ascty = channel.GetType(); - switch (ascty) { - case AudioServiceType::DAB_PLUS: - RenderSimple_Basic_DAB_Plus_Channel_Status(dynamic_cast(channel)); - break; - case AudioServiceType::DAB: - RenderSimple_Basic_DAB_Channel_Status(dynamic_cast(channel)); - break; - case AudioServiceType::UNDEFINED: - default: - break; + auto* data_channel = radio.Get_Data_Packet_Channel(subchannel_id); + if (data_channel != nullptr) { + if (ImGui::Begin("Data Channel###Channel")) { + RenderSimple_Basic_Data_Channel(radio, controller, *data_channel, subchannel_id); + } + ImGui::End(); + return; } +} - // Programme associated data - // 1. Dynamic label - // 2. MOT slideshow - auto label = channel.GetDynamicLabel(); - ImGui::Text("Dynamic label: %.*s", int(label.length()), label.data()); - - auto& slideshow_manager = channel.GetSlideshowManager(); +static void RenderSimple_Slideshow_Manager(BasicRadioViewController& controller, Basic_Slideshow_Manager& slideshow_manager, subchannel_id_t subchannel_id) { ImGuiChildFlags child_flags = ImGuiChildFlags_Border; ImGuiWindowFlags window_flags = ImGuiWindowFlags_None; if (ImGui::BeginChild("Slideshow", ImVec2(0, 0), child_flags, window_flags)) { @@ -372,6 +347,61 @@ void RenderSimple_Basic_Audio_Channel(BasicRadio& radio, BasicRadioViewControlle ImGui::EndChild(); } +void RenderSimple_Basic_Audio_Channel(BasicRadio& radio, BasicRadioViewController& controller, Basic_Audio_Channel& channel, subchannel_id_t subchannel_id) { + // Channel controls + auto& controls = channel.GetControls(); + if (ImGui::Button("Run All")) { + controls.RunAll(); + } + ImGui::SameLine(); + if (ImGui::Button("Stop All")) { + controls.StopAll(); + } + bool v = false; + v = controls.GetIsDecodeAudio(); + ImGui::SameLine(); + if (ImGui::Checkbox("Decode audio", &v)) { + controls.SetIsDecodeAudio(v); + } + v = controls.GetIsDecodeData(); + ImGui::SameLine(); + if (ImGui::Checkbox("Decode data", &v)) { + controls.SetIsDecodeData(v); + } + v = controls.GetIsPlayAudio(); + ImGui::SameLine(); + if (ImGui::Checkbox("Play audio", &v)) { + controls.SetIsPlayAudio(v); + } + + const auto ascty = channel.GetType(); + switch (ascty) { + case AudioServiceType::DAB_PLUS: + RenderSimple_Basic_DAB_Plus_Channel_Status(dynamic_cast(channel)); + break; + case AudioServiceType::DAB: + RenderSimple_Basic_DAB_Channel_Status(dynamic_cast(channel)); + break; + case AudioServiceType::UNDEFINED: + default: + break; + } + + // Programme associated data + // 1. Dynamic label + // 2. MOT slideshow + auto label = channel.GetDynamicLabel(); + ImGui::Text("Dynamic label: %.*s", int(label.length()), label.data()); + + auto& slideshow_manager = channel.GetSlideshowManager(); + RenderSimple_Slideshow_Manager(controller, slideshow_manager, subchannel_id); +} + +void RenderSimple_Basic_Data_Channel(BasicRadio& radio, BasicRadioViewController& controller, Basic_Data_Packet_Channel& channel, const subchannel_id_t subchannel_id) { + auto& slideshow_manager = channel.GetSlideshowManager(); + RenderSimple_Slideshow_Manager(controller, slideshow_manager, subchannel_id); +} + static void render_error_indicator(const char* label, bool is_error) { static const auto COLOR_NO_ERROR = ImColor(0,255,0).Value; static const auto COLOR_ERROR = ImColor(255,0,0).Value; diff --git a/examples/gui/basic_radio/render_common.cpp b/examples/gui/basic_radio/render_common.cpp index 7772868..5d89862 100644 --- a/examples/gui/basic_radio/render_common.cpp +++ b/examples/gui/basic_radio/render_common.cpp @@ -5,8 +5,10 @@ #include #include #include "basic_radio/basic_radio.h" +#include "basic_radio/basic_audio_channel.h" #include "dab/database/dab_database.h" #include "dab/database/dab_database_updater.h" +#include "dab/dab_misc_info.h" #include "./formatters.h" template diff --git a/examples/radio_app.cpp b/examples/radio_app.cpp index a0f26b6..cef7b57 100644 --- a/examples/radio_app.cpp +++ b/examples/radio_app.cpp @@ -12,6 +12,8 @@ #include #include #include +#include "basic_radio/basic_radio.h" +#include "basic_radio/basic_audio_channel.h" #include "basic_scraper/basic_scraper.h" #include "./block_frequencies.h" #include "./app_helpers/app_io_buffers.h" diff --git a/src/basic_radio/CMakeLists.txt b/src/basic_radio/CMakeLists.txt index 8407987..7acc5f8 100644 --- a/src/basic_radio/CMakeLists.txt +++ b/src/basic_radio/CMakeLists.txt @@ -13,6 +13,7 @@ add_library(basic_radio STATIC ${SRC_DIR}/basic_audio_channel.cpp ${SRC_DIR}/basic_dab_plus_channel.cpp ${SRC_DIR}/basic_dab_channel.cpp + ${SRC_DIR}/basic_data_packet_channel.cpp ${SRC_DIR}/basic_slideshow.cpp) set_target_properties(basic_radio PROPERTIES CXX_STANDARD 17) target_include_directories(basic_radio PRIVATE ${SRC_DIR} ${ROOT_DIR}) diff --git a/src/basic_radio/basic_audio_channel.cpp b/src/basic_radio/basic_audio_channel.cpp index 68ab825..48de93d 100644 --- a/src/basic_radio/basic_audio_channel.cpp +++ b/src/basic_radio/basic_audio_channel.cpp @@ -3,14 +3,16 @@ #include "dab/database/dab_database_entities.h" #include "dab/msc/msc_decoder.h" #include "dab/mot/MOT_slideshow_processor.h" -#include +#include +#include #include "./basic_radio_logging.h" #define LOG_MESSAGE(...) BASIC_RADIO_LOG_MESSAGE(fmt::format(__VA_ARGS__)) #define LOG_ERROR(...) BASIC_RADIO_LOG_ERROR(fmt::format(__VA_ARGS__)) Basic_Audio_Channel::Basic_Audio_Channel(const DAB_Parameters& params, const Subchannel subchannel, const AudioServiceType audio_service_type) : m_params(params), m_subchannel(subchannel), m_audio_service_type(audio_service_type) { + assert(subchannel.is_complete); m_msc_decoder = std::make_unique(m_subchannel); m_slideshow_manager = std::make_unique(); } diff --git a/src/basic_radio/basic_audio_channel.h b/src/basic_radio/basic_audio_channel.h index fd144e5..34f2183 100644 --- a/src/basic_radio/basic_audio_channel.h +++ b/src/basic_radio/basic_audio_channel.h @@ -6,6 +6,7 @@ #include #include +#include "./basic_msc_runner.h" #include "./basic_audio_params.h" #include "./basic_audio_controls.h" #include "dab/constants/dab_parameters.h" @@ -20,7 +21,7 @@ struct Basic_Slideshow; class Basic_Slideshow_Manager; // Shared interface for DAB+/DAB channels -class Basic_Audio_Channel +class Basic_Audio_Channel: public Basic_MSC_Runner { protected: const DAB_Parameters m_params; @@ -38,8 +39,8 @@ class Basic_Audio_Channel Observable m_obs_MOT_entity; public: explicit Basic_Audio_Channel(const DAB_Parameters& params, const Subchannel subchannel, const AudioServiceType audio_service_type); - virtual ~Basic_Audio_Channel(); - virtual void Process(tcb::span msc_bits_buf) = 0; + virtual ~Basic_Audio_Channel() override; + virtual void Process(tcb::span msc_bits_buf) override = 0; AudioServiceType GetType(void) const { return m_audio_service_type; } auto& GetControls(void) { return m_controls; } std::string_view GetDynamicLabel(void) const { return m_dynamic_label; } diff --git a/src/basic_radio/basic_data_packet_channel.cpp b/src/basic_radio/basic_data_packet_channel.cpp new file mode 100644 index 0000000..dfcb8ce --- /dev/null +++ b/src/basic_radio/basic_data_packet_channel.cpp @@ -0,0 +1,83 @@ +#include "./basic_data_packet_channel.h" +#include +#include +#include "dab/database/dab_database_entities.h" +#include "dab/msc/msc_decoder.h" +#include "dab/msc/msc_data_packet_processor.h" +#include "dab/msc/msc_reed_solomon_data_packet_processor.h" +#include "dab/mot/MOT_processor.h" +#include "./basic_slideshow.h" + +#include +#include "./basic_radio_logging.h" +#define LOG_MESSAGE(...) BASIC_RADIO_LOG_MESSAGE(fmt::format(__VA_ARGS__)) +#define LOG_ERROR(...) BASIC_RADIO_LOG_ERROR(fmt::format(__VA_ARGS__)) + +Basic_Data_Packet_Channel::Basic_Data_Packet_Channel(const DAB_Parameters& params, Subchannel subchannel, DataServiceType type) +: m_params(params), m_subchannel(subchannel), m_type(type) +{ + assert(subchannel.is_complete); + assert(subchannel.fec_scheme != FEC_Scheme::UNDEFINED); + m_msc_rs_data_packet_processor = nullptr; + m_msc_decoder = std::make_unique(m_subchannel); + m_msc_data_packet_processor = std::make_unique(); + m_slideshow_manager = std::make_unique(); + if (m_subchannel.fec_scheme == FEC_Scheme::REED_SOLOMON) { + m_msc_rs_data_packet_processor = std::make_unique(); + m_msc_rs_data_packet_processor->SetCallback([this](tcb::span buf, bool is_fec) { + ProcessNonFECPackets(buf); + }); + } + m_msc_data_packet_processor->Get_MOT_Processor().OnEntityComplete().Attach([this](MOT_Entity entity) { + auto slideshow = m_slideshow_manager->Process_MOT_Entity(entity); + if (slideshow == nullptr) { + m_obs_MOT_entity.Notify(entity); + } + }); + + // Where do we use this? + (void)m_type; +} + +Basic_Data_Packet_Channel::~Basic_Data_Packet_Channel() = default; + +void Basic_Data_Packet_Channel::Process(tcb::span msc_bits_buf) { + BASIC_RADIO_SET_THREAD_NAME(fmt::format("MSC-data-packet-subchannel-{}", m_subchannel.id)); + + const int nb_msc_bits = (int)msc_bits_buf.size(); + if (nb_msc_bits != m_params.nb_msc_bits) { + LOG_ERROR("Got incorrect number of MSC bits {}/{}", nb_msc_bits, m_params.nb_msc_bits); + return; + } + + for (int i = 0; i < m_params.nb_cifs; i++) { + const auto cif_buf = msc_bits_buf.subspan(i*m_params.nb_cif_bits, m_params.nb_cif_bits); + auto buf = m_msc_decoder->DecodeCIF(cif_buf); + // The MSC decoder can have 0 bytes if the deinterleaver is still collecting frames + if (buf.empty()) { + continue; + } + + if (m_msc_rs_data_packet_processor) { + ProcessFECPackets(buf); + } else { + ProcessNonFECPackets(buf); + } + } +} + +void Basic_Data_Packet_Channel::ProcessFECPackets(tcb::span buf) { + while (!buf.empty()) { + const size_t total_read = m_msc_rs_data_packet_processor->ReadPacket(buf); + assert(total_read <= buf.size()); + buf = buf.subspan(total_read); + } +} + +void Basic_Data_Packet_Channel::ProcessNonFECPackets(tcb::span buf) { + while (!buf.empty()) { + const size_t total_read = m_msc_data_packet_processor->ReadPacket(buf); + assert(total_read <= buf.size()); + buf = buf.subspan(total_read); + } +} diff --git a/src/basic_radio/basic_data_packet_channel.h b/src/basic_radio/basic_data_packet_channel.h new file mode 100644 index 0000000..6069e2f --- /dev/null +++ b/src/basic_radio/basic_data_packet_channel.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include "./basic_msc_runner.h" +#include "dab/constants/dab_parameters.h" +#include "dab/database/dab_database_entities.h" +#include "dab/database/dab_database_types.h" +#include "utility/span.h" +#include "viterbi_config.h" +#include "utility/observable.h" + +class MSC_Decoder; +class MSC_Data_Packet_Processor; +class MSC_Reed_Solomon_Data_Packet_Processor; +class Basic_Slideshow_Manager; +struct MOT_Entity; + +class Basic_Data_Packet_Channel: public Basic_MSC_Runner +{ +private: + const DAB_Parameters m_params; + const Subchannel m_subchannel; + const DataServiceType m_type; + std::unique_ptr m_msc_decoder; + std::unique_ptr m_msc_data_packet_processor; + std::unique_ptr m_msc_rs_data_packet_processor; + std::unique_ptr m_slideshow_manager; + Observable m_obs_MOT_entity; +public: + explicit Basic_Data_Packet_Channel(const DAB_Parameters& params, Subchannel subchannel, DataServiceType type); + ~Basic_Data_Packet_Channel() override; + void Process(tcb::span msc_bits_buf) override; + auto& GetSlideshowManager() { return *m_slideshow_manager; } + auto& OnMOTEntity() { return m_obs_MOT_entity; } +private: + void ProcessNonFECPackets(tcb::span buf); + void ProcessFECPackets(tcb::span buf); +}; diff --git a/src/basic_radio/basic_msc_runner.h b/src/basic_radio/basic_msc_runner.h new file mode 100644 index 0000000..f77b166 --- /dev/null +++ b/src/basic_radio/basic_msc_runner.h @@ -0,0 +1,10 @@ +#pragma once + +#include "utility/span.h" +#include "viterbi_config.h" + +class Basic_MSC_Runner { +public: + virtual ~Basic_MSC_Runner() {}; + virtual void Process(tcb::span msc_bits_buf) = 0; +}; diff --git a/src/basic_radio/basic_radio.cpp b/src/basic_radio/basic_radio.cpp index 610b671..6ad73dd 100644 --- a/src/basic_radio/basic_radio.cpp +++ b/src/basic_radio/basic_radio.cpp @@ -1,4 +1,9 @@ #include "./basic_radio.h" +#include "./basic_thread_pool.h" +#include "./basic_fic_runner.h" +#include "./basic_msc_runner.h" +#include "./basic_data_packet_channel.h" +#include "./basic_audio_channel.h" #include "./basic_dab_plus_channel.h" #include "./basic_dab_channel.h" #include "dab/database/dab_database.h" @@ -11,9 +16,11 @@ #define LOG_MESSAGE(...) BASIC_RADIO_LOG_MESSAGE(fmt::format(__VA_ARGS__)) #define LOG_ERROR(...) BASIC_RADIO_LOG_ERROR(fmt::format(__VA_ARGS__)) -BasicRadio::BasicRadio(const DAB_Parameters& _params, const size_t nb_threads) -: m_params(_params), m_thread_pool(nb_threads), m_fic_runner(_params) +BasicRadio::BasicRadio(const DAB_Parameters& params, const size_t nb_threads) +: m_params(params) { + m_thread_pool = std::make_unique(nb_threads); + m_fic_runner = std::make_unique(m_params); m_dab_misc_info = std::make_unique(); m_dab_database = std::make_unique(); m_dab_database_stats = std::make_unique(); @@ -21,6 +28,10 @@ BasicRadio::BasicRadio(const DAB_Parameters& _params, const size_t nb_threads) BasicRadio::~BasicRadio() = default; +size_t BasicRadio::GetTotalThreads() const { + return m_thread_pool->GetTotalThreads(); +} + void BasicRadio::Process(tcb::span buf) { const int N = (int)buf.size(); if (N != m_params.nb_frame_bits) { @@ -31,18 +42,18 @@ void BasicRadio::Process(tcb::span buf) { auto fic_buf = buf.subspan(0, m_params.nb_fic_bits); auto msc_buf = buf.subspan(m_params.nb_fic_bits, m_params.nb_msc_bits); - m_thread_pool.PushTask([this, &fic_buf] { - m_fic_runner.Process(fic_buf); + m_thread_pool->PushTask([this, &fic_buf] { + m_fic_runner->Process(fic_buf); }); - for (auto& [_, channel]: m_audio_channels) { - auto& dab_plus_channel = *(channel.get()); - m_thread_pool.PushTask([&dab_plus_channel, &msc_buf] { - dab_plus_channel.Process(msc_buf); + for (const auto& [_, _msc_runner]: m_msc_runners) { + auto* msc_runner = _msc_runner.get(); + m_thread_pool->PushTask([msc_runner, msc_buf]() { + msc_runner->Process(msc_buf); }); } - m_thread_pool.WaitAll(); + m_thread_pool->WaitAll(); UpdateAfterProcessing(); } @@ -55,10 +66,18 @@ Basic_Audio_Channel* BasicRadio::Get_Audio_Channel(const subchannel_id_t id) { return res->second.get(); } +Basic_Data_Packet_Channel* BasicRadio::Get_Data_Packet_Channel(const subchannel_id_t id) { + auto res = m_data_packet_channels.find(id); + if (res == m_data_packet_channels.end()) { + return nullptr; + } + return res->second.get(); +} + void BasicRadio::UpdateAfterProcessing() { auto lock = std::scoped_lock(m_mutex_data); - const auto& new_misc_info = m_fic_runner.GetMiscInfo(); - const auto& dab_database_updater = m_fic_runner.GetDatabaseUpdater(); + const auto& new_misc_info = m_fic_runner->GetMiscInfo(); + const auto& dab_database_updater = m_fic_runner->GetDatabaseUpdater(); const auto& new_dab_database = dab_database_updater.GetDatabase(); const auto& new_dab_database_stats = dab_database_updater.GetStatistics(); @@ -71,7 +90,9 @@ void BasicRadio::UpdateAfterProcessing() { *m_dab_database_stats = new_dab_database_stats; for (auto& subchannel: m_dab_database->subchannels) { - if (m_audio_channels.find(subchannel.id) != m_audio_channels.end()) { + if (!subchannel.is_complete) continue; + + if (m_msc_runners.find(subchannel.id) != m_msc_runners.end()) { continue; } @@ -85,25 +106,42 @@ void BasicRadio::UpdateAfterProcessing() { if (!service_component) { continue; } + if (!service_component->is_complete) { + continue; + } const auto mode = service_component->transport_mode; - if (mode != TransportMode::STREAM_MODE_AUDIO) { + const auto audio_type = service_component->audio_service_type; + const auto data_type = service_component->data_service_type; + + if (audio_type == AudioServiceType::DAB_PLUS && mode == TransportMode::STREAM_MODE_AUDIO) { + LOG_MESSAGE("Added DAB+ subchannel {}", subchannel.id); + auto channel = std::make_shared(m_params, subchannel, audio_type); + m_msc_runners.insert({ subchannel.id, channel }); + m_audio_channels.insert({ subchannel.id, channel }); + m_obs_audio_channel.Notify(subchannel.id, *channel); continue; } - const auto ascty = service_component->audio_service_type; - if (ascty == AudioServiceType::DAB_PLUS) { - LOG_MESSAGE("Added DAB+ subchannel {}", subchannel.id); - auto channel_ptr = std::make_unique(m_params, subchannel, ascty); - auto& channel = *(channel_ptr.get()); - m_audio_channels.insert({subchannel.id, std::move(channel_ptr)}); - m_obs_audio_channel.Notify(subchannel.id, channel); - } else if (ascty == AudioServiceType::DAB) { + if (audio_type == AudioServiceType::DAB && mode == TransportMode::STREAM_MODE_AUDIO) { LOG_MESSAGE("Added DAB subchannel {}", subchannel.id); - auto channel_ptr = std::make_unique(m_params, subchannel, ascty); - auto& channel = *(channel_ptr.get()); - m_audio_channels.insert({subchannel.id, std::move(channel_ptr)}); - m_obs_audio_channel.Notify(subchannel.id, channel); + auto channel = std::make_shared(m_params, subchannel, audio_type); + m_msc_runners.insert({ subchannel.id, channel }); + m_audio_channels.insert({ subchannel.id, channel }); + m_obs_audio_channel.Notify(subchannel.id, *channel); + continue; + } + + // DOC: EN 300 401 + // Clause: 5.3.5 FEC for MSC packet mode + // Data packet channels require the FEC scheme to be defined for outer encoding + if (mode == TransportMode::PACKET_MODE_DATA && (subchannel.fec_scheme != FEC_Scheme::UNDEFINED)) { + LOG_MESSAGE("Added data packet subchannel {}", subchannel.id); + auto channel = std::make_shared(m_params, subchannel, data_type); + m_msc_runners.insert({ subchannel.id, channel }); + m_data_packet_channels.insert({ subchannel.id, channel }); + m_obs_data_packet_channel.Notify(subchannel.id, *channel); + continue; } } } \ No newline at end of file diff --git a/src/basic_radio/basic_radio.h b/src/basic_radio/basic_radio.h index 5c8c46a..f1b8b11 100644 --- a/src/basic_radio/basic_radio.h +++ b/src/basic_radio/basic_radio.h @@ -4,41 +4,50 @@ #include #include -#include "./basic_thread_pool.h" -#include "./basic_fic_runner.h" -#include "./basic_audio_channel.h" #include "dab/constants/dab_parameters.h" +#include "dab/database/dab_database_types.h" #include "utility/observable.h" #include "utility/span.h" +#include "viterbi_config.h" struct DAB_Database; struct DAB_Misc_Info; struct DatabaseUpdaterGlobalStatistics; +class BasicThreadPool; +class BasicFICRunner; +class Basic_MSC_Runner; +class Basic_Audio_Channel; +class Basic_Data_Packet_Channel; // Our basic radio class BasicRadio { private: const DAB_Parameters m_params; - BasicThreadPool m_thread_pool; - BasicFICRunner m_fic_runner; + std::unique_ptr m_thread_pool; + std::unique_ptr m_fic_runner; + std::unordered_map> m_msc_runners; std::mutex m_mutex_data; std::unique_ptr m_dab_misc_info; std::unique_ptr m_dab_database; std::unique_ptr m_dab_database_stats; - std::unordered_map> m_audio_channels; + std::unordered_map> m_audio_channels; + std::unordered_map> m_data_packet_channels; Observable m_obs_audio_channel; + Observable m_obs_data_packet_channel; public: - explicit BasicRadio(const DAB_Parameters& _params, const size_t nb_threads=0); + explicit BasicRadio(const DAB_Parameters& params, const size_t nb_threads=0); ~BasicRadio(); void Process(tcb::span buf); Basic_Audio_Channel* Get_Audio_Channel(const subchannel_id_t id); + Basic_Data_Packet_Channel* Get_Data_Packet_Channel(const subchannel_id_t id); auto& GetMutex() { return m_mutex_data; } auto& GetMiscInfo() { return *(m_dab_misc_info.get()); } auto& GetDatabase() { return *(m_dab_database.get()); } auto& GetDatabaseStatistics() { return *(m_dab_database_stats.get()); } auto& On_Audio_Channel() { return m_obs_audio_channel; } - size_t GetTotalThreads() const { return m_thread_pool.GetTotalThreads(); } + auto& On_Data_Packet_Channel() { return m_obs_data_packet_channel; } + size_t GetTotalThreads() const; private: void UpdateAfterProcessing(); }; \ No newline at end of file diff --git a/src/basic_scraper/basic_scraper.cpp b/src/basic_scraper/basic_scraper.cpp index 54c86c5..ea48070 100644 --- a/src/basic_scraper/basic_scraper.cpp +++ b/src/basic_scraper/basic_scraper.cpp @@ -1,9 +1,13 @@ #include "./basic_scraper.h" +#include "basic_radio/basic_radio.h" +#include "basic_radio/basic_audio_channel.h" #include "basic_radio/basic_dab_channel.h" #include "basic_radio/basic_dab_plus_channel.h" +#include "basic_radio/basic_data_packet_channel.h" #include "basic_radio/basic_radio.h" #include "basic_radio/basic_slideshow.h" #include "dab/database/dab_database_entities.h" +#include "dab/database/dab_database_types.h" #include "dab/mot/MOT_processor.h" #include "dab/database/dab_database.h" #include @@ -24,6 +28,17 @@ static std::string GetCurrentTime(void) { tm.tm_hour, tm.tm_min, tm.tm_sec); } +static ServiceComponent* find_service_component(DAB_Database& db, subchannel_id_t id) { + ServiceComponent* component = nullptr; + for (auto& e: db.service_components) { + if (e.subchannel_id == id) { + component = &e; + break; + }; + } + return component; +} + void BasicScraper::attach_to_radio(std::shared_ptr scraper, BasicRadio& radio) { if (scraper == nullptr) return; auto root_directory = scraper->root_directory; @@ -31,16 +46,8 @@ void BasicScraper::attach_to_radio(std::shared_ptr scraper, BasicR [scraper, root_directory, &radio](subchannel_id_t id, Basic_Audio_Channel& channel) { // determine root folder auto& db = radio.GetDatabase(); - ServiceComponent* component = nullptr; - for (auto& e: db.service_components) { - if (e.subchannel_id == id) { - component = &e; - break; - }; - } - if (component == nullptr) { - return; - } + auto* component = find_service_component(db, id); + if (component == nullptr) return; const auto service_id = component->service_reference; const auto component_id = component->component_id; const auto root_folder = fmt::format("{:s}", root_directory); @@ -53,6 +60,25 @@ void BasicScraper::attach_to_radio(std::shared_ptr scraper, BasicR Basic_Audio_Channel_Scraper::attach_to_channel(dab_plus_scraper, channel); } ); + radio.On_Data_Packet_Channel().Attach( + [scraper, root_directory, &radio](subchannel_id_t id, Basic_Data_Packet_Channel& channel) { + // determine root folder + auto& db = radio.GetDatabase(); + auto* component = find_service_component(db, id); + if (component == nullptr) return; + const auto service_id = component->service_reference; + const auto component_id = component->component_id; + const auto root_folder = fmt::format("{:s}", root_directory); + const auto child_folder = fmt::format("service_{}_component_{}", service_id, component_id); + auto base_path = fs::path(root_folder) / fs::path(child_folder); + auto abs_path = fs::absolute(base_path); + + auto mot_scraper = std::make_shared(abs_path / "MOT"); + channel.OnMOTEntity().Attach([mot_scraper](MOT_Entity mot_entity) { + mot_scraper->OnMOTEntity(mot_entity); + }); + } + ); } Basic_Audio_Channel_Scraper::Basic_Audio_Channel_Scraper(const fs::path& dir) diff --git a/src/basic_scraper/basic_scraper.h b/src/basic_scraper/basic_scraper.h index 006f912..3d10113 100644 --- a/src/basic_scraper/basic_scraper.h +++ b/src/basic_scraper/basic_scraper.h @@ -1,14 +1,14 @@ #pragma once -#include "basic_radio/basic_radio.h" - #include #include #include #include #include - +#include "basic_radio/basic_audio_params.h" +#include "dab/mot/MOT_entities.h" #include "dab/audio/aac_frame_processor.h" #include "utility/span.h" + namespace fs = std::filesystem; // Scraping output directory structure @@ -21,6 +21,9 @@ namespace fs = std::filesystem; // │ └─{date}_{transport_id}_{label}.{ext} // └─MOT // └─{date}_{transport_id}_{label}.{ext} +class BasicRadio; +class Basic_Audio_Channel; +struct Basic_Slideshow; class BasicAudioScraper { diff --git a/src/dab/CMakeLists.txt b/src/dab/CMakeLists.txt index 65c10c3..2be6568 100644 --- a/src/dab/CMakeLists.txt +++ b/src/dab/CMakeLists.txt @@ -14,7 +14,9 @@ add_library(dab_core STATIC ${SRC_DIR}/database/dab_database_updater.cpp ${SRC_DIR}/msc/msc_decoder.cpp ${SRC_DIR}/msc/cif_deinterleaver.cpp - ${SRC_DIR}/msc/msc_xpad_processor.cpp + ${SRC_DIR}/msc/msc_data_group_processor.cpp + ${SRC_DIR}/msc/msc_data_packet_processor.cpp + ${SRC_DIR}/msc/msc_reed_solomon_data_packet_processor.cpp ${SRC_DIR}/audio/aac_frame_processor.cpp ${SRC_DIR}/audio/aac_audio_decoder.cpp ${SRC_DIR}/audio/aac_data_decoder.cpp diff --git a/src/dab/audio/aac_frame_processor.cpp b/src/dab/audio/aac_frame_processor.cpp index fd6bc76..79f3d53 100644 --- a/src/dab/audio/aac_frame_processor.cpp +++ b/src/dab/audio/aac_frame_processor.cpp @@ -98,12 +98,14 @@ AAC_Frame_Processor::AAC_Frame_Processor() { // DOC: ETSI TS 102 563 // Refer to clause 6.1 on reed solomon coding // The polynomial for this is given as - // G(x) = x^8 + x^4 + x^3 + x^2 + 1 - const int galois_field_poly = 0b100011101; + // P(x) = x^8 + x^4 + x^3 + x^2 + 1 + const int GALOIS_FIELD_POLY = 0b100011101; + // G(x) = (x+λ^0)*(x+λ^1)*...*(x+λ^9) + const int CODE_TOTAL_ROOTS = 10; // The Phil Karn reed solmon decoder works with the 2^8 Galois field // Therefore we need to use the RS(255,245) decoder // As according to the spec we should insert 135 padding symbols (bytes) - m_rs_decoder = std::make_unique(8, galois_field_poly, 0, 1, 10, NB_RS_PADDING_BYTES); + m_rs_decoder = std::make_unique(8, GALOIS_FIELD_POLY, 0, 1, CODE_TOTAL_ROOTS, NB_RS_PADDING_BYTES); m_rs_encoded_buf.resize(NB_RS_MESSAGE_BYTES, 0); // Reed solomon code can correct up to floor(t/2) symbols that were wrong // where t = the number of parity symbols diff --git a/src/dab/database/dab_database_entities.h b/src/dab/database/dab_database_entities.h index 1391ca3..779421f 100644 --- a/src/dab/database/dab_database_entities.h +++ b/src/dab/database/dab_database_entities.h @@ -34,6 +34,17 @@ enum class EEP_Type: uint8_t { UNDEFINED = 0xFF, }; +// DOC: ETSI EN 300 401 +// Clause: 6.2.2 FEC sub-channel organization +// Clause: 5.3.5 FEC for MSC packet mode +enum class FEC_Scheme: uint8_t { // Value passed in 4bit field + NONE = 0b00, + REED_SOLOMON = 0b01, + RFA0 = 0b10, + RFA1 = 0b11, + UNDEFINED = 0xFF, +}; + // NOTE: A valid database entry exists when all the required fields are set // The required fields constraint is also followed in the dab_database_updater.cpp // when we are regenerating the database from the FIC (fast information channel) @@ -48,10 +59,9 @@ struct Ensemble { uint16_t reconfiguration_count = 0; // optional: fig 0/7 provides this int8_t local_time_offset = 0; // Value of this shall be +- 155 (LTO is +-15.5 hours) uint8_t international_table_id = 0; // table id for programme type strings + bool is_complete = false; }; -struct ServiceComponent; - struct Service { service_id_t reference = 0; country_id_t country_id = 0; // required @@ -60,6 +70,7 @@ struct Service { programme_id_t programme_type = 0; language_id_t language = 0; closed_caption_id_t closed_caption = 0; + bool is_complete = false; explicit Service(const service_id_t _ref) : reference(_ref) {} }; @@ -74,7 +85,8 @@ struct ServiceComponent { std::string label; TransportMode transport_mode = TransportMode::UNDEFINED; // required AudioServiceType audio_service_type = AudioServiceType::UNDEFINED; // required for transport stream audio - DataServiceType data_service_type = DataServiceType::UNDEFINED; // required for transport stream/packet data + DataServiceType data_service_type = DataServiceType::UNDEFINED; // (optional) for transport stream/packet data - we expect this to be provided but real world data doesn't + bool is_complete = false; explicit ServiceComponent( const service_id_t _service_ref, const service_component_id_t _component_id @@ -82,14 +94,15 @@ struct ServiceComponent { }; struct Subchannel { - subchannel_id_t id; - subchannel_addr_t start_address = 0; // required - subchannel_size_t length = 0; // required - bool is_uep = false; // required - uep_protection_index_t uep_prot_index = 0; // required for UEP - eep_protection_level_t eep_prot_level = 0; // required for EEP - EEP_Type eep_type = EEP_Type::UNDEFINED; // required for EEP - fec_scheme_t fec_scheme = 0; // optional? used only for packet mode + subchannel_id_t id; + subchannel_addr_t start_address = 0; // required + subchannel_size_t length = 0; // required + bool is_uep = false; // required + uep_protection_index_t uep_prot_index = 0; // required for UEP + eep_protection_level_t eep_prot_level = 0; // required for EEP + EEP_Type eep_type = EEP_Type::UNDEFINED; // required for EEP + FEC_Scheme fec_scheme = FEC_Scheme::UNDEFINED; // (optional) used only for packet mode + bool is_complete = false; explicit Subchannel(const subchannel_id_t _id): id(_id) {} }; @@ -101,6 +114,7 @@ struct LinkService { bool is_hard_link = false; bool is_international = false; service_id_t service_reference = 0; // required + bool is_complete = false; explicit LinkService(const lsn_t _id): id(_id) {} }; @@ -109,6 +123,7 @@ struct FM_Service { lsn_t linkage_set_number = 0; // required bool is_time_compensated = false; std::vector frequencies; // required + bool is_complete = false; explicit FM_Service(const fm_id_t _id): RDS_PI_code(_id) {} }; @@ -117,6 +132,7 @@ struct DRM_Service { lsn_t linkage_set_number = 0; // required bool is_time_compensated = false; std::vector frequencies; // required + bool is_complete = false; explicit DRM_Service(const drm_id_t _id): drm_code(_id) {} }; @@ -124,6 +140,7 @@ struct AMSS_Service { amss_id_t amss_code; bool is_time_compensated = false; std::vector frequencies; // required + bool is_complete = false; explicit AMSS_Service(const amss_id_t _id): amss_code(_id) {} }; @@ -135,5 +152,6 @@ struct OtherEnsemble { bool is_geographically_adjacent = false; bool is_transmission_mode_I = false; freq_t frequency = 0; // required + bool is_complete = false; explicit OtherEnsemble(const ensemble_id_t _ref): reference(_ref) {} }; diff --git a/src/dab/database/dab_database_types.h b/src/dab/database/dab_database_types.h index 7141cce..277e6da 100644 --- a/src/dab/database/dab_database_types.h +++ b/src/dab/database/dab_database_types.h @@ -21,7 +21,6 @@ typedef uint16_t subchannel_addr_t; // 10bits typedef uint16_t subchannel_size_t; // 10bits (in capacity units) typedef uint8_t eep_protection_level_t; // 2bits (EEP) table index typedef uint8_t uep_protection_index_t; // 6bits (UEP) table index -typedef uint8_t fec_scheme_t; // 4bits typedef uint16_t lsn_t; // 12bits (linkage set number) typedef uint16_t fm_id_t; // 16bits diff --git a/src/dab/database/dab_database_updater.cpp b/src/dab/database/dab_database_updater.cpp index 88e23d6..289fac6 100644 --- a/src/dab/database/dab_database_updater.cpp +++ b/src/dab/database/dab_database_updater.cpp @@ -69,7 +69,7 @@ UpdateResult EnsembleUpdater::SetInternationalTableID(const uint8_t internationa } bool EnsembleUpdater::IsComplete() { - return (m_dirty_field & ENSEMBLE_FLAG_REQUIRED) == ENSEMBLE_FLAG_REQUIRED; + return GetData().is_complete = ((m_dirty_field & ENSEMBLE_FLAG_REQUIRED) == ENSEMBLE_FLAG_REQUIRED); } // Service form @@ -110,7 +110,7 @@ UpdateResult ServiceUpdater::SetClosedCaption(const closed_caption_id_t closed_c } bool ServiceUpdater::IsComplete() { - return (m_dirty_field & SERVICE_FLAG_REQUIRED) == SERVICE_FLAG_REQUIRED; + return GetData().is_complete = ((m_dirty_field & SERVICE_FLAG_REQUIRED) == SERVICE_FLAG_REQUIRED); } // Service component form @@ -122,7 +122,7 @@ const uint8_t SERVICE_COMPONENT_FLAG_SUBCHANNEL = 0b00001000; const uint8_t SERVICE_COMPONENT_FLAG_GLOBAL_ID = 0b00000100; // two different set of fields required between audio and data const uint8_t SERVICE_COMPONENT_FLAG_REQUIRED_AUDIO = 0b01101000; -const uint8_t SERVICE_COMPONENT_FLAG_REQUIRED_DATA = 0b01011000; +const uint8_t SERVICE_COMPONENT_FLAG_REQUIRED_DATA = 0b01001000; UpdateResult ServiceComponentUpdater::SetLabel(tcb::span buf) { auto new_label = std::string_view((const char*)buf.data(), buf.size()); @@ -174,7 +174,9 @@ uint32_t ServiceComponentUpdater::GetServiceReference() { bool ServiceComponentUpdater::IsComplete() { const bool audio_complete = (m_dirty_field & SERVICE_COMPONENT_FLAG_REQUIRED_AUDIO) == SERVICE_COMPONENT_FLAG_REQUIRED_AUDIO; const bool data_complete = (m_dirty_field & SERVICE_COMPONENT_FLAG_REQUIRED_DATA) == SERVICE_COMPONENT_FLAG_REQUIRED_DATA; - return (GetData().transport_mode == TransportMode::STREAM_MODE_AUDIO) ? audio_complete : data_complete; + const bool is_complete = (GetData().transport_mode == TransportMode::STREAM_MODE_AUDIO) ? audio_complete : data_complete; + GetData().is_complete = is_complete; + return is_complete; } // Subchannel form @@ -224,14 +226,16 @@ UpdateResult SubchannelUpdater::SetEEPType(const EEP_Type eep_type) { return UpdateField(GetData().eep_type, eep_type, SUBCHANNEL_FLAG_EEP_TYPE); } -UpdateResult SubchannelUpdater::SetFECScheme(const fec_scheme_t fec_scheme) { +UpdateResult SubchannelUpdater::SetFECScheme(const FEC_Scheme fec_scheme) { return UpdateField(GetData().fec_scheme, fec_scheme, SUBCHANNEL_FLAG_FEC_SCHEME); } bool SubchannelUpdater::IsComplete() { const bool eep_complete = (m_dirty_field & SUBCHANNEL_FLAG_REQUIRED_EEP) == SUBCHANNEL_FLAG_REQUIRED_EEP; const bool uep_complete = (m_dirty_field & SUBCHANNEL_FLAG_REQUIRED_UEP) == SUBCHANNEL_FLAG_REQUIRED_UEP; - return GetData().is_uep ? uep_complete : eep_complete; + const bool is_complete = GetData().is_uep ? uep_complete : eep_complete; + GetData().is_complete = is_complete; + return is_complete; } // link service form @@ -262,7 +266,7 @@ service_id_t LinkServiceUpdater::GetServiceReference() { } bool LinkServiceUpdater::IsComplete() { - return (m_dirty_field & LINK_FLAG_REQUIRED) == LINK_FLAG_REQUIRED; + return GetData().is_complete = ((m_dirty_field & LINK_FLAG_REQUIRED) == LINK_FLAG_REQUIRED); } // fm service form @@ -288,7 +292,7 @@ UpdateResult FM_ServiceUpdater::AddFrequency(const freq_t frequency) { } bool FM_ServiceUpdater::IsComplete() { - return (m_dirty_field & FM_FLAG_REQUIRED) == FM_FLAG_REQUIRED; + return GetData().is_complete = ((m_dirty_field & FM_FLAG_REQUIRED) == FM_FLAG_REQUIRED); } // drm service form @@ -314,7 +318,7 @@ UpdateResult DRM_ServiceUpdater::AddFrequency(const freq_t frequency) { } bool DRM_ServiceUpdater::IsComplete() { - return (m_dirty_field & DRM_FLAG_REQUIRED) == DRM_FLAG_REQUIRED; + return GetData().is_complete = ((m_dirty_field & DRM_FLAG_REQUIRED) == DRM_FLAG_REQUIRED); } // amss service form @@ -335,7 +339,7 @@ UpdateResult AMSS_ServiceUpdater::AddFrequency(const freq_t frequency) { } bool AMSS_ServiceUpdater::IsComplete() { - return (m_dirty_field & AMSS_FLAG_REQUIRED) == AMSS_FLAG_REQUIRED; + return GetData().is_complete = ((m_dirty_field & AMSS_FLAG_REQUIRED) == AMSS_FLAG_REQUIRED); } // other ensemble form @@ -367,7 +371,7 @@ UpdateResult OtherEnsembleUpdater::SetFrequency(const freq_t frequency) { } bool OtherEnsembleUpdater::IsComplete() { - return (m_dirty_field & OE_FLAG_REQUIRED) == OE_FLAG_REQUIRED; + return GetData().is_complete = ((m_dirty_field & OE_FLAG_REQUIRED) == OE_FLAG_REQUIRED); } // updater parent diff --git a/src/dab/database/dab_database_updater.h b/src/dab/database/dab_database_updater.h index d6bc955..bf7a5da 100644 --- a/src/dab/database/dab_database_updater.h +++ b/src/dab/database/dab_database_updater.h @@ -6,6 +6,7 @@ #include #include #include "./dab_database.h" +#include "dab/database/dab_database_entities.h" #include "utility/span.h" struct DatabaseUpdaterGlobalStatistics { @@ -172,7 +173,7 @@ class SubchannelUpdater: private DatabaseEntityUpdater UpdateResult SetUEPProtIndex(const uep_protection_index_t uep_prot_index); UpdateResult SetEEPProtLevel(const eep_protection_level_t eep_prot_level); UpdateResult SetEEPType(const EEP_Type eep_type); - UpdateResult SetFECScheme(const fec_scheme_t fec_scheme); + UpdateResult SetFECScheme(const FEC_Scheme fec_scheme); auto& GetData() { return m_db.subchannels[m_index]; } private: bool IsComplete() override; diff --git a/src/dab/msc/msc_data_group_processor.cpp b/src/dab/msc/msc_data_group_processor.cpp new file mode 100644 index 0000000..b83da0e --- /dev/null +++ b/src/dab/msc/msc_data_group_processor.cpp @@ -0,0 +1,156 @@ +#include "./msc_data_group_processor.h" +#include "../algorithms/crc.h" +#include + +#include "../dab_logging.h" +#define TAG "msc-data-group-processor" +#define LOG_MESSAGE(...) DAB_LOG_MESSAGE(TAG, fmt::format(__VA_ARGS__)) +#define LOG_ERROR(...) DAB_LOG_ERROR(TAG, fmt::format(__VA_ARGS__)) + +static auto Generate_CRC_Calc() { + // DOC: ETSI EN 300 401 + // Clause 5.3.3.4 - MSC data group CRC + // CRC16 Polynomial is given by: + // G(x) = x^16 + x^12 + x^5 + 1 + // POLY = 0b 0001 0000 0010 0001 = 0x1021 + static const uint16_t crc16_poly = 0x1021; + static auto crc16_calc = new CRC_Calculator(crc16_poly); + crc16_calc->SetInitialValue(0xFFFF); // initial value all 1s + crc16_calc->SetFinalXORValue(0xFFFF); // transmitted crc is 1s complemented + + return crc16_calc; +}; + +static auto CRC16_CALC = Generate_CRC_Calc(); + +MSC_Data_Group_Process_Result MSC_Data_Group_Process(tcb::span data_group) { + using Status = MSC_Data_Group_Process_Result::Status; + MSC_Data_Group_Process_Result res; + + // 5.3.3.1 MSC data group header + auto buf = data_group; + constexpr size_t MIN_HEADER_SIZE = 2; + if (buf.size() < MIN_HEADER_SIZE) { + LOG_ERROR("Data group smaller than minimum header size ({} < {})", buf.size(), MIN_HEADER_SIZE); + res.status = Status::SHORT_GROUP_HEADER; + return res; + } + + const uint8_t extension_flag = (buf[0] & 0b10000000) >> 7; + const uint8_t crc_flag = (buf[0] & 0b01000000) >> 6; + const uint8_t segment_flag = (buf[0] & 0b00100000) >> 5; + const uint8_t user_access_flag = (buf[0] & 0b00010000) >> 4; + const uint8_t data_group_type = (buf[0] & 0b00001111) >> 0; + const uint8_t continuity_index = (buf[1] & 0b11110000) >> 4; + const uint8_t repetition_index = (buf[1] & 0b00001111) >> 0; + buf = buf.subspan(MIN_HEADER_SIZE); + res.has_header_fields = true; + res.data_group_type = data_group_type; + res.continuity_index = continuity_index; + res.repetition_index = repetition_index; + + // Clause: 5.3.3.4 MSC data group CRC + if (crc_flag) { + constexpr size_t CRC_SIZE = 2; + if (buf.size() < CRC_SIZE) { + LOG_ERROR("[msc-data-group] Insufficient size for crc16 ({} < {})", buf.size(), CRC_SIZE); + res.status = Status::SHORT_CRC_FIELD; + return res; + } + buf = buf.first(buf.size() - CRC_SIZE); + + const auto crc_data = data_group.first(data_group.size() - CRC_SIZE); + const auto crc_buf = data_group.last(CRC_SIZE); + const uint16_t crc_rx = (crc_buf[0] << 8) | crc_buf[1]; + const uint16_t crc_calc = CRC16_CALC->Process(crc_data); + const bool is_crc_valid = (crc_rx == crc_calc); + res.has_crc = true; + res.crc_rx = crc_rx; + res.crc_calc = crc_calc; + if (!is_crc_valid) { + LOG_ERROR("[msc-data-group] is_match={} crc_rx={:04X} crc_calc={:04X}", is_crc_valid, crc_rx, crc_calc); + res.status = Status::CRC_INVALID; + return res; + } + } + + if (extension_flag) { + constexpr size_t EXTENSION_FIELD_SIZE = 2; + if (buf.size() < EXTENSION_FIELD_SIZE) { + LOG_ERROR("Data group too short to store extension field ({} < {})", buf.size(), EXTENSION_FIELD_SIZE); + res.status = Status::SHORT_EXTENSION_FIELD; + return res; + } + const uint16_t extension_field = (buf[0] << 8) | buf[1]; + buf = buf.subspan(EXTENSION_FIELD_SIZE); + // ETSI TS 102 367: Conditional access + res.has_extension_field = true; + res.extension_field = extension_field; + } + + // Clause: 5.3.3.2 Session header + if (segment_flag) { + constexpr size_t SEGMENT_SIZE = 2; + if (buf.size() < SEGMENT_SIZE) { + LOG_ERROR("Data group too short to store segment field ({} < {})", buf.size(), SEGMENT_SIZE); + res.status = Status::SHORT_SEGMENT_FIELD; + return res; + } + const uint8_t is_last = (buf[0] & 0b10000000) >> 7; + const uint16_t segment_number = ((buf[0] & 0b01111111) << 8) | buf[1]; + buf = buf.subspan(SEGMENT_SIZE); + res.has_segment_field = true; + res.segment_field.is_last_segment = (is_last == 0b1); + res.segment_field.segment_number = segment_number; + } + + // Clause: 5.3.3.2 Session header + if (user_access_flag) { + constexpr size_t ACCESS_FIELD_HEADER_SIZE = 1; + if (buf.size() < ACCESS_FIELD_HEADER_SIZE) { + LOG_ERROR("Data group too short to store access field header ({} < {})", buf.size(), ACCESS_FIELD_HEADER_SIZE); + res.status = Status::SHORT_ACCESS_FIELD_HEADER; + return res; + } + // const uint8_t rfa = (buf[0] & 0b11100000) >> 5; + const uint8_t transport_id_flag = (buf[0] & 0b00010000) >> 4; + const uint8_t length_indicator = (buf[0] & 0b00001111) >> 0; + buf = buf.subspan(ACCESS_FIELD_HEADER_SIZE); + + if (length_indicator > buf.size()) { + LOG_ERROR("Data group too short to store user access fields ({} < {})", buf.size(), length_indicator); + res.status = Status::SHORT_ACCESS_FIELDS; + return res; + } + auto fields = buf.first(length_indicator); + buf = buf.subspan(length_indicator); + + if (transport_id_flag) { + constexpr size_t TRANSPORT_ID_SIZE = 2; + if (fields.size() < TRANSPORT_ID_SIZE) { + LOG_ERROR("User access fields too short to store transport id ({} < {})", fields.size(), TRANSPORT_ID_SIZE); + res.status = Status::SHORT_TRANSPORT_ID_FIELD; + return res; + } + const uint16_t transport_id = (fields[0] << 8) | fields[1]; + res.has_transport_id = true; + res.transport_id = transport_id; + fields = fields.subspan(TRANSPORT_ID_SIZE); + } + + res.has_user_access_fields = true; + res.user_access_fields = fields; + } + + // Clause: 5.3.3.3 MSC data group data field + constexpr size_t MAX_DATA_FIELD_SIZE = 8191; + if (buf.size() >= MAX_DATA_FIELD_SIZE) { + LOG_ERROR("Data field exceeds maximum allowed size in standard ({} > {})", buf.size(), MAX_DATA_FIELD_SIZE); + res.status = Status::OVERFLOW_MAX_DATA_FIELD_SIZE; + return res; + } + + res.status = Status::SUCCESS; + res.data_field = buf; + return res; +} \ No newline at end of file diff --git a/src/dab/msc/msc_data_group_processor.h b/src/dab/msc/msc_data_group_processor.h new file mode 100644 index 0000000..40d566f --- /dev/null +++ b/src/dab/msc/msc_data_group_processor.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include "utility/span.h" + +// Decodes the data group sent over MSC (main service component) +struct MSC_Data_Group_Process_Result { + enum class Status { + SUCCESS, + SHORT_GROUP_HEADER, + SHORT_CRC_FIELD, + CRC_INVALID, + SHORT_EXTENSION_FIELD, + SHORT_SEGMENT_FIELD, + SHORT_SESSION_HEADER, + SHORT_ACCESS_FIELD_HEADER, + SHORT_ACCESS_FIELDS, + SHORT_TRANSPORT_ID_FIELD, + OVERFLOW_MAX_DATA_FIELD_SIZE, + }; + Status status = Status::SUCCESS; + // header flags + bool has_header_fields = false; + uint8_t data_group_type = 0; + uint8_t continuity_index = 0; + uint8_t repetition_index = 0; + // crc check + bool has_crc = false; + uint16_t crc_rx = 0; + uint16_t crc_calc = 0; + // extension + bool has_extension_field = false; + uint16_t extension_field = 0; + // segment field + bool has_segment_field = false; + struct { + bool is_last_segment = false; + uint16_t segment_number = 0; + } segment_field; + // user access fields + bool has_transport_id = false; + uint16_t transport_id = 0; + bool has_user_access_fields = false; + tcb::span user_access_fields = {}; + // data fields + tcb::span data_field; +}; + +MSC_Data_Group_Process_Result MSC_Data_Group_Process(tcb::span data_group); \ No newline at end of file diff --git a/src/dab/msc/msc_data_packet_processor.cpp b/src/dab/msc/msc_data_packet_processor.cpp new file mode 100644 index 0000000..7c12e79 --- /dev/null +++ b/src/dab/msc/msc_data_packet_processor.cpp @@ -0,0 +1,177 @@ +#include "./msc_data_packet_processor.h" +#include "./msc_data_group_processor.h" +#include "../mot/MOT_processor.h" +#include "../algorithms/crc.h" +#include +#include + +#include +#include "../dab_logging.h" +#include "database/dab_database_entities.h" +#define TAG "msc-data-packet-processor" +static auto _logger = DAB_LOG_REGISTER(TAG); +#define LOG_MESSAGE(...) DAB_LOG_MESSAGE(TAG, fmt::format(__VA_ARGS__)) +#define LOG_ERROR(...) DAB_LOG_ERROR(TAG, fmt::format(__VA_ARGS__)) + +// DOC: ETSI EN 300 401 +// Clause: 5.3.2 Packet mode - network level +// Clause: 5.3.3 Packet mode - data group level + +// DOC: ETSI EN 300 401 +// Clause: 5.3.2.3 Packet CRC +static auto CRC16_CALC = []() { + // Generator polynomial for the packet crc check + // G(x) = x^16 + x^12 + x^5 + 1 + // initial = all 1s, complement = true + const uint16_t au_crc_poly = 0b0001000000100001; + auto calc = new CRC_Calculator(au_crc_poly); + calc->SetInitialValue(0xFFFF); + calc->SetFinalXORValue(0xFFFF); + return calc; +} (); + +// Table 7: First/Last flags for packet mode +enum class PacketLocation: uint8_t { + INTERMEDIATE = 0b00, + LAST = 0b01, + FIRST = 0b10, + SINGLE = 0b11, +}; + +// Table 6: Packet length +static const size_t PACKET_LENGTH[4] = { 24, 48, 72, 96 }; + +MSC_Data_Packet_Processor::MSC_Data_Packet_Processor() { + m_assembly_buffer.reserve(128); + m_mot_processor = std::make_unique(); +} + +MSC_Data_Packet_Processor::~MSC_Data_Packet_Processor() = default; + +size_t MSC_Data_Packet_Processor::ReadPacket(tcb::span buf) { + constexpr size_t PACKET_HEADER_SIZE = 3; + if (buf.size() < PACKET_HEADER_SIZE) { + LOG_ERROR("Packet is too small to fit minimum non FEC header ({} < {})", buf.size(), PACKET_HEADER_SIZE); + return buf.size(); + } + + // Figure 11: Packet structure + const uint8_t packet_length_id = (buf[0] & 0b11000000) >> 6; + const uint8_t continuity_index = (buf[0] & 0b00110000) >> 4; + const uint8_t _packet_location = (buf[0] & 0b00001100) >> 2; + const uint16_t address = (uint16_t(buf[0] & 0b00000011) << 8) | + (uint16_t(buf[1] & 0b11111111) << 0); + // const uint8_t command_flag = (buf[2] & 0b10000000) >> 7; + const uint8_t useful_data_length = (buf[2] & 0b01111111) >> 0; + + const size_t packet_length = PACKET_LENGTH[packet_length_id]; + if (buf.size() < packet_length) { + LOG_ERROR("Packet length smaller than minimum specified in headers ({} < {})", buf.size(), packet_length); + return buf.size(); + } + auto packet = buf.first(packet_length); + + constexpr size_t PACKET_CRC_SIZE = 2; + const size_t data_field_length = packet.size()-PACKET_CRC_SIZE-PACKET_HEADER_SIZE; + if (data_field_length < useful_data_length) { + LOG_ERROR("Packet data field length ({}) is smaller than specified useful length in headers ({})", data_field_length, useful_data_length); + return buf.size(); + } + + const auto crc_buf = packet.last(PACKET_CRC_SIZE); + const auto crc_data = packet.first(PACKET_HEADER_SIZE + data_field_length); + const uint16_t crc_rx = (crc_buf[0] << 8) | crc_buf[1]; + const uint16_t crc_pred = CRC16_CALC->Process(crc_data); + const bool is_crc_valid = (crc_rx == crc_pred); + if (!is_crc_valid) { + LOG_MESSAGE("[crc16] is_match={} crc_pred={:04X} crc_rx={:04X}", is_crc_valid, crc_pred, crc_rx); + return packet_length; + } + + const auto data_field = packet.subspan(PACKET_HEADER_SIZE, useful_data_length); + const auto packet_location = static_cast(_packet_location); + + // Determine if we should scratch current assembly + const uint8_t expected_continuity_index = (m_last_continuity_index+1) % 4; // mod4 counter + m_last_continuity_index = continuity_index; + + const bool is_restart = [&]() -> bool { + if (!m_last_address.has_value()) return false; + if (m_last_address.value() != address) return true; + if (expected_continuity_index != continuity_index) return true; + if (packet_location == PacketLocation::FIRST) return true; // missed last packet + if (packet_location == PacketLocation::SINGLE) return true; // interrupted + return false; + } (); + + if (is_restart) ResetAssembler(); + + switch (packet_location) { + case PacketLocation::SINGLE: + HandleDataGroup(data_field); + break; + case PacketLocation::FIRST: + m_last_address = std::optional(address); + PushPiece(data_field); + break; + case PacketLocation::INTERMEDIATE: + PushPiece(data_field); + break; + case PacketLocation::LAST: + PushPiece(data_field); + HandleDataGroup(m_assembly_buffer); + ResetAssembler(); + break; + } + + return packet_length; +} + +void MSC_Data_Packet_Processor::PushPiece(tcb::span piece) { + const size_t old_size = m_assembly_buffer.size(); + const size_t new_size = old_size + piece.size(); + m_assembly_buffer.resize(new_size); + for (size_t i = 0; i < piece.size(); i++) { + size_t j = i + old_size; + m_assembly_buffer[j] = piece[i]; + } + m_total_packets++; +} + +void MSC_Data_Packet_Processor::ResetAssembler() { + m_last_address = std::nullopt; + m_total_packets = 0; + m_assembly_buffer.resize(0); +} + +void MSC_Data_Packet_Processor::HandleDataGroup(tcb::span data_group) { + const auto res = MSC_Data_Group_Process(data_group); + using Status = MSC_Data_Group_Process_Result::Status; + if (res.status != Status::SUCCESS) { + return; + } + + // DOC: ETSI EN 300 401 + // Clause 5.3.3.1 - MSC data group header + // Depending on what the MSC data group is used for the header might have certain fields + // For a MOT (multimedia object transfer) transported via XPAD we need the following: + // 1. Segment number - So we can reassemble the MOT object + if (!res.has_segment_field) { + LOG_ERROR("Missing segment field in MSC XPAD header"); + return; + } + // 2. Transport id - So we can identify if a new MOT object is being transmitted + if (!res.has_transport_id) { + LOG_ERROR("Missing transport if field in MSC XPAD header"); + return; + } + + MOT_MSC_Data_Group_Header header; + header.data_group_type = static_cast(res.data_group_type); + header.continuity_index = res.continuity_index; + header.repetition_index = res.repetition_index; + header.is_last_segment = res.segment_field.is_last_segment; + header.segment_number = res.segment_field.segment_number; + header.transport_id = res.transport_id; + m_mot_processor->Process_Segment(header, res.data_field); +} diff --git a/src/dab/msc/msc_data_packet_processor.h b/src/dab/msc/msc_data_packet_processor.h new file mode 100644 index 0000000..ec5264c --- /dev/null +++ b/src/dab/msc/msc_data_packet_processor.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "utility/span.h" + +class MOT_Processor; + +class MSC_Data_Packet_Processor +{ +private: + std::optional m_last_address = std::nullopt; + uint8_t m_last_continuity_index = 0; + size_t m_total_packets = 0; + std::vector m_assembly_buffer; + std::unique_ptr m_mot_processor; +public: + MSC_Data_Packet_Processor(); + ~MSC_Data_Packet_Processor(); + size_t ReadPacket(tcb::span buf); + MOT_Processor& Get_MOT_Processor() const { return *m_mot_processor; } +private: + void PushPiece(tcb::span piece); + void ResetAssembler(); + void HandleDataGroup(tcb::span data_group); +}; + diff --git a/src/dab/msc/msc_reed_solomon_data_packet_processor.cpp b/src/dab/msc/msc_reed_solomon_data_packet_processor.cpp new file mode 100644 index 0000000..f72a465 --- /dev/null +++ b/src/dab/msc/msc_reed_solomon_data_packet_processor.cpp @@ -0,0 +1,255 @@ +#include "./msc_reed_solomon_data_packet_processor.h" +#include "../algorithms/reed_solomon_decoder.h" +#include + +#include +#include "../dab_logging.h" +#include "database/dab_database_entities.h" +#define TAG "msc-reed-solomon-data-packet-processor" +static auto _logger = DAB_LOG_REGISTER(TAG); +#define LOG_MESSAGE(...) DAB_LOG_MESSAGE(TAG, fmt::format(__VA_ARGS__)) +#define LOG_ERROR(...) DAB_LOG_ERROR(TAG, fmt::format(__VA_ARGS__)) + +// ETSI EN 300 401 +// Clause: 5.3.5 FEC for MSC packet mode +// Table 6: Packet length +static const size_t PACKET_LENGTH[4] = { 24, 48, 72, 96 }; +// Figure 15: Structure of FEC frame +static constexpr size_t RS_DATA_BYTES = 188; +static constexpr size_t RS_PARITY_BYTES = 16; +static constexpr size_t RS_MESSAGE_BYTES = RS_DATA_BYTES + RS_PARITY_BYTES; +static constexpr size_t RS_TOTAL_ROWS = 12; +// We pad the RS(204,188) code to RS(255,239) by adding zero symbols to the left of the message +static constexpr size_t RS_PADDING_BYTES = 255 - RS_MESSAGE_BYTES; +// Clause: 5.3.5.2 Transport of RS data +static constexpr size_t APPLICATION_DATA_TABLE_SIZE = 2256; +static_assert(RS_DATA_BYTES*RS_TOTAL_ROWS == APPLICATION_DATA_TABLE_SIZE); + +static constexpr size_t RS_DATA_TABLE_SIZE = 192; +static constexpr size_t FEC_PACKET_LENGTH = 24; +static constexpr size_t TOTAL_FEC_PACKETS = 9; +static constexpr size_t FEC_PACKET_HEADER_SIZE = 2; +static constexpr size_t FEC_PACKET_DATA_FIELD_SIZE = FEC_PACKET_LENGTH-FEC_PACKET_HEADER_SIZE; +static constexpr size_t FEC_PACKET_PADDING_SIZE = 6; +static constexpr size_t TOTAL_RING_BUFFER_SIZE = APPLICATION_DATA_TABLE_SIZE + FEC_PACKET_LENGTH*TOTAL_FEC_PACKETS; +static_assert(RS_DATA_TABLE_SIZE == (FEC_PACKET_DATA_FIELD_SIZE*TOTAL_FEC_PACKETS - FEC_PACKET_PADDING_SIZE)); + +MSC_Reed_Solomon_Data_Packet_Processor::MSC_Reed_Solomon_Data_Packet_Processor() { + m_rs_encoded_buf.resize(RS_MESSAGE_BYTES); + m_rs_error_positions.resize(RS_PARITY_BYTES); + m_rs_data_table.resize(RS_DATA_TABLE_SIZE); + m_ring_buf.resize(TOTAL_RING_BUFFER_SIZE); + // ETSI EN 300 401 + // Clause: 5.3.5.1 FEC frame + // P(x) = x^8 + x^4 + x^3 + x^2 + 1 + constexpr int GALOIS_FIELD_POLY = 0b100011101; + // G(x) = (x+λ^0)*(x+λ^1)*...*(x+λ^15) + constexpr int CODE_TOTAL_ROOTS = 16; // same as number of rs parity bits + // We pad the RS(204,188) code to RS(255,239) by adding zero symbols to the left of the message + m_rs_decoder = std::make_unique(8, GALOIS_FIELD_POLY, 0, 1, CODE_TOTAL_ROOTS, int(RS_PADDING_BYTES)); +} + +MSC_Reed_Solomon_Data_Packet_Processor::~MSC_Reed_Solomon_Data_Packet_Processor() = default; + +size_t MSC_Reed_Solomon_Data_Packet_Processor::ReadPacket(tcb::span buf) { + if (buf.size() < FEC_PACKET_HEADER_SIZE) { + LOG_ERROR("Packet is too small to fit minimum FEC header ({} < {})", buf.size(), FEC_PACKET_HEADER_SIZE); + return buf.size(); + } + + // Figure 11: Packet structure + uint8_t packet_length_id = (buf[0] & 0b11000000) >> 6; + const uint8_t counter = (buf[0] & 0b00111100) >> 2; + const uint16_t address = (uint16_t(buf[0] & 0b00000011) << 8) | + (uint16_t(buf[1] & 0b11111111) << 0); + + // Clause: 5.3.5.2 Transport of RS data + constexpr uint16_t FEC_ADDRESS = 0b11'1111'1110; + const bool is_fec_packet = (FEC_ADDRESS == address); + if (is_fec_packet) { + // Ignore provided packet length since that may be incorrect/corrupted + packet_length_id = 0b00; + } + + const size_t packet_length = PACKET_LENGTH[packet_length_id]; + if (buf.size() < packet_length) { + LOG_ERROR("Packet length smaller than minimum specified in headers ({} < {})", buf.size(), packet_length); + return buf.size(); + } + + auto packet = buf.first(packet_length); + PushIntoRingBuffer(packet, packet_length_id); + if (!is_fec_packet) { + return packet_length; + } + + const bool is_fec_invalid = [&]() -> bool {; + // Clause 5.3.5.2 Transport of RS data + // FEC packets count from 0 to 8 (inclusive) + if (m_last_counter.has_value()) { + const uint8_t expected_counter = m_last_counter.value() + 1; + return expected_counter != counter; + } else { + return (counter != 0); + } + } (); + + // Eject all packets and skip correction + if (is_fec_invalid) { + m_last_counter = std::nullopt; + ClearRingBuffer(); + return packet_length; + } + + m_last_counter = std::optional(counter); + if (counter != uint8_t(TOTAL_FEC_PACKETS-1)) { + return packet_length; + } + + // All FEC packets are stored check if we can perform decoding + if (m_ring_size != TOTAL_RING_BUFFER_SIZE) { + ClearRingBuffer(); + } else { + PerformReedSolomonCorrection(); + } + m_last_counter = std::nullopt; + ResetRingBuffer(); + return packet_length; +} + +void MSC_Reed_Solomon_Data_Packet_Processor::ClearRingBuffer() { + while (true) { + auto stored_packet = PopRingBuffer(); + if (stored_packet.empty()) break; + if (m_callback != nullptr) m_callback(stored_packet, false); + } + assert(m_ring_size == 0); +} + +void MSC_Reed_Solomon_Data_Packet_Processor::PushIntoRingBuffer(tcb::span packet, uint8_t packet_length_id) { + const size_t packet_length = PACKET_LENGTH[packet_length_id]; + assert(packet.size() == packet_length); + // Free up last packet/s to make room for new packet + while (true) { + const size_t total_free = m_ring_buf.size() - m_ring_size; + if (total_free >= packet_length) break; + const uint8_t other_header = m_ring_buf[m_ring_read_head]; + const uint8_t other_packet_length_id = (other_header & 0b11000000) >> 6; + const size_t other_packet_length = PACKET_LENGTH[other_packet_length_id]; + assert(m_ring_size >= other_packet_length); + m_ring_size -= other_packet_length; + m_ring_read_head = (m_ring_read_head+other_packet_length) % m_ring_buf.size(); + m_ring_total_bytes_discarded += other_packet_length; + m_ring_total_packets_discarded++; + } + + // override with correct packet length if we want to ignore the source which can be corrupted + uint8_t header = packet[0]; + header = (header & 0b00111111) | ((packet_length_id & 0b11) << 6); + m_ring_buf[m_ring_write_head] = header; + for (size_t i = 1; i < packet_length; i++) { + const size_t j = (m_ring_write_head+i) % m_ring_buf.size(); + m_ring_buf[j] = packet[i]; + } + m_ring_size += packet_length; + m_ring_write_head = (m_ring_write_head+packet_length) % m_ring_buf.size(); +} + +tcb::span MSC_Reed_Solomon_Data_Packet_Processor::PopRingBuffer() { + if (m_ring_size == 0) return {}; + const uint8_t header = m_ring_buf[m_ring_read_head]; + const uint8_t packet_length_id = (header & 0b11000000) >> 6; + const size_t packet_length = PACKET_LENGTH[packet_length_id]; + assert(m_ring_size >= packet_length); + + m_pop_buf.resize(packet_length); + for (size_t i = 0; i < packet_length; i++) { + const size_t j = (m_ring_read_head+i) % m_ring_buf.size(); + m_pop_buf[i] = m_ring_buf[j]; + } + m_ring_size -= packet_length; + m_ring_read_head = (m_ring_read_head+packet_length) % m_ring_buf.size(); + return m_pop_buf; +} + +void MSC_Reed_Solomon_Data_Packet_Processor::ResetRingBuffer() { + m_ring_read_head = 0; + m_ring_write_head = 0; + m_ring_size = 0; + m_ring_total_bytes_discarded = 0; + m_ring_total_packets_discarded = 0; +} + +void MSC_Reed_Solomon_Data_Packet_Processor::PerformReedSolomonCorrection() { + assert(m_ring_size == TOTAL_RING_BUFFER_SIZE); + + // Figure 17: Complete FEC packet set + for (size_t i = 0; i < TOTAL_FEC_PACKETS; i++) { + // Remove header from FEC packets + const size_t i_ring = m_ring_read_head + APPLICATION_DATA_TABLE_SIZE + i*FEC_PACKET_LENGTH + FEC_PACKET_HEADER_SIZE; + const size_t i_table = i*FEC_PACKET_DATA_FIELD_SIZE; + + size_t data_field_size = FEC_PACKET_DATA_FIELD_SIZE; + // Figure 17: Complete FEC packet set + // Last FEC packet has 6 padding bytes which we ignore + if (i == (TOTAL_FEC_PACKETS-1)) { + constexpr size_t TOTAL_PADDING_BYTES = 6; + data_field_size = (FEC_PACKET_DATA_FIELD_SIZE-TOTAL_PADDING_BYTES); + } + for (size_t j = 0; j < data_field_size; j++) { + const size_t j_ring = (i_ring+j) % m_ring_buf.size(); + m_rs_data_table[i_table+j] = m_ring_buf[j_ring]; + } + } + + // Figure 15: Structure of FEC frame + for (size_t y = 0; y < RS_TOTAL_ROWS; y++) { + // Read application data table (transform from ring row-wise to table column-wise) + for (size_t i = 0; i < RS_DATA_BYTES; i++) { + const size_t x = i; + const size_t i_offset = i*RS_TOTAL_ROWS + y; + const size_t i_ring = (m_ring_read_head + i_offset) % m_ring_buf.size(); + m_rs_encoded_buf[x] = m_ring_buf[i_ring]; + } + // Read rs data table (transform from ring row-wise to table column-wise) + for (size_t i = 0; i < RS_PARITY_BYTES; i++) { + const size_t x = RS_DATA_BYTES + i; + const size_t i_offset = i*RS_TOTAL_ROWS + y; + m_rs_encoded_buf[x] = m_rs_data_table[i_offset]; + } + + const int error_count = m_rs_decoder->Decode(m_rs_encoded_buf.data(), m_rs_error_positions.data(), 0); + LOG_MESSAGE("[reed-solomon] row={}/{} error_count={}", y, RS_TOTAL_ROWS, error_count); + // rs decoder returns -1 to indicate too many errors + if (error_count < 0) { + LOG_ERROR("[reed-solomon] Too many errors to correct"); + continue; + } + // correct any errors + for (int i = 0; i < error_count; i++) { + // NOTE: Phil Karn's reed solmon decoder returns the position of errors + // with the amount of padding added onto it + const int x_err = m_rs_error_positions[i] - int(RS_PADDING_BYTES); + if (x_err < 0) { + LOG_ERROR("[reed-solomon] Got a negative error index={}, row={}/{}", x_err, y, RS_TOTAL_ROWS); + continue; + } + // correct data packets + if (x_err < int(RS_DATA_BYTES)) { + const size_t i_offset = size_t(x_err)*RS_TOTAL_ROWS + y; + const size_t i_ring = (m_ring_read_head + i_offset) % m_ring_buf.size(); + m_ring_buf[i_ring] = m_rs_encoded_buf[size_t(x_err)]; + } + // dont correct fec packets they aren't used for anything else + } + } + + size_t total_read = 0; + while (total_read < APPLICATION_DATA_TABLE_SIZE) { + auto buf = PopRingBuffer(); + if (buf.empty()) break; + if (m_callback != nullptr) m_callback(buf, true); + total_read += buf.size(); + } + assert(total_read == APPLICATION_DATA_TABLE_SIZE); +} diff --git a/src/dab/msc/msc_reed_solomon_data_packet_processor.h b/src/dab/msc/msc_reed_solomon_data_packet_processor.h new file mode 100644 index 0000000..7014551 --- /dev/null +++ b/src/dab/msc/msc_reed_solomon_data_packet_processor.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "utility/span.h" + +class Reed_Solomon_Decoder; + +class MSC_Reed_Solomon_Data_Packet_Processor +{ +public: + // packet, is_corrected + using Callback = std::function, bool)>; +private: + std::vector m_rs_encoded_buf; + std::vector m_rs_error_positions; + std::vector m_rs_data_table; + std::vector m_pop_buf; + std::vector m_ring_buf; + size_t m_ring_read_head = 0; + size_t m_ring_write_head = 0; + size_t m_ring_size = 0; + size_t m_ring_total_bytes_discarded = 0; + size_t m_ring_total_packets_discarded = 0; + std::optional m_last_counter = std::nullopt; + Callback m_callback = nullptr; + std::unique_ptr m_rs_decoder; +public: + MSC_Reed_Solomon_Data_Packet_Processor(); + ~MSC_Reed_Solomon_Data_Packet_Processor(); + size_t ReadPacket(tcb::span buf); + void SetCallback(const Callback& callback) { m_callback = callback; } + void SetCallback(Callback&& callback) { m_callback = std::move(callback); } +private: + void PushIntoRingBuffer(tcb::span buf, uint8_t packet_length_id); + tcb::span PopRingBuffer(); + void ClearRingBuffer(); + void PerformReedSolomonCorrection(); + void ResetRingBuffer(); +}; diff --git a/src/dab/msc/msc_xpad_processor.cpp b/src/dab/msc/msc_xpad_processor.cpp deleted file mode 100644 index 181372e..0000000 --- a/src/dab/msc/msc_xpad_processor.cpp +++ /dev/null @@ -1,193 +0,0 @@ -#include "./msc_xpad_processor.h" -#include "../algorithms/crc.h" -#include - -#include "../dab_logging.h" -#define TAG "msc-xpad-processor" -static auto _logger = DAB_LOG_REGISTER(TAG); -#define LOG_MESSAGE(...) DAB_LOG_MESSAGE(TAG, fmt::format(__VA_ARGS__)) -#define LOG_ERROR(...) DAB_LOG_ERROR(TAG, fmt::format(__VA_ARGS__)) - -static auto Generate_CRC_Calc() { - // DOC: ETSI EN 300 401 - // Clause 5.3.3.4 - MSC data group CRC - // CRC16 Polynomial is given by: - // G(x) = x^16 + x^12 + x^5 + 1 - // POLY = 0b 0001 0000 0010 0001 = 0x1021 - static const uint16_t crc16_poly = 0x1021; - static auto crc16_calc = new CRC_Calculator(crc16_poly); - crc16_calc->SetInitialValue(0xFFFF); // initial value all 1s - crc16_calc->SetFinalXORValue(0xFFFF); // transmitted crc is 1s complemented - - return crc16_calc; -}; - -static auto CRC16_CALC = Generate_CRC_Calc(); - -MSC_XPAD_Processor::ProcessResult MSC_XPAD_Processor::Process(tcb::span buf) { - const int N = (int)buf.size(); - - // DOC: ETSI EN 300 401 - // Clause 5.3.3 - Packet mode - Data group level - // Figure 12 - Structure of MSC data group - ProcessResult res; - res.is_success = false; - - int curr_byte = 0; - int nb_remain = N-curr_byte; - const uint8_t* data = NULL; - - // Part 1: (required) Data group header - const int MIN_DATA_GROUP_HEADER_BYTES = 2; - if (nb_remain < MIN_DATA_GROUP_HEADER_BYTES) { - LOG_ERROR("Insufficient length for min data group header {}<{}", - nb_remain, MIN_DATA_GROUP_HEADER_BYTES); - return res; - } - - data = &buf[curr_byte]; - curr_byte += MIN_DATA_GROUP_HEADER_BYTES; - nb_remain = N-curr_byte; - const uint8_t extension_flag = (data[0] & 0b10000000) >> 7; - const uint8_t crc_flag = (data[0] & 0b01000000) >> 6; - const uint8_t segment_flag = (data[0] & 0b00100000) >> 5; - const uint8_t user_access_flag = (data[0] & 0b00010000) >> 4; - const uint8_t data_group_type = (data[0] & 0b00001111) >> 0; - const uint8_t continuity_index = (data[1] & 0b11110000) >> 4; - const uint8_t repetition_index = (data[1] & 0b00001111) >> 0; - - res.data_group_type = data_group_type; - res.continuity_index = continuity_index; - res.repetition_index = repetition_index; - - // Part 1.1: (optional) Extension field is used to carry CA information - const int TOTAL_EXTENSION_FIELD_BYTES = 2; - if (extension_flag) { - if (nb_remain < TOTAL_EXTENSION_FIELD_BYTES) { - LOG_ERROR("Insufficient length for extended data group header {}<{}", - nb_remain, TOTAL_EXTENSION_FIELD_BYTES); - return res; - } - - data = &buf[curr_byte]; - curr_byte += TOTAL_EXTENSION_FIELD_BYTES; - nb_remain = N-curr_byte; - uint16_t extension_field = (data[0] << 8) | data[1]; - - // DOC: ETSI TS 102 367 - // This field is used for conditional access - res.has_extension_field = true; - res.extension_field = extension_field; - } - - // Part 2: Session header - // Part 2.1: (optional) Segment field - const int MIN_SEGMENT_FIELD_BYTES = 2; - if (segment_flag) { - if (nb_remain < MIN_SEGMENT_FIELD_BYTES) { - LOG_ERROR("Insufficient length for min session header {}<{}", - nb_remain, MIN_SEGMENT_FIELD_BYTES); - return res; - } - - data = &buf[curr_byte]; - curr_byte += MIN_SEGMENT_FIELD_BYTES; - nb_remain = N-curr_byte; - const uint8_t last_flag = (data[0] & 0b10000000) >> 7; - const uint16_t segment_number = ((data[0] & 0b01111111) << 8) | - ((data[1] & 0b11111111) >> 0); - res.has_segment_field = true; - res.segment_field.is_last_segment = last_flag; - res.segment_field.segment_number = segment_number; - } - - // Part 2.2: (optional) User access field - const int MIN_USER_ACCESS_FIELD_BYTES = 1; - if (user_access_flag) { - if (nb_remain < MIN_USER_ACCESS_FIELD_BYTES) { - LOG_ERROR("Insufficient length for min user access field {}<{}", - nb_remain, MIN_USER_ACCESS_FIELD_BYTES); - return res; - } - - data = &buf[curr_byte]; - curr_byte += MIN_USER_ACCESS_FIELD_BYTES; - nb_remain = N-curr_byte; - // const uint8_t rfa0 = (data[0] & 0b11100000) >> 5; - const uint8_t transport_id_flag = (data[0] & 0b00010000) >> 4; - const uint8_t length_indicator = (data[0] & 0b00001111) >> 0; - - res.has_user_access_field = true; - - // Part 2.2.1: (optional) Transport id field - const int TOTAL_TRANSPORT_ID_BYTES = transport_id_flag ? 2 : 0; - if (nb_remain < TOTAL_TRANSPORT_ID_BYTES) { - LOG_ERROR("Insufficient length for transport id {}<{}", - nb_remain, TOTAL_TRANSPORT_ID_BYTES); - return res; - } - - if (transport_id_flag) { - data = &buf[curr_byte]; - curr_byte += TOTAL_TRANSPORT_ID_BYTES; - nb_remain = N-curr_byte; - const uint16_t transport_id = (data[0] << 8) | data[1]; - - res.user_access_field.has_transport_id = true; - res.user_access_field.transport_id = transport_id; - } - - // Part 2.2.2: (required) End user address field - const int nb_end_user_address_bytes = (int)(length_indicator)-TOTAL_TRANSPORT_ID_BYTES; - if (nb_remain < nb_end_user_address_bytes) { - LOG_ERROR("Insufficient length for end user address field by indicated length {}<{}", - nb_remain, nb_end_user_address_bytes); - return res; - } - - auto* end_user_address = &buf[curr_byte]; - curr_byte += nb_end_user_address_bytes; - nb_remain = N-curr_byte; - - res.user_access_field.end_address = end_user_address; - res.user_access_field.nb_end_address_bytes = nb_end_user_address_bytes; - } - - // Part 3: (required) Data group data field - auto* data_field = &buf[curr_byte]; - const int TOTAL_CRC16_BYTES = 2; - const int nb_data_bytes = crc_flag ? (nb_remain-TOTAL_CRC16_BYTES) : nb_remain; - if (nb_data_bytes < 0) { - LOG_ERROR("Insufficient length for data field where CRC?={} {}<{}", - crc_flag, nb_data_bytes, 0); - return res; - } - - // Part 3.1: (optional) CRC16 on entire buffer - if (crc_flag) { - const uint16_t crc16_rx = (buf[N-2] << 8) | buf[N-1]; - const uint16_t crc16_calc = CRC16_CALC->Process({buf.data(), (size_t)(N-TOTAL_CRC16_BYTES)}); - const bool is_valid = (crc16_rx == crc16_calc); - if (!is_valid) { - LOG_ERROR("CRC mismatch {:04X}!={:04X}", crc16_rx, crc16_calc); - return res; - } - } - - res.data_field = {data_field, (size_t)nb_data_bytes}; - res.is_success = true; - - LOG_MESSAGE("type={} cont={:>2} rep={} " - "ext?={} ext={:>2} " - "seg?={} last={} segnum={} " - "tid?={} tid={:>4} " - "user_access?={} nb_end_addr={} crc?={} nb_data={:>4d}", - res.data_group_type, res.continuity_index, res.repetition_index, - res.has_extension_field, res.extension_field, - res.has_segment_field, res.segment_field.is_last_segment, res.segment_field.segment_number, - res.user_access_field.has_transport_id, res.user_access_field.transport_id, - res.has_user_access_field, res.user_access_field.nb_end_address_bytes, - crc_flag, res.data_field.size()); - - return res; -} \ No newline at end of file diff --git a/src/dab/msc/msc_xpad_processor.h b/src/dab/msc/msc_xpad_processor.h deleted file mode 100644 index 1edc1de..0000000 --- a/src/dab/msc/msc_xpad_processor.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include -#include "utility/span.h" - -// Decodes the XPAD field sent over MSC (main service component) -class MSC_XPAD_Processor -{ -public: - struct ProcessResult { - bool is_success = false; - - uint8_t data_group_type = 0; - uint8_t continuity_index = 0; - uint8_t repetition_index = 0; - - bool has_extension_field = false; - uint16_t extension_field = 0; - - bool has_segment_field = false; - struct { - bool is_last_segment = false; - uint16_t segment_number = 0; - } segment_field; - - bool has_user_access_field = false; - struct { - bool has_transport_id = false; - uint16_t transport_id = 0; - - const uint8_t* end_address = NULL; - int nb_end_address_bytes = 0; - } user_access_field; - - - tcb::span data_field; - }; -public: - MSC_XPAD_Processor() = delete; - static ProcessResult Process(tcb::span buf); -}; \ No newline at end of file diff --git a/src/dab/pad/pad_MOT_processor.cpp b/src/dab/pad/pad_MOT_processor.cpp index 938e07b..db3d959 100644 --- a/src/dab/pad/pad_MOT_processor.cpp +++ b/src/dab/pad/pad_MOT_processor.cpp @@ -1,5 +1,5 @@ #include "./pad_MOT_processor.h" -#include "../msc/msc_xpad_processor.h" +#include "../msc/msc_data_group_processor.h" #include "../mot/MOT_processor.h" #include @@ -111,8 +111,9 @@ size_t PAD_MOT_Processor::Consume( void PAD_MOT_Processor::Interpret(void) { const auto buf = m_data_group.GetData(); const size_t N = m_data_group.GetRequiredBytes(); - const auto res = MSC_XPAD_Processor::Process({buf.data(), N}); - if (!res.is_success) { + const auto res = MSC_Data_Group_Process({buf.data(), N}); + using Status = MSC_Data_Group_Process_Result::Status; + if (res.status != Status::SUCCESS) { return; } @@ -126,7 +127,7 @@ void PAD_MOT_Processor::Interpret(void) { return; } // 2. Transport id - So we can identify if a new MOT object is being transmitted - if (!res.has_user_access_field || !res.user_access_field.has_transport_id) { + if (!res.has_transport_id) { LOG_ERROR("Missing transport if field in MSC XPAD header"); return; } @@ -137,6 +138,6 @@ void PAD_MOT_Processor::Interpret(void) { header.repetition_index = res.repetition_index; header.is_last_segment = res.segment_field.is_last_segment; header.segment_number = res.segment_field.segment_number; - header.transport_id = res.user_access_field.transport_id; + header.transport_id = res.transport_id; m_mot_processor->Process_Segment(header, res.data_field); } \ No newline at end of file diff --git a/src/dab/radio_fig_handler.cpp b/src/dab/radio_fig_handler.cpp index 5d555db..6b43283 100644 --- a/src/dab/radio_fig_handler.cpp +++ b/src/dab/radio_fig_handler.cpp @@ -6,6 +6,7 @@ #include #include "./dab_logging.h" +#include "database/dab_database_entities.h" #define TAG "radio-fig-handler" static auto _logger = DAB_LOG_REGISTER(TAG); #define LOG_MESSAGE(...) DAB_LOG_MESSAGE(TAG, fmt::format(__VA_ARGS__)) @@ -482,7 +483,8 @@ void Radio_FIG_Handler::OnSubchannel_2_FEC( { if (!m_updater) return; auto& u = m_updater->GetSubchannelUpdater(subchannel_id); - u.SetFECScheme(fec_type); + auto fec_scheme = static_cast(fec_type & 0b11); + u.SetFECScheme(fec_scheme); } // fig 0/17 - Programme type