arduino-audio-tools
AudioOutput.h
1 #pragma once
2 #include "AudioConfig.h"
3 #include "AudioTools/AudioTypes.h"
4 #include "AudioTools/BaseConverter.h"
5 #include "AudioTools/Buffers.h"
6 
7 namespace audio_tools {
8 
9 #if USE_PRINT_FLUSH
10 #define PRINT_FLUSH_OVERRIDE override
11 #else
12 #define PRINT_FLUSH_OVERRIDE
13 #endif
14 
20 class AudioOutput : public Print,
21  public AudioInfoSupport,
22  public AudioInfoSource {
23 public:
24  virtual ~AudioOutput() = default;
25 
26  virtual size_t write(const uint8_t *data, size_t len) override = 0;
27 
28  virtual size_t write(uint8_t ch) override {
29  if (tmp.isFull()) {
30  flush();
31  }
32  return tmp.write(ch);
33  }
34 
35  virtual int availableForWrite() override { return DEFAULT_BUFFER_SIZE; }
36 
37  // removed override because some old implementation did not define this method
38  // as virtual
39  virtual void flush() PRINT_FLUSH_OVERRIDE {
40  if (tmp.available() > 0) {
41  write((const uint8_t *)tmp.address(), tmp.available());
42  }
43  }
44 
45  // overwrite to do something useful
46  virtual void setAudioInfo(AudioInfo newInfo) override {
47  TRACED();
48  if (cfg != newInfo){
49  cfg = newInfo;
50  cfg.logInfo();
51  }
52  AudioInfo out = audioInfoOut();
53  if (out) notifyAudioChange(out);
54  }
55 
57  virtual bool isDeletable() { return false; }
58 
59  virtual AudioInfo audioInfo() override { return cfg; }
60 
63  virtual void writeSilence(size_t len) {
64  int16_t zero = 0;
65  for (int j = 0; j < len / 2; j++) {
66  write((uint8_t *)&zero, 2);
67  }
68  }
69 
70  virtual bool begin(AudioInfo info) {
71  setAudioInfo(info);
72  return begin();
73  }
74 
75  virtual bool begin() {
76  is_active = true;
77  return true;
78  }
79  virtual void end() { is_active = false; }
80  operator bool() { return is_active; }
81 
82 protected:
83  int tmpPos = 0;
84  AudioInfo cfg;
85  SingleBuffer<uint8_t> tmp{MAX_SINGLE_CHARS};
86  bool is_active = false;
87 };
88 
96 class ModifyingOutput : public AudioOutput {
97  public:
99  virtual void setOutput(Print& out) = 0;
100 };
101 
102 
112 template <typename T> class CsvOutput : public AudioOutput {
113 public:
114  CsvOutput(int buffer_size = DEFAULT_BUFFER_SIZE, bool active = true) {
115  this->is_active = active;
116  }
117 
119  CsvOutput(Print &out, int channels = 2, int buffer_size = DEFAULT_BUFFER_SIZE,
120  bool active = true) {
121  this->out_ptr = &out;
122  this->is_active = active;
123  cfg.channels = channels;
124  }
125 
127  void setDelimiter(const char *del) { delimiter_str = del; }
128 
130  const char *delimiter() { return delimiter_str; }
131 
132  AudioInfo defaultConfig(RxTxMode mode) { return defaultConfig(); }
133 
136  AudioInfo info;
137  info.channels = 2;
138  info.sample_rate = 44100;
139  info.bits_per_sample = sizeof(T) * 8;
140  return info;
141  }
142 
144  bool begin(AudioInfo info) override { return begin(info.channels); }
145 
147  bool begin(int channels) {
148  TRACED();
149  cfg.channels = channels;
150  return begin();
151  }
152 
154  bool begin() override {
155  this->is_active = true;
156  // if (out_ptr == &Serial){
157  // Serial.setTimeout(60000);
158  // }
159  return true;
160  }
161 
163  virtual void setAudioInfo(AudioInfo info) override {
164  TRACEI();
165  this->is_active = true;
166  info.logInfo();
167  cfg = info;
168  };
169 
171  virtual size_t write(const uint8_t *data, size_t len) override {
172  LOGD("CsvOutput::write: %d", (int)len);
173  if (!is_active) {
174  LOGE("is not active");
175  return 0;
176  }
177 
178  if (cfg.channels == 0) {
179  LOGW("Channels not defined: using 2");
180  cfg.channels = 2;
181  }
182  size_t lenChannels = len / (sizeof(T) * cfg.channels);
183  if (lenChannels > 0) {
184  writeFrames((T *)data, lenChannels);
185  } else if (len == sizeof(T)) {
186  // if the write contains less then a frame we buffer the data
187  T *data_value = (T *)data;
188  out_ptr->print(data_value[0]);
189  channel++;
190  if (channel == cfg.channels) {
191  out_ptr->println();
192  channel = 0;
193  } else {
194  out_ptr->print(delimiter_str);
195  }
196  } else {
197  LOGE("Unsupported size: %d for channels %d and bits: %d", (int)len,
198  cfg.channels, cfg.bits_per_sample);
199  }
200 #if USE_PRINT_FLUSH
201  out_ptr->flush();
202 #endif
203  return len;
204  }
205 
206  int availableForWrite() override { return 1024; }
207 
208 protected:
209  T *data_ptr;
210  Print *out_ptr = &Serial;
211  int channel = 0;
212  const char *delimiter_str = ",";
213 
214  void writeFrames(T *data_ptr, int frameCount) {
215  for (size_t j = 0; j < frameCount; j++) {
216  for (int ch = 0; ch < cfg.channels; ch++) {
217  if (out_ptr != nullptr && data_ptr != nullptr) {
218  int value = *data_ptr;
219  out_ptr->print(value);
220  }
221  data_ptr++;
222  if (ch < cfg.channels - 1)
223  this->out_ptr->print(delimiter_str);
224  }
225  this->out_ptr->println();
226  }
227  }
228 };
229 
230 // legacy name
231 template <typename T> using CsvStream = CsvOutput<T>;
232 
239 class HexDumpOutput : public AudioOutput {
240 public:
241  HexDumpOutput(int buffer_size = DEFAULT_BUFFER_SIZE, bool active = true) {
242  this->is_active = active;
243  }
244 
246  HexDumpOutput(Print &out, int buffer_size = DEFAULT_BUFFER_SIZE,
247  bool active = true) {
248  this->out_ptr = &out;
249  this->is_active = active;
250  }
251 
252  bool begin() override {
253  TRACED();
254  this->is_active = true;
255  pos = 0;
256  return is_active;
257  }
258 
259  void flush() override {
260  out_ptr->println();
261  pos = 0;
262  }
263 
264  virtual size_t write(const uint8_t *data, size_t len) override {
265  if (!is_active)
266  return 0;
267  TRACED();
268  for (size_t j = 0; j < len; j++) {
269  out_ptr->print(data[j], HEX);
270  out_ptr->print(" ");
271  pos++;
272  if (pos == 8) {
273  out_ptr->print(" - ");
274  }
275  if (pos == 16) {
276  out_ptr->println();
277  pos = 0;
278  }
279  }
280  return len;
281  }
282 
283  //
284  AudioInfo defaultConfig(RxTxMode mode = TX_MODE) {
285  AudioInfo info;
286  return info;
287  }
288 
289 protected:
290  Print *out_ptr = &Serial;
291  int pos = 0;
292 };
293 
294 // legacy name
295 using HexDumpStream = HexDumpOutput;
296 
304 template <typename T>
305 class OutputMixer : public Print {
306 public:
307  OutputMixer() = default;
308 
309  OutputMixer(Print &finalOutput, int outputStreamCount) {
310  setOutput(finalOutput);
311  setOutputCount(outputStreamCount);
312  }
313 
314  void setOutput(Print &finalOutput) { p_final_output = &finalOutput; }
315 
316  void setOutputCount(int count) {
317  output_count = count;
318  buffers.resize(count);
319  for (int i = 0; i < count; i++) {
320  buffers[i] = nullptr;
321  }
322  weights.resize(count);
323  for (int i = 0; i < count; i++) {
324  weights[i] = 1.0;
325  }
326 
327  update_total_weights();
328  }
329 
332  void setWeight(int channel, float weight) {
333  if (channel < size()) {
334  weights[channel] = weight;
335  } else {
336  LOGE("Invalid channel %d - max is %d", channel, size() - 1);
337  }
338  update_total_weights();
339  }
340 
342  bool begin(int copy_buffer_size_bytes = DEFAULT_BUFFER_SIZE,
343  MemoryType memoryType = PS_RAM) {
344  is_active = true;
345  size_bytes = copy_buffer_size_bytes;
346  stream_idx = 0;
347  memory_type = memoryType;
348  allocate_buffers(size_bytes);
349  return true;
350  }
351 
353  void end() {
354  total_weights = 0.0;
355  is_active = false;
356  // release memory for buffers
357  free_buffers();
358  }
359 
361  int size() { return output_count; }
362 
363  size_t write(uint8_t) override { return 0; }
364 
367  size_t write(const uint8_t *data, size_t len) override {
368  size_t result = write(stream_idx, data, len);
369  // after writing the last stream we flush
370  if (is_auto_index) {
371  stream_idx++;
372  if (stream_idx >= output_count) {
373  flushMixer();
374  }
375  }
376  return result;
377  }
378 
380  size_t write(int idx, const uint8_t *buffer_c, size_t bytes) {
381  LOGD("write idx %d: %d", idx, bytes);
382  size_t result = 0;
383  RingBuffer<T> *p_buffer = idx < output_count ? buffers[idx] : nullptr;
384  assert(p_buffer != nullptr);
385  size_t samples = bytes / sizeof(T);
386  if (p_buffer->availableForWrite() >= samples) {
387  result = p_buffer->writeArray((T *)buffer_c, samples) * sizeof(T);
388  } else {
389  LOGW("Available Buffer %d too small %d: requested: %d -> increase the "
390  "buffer size", idx,
391  p_buffer->availableForWrite()*sizeof(T), bytes);
392  }
393  return result;
394  }
395 
397  int availableForWrite() override {
398  return is_active ? availableForWrite(stream_idx) : 0;
399  }
400 
402  int availableForWrite(int idx) {
403  RingBuffer<T> *p_buffer = buffers[idx];
404  if (p_buffer == nullptr)
405  return 0;
406  return p_buffer->availableForWrite() * sizeof(T);
407  }
408 
410  int available(int idx){
411  RingBuffer<T> *p_buffer = buffers[idx];
412  if (p_buffer == nullptr)
413  return 0;
414  return p_buffer->available() * sizeof(T);
415  }
416 
418  void flushMixer() {
419  LOGD("flush");
420  bool result = false;
421 
422  // determine ringbuffer with mininum available data
423  size_t samples = availableSamples();
424  // sum up samples
425  if (samples > 0) {
426  result = true;
427  // mix data from ringbuffers to output
428  output.resize(samples);
429  memset(output.data(), 0, samples * sizeof(T));
430  for (int j = 0; j < output_count; j++) {
431  float weight = weights[j];
432  // sum up input samples to result samples
433  for (int i = 0; i < samples; i++) {
434  output[i] += weight * buffers[j]->read() / total_weights;
435  }
436  }
437 
438  // write output
439  LOGD("write to final out: %d", samples * sizeof(T));
440  p_final_output->write((uint8_t *)output.data(), samples * sizeof(T));
441  }
442  stream_idx = 0;
443  return;
444  }
445 
446  int availableSamples() {
447  size_t samples = 0;
448  for (int j = 0; j < output_count; j++) {
449  int available_samples = buffers[j]->available();
450  if (available_samples > 0){
451  samples = MIN(size_bytes / sizeof(T), (size_t)available_samples);
452  }
453  }
454  return samples;
455  }
456 
458  void resize(int size) {
459  if (size != size_bytes) {
460  allocate_buffers(size);
461  }
462  size_bytes = size;
463  }
464 
465 
466  size_t writeSilence(size_t bytes) {
467  if (bytes == 0) return 0;
468  uint8_t silence[bytes] = {0};
469  return write(stream_idx, silence, bytes);
470  }
471 
472  size_t writeSilence(int idx, size_t bytes){
473  if (bytes == 0) return 0;
474  uint8_t silence[bytes] = {0};
475  return write(idx, silence, bytes);
476  }
477 
479  void setAutoIndex(bool flag){
480  is_auto_index = flag;
481  }
482 
484  void setIndex(int idx){
485  stream_idx = idx;
486  }
487 
489  void next() {
490  stream_idx++;
491  }
492 
493 protected:
494  Vector<RingBuffer<T> *> buffers{0};
495  Vector<T> output{0};
496  Vector<float> weights{0};
497  Print *p_final_output = nullptr;
498  float total_weights = 0.0;
499  bool is_active = false;
500  int stream_idx = 0;
501  int size_bytes = 0;
502  int output_count = 0;
503  MemoryType memory_type;
504  void *p_memory = nullptr;
505  bool is_auto_index = true;
506 
507  void update_total_weights() {
508  total_weights = 0.0;
509  for (int j = 0; j < weights.size(); j++) {
510  total_weights += weights[j];
511  }
512  }
513 
514  void allocate_buffers(int size) {
515  // allocate ringbuffers for each output
516  for (int j = 0; j < output_count; j++) {
517  if (buffers[j] != nullptr) {
518  delete buffers[j];
519  }
520 #if defined(ESP32) && defined(ARDUINO)
521  if (memory_type == PS_RAM && ESP.getFreePsram() >= size) {
522  p_memory = ps_malloc(size);
523  LOGI("Buffer %d allocated %d bytes in PS_RAM", j, size);
524  } else {
525  p_memory = malloc(size);
526  LOGI("Buffer %d allocated %d bytes in RAM", j, size);
527  }
528  if (p_memory != nullptr) {
529  buffers[j] = new (p_memory) RingBuffer<T>(size / sizeof(T));
530  } else {
531  LOGE("Not enough memory to allocate %d bytes", size);
532  }
533 #else
534  buffers[j] = new RingBuffer<T>(size / sizeof(T));
535 #endif
536  }
537  }
538 
539  void free_buffers() {
540  // allocate ringbuffers for each output
541  for (int j = 0; j < output_count; j++) {
542  if (buffers[j] != nullptr) {
543  delete buffers[j];
544 #ifdef ESP32
545  if (p_memory != nullptr) {
546  free(p_memory);
547  }
548 #endif
549  buffers[j] = nullptr;
550  }
551  }
552  }
553 };
554 
555 
560 class MemoryOutput : public AudioOutput {
561 public:
562  MemoryOutput(uint8_t *start, int len) {
563  p_start = start;
564  p_next = start;
565  max_size = len;
566  is_active = true;
567  if (p_next == nullptr) {
568  LOGE("start must not be null");
569  }
570  }
571 
572  bool begin() {
573  is_active = true;
574  p_next = p_start;
575  pos = 0;
576  return true;
577  }
578 
579  size_t write(const uint8_t *data, size_t len) override {
580  if (p_next == nullptr)
581  return 0;
582  if (pos + len <= max_size) {
583  memcpy(p_next, data, len);
584  pos += len;
585  p_next += len;
586  return len;
587  } else {
588  LOGE("Buffer too small: pos:%d, size: %d ", pos, (int)max_size);
589  return 0;
590  }
591  }
592 
593  int availableForWrite() override { return max_size - pos; }
594 
595  int size() { return max_size; }
596 
597 protected:
598  int pos = 0;
599  uint8_t *p_start = nullptr;
600  uint8_t *p_next = nullptr;
601  size_t max_size;
602 };
603 
604 // legacy name
605 using MemoryPrint = MemoryOutput;
606 
616 public:
617  ChannelSplitOutput() = default;
618 
619  ChannelSplitOutput(Print &out, int channel) { addOutput(out, channel); }
620 
623  void addOutput(Print &out, int channel) {
625  def.channel = channel;
626  def.p_out = &out;
627  out_channels.push_back(def);
628  }
629 
630  size_t write(const uint8_t *data, size_t len) override {
631  switch(cfg.bits_per_sample){
632  case 16:
633  return writeT<int16_t>(data, len);
634  case 24:
635  return writeT<int24_t>(data, len);
636  case 32:
637  return writeT<int32_t>(data, len);
638  default:
639  return 0;
640  }
641  }
642 
643 protected:
645  Print *p_out = nullptr;
646  int channel;
647  };
649 
650  template <typename T>
651  size_t writeT(const uint8_t *buffer, size_t size) {
652  int sample_count = size / sizeof(T);
653  int result_size = sample_count / cfg.channels;
654  T *data = (T *)buffer;
655  T result[result_size];
656 
657  for (int ch = 0; ch < out_channels.size(); ch++) {
658  ChannelSelectionOutputDef &def = out_channels[ch];
659  // extract mono result
660  int i = 0;
661  for (int j = def.channel; j < sample_count; j += cfg.channels) {
662  result[i++] = data[j];
663  }
664  // write mono result
665  size_t written =
666  def.p_out->write((uint8_t *)result, result_size * sizeof(T));
667  if (written != result_size * sizeof(T)) {
668  LOGW("Could not write all samples");
669  }
670  }
671  return size;
672  }
673 
674 };
675 
676 
677 } // namespace audio_tools
Supports the subscription to audio change notifications.
Definition: AudioTypes.h:156
Supports changes to the sampling rate, bits and channels.
Definition: AudioTypes.h:137
virtual AudioInfo audioInfoOut()
provides the actual output AudioInfo: this is usually the same as audioInfo() unless we use a transfo...
Definition: AudioTypes.h:144
Abstract Audio Ouptut class.
Definition: AudioOutput.h:22
virtual bool isDeletable()
If true we need to release the related memory in the destructor.
Definition: AudioOutput.h:57
virtual void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition: AudioOutput.h:46
virtual void writeSilence(size_t len)
Definition: AudioOutput.h:63
virtual AudioInfo audioInfo() override
provides the actual input AudioInfo
Definition: AudioOutput.h:59
virtual int writeArray(const T data[], int len)
Fills the buffer data.
Definition: Buffers.h:65
Simple functionality to extract mono streams from a multichannel (e.g. stereo) signal.
Definition: AudioOutput.h:615
void addOutput(Print &out, int channel)
Definition: AudioOutput.h:623
Stream Wrapper which can be used to print the values as readable ASCII to the screen to be analyzed i...
Definition: AudioOutput.h:112
virtual size_t write(const uint8_t *data, size_t len) override
Writes the data - formatted as CSV - to the output stream.
Definition: AudioOutput.h:171
AudioInfo defaultConfig()
Provides the default configuration.
Definition: AudioOutput.h:135
bool begin(int channels)
Starts the processing with the defined number of channels.
Definition: AudioOutput.h:147
CsvOutput(Print &out, int channels=2, int buffer_size=DEFAULT_BUFFER_SIZE, bool active=true)
Constructor.
Definition: AudioOutput.h:119
virtual void setAudioInfo(AudioInfo info) override
defines the number of channels
Definition: AudioOutput.h:163
const char * delimiter()
Provides the current column delimiter.
Definition: AudioOutput.h:130
bool begin(AudioInfo info) override
Starts the processing with the defined number of channels.
Definition: AudioOutput.h:144
bool begin() override
(Re)start (e.g. if channels is set in constructor)
Definition: AudioOutput.h:154
void setDelimiter(const char *del)
Defines an alternative (column) delimiter. The default is ,.
Definition: AudioOutput.h:127
Creates a Hex Dump.
Definition: AudioOutput.h:239
HexDumpOutput(Print &out, int buffer_size=DEFAULT_BUFFER_SIZE, bool active=true)
Constructor.
Definition: AudioOutput.h:246
Writes to a preallocated memory.
Definition: AudioOutput.h:560
Abstract class: Objects can be put into a pipleline.
Definition: AudioOutput.h:96
virtual void setOutput(Print &out)=0
Defines/Changes the output target.
Mixing of multiple outputs to one final output.
Definition: AudioOutput.h:305
void next()
Moves to the next mixing index.
Definition: AudioOutput.h:489
void setIndex(int idx)
Sets the Output Stream index.
Definition: AudioOutput.h:484
size_t write(int idx, const uint8_t *buffer_c, size_t bytes)
Write the data for an individual stream idx which will be mixed together.
Definition: AudioOutput.h:380
size_t write(const uint8_t *data, size_t len) override
Definition: AudioOutput.h:367
int available(int idx)
Provides the available bytes in the buffer.
Definition: AudioOutput.h:410
bool begin(int copy_buffer_size_bytes=DEFAULT_BUFFER_SIZE, MemoryType memoryType=PS_RAM)
Starts the processing.
Definition: AudioOutput.h:342
int availableForWrite() override
Provides the bytes available to write for the current stream buffer.
Definition: AudioOutput.h:397
void flushMixer()
Force output to final destination.
Definition: AudioOutput.h:418
void end()
Remove all input streams.
Definition: AudioOutput.h:353
void setWeight(int channel, float weight)
Definition: AudioOutput.h:332
int availableForWrite(int idx)
Provides the bytes available to write for the indicated stream index.
Definition: AudioOutput.h:402
void resize(int size)
Resizes the buffer to the indicated number of bytes.
Definition: AudioOutput.h:458
void setAutoIndex(bool flag)
Automatically increment mixing index after each write.
Definition: AudioOutput.h:479
int size()
Number of stremams to which are mixed together.
Definition: AudioOutput.h:361
Definition: NoArduino.h:58
Implements a typed Ringbuffer.
Definition: Buffers.h:298
virtual int availableForWrite()
provides the number of entries that are available to write
Definition: Buffers.h:365
virtual int available()
provides the number of entries that are available to read
Definition: Buffers.h:362
T * address() override
Provides address to beginning of the buffer.
Definition: Buffers.h:243
bool write(T sample) override
write add an entry to the buffer
Definition: Buffers.h:188
int available() override
provides the number of entries that are available to read
Definition: Buffers.h:213
bool isFull() override
checks if the buffer is full
Definition: Buffers.h:220
Vector implementation which provides the most important methods as defined by std::vector....
Definition: Vector.h:21
MemoryType
Memory types.
Definition: AudioTypes.h:33
RxTxMode
The Microcontroller is the Audio Source (TX_MODE) or Audio Sink (RX_MODE). RXTX_MODE is Source and Si...
Definition: AudioTypes.h:26
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AnalogAudioArduino.h:12
Basic Audio information which drives e.g. I2S.
Definition: AudioTypes.h:50
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition: AudioTypes.h:53
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition: AudioTypes.h:55
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition: AudioTypes.h:57