From 5d0b852bd60fd54c24316b5bc35d44ca5530b200 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 18 Jul 2018 11:04:00 +0200 Subject: Decode AAC to measure audio levels --- Makefile.am | 1 + README.md | 8 +- configure.ac | 13 ++++ src/AACDecoder.cpp | 181 ++++++++++++++++++++++++++++++++++++++++++++ src/AACDecoder.h | 53 +++++++++++++ src/odr-sourcecompanion.cpp | 36 +++++++-- 6 files changed, 277 insertions(+), 15 deletions(-) create mode 100644 src/AACDecoder.cpp create mode 100644 src/AACDecoder.h 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 ]], + [[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 +#include + +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 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 +#include +#include + +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 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 #include #include @@ -117,13 +118,15 @@ int main(int argc, char *argv[]) std::vector 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)); } -- cgit v1.2.3