/* Copyright (C) 2017 Matthias P. Braendli (http://www.opendigitalradio.org) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . dabplussnoop.cpp Parse DAB+ frames from a ETI file Authors: Matthias P. Braendli Mathias Coinchon */ #include #include #include #include #include #include #include #include #include #include #include "dabplussnoop.hpp" extern "C" { #include "firecode.h" #include "lib_crc.h" } #include "faad_decoder.hpp" #include "rsdecoder.hpp" #define DPS_INDENT "\t\t" #define DPS_PREFIX "DAB+ decode:" #ifndef DPS_DEBUG # define DPS_DEBUG 0 #endif using namespace std; void DabPlusSnoop::push(uint8_t* streamdata, size_t streamsize) { // Try to decode audio size_t original_size = m_data.size(); m_data.resize(original_size + streamsize); memcpy(&m_data[original_size], streamdata, streamsize); const size_t sf_len = m_subchannel_index * 120; while (seek_valid_firecode() and decode()) { // We have been able to decode the AUs, now flush vector m_data.erase(m_data.begin(), m_data.begin() + sf_len);; } } audio_statistics_t DabPlusSnoop::get_audio_statistics(void) const { return m_faad_decoder.get_audio_statistics(); } // Idea and some code taken from Xpadxpert bool DabPlusSnoop::seek_valid_firecode() { if (m_data.size() < 10) { // Not enough data return false; } bool crc_ok = false; size_t i; for (i = 0; i < m_data.size() - 10; i++) { uint8_t* b = &m_data[i]; // the three bytes after the firecode must not be zero // (simple plausibility check to avoid sync in zero byte region) if (b[3] != 0x00 || (b[4] & 0xF0) != 0x00) { uint16_t header_firecode = (b[0] << 8) | b[1]; uint16_t calculated_firecode = firecode_crc(b+2, 9); if (header_firecode == calculated_firecode) { crc_ok = true; break; } } } if (crc_ok) { #if DPS_DEBUG printf(DPS_PREFIX " Found valid FireCode at %zu\n", i); #endif //erase elements before the header m_data.erase(m_data.begin(), m_data.begin() + i); return true; } else { #if DPS_DEBUG printf(DPS_PREFIX " No valid FireCode found\n"); #endif m_data.clear(); return false; } } bool DabPlusSnoop::decode() { #if DPS_DEBUG printf(DPS_PREFIX " We have %zu bytes of data\n", m_data.size()); #endif const size_t sf_len = m_subchannel_index * 120; if (m_subchannel_index && m_data.size() >= sf_len) { std::vector b(sf_len); std::copy(m_data.begin(), m_data.begin() + sf_len, b.begin()); RSDecoder rs_dec; int rs_errors = rs_dec.DecodeSuperframe(b, m_subchannel_index); if (rs_errors == -1) { // Uncorrectable errors, flush our buffer m_data.clear(); return false; } else if (rs_errors > 0) { printf("RS Decoder for stream %d: %d uncorrected errors\n", m_index, rs_errors); } // -- Parse he_aac_super_frame // ---- Parse he_aac_super_frame_header // ------ Parse firecode and audio params uint16_t header_firecode = (b[0] << 8) | b[1]; uint8_t audio_params = b[2]; int rfa = (audio_params & 0x80) ? true : false; m_dac_rate = (audio_params & 0x40) ? true : false; m_sbr_flag = (audio_params & 0x20) ? true : false; m_aac_channel_mode = (audio_params & 0x10) ? true : false; m_ps_flag = (audio_params & 0x08) ? true : false; m_mpeg_surround_config = (audio_params & 0x07); int num_aus = 0; if (!m_dac_rate && m_sbr_flag) num_aus = 2; // AAC core sampling rate 16 kHz else if (m_dac_rate && m_sbr_flag) num_aus = 3; // AAC core sampling rate 24 kHz else if (!m_dac_rate && !m_sbr_flag) num_aus = 4; // AAC core sampling rate 32 kHz else if (m_dac_rate && !m_sbr_flag) num_aus = 6; // AAC core sampling rate 48 kHz #if DPS_DEBUG printf( DPS_INDENT DPS_PREFIX "\n" DPS_INDENT "\tfirecode 0x%x\n" DPS_INDENT "\trfa %d\n" DPS_INDENT "\tdac_rate %d\n" DPS_INDENT "\tsbr_flag %d\n" DPS_INDENT "\taac_channel_mode %d\n" DPS_INDENT "\tps_flag %d\n" DPS_INDENT "\tmpeg_surround_config %d\n" DPS_INDENT "\tnum_aus %d\n", header_firecode, rfa, m_dac_rate, m_sbr_flag, m_aac_channel_mode, m_ps_flag, m_mpeg_surround_config, num_aus); #else // Avoid "unused variable" warning (void)header_firecode; (void)rfa; #endif // ------ Parse au_start auto au_starts = b.begin() + 3; vector au_start_nibbles(0); /* Each AU_START is encoded in three nibbles. * When we have n AUs, we have n-1 au_start values. */ for (int i = 0; i < (num_aus-1)*3; i++) { if (i % 2 == 0) { char nibble = au_starts[i/2] >> 4; au_start_nibbles.push_back( nibble ); } else { char nibble = au_starts[i/2] & 0x0F; au_start_nibbles.push_back( nibble ); } } vector au_start(num_aus); if (num_aus == 2) au_start[0] = 5; else if (num_aus == 3) au_start[0] = 6; else if (num_aus == 4) au_start[0] = 8; else if (num_aus == 6) au_start[0] = 11; int nib = 0; for (int au = 1; au < num_aus; au++) { au_start[au] = au_start_nibbles[nib] << 8 | \ au_start_nibbles[nib+1] << 4 | \ au_start_nibbles[nib+2]; nib += 3; } #if DPS_DEBUG printf(DPS_INDENT DPS_PREFIX " AU start\n"); for (int au = 0; au < num_aus; au++) { printf(DPS_INDENT "\tAU[%d] %d 0x%x\n", au, au_start[au], au_start[au]); } #endif return extract_au(au_start); } else { return false; } } bool DabPlusSnoop::extract_au(vector au_start) { vector > aus(au_start.size()); // The last entry of au_start must the end of valid // AU data. We stop at m_subchannel_index * 110 because // what comes after is RS parity au_start.push_back(m_subchannel_index * 110); bool all_crc_ok = true; for (size_t au = 0; au < aus.size(); au++) { #if DPS_DEBUG printf(DPS_PREFIX DPS_INDENT "Copy au %zu of size %d\n", au, au_start[au+1] - au_start[au]-2 ); #endif aus[au].resize(au_start[au+1] - au_start[au]-2); std::copy( m_data.begin() + au_start[au], m_data.begin() + au_start[au+1]-2, aus[au].begin() ); /* Check CRC */ uint16_t au_crc = m_data[au_start[au+1]-2] << 8 | \ m_data[au_start[au+1]-1]; uint16_t calc_crc = 0xFFFF; for (vector::iterator au_data = aus[au].begin(); au_data != aus[au].end(); ++au_data) { calc_crc = update_crc_ccitt(calc_crc, *au_data); } calc_crc =~ calc_crc; if (calc_crc != au_crc) { printf(DPS_INDENT DPS_PREFIX "Erroneous CRC for au %zu: 0x%04x vs 0x%04x\n", au, calc_crc, au_crc); all_crc_ok = false; } } if (all_crc_ok) { return analyse_au(aus); } else { //discard faulty superframe (to be improved to correct/conceal) m_data.clear(); return false; } } bool DabPlusSnoop::analyse_au(vector >& aus) { stringstream ss_filename; if (m_write_to_wav_file) { ss_filename << "stream-" << m_index; } if (!m_faad_decoder.is_initialised()) { m_faad_decoder.open(ss_filename.str(), m_ps_flag, m_aac_channel_mode, m_dac_rate, m_sbr_flag, m_mpeg_surround_config); } return m_faad_decoder.decode(aus); } StreamSnoop::StreamSnoop(StreamSnoop&& other) { dps = move(other.dps); m_index = other.m_index; m_raw_data_stream_fd = other.m_raw_data_stream_fd; other.m_raw_data_stream_fd = nullptr; m_dump_to_file = other.m_dump_to_file; other.m_dump_to_file = false; } StreamSnoop::~StreamSnoop() { if (m_raw_data_stream_fd) { fclose(m_raw_data_stream_fd); } } void StreamSnoop::push(uint8_t* streamdata, size_t streamsize) { if (m_index == -1) { throw std::runtime_error("StreamSnoop not properly initialised"); } // First dump to subchannel file (superframe+parity word) if (m_dump_to_file and m_raw_data_stream_fd == nullptr) { stringstream dump_filename; dump_filename << "stream-" << m_index << ".msc"; m_raw_data_stream_fd = fopen(dump_filename.str().c_str(), "w"); if (m_raw_data_stream_fd == nullptr) { perror("File open failed"); } } if (m_raw_data_stream_fd) { fwrite(streamdata, streamsize, 1, m_raw_data_stream_fd); } dps.push(streamdata, streamsize); } audio_statistics_t StreamSnoop::get_audio_statistics(void) const { return dps.get_audio_statistics(); }