diff options
-rw-r--r-- | Makefile.am | 11 | ||||
-rw-r--r-- | configure.ac | 40 | ||||
-rw-r--r-- | src/GSTInput.cpp | 188 | ||||
-rw-r--r-- | src/GSTInput.h | 89 | ||||
-rw-r--r-- | src/odr-audioenc.cpp | 45 | ||||
-rw-r--r-- | src/utils.c | 3 | ||||
-rw-r--r-- | src/utils.h | 3 |
7 files changed, 359 insertions, 20 deletions
diff --git a/Makefile.am b/Makefile.am index 176439b..d05aa7f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -80,12 +80,9 @@ odr_audioenc_LDADD = libtoolame-dab.la \ -lzmq \ $(odr_audioenc_LDADD_JACK) \ $(odr_audioenc_LDADD_ALSA) \ - $(LIBVLC_LIBS) -odr_audioenc_CXXFLAGS = $(GITVERSION_FLAGS) \ - -Wall -ggdb -O2 -Isrc -Icontrib \ - -Ifdk-aac/libSYS/include/ \ - -Ifdk-aac/libAACenc/include/ \ - -Ifdk-aac/libAACdec/include/ + $(LIBVLC_LIBS) $(GST_LIBS) +odr_audioenc_CXXFLAGS = $(GST_CFLAGS) $(GITVERSION_FLAGS) \ + -Wall -ggdb -O2 -Isrc -Icontrib odr_audioenc_SOURCES = src/odr-audioenc.cpp \ src/FileInput.cpp \ @@ -94,6 +91,8 @@ odr_audioenc_SOURCES = src/odr-audioenc.cpp \ src/AlsaInput.h \ src/JackInput.cpp \ src/JackInput.h \ + src/GSTInput.cpp \ + src/GSTInput.h \ src/VLCInput.cpp \ src/VLCInput.h \ src/Outputs.cpp \ diff --git a/configure.ac b/configure.ac index 8324947..5501032 100644 --- a/configure.ac +++ b/configure.ac @@ -50,6 +50,44 @@ AC_ARG_ENABLE([jack], AC_ARG_ENABLE([vlc], AS_HELP_STRING([--enable-vlc], [Enable libvlc input])) +AC_ARG_ENABLE([gst], + AS_HELP_STRING([--enable-gst], [Enable GStreamer input])) + +GST_REQUIRED=1.0.0 + +AS_IF([test "x$enable_gst" = "xyes"], + [PKG_CHECK_MODULES(GST, [ + gstreamer-1.0 >= $GST_REQUIRED + gstreamer-base-1.0 >= $GST_REQUIRED + gstreamer-controller-1.0 >= $GST_REQUIRED + gstreamer-audio-1.0 >= $GST_REQUIRED + ], [ + AC_SUBST(GST_CFLAGS) + AC_SUBST(GST_LIBS) + ], [ + AC_MSG_ERROR([ + Can't find the following GStreamer development packages: + + gstreamer-1.0 >= $GST_REQUIRED + gstreamer-base-1.0 >= $GST_REQUIRED + gstreamer-controller-1.0 >= $GST_REQUIRED + gstreamer-audio-1.0 >= $GST_REQUIRED + + Please make sure you have the necessary GStreamer-1.0 + development headers installed. + + On debian/Ubuntu systems you will probably need to install the + 'libgstreamer1.0-dev' and 'libgstreamer-plugins-base1.0-dev' packages. + + On RPM-based systems you will probably need to install the + 'gstreamer-devel-1.0' package. + ]) + ]) +]) + +AS_IF([test "x$enable_gst" = "xyes"], + AC_DEFINE(HAVE_GST, [1], [Define if GST input is enabled])) + AS_IF([test "x$enable_alsa" = "xyes"], [AM_PATH_ALSA(1.0.25)]) @@ -133,7 +171,7 @@ echo echo "Features enabled:" enabled="" disabled="" -for feature in jack vlc alsa +for feature in jack vlc alsa gst do eval var=\$enable_$feature AS_IF([test "x$var" = "xyes"], diff --git a/src/GSTInput.cpp b/src/GSTInput.cpp new file mode 100644 index 0000000..41fbfc0 --- /dev/null +++ b/src/GSTInput.cpp @@ -0,0 +1,188 @@ +/* ------------------------------------------------------------------ + * Copyright (C) 2019 Matthias P. Braendli + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. + * See the License for the specific language governing permissions + * and limitations under the License. + * ------------------------------------------------------------------- + */ + +#include <string> +#include <chrono> +#include <algorithm> +#include <functional> +#include <cstddef> +#include <cstdint> +#include <cstdio> +#include <cstring> + +#include <gst/audio/audio.h> + +#include "GSTInput.h" + +#include "config.h" + +#if HAVE_GST + +using namespace std; + +GSTData::GSTData(SampleQueue<uint8_t>& samplequeue) : + samplequeue(samplequeue) +{ } + +GSTInput::GSTInput(const std::string& uri, + int rate, + unsigned channels, + SampleQueue<uint8_t>& queue) : + m_uri(uri), + m_channels(channels), + m_rate(rate), + m_gst_data(queue), + m_samplequeue(queue) +{ } + +static void error_cb(GstBus *bus, GstMessage *msg, GSTData *data) +{ + GError *err; + gchar *debug_info; + + /* Print error details on the screen */ + gst_message_parse_error(msg, &err, &debug_info); + g_printerr("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); + 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) +{ + /* only link once */ + GstPad *audiopad = gst_element_get_static_pad(data->audio_convert, "sink"); + if (GST_PAD_IS_LINKED(audiopad)) { + g_object_unref(audiopad); + return; + } + + /* check media type */ + GstCaps *caps = gst_pad_query_caps(pad, NULL); + GstStructure *str = gst_caps_get_structure(caps, 0); + if (!g_strrstr(gst_structure_get_name(str), "audio")) { + gst_caps_unref(caps); + gst_object_unref(audiopad); + return; + } + gst_caps_unref(caps); + + gst_pad_link(pad, audiopad); + + g_object_unref(audiopad); +} + +static GstFlowReturn new_sample (GstElement *sink, GSTData *data) { + GstSample *sample; + /* Retrieve the buffer */ + g_signal_emit_by_name(sink, "pull-sample", &sample); + if (sample) { + GstBuffer* buffer = gst_sample_get_buffer(sample); + + GstMapInfo map; + gst_buffer_map(buffer, &map, GST_MAP_READ); + + data->samplequeue.push(map.data, map.size); + + gst_buffer_unmap(buffer, &map); + gst_sample_unref(sample); + + return GST_FLOW_OK; + } + return GST_FLOW_ERROR; +} + +void GSTInput::prepare() +{ + gst_init(nullptr, nullptr); + + m_gst_data.uridecodebin = gst_element_factory_make("uridecodebin", "uridecodebin"); + assert(m_gst_data.uridecodebin != nullptr); + g_object_set(m_gst_data.uridecodebin, "uri", m_uri.c_str(), nullptr); + g_signal_connect(m_gst_data.uridecodebin, "pad-added", G_CALLBACK(cb_newpad), &m_gst_data); + + m_gst_data.audio_convert = gst_element_factory_make("audioconvert", "audio_convert"); + assert(m_gst_data.audio_convert != nullptr); + + m_gst_data.caps_filter = gst_element_factory_make("capsfilter", "caps_filter"); + assert(m_gst_data.caps_filter != nullptr); + + GstAudioInfo info; + gst_audio_info_set_format(&info, GST_AUDIO_FORMAT_S16, m_rate, m_channels, NULL); + GstCaps *audio_caps = gst_audio_info_to_caps(&info); + g_object_set(m_gst_data.caps_filter, "caps", audio_caps, NULL); + + m_gst_data.app_sink = gst_element_factory_make("appsink", "app_sink"); + assert(m_gst_data.app_sink != nullptr); + + m_gst_data.pipeline = gst_pipeline_new("pipeline"); + assert(m_gst_data.pipeline != nullptr); + + 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); + + gst_bin_add_many(GST_BIN(m_gst_data.pipeline), + m_gst_data.uridecodebin, + m_gst_data.audio_convert, + m_gst_data.caps_filter, + m_gst_data.app_sink, NULL); + + if (gst_element_link_many( + m_gst_data.audio_convert, + m_gst_data.caps_filter, + m_gst_data.app_sink, NULL) != true) { + throw runtime_error("Could not link GST elements"); + } + + m_gst_data.bus = gst_element_get_bus(m_gst_data.pipeline); + gst_bus_add_signal_watch(m_gst_data.bus); + 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); +} + +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); + + if (msg) { + gst_message_unref(msg); + return false; + } + return true; +} + +GSTInput::~GSTInput() +{ + fprintf(stderr, "<<<<<<<<<<<<<<<<<<<< DTOR\n"); + + if (m_gst_data.bus) { + gst_object_unref(m_gst_data.bus); + } + + if (m_gst_data.pipeline) { + gst_element_set_state(m_gst_data.pipeline, GST_STATE_NULL); + gst_object_unref(m_gst_data.pipeline); + } +} + +#endif // HAVE_GST diff --git a/src/GSTInput.h b/src/GSTInput.h new file mode 100644 index 0000000..07cf62e --- /dev/null +++ b/src/GSTInput.h @@ -0,0 +1,89 @@ +/* ------------------------------------------------------------------ + * Copyright (C) 2019 Matthias P. Braendli + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. + * See the License for the specific language governing permissions + * and limitations under the License. + * ------------------------------------------------------------------- + */ +/*! \file GSTInput.h + * + * This input uses GStreamer to get audio data. + */ + +#pragma once + +#include "config.h" + +#if HAVE_GST + +#include <string> +#include <vector> +#include <cstddef> +#include <cstdint> + +#include <gst/gst.h> + +#include "SampleQueue.h" +#include "common.h" +#include "InputInterface.h" + +extern "C" { +#include "utils.h" +} + +struct GSTData { + GSTData(SampleQueue<uint8_t>& samplequeue); + + GstElement *pipeline = nullptr; + GstElement *uridecodebin = nullptr; + GstElement *audio_convert = nullptr; + GstElement *caps_filter = nullptr; + GstElement *app_sink = nullptr; + + GstBus *bus = nullptr; + GMainLoop *main_loop = nullptr; + + SampleQueue<uint8_t>& samplequeue; +}; + +class GSTInput : public InputInterface +{ + public: + GSTInput(const std::string& uri, + int rate, + unsigned channels, + SampleQueue<uint8_t>& queue); + + GSTInput(const GSTInput& other) = delete; + GSTInput& operator=(const GSTInput& other) = delete; + virtual ~GSTInput(); + + virtual void prepare() override; + + virtual bool read_source(size_t num_bytes) override; + + int getRate() { return m_rate; } + + virtual bool fault_detected(void) const override { return false; }; + private: + std::string m_uri; + unsigned m_channels; + int m_rate; + + GSTData m_gst_data; + + SampleQueue<uint8_t>& m_samplequeue; +}; + +#endif // HAVE_GST + diff --git a/src/odr-audioenc.cpp b/src/odr-audioenc.cpp index e0b38f5..aab76b2 100644 --- a/src/odr-audioenc.cpp +++ b/src/odr-audioenc.cpp @@ -34,6 +34,7 @@ * Interesting starting points for the encoder * - \ref odr-audioenc.cpp Main encoder file * - \ref VLCInput.h VLC Input + * - \ref GSTInput.h GST Input * - \ref AlsaInput.h Alsa Input * - \ref JackInput.h JACK Input * - \ref Outputs.h ZeroMQ, file and EDI outputs @@ -53,6 +54,7 @@ #include "FileInput.h" #include "JackInput.h" #include "VLCInput.h" +#include "GSTInput.h" #include "SampleQueue.h" #include "AACDecoder.h" #include "StatsPublish.h" @@ -65,6 +67,7 @@ extern "C" { #include "utils.h" } +#include <algorithm> #include <vector> #include <deque> #include <chrono> @@ -103,7 +106,7 @@ void usage(const char* name) "ODR-AudioEnc %s is an audio encoder for both DAB and DAB+.\n" "The encoder can read from JACK, ALSA or\n" "a file source and encode to a ZeroMQ output for ODR-DabMux.\n" - "It can also use libvlc as an input.\n" + "It can also use libvlc and GStreamer as input.\n" "\n" "The -D option enables sound card clock drift compensation.\n" "A consumer sound card has a clock that is always a bit imprecise, and\n" @@ -162,6 +165,12 @@ void usage(const char* name) #else " The VLC input was disabled at compile-time\n" #endif + " For the GStreamer input:\n" +#if HAVE_GST + " -G, --gst-uri=uri Enable GStreamer input and use the URI given as source\n" +#else + " The GStreamer input was disabled at compile-time\n" +#endif " Drift compensation\n" " -D, --drift-comp Enable ALSA/VLC sound card drift compensation.\n" " Encoder parameters:\n" @@ -415,6 +424,8 @@ public: vector<string> vlc_additional_opts; unsigned verbosity = 0; + string gst_uri; + string jack_name; bool drift_compensation = false; @@ -451,8 +462,8 @@ public: string dab_channel_mode; /* Keep track of peaks */ - int peak_left = 0; - int peak_right = 0; + int16_t peak_left = 0; + int16_t peak_right = 0; /* On silence, die after the silence_timeout expires */ bool die_on_silence = false; @@ -489,7 +500,7 @@ public: ~AudioEnc(); int run(); - bool send_frame(const uint8_t *buf, size_t len, int peak_left, int peak_right); + bool send_frame(const uint8_t *buf, size_t len, int16_t peak_left, int16_t peak_right); shared_ptr<InputInterface> initialise_input(); }; @@ -506,6 +517,9 @@ int AudioEnc::run() #if HAVE_VLC if (not vlc_uri.empty()) num_inputs++; #endif +#if HAVE_GST + if (not gst_uri.empty()) num_inputs++; +#endif if (num_inputs == 0) { fprintf(stderr, "No input defined!\n"); @@ -989,8 +1003,8 @@ int AudioEnc::run() for (int i = 0; i < read_bytes; i+=4) { int16_t l = input_buf[i] | (input_buf[i+1] << 8); int16_t r = input_buf[i+2] | (input_buf[i+3] << 8); - peak_left = MAX(peak_left, l); - peak_right = MAX(peak_right, r); + peak_left = std::max(peak_left, l); + peak_right = std::max(peak_right, r); } if (stats_publisher) { @@ -1004,7 +1018,7 @@ int AudioEnc::run() * threshold is 0, and not configurable. The rationale is that we want to * guard against connection issues, not source level issues. */ - if (die_on_silence && MAX(peak_left, peak_right) == 0) { + if (die_on_silence && std::max(peak_left, peak_right) == 0) { const unsigned int frame_time_msec = 1000ul * read_bytes / (BYTES_PER_SAMPLE * channels * sample_rate); @@ -1186,7 +1200,7 @@ int AudioEnc::run() if (show_level) { if (channels == 1) { fprintf(stderr, "\rIn: [%-6s] %1s %1s %1s", - level(1, MAX(peak_right, peak_left)), + level(1, std::max(peak_right, peak_left)), status & STATUS_PAD_INSERTED ? "P" : " ", status & STATUS_UNDERRUN ? "U" : " ", status & STATUS_OVERRUN ? "O" : " "); @@ -1227,7 +1241,7 @@ int AudioEnc::run() return retval; } -bool AudioEnc::send_frame(const uint8_t *buf, size_t len, int peak_left, int peak_right) +bool AudioEnc::send_frame(const uint8_t *buf, size_t len, int16_t peak_left, int16_t peak_right) { if (file_output) { file_output->update_audio_levels(peak_left, peak_right); @@ -1299,6 +1313,11 @@ shared_ptr<InputInterface> AudioEnc::initialise_input() queue); } #endif +#if HAVE_GST + else if (not gst_uri.empty()) { + input = make_shared<GSTInput>(gst_uri, sample_rate, channels, queue); + } +#endif #if HAVE_ALSA else if (drift_compensation) { input = make_shared<AlsaInputThreaded>(alsa_device, channels, @@ -1334,6 +1353,7 @@ int main(int argc, char *argv[]) {"timestamp-delay", required_argument, 0, 'T'}, {"decode", required_argument, 0, 6 }, {"format", required_argument, 0, 'f'}, + {"gst-uri", required_argument, 0, 'G'}, {"identifier", required_argument, 0, 7 }, {"input", required_argument, 0, 'i'}, {"jack", required_argument, 0, 'j'}, @@ -1385,7 +1405,7 @@ int main(int argc, char *argv[]) int ch=0; int index; while(ch != -1) { - ch = getopt_long(argc, argv, "aAhDlRVb:B:c:e:f:i:j:k:L:o:r:d:p:P:s:S:T:v:w:Wg:C:", longopts, &index); + ch = getopt_long(argc, argv, "aAhDlRVb:B:c:e:f:G:i:j:k:L:o:r:d:p:P:s:S:T:v:w:Wg:C:", longopts, &index); switch (ch) { case 0: // AAC-LC audio_enc.aot = AOT_DABPLUS_AAC_LC; @@ -1465,6 +1485,11 @@ int main(int argc, char *argv[]) return 1; } break; +#ifdef HAVE_GST + case 'G': + audio_enc.gst_uri = optarg; + break; +#endif case 'i': audio_enc.infile = optarg; break; diff --git a/src/utils.c b/src/utils.c index 24da427..928304e 100644 --- a/src/utils.c +++ b/src/utils.c @@ -3,6 +3,9 @@ #include <stdint.h> #include <math.h> +#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) { diff --git a/src/utils.h b/src/utils.h index 2cb06c3..ca77a53 100644 --- a/src/utils.h +++ b/src/utils.h @@ -4,9 +4,6 @@ #include <stdint.h> #include <stddef.h> -#define MIN(a,b) (((a)<(b))?(a):(b)) -#define MAX(a,b) (((a)>(b))?(a):(b)) - #define NUMOF(l) (sizeof(l) / sizeof(*l)) #define linear_to_dB(x) (log10(x) * 20) |