aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am13
-rw-r--r--README.md11
-rw-r--r--configure.ac13
-rw-r--r--src/JackInput.cpp139
-rw-r--r--src/JackInput.h101
-rw-r--r--src/dabplus-enc.cpp47
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 \
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 <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;
}