diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | src/AACDecoder.cpp | 160 | ||||
-rw-r--r-- | src/AACDecoder.h | 49 | ||||
-rw-r--r-- | src/odr-audioenc.cpp | 28 | ||||
-rw-r--r-- | src/wavfile.cpp | 19 | ||||
-rw-r--r-- | src/wavfile.h | 7 |
6 files changed, 255 insertions, 10 deletions
diff --git a/Makefile.am b/Makefile.am index eb5cda6..9c393d7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -88,6 +88,8 @@ odr_audioenc_SOURCES = src/odr-audioenc.cpp \ src/JackInput.h \ src/VLCInput.cpp \ src/VLCInput.h \ + src/AACDecoder.cpp \ + src/AACDecoder.h \ src/SampleQueue.h \ src/encryption.c \ src/encryption.h \ diff --git a/src/AACDecoder.cpp b/src/AACDecoder.cpp new file mode 100644 index 0000000..6cbe8c4 --- /dev/null +++ b/src/AACDecoder.cpp @@ -0,0 +1,160 @@ +/* ------------------------------------------------------------------ + * 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> + +AACDecoder::AACDecoder(const char* wavfilename) : + m_wav_writer(wavfilename) +{ + m_handle = aacDecoder_Open(TT_MP4_RAW, 1); + if (not m_handle) { + throw std::runtime_error("AACDecoder: error opening decoder"); + } +} + +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 || 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_wav_writer.initialise_header(sample_rate); + 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); + } +} + +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)); + } + + m_wav_writer.write_data(m_output_frame.data(), m_output_frame.size()); +} diff --git a/src/AACDecoder.h b/src/AACDecoder.h new file mode 100644 index 0000000..2c09548 --- /dev/null +++ b/src/AACDecoder.h @@ -0,0 +1,49 @@ +/* ------------------------------------------------------------------ + * 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 + */ + +#pragma once + +#include <fdk-aac/aacdecoder_lib.h> +#include <cstdint> +#include <vector> +#include "wavfile.h" + +class AACDecoder { + public: + AACDecoder(const char* wavfilename); + + void decode_frame(uint8_t *data, size_t len); + + private: + void decode_au(uint8_t *data, size_t len); + + bool m_decoder_set_up = false; + + int m_channels = 0; + + WavWriter m_wav_writer; + HANDLE_AACDECODER m_handle; + std::vector<uint8_t> m_output_frame; +}; + diff --git a/src/odr-audioenc.cpp b/src/odr-audioenc.cpp index 84218c8..edb98f7 100644 --- a/src/odr-audioenc.cpp +++ b/src/odr-audioenc.cpp @@ -53,6 +53,7 @@ #include "JackInput.h" #include "VLCInput.h" #include "SampleQueue.h" +#include "AACDecoder.h" #include "zmq.hpp" #include "common.h" #include "wavfile.h" @@ -177,6 +178,7 @@ void usage(const char* name) " --sbr Force the usage of SBR (HE-AAC)\n" " --ps Force the usage of SBR and PS (HE-AACv2)\n" " -B, --bandwidth=VALUE Set the AAC encoder bandwidth to VALUE [Hz].\n" + " --decode=FILE Decode the AAC back to a wav file (loopback test).\n" " Output and pad parameters:\n" " -o, --output=URI Output ZMQ uri. (e.g. 'tcp://localhost:9000')\n" " -or- Output file uri. (e.g. 'file.dabp')\n" @@ -419,6 +421,8 @@ int main(int argc, char *argv[]) AACENC_InfoStruct info = { 0 }; int aot = AOT_NONE; + std::string decode_wavfilename; + std::string dab_channel_mode; int dab_psy_model = 1; std::deque<uint8_t> toolame_output_buffer; @@ -454,6 +458,7 @@ int main(int argc, char *argv[]) {"dabmode", required_argument, 0, 4 }, {"dabpsy", required_argument, 0, 5 }, {"device", required_argument, 0, 'd'}, + {"decode", required_argument, 0, 6 }, {"format", required_argument, 0, 'f'}, {"input", required_argument, 0, 'i'}, {"jack", required_argument, 0, 'j'}, @@ -528,6 +533,9 @@ int main(int argc, char *argv[]) case 5: // DAB psy model dab_psy_model = std::stoi(optarg); break; + case 6: // Enable loopback decoder for AAC + decode_wavfilename = optarg; + break; case 'a': selected_encoder = encoder_selection_t::toolame_dab; break; @@ -756,6 +764,7 @@ int main(int argc, char *argv[]) vec_u8 input_buf; HANDLE_AACENCODER encoder; + std::unique_ptr<AACDecoder> decoder; if (selected_encoder == encoder_selection_t::fdk_dabplus) { int subchannel_index = bitrate / 8; @@ -777,6 +786,10 @@ int main(int argc, char *argv[]) input_size); input_buf.resize(input_size); + + if (not decode_wavfilename.empty()) { + decoder.reset(new AACDecoder(decode_wavfilename.c_str())); + } } else if (selected_encoder == encoder_selection_t::toolame_dab) { int err = toolame_init(); @@ -821,6 +834,11 @@ int main(int argc, char *argv[]) } input_buf.resize(channels * 1152 * BYTES_PER_SAMPLE); + + if (not decode_wavfilename.empty()) { + fprintf(stderr, "--decode not supported for DAB\n"); + return 1; + } } /* We assume that we need to call the encoder @@ -1225,6 +1243,16 @@ int main(int argc, char *argv[]) } } + if (numOutBytes != 0 and decoder) { + try { + decoder->decode_frame(outbuf.data(), numOutBytes); + } + catch (std::runtime_error &e) { + fprintf(stderr, "Decoding failed with: %s\n", e.what()); + return 1; + } + } + /* Check if the encoder has generated output data. * DAB+ requires RS encoding, which is not done in ODR-DabMux and not necessary * for DAB. diff --git a/src/wavfile.cpp b/src/wavfile.cpp index dbc1e13..7de4ffc 100644 --- a/src/wavfile.cpp +++ b/src/wavfile.cpp @@ -211,7 +211,15 @@ struct wavfile_header { int data_length; }; -WavWriter::WavWriter(const char *filename, int rate) +WavWriter::WavWriter(const char *filename) +{ + m_fd = fopen(filename, "w+"); + if (not m_fd) { + throw std::runtime_error("Could not open wav file"); + } +} + +void WavWriter::initialise_header(int rate) { struct wavfile_header header; @@ -233,11 +241,6 @@ WavWriter::WavWriter(const char *filename, int rate) header.bits_per_sample = bits_per_sample; header.data_length = 0; - m_fd = fopen(filename, "w+"); - if (not m_fd) { - throw std::runtime_error("Could not open wav file"); - } - fwrite(&header,sizeof(header),1,m_fd); fflush(m_fd); @@ -261,8 +264,8 @@ WavWriter::~WavWriter() fclose(m_fd); } -void WavWriter::write_data(short data[], int length) +void WavWriter::write_data(const uint8_t *data, int length) { - fwrite(data,sizeof(short),length,m_fd); + fwrite(data,sizeof(uint8_t),length,m_fd); } diff --git a/src/wavfile.h b/src/wavfile.h index 92ea56f..a8cd4d9 100644 --- a/src/wavfile.h +++ b/src/wavfile.h @@ -20,6 +20,7 @@ #pragma once #include <cstdio> +#include <cstdint> void* wav_read_open(const char *filename); void wav_read_close(void* obj); @@ -29,12 +30,14 @@ int wav_read_data(void* obj, unsigned char* data, unsigned int length); class WavWriter { public: - WavWriter(const char *filename, int rate); + WavWriter(const char *filename); ~WavWriter(); WavWriter(const WavWriter& other) = delete; WavWriter& operator=(const WavWriter& other) = delete; - void write_data(short *data, int length); + void initialise_header(int rate); + + void write_data(const uint8_t *data, int length); private: FILE *m_fd = nullptr; |