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