diff -Nru dablin-1.13.0/debian/changelog dablin-1.14.0/debian/changelog --- dablin-1.13.0/debian/changelog 2020-04-15 21:19:00.000000000 +0000 +++ dablin-1.14.0/debian/changelog 2022-01-12 18:11:38.000000000 +0000 @@ -1,3 +1,13 @@ +dablin (1.14.0-1) unstable; urgency=medium + + * New upstream version. + * Bump standards version to 4.6.0. + * Bump debhelper version to 13, drop d/compat. + * d/copyright: bump years. + * d/upstream/metadata: added. + + -- Gürkan Myczko Wed, 12 Jan 2022 19:11:38 +0100 + dablin (1.13.0-1) unstable; urgency=medium * New upstream version. diff -Nru dablin-1.13.0/debian/compat dablin-1.14.0/debian/compat --- dablin-1.13.0/debian/compat 2018-01-18 13:55:13.000000000 +0000 +++ dablin-1.14.0/debian/compat 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -11 diff -Nru dablin-1.13.0/debian/control dablin-1.14.0/debian/control --- dablin-1.13.0/debian/control 2020-04-15 21:19:00.000000000 +0000 +++ dablin-1.14.0/debian/control 2022-01-12 18:11:38.000000000 +0000 @@ -1,9 +1,9 @@ Source: dablin Section: hamradio Priority: optional -Maintainer: Gürkan Myczko -Build-Depends: debhelper (>= 11), cmake, libmpg123-dev, libfaad-dev, libsdl2-dev, libgtkmm-3.0-dev -Standards-Version: 4.5.0 +Maintainer: Gürkan Myczko +Build-Depends: debhelper-compat (= 13), cmake, libmpg123-dev, libfaad-dev, libsdl2-dev, libgtkmm-3.0-dev +Standards-Version: 4.6.0 Homepage: http://www.opendigitalradio.org/ Package: dablin diff -Nru dablin-1.13.0/debian/copyright dablin-1.14.0/debian/copyright --- dablin-1.13.0/debian/copyright 2018-02-22 13:44:08.000000000 +0000 +++ dablin-1.14.0/debian/copyright 2022-01-12 18:11:38.000000000 +0000 @@ -1,9 +1,10 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: dablin +Upstream-Contact: Stefan Pöschel Source: https://github.com/Opendigitalradio/dablin Files: * -Copyright: 2015-2018 Stefan Pöschel +Copyright: 2015-2022 Stefan Pöschel License: GPL-3+ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,7 +40,7 @@ USA. Files: debian/* -Copyright: 2018 Gürkan Myczko +Copyright: 2018-2022 Gürkan Myczko License: GPL-2+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff -Nru dablin-1.13.0/debian/upstream/metadata dablin-1.14.0/debian/upstream/metadata --- dablin-1.13.0/debian/upstream/metadata 1970-01-01 00:00:00.000000000 +0000 +++ dablin-1.14.0/debian/upstream/metadata 2022-01-12 18:11:38.000000000 +0000 @@ -0,0 +1,4 @@ +Bug-Database: https://github.com/Opendigitalradio/dablin/issues +Bug-Submit: https://github.com/Opendigitalradio/dablin/issues/new +Repository: https://github.com/Opendigitalradio/dablin.git +Repository-Browse: https://github.com/Opendigitalradio/dablin diff -Nru dablin-1.13.0/doc/dablin.1 dablin-1.14.0/doc/dablin.1 --- dablin-1.13.0/doc/dablin.1 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/doc/dablin.1 2022-01-12 17:40:00.000000000 +0000 @@ -1,4 +1,4 @@ -.TH DABLIN 1 "2020-01-18" +.TH DABLIN 1 "2020-04-22" .\"------------------------------------------------------------------------ .SH NAME dablin \- CLI DAB/DAB+ receiver for Linux @@ -52,6 +52,9 @@ .B \-g USB stick gain to pass to DAB live source (auto gain is default) .TP +.B \-G +Use default gain for DAB live source (instead of auto gain) +.TP .B \-p Output PCM to stdout instead of using SDL .TP diff -Nru dablin-1.13.0/doc/dablin_gtk.1 dablin-1.14.0/doc/dablin_gtk.1 --- dablin-1.13.0/doc/dablin_gtk.1 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/doc/dablin_gtk.1 2022-01-12 17:40:00.000000000 +0000 @@ -1,4 +1,4 @@ -.TH DABLIN_GTK 1 "2020-01-18" +.TH DABLIN_GTK 1 "2021-12-14" .\"------------------------------------------------------------------------ .SH NAME dablin_gtk \- GTK DAB/DAB+ receiver for Linux @@ -52,6 +52,9 @@ .B \-g USB stick gain to pass to DAB live source (auto gain is default) .TP +.B \-G +Use default gain for DAB live source (instead of auto gain) +.TP .B \-r Path for recordings (default: /tmp) .TP @@ -67,6 +70,9 @@ .B \-I Don't catch up on stream after interruption .TP +.B \-Y +Initially disable Dynamic Label Plus (DL+) +.TP .B \-S Initially disable slideshow .TP diff -Nru dablin-1.13.0/README.md dablin-1.14.0/README.md --- dablin-1.13.0/README.md 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/README.md 2022-01-12 17:40:00.000000000 +0000 @@ -134,8 +134,9 @@ * [Debian](https://packages.debian.org/dablin) * [Ubuntu](https://launchpad.net/ubuntu/+source/dablin) -On Ubuntu 18.04 you can simply install DABlin from the official package -sources (note that the GitHub version may be newer): +Starting with Debian 10 and Ubuntu 18.04, you can simply install DABlin +from the official package sources (note that the GitHub version may be +newer): ```sh sudo apt-get install dablin @@ -153,13 +154,50 @@ * [Gentoo](https://github.com/paraenggu/delicious-absurdities-overlay/tree/master/media-sound/dablin) (by Christian Affolter, as part of his [delicious-absurdities-overlay](https://github.com/paraenggu/delicious-absurdities-overlay)) +## Ubuntu 20.04, FAAD2 and HE-AAC v2 services + +Ubuntu 20.04 currently ships a version of the FAAD2 library which can't +decode HE-AAC v2 services (= SBR and PS) due to a [bug](https://github.com/knik0/faad2/pull/51). This affects +FAAD2 version 2.9.0 and 2.9.1. + +To address this, a more recent version of the library can be compiled. + +First make sure, you have automake and libtool installed on your system. + +``` +sudo apt-get install automake libtool +``` +Then continue with + +```sh +git clone -b 2_9_2 https://github.com/knik0/faad2.git +cd faad2 +./bootstrap +./configure +make +sudo make install +sudo ldconfig +``` + +Now DABlin (and all other applications) uses the newer lib version. + +If the newer lib version shall only be used together with DABlin, the +two last commands must not be executed. Instead DABlin has always to be +invoked with the following prefix string (assuming the above commands +were executed in `/home/my_user`): + +```sh +LD_LIBRARY_PATH=/home/my_user/faad2/libfaad/.libs/ +``` + + ## Compilation -If the gtkmm library is available both the console and GTK GUI executables will -be built. If the gtkmm library is not available only the console executable will -be built. +If the gtkmm library is available both the console and GTK GUI +executables will be built, otherwise only the console executable will be +built. -To fetch the DABlin source code, execute the following commmands: +To fetch the DABlin source code, execute the following commands: ```sh git clone https://github.com/Opendigitalradio/dablin.git @@ -171,8 +209,8 @@ which can instead be cloned by appending `-b next` to the end of the above `git clone` command line. -You can use, for example, the following command sequence in order to compile and -install DABlin: +You can use, for example, the following command sequence in order to +compile and install DABlin (for Ubuntu 20.04 please also see above): ```sh mkdir build @@ -211,7 +249,7 @@ Unfortunately the Cygwin package of FDK-AAC doesn't seem to have been compiled with SBR support, so using [FAAD2](http://www.audiocoding.com/faad2.html) for DAB+ services is -recommended. However FAAD2 has to be compiled and installed by hand, as +recommended. However, FAAD2 has to be compiled and installed by hand, as there is no Cygwin package. This requires the following additional packages to be installed: - autoconf @@ -221,7 +259,7 @@ ![Screenshot of the console version on Windows (Cygwin)](https://basicmaster.de/dab/DABlin_console_cygwin.png) When Cygwin is installed, all the aforementioned packages can be -pre-selected for installation by calling Cygwin's `setup-.exe` +preselected for installation by calling Cygwin's `setup-.exe` with the following parameter: ```sh @@ -282,24 +320,28 @@ DAB) or `-R` (for DAB+). Note that the console output always shows the programme type just using -RDS PTYs despite the actually used international table ID (which should +RDS PTys despite the actually used international table ID (which should work in nearly all cases). The GTK version in contrast always shows the correct programme type, based on the transmitted international table ID. Dynamic FIC messages can be suppressed using `-F` (currently affects -dynamic PTY only). +dynamic PTy only). ### Date/Time The console version shows the related parameters as they are received: While the Local Time Offset (LTO) is shown upon any change, the UTC -date/time is shown once. +date/time is shown once (or once again on precision change). The GTK version in contrast starts to display the local date/time as soon as both mentioned values have been received. The used clock is then resynchronised upon further received UTC date/time. +If an ensemble transmits both short (minute precision) and long (millisecond +precision) form, only the long form will be used, as soon as received for the +first time. + ### Announcement support/switching @@ -319,10 +361,10 @@ To switch the channel instead of the service, press `Ctrl` in addition. Hotkey | Meaning ------------------|--------------------------------------------- +-----------------|-------------------------------------------------------------- `m` | Enable/disable audio mute `r` | Start/stop recording -`Ctrl` + `c` | Copy DL text or Slideshow slide to clipboard +`Ctrl` + `c` | Copy DL text, DL+ object text or Slideshow slide to clipboard `-` | Switch to previous service `+` | Switch to next service `1`..`0` | Switch to 1st..10th service @@ -365,14 +407,14 @@ have to change the DAB live source type accordingly by using `-D`. ```sh -dablin -d ~/bin/eti-cmdline-rtlsdr -D eti-cmdline -c 11D -s 0xd911 +dablin -D eti-cmdline -d ~/bin/eti-cmdline-rtlsdr -c 11D -s 0xd911 ``` When enclosed in quotes, you can also pass command line parameters to the binary, e.g. to set some frequency correction (here: +40 ppm): ```sh -dablin -d "~/bin/eti-cmdline-rtlsdr -P 40" -D eti-cmdline -c 11D -s 0xd911 +dablin -D eti-cmdline -d "~/bin/eti-cmdline-rtlsdr -p 40" -c 11D -s 0xd911 ``` In case of the GTK GUI version the desired channel may not be specified. To @@ -386,6 +428,26 @@ dablin_gtk -d ~/bin/dab2eti -c 11D -C 5C,7B,11A,11C,11D -s 0xd911 ``` +You also can use an Airspy or Airspy Mini, but you have to specify the +`-G` parameter. + +```sh +dablin_gtk -D eti-cmdline -d ~/bin/eti-cmdline-airspy -c 11D -C 11D -G +``` + +For a HackRF use it together with the eti-cmdline parameter `-E` (which +switches its amplifier on) and with the dablin_gtk parameter `-G`. + +```sh +dablin_gtk -D eti-cmdline -d "~/bin/eti-cmdline-hackrf -E" -c 11D -C 11D -G +``` + +For a raw file the syntax is just: + +```sh +~/bin/eti-cmdline-rawfiles -F foo.raw | dablin_gtk +``` + It may happen that an ETI live stream is interrupted (e.g. transponder lock lost). Later when the stream recovers, DABlin "catches up" on the stream and plays all (available) ETI frames until again in sync. This @@ -394,12 +456,12 @@ The `-I` parameter disables the described catch-up behaviour and instead resyncs to the stream after an interruption i.e. continues to play the -later received ETI frames in realtime. However this means that the +later received ETI frames in realtime. However, this means that the playback is delayed by the amount of all previous interruptions i.e. the news will start some seconds/minutes later compared to live reception because of that. -The GTK GUI version also allows to stop decoding the current +The GTK GUI version also allows a user to stop decoding the current channel/service by using the stop button next to the channel combobox. If desired, decoding can then be resumed using the same button again. @@ -470,7 +532,7 @@ `-x` in addition to `-s`. In the GTK version in the service list such components are shown -prefixed with `» ` (e.g. `» BBC R5LiveSportX`). Meanwhile the related +prefixed with `» ` (e.g. `» BBC R5LiveSportX`). Meanwhile, the related primary component is suffixed with ` »` (e.g. `BBC Radio 5 Live »`). @@ -482,12 +544,12 @@ ### Common -During (re-)synchronisation status messages are shown. Also dropped -Superframes or AU are mentioned. +During (re-)synchronisation status messages are shown. Furthermore, dropped +Superframes or AUs are mentioned. If the Reed Solomon FEC was used to correct bytes of a Superframe, this is mentioned by messages of the format `(3+)` in cyan color. This -shorter format is used as those messages occure several times with +shorter format is used as those messages occur several times with borderline reception. The digit refers to the number of corrected bytes within the Superframe while a plus (if present) indicates that at least one byte was incorrectable. @@ -503,7 +565,7 @@ AU No. 2 failed and hence the AU was dismissed. When the decoding of an AU nevertheless fails, this is indicated by an -`(AAC)` message in magenta color. However in that case the AAC decoder +`(AAC)` message in magenta color. However, in that case the AAC decoder may output audio samples anyway. ### Uncommon @@ -530,6 +592,7 @@ * ETSI TS 102 693 (EDI) together with ETSI TS 102 821 (DCP) ### Data applications +* ETSI TS 102 980 (Dynamic Label Plus) * ETSI EN 301 234 (MOT) * ETSI TS 101 499 (MOT Slideshow) @@ -540,6 +603,21 @@ DAB/DAB+ services. It is planned to add support for further Program Aided Data (PAD) features. +### Dynamic Label Plus (DL+) + +When in the GTK GUI version DL+ is enabled and DL+ objects are received, +the DL+ window is shown. Some stations only transmit DUMMY objects - in +this case the DL+ window will be shown but stays empty. + +if the running state indicates that the current item is running, all +objects with an ITEM content type have a content type with light green +background color. + +Depending on the object category, the object text is highlighted in bold +text or a different text color. Deleted objects have a text with grey +background color. + + ### Slideshow The GTK GUI version supports the MOT Slideshow. If Slideshow is enabled @@ -562,7 +640,7 @@ *Please note that the included FEC lib by KA9Q has a separate license!* DABlin - capital DAB experience -Copyright (C) 2015-2020 Stefan Pöschel +Copyright (C) 2015-2021 Stefan Pöschel This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff -Nru dablin-1.13.0/src/CMakeLists.txt dablin-1.14.0/src/CMakeLists.txt --- dablin-1.13.0/src/CMakeLists.txt 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/src/CMakeLists.txt 2022-01-12 17:40:00.000000000 +0000 @@ -25,6 +25,7 @@ mot_manager.cpp pad_decoder.cpp dablin_gtk.cpp + dablin_gtk_dl_plus.cpp dablin_gtk_sls.cpp ) diff -Nru dablin-1.13.0/src/dablin_gtk.cpp dablin-1.14.0/src/dablin_gtk.cpp --- dablin-1.13.0/src/dablin_gtk.cpp 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/src/dablin_gtk.cpp 2022-01-12 17:40:00.000000000 +0000 @@ -1,6 +1,6 @@ /* DABlin - capital DAB experience - Copyright (C) 2015-2020 Stefan Pöschel + Copyright (C) 2015-2021 Stefan Pöschel This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -50,6 +50,7 @@ " -p Output PCM to stdout instead of using SDL\n" " -u Output untouched audio stream to stdout instead of using SDL\n" " -I Don't catch up on stream after interruption\n" + " -Y Initially disable Dynamic Label Plus (DL+)\n" " -S Initially disable slideshow\n" " -L Enable loose behaviour (e.g. PAD conformance)\n" " -F Disable dynamic FIC messages (dynamic PTY, announcements)\n" @@ -82,7 +83,7 @@ // option args int c; - while((c = getopt(argc, argv, "hf:d:D:C:c:l:g:Gr:P:s:x:1puISLF")) != -1) { + while((c = getopt(argc, argv, "hf:d:D:C:c:l:g:Gr:P:s:x:1puIYSLF")) != -1) { switch(c) { case 'h': usage(argv[0]); @@ -140,6 +141,9 @@ case 'I': options.disable_int_catch_up = true; break; + case 'Y': + options.initially_disable_dl_plus = true; + break; case 'S': options.initially_disable_slideshow = true; break; @@ -251,6 +255,7 @@ dt_lto = FIC_ENSEMBLE::lto_none; + dl_plus_window.set_transient_for(*this); slideshow_window.set_transient_for(*this); ensemble_update_progress.GetDispatcher().connect(sigc::mem_fun(*this, &DABlinGTK::EnsembleUpdateProgressEmitted)); @@ -260,6 +265,7 @@ pad_change_dynamic_label.GetDispatcher().connect(sigc::mem_fun(*this, &DABlinGTK::PADChangeDynamicLabelEmitted)); pad_change_slide.GetDispatcher().connect(sigc::mem_fun(*this, &DABlinGTK::PADChangeSlideEmitted)); do_rec_status_update.GetDispatcher().connect(sigc::mem_fun(*this, &DABlinGTK::DoRecStatusUpdateEmitted)); + do_datetime_sync.GetDispatcher().connect(sigc::mem_fun(*this, &DABlinGTK::DoDateTimeSyncEmitted)); do_datetime_update.GetDispatcher().connect(sigc::mem_fun(*this, &DABlinGTK::DoDateTimeUpdateEmitted)); if(options.source_format == EnsembleSource::FORMAT_ETI) @@ -406,6 +412,11 @@ tglbtn_slideshow.signal_clicked().connect(sigc::mem_fun(*this, &DABlinGTK::on_tglbtn_slideshow)); tglbtn_slideshow.set_tooltip_text("Slideshow (if available)"); + tglbtn_dl_plus.set_label("DL+"); + tglbtn_dl_plus.set_active(!options.initially_disable_dl_plus); + tglbtn_dl_plus.signal_clicked().connect(sigc::mem_fun(*this, &DABlinGTK::on_tglbtn_dl_plus)); + tglbtn_dl_plus.set_tooltip_text("Dynamic Label Plus (if available)"); + tglbtn_mute.set_image_from_icon_name("audio-volume-muted"); tglbtn_mute.signal_clicked().connect(sigc::mem_fun(*this, &DABlinGTK::on_tglbtn_mute)); tglbtn_mute.set_tooltip_text("Mute"); @@ -452,14 +463,15 @@ top_grid.attach_next_to(frame_combo_services, frame_label_ensemble, Gtk::POS_RIGHT, 1, 2); top_grid.attach_next_to(frame_label_format, frame_combo_services, Gtk::POS_RIGHT, 1, 2); top_grid.attach_next_to(tglbtn_record, frame_label_format, Gtk::POS_RIGHT, 1, 1); - top_grid.attach_next_to(label_record, tglbtn_record, Gtk::POS_RIGHT, 2, 1); + top_grid.attach_next_to(label_record, tglbtn_record, Gtk::POS_RIGHT, 3, 1); top_grid.attach_next_to(tglbtn_slideshow, tglbtn_record, Gtk::POS_BOTTOM, 1, 1); - top_grid.attach_next_to(tglbtn_mute, tglbtn_slideshow, Gtk::POS_RIGHT, 1, 1); + top_grid.attach_next_to(tglbtn_dl_plus, tglbtn_slideshow, Gtk::POS_RIGHT, 1, 1); + top_grid.attach_next_to(tglbtn_mute, tglbtn_dl_plus, Gtk::POS_RIGHT, 1, 1); top_grid.attach_next_to(vlmbtn, tglbtn_mute, Gtk::POS_RIGHT, 1, 1); - top_grid.attach_next_to(frame_label_dl, frame_combo_channels, Gtk::POS_BOTTOM, 7, 1); + top_grid.attach_next_to(frame_label_dl, frame_combo_channels, Gtk::POS_BOTTOM, 8, 1); top_grid.attach_next_to(frame_label_datetime, frame_label_dl, Gtk::POS_BOTTOM, 2, 1); - top_grid.attach_next_to(frame_label_asu, frame_label_datetime, Gtk::POS_RIGHT, 5, 1); - top_grid.attach_next_to(progress_position, frame_label_datetime, Gtk::POS_BOTTOM, 7, 1); + top_grid.attach_next_to(frame_label_asu, frame_label_datetime, Gtk::POS_RIGHT, 6, 1); + top_grid.attach_next_to(progress_position, frame_label_datetime, Gtk::POS_BOTTOM, 8, 1); show_all_children(); progress_position.hide(); // invisible until progress updated @@ -574,6 +586,9 @@ label_dl.set_label(""); frame_label_dl.set_tooltip_text(""); + dl_plus_window.hide(); + dl_plus_window.ClearDLPlusInfo(); + if(!service.HasSLS()) { slideshow_window.hide(); slideshow_window.ClearSlide(); @@ -617,6 +632,7 @@ // assemble text - iterate through all supported announcement types std::string ac_str; + size_t count = 0; for(int type = 0; type < 16; type++) { uint16_t type_flag = 1 << type; if(!(service.asu_flags & type_flag)) @@ -637,8 +653,13 @@ } } - if(!ac_str.empty()) - ac_str += " + "; + // regularly insert line break + if(!ac_str.empty()) { + ac_str += (count % 6 == 0) ? "\n" : " "; + ac_str += "+ "; + } + count++; + if(!asw_color.empty()) ac_str += ""; ac_str += FICDecoder::ConvertASuTypeToString(type); @@ -847,6 +868,13 @@ tglbtn_mute.clicked(); } +void DABlinGTK::on_tglbtn_dl_plus() { + if(tglbtn_dl_plus.get_active()) + dl_plus_window.TryToShow(); + else + dl_plus_window.hide(); +} + void DABlinGTK::on_tglbtn_slideshow() { if(tglbtn_slideshow.get_active()) slideshow_window.TryToShow(); @@ -1005,7 +1033,9 @@ } bool DABlinGTK::HandleConfigureEvent(GdkEventConfigure* /*configure_event*/) { - // move together with slideshow window + // move together with children windows + if(dl_plus_window.get_visible()) + dl_plus_window.AlignToParent(); if(slideshow_window.get_visible()) slideshow_window.AlignToParent(); return false; @@ -1113,6 +1143,10 @@ utc_dt_next = utc_dt; dt_update = std::chrono::steady_clock::now(); // fprintf(stderr, "### time synced\n"); + + if(dt_lto != FIC_ENSEMBLE::lto_none) + DoDateTimeSync(FICDecoder::ConvertDateTimeToString(utc_dt, dt_lto, true)); + ShowDateTime(true); } @@ -1156,11 +1190,17 @@ } void DABlinGTK::DoDateTimeUpdateEmitted() { - // display received date/time + // display updated date/time frame_label_datetime.set_sensitive(true); label_datetime.set_label(do_datetime_update.Pop()); } +void DABlinGTK::DoDateTimeSyncEmitted() { + // display synced date/time + frame_label_datetime.set_sensitive(true); + frame_label_datetime.set_tooltip_text("Last synced date/time: " + do_datetime_sync.Pop()); +} + void DABlinGTK::FICDiscardedFIB() { fprintf(stderr, "\x1B[33m" "(FIB)" "\x1B[0m" " "); } @@ -1183,6 +1223,7 @@ frame_label_ensemble.set_tooltip_text(""); frame_label_datetime.set_sensitive(false); + frame_label_datetime.set_tooltip_text(""); label_datetime.set_label(""); utc_dt_curr = FIC_DAB_DT(); @@ -1280,6 +1321,13 @@ label_dl.set_label(""); frame_label_dl.set_tooltip_text(""); } + + // if present, forward DL Plus updates + if(!dl.dl_plus_objects.empty()) { + dl_plus_window.UpdateDLPlusInfo(dl); + if(tglbtn_dl_plus.get_active()) + dl_plus_window.TryToShow(); + } } void DABlinGTK::PADChangeSlideEmitted() { diff -Nru dablin-1.13.0/src/dablin_gtk_dl_plus.cpp dablin-1.14.0/src/dablin_gtk_dl_plus.cpp --- dablin-1.13.0/src/dablin_gtk_dl_plus.cpp 1970-01-01 00:00:00.000000000 +0000 +++ dablin-1.14.0/src/dablin_gtk_dl_plus.cpp 2022-01-12 17:40:00.000000000 +0000 @@ -0,0 +1,187 @@ +/* + DABlin - capital DAB experience + Copyright (C) 2021 Stefan Pöschel + + This program 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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include "dablin_gtk_dl_plus.h" + + + +// --- DABlinGTKDLPlusWindow ----------------------------------------------------------------- +DABlinGTKDLPlusWindow::DABlinGTKDLPlusWindow() { + prev_parent_x = -1; + prev_parent_y = -1; + + set_title("Dynamic Label Plus"); + set_type_hint(Gdk::WINDOW_TYPE_HINT_UTILITY); + set_resizable(false); + set_deletable(false); + + list_dl_plus_liststore = Gtk::ListStore::create(list_dl_plus_cols); + list_dl_plus_liststore->set_sort_column(list_dl_plus_cols.col_content_type, Gtk::SORT_ASCENDING); + + list_dl_plus.set_model(list_dl_plus_liststore); + list_dl_plus.set_size_request(250, -1); + list_dl_plus.insert_column_with_data_func(-1, "Content type", cell_renderer_text, sigc::mem_fun(*this, &DABlinGTKDLPlusWindow::RenderContentType)); + list_dl_plus.insert_column_with_data_func(-1, "Text", cell_renderer_text, sigc::mem_fun(*this, &DABlinGTKDLPlusWindow::RenderText)); + + add(top_grid); + + top_grid.attach(list_dl_plus, 0, 0, 1, 1); + + show_all_children(); + + // add window key press event handler + signal_key_press_event().connect(sigc::mem_fun(*this, &DABlinGTKDLPlusWindow::HandleKeyPressEvent)); + add_events(Gdk::KEY_PRESS_MASK); +} + +void DABlinGTKDLPlusWindow::RenderContentType(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter) { + Gtk::CellRendererText *rt = (Gtk::CellRendererText*) cell; + + int content_type = (*iter)[list_dl_plus_cols.col_content_type]; + bool item_running = (*iter)[list_dl_plus_cols.col_item_running]; + + rt->property_text() = DynamicLabelDecoder::ConvertDLPlusContentTypeToString(content_type); + rt->property_family() = "Monospace"; + rt->property_foreground_rgba() = Gdk::RGBA("black"); + rt->property_background_rgba() = (content_type < 12 && item_running) ? Gdk::RGBA("light green") : Gdk::RGBA("grey90"); // highlight running ITEM objects + rt->property_weight() = Pango::WEIGHT_NORMAL; +} + +void DABlinGTKDLPlusWindow::RenderText(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter) { + Gtk::CellRendererText *rt = (Gtk::CellRendererText*) cell; + + int content_type = (*iter)[list_dl_plus_cols.col_content_type]; + std::string text = (*iter)[list_dl_plus_cols.col_text]; + bool deleted = (*iter)[list_dl_plus_cols.col_deleted]; + + // color highlighting + std::string color = "black"; + if(content_type == 1) // ITEM.TITLE + color = "blue"; + if(content_type >= 12 && content_type < 31) // INFO + color = "green"; + if(content_type >= 41 && content_type < 54) // INTERACTIVITY + color = "blue"; + + rt->property_text() = text; + rt->property_family() = ""; + rt->property_foreground_rgba() = Gdk::RGBA(color); + rt->property_background_rgba() = Gdk::RGBA(deleted ? "light grey" : "white"); // indicate deleted objects + rt->property_weight() = content_type < 12 ? Pango::WEIGHT_BOLD : Pango::WEIGHT_NORMAL; // highlight ITEM objects +} + +void DABlinGTKDLPlusWindow::TryToShow() { + // if already visible or no DL Plus info present, abort + if(get_visible() || get_tooltip_text().empty()) + return; + + AlignToParent(); + show(); +} + +void DABlinGTKDLPlusWindow::AlignToParent() { + int x, y, w, h, this_x, this_y; + get_transient_for()->get_position(x, y); + get_transient_for()->get_size(w, h); + get_position(this_x, this_y); // event position doesn't work! + + int curr_parent_x = x; + int curr_parent_y = y + h; + + // TODO: fix multi head issue + if(prev_parent_x == -1 && prev_parent_y == -1) { + // first: align to the bottom of parent + move(curr_parent_x, curr_parent_y + 40); // add some vertical padding for WM decoration + } else { + // later: follow parent + move(curr_parent_x + (this_x - prev_parent_x), curr_parent_y + (this_y - prev_parent_y)); + } + + prev_parent_x = curr_parent_x; + prev_parent_y = curr_parent_y; +} + +bool DABlinGTKDLPlusWindow::HandleKeyPressEvent(GdkEventKey* key_event) { + // events without Shift/Alt/Win, but with Control + if((key_event->state & (Gdk::SHIFT_MASK | Gdk::MOD1_MASK | Gdk::SUPER_MASK)) == 0 && (key_event->state & Gdk::CONTROL_MASK)) { + switch(key_event->keyval) { + case GDK_KEY_c: + case GDK_KEY_C: + // copy DL Plus object text, if entry selected + const Gtk::TreeModel::iterator& row_it = list_dl_plus.get_selection()->get_selected(); + if(list_dl_plus_liststore->iter_is_valid(row_it)) { + std::string text = (*row_it)[list_dl_plus_cols.col_text]; + Gtk::Clipboard::get()->set_text(text); + } + return true; + } + } + return false; +} + +void DABlinGTKDLPlusWindow::UpdateDLPlusInfo(const DL_STATE& dl) { + // add/update DL Plus objects + Gtk::ListStore::Children children = list_dl_plus_liststore->children(); + + // process objects + for(const DL_PLUS_OBJECT& obj : dl.dl_plus_objects) { + // ignore DUMMY tags + if(obj.content_type == 0) + continue; + + bool deleted = obj.text == " "; + + // get row (add new one, if needed and no deletion) + Gtk::ListStore::iterator row_it = std::find_if( + children.begin(), children.end(), + [&](const Gtk::TreeRow& row)->bool { + return row[list_dl_plus_cols.col_content_type] == obj.content_type; + } + ); + bool add_new_row = row_it == children.end(); + if(add_new_row && !deleted) + row_it = list_dl_plus_liststore->append(); + + // continue, if no existing object for received deletion + if(row_it == children.end()) + continue; + + Gtk::TreeRow row = *row_it; + if(add_new_row) + row[list_dl_plus_cols.col_content_type] = obj.content_type; + if(!deleted) + row[list_dl_plus_cols.col_text] = obj.text; + row[list_dl_plus_cols.col_deleted] = deleted; + } + + // forward item running flag to all existing objects + for(const Gtk::TreeRow row : children) + row[list_dl_plus_cols.col_item_running] = dl.dl_plus_ir; + + set_tooltip_text( + "Item toggle bit: " + std::to_string(dl.dl_plus_it ? 1 : 0) + "\n" + "Item running bit: " + std::to_string(dl.dl_plus_ir ? 1 : 0) + ); +} + +void DABlinGTKDLPlusWindow::ClearDLPlusInfo() { + list_dl_plus_liststore->clear(); + list_dl_plus.columns_autosize(); + list_dl_plus.check_resize(); // prevent resizing bug + set_tooltip_text(""); +} diff -Nru dablin-1.13.0/src/dablin_gtk_dl_plus.h dablin-1.14.0/src/dablin_gtk_dl_plus.h --- dablin-1.13.0/src/dablin_gtk_dl_plus.h 1970-01-01 00:00:00.000000000 +0000 +++ dablin-1.14.0/src/dablin_gtk_dl_plus.h 2022-01-12 17:40:00.000000000 +0000 @@ -0,0 +1,74 @@ +/* + DABlin - capital DAB experience + Copyright (C) 2021 Stefan Pöschel + + This program 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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#ifndef DABLIN_GTK_DL_PLUS_H_ +#define DABLIN_GTK_DL_PLUS_H_ + +#include + +#include + +#include "pad_decoder.h" + + + +// --- DABlinGTKDLPlusObjectColumns ----------------------------------------------------------------- +class DABlinGTKDLPlusObjectColumns : public Gtk::TreeModelColumnRecord { +public: + Gtk::TreeModelColumn col_content_type; + Gtk::TreeModelColumn col_text; + Gtk::TreeModelColumn col_deleted; + Gtk::TreeModelColumn col_item_running; + + DABlinGTKDLPlusObjectColumns() { + add(col_content_type); + add(col_text); + add(col_deleted); + add(col_item_running); + } +}; + + +// --- DABlinGTKDLPlusWindow ----------------------------------------------------------------- +class DABlinGTKDLPlusWindow : public Gtk::Window { +private: + Gtk::Grid top_grid; + + DABlinGTKDLPlusObjectColumns list_dl_plus_cols; + Glib::RefPtr list_dl_plus_liststore; + Gtk::TreeView list_dl_plus; + Gtk::CellRendererText cell_renderer_text; + void RenderContentType(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter); + void RenderText(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter); + + int prev_parent_x; + int prev_parent_y; + + bool HandleKeyPressEvent(GdkEventKey* key_event); +public: + DABlinGTKDLPlusWindow(); + + void TryToShow(); + void AlignToParent(); + + void UpdateDLPlusInfo(const DL_STATE& dl); + void ClearDLPlusInfo(); +}; + + +#endif /* DABLIN_GTK_DL_PLUS_H_ */ diff -Nru dablin-1.13.0/src/dablin_gtk.h dablin-1.14.0/src/dablin_gtk.h --- dablin-1.13.0/src/dablin_gtk.h 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/src/dablin_gtk.h 2022-01-12 17:40:00.000000000 +0000 @@ -1,6 +1,6 @@ /* DABlin - capital DAB experience - Copyright (C) 2015-2020 Stefan Pöschel + Copyright (C) 2015-2021 Stefan Pöschel This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,6 +19,9 @@ #ifndef DABLIN_GTK_H_ #define DABLIN_GTK_H_ +// eti_player.h, that indirectly includes SDL.h, must be included before or it won't compile on OS X +#include "eti_player.h" + #include #include #include @@ -30,9 +33,9 @@ #include +#include "dablin_gtk_dl_plus.h" #include "dablin_gtk_sls.h" #include "eti_source.h" -#include "eti_player.h" #include "edi_source.h" #include "edi_player.h" #include "fic_decoder.h" @@ -88,6 +91,7 @@ bool untouched_output; bool disable_int_catch_up; int gain; + bool initially_disable_dl_plus; bool initially_disable_slideshow; bool loose; bool disable_dyn_fic_msgs; @@ -104,6 +108,7 @@ untouched_output(false), disable_int_catch_up(false), gain(DAB_LIVE_SOURCE_CHANNEL::auto_gain), + initially_disable_dl_plus(false), initially_disable_slideshow(false), loose(false), disable_dyn_fic_msgs(false) @@ -172,6 +177,7 @@ int switch_service_scids; bool switch_service_applied; + DABlinGTKDLPlusWindow dl_plus_window; DABlinGTKSlideshowWindow slideshow_window; EnsembleSource *ensemble_source; @@ -247,6 +253,7 @@ Gtk::Label label_record; Gtk::ToggleButton tglbtn_slideshow; + Gtk::ToggleButton tglbtn_dl_plus; Gtk::ToggleButton tglbtn_mute; Gtk::VolumeButton vlmbtn; @@ -274,6 +281,7 @@ void on_tglbtn_record(); void on_tglbtn_mute(); void on_vlmbtn(double value); + void on_tglbtn_dl_plus(); void on_tglbtn_slideshow(); void on_combo_channels(); void on_combo_services(); @@ -289,6 +297,10 @@ void DoRecStatusUpdateEmitted(); void UpdateRecStatus(bool decoding); + GTKDispatcherQueue do_datetime_sync; + void DoDateTimeSync(const std::string& dt_str) {do_datetime_sync.PushAndEmit(dt_str);} + void DoDateTimeSyncEmitted(); + GTKDispatcherQueue do_datetime_update; void DoDateTimeUpdate(const std::string& dt_str) {do_datetime_update.PushAndEmit(dt_str);} void DoDateTimeUpdateEmitted(); diff -Nru dablin-1.13.0/src/dablin_gtk_sls.cpp dablin-1.14.0/src/dablin_gtk_sls.cpp --- dablin-1.13.0/src/dablin_gtk_sls.cpp 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/src/dablin_gtk_sls.cpp 2022-01-12 17:40:00.000000000 +0000 @@ -1,6 +1,6 @@ /* DABlin - capital DAB experience - Copyright (C) 2018-2019 Stefan Pöschel + Copyright (C) 2018-2021 Stefan Pöschel This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -25,9 +25,8 @@ pixbuf_waiting = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, false, 8, 320, 240); // default slide size pixbuf_waiting->fill(0x003000FF); - // align to the right of parent - offset_x = 20; // add some horizontal padding for WM decoration - offset_y = 0; + prev_parent_x = -1; + prev_parent_y = -1; set_type_hint(Gdk::WINDOW_TYPE_HINT_UTILITY); set_resizable(false); @@ -43,10 +42,6 @@ // add window key press event handler signal_key_press_event().connect(sigc::mem_fun(*this, &DABlinGTKSlideshowWindow::HandleKeyPressEvent)); add_events(Gdk::KEY_PRESS_MASK); - - // add window config event handler (before default handler) - signal_configure_event().connect(sigc::mem_fun(*this, &DABlinGTKSlideshowWindow::HandleConfigureEvent), false); - add_events(Gdk::STRUCTURE_MASK); } void DABlinGTKSlideshowWindow::TryToShow() { @@ -59,12 +54,25 @@ } void DABlinGTKSlideshowWindow::AlignToParent() { - int x, y, w, h; + int x, y, w, h, this_x, this_y; get_transient_for()->get_position(x, y); get_transient_for()->get_size(w, h); + get_position(this_x, this_y); // event position doesn't work! + + int curr_parent_x = x + w; + int curr_parent_y = y; // TODO: fix multi head issue - move(x + w + offset_x, y + offset_y); + if(prev_parent_x == -1 && prev_parent_y == -1) { + // first: align to the right of parent + move(curr_parent_x + 20, curr_parent_y); // add some horizontal padding for WM decoration + } else { + // later: follow parent + move(curr_parent_x + (this_x - prev_parent_x), curr_parent_y + (this_y - prev_parent_y)); + } + + prev_parent_x = curr_parent_x; + prev_parent_y = curr_parent_y; } bool DABlinGTKSlideshowWindow::HandleKeyPressEvent(GdkEventKey* key_event) { @@ -82,20 +90,6 @@ } return false; } - -bool DABlinGTKSlideshowWindow::HandleConfigureEvent(GdkEventConfigure* /*configure_event*/) { - // update window offset, if visible - if(get_visible()) { - int x, y, w, h, sls_x, sls_y; - get_transient_for()->get_position(x, y); - get_transient_for()->get_size(w, h); - get_position(sls_x, sls_y); // event position doesn't work! - - offset_x = sls_x - w - x; - offset_y = sls_y - y; - } - return false; -} void DABlinGTKSlideshowWindow::AwaitSlide() { set_title("Slideshow..."); diff -Nru dablin-1.13.0/src/dablin_gtk_sls.h dablin-1.14.0/src/dablin_gtk_sls.h --- dablin-1.13.0/src/dablin_gtk_sls.h 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/src/dablin_gtk_sls.h 2022-01-12 17:40:00.000000000 +0000 @@ -1,6 +1,6 @@ /* DABlin - capital DAB experience - Copyright (C) 2018-2019 Stefan Pöschel + Copyright (C) 2018-2021 Stefan Pöschel This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,7 +19,6 @@ #ifndef DABLIN_GTK_SLS_H_ #define DABLIN_GTK_SLS_H_ -#include #include #include @@ -36,11 +35,10 @@ Gtk::LinkButton link_button; Glib::RefPtr pixbuf_waiting; - std::atomic offset_x; - std::atomic offset_y; + int prev_parent_x; + int prev_parent_y; bool HandleKeyPressEvent(GdkEventKey* key_event); - bool HandleConfigureEvent(GdkEventConfigure* configure_event); public: DABlinGTKSlideshowWindow(); diff -Nru dablin-1.13.0/src/dablin.h dablin-1.14.0/src/dablin.h --- dablin-1.13.0/src/dablin.h 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/src/dablin.h 2022-01-12 17:40:00.000000000 +0000 @@ -19,11 +19,13 @@ #ifndef DABLIN_H_ #define DABLIN_H_ +// eti_player.h, that indirectly includes SDL.h, must be included before or it won't compile on OS X +#include "eti_player.h" + #include #include #include "eti_source.h" -#include "eti_player.h" #include "edi_source.h" #include "edi_player.h" #include "fic_decoder.h" diff -Nru dablin-1.13.0/src/ensemble_player.h dablin-1.14.0/src/ensemble_player.h --- dablin-1.13.0/src/ensemble_player.h 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/src/ensemble_player.h 2022-01-12 17:40:00.000000000 +0000 @@ -19,6 +19,11 @@ #ifndef ENSEMBLE_PLAYER_H_ #define ENSEMBLE_PLAYER_H_ +// sdl_output.h, that includes SDL.h, must be included before or it won't compile on OS X +#ifndef DABLIN_DISABLE_SDL +#include "sdl_output.h" +#endif + #include #include #include @@ -31,10 +36,6 @@ #include "pcm_output.h" #include "tools.h" -#ifndef DABLIN_DISABLE_SDL -#include "sdl_output.h" -#endif - // --- EnsemblePlayerObserver ----------------------------------------------------------------- class EnsemblePlayerObserver { diff -Nru dablin-1.13.0/src/fic_decoder.cpp dablin-1.14.0/src/fic_decoder.cpp --- dablin-1.13.0/src/fic_decoder.cpp 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/src/fic_decoder.cpp 2022-01-12 17:40:00.000000000 +0000 @@ -350,6 +350,11 @@ 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; @@ -365,7 +370,6 @@ new_utc_dt.dt.tm_mday = d; // retrieve time - bool utc_flag = data[2] & 0x08; 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 @@ -375,6 +379,7 @@ 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; @@ -382,8 +387,8 @@ } if(utc_dt != new_utc_dt) { - // print only once - if(utc_dt.IsNone()) + // print only once (or once again on precision change) + if(utc_dt.IsNone() || utc_dt.IsMsNone() != new_utc_dt.IsMsNone()) fprintf(stderr, "FICDecoder: UTC date/time: %s\n", ConvertDateTimeToString(new_utc_dt, 0, true).c_str()); utc_dt = new_utc_dt; @@ -869,8 +874,11 @@ } 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", value / 2, (value % 2) ? 30 : 0); + snprintf(lto_string, sizeof(lto_string), "%+03d:%02d", lto_value / 2, (lto_value % 2) ? 30 : 0); return lto_string; } diff -Nru dablin-1.13.0/src/fic_decoder.h dablin-1.14.0/src/fic_decoder.h --- dablin-1.13.0/src/fic_decoder.h 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/src/fic_decoder.h 2022-01-12 17:40:00.000000000 +0000 @@ -317,6 +317,7 @@ fic_subchannels_t subchannels; // from FIG 0/1: SubChId -> FIC_SUBCHANNEL FIC_DAB_DT utc_dt; + bool utc_dt_long; static const size_t uep_sizes[]; static const int uep_pls[]; @@ -334,7 +335,8 @@ public: FICDecoder(FICDecoderObserver *observer, bool disable_dyn_msgs) : observer(observer), - disable_dyn_msgs(disable_dyn_msgs) + disable_dyn_msgs(disable_dyn_msgs), + utc_dt_long(false) {} void Process(const uint8_t *data, size_t len); diff -Nru dablin-1.13.0/src/pad_decoder.cpp dablin-1.14.0/src/pad_decoder.cpp --- dablin-1.13.0/src/pad_decoder.cpp 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/src/pad_decoder.cpp 2022-01-12 17:40:00.000000000 +0000 @@ -1,6 +1,6 @@ /* DABlin - capital DAB experience - Copyright (C) 2015-2019 Stefan Pöschel + Copyright (C) 2015-2021 Stefan Pöschel This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -294,6 +294,7 @@ DataGroup::Reset(); dl_sr.Reset(); + dl_plus_sr.Reset(); label.Reset(); } @@ -302,6 +303,7 @@ size_t field_len = 0; bool cmd_remove_label = false; + bool cmd_dl_plus = false; // handle command/segment if(command) { @@ -309,6 +311,10 @@ case 0x01: // remove label cmd_remove_label = true; break; + case 0x02: // DL Plus + cmd_dl_plus = true; + field_len = (dg_raw[1] & 0x0F) + 1; + break; default: // ignore command DataGroup::Reset(); @@ -331,6 +337,7 @@ // on Remove Label command, display empty label if(cmd_remove_label) { + DataGroup::Reset(); label.Reset(); return true; } @@ -340,20 +347,111 @@ memcpy(dl_seg.prefix, &dg_raw[0], 2); dl_seg.chars.insert(dl_seg.chars.begin(), dg_raw.begin() + 2, dg_raw.begin() + 2 + field_len); + bool current_flag = cmd_dl_plus ? dl_seg.DLPlusLink() : dl_seg.Toggle(); + bool dl_toggle; + bool dl_plus_link; + + // reset affected reassemblers upon different relevant (toggle or DL Plus link) flag value + if(dl_sr.GetToggle(dl_toggle) && dl_toggle != current_flag) + dl_sr.Reset(); + if(dl_plus_sr.GetDLPlusLink(dl_plus_link) && dl_plus_link != current_flag) + dl_plus_sr.Reset(); + DataGroup::Reset(); -// fprintf(stderr, "DynamicLabelDecoder: segnum %d, toggle: %s, chars_len: %2d%s\n", dl_seg.SegNum(), dl_seg.Toggle() ? "Y" : "N", dl_seg.chars.size(), dl_seg.Last() ? " [LAST]" : ""); +// fprintf(stderr, "DynamicLabelDecoder: segnum %d, toggle: %s, DL Plus: %s, chars_len: %2zu%s\n", +// dl_seg.SegNum(), dl_seg.Toggle() ? "Y" : "N", cmd_dl_plus ? "Y" : "N", dl_seg.chars.size(), dl_seg.Last() ? " [LAST]" : ""); - // try to add segment - if(!dl_sr.AddSegment(dl_seg)) - return false; + if(cmd_dl_plus) { + // try to add segment + if(!dl_plus_sr.AddSegment(dl_seg)) + return false; + + // ensure DL is already completed + if(!dl_sr.CheckForCompleteLabel()) + return false; + } else { + // try to add segment + if(!dl_sr.AddSegment(dl_seg)) + return false; + } // append new label + label.Reset(); label.raw = dl_sr.label_raw; label.charset = dl_sr.dl_segs[0].prefix[1] >> 4; + + // if completed, append also DL Plus + if(dl_plus_sr.CheckForCompleteLabel()) + AppendDLPlus(); + return true; } +void DynamicLabelDecoder::AppendDLPlus() { + std::vector& dl_plus_cmd = dl_plus_sr.label_raw; + + // abort, if not DL Plus tags command + if((dl_plus_cmd[0]) >> 4 != 0b0000) + return; + +// fprintf(stderr, "### decoded DL Plus ###\n"); + + label.dl_plus_it = dl_plus_cmd[0] & 0x08; + label.dl_plus_ir = dl_plus_cmd[0] & 0x04; + size_t nt = dl_plus_cmd[0] & 0x03; + + // retrieve DL text + std::string label_text = CharsetTools::ConvertTextToUTF8(&label.raw[0], label.raw.size(), label.charset, false, nullptr); + +// bool link_bit = false; +// fprintf(stderr, " Link: %s, Item Toggle: %s, Item Running: %s\n", +// dl_plus_sr.GetDLPlusLink(link_bit) ? (link_bit ? "Y" : "N") : "-", label.dl_plus_it ? "Y" : "N", label.dl_plus_ir ? "Y" : "N"); + + // process tags + for(size_t i = 0; i < (nt + 1); i++) { + uint8_t* dl_plus_tag = &dl_plus_cmd[1 + i * 3]; + + int content_type = dl_plus_tag[0] & 0x7F; + size_t start_marker = dl_plus_tag[1] & 0x7F; + size_t length_marker = dl_plus_tag[2] & 0x7F; + + // extract object text from DL text + std::string text; + if(content_type != 0) { + text = StringTools::UTF8Substr(label_text, start_marker, length_marker + 1); +// fprintf(stderr, " content_type: %-25s, start marker: %2zu, length marker: %2zu, '%s'\n", +// ConvertDLPlusContentTypeToString(content_type).c_str(), start_marker, length_marker, text.c_str()); + } else { +// fprintf(stderr, " DUMMY\n"); + } + + // store object + label.dl_plus_objects.push_back(DL_PLUS_OBJECT(content_type, text)); + } +} + +const char* DynamicLabelDecoder::dl_plus_content_types[] = { + "DUMMY", + "ITEM.TITLE", "ITEM.ALBUM", "ITEM.TRACKNUMBER", "ITEM.ARTIST", "ITEM.COMPOSITION", "ITEM.MOVEMENT", "ITEM.CONDUCTOR", "ITEM.COMPOSER", "ITEM.BAND", "ITEM.COMMENT", "ITEM.GENRE", + "INFO.NEWS", "INFO.NEWS.LOCAL", "INFO.STOCKMARKET", "INFO.SPORT", "INFO.LOTTERY", "INFO.HOROSCOPE", "INFO.DAILY_DIVERSION", "INFO.HEALTH", "INFO.EVENT", "INFO.SCENE", "INFO.CINEMA", "INFO.TV", "INFO.DATE_TIME", "INFO.WEATHER", "INFO.TRAFFIC", "INFO.ALARM", "INFO.ADVERTISEMENT", "INFO.URL", "INFO.OTHER", + "STATIONNAME.SHORT", "STATIONNAME.LONG", + "PROGRAMME.NOW", "PROGRAMME.NEXT", "PROGRAMME.PART", "PROGRAMME.HOST", "PROGRAMME.EDITORIAL_STAFF", "PROGRAMME.FREQUENCY", "PROGRAMME.HOMEPAGE", "PROGRAMME.SUBCHANNEL", + "PHONE.HOTLINE", "PHONE.STUDIO", "PHONE.OTHER", + "SMS.STUDIO", "SMS.OTHER", + "EMAIL.HOTLINE", "EMAIL.STUDIO", "EMAIL.OTHER", + "MMS.OTHER", + "CHAT", "CHAT.CENTER", + "VOTE.QUESTION", "VOTE.CENTRE", + "(reserved)", "(reserved)", + "(private class)", "(private class)", "(private class)", + "DESCRIPTOR.PLACE", "DESCRIPTOR.APPOINTMENT", "DESCRIPTOR.IDENTIFIER", "DESCRIPTOR.PURCHASE", "DESCRIPTOR.GET_DATA" +}; + +std::string DynamicLabelDecoder::ConvertDLPlusContentTypeToString(const int value) { + return value < 64 ? dl_plus_content_types[value] : "(reserved)"; +} + // --- DL_SEG_REASSEMBLER ----------------------------------------------------------------- void DL_SEG_REASSEMBLER::Reset() { @@ -362,16 +460,13 @@ } bool DL_SEG_REASSEMBLER::AddSegment(DL_SEG &dl_seg) { - dl_segs_t::const_iterator it; - // if there are already segments with other toggle value in cache, first clear it - it = dl_segs.cbegin(); - if(it != dl_segs.cend() && it->second.Toggle() != dl_seg.Toggle()) + bool current_toggle; + if(GetToggle(current_toggle) && current_toggle != dl_seg.Toggle()) dl_segs.clear(); // if the segment is already there, abort - it = dl_segs.find(dl_seg.SegNum()); - if(it != dl_segs.cend()) + if(dl_segs.find(dl_seg.SegNum()) != dl_segs.end()) return false; // add segment @@ -381,6 +476,20 @@ return CheckForCompleteLabel(); } +bool DL_SEG_REASSEMBLER::GetToggle(bool& result) { + if(dl_segs.empty()) + return false; + result = dl_segs.cbegin()->second.Toggle(); + return true; +} + +bool DL_SEG_REASSEMBLER::GetDLPlusLink(bool& result) { + if(dl_segs.empty()) + return false; + result = dl_segs.cbegin()->second.DLPlusLink(); + return true; +} + bool DL_SEG_REASSEMBLER::CheckForCompleteLabel() { dl_segs_t::const_iterator it; diff -Nru dablin-1.13.0/src/pad_decoder.h dablin-1.14.0/src/pad_decoder.h --- dablin-1.13.0/src/pad_decoder.h 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/src/pad_decoder.h 2022-01-12 17:40:00.000000000 +0000 @@ -1,6 +1,6 @@ /* DABlin - capital DAB experience - Copyright (C) 2015-2019 Stefan Pöschel + Copyright (C) 2015-2021 Stefan Pöschel This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -40,6 +40,9 @@ bool Toggle() const {return prefix[0] & 0x80;} bool First() const {return prefix[0] & 0x40;} bool Last() const {return prefix[0] & 0x20;} + + bool DLPlusLink() const {return prefix[1] & 0x80;} + int SegNum() const {return First() ? 0 : ((prefix[1] & 0x70) >> 4);} }; @@ -90,18 +93,43 @@ bool AddSegment(DL_SEG &dl_seg); bool CheckForCompleteLabel(); void Reset(); + + bool GetToggle(bool& result); + bool GetDLPlusLink(bool& result); }; +// --- DL_PLUS_OBJECT ----------------------------------------------------------------- +struct DL_PLUS_OBJECT { + int content_type; + std::string text; + + DL_PLUS_OBJECT() : content_type(0) {} // = DUMMY + DL_PLUS_OBJECT(int content_type, std::string text) : + content_type(content_type), + text(text) + {} +}; + +typedef std::vector dl_plus_objects_t; + // --- DL_STATE ----------------------------------------------------------------- struct DL_STATE { std::vector raw; int charset; + bool dl_plus_it; + bool dl_plus_ir; + dl_plus_objects_t dl_plus_objects; + DL_STATE() {Reset();} void Reset() { raw.clear(); charset = -1; + + dl_plus_it = false; + dl_plus_ir = false; + dl_plus_objects.clear(); } }; @@ -110,16 +138,22 @@ class DynamicLabelDecoder : public DataGroup { private: DL_SEG_REASSEMBLER dl_sr; + DL_SEG_REASSEMBLER dl_plus_sr; DL_STATE label; size_t GetInitialNeededSize() {return 2 + CalcCRC::CRCLen;} // at least prefix + CRC bool DecodeDataGroup(); + void AppendDLPlus(); + + static const char* dl_plus_content_types[]; public: DynamicLabelDecoder() : DataGroup(2 + 16 + CalcCRC::CRCLen) {Reset();} void Reset(); DL_STATE GetLabel() {return label;} + + static std::string ConvertDLPlusContentTypeToString(const int value); }; diff -Nru dablin-1.13.0/src/sdl_output.h dablin-1.14.0/src/sdl_output.h --- dablin-1.13.0/src/sdl_output.h 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/src/sdl_output.h 2022-01-12 17:40:00.000000000 +0000 @@ -19,6 +19,9 @@ #ifndef SDL_OUTPUT_H_ #define SDL_OUTPUT_H_ +// SDL.h must be included before or it won't compile on OS X +#include "SDL.h" + #include #include #include @@ -26,8 +29,6 @@ #include #include -#include "SDL.h" - #include "audio_output.h" #include "tools.h" diff -Nru dablin-1.13.0/src/tools.cpp dablin-1.14.0/src/tools.cpp --- dablin-1.13.0/src/tools.cpp 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/src/tools.cpp 2022-01-12 17:40:00.000000000 +0000 @@ -1,6 +1,6 @@ /* DABlin - capital DAB experience - Copyright (C) 2015-2019 Stefan Pöschel + Copyright (C) 2015-2021 Stefan Pöschel This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -72,6 +72,11 @@ return result; } +size_t StringTools::UTF8Len(const std::string &s) { + // ignore continuation bytes + return std::count_if(s.cbegin(), s.cend(), [](const char c){return (c & 0xC0) != 0x80;}); +} + std::string StringTools::UTF8Substr(const std::string &s, size_t pos, size_t count) { std::string result = s; result.erase(0, UTF8CharsLen(result, pos)); diff -Nru dablin-1.13.0/src/tools.h dablin-1.14.0/src/tools.h --- dablin-1.13.0/src/tools.h 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/src/tools.h 2022-01-12 17:40:00.000000000 +0000 @@ -1,6 +1,6 @@ /* DABlin - capital DAB experience - Copyright (C) 2015-2019 Stefan Pöschel + Copyright (C) 2015-2021 Stefan Pöschel This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,6 +39,7 @@ public: static string_vector_t SplitString(const std::string &s, const char delimiter); static std::string MsToTimecode(long int value); + static size_t UTF8Len(const std::string &s); static std::string UTF8Substr(const std::string &s, size_t pos, size_t count); static std::string IntToHex(int value, size_t nibbles); }; diff -Nru dablin-1.13.0/src/version.h dablin-1.14.0/src/version.h --- dablin-1.13.0/src/version.h 2020-04-15 20:56:52.000000000 +0000 +++ dablin-1.14.0/src/version.h 2022-01-12 17:40:00.000000000 +0000 @@ -1,6 +1,6 @@ /* DABlin - capital DAB experience - Copyright (C) 2016-2020 Stefan Pöschel + Copyright (C) 2016-2022 Stefan Pöschel This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,7 +23,7 @@ // usually externally derived from git #ifndef DABLIN_VERSION -#define DABLIN_VERSION "1.13.0" +#define DABLIN_VERSION "1.14.0" #endif