From 45613a2ac14272c3e4779dd9aa971b5160bb060e Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 26 Jun 2014 20:16:37 +0200 Subject: Add JACK input support --- Makefile.am | 13 ++++- README.md | 11 +++-- configure.ac | 13 +++++ src/JackInput.cpp | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/JackInput.h | 101 ++++++++++++++++++++++++++++++++++++++ src/dabplus-enc.cpp | 47 +++++++++++++++--- 6 files changed, 311 insertions(+), 13 deletions(-) create mode 100644 src/JackInput.cpp create mode 100644 src/JackInput.h diff --git a/Makefile.am b/Makefile.am index b807cd3..89186f2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -43,15 +43,24 @@ libfdk_aac_la_LDFLAGS = -version-info @FDK_AAC_VERSION@ -no-undefined \ #aac_enc_SOURCES = src/aac-enc.c \ # src/wavreader.c +if HAVE_JACK +dabplus_enc_LDADD_JACK = -ljack -lpthread +else +dabplus_enc_LDADD_JACK = +endif + + dabplus_enc_LDFLAGS = -no-install dabplus_enc_LDADD = libfdk-aac.la -lfec -lzmq -lasound \ - -lrt -lboost_thread -dabplus_enc_CPPFLAGS = $(AM_CPPFLAGS) $(GITVERSION_FLAGS) + -lrt -lboost_thread $(dabplus_enc_LDADD_JACK) +dabplus_enc_CPPFLAGS = $(AM_CPPFLAGS) $(GITVERSION_FLAGS) -ggdb dabplus_enc_SOURCES = src/dabplus-enc.cpp \ src/FileInput.cpp \ src/FileInput.h \ src/AlsaInput.cpp \ src/AlsaInput.h \ + src/JackInput.cpp \ + src/JackInput.h \ src/SampleQueue.h \ src/encryption.c \ src/encryption.h \ diff --git a/README.md b/README.md index 3af1fa2..7abe9af 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,17 @@ This package contains several tools that use the standalone library of the Fraunhofer FDK AAC code from Android, patched for 960-transform to do DAB+ broadcast encoding. -The main tool is the *dabplus-enc* encoder, which can encode from -a file (raw or wav) or from an ALSA source to a file or a pipe, and -to a ZeroMQ output compatible with ODR-DabMux. +The main tool is the *dabplus-enc* encoder, which can read audio from +a file (raw or wav), from an ALSA source or from JACK, and encode +to a file, a pipe, or to a ZeroMQ output compatible with ODR-DabMux. The ALSA input supports experimental sound card clock drift compensation, that can compensate for imprecise sound card clocks. +The JACK input does not automatically connect to anything. The encoder runs +at the rate defined by the system clock, and therefore sound +card clock drift compensation is also used. + *dabplus-enc* includes support for DAB MOT Slideshow and DLS, written by [CSP](http://rd.csp.it). @@ -34,6 +38,7 @@ Requirements: * The alsa libraries (libasound2) * Download and install libfec from https://github.com/Opendigitalradio/ka9q-fec * Download and install ZeroMQ from http://download.zeromq.org/zeromq-4.0.3.tar.gz +* JACK audio connection kit (optional) This package: diff --git a/configure.ac b/configure.ac index 430ba20..8635cf0 100644 --- a/configure.ac +++ b/configure.ac @@ -6,6 +6,7 @@ AC_CONFIG_AUX_DIR(.) AC_CONFIG_MACRO_DIR([m4]) AM_INIT_AUTOMAKE([tar-ustar foreign]) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) +AC_CONFIG_HEADER([config.h]) dnl Checks for programs. AC_PROG_CC @@ -23,6 +24,18 @@ AC_CHECK_LIB([rt], [clock_gettime], [], [AC_MSG_ERROR([library rt is missing])]) AM_PATH_ALSA(1.0.25) +# Check for JACK +AC_ARG_ENABLE([jack], + AS_HELP_STRING([--enable-jack], [Enable JACK input])) + +AS_IF([test "x$enable_jack" = "xyes"], + AC_CHECK_LIB(jack, jack_client_open, [], + [AC_MSG_ERROR([JACK is required])])) +AS_IF([test "x$enable_jack" = "xyes"], + AC_DEFINE(HAVE_JACK, [1], [Define if JACK input is enabled])) + +# Link against jack +AM_CONDITIONAL([HAVE_JACK], [ test "x$enable_jack" = "xyes" ]) # fdk-aac-dabplus-zmq needs ZeroMQ AC_CHECK_LIB(zmq, zmq_init, , AC_MSG_ERROR(ZeroMQ libzmq is required)) diff --git a/src/JackInput.cpp b/src/JackInput.cpp new file mode 100644 index 0000000..f39bf14 --- /dev/null +++ b/src/JackInput.cpp @@ -0,0 +1,139 @@ +/* ------------------------------------------------------------------ + * Copyright (C) 2011 Martin Storsjo + * Copyright (C) 2013,2014 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 +#include +#include "config.h" + +#if HAVE_JACK + +extern "C" { +#include +} + +#include "JackInput.h" +#include + +using namespace std; + +int JackInput::prepare() +{ + jack_options_t options = JackNullOption; + jack_status_t status; + const char *server_name = NULL; + + m_client = jack_client_open(m_jack_name.c_str(), options, &status, server_name); + if (m_client == NULL) { + fprintf(stderr, "jack_client_open() failed, " + "status = 0x%2.0x\n", status); + if (status & JackServerFailed) { + fprintf(stderr, "Unable to connect to JACK server\n"); + } + return -1; + } + + if (status & JackServerStarted) { + fprintf(stderr, "JACK server started\n"); + } + + if (status & JackNameNotUnique) { + fprintf(stderr, "JACK name '%s' not unique!\n", m_jack_name.c_str()); + return -1; + } + + /* Set up real-time process callback */ + jack_set_process_callback(m_client, process_cb, this); + + /* tell the JACK server to call `shutdown_cb' if + it ever shuts down, either entirely, or if it + just decides to stop calling us. */ + jack_on_shutdown(m_client, shutdown_cb, this); + + if (m_rate != jack_get_sample_rate(m_client)) { + fprintf(stderr, "JACK uses different sample_rate %d " + "than requested (%d)!\n", + jack_get_sample_rate(m_client), + m_rate); + return -1; + } + + /* create ports */ + for (int i = 0; i < m_channels; i++) { + std::stringstream port_name; + port_name << "input" << i; + + jack_port_t* input_port = jack_port_register(m_client, + port_name.str().c_str(), + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, + 0); + + if (input_port == NULL) { + fprintf(stderr, "no more JACK ports available\n"); + return -1; + } + + m_input_ports.push_back(input_port); + } + + /* Tell the JACK server that we are ready to roll. Our + * process() callback will start running now. */ + if (jack_activate(m_client)) { + fprintf (stderr, "JACK: cannot activate client"); + return -1; + } +} + +void JackInput::jack_process(jack_nframes_t nframes) +{ + // Convert samples to shorts + std::vector buffer(m_channels * nframes); + + for (int chan = 0; chan < m_channels; chan++) { + // start offset interleaving + int i = chan; + + const int dst_skip = m_channels; + + jack_default_audio_sample_t* src = + (jack_default_audio_sample_t*)jack_port_get_buffer(m_input_ports[chan], nframes); + + jack_nframes_t n = nframes; + while (n--) { + if (*src <= -1.0f) { + buffer[i] = 32767; + } + else if (*src >= 1.0f) { + buffer[i] = -32768; + } + else { + buffer[i] = + (int16_t)lrintf(*src * 32768.0f); + } + + i += dst_skip; + src++; + } + } + + m_queue.push((uint8_t*)&buffer.front(), buffer.size() * sizeof(uint16_t)); +} + +#endif // HAVE_JACK + diff --git a/src/JackInput.h b/src/JackInput.h new file mode 100644 index 0000000..d5832c7 --- /dev/null +++ b/src/JackInput.h @@ -0,0 +1,101 @@ +/* ------------------------------------------------------------------ + * Copyright (C) 2011 Martin Storsjo + * Copyright (C) 2013,2014 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. + * ------------------------------------------------------------------- + */ + +#ifndef __JACK_INPUT_H +#define __JACK_INPUT_H +#include "config.h" +#include +#include + +#if HAVE_JACK + +extern "C" { +#include +} + +#include "SampleQueue.h" + +// 16 bits per sample is fine for now +#define BYTES_PER_SAMPLE 2 + +class JackInput +{ + public: + JackInput(const std::string& jack_name, + unsigned int channels, + unsigned int samplerate, + SampleQueue& queue) : + m_client(NULL), + m_jack_name(jack_name), + m_channels(channels), + m_rate(samplerate), + m_queue(queue) { } + + ~JackInput() { + if (m_client) { + jack_client_close(m_client); + } + } + + /* Prepare the audio input */ + int prepare(); + + private: + JackInput(const JackInput& other); + + jack_client_t* m_client; + + std::vector m_input_ports; + + std::string m_jack_name; + unsigned int m_channels; + unsigned int m_rate; + + // Callback for real-time JACK process + void jack_process(jack_nframes_t nframes); + + // Callback when JACK shuts down + void jack_shutdown() + { + m_fault = true; + } + + bool m_fault; + bool m_running; + + SampleQueue& m_queue; + + // Static functions for JACK callbacks + static int process_cb(jack_nframes_t nframes, void* arg) + { + ((JackInput*)arg)->jack_process(nframes); + return 0; + } + + static void shutdown_cb(void* arg) + { + ((JackInput*)arg)->jack_shutdown(); + } + +}; + +#endif // HAVE_JACK + +#endif + diff --git a/src/dabplus-enc.cpp b/src/dabplus-enc.cpp index 2822f94..6470e97 100644 --- a/src/dabplus-enc.cpp +++ b/src/dabplus-enc.cpp @@ -19,6 +19,7 @@ #include "AlsaInput.h" #include "FileInput.h" +#include "JackInput.h" #include "SampleQueue.h" #include "zmq.hpp" @@ -49,7 +50,12 @@ using namespace std; void usage(const char* name) { fprintf(stderr, "dabplus-enc %s is a HE-AACv2 encoder for DAB+\n" - "based on fdk-aac-dabplus that can read from a ALSA or file source\n" + "based on fdk-aac-dabplus that can read from" +#if HAVE_JACK + " JACK, ALSA or a file source\n" +#else + " a ALSA or file source\n" +#endif "and encode to a ZeroMQ output for ODR-DabMux.\n" "\n" "The -D option enables experimental sound card clock drift compensation.\n" @@ -85,6 +91,10 @@ void usage(const char* name) { " -i, --input=FILENAME Input filename (default: stdin).\n" " -f, --format={ wav, raw } Set input file format (default: wav).\n" " --fifo-silence Input file is fifo and encoder generates silence when fifo is empty. Ignore EOF.\n" +#if HAVE_JACK + " For the JACK input:\n" + " -j, --jack=name Enable JACK input, and define our name\n" +#endif " Encoder parameters:\n" " -b, --bitrate={ 8, 16, ..., 192 } Output bitrate in kbps. Must be 8 multiple.\n" " -a, --afterburner Turn on AAC encoder quality increaser.\n" @@ -94,7 +104,7 @@ void usage(const char* name) { " --sbr Force the usage of SBR\n" " --ps Force the usage of PS\n" " Output and pad parameters:\n" - " -o, --output=URI Output zmq uri. (e.g. 'tcp://*:9000')\n" + " -o, --output=URI Output zmq uri. (e.g. 'tcp://localhost:9000')\n" " -or- Output file uri. (e.g. 'file.dab')\n" " -or- a single dash '-' to denote stdout\n" " -k, --secret-key=FILE Enable ZMQ encryption with the given secret key.\n" @@ -227,6 +237,8 @@ int main(int argc, char *argv[]) // For the file output FILE *out_fh = NULL; + const char *jack_name = NULL; + const char *outuri = NULL; int sample_rate=48000, channels=2; const int bytes_per_sample = 2; @@ -263,6 +275,9 @@ int main(int argc, char *argv[]) {"device", required_argument, 0, 'd'}, {"format", required_argument, 0, 'f'}, {"input", required_argument, 0, 'i'}, +#if HAVE_JACK + {"jack", required_argument, 0, 'j'}, +#endif {"output", required_argument, 0, 'o'}, {"pad", required_argument, 0, 'p'}, {"pad-fifo", required_argument, 0, 'P'}, @@ -286,7 +301,7 @@ int main(int argc, char *argv[]) int index; while(ch != -1) { - ch = getopt_long(argc, argv, "ahDlb:c:f:i:k:o:r:d:p:P:", longopts, &index); + ch = getopt_long(argc, argv, "ahDlb:c:f:i:j:k:o:r:d:p:P:", longopts, &index); switch (ch) { case 0: // AAC-LC aot = AOT_DABPLUS_AAC_LC; @@ -324,6 +339,11 @@ int main(int argc, char *argv[]) case 'i': infile = optarg; break; +#if HAVE_JACK + case 'j': + jack_name = optarg; + break; +#endif case 'k': keyfile = optarg; break; @@ -349,8 +369,8 @@ int main(int argc, char *argv[]) } } - if (alsa_device && infile) { - fprintf(stderr, "You must define either alsa or file input, not both\n"); + if (alsa_device && infile && jack_name) { + fprintf(stderr, "You must define only one possible input, not several!\n"); return 1; } @@ -478,6 +498,9 @@ int main(int argc, char *argv[]) AlsaInputThreaded alsa_in_threaded(alsa_device, channels, sample_rate, queue); AlsaInputDirect alsa_in_direct(alsa_device, channels, sample_rate); FileInput file_in(infile, raw_input, sample_rate); +#if HAVE_JACK + JackInput jack_in(jack_name, channels, sample_rate, queue); +#endif if (infile) { if (file_in.prepare() != 0) { @@ -485,6 +508,14 @@ int main(int argc, char *argv[]) return 1; } } +#if HAVE_JACK + else if (jack_name) { + if (jack_in.prepare() != 0) { + fprintf(stderr, "JACK preparation failed\n"); + return 1; + } + } +#endif else if (drift_compensation) { if (alsa_in_threaded.prepare() != 0) { fprintf(stderr, "Alsa preparation failed\n"); @@ -531,7 +562,7 @@ int main(int argc, char *argv[]) // -------------- wait the right amount of time - if (drift_compensation) { + if (drift_compensation || jack_name) { struct timespec tp_now; clock_gettime(CLOCK_MONOTONIC, &tp_now); @@ -609,8 +640,8 @@ int main(int argc, char *argv[]) } } } - else if (drift_compensation) { - if (alsa_in_threaded.fault_detected()) { + else if (drift_compensation || jack_name) { + if (drift_compensation && alsa_in_threaded.fault_detected()) { fprintf(stderr, "Detected fault in alsa input!\n"); break; } -- cgit v1.2.3