diff options
| author | Matthias P. Braendli <matthias.braendli@mpb.li> | 2014-06-26 20:16:37 +0200 | 
|---|---|---|
| committer | Matthias P. Braendli <matthias.braendli@mpb.li> | 2014-06-26 20:16:37 +0200 | 
| commit | 45613a2ac14272c3e4779dd9aa971b5160bb060e (patch) | |
| tree | ffec3e9f2e213415a3755849f0feaa53ea5101ad | |
| parent | c0ec1e01c7dd84ce694bc5268916cc43870f00cf (diff) | |
| download | fdk-aac-45613a2ac14272c3e4779dd9aa971b5160bb060e.tar.gz fdk-aac-45613a2ac14272c3e4779dd9aa971b5160bb060e.tar.bz2 fdk-aac-45613a2ac14272c3e4779dd9aa971b5160bb060e.zip | |
Add JACK input support
| -rw-r--r-- | Makefile.am | 13 | ||||
| -rw-r--r-- | README.md | 11 | ||||
| -rw-r--r-- | configure.ac | 13 | ||||
| -rw-r--r-- | src/JackInput.cpp | 139 | ||||
| -rw-r--r-- | src/JackInput.h | 101 | ||||
| -rw-r--r-- | src/dabplus-enc.cpp | 47 | 
6 files changed, 311 insertions, 13 deletions
| 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 \ @@ -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 <cstdio> +#include <string> +#include "config.h" + +#if HAVE_JACK + +extern "C" { +#include <jack/jack.h> +} + +#include "JackInput.h" +#include <sys/time.h> + +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<int16_t> 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 <cstdio> +#include <string> + +#if HAVE_JACK + +extern "C" { +#include <jack/jack.h> +} + +#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<uint8_t>& 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<jack_port_t*> 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<uint8_t>& 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;              } | 
