diff options
| author | Matthias P. Braendli <matthias.braendli@mpb.li> | 2018-07-18 11:04:00 +0200 | 
|---|---|---|
| committer | Matthias P. Braendli <matthias.braendli@mpb.li> | 2018-07-18 11:04:00 +0200 | 
| commit | 5d0b852bd60fd54c24316b5bc35d44ca5530b200 (patch) | |
| tree | 1ed4c97661268dd5b55ab868a01c393f18243cb9 /src | |
| parent | 4714fe46d8ee1bfd43fbe936d1d50402411e3446 (diff) | |
| download | ODR-SourceCompanion-5d0b852bd60fd54c24316b5bc35d44ca5530b200.tar.gz ODR-SourceCompanion-5d0b852bd60fd54c24316b5bc35d44ca5530b200.tar.bz2 ODR-SourceCompanion-5d0b852bd60fd54c24316b5bc35d44ca5530b200.zip | |
Decode AAC to measure audio levels
Diffstat (limited to 'src')
| -rw-r--r-- | src/AACDecoder.cpp | 181 | ||||
| -rw-r--r-- | src/AACDecoder.h | 53 | ||||
| -rw-r--r-- | src/odr-sourcecompanion.cpp | 36 | 
3 files changed, 262 insertions, 8 deletions
| 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));                  } | 
