mirror of
https://github.com/pschatzmann/arduino-audio-tools.git
synced 2024-09-21 10:27:27 +00:00
HLSStream DRAFT
This commit is contained in:
parent
ba4376e2db
commit
dcc2b36d24
@ -1,35 +0,0 @@
|
||||
#include "AudioTools.h"
|
||||
#include "AudioCodecs/CodecMTS.h"
|
||||
#include "AudioCodecs/CodecAACHelix.h"
|
||||
|
||||
|
||||
// HLSStream hls(out,"SSID", "PWD");
|
||||
const int buffer_size = 1024;
|
||||
CsvOutput<int16_t> out(Serial, 2); // Or use StdOuput
|
||||
MTSDecoder decoder;
|
||||
AACDecoderHelix aac;
|
||||
EncodedAudioStream dec_str(&out, &aac); // Decoding stream
|
||||
EncodedAudioStream mts_str(&dec_str, &decoder);
|
||||
|
||||
FILE *file;
|
||||
uint8_t buffer[buffer_size];
|
||||
|
||||
// Arduino Setup
|
||||
void setup(void) {
|
||||
// Serial.begin(115200);
|
||||
AudioLogger::instance().begin(Serial, AudioLogger::Debug);
|
||||
|
||||
file = fopen("/home/pschatzmann/Downloads/test.ts", "rb");
|
||||
dec_str.begin();
|
||||
mts_str.begin();
|
||||
}
|
||||
|
||||
// Arduino loop
|
||||
void loop() {
|
||||
size_t fileSize = fread(buffer, 1, buffer_size, file);
|
||||
if (fileSize > 0) {
|
||||
int written = mts_str.write(buffer, fileSize);
|
||||
LOGI("written %d, ", written);
|
||||
fileSize = fread(buffer, 1, buffer_size, file);
|
||||
}
|
||||
}
|
89
examples/tests/test-vector/test-vector.ino
Normal file
89
examples/tests/test-vector/test-vector.ino
Normal file
@ -0,0 +1,89 @@
|
||||
|
||||
#include "AudioTools.h"
|
||||
|
||||
void print(const char *title, Vector<int> &vector) {
|
||||
Serial.println(title);
|
||||
for (int j = 0; j < vector.size(); j++) {
|
||||
Serial.print(vector[j]);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
void testPushBack() {
|
||||
Vector<int> vector;
|
||||
for (int j = 0; j < 10; j++) {
|
||||
vector.push_back(j);
|
||||
}
|
||||
print("testPushBack", vector);
|
||||
for (int j = 0; j < 10; j++) {
|
||||
assert(vector[j] == j);
|
||||
}
|
||||
}
|
||||
|
||||
void testPushFront() {
|
||||
Vector<int> vector;
|
||||
for (int j = 0; j < 10; j++) {
|
||||
vector.push_front(j);
|
||||
}
|
||||
print("testPushFront", vector);
|
||||
|
||||
for (int j = 0; j < 10; j++) {
|
||||
assert(vector[9 - j] == j);
|
||||
}
|
||||
}
|
||||
|
||||
void testPopFront() {
|
||||
Vector<int> vector;
|
||||
for (int j = 0; j < 10; j++) {
|
||||
vector.push_back(j);
|
||||
}
|
||||
vector.pop_front();
|
||||
print("testPopFront", vector);
|
||||
|
||||
assert(vector.size() == 9);
|
||||
for (int j = 0; j < 9; j++) {
|
||||
assert(vector[j] == j + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void testPopBack() {
|
||||
Vector<int> vector;
|
||||
for (int j = 0; j < 10; j++) {
|
||||
vector.push_back(j);
|
||||
}
|
||||
vector.pop_back();
|
||||
print("testPopBack", vector);
|
||||
|
||||
assert(vector.size() == 9);
|
||||
for (int j = 0; j < 9; j++) {
|
||||
assert(vector[j] == j);
|
||||
}
|
||||
}
|
||||
|
||||
void testErase() {
|
||||
Vector<int> vector;
|
||||
for (int j = 0; j < 10; j++) {
|
||||
vector.push_back(j);
|
||||
}
|
||||
vector.erase(0);
|
||||
print("testErase", vector);
|
||||
|
||||
assert(vector.size() == 9);
|
||||
for (int j = 0; j < 9; j++) {
|
||||
assert(vector[j] == j + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
testPushBack();
|
||||
testPushFront();
|
||||
testPopFront();
|
||||
testPopBack();
|
||||
testErase();
|
||||
Serial.print("All tests passed");
|
||||
}
|
||||
|
||||
void loop() {}
|
@ -188,8 +188,7 @@ class Vector {
|
||||
}
|
||||
|
||||
inline void pop_front(){
|
||||
erase(0);
|
||||
|
||||
erase(0);
|
||||
}
|
||||
|
||||
|
||||
|
@ -555,7 +555,8 @@ class Str {
|
||||
/// remove leading spaces
|
||||
virtual void ltrim(){
|
||||
int n = count(' ',0);
|
||||
*this << n;
|
||||
if (n > 0)
|
||||
*this << n;
|
||||
}
|
||||
|
||||
/// remove trailing spaces
|
||||
|
@ -27,6 +27,7 @@ class StrExt : public Str {
|
||||
|
||||
StrExt(int initialAllocatedLength) : Str() {
|
||||
maxlen = initialAllocatedLength;
|
||||
is_const = false;
|
||||
}
|
||||
|
||||
StrExt(Str &source) : Str() {
|
||||
@ -52,7 +53,10 @@ class StrExt : public Str {
|
||||
StrExt (StrExt &&obj) = default;
|
||||
|
||||
// move assignment
|
||||
StrExt& operator = (StrExt &&obj) = default;
|
||||
StrExt& operator = (StrExt &&obj) {
|
||||
set(obj.c_str());
|
||||
return *this;
|
||||
}
|
||||
|
||||
// copy assingment
|
||||
StrExt& operator = (StrExt &obj) {
|
||||
@ -61,19 +65,14 @@ class StrExt : public Str {
|
||||
};
|
||||
|
||||
|
||||
~StrExt() {
|
||||
if (chars!=nullptr){
|
||||
LOGD("delete %d",maxlen);
|
||||
delete [] chars;
|
||||
chars = nullptr;
|
||||
}
|
||||
~StrExt() {
|
||||
}
|
||||
|
||||
bool isOnHeap() {
|
||||
bool isOnHeap() override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isConst() {
|
||||
bool isConst() override {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -134,9 +133,11 @@ class StrExt : public Str {
|
||||
}
|
||||
|
||||
protected:
|
||||
Vector<char> vector;
|
||||
|
||||
bool grow(int newMaxLen){
|
||||
bool grown = false;
|
||||
assert(newMaxLen<1024*10);
|
||||
|
||||
if (chars==nullptr || newMaxLen > maxlen ){
|
||||
LOGD("grow(%d)",newMaxLen);
|
||||
@ -144,18 +145,8 @@ class StrExt : public Str {
|
||||
grown = true;
|
||||
// we use at minimum the defined maxlen
|
||||
int newSize = newMaxLen > maxlen ? newMaxLen : maxlen;
|
||||
if (chars!=nullptr){
|
||||
char* tmp = chars;
|
||||
chars = new char[newSize+1];
|
||||
if (chars!=nullptr){
|
||||
strcpy(chars,tmp);
|
||||
}
|
||||
delete [] tmp;
|
||||
} else {
|
||||
chars = new char[newSize+1];
|
||||
if (chars!=nullptr)
|
||||
chars[0] = 0;
|
||||
}
|
||||
vector.resize(newSize+1);
|
||||
chars = &vector[0];
|
||||
maxlen = newSize;
|
||||
|
||||
}
|
||||
|
@ -380,7 +380,7 @@ class EncodedAudioOutput : public AudioStream {
|
||||
return result;
|
||||
}
|
||||
|
||||
int availableForWrite() override { return ptr_out->availableForWrite(); }
|
||||
int availableForWrite() override { return min(ptr_out->availableForWrite(), DEFAULT_BUFFER_SIZE); }
|
||||
|
||||
/// Returns true if status is active and we still have data to be processed
|
||||
operator bool() { return active; }
|
||||
|
@ -104,8 +104,7 @@ class AACDecoderHelix : public AudioDecoder {
|
||||
}
|
||||
|
||||
void setAudioInfo(AudioInfo info) override {
|
||||
AudioDecoder::setAudioInfo(info);
|
||||
//aac->setAudioInfo(info.channels, info.sample_rate);
|
||||
this->info = info;
|
||||
if(p_notify!=nullptr && info_notifications_active){
|
||||
p_notify->setAudioInfo(info);
|
||||
}
|
||||
@ -114,7 +113,16 @@ class AACDecoderHelix : public AudioDecoder {
|
||||
/// Write AAC data to decoder
|
||||
size_t write(const void* aac_data, size_t len) override {
|
||||
LOGD("AACDecoderHelix::write: %d", (int)len);
|
||||
return aac==nullptr ? 0 : aac->write(aac_data, len);
|
||||
if (aac==nullptr) return 0;
|
||||
int open = len;
|
||||
int processed = 0;
|
||||
uint8_t *data = (uint8_t*)aac_data;
|
||||
while(open>0){
|
||||
int act_write = aac->write(data+processed, min(open, DEFAULT_BUFFER_SIZE));
|
||||
open -= act_write;
|
||||
processed += act_write;
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
|
||||
/// checks if the class is active
|
||||
|
@ -133,7 +133,7 @@ class ADTSDecoder : public AudioDecoder {
|
||||
|
||||
/// Write AAC data to decoder
|
||||
size_t write(const void *dataIn, size_t len) override {
|
||||
LOGD("ADTSDecoder::write: %d", len);
|
||||
LOGD("AACDecoderADTS::write: %d", (int)len);
|
||||
|
||||
// make sure that we can hold at least the len
|
||||
if (buffer.size() < len) {
|
||||
@ -225,7 +225,7 @@ class ADTSDecoder : public AudioDecoder {
|
||||
|
||||
void resizeBuffer() {
|
||||
if (parser.size() > buffer.size()) {
|
||||
LOGI("resize buffer %d to %d", buffer.size(), parser.size());
|
||||
LOGI("resize buffer %d to %d", (int)buffer.size(), (int)parser.size());
|
||||
buffer.resize(parser.size());
|
||||
buffer_write_size = parser.size();
|
||||
}
|
||||
|
@ -252,7 +252,7 @@ class MTSDecoder : public AudioDecoder {
|
||||
TSDPESPacket *pes = (TSDPESPacket *)data;
|
||||
// This is where we write the PES data into our buffer.
|
||||
LOGD("====================");
|
||||
LOGD("PID %d PES Packet, Size: %ld, stream_id=%u, pts=%lu, dts=%lu", pid,
|
||||
LOGD("PID %x PES Packet, Size: %ld, stream_id=%u, pts=%lu, dts=%lu", pid,
|
||||
pes->data_bytes_length, pes->stream_id, pes->pts, pes->dts);
|
||||
// print out the PES Packet data if it's in our print list
|
||||
int i;
|
||||
@ -261,7 +261,7 @@ class MTSDecoder : public AudioDecoder {
|
||||
if (print_pids[i] == pid) {
|
||||
// log data
|
||||
if (logger.isLogging(AudioLogger::Debug)) {
|
||||
logger.print(" PES data ");
|
||||
logger.print(" PES data");
|
||||
logger.print(is_write_active? "active:":"inactive:");
|
||||
int j = 0;
|
||||
while (j < pes->data_bytes_length) {
|
||||
@ -862,24 +862,24 @@ class MTSDecoder : public AudioDecoder {
|
||||
|
||||
static void* log_malloc (size_t size) {
|
||||
void *result = malloc(size);
|
||||
LOGI("malloc(%d) -> %p %s\n", (int)size,result, result!=NULL?"OK":"ERROR");
|
||||
LOGI("malloc(%d) -> %p %s", (int)size,result, result!=NULL?"OK":"ERROR");
|
||||
return result;
|
||||
}
|
||||
|
||||
static void* log_calloc(size_t num, size_t size){
|
||||
void *result = calloc(num, size);
|
||||
LOGI("calloc(%d) -> %p %s\n", (int)(num*size),result, result!=NULL?"OK":"ERROR");
|
||||
LOGI("calloc(%d) -> %p %s", (int)(num*size),result, result!=NULL?"OK":"ERROR");
|
||||
return result;
|
||||
}
|
||||
|
||||
static void* log_realloc(void *ptr, size_t size){
|
||||
void *result = realloc(ptr, size);
|
||||
LOGI("realloc(%d) -> %p %s\n", (int)size, result, result!=NULL?"OK":"ERROR");
|
||||
LOGI("realloc(%d) -> %p %s", (int)size, result, result!=NULL?"OK":"ERROR");
|
||||
return result;
|
||||
}
|
||||
|
||||
static void log_free (void *mem){
|
||||
LOGD("free(%p)\n", mem);
|
||||
LOGD("free(%p)", mem);
|
||||
free(mem);
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@
|
||||
#define LOG_STREAM Serial
|
||||
#endif
|
||||
|
||||
#define LOG_PRINTF_BUFFER_SIZE 256
|
||||
#define LOG_PRINTF_BUFFER_SIZE 303
|
||||
#define LOG_METHOD __PRETTY_FUNCTION__
|
||||
|
||||
// cheange USE_CHECK_MEMORY to 1 to activate memory checks
|
||||
@ -648,6 +648,7 @@ using WiFiServerSecure = BearSSL::WiFiServerSecure;
|
||||
#ifdef IS_DESKTOP
|
||||
# include <Client.h>
|
||||
# include <WiFi.h>
|
||||
# define USE_WIFI
|
||||
# define USE_URL_ARDUINO
|
||||
# define USE_STREAM_WRITE_OVERRIDE
|
||||
# define USE_STREAM_READ_OVERRIDE
|
||||
|
@ -6,7 +6,7 @@
|
||||
#include "AudioBasic/StrExt.h"
|
||||
#include "AudioHttp/URLStream.h"
|
||||
|
||||
#define MAX_HLS_LINE 400
|
||||
#define MAX_HLS_LINE 512
|
||||
|
||||
namespace audio_tools {
|
||||
|
||||
@ -27,6 +27,7 @@ class URLLoader {
|
||||
|
||||
bool begin() {
|
||||
TRACED();
|
||||
buffer.resize(buffer_size, buffer_count);
|
||||
active = true;
|
||||
return true;
|
||||
}
|
||||
@ -74,15 +75,22 @@ class URLLoader {
|
||||
return stream.httpRequest().reply().get(CONTENT_TYPE);
|
||||
}
|
||||
|
||||
const char *contentLength() {
|
||||
if (!stream) return nullptr;
|
||||
return stream.httpRequest().reply().get(CONTENT_LENGTH);
|
||||
int contentLength() {
|
||||
if (!stream) return 0;
|
||||
return stream.contentLength();
|
||||
}
|
||||
|
||||
void setBuffer(int size, int count){
|
||||
buffer_size = size;
|
||||
buffer_count = count;
|
||||
}
|
||||
|
||||
protected:
|
||||
Vector<const char*> urls{10};
|
||||
NBuffer<uint8_t> buffer{DEFAULT_BUFFER_SIZE, 50};
|
||||
NBuffer<uint8_t> buffer{DEFAULT_BUFFER_SIZE, 10};
|
||||
bool active = false;
|
||||
int buffer_size = DEFAULT_BUFFER_SIZE;
|
||||
int buffer_count = 10;
|
||||
URLStream stream;
|
||||
|
||||
|
||||
@ -90,12 +98,18 @@ protected:
|
||||
void bufferRefill() {
|
||||
TRACED();
|
||||
// we have nothing to do
|
||||
if (urls.empty()) return;
|
||||
if (buffer.availableForWrite()==0) return;
|
||||
if (urls.empty()) {
|
||||
LOGD("urls empty");
|
||||
return;
|
||||
}
|
||||
if (buffer.availableForWrite()==0) {
|
||||
LOGD("buffer full");
|
||||
return;
|
||||
}
|
||||
|
||||
// switch current stream if we have no more data
|
||||
if ((!stream || stream.totalRead()==stream.contentLength()) && !urls.empty()) {
|
||||
assert(stream.available()==0);
|
||||
LOGD("Refilling");
|
||||
const char* url = urls[0];
|
||||
urls.pop_front();
|
||||
assert(urls[0]!=url);
|
||||
@ -106,26 +120,26 @@ protected:
|
||||
LOGI("Playing %s of %d", url, urls.size());
|
||||
|
||||
stream.clear();
|
||||
stream.setWaitForData(false);
|
||||
stream.setWaitForData(true);
|
||||
if (!stream.begin(url)){
|
||||
TRACEE();
|
||||
}
|
||||
// free memory
|
||||
delete(url);
|
||||
return;
|
||||
}
|
||||
|
||||
// copy data to buffer
|
||||
stream.waitForData();
|
||||
//LOGD("waitForData");
|
||||
//stream.waitForData();
|
||||
int to_write = min(buffer.availableForWrite(),DEFAULT_BUFFER_SIZE);
|
||||
if (to_write>0){
|
||||
int total = 0;
|
||||
while(to_write>0){
|
||||
if (stream.totalRead()==stream.contentLength()) break;
|
||||
uint8_t tmp[to_write]={0};
|
||||
int read = stream.readBytes(tmp, to_write);
|
||||
total += read;
|
||||
if (read>0){
|
||||
if (stream.totalRead()==stream.contentLength()) break;
|
||||
buffer.writeArray(tmp, read);
|
||||
to_write = min(buffer.availableForWrite(),DEFAULT_BUFFER_SIZE);
|
||||
} else {
|
||||
@ -242,14 +256,13 @@ class HLSParser {
|
||||
return url_loader.contentType();
|
||||
}
|
||||
|
||||
const char *contentLength() {
|
||||
int contentLength() {
|
||||
return url_loader.contentLength();
|
||||
}
|
||||
|
||||
/// Closes the processing
|
||||
void end() {
|
||||
TRACEI();
|
||||
//timer.end();
|
||||
codec.clear();
|
||||
segments_url_str.clear();
|
||||
url_stream.end();
|
||||
@ -267,6 +280,10 @@ class HLSParser {
|
||||
custom_log_level.set(level);
|
||||
}
|
||||
|
||||
void setBuffer(int size, int count){
|
||||
url_loader.setBuffer(size, count);
|
||||
}
|
||||
|
||||
protected:
|
||||
CustomLogLevel custom_log_level;
|
||||
int bandwidth = 0;
|
||||
@ -509,7 +526,7 @@ class HLSStream : public AudioStream {
|
||||
return parser.contentType();
|
||||
}
|
||||
|
||||
const char *contentLength() {
|
||||
int contentLength() {
|
||||
return parser.contentLength();
|
||||
}
|
||||
|
||||
@ -528,6 +545,11 @@ class HLSStream : public AudioStream {
|
||||
parser.setLogLevel(level);
|
||||
}
|
||||
|
||||
/// Defines the buffer size
|
||||
void setBuffer(int size, int count){
|
||||
parser.setBuffer(size, count);
|
||||
}
|
||||
|
||||
protected:
|
||||
HLSParser parser;
|
||||
const char* ssid = nullptr;
|
||||
@ -547,8 +569,6 @@ class HLSStream : public AudioStream {
|
||||
LOGW("login not supported");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
} // namespace audio_tools
|
||||
|
@ -80,7 +80,8 @@ class HttpHeader {
|
||||
if (activeFlag){
|
||||
(*it)->active = false;
|
||||
} else {
|
||||
delete *it;
|
||||
(*it)->active = false;
|
||||
//delete *it; // this leads to exceptions
|
||||
}
|
||||
}
|
||||
if (!activeFlag){
|
||||
@ -195,8 +196,8 @@ class HttpHeader {
|
||||
msg[len-2] = 0;
|
||||
LOGI(" -> %s ", msg);
|
||||
|
||||
// marke as processed
|
||||
header->active = false;
|
||||
// mark as processed
|
||||
// header->active = false;
|
||||
}
|
||||
|
||||
const char* urlPath() {
|
||||
@ -243,6 +244,7 @@ class HttpHeader {
|
||||
}
|
||||
LOGI("Data availble");
|
||||
}
|
||||
|
||||
readLine(in, line, MAX_HTTP_HEADER_LINE_LENGTH);
|
||||
parse1stLine(line);
|
||||
while (in.available()){
|
||||
@ -254,11 +256,9 @@ class HttpHeader {
|
||||
break;
|
||||
}
|
||||
put(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOGW("connection lost");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// writes the full header to the indicated HttpStreamedMultiOutput stream
|
||||
@ -274,6 +274,12 @@ class HttpHeader {
|
||||
is_written = true;
|
||||
}
|
||||
|
||||
void setProcessed() {
|
||||
for (auto it = lines.begin() ; it != lines.end(); ++it){
|
||||
(*it)->active = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// automatically create new lines
|
||||
void setAutoCreateLines(bool is_auto_line){
|
||||
create_new_lines = is_auto_line;
|
||||
|
@ -237,7 +237,7 @@ class HttpRequest {
|
||||
if (!this->connected()){
|
||||
LOGI("process connecting to host %s port %d", url.host(), url.port());
|
||||
int is_connected = connect(url.host(), url.port(), clientTimeout);
|
||||
if (is_connected!=1){
|
||||
if (!is_connected){
|
||||
LOGE("Connect failed");
|
||||
return false;
|
||||
}
|
||||
@ -249,6 +249,9 @@ class HttpRequest {
|
||||
LOGI("Free heap: %u", ESP.getFreeHeap());
|
||||
#endif
|
||||
|
||||
reply_header.setProcessed();
|
||||
|
||||
|
||||
host_name = url.host();
|
||||
request_header.setValues(action, url.path());
|
||||
if (lenData>0){
|
||||
@ -330,7 +333,7 @@ class HttpRequest {
|
||||
LOGI("connected %d timeout %d", is_connected, (int) timeout);
|
||||
return is_connected;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -52,6 +52,9 @@ class URLStream : public AbstractURLStream {
|
||||
setPassword(password);
|
||||
}
|
||||
|
||||
URLStream(const URLStream&) = delete;
|
||||
|
||||
|
||||
~URLStream(){
|
||||
TRACED();
|
||||
end();
|
||||
@ -93,7 +96,7 @@ class URLStream : public AbstractURLStream {
|
||||
url.setUrl(url_str.c_str());
|
||||
int result = -1;
|
||||
|
||||
// close it - if we have an active connection
|
||||
// close it - if we have an active connection
|
||||
if (active) end();
|
||||
|
||||
// optional: login if necessary
|
||||
@ -232,8 +235,8 @@ class URLStream : public AbstractURLStream {
|
||||
wait_for_data = flag;
|
||||
}
|
||||
|
||||
size_t contentLength() {
|
||||
return request.contentLength();
|
||||
int contentLength() {
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t totalRead() {
|
||||
@ -387,7 +390,6 @@ class URLStream : public AbstractURLStream {
|
||||
delay(500);
|
||||
}
|
||||
#else
|
||||
LOGE("login not supported");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -82,13 +82,13 @@ public:
|
||||
}
|
||||
|
||||
virtual int print(char c, PrintCharFmt spec){
|
||||
char result[3];
|
||||
char result[5];
|
||||
switch(spec){
|
||||
case DEC:
|
||||
snprintf(result, 3,"%c", c);
|
||||
return print(result);
|
||||
case HEX:
|
||||
snprintf(result, 3,"%x", c & 0xff);
|
||||
snprintf(result, 3,"%x", c);
|
||||
return print(result);
|
||||
}
|
||||
return -1;
|
||||
|
@ -189,10 +189,6 @@ class PortAudioStream : public AudioStream {
|
||||
return len;
|
||||
}
|
||||
|
||||
int available() override {
|
||||
return DEFAULT_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
PaStream *stream = nullptr;
|
||||
@ -219,7 +215,7 @@ class PortAudioStream : public AudioStream {
|
||||
|
||||
/// automatically start the stream when we start to get data
|
||||
void startStream() {
|
||||
if (!stream_started) {
|
||||
if (!stream_started && stream != nullptr) {
|
||||
TRACED();
|
||||
err = Pa_StartStream( stream );
|
||||
if( err == paNoError ) {
|
||||
|
@ -1582,6 +1582,87 @@ class CallbackStream : public AudioStream {
|
||||
size_t (*cb_read)(uint8_t* data, size_t len) = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Provides data from a concatenation of Streams. Please note that the provided
|
||||
* Streams can be played only once! You will need to reset them (e.g. moving the file pointer to the beginning)
|
||||
* and readd them back if you want to process them a second time. The default timeout on the available() method
|
||||
* is set to 0. This might be not good if you use e.g. a URLStream.
|
||||
* @ingroup io
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
class CatStream : public AudioStream {
|
||||
CatStream(){
|
||||
_timeout = 0;
|
||||
}
|
||||
void add(Stream *stream){
|
||||
input_streams.push_back(stream);
|
||||
}
|
||||
void add(Stream &stream){
|
||||
input_streams.push_back(&stream);
|
||||
}
|
||||
|
||||
bool begin() override {
|
||||
is_active = true;
|
||||
return AudioStream::begin();
|
||||
}
|
||||
|
||||
void end() override {
|
||||
is_active = false;
|
||||
return AudioStream::end();
|
||||
}
|
||||
|
||||
int available() override {
|
||||
if (!is_active) return 0;
|
||||
if (!moveToNextStreamOnEnd()){
|
||||
return 0;
|
||||
}
|
||||
return availableWithTimout();
|
||||
}
|
||||
|
||||
size_t readBytes(uint8_t* data, size_t len) override {
|
||||
if (!is_active) return 0;
|
||||
if (!moveToNextStreamOnEnd()){
|
||||
return 0;
|
||||
}
|
||||
return p_current_stream->readBytes(data, len);
|
||||
}
|
||||
|
||||
/// Returns true if active and we still have data
|
||||
operator bool(){
|
||||
return is_active && available()>0;
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
Vector<Stream*> input_streams;
|
||||
Stream *p_current_stream = nullptr;
|
||||
bool is_active = false;
|
||||
|
||||
/// moves to the next stream if necessary: returns true if we still have a valid stream
|
||||
bool moveToNextStreamOnEnd(){
|
||||
if ((p_current_stream==nullptr || availableWithTimout()) && !input_streams.empty()){
|
||||
p_current_stream = input_streams[0];
|
||||
input_streams.pop_front();
|
||||
}
|
||||
// returns true if we have a valid stream
|
||||
return p_current_stream!=nullptr;
|
||||
}
|
||||
|
||||
int availableWithTimout(){
|
||||
int result = p_current_stream->available();
|
||||
if (result==0){
|
||||
for (int j=0; j <_timeout/10;j++){
|
||||
delay(10);
|
||||
result = p_current_stream->available();
|
||||
if (result!=0) break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Stream to which we can apply Filters for each channel. The filter
|
||||
* might change the result size!
|
||||
|
@ -549,35 +549,11 @@ template <typename T>
|
||||
class NBuffer : public BaseBuffer<T> {
|
||||
public:
|
||||
NBuffer(int size, int count) {
|
||||
filled_buffers.resize(count);
|
||||
avaliable_buffers.resize(count);
|
||||
|
||||
write_buffer_count = 0;
|
||||
buffer_count = count;
|
||||
buffer_size = size;
|
||||
for (int j = 0; j < count; j++) {
|
||||
avaliable_buffers[j] = new SingleBuffer<T>(size);
|
||||
if (avaliable_buffers[j] == nullptr) {
|
||||
LOGE("Not Enough Memory for buffer %d", j);
|
||||
}
|
||||
}
|
||||
resize(size, count);
|
||||
}
|
||||
|
||||
virtual ~NBuffer() {
|
||||
delete actual_write_buffer;
|
||||
delete actual_read_buffer;
|
||||
|
||||
BaseBuffer<T> *ptr = getNextAvailableBuffer();
|
||||
while (ptr != nullptr) {
|
||||
delete ptr;
|
||||
ptr = getNextAvailableBuffer();
|
||||
}
|
||||
|
||||
ptr = getNextFilledBuffer();
|
||||
while (ptr != nullptr) {
|
||||
delete ptr;
|
||||
ptr = getNextFilledBuffer();
|
||||
}
|
||||
freeMemory();
|
||||
}
|
||||
|
||||
// reads an entry from the buffer
|
||||
@ -724,7 +700,23 @@ class NBuffer : public BaseBuffer<T> {
|
||||
return result;
|
||||
}
|
||||
|
||||
void setBufferSize(int size) { buffer_size = size; }
|
||||
void resize(int size, int count) {
|
||||
if (buffer_size==size && buffer_count == count)
|
||||
return;
|
||||
freeMemory();
|
||||
filled_buffers.resize(count);
|
||||
avaliable_buffers.resize(count);
|
||||
|
||||
write_buffer_count = 0;
|
||||
buffer_count = count;
|
||||
buffer_size = size;
|
||||
for (int j = 0; j < count; j++) {
|
||||
avaliable_buffers[j] = new SingleBuffer<T>(size);
|
||||
if (avaliable_buffers[j] == nullptr) {
|
||||
LOGE("Not Enough Memory for buffer %d", j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
int buffer_size = 0;
|
||||
@ -740,6 +732,25 @@ class NBuffer : public BaseBuffer<T> {
|
||||
// empty constructor only allowed by subclass
|
||||
NBuffer() = default;
|
||||
|
||||
void freeMemory() {
|
||||
delete actual_write_buffer;
|
||||
actual_write_buffer = nullptr;
|
||||
delete actual_read_buffer;
|
||||
actual_read_buffer = nullptr;
|
||||
|
||||
BaseBuffer<T> *ptr = getNextAvailableBuffer();
|
||||
while (ptr != nullptr) {
|
||||
delete ptr;
|
||||
ptr = getNextAvailableBuffer();
|
||||
}
|
||||
|
||||
ptr = getNextFilledBuffer();
|
||||
while (ptr != nullptr) {
|
||||
delete ptr;
|
||||
ptr = getNextFilledBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
void resetCurrent() {
|
||||
if (actual_read_buffer != nullptr) {
|
||||
actual_read_buffer->reset();
|
||||
|
@ -9,12 +9,12 @@ set (DCMAKE_CXX_FLAGS "-Werror")
|
||||
include(FetchContent)
|
||||
|
||||
# Deactivate Emulator and Portaudio
|
||||
set(ADD_ARDUINO_EMULATOR OFF CACHE BOOL "Add Arduino Emulator Library")
|
||||
set(ADD_ARDUINO_EMULATOR ON CACHE BOOL "Add Arduino Emulator Library")
|
||||
set(ADD_PORTAUDIO OFF CACHE BOOL "Add Portaudio Library")
|
||||
|
||||
# Build with arduino-audio-tools
|
||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../.. ${CMAKE_CURRENT_BINARY_DIR}/arduino-audio-tools )
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../.. ${CMAKE_CURRENT_BINARY_DIR}/arduino-audio-tools )
|
||||
endif()
|
||||
|
||||
# Build with tms
|
||||
@ -36,13 +36,13 @@ endif()
|
||||
|
||||
# build sketch as executable
|
||||
set_source_files_properties(hls.ino PROPERTIES LANGUAGE CXX)
|
||||
add_executable (hls hls.ino)
|
||||
add_executable (hls hls.cpp ../main.cpp )
|
||||
|
||||
# set preprocessor defines
|
||||
target_compile_definitions(hls PUBLIC -DIS_MIN_DESKTOP -DEXIT_ON_STOP -DHELIX_PRINT)
|
||||
target_compile_definitions(hls PUBLIC -DARDUINO -DIS_DESKTOP -DEXIT_ON_STOP -DHELIX_PRINT)
|
||||
|
||||
# specify libraries
|
||||
target_link_libraries(hls arduino-audio-tools tsdemux arduino_helix)
|
||||
target_link_libraries(hls arduino-audio-tools arduino_emulator tsdemux arduino_helix portaudio)
|
||||
|
||||
|
||||
|
46
tests-cmake/hls/hls.cpp
Normal file
46
tests-cmake/hls/hls.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
#include "AudioTools.h"
|
||||
#include "AudioCodecs/CodecMTS.h"
|
||||
#include "AudioCodecs/CodecADTS.h"
|
||||
#include "AudioCodecs/CodecAACHelix.h"
|
||||
#include "AudioLibs/PortAudioStream.h"
|
||||
|
||||
AudioInfo info(48000,2,16);
|
||||
HLSStream hls_stream("NA", "NA");
|
||||
// HexDumpOutput hex(Serial);
|
||||
// NullStream null;
|
||||
//CsvOutput<int16_t> out(Serial, 2); // Or use StdOuput
|
||||
PortAudioStream out;
|
||||
MTSDecoder mts;
|
||||
AACDecoderADTS adts;
|
||||
AACDecoderHelix aac;
|
||||
EncodedAudioStream aac_stream(&out, &aac);
|
||||
EncodedAudioStream adts_stream(&aac_stream, &adts);
|
||||
EncodedAudioStream mts_stream(&adts_stream, &mts);
|
||||
StreamCopy copier(mts_stream, hls_stream);
|
||||
|
||||
// Arduino Setup
|
||||
void setup(void) {
|
||||
//Serial.begin(115200);
|
||||
AudioLogger::instance().begin(Serial, AudioLogger::Info);
|
||||
//hls_stream.setLogLevel(AudioLogger::Debug); // hls_stream is quite chatty at Info
|
||||
//adts_stream.setLogLevel(AudioLogger::Debug);
|
||||
mts_stream.setLogLevel(AudioLogger::Debug);
|
||||
|
||||
aac.setAudioInfoNotifications(false);
|
||||
|
||||
auto cfg = out.defaultConfig(TX_MODE);
|
||||
cfg.copyFrom(info);
|
||||
out.begin();
|
||||
|
||||
mts_stream.begin();
|
||||
aac_stream.begin();
|
||||
adts_stream.begin();
|
||||
|
||||
hls_stream.begin("http://a.files.bbci.co.uk/media/live/manifesto/audio/simulcast/hls/nonuk/sbr_vlow/ak/bbc_world_service.m3u8");
|
||||
Serial.println("playing...");
|
||||
}
|
||||
|
||||
// Arduino loop
|
||||
void loop() {
|
||||
copier.copy();
|
||||
}
|
@ -9,7 +9,7 @@ StreamCopy copier(null_out, url); // copy url to decoder
|
||||
|
||||
void setup(){
|
||||
Serial.begin(115200);
|
||||
AudioLogger::instance().begin(Serial, AudioLogger::Debug);
|
||||
AudioLogger::instance().begin(Serial, AudioLogger::Info);
|
||||
// mp3 radio
|
||||
url.begin("http://stream.srg-ssr.ch/m/rsj/mp3_128","audio/mp3");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user