From ec75b9e317baf249d67295300bc5308b7c33f4ac Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 31 Mar 2020 17:07:38 +0200 Subject: Fix GStreamer input, rework ICY-Text write --- Makefile.am | 5 ++- configure.ac | 2 + src/GSTInput.cpp | 112 +++++++++++++++++++++++++++++++++++++++++++++------ src/GSTInput.h | 19 ++++++--- src/VLCInput.cpp | 86 +++------------------------------------ src/VLCInput.h | 42 ++----------------- src/odr-audioenc.cpp | 55 ++++++++++++++++--------- src/utils.c | 43 -------------------- src/utils.cpp | 96 +++++++++++++++++++++++++++++++++++++++++++ src/utils.h | 45 +++++++++++++++++++-- 10 files changed, 302 insertions(+), 203 deletions(-) delete mode 100644 src/utils.c create mode 100644 src/utils.cpp diff --git a/Makefile.am b/Makefile.am index d05aa7f..29ac878 100644 --- a/Makefile.am +++ b/Makefile.am @@ -82,6 +82,9 @@ odr_audioenc_LDADD = libtoolame-dab.la \ $(odr_audioenc_LDADD_ALSA) \ $(LIBVLC_LIBS) $(GST_LIBS) odr_audioenc_CXXFLAGS = $(GST_CFLAGS) $(GITVERSION_FLAGS) \ + -Ifdk-aac/libSYS/include/ \ + -Ifdk-aac/libAACenc/include/ \ + -Ifdk-aac/libAACdec/include/ \ -Wall -ggdb -O2 -Isrc -Icontrib odr_audioenc_SOURCES = src/odr-audioenc.cpp \ @@ -105,7 +108,7 @@ odr_audioenc_SOURCES = src/odr-audioenc.cpp \ src/encryption.c \ src/encryption.h \ src/zmq.hpp \ - src/utils.c \ + src/utils.cpp \ src/utils.h \ src/wavfile.cpp \ src/common.h \ diff --git a/configure.ac b/configure.ac index 5501032..17963a3 100644 --- a/configure.ac +++ b/configure.ac @@ -59,6 +59,7 @@ AS_IF([test "x$enable_gst" = "xyes"], [PKG_CHECK_MODULES(GST, [ gstreamer-1.0 >= $GST_REQUIRED gstreamer-base-1.0 >= $GST_REQUIRED + gstreamer-app-1.0 >= $GST_REQUIRED gstreamer-controller-1.0 >= $GST_REQUIRED gstreamer-audio-1.0 >= $GST_REQUIRED ], [ @@ -70,6 +71,7 @@ AS_IF([test "x$enable_gst" = "xyes"], gstreamer-1.0 >= $GST_REQUIRED gstreamer-base-1.0 >= $GST_REQUIRED + gstreamer-app-1.0 >= $GST_REQUIRED gstreamer-controller-1.0 >= $GST_REQUIRED gstreamer-audio-1.0 >= $GST_REQUIRED diff --git a/src/GSTInput.cpp b/src/GSTInput.cpp index 41fbfc0..bc7d44b 100644 --- a/src/GSTInput.cpp +++ b/src/GSTInput.cpp @@ -26,6 +26,7 @@ #include #include +#include #include "GSTInput.h" @@ -46,8 +47,7 @@ GSTInput::GSTInput(const std::string& uri, m_uri(uri), m_channels(channels), m_rate(rate), - m_gst_data(queue), - m_samplequeue(queue) + m_gst_data(queue) { } static void error_cb(GstBus *bus, GstMessage *msg, GSTData *data) @@ -61,8 +61,6 @@ static void error_cb(GstBus *bus, GstMessage *msg, GSTData *data) g_printerr("Debugging information: %s\n", debug_info ? debug_info : "none"); g_clear_error(&err); g_free(debug_info); - - g_main_loop_quit(data->main_loop); } static void cb_newpad(GstElement *decodebin, GstPad *pad, GSTData *data) @@ -89,10 +87,9 @@ static void cb_newpad(GstElement *decodebin, GstPad *pad, GSTData *data) g_object_unref(audiopad); } -static GstFlowReturn new_sample (GstElement *sink, GSTData *data) { - GstSample *sample; +static GstFlowReturn new_sample(GstElement *sink, GSTData *data) { /* Retrieve the buffer */ - g_signal_emit_by_name(sink, "pull-sample", &sample); + GstSample* sample = gst_app_sink_pull_sample(GST_APP_SINK(sink)); if (sample) { GstBuffer* buffer = gst_sample_get_buffer(sample); @@ -121,6 +118,14 @@ void GSTInput::prepare() m_gst_data.audio_convert = gst_element_factory_make("audioconvert", "audio_convert"); assert(m_gst_data.audio_convert != nullptr); + m_gst_data.audio_resample = gst_element_factory_make("audioresample", "audio_resample"); + assert(m_gst_data.audio_resample != nullptr); + g_object_set(m_gst_data.audio_resample, + "sinc-filter-mode", GST_AUDIO_RESAMPLER_FILTER_MODE_FULL, + "quality", 6, // between 0 and 10, 10 being best + /* default audio-resampler-method: GST_AUDIO_RESAMPLER_METHOD_KAISER */ + NULL); + m_gst_data.caps_filter = gst_element_factory_make("capsfilter", "caps_filter"); assert(m_gst_data.caps_filter != nullptr); @@ -135,6 +140,7 @@ void GSTInput::prepare() m_gst_data.pipeline = gst_pipeline_new("pipeline"); assert(m_gst_data.pipeline != nullptr); + // TODO also set max-buffers g_object_set(m_gst_data.app_sink, "emit-signals", TRUE, "caps", audio_caps, NULL); g_signal_connect(m_gst_data.app_sink, "new-sample", G_CALLBACK(new_sample), &m_gst_data); gst_caps_unref(audio_caps); @@ -142,11 +148,13 @@ void GSTInput::prepare() gst_bin_add_many(GST_BIN(m_gst_data.pipeline), m_gst_data.uridecodebin, m_gst_data.audio_convert, + m_gst_data.audio_resample, m_gst_data.caps_filter, m_gst_data.app_sink, NULL); if (gst_element_link_many( m_gst_data.audio_convert, + m_gst_data.audio_resample, m_gst_data.caps_filter, m_gst_data.app_sink, NULL) != true) { throw runtime_error("Could not link GST elements"); @@ -157,23 +165,101 @@ void GSTInput::prepare() g_signal_connect(G_OBJECT(m_gst_data.bus), "message::error", (GCallback)error_cb, &m_gst_data); gst_element_set_state(m_gst_data.pipeline, GST_STATE_PLAYING); + + m_running = true; + m_thread = std::thread(&GSTInput::process, this); } bool GSTInput::read_source(size_t num_bytes) { - // Reading done in glib main loop - GstMessage *msg = gst_bus_pop_filtered(m_gst_data.bus, GST_MESSAGE_EOS); + return m_running; +} - if (msg) { +ICY_TEXT_t GSTInput::get_icy_text() const +{ + ICY_TEXT_t now_playing; + { + std::lock_guard lock(m_nowplaying_mutex); + now_playing = m_nowplaying; + } + + return now_playing; +} + +void GSTInput::process() +{ + while (m_running) { + GstMessage *msg = gst_bus_timed_pop(m_gst_data.bus, 100000); + + if (not msg) { + continue; + } + + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_BUFFERING: + { + gint percent = 0; + gst_message_parse_buffering(msg, &percent); + //fprintf(stderr, "GST buffering %d\n", percent); + break; + } + case GST_MESSAGE_TAG: + { + GstTagList *tags = nullptr; + gst_message_parse_tag(msg, &tags); + //fprintf(stderr, "Got tags from element %s\n", GST_OBJECT_NAME(msg->src)); + + string new_title; + + auto extract_title = [](const GstTagList *list, const gchar *tag, void *user_data) { + GValue val = { 0, }; + + auto new_title = (string*)user_data; + + gst_tag_list_copy_value(&val, list, tag); + + if (strcmp(tag, "title") == 0 and G_VALUE_HOLDS_STRING(&val)) { + *new_title = g_value_dup_string(&val); + } + + g_value_unset(&val); + }; + + gst_tag_list_foreach(tags, extract_title, &new_title); + + gst_tag_list_unref(tags); + { + std::lock_guard lock(m_nowplaying_mutex); + m_nowplaying.useNowPlaying(new_title); + } + break; + } + case GST_MESSAGE_ERROR: + { + GError *err = nullptr; + gst_message_parse_error(msg, &err, nullptr); + fprintf(stderr, "GST error: %s\n", err->message); + g_error_free(err); + m_fault = true; + break; + } + case GST_MESSAGE_EOS: + m_fault = true; + break; + default: + //fprintf(stderr, "GST message %s\n", gst_message_type_get_name(GST_MESSAGE_TYPE(msg))); + break; + } gst_message_unref(msg); - return false; } - return true; } GSTInput::~GSTInput() { - fprintf(stderr, "<<<<<<<<<<<<<<<<<<<< DTOR\n"); + m_running = false; + if (m_thread.joinable()) { + m_thread.join(); + } if (m_gst_data.bus) { gst_object_unref(m_gst_data.bus); diff --git a/src/GSTInput.h b/src/GSTInput.h index 07cf62e..4bfae34 100644 --- a/src/GSTInput.h +++ b/src/GSTInput.h @@ -27,6 +27,8 @@ #if HAVE_GST #include +#include +#include #include #include #include @@ -36,10 +38,7 @@ #include "SampleQueue.h" #include "common.h" #include "InputInterface.h" - -extern "C" { #include "utils.h" -} struct GSTData { GSTData(SampleQueue& samplequeue); @@ -47,11 +46,11 @@ struct GSTData { GstElement *pipeline = nullptr; GstElement *uridecodebin = nullptr; GstElement *audio_convert = nullptr; + GstElement *audio_resample = nullptr; GstElement *caps_filter = nullptr; GstElement *app_sink = nullptr; GstBus *bus = nullptr; - GMainLoop *main_loop = nullptr; SampleQueue& samplequeue; }; @@ -72,9 +71,11 @@ class GSTInput : public InputInterface virtual bool read_source(size_t num_bytes) override; + ICY_TEXT_t get_icy_text() const; + int getRate() { return m_rate; } - virtual bool fault_detected(void) const override { return false; }; + virtual bool fault_detected(void) const override { return m_fault; }; private: std::string m_uri; unsigned m_channels; @@ -82,7 +83,13 @@ class GSTInput : public InputInterface GSTData m_gst_data; - SampleQueue& m_samplequeue; + mutable std::mutex m_nowplaying_mutex; + ICY_TEXT_t m_nowplaying; + + void process(); + std::atomic m_fault = ATOMIC_VAR_INIT(false); + std::atomic m_running; + std::thread m_thread; }; #endif // HAVE_GST diff --git a/src/VLCInput.cpp b/src/VLCInput.cpp index d2ae4f0..7b10d81 100644 --- a/src/VLCInput.cpp +++ b/src/VLCInput.cpp @@ -431,91 +431,17 @@ ssize_t VLCInput::m_read(uint8_t* buf, size_t length) return err; } -const std::string VLCInput::ICY_TEXT_SEPARATOR = " - "; -/*! Write the corresponding text to a file readable by ODR-PadEnc, with optional - * DL+ information. The text is passed as a copy because we actually use the - * m_nowplaying variable which is also accessed in another thread, so better - * make a copy. - * - * \return false on failure - */ -bool write_icy_to_file(const ICY_TEXT_T text, const std::string& filename, bool dl_plus) +ICY_TEXT_t VLCInput::get_icy_text() const { - FILE* fd = fopen(filename.c_str(), "wb"); - if (fd) { - bool ret = true; - bool artist_title_used = !text.artist.empty() and !text.title.empty(); - - // if desired, prepend DL Plus information - if (dl_plus) { - std::stringstream ss; - ss << "##### parameters { #####\n"; - ss << "DL_PLUS=1\n"; - - // if non-empty text, add tag - if (artist_title_used) { - size_t artist_len = strlen_utf8(text.artist.c_str()); - size_t title_start = artist_len + strlen_utf8(VLCInput::ICY_TEXT_SEPARATOR.c_str()); - - // ITEM.ARTIST - ss << "DL_PLUS_TAG=4 0 " << (artist_len - 1) << "\n"; // -1 ! - - // ITEM.TITLE - ss << "DL_PLUS_TAG=1 " << title_start << " " << (strlen_utf8(text.title.c_str()) - 1) << "\n"; // -1 ! - } else if (!text.now_playing.empty()) { - // PROGRAMME.NOW - ss << "DL_PLUS_TAG=33 0 " << (strlen_utf8(text.now_playing.c_str()) - 1) << "\n"; // -1 ! - } - - ss << "##### parameters } #####\n"; - ret &= fputs(ss.str().c_str(), fd) >= 0; - } - - if (artist_title_used) { - ret &= fputs(text.artist.c_str(), fd) >= 0; - ret &= fputs(VLCInput::ICY_TEXT_SEPARATOR.c_str(), fd) >= 0; - ret &= fputs(text.title.c_str(), fd) >= 0; - } - else { - ret &= fputs(text.now_playing.c_str(), fd) >= 0; - } - fclose(fd); - - return ret; - } - - return false; -} - -void VLCInput::write_icy_text(const std::string& filename, bool dl_plus) -{ - if (icy_text_written.valid()) { - auto status = icy_text_written.wait_for(std::chrono::microseconds(1)); - if (status == std::future_status::ready) { - if (not icy_text_written.get()) { - fprintf(stderr, "Failed to write ICY Text to file!\n"); - } - } - } - - else { + ICY_TEXT_t now_playing; + { std::lock_guard lock(m_nowplaying_mutex); - - if (m_nowplaying_previous != m_nowplaying) { - /*! We write the ICY text in a separate task because - * we do not want to have a delay due to IO - */ - icy_text_written = std::async(std::launch::async, - std::bind(write_icy_to_file, m_nowplaying, filename, dl_plus)); - - } - - m_nowplaying_previous = m_nowplaying; + now_playing = m_nowplaying; } -} - + return now_playing; +} /*! How many samples we insert into the queue each call * 10 samples @ 32kHz = 3.125ms diff --git a/src/VLCInput.h b/src/VLCInput.h index 0eb3e37..47a9cdd 100644 --- a/src/VLCInput.h +++ b/src/VLCInput.h @@ -41,42 +41,12 @@ #include "SampleQueue.h" #include "common.h" #include "InputInterface.h" - -extern "C" { #include "utils.h" -} /*! Common functionality for the direct libvlc input and the * threaded libvlc input */ -struct ICY_TEXT_T { - std::string artist; - std::string title; - std::string now_playing; - - bool operator==(const ICY_TEXT_T& other) const { - return - artist == other.artist and - title == other.title and - now_playing == other.now_playing; - } - bool operator!=(const ICY_TEXT_T& other) const { - return !(*this == other); - } - void useArtistTitle(const std::string& artist, const std::string& title) { - this->artist = artist; - this->title = title; - now_playing = ""; - } - void useNowPlaying(const std::string& now_playing) { - artist = ""; - title = ""; - this->now_playing = now_playing; - } -}; - - class VLCInput : public InputInterface { public: @@ -114,7 +84,7 @@ class VLCInput : public InputInterface /*! Write the last received ICY-Text to the * file. */ - void write_icy_text(const std::string& filename, bool dl_plus); + ICY_TEXT_t get_icy_text() const; //! Callbacks for VLC @@ -135,10 +105,6 @@ class VLCInput : public InputInterface int getRate() { return m_rate; } virtual bool fault_detected(void) const override { return m_fault; }; - - /*! Separator string used when artist/title are written - */ - static const std::string ICY_TEXT_SEPARATOR; private: /*! Stop the player and release resources */ @@ -178,10 +144,8 @@ class VLCInput : public InputInterface /*! VLC can give us the ICY-Text from an Icecast stream, * which we optionally write into a text file for ODR-PadEnc */ - std::future icy_text_written; - std::mutex m_nowplaying_mutex; - ICY_TEXT_T m_nowplaying; - ICY_TEXT_T m_nowplaying_previous; + mutable std::mutex m_nowplaying_mutex; + ICY_TEXT_t m_nowplaying; // VLC pointers libvlc_instance_t *m_vlc; diff --git a/src/odr-audioenc.cpp b/src/odr-audioenc.cpp index aab76b2..e077981 100644 --- a/src/odr-audioenc.cpp +++ b/src/odr-audioenc.cpp @@ -61,10 +61,10 @@ #include "Outputs.h" #include "common.h" #include "wavfile.h" +#include "utils.h" extern "C" { #include "encryption.h" -#include "utils.h" } #include @@ -98,9 +98,7 @@ using vec_u8 = std::vector; using namespace std; - - -void usage(const char* name) +static void usage(const char* name) { fprintf(stderr, "ODR-AudioEnc %s is an audio encoder for both DAB and DAB+.\n" @@ -160,8 +158,6 @@ void usage(const char* name) " multiple times)\n" " -L OPTION Give an additional options to VLC (can be given\n" " multiple times)\n" - " -w, --write-icy-text=filename Write the ICY Text into the file, so that ODR-PadEnc can read it.\n" - " -W, --write-icy-text-dl-plus When writing the ICY Text into the file, add DL Plus information.\n" #else " The VLC input was disabled at compile-time\n" #endif @@ -171,6 +167,8 @@ void usage(const char* name) #else " The GStreamer input was disabled at compile-time\n" #endif + " -w, --write-icy-text=filename Write the ICY Text into the file, so that ODR-PadEnc can read it.\n" + " -W, --write-icy-text-dl-plus When writing the ICY Text into the file, add DL Plus information.\n" " Drift compensation\n" " -D, --drift-comp Enable ALSA/VLC sound card drift compensation.\n" " Encoder parameters:\n" @@ -407,6 +405,9 @@ public: int sample_rate=48000; int channels=2; + string icytext_file; + bool icytext_dlplus = false; + // For the ALSA input string alsa_device; @@ -417,13 +418,12 @@ public: // For the VLC input string vlc_uri; - string vlc_icytext_file; - bool vlc_icytext_dlplus = false; string vlc_gain; string vlc_cache; vector vlc_additional_opts; unsigned verbosity = 0; + // For the GST input string gst_uri; string jack_name; @@ -985,14 +985,33 @@ int AudioEnc::run() * The VLC input is the only input that can also give us metadata, which * we can hand over to ODR-PadEnc. */ + if (not icytext_file.empty()) { + ICY_TEXT_t text; + + if (false) {} #if HAVE_VLC - if (not vlc_uri.empty() and not vlc_icytext_file.empty()) { // Using std::dynamic_pointer_cast would be safer, but is C++17 - VLCInput *vlc_input = (VLCInput*)(input.get()); - vlc_input->write_icy_text(vlc_icytext_file, vlc_icytext_dlplus); - } + else if (not vlc_uri.empty()) { + VLCInput *vlc_input = (VLCInput*)(input.get()); + text = vlc_input->get_icy_text(); + } +#endif +#if HAVE_GST + else if (not gst_uri.empty()) { + GSTInput *gst_input = (GSTInput*)(input.get()); + text = gst_input->get_icy_text(); + } #endif + if (text) { + bool success = write_icy_to_file(text, icytext_file, icytext_dlplus); + + if (not success) { + fprintf(stderr, "Failed to write ICY Text\n"); + } + } + } + /*! \section AudioLevel * Audio level measurement is always done assuming we have two * channels, and is formally wrong in mono, but still gives @@ -1536,15 +1555,15 @@ int main(int argc, char *argv[]) case 'S': audio_enc.send_stats_to = optarg; break; -#ifdef HAVE_VLC - case 'v': - audio_enc.vlc_uri = optarg; - break; case 'w': - audio_enc.vlc_icytext_file = optarg; + audio_enc.icytext_file = optarg; break; case 'W': - audio_enc.vlc_icytext_dlplus = true; + audio_enc.icytext_dlplus = true; + break; +#ifdef HAVE_VLC + case 'v': + audio_enc.vlc_uri = optarg; break; case 'g': audio_enc.vlc_gain = optarg; diff --git a/src/utils.c b/src/utils.c deleted file mode 100644 index 928304e..0000000 --- a/src/utils.c +++ /dev/null @@ -1,43 +0,0 @@ -#include "utils.h" -#include -#include -#include - -#define MIN(a,b) (((a)<(b))?(a):(b)) -#define MAX(a,b) (((a)>(b))?(a):(b)) - -/* Taken from sox */ -const char* level(int channel, int peak) -{ - static char const * const text[][2] = { - /* White: 2dB steps */ - {"", ""}, {"-", "-"}, {"=", "="}, {"-=", "=-"}, - {"==", "=="}, {"-==", "==-"}, {"===", "==="}, {"-===", "===-"}, - {"====", "===="}, {"-====", "====-"}, {"=====", "====="}, - {"-=====", "=====-"}, {"======", "======"}, - /* Red: 1dB steps */ - {"!=====", "=====!"}, - }; - int const red = 1, white = NUMOF(text) - red; - - double linear = ((double)peak) / INT16_MAX; - - int vu_dB = linear ? floor(2 * white + red + linear_to_dB(linear)) : 0; - - int index = vu_dB < 2 * white ? - MAX(vu_dB / 2, 0) : - MIN(vu_dB - white, red + white - 1); - - return text[index][channel]; -} - -size_t strlen_utf8(const char *s) { - size_t result = 0; - - // ignore continuation bytes - only count single/leading bytes - while (*s) - if ((*s++ & 0xC0) != 0x80) - result++; - - return result; -} diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..525f05e --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,96 @@ +#include +#include +#include +#include + +#include "utils.h" +#include + +#define MIN(a,b) (((a)<(b))?(a):(b)) +#define MAX(a,b) (((a)>(b))?(a):(b)) + +/* Taken from sox */ +const char* level(int channel, int peak) +{ + static char const * const text[][2] = { + /* White: 2dB steps */ + {"", ""}, {"-", "-"}, {"=", "="}, {"-=", "=-"}, + {"==", "=="}, {"-==", "==-"}, {"===", "==="}, {"-===", "===-"}, + {"====", "===="}, {"-====", "====-"}, {"=====", "====="}, + {"-=====", "=====-"}, {"======", "======"}, + /* Red: 1dB steps */ + {"!=====", "=====!"}, + }; + int const red = 1, white = NUMOF(text) - red; + + double linear = ((double)peak) / INT16_MAX; + + int vu_dB = linear ? floor(2 * white + red + linear_to_dB(linear)) : 0; + + int index = vu_dB < 2 * white ? + MAX(vu_dB / 2, 0) : + MIN(vu_dB - white, red + white - 1); + + return text[index][channel]; +} + +size_t strlen_utf8(const char *s) { + size_t result = 0; + + // ignore continuation bytes - only count single/leading bytes + while (*s) + if ((*s++ & 0xC0) != 0x80) + result++; + + return result; +} + +static const std::string ICY_TEXT_SEPARATOR = " - "; + +bool write_icy_to_file(const ICY_TEXT_t text, const std::string& filename, bool dl_plus) +{ + FILE* fd = fopen(filename.c_str(), "wb"); + if (fd) { + bool ret = true; + bool artist_title_used = !text.artist.empty() and !text.title.empty(); + + // if desired, prepend DL Plus information + if (dl_plus) { + std::stringstream ss; + ss << "##### parameters { #####\n"; + ss << "DL_PLUS=1\n"; + + // if non-empty text, add tag + if (artist_title_used) { + size_t artist_len = strlen_utf8(text.artist.c_str()); + size_t title_start = artist_len + strlen_utf8(ICY_TEXT_SEPARATOR.c_str()); + + // ITEM.ARTIST + ss << "DL_PLUS_TAG=4 0 " << (artist_len - 1) << "\n"; // -1 ! + + // ITEM.TITLE + ss << "DL_PLUS_TAG=1 " << title_start << " " << (strlen_utf8(text.title.c_str()) - 1) << "\n"; // -1 ! + } else if (!text.now_playing.empty()) { + // PROGRAMME.NOW + ss << "DL_PLUS_TAG=33 0 " << (strlen_utf8(text.now_playing.c_str()) - 1) << "\n"; // -1 ! + } + + ss << "##### parameters } #####\n"; + ret &= fputs(ss.str().c_str(), fd) >= 0; + } + + if (artist_title_used) { + ret &= fputs(text.artist.c_str(), fd) >= 0; + ret &= fputs(ICY_TEXT_SEPARATOR.c_str(), fd) >= 0; + ret &= fputs(text.title.c_str(), fd) >= 0; + } + else { + ret &= fputs(text.now_playing.c_str(), fd) >= 0; + } + fclose(fd); + + return ret; + } + + return false; +} diff --git a/src/utils.h b/src/utils.h index ca77a53..2f6b639 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,8 +1,9 @@ #pragma once -#include -#include -#include +#include +#include +#include +#include #define NUMOF(l) (sizeof(l) / sizeof(*l)) @@ -15,3 +16,41 @@ const char* level(int channel, int peak); size_t strlen_utf8(const char *s); +struct ICY_TEXT_t { + std::string artist; + std::string title; + std::string now_playing; + + operator bool() const { + return not (artist.empty() and title.empty() and now_playing.empty()); + } + + bool operator==(const ICY_TEXT_t& other) const { + return + artist == other.artist and + title == other.title and + now_playing == other.now_playing; + } + bool operator!=(const ICY_TEXT_t& other) const { + return !(*this == other); + } + void useArtistTitle(const std::string& artist, const std::string& title) { + this->artist = artist; + this->title = title; + now_playing = ""; + } + void useNowPlaying(const std::string& now_playing) { + artist = ""; + title = ""; + this->now_playing = now_playing; + } +}; + +/*! Write the corresponding text to a file readable by ODR-PadEnc, with optional + * DL+ information. The text is passed as a copy because we actually use the + * m_nowplaying variable which is also accessed in another thread, so better + * make a copy. + * + * \return false on failure + */ +bool write_icy_to_file(const ICY_TEXT_t text, const std::string& filename, bool dl_plus); -- cgit v1.2.3