/*
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;
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;
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();
}