/*
Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty
the Queen in Right of Canada (Communications Research Center Canada)
Most parts of this file are taken from dablin,
Copyright (C) 2015-2022 Stefan Pöschel
Copyright (C) 2023
Matthias P. Braendli, matthias.braendli@mpb.li
http://opendigitalradio.org
*/
/*
This file is part of ODR-DabMod.
ODR-DabMod is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
ODR-DabMod is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with ODR-DabMod. If not, see .
*/
#include "FigParser.h"
#include "PcDebug.h"
#include "Log.h"
#include "crc.h"
#include "CharsetTools.h"
#include
#include
#include
#include
#include
template
static uint16_t read_16b(T buf)
{
uint16_t value = 0;
value = (uint16_t)(buf[0]) << 8;
value |= (uint16_t)(buf[1]);
return value;
}
static bool checkCRC(const uint8_t *buf, size_t size)
{
const uint16_t crc_from_packet = read_16b(buf + size - 2);
uint16_t crc_calc = 0xffff;
crc_calc = crc16(crc_calc, buf, size - 2);
crc_calc ^= 0xffff;
return crc_from_packet == crc_calc;
}
void FICDecoderObserver::FICChangeEnsemble(const FIC_ENSEMBLE& e)
{
if (ensemble.has_value() and e.eid == ensemble->eid and e.ecc == ensemble->ecc) {
return;
}
services.clear();
ensemble = e;
}
void FICDecoderObserver::FICChangeService(const LISTED_SERVICE& ls)
{
services[ls.sid] = ls;
}
void FICDecoderObserver::FICChangeUTCDateTime(const FIC_DAB_DT& dt)
{
utc_dt = dt;
}
// --- FICDecoder -----------------------------------------------------------------
FICDecoder::FICDecoder(bool verbose) :
verbose(verbose),
utc_dt_long(false)
{ }
void FICDecoder::Reset() {
ensemble = FIC_ENSEMBLE();
services.clear();
subchannels.clear();
utc_dt = FIC_DAB_DT();
}
void FICDecoder::Process(const uint8_t *data, size_t len) {
// check for integer FIB count
if(len % 32) {
etiLog.log(warn, "FICDecoder: Ignoring non-integer FIB count FIC data with %zu bytes\n", len);
return;
}
for(size_t i = 0; i < len; i += 32)
ProcessFIB(data + i);
}
void FICDecoder::ProcessFIB(const uint8_t *data) {
if (not checkCRC(data, 32)) {
observer.FICDiscardedFIB();
return;
}
// iterate over all FIGs
for(size_t offset = 0; offset < 30 && data[offset] != 0xFF;) {
int type = data[offset] >> 5;
size_t len = data[offset] & 0x1F;
offset++;
switch(type) {
case 0:
ProcessFIG0(data + offset, len);
break;
case 1:
ProcessFIG1(data + offset, len);
break;
// default:
// etiLog.log(warn, "FICDecoder: received unsupported FIG %d with %zu bytes\n", type, len);
}
offset += len;
}
}
void FICDecoder::ProcessFIG0(const uint8_t *data, size_t len) {
if(len < 1) {
etiLog.log(warn, "FICDecoder: received empty FIG 0\n");
return;
}
// read/skip FIG 0 header
FIG0_HEADER header(data[0]);
data++;
len--;
// ignore next config/other ensembles/data services
if(header.cn || header.oe || header.pd)
return;
// handle extension
switch(header.extension) {
case 0:
ProcessFIG0_0(data, len);
break;
case 1:
ProcessFIG0_1(data, len);
break;
case 2:
ProcessFIG0_2(data, len);
break;
case 5:
ProcessFIG0_5(data, len);
break;
case 8:
ProcessFIG0_8(data, len);
break;
case 9:
ProcessFIG0_9(data, len);
break;
case 10:
ProcessFIG0_10(data, len);
break;
case 13:
ProcessFIG0_13(data, len);
break;
case 17:
ProcessFIG0_17(data, len);
break;
case 18:
ProcessFIG0_18(data, len);
break;
case 19:
ProcessFIG0_19(data, len);
break;
// default:
// etiLog.log(warn, "FICDecoder: received unsupported FIG 0/%d with %zu field bytes\n", header.extension, len);
}
}
void FICDecoder::ProcessFIG0_0(const uint8_t *data, size_t len) {
// FIG 0/0 - Ensemble information
// EId and alarm flag only
if(len < 4)
return;
FIC_ENSEMBLE new_ensemble = ensemble;
new_ensemble.eid = data[0] << 8 | data[1];
new_ensemble.al_flag = data[2] & 0x20;
if(ensemble != new_ensemble) {
ensemble = new_ensemble;
if (verbose)
etiLog.log(debug, "FICDecoder: EId 0x%04X: alarm flag: %s\n",
ensemble.eid, ensemble.al_flag ? "true" : "false");
UpdateEnsemble();
}
}
void FICDecoder::ProcessFIG0_1(const uint8_t *data, size_t len) {
// FIG 0/1 - Basic sub-channel organization
// iterate through all sub-channels
for(size_t offset = 0; offset < len;) {
int subchid = data[offset] >> 2;
size_t start_address = (data[offset] & 0x03) << 8 | data[offset + 1];
offset += 2;
FIC_SUBCHANNEL sc;
sc.start = start_address;
bool short_long_form = data[offset] & 0x80;
if(short_long_form) {
// long form
int option = (data[offset] & 0x70) >> 4;
int pl = (data[offset] & 0x0C) >> 2;
size_t subch_size = (data[offset] & 0x03) << 8 | data[offset + 1];
switch(option) {
case 0b000:
sc.size = subch_size;
sc.pl = "EEP " + std::to_string(pl + 1) + "-A";
sc.bitrate = subch_size / eep_a_size_factors[pl] * 8;
break;
case 0b001:
sc.size = subch_size;
sc.pl = "EEP " + std::to_string(pl + 1) + "-B";
sc.bitrate = subch_size / eep_b_size_factors[pl] * 32;
break;
}
offset += 2;
} else {
// short form
bool table_switch = data[offset] & 0x40;
if(!table_switch) {
int table_index = data[offset] & 0x3F;
sc.size = uep_sizes[table_index];
sc.pl = "UEP " + std::to_string(uep_pls[table_index]);
sc.bitrate = uep_bitrates[table_index];
}
offset++;
}
if(!sc.IsNone()) {
FIC_SUBCHANNEL& current_sc = GetSubchannel(subchid);
sc.language = current_sc.language; // ignored for comparison
if(current_sc != sc) {
current_sc = sc;
if (verbose)
etiLog.log(debug, "FICDecoder: SubChId %2d: start %3zu CUs, size %3zu CUs, PL %-7s = %3d kBit/s\n", subchid, sc.start, sc.size, sc.pl.c_str(), sc.bitrate);
UpdateSubchannel(subchid);
}
}
}
}
void FICDecoder::ProcessFIG0_2(const uint8_t *data, size_t len) {
// FIG 0/2 - Basic service and service component definition
// programme services only
// iterate through all services
for(size_t offset = 0; offset < len;) {
uint16_t sid = data[offset] << 8 | data[offset + 1];
offset += 2;
size_t num_service_comps = data[offset++] & 0x0F;
// iterate through all service components
for(size_t comp = 0; comp < num_service_comps; comp++) {
int tmid = data[offset] >> 6;
switch(tmid) {
case 0b00: // MSC stream audio
int ascty = data[offset] & 0x3F;
int subchid = data[offset + 1] >> 2;
bool ps = data[offset + 1] & 0x02;
bool ca = data[offset + 1] & 0x01;
if(!ca) {
switch(ascty) {
case 0: // DAB
case 63: // DAB+
bool dab_plus = ascty == 63;
AUDIO_SERVICE audio_service(subchid, dab_plus);
FIC_SERVICE& service = GetService(sid);
AUDIO_SERVICE& current_audio_service = service.audio_comps[subchid];
if(current_audio_service != audio_service || ps != (service.pri_comp_subchid == subchid)) {
current_audio_service = audio_service;
if(ps)
service.pri_comp_subchid = subchid;
if (verbose)
etiLog.log(debug, "FICDecoder: SId 0x%04X: audio service (SubChId %2d, %-4s, %s)\n", sid, subchid, dab_plus ? "DAB+" : "DAB", ps ? "primary" : "secondary");
UpdateService(service);
}
break;
}
}
}
offset += 2;
}
}
}
void FICDecoder::ProcessFIG0_5(const uint8_t *data, size_t len) {
// FIG 0/5 - Service component language
// programme services only
// iterate through all components
for(size_t offset = 0; offset < len;) {
bool ls_flag = data[offset] & 0x80;
if(ls_flag) {
// long form - skipped, as not relevant
offset += 3;
} else {
// short form
bool msc_fic_flag = data[offset] & 0x40;
// handle only MSC components
if(!msc_fic_flag) {
int subchid = data[offset] & 0x3F;
int language = data[offset + 1];
FIC_SUBCHANNEL& current_sc = GetSubchannel(subchid);
if(current_sc.language != language) {
current_sc.language = language;
if (verbose)
etiLog.log(debug, "FICDecoder: SubChId %2d: language '%s'\n", subchid, ConvertLanguageToString(language).c_str());
UpdateSubchannel(subchid);
}
}
offset += 2;
}
}
}
void FICDecoder::ProcessFIG0_8(const uint8_t *data, size_t len) {
// FIG 0/8 - Service component global definition
// programme services only
// iterate through all service components
for(size_t offset = 0; offset < len;) {
uint16_t sid = data[offset] << 8 | data[offset + 1];
offset += 2;
bool ext_flag = data[offset] & 0x80;
int scids = data[offset] & 0x0F;
offset++;
bool ls_flag = data[offset] & 0x80;
if(ls_flag) {
// long form - skipped, as not relevant
offset += 2;
} else {
// short form
bool msc_fic_flag = data[offset] & 0x40;
// handle only MSC components
if(!msc_fic_flag) {
int subchid = data[offset] & 0x3F;
FIC_SERVICE& service = GetService(sid);
bool new_comp = service.comp_defs.find(scids) == service.comp_defs.end();
int& current_subchid = service.comp_defs[scids];
if(new_comp || current_subchid != subchid) {
current_subchid = subchid;
if (verbose)
etiLog.log(debug, "FICDecoder: SId 0x%04X, SCIdS %2d: MSC service component (SubChId %2d)\n", sid, scids, subchid);
UpdateService(service);
}
}
offset++;
}
// skip Rfa field, if needed
if(ext_flag)
offset++;
}
}
void FICDecoder::ProcessFIG0_9(const uint8_t *data, size_t len) {
// FIG 0/9 - Time and country identifier - Country, LTO and International table
// ensemble ECC/LTO and international table ID only
if(len < 3)
return;
FIC_ENSEMBLE new_ensemble = ensemble;
new_ensemble.lto = (data[0] & 0x20 ? -1 : 1) * (data[0] & 0x1F);
new_ensemble.ecc = data[1];
new_ensemble.inter_table_id = data[2];
if(ensemble != new_ensemble) {
ensemble = new_ensemble;
if (verbose)
etiLog.log(debug, "FICDecoder: ECC: 0x%02X, LTO: %s, international table ID: 0x%02X (%s)\n",
ensemble.ecc, ConvertLTOToString(ensemble.lto).c_str(), ensemble.inter_table_id, ConvertInterTableIDToString(ensemble.inter_table_id).c_str());
UpdateEnsemble();
// update services that changes may affect
for(const fic_services_t::value_type& service : services) {
const FIC_SERVICE& s = service.second;
if(s.pty_static != FIC_SERVICE::pty_none || s.pty_dynamic != FIC_SERVICE::pty_none)
UpdateService(s);
}
}
}
void FICDecoder::ProcessFIG0_10(const uint8_t *data, size_t len) {
// FIG 0/10 - Date and time (d&t)
if(len < 4)
return;
FIC_DAB_DT new_utc_dt;
// ignore short form, once long form available
bool utc_flag = data[2] & 0x08;
if(!utc_flag && utc_dt_long)
return;
// retrieve date
int mjd = (data[0] & 0x7F) << 10 | data[1] << 2 | data[2] >> 6;
int y0 = floor((mjd - 15078.2) / 365.25);
int m0 = floor((mjd - 14956.1 - floor(y0 * 365.25)) / 30.6001);
int d = mjd - 14956 - floor(y0 * 365.25) - floor(m0 * 30.6001);
int k = (m0 == 14 || m0 == 15) ? 1 : 0;
int y = y0 + k;
int m = m0 - 1 - k * 12;
new_utc_dt.dt.tm_year = y; // from 1900
new_utc_dt.dt.tm_mon = m - 1; // 0-based
new_utc_dt.dt.tm_mday = d;
// retrieve time
new_utc_dt.dt.tm_hour = (data[2] & 0x07) << 2 | data[3] >> 6;
new_utc_dt.dt.tm_min = data[3] & 0x3F;
new_utc_dt.dt.tm_isdst = -1; // ignore DST
if(utc_flag) {
// long form
if(len < 6)
return;
new_utc_dt.dt.tm_sec = data[4] >> 2;
new_utc_dt.ms = (data[4] & 0x03) << 8 | data[5];
utc_dt_long = true;
} else {
// short form
new_utc_dt.dt.tm_sec = 0;
new_utc_dt.ms = FIC_DAB_DT::ms_none;
}
if(utc_dt != new_utc_dt) {
// print only once (or once again on precision change)
if(utc_dt.IsNone() || utc_dt.IsMsNone() != new_utc_dt.IsMsNone())
if (verbose)
etiLog.log(debug, "FICDecoder: UTC date/time: %s\n", ConvertDateTimeToString(new_utc_dt, 0, true).c_str());
utc_dt = new_utc_dt;
observer.FICChangeUTCDateTime(utc_dt);
}
}
void FICDecoder::ProcessFIG0_13(const uint8_t *data, size_t len) {
// FIG 0/13 - User application information
// programme services only
// iterate through all service components
for(size_t offset = 0; offset < len;) {
uint16_t sid = data[offset] << 8 | data[offset + 1];
offset += 2;
int scids = data[offset] >> 4;
size_t num_scids_uas = data[offset] & 0x0F;
offset++;
// iterate through all user applications
for(size_t scids_ua = 0; scids_ua < num_scids_uas; scids_ua++) {
int ua_type = data[offset] << 3 | data[offset + 1] >> 5;
size_t ua_data_length = data[offset + 1] & 0x1F;
offset += 2;
// handle only Slideshow
if(ua_type == 0x002) {
FIC_SERVICE& service = GetService(sid);
if(service.comp_sls_uas.find(scids) == service.comp_sls_uas.end()) {
ua_data_t& sls_ua_data = service.comp_sls_uas[scids];
sls_ua_data.resize(ua_data_length);
if(ua_data_length)
memcpy(&sls_ua_data[0], data + offset, ua_data_length);
if (verbose)
etiLog.log(debug, "FICDecoder: SId 0x%04X, SCIdS %2d: Slideshow (%zu bytes UA data)\n", sid, scids, ua_data_length);
UpdateService(service);
}
}
offset += ua_data_length;
}
}
}
void FICDecoder::ProcessFIG0_17(const uint8_t *data, size_t len) {
// FIG 0/17 - Programme Type
// programme type only
// iterate through all services
for(size_t offset = 0; offset < len;) {
uint16_t sid = data[offset] << 8 | data[offset + 1];
bool sd = data[offset + 2] & 0x80;
bool l_flag = data[offset + 2] & 0x20;
bool cc_flag = data[offset + 2] & 0x10;
offset += 3;
// skip language, if present
if(l_flag)
offset++;
// programme type (international code)
int pty = data[offset] & 0x1F;
offset++;
// skip CC part, if present
if(cc_flag)
offset++;
FIC_SERVICE& service = GetService(sid);
int& current_pty = sd ? service.pty_dynamic : service.pty_static;
if(current_pty != pty) {
// suppress message, if dynamic FIC messages disabled and dynamic PTY not initally be set
bool show_msg = !(sd && current_pty != FIC_SERVICE::pty_none);
current_pty = pty;
if(verbose && show_msg) {
// assuming international table ID 0x01 here!
etiLog.log(debug, "FICDecoder: SId 0x%04X: programme type (%s): '%s'\n",
sid, sd ? "dynamic" : "static", ConvertPTYToString(pty, 0x01).c_str());
}
UpdateService(service);
}
}
}
void FICDecoder::ProcessFIG0_18(const uint8_t *data, size_t len) {
// FIG 0/18 - Announcement support
// iterate through all services
for(size_t offset = 0; offset < len;) {
uint16_t sid = data[offset] << 8 | data[offset + 1];
uint16_t asu_flags = data[offset + 2] << 8 | data[offset + 3];
size_t number_of_clusters = data[offset + 4] & 0x1F;
offset += 5;
cids_t cids;
for(size_t i = 0; i < number_of_clusters; i++)
cids.emplace(data[offset++]);
FIC_SERVICE& service = GetService(sid);
uint16_t& current_asu_flags = service.asu_flags;
cids_t& current_cids = service.cids;
if(current_asu_flags != asu_flags || current_cids != cids) {
current_asu_flags = asu_flags;
current_cids = cids;
std::string cids_str;
char cid_string[5];
for(const cids_t::value_type& cid : cids) {
if(!cids_str.empty())
cids_str += "/";
snprintf(cid_string, sizeof(cid_string), "0x%02X", cid);
cids_str += std::string(cid_string);
}
if (verbose)
etiLog.log(debug, "FICDecoder: SId 0x%04X: ASu flags 0x%04X, cluster(s) %s\n",
sid, asu_flags, cids_str.c_str());
UpdateService(service);
}
}
}
void FICDecoder::ProcessFIG0_19(const uint8_t *data, size_t len) {
// FIG 0/19 - Announcement switching
// iterate through all announcement clusters
for(size_t offset = 0; offset < len;) {
uint8_t cid = data[offset];
uint16_t asw_flags = data[offset + 1] << 8 | data[offset + 2];
bool region_flag = data[offset + 3] & 0x40;
int subchid = data[offset + 3] & 0x3F;
offset += region_flag ? 5 : 4;
FIC_ASW_CLUSTER ac;
ac.asw_flags = asw_flags;
ac.subchid = subchid;
FIC_ASW_CLUSTER& current_ac = ensemble.asw_clusters[cid];
if(current_ac != ac) {
current_ac = ac;
if (verbose) {
etiLog.log(debug, "FICDecoder: ASw cluster 0x%02X: flags 0x%04X, SubChId %2d\n",
cid, asw_flags, subchid);
}
UpdateEnsemble();
// update services that changes may affect
for(const fic_services_t::value_type& service : services) {
const FIC_SERVICE& s = service.second;
if(s.cids.find(cid) != s.cids.cend())
UpdateService(s);
}
}
}
}
void FICDecoder::ProcessFIG1(const uint8_t *data, size_t len) {
if(len < 1) {
etiLog.log(warn, "FICDecoder: received empty FIG 1\n");
return;
}
// read/skip FIG 1 header
FIG1_HEADER header(data[0]);
data++;
len--;
// ignore other ensembles
if(header.oe)
return;
// check for (un)supported extension + set ID field len
size_t len_id = -1;
switch(header.extension) {
case 0: // ensemble
case 1: // programme service
len_id = 2;
break;
case 4: // service component
// programme services only (P/D = 0)
if(data[0] & 0x80)
return;
len_id = 3;
break;
default:
// etiLog.log(debug, "FICDecoder: received unsupported FIG 1/%d with %zu field bytes\n", header.extension, len);
return;
}
// check length
size_t len_calced = len_id + 16 + 2;
if(len != len_calced) {
etiLog.log(warn, "FICDecoder: received FIG 1/%d having %zu field bytes (expected: %zu)\n", header.extension, len, len_calced);
return;
}
// parse actual label data
FIC_LABEL label;
label.charset = header.charset;
memcpy(label.label, data + len_id, 16);
label.short_label_mask = data[len_id + 16] << 8 | data[len_id + 17];
// handle extension
switch(header.extension) {
case 0: { // ensemble
uint16_t eid = data[0] << 8 | data[1];
ProcessFIG1_0(eid, label);
break; }
case 1: { // programme service
uint16_t sid = data[0] << 8 | data[1];
ProcessFIG1_1(sid, label);
break; }
case 4: { // service component
int scids = data[0] & 0x0F;
uint16_t sid = data[1] << 8 | data[2];
ProcessFIG1_4(sid, scids, label);
break; }
}
}
void FICDecoder::ProcessFIG1_0(uint16_t eid, const FIC_LABEL& label) {
if(ensemble.label != label) {
ensemble.label = label;
std::string label_str = ConvertLabelToUTF8(label, nullptr);
std::string short_label_str = DeriveShortLabelUTF8(label_str, label.short_label_mask);
if (verbose)
etiLog.log(debug, "FICDecoder: EId 0x%04X: ensemble label '" "\x1B[32m" "%s" "\x1B[0m" "' ('" "\x1B[32m" "%s" "\x1B[0m" "')\n",
eid, label_str.c_str(), short_label_str.c_str());
UpdateEnsemble();
}
}
void FICDecoder::ProcessFIG1_1(uint16_t sid, const FIC_LABEL& label) {
FIC_SERVICE& service = GetService(sid);
if(service.label != label) {
service.label = label;
if (verbose) {
std::string label_str = ConvertLabelToUTF8(label, nullptr);
std::string short_label_str = DeriveShortLabelUTF8(label_str, label.short_label_mask);
etiLog.log(debug, "FICDecoder: SId 0x%04X: programme service label '" "\x1B[32m" "%s" "\x1B[0m" "' ('" "\x1B[32m" "%s" "\x1B[0m" "')\n",
sid, label_str.c_str(), short_label_str.c_str());
}
UpdateService(service);
}
}
void FICDecoder::ProcessFIG1_4(uint16_t sid, int scids, const FIC_LABEL& label) {
// programme services only
FIC_SERVICE& service = GetService(sid);
FIC_LABEL& comp_label = service.comp_labels[scids];
if(comp_label != label) {
comp_label = label;
if (verbose) {
std::string label_str = ConvertLabelToUTF8(label, nullptr);
std::string short_label_str = DeriveShortLabelUTF8(label_str, label.short_label_mask);
etiLog.log(debug, "FICDecoder: SId 0x%04X, SCIdS %2d: service component label '" "\x1B[32m" "%s" "\x1B[0m" "' ('" "\x1B[32m" "%s" "\x1B[0m" "')\n",
sid, scids, label_str.c_str(), short_label_str.c_str());
}
UpdateService(service);
}
}
FIC_SUBCHANNEL& FICDecoder::GetSubchannel(int subchid) {
// created automatically, if not yet existing
return subchannels[subchid];
}
void FICDecoder::UpdateSubchannel(int subchid) {
// update services that consist of this sub-channel
for(const fic_services_t::value_type& service : services) {
const FIC_SERVICE& s = service.second;
if(s.audio_comps.find(subchid) != s.audio_comps.end())
UpdateService(s);
}
}
FIC_SERVICE& FICDecoder::GetService(uint16_t sid) {
FIC_SERVICE& result = services[sid]; // created, if not yet existing
// if new service, set SID
if(result.IsNone())
result.sid = sid;
return result;
}
void FICDecoder::UpdateService(const FIC_SERVICE& service) {
// abort update, if primary component or label not yet present
if(service.HasNoPriCompSubchid() || service.label.IsNone())
return;
// secondary components (if both component and definition are present)
bool multi_comps = false;
for(const comp_defs_t::value_type& comp_def : service.comp_defs) {
if(comp_def.second == service.pri_comp_subchid || service.audio_comps.find(comp_def.second) == service.audio_comps.end())
continue;
UpdateListedService(service, comp_def.first, true);
multi_comps = true;
}
// primary component
UpdateListedService(service, LISTED_SERVICE::scids_none, multi_comps);
}
void FICDecoder::UpdateListedService(const FIC_SERVICE& service, int scids, bool multi_comps) {
// assemble listed service
LISTED_SERVICE ls;
ls.sid = service.sid;
ls.scids = scids;
ls.label = service.label;
ls.pty_static = service.pty_static;
ls.pty_dynamic = service.pty_dynamic;
ls.asu_flags = service.asu_flags;
ls.cids = service.cids;
ls.pri_comp_subchid = service.pri_comp_subchid;
ls.multi_comps = multi_comps;
if(scids == LISTED_SERVICE::scids_none) { // primary component
ls.audio_service = service.audio_comps.at(service.pri_comp_subchid);
} else { // secondary component
ls.audio_service = service.audio_comps.at(service.comp_defs.at(scids));
// use component label, if available
comp_labels_t::const_iterator cl_it = service.comp_labels.find(scids);
if(cl_it != service.comp_labels.end())
ls.label = cl_it->second;
}
// use sub-channel information, if available
fic_subchannels_t::const_iterator sc_it = subchannels.find(ls.audio_service.subchid);
if(sc_it != subchannels.end())
ls.subchannel = sc_it->second;
/* check (for) Slideshow; currently only supported in X-PAD
* - derive the required SCIdS (if not yet known)
* - derive app type from UA data (if present)
*/
int sls_scids = scids;
if(sls_scids == LISTED_SERVICE::scids_none) {
for(const comp_defs_t::value_type& comp_def : service.comp_defs) {
if(comp_def.second == ls.audio_service.subchid) {
sls_scids = comp_def.first;
break;
}
}
}
if(sls_scids != LISTED_SERVICE::scids_none && service.comp_sls_uas.find(sls_scids) != service.comp_sls_uas.end())
ls.sls_app_type = GetSLSAppType(service.comp_sls_uas.at(sls_scids));
// forward to observer
observer.FICChangeService(ls);
}
int FICDecoder::GetSLSAppType(const ua_data_t& ua_data) {
// default values, if no UA data present
bool ca_flag = false;
int xpad_app_type = 12;
bool dg_flag = false;
int dscty = 60; // MOT
// if UA data present, parse X-PAD data
if(ua_data.size() >= 2) {
ca_flag = ua_data[0] & 0x80;
xpad_app_type = ua_data[0] & 0x1F;
dg_flag = ua_data[1] & 0x80;
dscty = ua_data[1] & 0x3F;
}
// if no CA is used, but DGs and MOT, enable Slideshow
if(!ca_flag && !dg_flag && dscty == 60)
return xpad_app_type;
else
return LISTED_SERVICE::sls_app_type_none;
}
void FICDecoder::UpdateEnsemble() {
// abort update, if EId or label not yet present
if(ensemble.IsNone() || ensemble.label.IsNone())
return;
// forward to observer
observer.FICChangeEnsemble(ensemble);
}
std::string FICDecoder::ConvertLabelToUTF8(const FIC_LABEL& label, std::string* charset_name) {
std::string result = CharsetTools::ConvertTextToUTF8(label.label, sizeof(label.label), label.charset, charset_name);
// discard trailing spaces
size_t last_pos = result.find_last_not_of(' ');
if(last_pos != std::string::npos)
result.resize(last_pos + 1);
return result;
}
const size_t FICDecoder::uep_sizes[] = {
16, 21, 24, 29, 35, 24, 29, 35, 42, 52, 29, 35, 42, 52, 32, 42,
48, 58, 70, 40, 52, 58, 70, 84, 48, 58, 70, 84, 104, 58, 70, 84,
104, 64, 84, 96, 116, 140, 80, 104, 116, 140, 168, 96, 116, 140, 168, 208,
116, 140, 168, 208, 232, 128, 168, 192, 232, 280, 160, 208, 280, 192, 280, 416
};
const int FICDecoder::uep_pls[] = {
5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 3, 2, 5, 4,
3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 3,
2, 5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 3, 2, 1,
5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 2, 5, 3, 1
};
const int FICDecoder::uep_bitrates[] = {
32, 32, 32, 32, 32, 48, 48, 48, 48, 48, 56, 56, 56, 56, 64, 64,
64, 64, 64, 80, 80, 80, 80, 80, 96, 96, 96, 96, 96, 112, 112, 112,
112, 128, 128, 128, 128, 128, 160, 160, 160, 160, 160, 192, 192, 192, 192, 192,
224, 224, 224, 224, 224, 256, 256, 256, 256, 256, 320, 320, 320, 384, 384, 384
};
const int FICDecoder::eep_a_size_factors[] = {12, 8, 6, 4};
const int FICDecoder::eep_b_size_factors[] = {27, 21, 18, 15};
const char* FICDecoder::languages_0x00_to_0x2B[] = {
"unknown/not applicable", "Albanian", "Breton", "Catalan", "Croatian", "Welsh", "Czech", "Danish",
"German", "English", "Spanish", "Esperanto", "Estonian", "Basque", "Faroese", "French",
"Frisian", "Irish", "Gaelic", "Galician", "Icelandic", "Italian", "Sami", "Latin",
"Latvian", "Luxembourgian", "Lithuanian", "Hungarian", "Maltese", "Dutch", "Norwegian", "Occitan",
"Polish", "Portuguese", "Romanian", "Romansh", "Serbian", "Slovak", "Slovene", "Finnish",
"Swedish", "Turkish", "Flemish", "Walloon"
};
const char* FICDecoder::languages_0x7F_downto_0x45[] = {
"Amharic", "Arabic", "Armenian", "Assamese", "Azerbaijani", "Bambora", "Belorussian", "Bengali",
"Bulgarian", "Burmese", "Chinese", "Chuvash", "Dari", "Fulani", "Georgian", "Greek",
"Gujurati", "Gurani", "Hausa", "Hebrew", "Hindi", "Indonesian", "Japanese", "Kannada",
"Kazakh", "Khmer", "Korean", "Laotian", "Macedonian", "Malagasay", "Malaysian", "Moldavian",
"Marathi", "Ndebele", "Nepali", "Oriya", "Papiamento", "Persian", "Punjabi", "Pushtu",
"Quechua", "Russian", "Rusyn", "Serbo-Croat", "Shona", "Sinhalese", "Somali", "Sranan Tongo",
"Swahili", "Tadzhik", "Tamil", "Tatar", "Telugu", "Thai", "Ukranian", "Urdu",
"Uzbek", "Vietnamese", "Zulu"
};
const char* FICDecoder::ptys_rds_0x00_to_0x1D[] = {
"No programme type", "News", "Current Affairs", "Information",
"Sport", "Education", "Drama", "Culture",
"Science", "Varied", "Pop Music", "Rock Music",
"Easy Listening Music", "Light Classical", "Serious Classical", "Other Music",
"Weather/meteorology", "Finance/Business", "Children's programmes", "Social Affairs",
"Religion", "Phone In", "Travel", "Leisure",
"Jazz Music", "Country Music", "National Music", "Oldies Music",
"Folk Music", "Documentary"
};
const char* FICDecoder::ptys_rbds_0x00_to_0x1D[] = {
"No program type", "News", "Information", "Sports",
"Talk", "Rock", "Classic Rock", "Adult Hits",
"Soft Rock", "Top 40", "Country", "Oldies",
"Soft", "Nostalgia", "Jazz", "Classical",
"Rhythm and Blues", "Soft Rhythm and Blues", "Foreign Language", "Religious Music",
"Religious Talk", "Personality", "Public", "College",
"(rfu)", "(rfu)", "(rfu)", "(rfu)",
"(rfu)", "Weather"
};
const char* FICDecoder::asu_types_0_to_10[] = {
"Alarm", "Road Traffic flash", "Transport flash", "Warning/Service",
"News flash", "Area weather flash", "Event announcement", "Special event",
"Programme Information", "Sport report", "Financial report"
};
std::string FICDecoder::ConvertLanguageToString(const int value) {
if(value >= 0x00 && value <= 0x2B)
return languages_0x00_to_0x2B[value];
if(value == 0x40)
return "background sound/clean feed";
if(value >= 0x45 && value <= 0x7F)
return languages_0x7F_downto_0x45[0x7F - value];
return "unknown (" + std::to_string(value) + ")";
}
std::string FICDecoder::ConvertLTOToString(const int value) {
// just to silence recent GCC's truncation warnings
int lto_value = value % 0x3F;
char lto_string[7];
snprintf(lto_string, sizeof(lto_string), "%+03d:%02d", lto_value / 2, (lto_value % 2) ? 30 : 0);
return lto_string;
}
std::string FICDecoder::ConvertInterTableIDToString(const int value) {
switch(value) {
case 0x01:
return "RDS PTY";
case 0x02:
return "RBDS PTY";
default:
return "unknown";
}
}
std::string FICDecoder::ConvertDateTimeToString(FIC_DAB_DT utc_dt, const int lto, bool output_ms) {
const char* weekdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
// if desired, apply LTO
if(lto)
utc_dt.dt.tm_min += lto * 30;
// normalize time (apply LTO, set day of week)
if(mktime(&utc_dt.dt) == (time_t) -1)
throw std::runtime_error("FICDecoder: error while normalizing date/time");
std::string result;
char s[11];
strftime(s, sizeof(s), "%F", &utc_dt.dt);
result += std::string(s) + ", " + weekdays[utc_dt.dt.tm_wday] + " - ";
if(!utc_dt.IsMsNone()) {
// long form
strftime(s, sizeof(s), "%T", &utc_dt.dt);
result += s;
if(output_ms) {
snprintf(s, sizeof(s), ".%03d", utc_dt.ms);
result += s;
}
} else {
// short form
strftime(s, sizeof(s), "%R", &utc_dt.dt);
result += s;
}
return result;
}
std::string FICDecoder::ConvertPTYToString(const int value, const int inter_table_id) {
switch(inter_table_id) {
case 0x01:
return value <= 0x1D ? ptys_rds_0x00_to_0x1D[value] : "(not used)";
case 0x02:
return value <= 0x1D ? ptys_rbds_0x00_to_0x1D[value] : "(not used)";
default:
return "(unknown)";
}
}
std::string FICDecoder::ConvertASuTypeToString(const int value) {
if(value >= 0 && value <= 10)
return asu_types_0_to_10[value];
return "unknown (" + std::to_string(value) + ")";
}
std::string FICDecoder::DeriveShortLabelUTF8(const std::string& long_label, uint16_t short_label_mask) {
std::string short_label;
for(size_t i = 0; i < long_label.length(); i++) // consider discarded trailing spaces
if(short_label_mask & (0x8000 >> i))
short_label += StringTools::UTF8Substr(long_label, i, 1);
return short_label;
}