From ce003dc2dd16a4987ae6a2f54721f79fb2bd91e7 Mon Sep 17 00:00:00 2001 From: pschatzmann Date: Sun, 17 Sep 2023 18:18:01 +0200 Subject: [PATCH] AACDecoderADTS --- src/AudioBasic/Collections/Vector.h | 33 ++-- src/AudioCodecs/CodecAACHelix.h | 32 ++-- src/AudioCodecs/CodecADTS.h | 235 ++++++++++++++++++++++++++++ src/AudioCodecs/CodecMTS.h | 82 +++++++++- src/AudioHttp/HLSStream.h | 2 + src/AudioHttp/HttpRequest.h | 2 +- src/AudioLibs/AudioKit.h | 7 +- src/AudioTools/AudioTypes.h | 8 +- 8 files changed, 365 insertions(+), 36 deletions(-) create mode 100644 src/AudioCodecs/CodecADTS.h diff --git a/src/AudioBasic/Collections/Vector.h b/src/AudioBasic/Collections/Vector.h index 6925582ba4..7feeb86578 100644 --- a/src/AudioBasic/Collections/Vector.h +++ b/src/AudioBasic/Collections/Vector.h @@ -2,6 +2,8 @@ #ifdef USE_INITIALIZER_LIST # include "InitializerList.h" #endif +#include + namespace audio_tools { /** @@ -169,9 +171,12 @@ class Vector { len++; } - inline void push_front(T value){ + void push_front(T value){ resize_internal(len+1, true); - memmove(p_data,p_data+1,len*sizeof(T)); + //memmove(p_data,p_data+1,len*sizeof(T)); + for (int j=len; j >= 0; j--){ + p_data[j+1] = p_data[j]; + } p_data[0] = value; len++; } @@ -183,12 +188,8 @@ class Vector { } inline void pop_front(){ - if (len>0) { - len--; - if (len>0){ - memmove(p_data, p_data+1,len*sizeof(T)); - } - } + erase(0); + } @@ -282,13 +283,21 @@ class Vector { // removes a single element inline void erase(iterator it) { - int pos = it.pos(); + return erase(it.pos()); + } + + // removes a single element + inline void erase(int pos) { if (possetOutput(out_stream); } @@ -78,6 +79,7 @@ class AACDecoderHelix : public AudioDecoder { TRACED(); if (aac!=nullptr) { aac->setDelay(CODEC_DELAY_MS); + aac->setInfoCallback(infoCallback, this); aac->begin(); } } @@ -103,7 +105,10 @@ class AACDecoderHelix : public AudioDecoder { void setAudioInfo(AudioInfo info) override { AudioDecoder::setAudioInfo(info); - aac->setAudioInfo(info.channels, info.sample_rate); + //aac->setAudioInfo(info.channels, info.sample_rate); + if(p_notify!=nullptr && info_notifications_active){ + p_notify->setAudioInfo(info); + } } /// Write AAC data to decoder @@ -121,23 +126,25 @@ class AACDecoderHelix : public AudioDecoder { // aac->flush(); } - /// Defines the callback object to which the Audio information change is provided - virtual void setNotifyAudioChange(AudioInfoSupport &bi) override { - TRACED(); - audioChangeAACHelix = &bi; - if (aac!=nullptr) aac->setInfoCallback(infoCallback, this); - } + // /// Defines the callback object to which the Audio information change is provided + // virtual void setNotifyAudioChange(AudioInfoSupport &bi) override { + // TRACED(); + // audioChangeAACHelix = &bi; + // if (aac!=nullptr) aac->setInfoCallback(infoCallback, this); + // } /// notifies the subscriber about a change static void infoCallback(_AACFrameInfo &i, void* ref){ AACDecoderHelix *p_helix = (AACDecoderHelix *)ref; - if (p_helix!=nullptr && p_helix->audioChangeAACHelix!=nullptr){ + if (p_helix!=nullptr){ TRACED(); AudioInfo baseInfo; baseInfo.channels = i.nChans; baseInfo.sample_rate = i.sampRateOut; baseInfo.bits_per_sample = i.bitsPerSample; - p_helix->audioChangeAACHelix->setAudioInfo(baseInfo); + //p_helix->audioChangeAACHelix->setAudioInfo(baseInfo); + LOGW("sample_rate: %d", i.sampRateOut); + p_helix->setAudioInfo(baseInfo); } } @@ -151,6 +158,10 @@ class AACDecoderHelix : public AudioDecoder { aac->setMaxFrameSize(len); } + void setAudioInfoNotifications(bool active){ + info_notifications_active = active; + } + #ifdef HELIX_PCM_CORRECTED /// Provides the maximum pwm buffer size - this is allocated on the heap and you can reduce the heap size my minimizing this value size_t maxPCMSize() { @@ -175,8 +186,7 @@ class AACDecoderHelix : public AudioDecoder { protected: libhelix::AACDecoderHelix *aac=nullptr; - // audio change notification target - AudioInfoSupport *audioChangeAACHelix=nullptr; + bool info_notifications_active = true; }; diff --git a/src/AudioCodecs/CodecADTS.h b/src/AudioCodecs/CodecADTS.h new file mode 100644 index 0000000000..3ce7c69e4f --- /dev/null +++ b/src/AudioCodecs/CodecADTS.h @@ -0,0 +1,235 @@ +#include "AACDecoderHelix.h" +#include "AudioCodecs/AudioEncoded.h" + +namespace audio_tools { + +#ifndef SYNCWORDH +#define SYNCWORDH 0xff +#define SYNCWORDL 0xf0 +#endif + +struct ADTSParser { + const int adtsSamplingRates[13] = {96000, 88200, 64000, 48000, 44100, + 32000, 24000, 22050, 16000, 12000, + 11025, 8000, 7350}; + + bool is_valid = false; + uint16_t syncword; + uint8_t id; + uint8_t layer; + uint8_t protection_absent; + uint8_t profile; + uint8_t sampling_freq_idx; + uint8_t private_bit; + uint8_t channel_cfg; + uint8_t original_copy; + uint8_t home; + uint8_t copyright_id_bit; + uint8_t copyright_id_start; + uint16_t frame_length; + uint8_t adts_buf_fullness; + uint8_t num_rawdata_blocks; + uint32_t quick_check = 0; + + void begin() { quick_check = 0; } + + bool parse(uint8_t *hdr) { + syncword = (hdr[0] << 4) | (hdr[1] >> 4); + // parse fixed header + id = (hdr[1] >> 3) & 0b1; + layer = (hdr[1] >> 1) & 0b11; + protection_absent = (hdr[1]) & 0b1; + profile = (hdr[2] >> 6) & 0b11; + sampling_freq_idx = (hdr[2] >> 2) & 0b1111; + private_bit = (hdr[2] >> 1) & 0b1; + channel_cfg = ((hdr[2] & 0x01) << 2) | ((hdr[3] & 0xC0) >> 6); + original_copy = (hdr[3] >> 5) & 0b1; + home = (hdr[3] >> 4) & 0b1; + // parse variable header + copyright_id_bit = (hdr[3] >> 3) & 0b1; + copyright_id_start = (hdr[3] >> 2) & 0b1; + // frame_length = ((hdr[3] & 0b11) << 11) | (hdr[4] << 3) | (hdr[5] >> 5); + frame_length = ((((unsigned int)hdr[3] & 0x3)) << 11) | + (((unsigned int)hdr[4]) << 3) | (hdr[5] >> 5); + adts_buf_fullness = ((hdr[5] & 0b11111) << 6) | (hdr[6] >> 2); + num_rawdata_blocks = (hdr[6]) & 0b11; + + LOGD("id:%d layer:%d profile:%d freq:%d channel:%d frame_length:%d", id, + layer, profile, rate(), channel_cfg, + frame_length); + + // check + is_valid = true; + if (syncword != 0b111111111111) { + is_valid = false; + } + if (id > 6) { + LOGD("- Invalid id"); + is_valid = false; + } + if (sampling_freq_idx > 0xb) { + LOGD("- Invalid sampl.freq"); + is_valid = false; + } + if (channel_cfg > 2) { + LOGD("- Invalid channels"); + is_valid = false; + } + if (frame_length > 1024) { + LOGD("- Invalid frame_length"); + is_valid = false; + } + if (!is_valid) { + LOGD("=> Invalid ADTS"); + } + return is_valid; + } + + unsigned int size() { return frame_length; }; + + void log() { + LOGI("%s id:%d layer:%d profile:%d freq:%d channel:%d frame_length:%d", + is_valid ? "+" : "-", id, layer, profile, + rate(), channel_cfg, frame_length); + } + + int rate(){ + return sampling_freq_idx>12? sampling_freq_idx : adtsSamplingRates[sampling_freq_idx]; + } + + bool isSyncWord(uint8_t *buf) { + return ((buf[0] & SYNCWORDH) == SYNCWORDH && + (buf[1] & SYNCWORDL) == SYNCWORDL); + } + + int findSynchWord(unsigned char *buf, int nBytes, int start = 0) { + /* find byte-aligned syncword (12 bits = 0xFFF) */ + for (int i = start; i < nBytes - 1; i++) { + if (isSyncWord(buf + i)) return i; + } + return -1; + } +}; + +/** + * @brief Audio Data Transport Stream (ADTS) is a format similar to Audio Data + * Interchange Format (ADIF), used by MPEG TS or Shoutcast to stream audio + * defined in MPEG-2 Part 7, usually AAC. This parser extracts all valid ADTS + * frames from the data stream ignoring other data. + * + * @ingroup codecs + * @ingroup decoder + * @author Phil Schatzmann + * @copyright GPLv3 + */ +class AACDecoderADTS : public AudioDecoder { + public: + void begin() override { + parser.begin(); + buffer_write_size = 0; + } + + void end() override { buffer.resize(0); } + + /// Write AAC data to decoder + size_t write(const void *dataIn, size_t len) override { + LOGD("AACDecoderADTS::write: %d", len); + + // make sure that we can hold at least the len + if (buffer.size() < len) { + buffer.resize(len); + } + + // write data to buffer + uint8_t *data = (uint8_t *)dataIn; + size_t result = buffer.writeArray(data, len); + LOGD("buffer size: %d", buffer.available()); + + // process open bytes + if (buffer_write_size == 0) { + parseBuffer(); + } else { + // process open frame + if (buffer.available() >= buffer_write_size) { + // write out data + assert(buffer_write_size == parser.size()); + writeFrame(); + buffer_write_size = 0; + } + } + return result; + } + + /// checks if the class is active + operator bool() override { return true; } + + protected: + SingleBuffer buffer{DEFAULT_BUFFER_SIZE}; + ADTSParser parser; + int buffer_write_size = 0; + + void parseBuffer() { + // when nothing is open + while (buffer.available() >= 7 && buffer_write_size == 0) { + int pos = parser.findSynchWord(buffer.data(), buffer.available()); + LOGD("synchword at %d from %d", pos, buffer.available()); + if (pos >= 0) { + processSync(pos); + } else { + // if no sync word was found + int to_delete = max(7, buffer.available()); + buffer.clearArray(to_delete); + LOGW("Removed invalid %d bytes", to_delete); + } + } + } + + void processSync(int pos) { + // remove data up to the sync word + buffer.clearArray(pos); + LOGD("Removing %d", pos); + assert(parser.isSyncWord(buffer.data())); + // the header needs 7 bytes + if (buffer.available() < 7) { + return; + } + + if (parser.parse(buffer.data())) { + processValidFrame(); + } else { + // header not valid -> remove current synch word + buffer.clearArray(2); + LOGD("Removing invalid synch to restart scanning: %d", buffer.available()); + } + } + + void processValidFrame() { + resizeBuffer(); + if (buffer.available() >= parser.size()) { + writeFrame(); + } else { + LOGD("Expecting more data up to %d", parser.size()); + // we must load more data + buffer_write_size = parser.size(); + } + } + + void writeFrame() { + // write out data + parser.log(); + LOGD("writing ADTS Frame: %d bytes", parser.size()); + assert(buffer.available() >= parser.size()); + p_print->write(buffer.data(), parser.size()); + buffer.clearArray(parser.size()); + } + + void resizeBuffer() { + if (parser.size() > buffer.size()) { + LOGI("resize buffer %d to %d", buffer.size(), parser.size()); + buffer.resize(parser.size()); + buffer_write_size = parser.size(); + } + } +}; + +} // namespace audio_tools diff --git a/src/AudioCodecs/CodecMTS.h b/src/AudioCodecs/CodecMTS.h index c9e3f52942..192c949f21 100644 --- a/src/AudioCodecs/CodecMTS.h +++ b/src/AudioCodecs/CodecMTS.h @@ -6,6 +6,7 @@ namespace audio_tools { #include "AudioCodecs/AudioEncoded.h" #include "tsdemux.h" +#include "stdlib.h" #ifndef MTS_PRINT_PIDS_LEN # define MTS_PRINT_PIDS_LEN (16) @@ -18,6 +19,22 @@ namespace audio_tools { #ifndef MTS_WRITE_BUFFER_SIZE # define MTS_WRITE_BUFFER_SIZE 2000 #endif + +#ifndef ALLOC_MEM_INIT +# define ALLOC_MEM_INIT 0 +#endif + +struct AllocSize { + void *data = nullptr; + size_t size = 0; + + AllocSize() = default; + AllocSize(void*data, size_t size){ + this->data = data; + this->size = size; + } +}; + /** * @brief MPEG-TS (MTS) decoder * https://github.com/pschatzmann/arduino-tsdemux @@ -52,12 +69,12 @@ class MTSDecoder : public AudioDecoder { } // log memory allocations ? - if (is_alloc_active){ + //if (is_alloc_active){ ctx.malloc = log_malloc; ctx.realloc = log_realloc; ctx.calloc = log_calloc; ctx.free = log_free; - } + //} // default supported stream types if (stream_types.empty()){ @@ -141,6 +158,7 @@ class MTSDecoder : public AudioDecoder { uint16_t print_pids[MTS_PRINT_PIDS_LEN] = {0}; SingleBuffer buffer{MTS_WRITE_BUFFER_SIZE}; Vector stream_types; + Vector alloc_vector; void set_write_active(bool flag){ //LOGD("is_write_active: %s", flag ? "true":"false"); @@ -254,7 +272,8 @@ class MTSDecoder : public AudioDecoder { } // output data if (p_print != nullptr) { - size_t eff = p_print->write(pes->data_bytes, pes->data_bytes_length); + //size_t eff = p_print->write(pes->data_bytes, pes->data_bytes_length); + size_t eff = writeSamples(p_print,(uint8_t*) pes->data_bytes, pes->data_bytes_length); if(eff!=pes->data_bytes_length){ // we should not get here TRACEE(); @@ -863,6 +882,63 @@ class MTSDecoder : public AudioDecoder { free(mem); } + // // store allocated size in first bytes + // static void* log_malloc (size_t size) { + // void *result = malloc(size); + // memset(result, 0, size); + // AllocSize entry{result, size}; + // self->alloc_vector.push_back(entry); + // assert(find_size(result)>=0); + // LOGI("malloc(%d) -> %p %s\n", (int)size,result, result!=NULL?"OK":"ERROR"); + // return result; + // } + + // static void* log_calloc(size_t num, size_t size){ + // return log_malloc(num*size); + // } + + // static int find_size(void *ptr){ + // for (int j=0;jalloc_vector.size();j++){ + // if (self->alloc_vector[j].data==ptr) return j; + // } + // return -1; + // } + + // static void* log_realloc(void *ptr, size_t size){ + // int pos = find_size(ptr); + // void *result = nullptr; + // if (pos>=0){ + // result = realloc(ptr, size); + // // store size in header + // size_t old_size = self->alloc_vector[pos].size; + // memset(result+old_size, 0, size-old_size); + // self->alloc_vector[pos].size = size; + // } else { + // LOGE("realloc of unallocatd memory %p", ptr); + // result = realloc(ptr, size); + // AllocSize entry{result, size}; + // self->alloc_vector.push_back(entry); + // assert(find_size(result)>=0); + // } + + // LOGI("realloc(%d) -> %p %s\n", (int)size, result, result!=NULL?"OK":"ERROR"); + // return result; + // } + + // static void log_free (void *mem){ + // LOGD("free(%p)\n", mem); + // free(mem); + // int pos = find_size(mem); + // if (pos>=0){ + // self->alloc_vector.erase(pos); + // assert(find_size(mem)==-1); + + // } else { + // LOGE("free of unallocatd memory %p", mem); + // } + // } + + }; // init static variable MTSDecoder *MTSDecoder::self = nullptr; diff --git a/src/AudioHttp/HLSStream.h b/src/AudioHttp/HLSStream.h index ced82aa95e..b905b90ae9 100644 --- a/src/AudioHttp/HLSStream.h +++ b/src/AudioHttp/HLSStream.h @@ -65,6 +65,7 @@ class URLLoader { if(!active) return 0; TRACED(); bufferRefill(); + if (buffer.available()