aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias P. Braendli <matthias.braendli@mpb.li>2018-07-18 11:04:00 +0200
committerMatthias P. Braendli <matthias.braendli@mpb.li>2018-07-18 11:04:00 +0200
commit5d0b852bd60fd54c24316b5bc35d44ca5530b200 (patch)
tree1ed4c97661268dd5b55ab868a01c393f18243cb9
parent4714fe46d8ee1bfd43fbe936d1d50402411e3446 (diff)
downloadODR-SourceCompanion-5d0b852bd60fd54c24316b5bc35d44ca5530b200.tar.gz
ODR-SourceCompanion-5d0b852bd60fd54c24316b5bc35d44ca5530b200.tar.bz2
ODR-SourceCompanion-5d0b852bd60fd54c24316b5bc35d44ca5530b200.zip
Decode AAC to measure audio levels
-rw-r--r--Makefile.am1
-rw-r--r--README.md8
-rw-r--r--configure.ac13
-rw-r--r--src/AACDecoder.cpp181
-rw-r--r--src/AACDecoder.h53
-rw-r--r--src/odr-sourcecompanion.cpp36
6 files changed, 277 insertions, 15 deletions
diff --git a/Makefile.am b/Makefile.am
index 71afc01..d949d15 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -11,6 +11,7 @@ odr_sourcecompanion_LDADD = -lzmq
odr_sourcecompanion_CFLAGS = $(GITVERSION_FLAGS) -ggdb -O2 -Wall
odr_sourcecompanion_CXXFLAGS = $(GITVERSION_FLAGS) -ggdb -O2 -Wall -Isrc/fec
odr_sourcecompanion_SOURCES = src/odr-sourcecompanion.cpp \
+ src/AACDecoder.h src/AACDecoder.cpp \
src/AVTEDIInput.h src/AVTEDIInput.cpp \
src/AVTInput.h src/AVTInput.cpp \
src/InetAddress.h src/InetAddress.cpp \
diff --git a/README.md b/README.md
index 9a34c82..abb9f05 100644
--- a/README.md
+++ b/README.md
@@ -15,9 +15,7 @@ How to build
Requirements:
* A C++11 compiler
-* Install ZeroMQ 4.0.4 or more recent
- * If your distribution does not include it, take it from
- from http://download.zeromq.org/zeromq-4.0.4.tar.gz
+* ZeroMQ 4.0.4 or more recent
This package:
@@ -42,7 +40,3 @@ Also, assuming you have an AVT encoder on the IP address 192.168.128.111 and a f
--input-uri=udp://:32010 --control-uri=udp://192.168.128.111:9325 --jitter-size=80 \
-o $DST
-TODO
-====
-
-A proper setting for the audio level in the ZeroMQ output metadata fields.
diff --git a/configure.ac b/configure.ac
index eb9e5e3..6811e1d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -30,6 +30,19 @@ AC_CHECK_LIB([rt], [clock_gettime], [], [AC_MSG_ERROR([library rt is missing])])
AC_CHECK_LIB(zmq, zmq_init, , AC_MSG_ERROR(ZeroMQ libzmq is required))
+AC_CHECK_LIB(fdk-aac, aacEncOpen, , AC_MSG_ERROR(The FDK-AAC library is required))
+# We need to have the ODR fdk-aac, the upstream one doesn't support DAB+
+AC_MSG_CHECKING([for DAB+ support in FDK-AAC])
+AC_COMPILE_IFELSE( [AC_LANG_PROGRAM([[#include <fdk-aac/aacenc_lib.h>]],
+ [[char dummy[TT_DABPLUS];]])],
+ [
+ AC_MSG_RESULT([yes])
+ ],
+ [
+ AC_MSG_RESULT([no])
+ AC_MSG_ERROR(["Your FDK-AAC does not support DAB+, make sure you have installed the ODR version!"])
+ ]
+ )
AM_CONDITIONAL([IS_GIT_REPO], [test -d '.git'])
diff --git a/src/AACDecoder.cpp b/src/AACDecoder.cpp
new file mode 100644
index 0000000..3f34ca0
--- /dev/null
+++ b/src/AACDecoder.cpp
@@ -0,0 +1,181 @@
+/* ------------------------------------------------------------------
+ * Copyright (C) 2011 Martin Storsjo
+ * Copyright (C) 2017 Matthias P. Braendli
+ * Copyright (C) 2016 Stefan Pöschel
+ *
+ * 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 "config.h"
+#include "AACDecoder.h"
+#include <stdexcept>
+#include <string>
+
+AACDecoder::AACDecoder()
+{
+ m_handle = aacDecoder_Open(TT_MP4_RAW, 1);
+ if (not m_handle) {
+ throw std::runtime_error("AACDecoder: error opening decoder");
+ }
+
+}
+
+AACDecoder::~AACDecoder()
+{
+ if (m_handle) {
+ aacDecoder_Close(m_handle);
+ }
+}
+
+void AACDecoder::decode_frame(uint8_t *data, size_t len)
+{
+ const bool dac_rate = data[2] & 0x40;
+ const bool sbr_flag = data[2] & 0x20;
+ const bool aac_channel_mode = data[2] & 0x10;
+ const bool ps_flag = data[2] & 0x08;
+ const uint8_t mpeg_surround_config = data[2] & 0x07;
+
+ const int core_sr_index = dac_rate ?
+ (sbr_flag ? 6 : 3) : (sbr_flag ? 8 : 5); // 24/48/16/32 kHz
+ const int core_ch_config = aac_channel_mode ? 2 : 1;
+ const int extension_sr_index = dac_rate ? 3 : 5; // 48/32 kHz
+
+ int au_start[6] = {};
+
+ int num_aus = dac_rate ? (sbr_flag ? 3 : 6) : (sbr_flag ? 2 : 4);
+ au_start[0] = dac_rate ? (sbr_flag ? 6 : 11) : (sbr_flag ? 5 : 8);
+ au_start[1] = data[3] << 4 | data[4] >> 4;
+
+ if (num_aus >= 3) {
+ au_start[2] = (data[4] & 0x0F) << 8 | data[5];
+ }
+
+ if (num_aus >= 4) {
+ au_start[3] = data[6] << 4 | data[7] >> 4;
+ }
+
+ if (num_aus == 6) {
+ au_start[4] = (data[7] & 0x0F) << 8 | data[8];
+ au_start[5] = data[9] << 4 | data[10] >> 4;
+ }
+
+ au_start[num_aus] = len; // end of the buffer
+
+ for (int i = 0; i < num_aus; i++) {
+ if (au_start[i] >= au_start[i+1]) {
+ throw std::runtime_error(" AU ordering check failed\n");
+ }
+ }
+
+ if (not m_decoder_set_up) {
+ std::vector<uint8_t> asc;
+
+ // AAC LC
+ asc.push_back(0b00010 << 3 | core_sr_index >> 1);
+ asc.push_back((core_sr_index & 0x01) << 7 | core_ch_config << 3 | 0b100);
+
+ if (sbr_flag) {
+ // add SBR
+ asc.push_back(0x56);
+ asc.push_back(0xE5);
+ asc.push_back(0x80 | (extension_sr_index << 3));
+
+ if (ps_flag) {
+ // add PS
+ asc.back() |= 0x05;
+ asc.push_back(0x48);
+ asc.push_back(0x80);
+ }
+ }
+
+ uint8_t* asc_array[1] {asc.data()};
+ const unsigned int asc_sizeof_array[1] {(unsigned int) asc.size()};
+
+ AAC_DECODER_ERROR init_result = aacDecoder_ConfigRaw(m_handle,
+ asc_array, asc_sizeof_array);
+ if (init_result != AAC_DEC_OK) {
+ throw std::runtime_error(
+ "AACDecoderFDKAAC: error while aacDecoder_ConfigRaw: " +
+ std::to_string(init_result));
+ }
+
+ m_channels = (aac_channel_mode or ps_flag) ? 2 : 1;
+ size_t output_frame_len = 960 * 2 * m_channels * (sbr_flag ? 2 : 1);
+ m_output_frame.resize(output_frame_len);
+ fprintf(stderr, " Setting decoder output frame len %zu\n", output_frame_len);
+
+ const int sample_rate = dac_rate ? 48000 : 32000;
+ m_decoder_set_up = true;
+
+ fprintf(stderr, " Set up decoder with %d Hz, %s%swith %d channels\n",
+ sample_rate, (sbr_flag ? "SBR " : ""), (ps_flag ? "PS " : ""),
+ m_channels);
+
+ }
+
+ const size_t AU_CRCLEN = 2;
+ for (int i = 0; i < num_aus; i++) {
+ uint8_t *au_data = data + au_start[i];
+ size_t au_len = au_start[i+1] - au_start[i] - AU_CRCLEN;
+ decode_au(au_data, au_len);
+ }
+}
+
+AACDecoder::peak_t AACDecoder::get_peaks()
+{
+ auto p = m_peak;
+ m_peak.peak_left = 0;
+ m_peak.peak_right = 0;
+ return p;
+}
+
+void AACDecoder::decode_au(uint8_t *data, size_t len)
+{
+ uint8_t* input_buffer[1] {data};
+ const unsigned int input_buffer_size[1] {(unsigned int) len};
+ unsigned int bytes_valid = len;
+
+ // fill internal input buffer
+ AAC_DECODER_ERROR result = aacDecoder_Fill(
+ m_handle, input_buffer, input_buffer_size, &bytes_valid);
+
+ if (result != AAC_DEC_OK) {
+ throw std::runtime_error(
+ "AACDecoderFDKAAC: error while aacDecoder_Fill: " +
+ std::to_string(result));
+ }
+
+ if (bytes_valid) {
+ throw std::runtime_error(
+ "AACDecoderFDKAAC: aacDecoder_Fill did not consume all bytes");
+ }
+
+ // decode audio
+ result = aacDecoder_DecodeFrame(m_handle,
+ (short int*)m_output_frame.data(), m_output_frame.size(), 0);
+ if (result != AAC_DEC_OK) {
+ throw std::runtime_error(
+ "AACDecoderFDKAAC: error while aacDecoder_DecodeFrame: " +
+ std::to_string(result));
+ }
+
+ for (int i = 0; i < m_output_frame.size(); i+=4) {
+ const uint8_t *input_buf = m_output_frame.data();
+ int16_t l = input_buf[i] | (input_buf[i+1] << 8);
+ int16_t r = input_buf[i+2] | (input_buf[i+3] << 8);
+ m_peak.peak_left = std::max(m_peak.peak_left, l);
+ m_peak.peak_right = std::max(m_peak.peak_right, r);
+ }
+}
diff --git a/src/AACDecoder.h b/src/AACDecoder.h
new file mode 100644
index 0000000..7f19cb9
--- /dev/null
+++ b/src/AACDecoder.h
@@ -0,0 +1,53 @@
+/* ------------------------------------------------------------------
+ * Copyright (C) 2011 Martin Storsjo
+ * Copyright (C) 2017 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 AACDecoder.h
+ * \brief Uses FDK-AAC to decode the AAC format for loopback tests and
+ * to measure the audio level
+ */
+
+#pragma once
+
+#include <fdk-aac/aacdecoder_lib.h>
+#include <cstdint>
+#include <vector>
+
+class AACDecoder {
+ public:
+ AACDecoder();
+ ~AACDecoder();
+ AACDecoder(const AACDecoder&) = delete;
+ AACDecoder& operator=(const AACDecoder&) = delete;
+ void decode_frame(uint8_t *data, size_t len);
+
+ struct peak_t { int16_t peak_left; int16_t peak_right; };
+ peak_t get_peaks();
+
+ private:
+ void decode_au(uint8_t *data, size_t len);
+ bool m_decoder_set_up = false;
+ int m_channels = 0;
+
+ peak_t m_peak;
+
+ HANDLE_AACDECODER m_handle;
+ std::vector<uint8_t> m_output_frame;
+};
+
diff --git a/src/odr-sourcecompanion.cpp b/src/odr-sourcecompanion.cpp
index a464883..b687105 100644
--- a/src/odr-sourcecompanion.cpp
+++ b/src/odr-sourcecompanion.cpp
@@ -27,6 +27,7 @@
#include "zmq.hpp"
#include "AVTInput.h"
+#include "AACDecoder.h"
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
@@ -117,13 +118,15 @@ int main(int argc, char *argv[])
std::vector<std::string> output_uris;
+ AACDecoder decoder;
+
/* For MOT Slideshow and DLS insertion */
const char* pad_fifo = "/tmp/pad.fifo";
int pad_fd;
int padlen = 0;
/* Whether to show the 'sox'-like measurement */
- int show_level = 0;
+ bool show_level = false;
/* Data for ZMQ CURVE authentication */
char* keyfile = nullptr;
@@ -201,7 +204,7 @@ int main(int argc, char *argv[])
keyfile = optarg;
break;
case 'l':
- show_level = 1;
+ show_level = true;
break;
case 'o':
output_uris.push_back(optarg);
@@ -397,9 +400,27 @@ int main(int argc, char *argv[])
}
}
- // TODO get level information from encoder. In the meantime, set to max value to avoid alarms.
- peak_left = 0x7FFF;
- peak_right = 0x7FFF;
+ if (numOutBytes != 0) {
+ try {
+ // Drop the Reed-Solomon data
+ if (numOutBytes % 120 != 0) {
+ throw runtime_error("Invalid data length " + to_string(numOutBytes));
+ }
+ numOutBytes /= 120;
+ numOutBytes *= 110;
+
+ decoder.decode_frame(outbuf.data(), numOutBytes);
+
+ auto p = decoder.get_peaks();
+ peak_left = p.peak_left;
+ peak_right = p.peak_right;
+ }
+ catch (const runtime_error &e) {
+ fprintf(stderr, "AAC decoding failed with: %s\n", e.what());
+ peak_left = 0;
+ peak_right = 0;
+ }
+ }
read_bytes = numOutBytes;
@@ -432,15 +453,14 @@ int main(int argc, char *argv[])
}
}
- if (numOutBytes != 0)
- {
+ if (numOutBytes != 0) {
if (show_level) {
if (channels == 1) {
fprintf(stderr, "\rIn: [%-6s]",
level(1, MAX(peak_right, peak_left)));
}
else if (channels == 2) {
- fprintf(stderr, "\rIn: [%6s|%-6s]",
+ fprintf(stderr, "\rIn: [%6s|%-6s]",
level(0, peak_left),
level(1, peak_right));
}