arduino-audio-tools
ContainerOgg.h
1 #pragma once
2 
3 #include "AudioTools/AudioCodecs/AudioCodecsBase.h"
4 #include "AudioTools/AudioCodecs/CodecOpus.h"
5 #include "AudioTools/CoreAudio/Buffers.h"
6 #include "oggz/oggz.h"
7 
8 #define OGG_READ_SIZE (1024)
9 #define OGG_DEFAULT_BUFFER_SIZE (OGG_READ_SIZE)
10 // #define OGG_DEFAULT_BUFFER_SIZE (246)
11 // #define OGG_READ_SIZE (512)
12 
13 namespace audio_tools {
14 
27  public:
33  p_codec = &dec_copy;
34  out.setDecoder(p_codec);
35  }
36 
37  OggContainerDecoder(AudioDecoder *decoder) { setDecoder(decoder); }
38 
39  OggContainerDecoder(AudioDecoder &decoder) { setDecoder(&decoder); }
40 
41  void setDecoder(AudioDecoder *decoder) {
42  p_codec = decoder;
43  out.setDecoder(p_codec);
44  }
45 
47  void setOutput(Print &print) override { out.setOutput(&print); }
48 
50  out.addNotifyAudioChange(bi);
52  }
53 
54  AudioInfo audioInfo() override { return out.audioInfo(); }
55 
56  bool begin(AudioInfo info) override {
57  TRACED();
58  this->info = info;
59  return begin();
60  }
61 
62  bool begin() override {
63  TRACED();
64  out.setAudioInfo(info);
65  out.begin();
66  if (p_oggz == nullptr) {
67  p_oggz = oggz_new(OGGZ_READ | OGGZ_AUTO); // OGGZ_NONSTRICT
68  is_open = true;
69  // Callback to Replace standard IO
70  if (oggz_io_set_read(p_oggz, ogg_io_read, this) != 0) {
71  LOGE("oggz_io_set_read");
72  is_open = false;
73  }
74  // Callback
75  if (oggz_set_read_callback(p_oggz, -1, read_packet, this) != 0) {
76  LOGE("oggz_set_read_callback");
77  is_open = false;
78  }
79 
80  if (oggz_set_read_page(p_oggz, -1, read_page, this) != 0) {
81  LOGE("oggz_set_read_page");
82  is_open = false;
83  }
84  }
85  return is_open;
86  }
87 
88  void end() override {
89  TRACED();
90  flush();
91  out.end();
92  is_open = false;
93  oggz_close(p_oggz);
94  p_oggz = nullptr;
95  }
96 
97  void flush() {
98  LOGD("oggz_read...");
99  while ((oggz_read(p_oggz, OGG_READ_SIZE)) > 0)
100  ;
101  }
102 
103  virtual size_t write(const uint8_t *data, size_t len) override {
104  LOGD("write: %d", (int)len);
105 
106  // fill buffer
107  size_t size_consumed = buffer.writeArray((uint8_t *)data, len);
108  if (buffer.availableForWrite() == 0) {
109  // Read all bytes into oggz, calling any read callbacks on the fly.
110  flush();
111  }
112  // write remaining bytes
113  if (size_consumed < len) {
114  size_consumed += buffer.writeArray((uint8_t *)data + size_consumed,
115  len - size_consumed);
116  flush();
117  }
118  return size_consumed;
119  }
120 
121  virtual operator bool() override { return is_open; }
122 
123  protected:
124  EncodedAudioOutput out;
125  CopyDecoder dec_copy;
126  AudioDecoder *p_codec = nullptr;
127  RingBuffer<uint8_t> buffer{OGG_DEFAULT_BUFFER_SIZE};
128  OGGZ *p_oggz = nullptr;
129  bool is_open = false;
130  long pos = 0;
131 
132  // Final Stream Callback -> provide data to ogg
133  static size_t ogg_io_read(void *user_handle, void *buf, size_t n) {
134  LOGD("ogg_io_read: %d", (int)n);
135  size_t result = 0;
136  OggContainerDecoder *self = (OggContainerDecoder *)user_handle;
137  if (self->buffer.available() >= n) {
138  OggContainerDecoder *self = (OggContainerDecoder *)user_handle;
139  result = self->buffer.readArray((uint8_t *)buf, n);
140  self->pos += result;
141 
142  } else {
143  result = 0;
144  }
145  return result;
146  }
147 
148  // Process full packet
149  static int read_packet(OGGZ *oggz, oggz_packet *zp, long serialno,
150  void *user_data) {
151  LOGD("read_packet: %d", (int)zp->op.bytes);
152  OggContainerDecoder *self = (OggContainerDecoder *)user_data;
153  ogg_packet *op = &zp->op;
154  int result = op->bytes;
155  if (op->b_o_s) {
156  self->beginOfSegment(op);
157  } else if (op->e_o_s) {
158  self->endOfSegment(op);
159  } else {
160  if (memcmp(op->packet, "OpusTags", 8) == 0) {
161  self->beginOfSegment(op);
162  } else {
163  LOGD("process audio packet");
164  int eff = self->out.write(op->packet, op->bytes);
165  if (eff != result) {
166  LOGE("Incomplere write");
167  }
168  }
169  }
170  // 0 = success
171  return 0;
172  }
173 
174  static int read_page(OGGZ *oggz, const ogg_page *og, long serialno,
175  void *user_data) {
176  LOGD("read_page: %d", (int)og->body_len);
177  // 0 = success
178  return 0;
179  }
180 
181  virtual void beginOfSegment(ogg_packet *op) {
182  LOGD("bos");
183  if (op->bytes == sizeof(AudioInfo)) {
184  AudioInfo cfg(*(AudioInfo*)op->packet);
185  cfg.logInfo();
186  if (cfg.bits_per_sample == 16 || cfg.bits_per_sample == 24 ||
187  cfg.bits_per_sample == 32) {
188  setAudioInfo(cfg);
189  } else {
190  LOGE("Invalid AudioInfo")
191  }
192  } else {
193  LOGE("Invalid Header")
194  }
195  }
196 
197  virtual void endOfSegment(ogg_packet *op) {
198  // end segment not supported
199  LOGW("e_o_s");
200  }
201 };
202 
210  public:
211  // Empty Constructor - the output stream must be provided with begin()
212  OggContainerOutput() = default;
213 
215  void setOutput(Print &print) { p_out = &print; }
216 
218  virtual bool begin() override {
219  TRACED();
220  assert(cfg.channels != 0);
221  assert(cfg.sample_rate != 0);
222  is_open = true;
223  if (p_oggz == nullptr) {
224  p_oggz = oggz_new(OGGZ_WRITE | OGGZ_NONSTRICT | OGGZ_AUTO);
225  serialno = oggz_serialno_new(p_oggz);
226  oggz_io_set_write(p_oggz, ogg_io_write, this);
227  packetno = 0;
228  granulepos = 0;
229 
230  if (!writeHeader()) {
231  is_open = false;
232  LOGE("writeHeader");
233  }
234  }
235  return is_open;
236  }
237 
239  void end() override {
240  TRACED();
241 
242  writeFooter();
243 
244  is_open = false;
245  oggz_close(p_oggz);
246  p_oggz = nullptr;
247  }
248 
250  virtual size_t write(const uint8_t *data, size_t len) override {
251  if (data == nullptr) return 0;
252  LOGD("OggContainerOutput::write: %d", (int)len);
253  assert(cfg.channels != 0);
254 
255  // encode the data
256  op.packet = (uint8_t *)data;
257  op.bytes = len;
258  if (op.bytes > 0) {
259  int bytes_per_sample = cfg.bits_per_sample / 8;
260  granulepos += op.bytes / bytes_per_sample; // sample
261  op.granulepos = granulepos;
262  op.b_o_s = false;
263  op.e_o_s = false;
264  op.packetno = packetno++;
265  is_audio = true;
266  if (!writePacket(op, OGGZ_FLUSH_AFTER)) {
267  return 0;
268  }
269  }
270  // trigger pysical write
271  while ((oggz_write(p_oggz, len)) > 0)
272  ;
273 
274  return len;
275  }
276  bool isOpen() { return is_open; }
277 
278  protected:
279  Print *p_out = nullptr;
280  bool is_open = false;
281  OGGZ *p_oggz = nullptr;
282  ogg_packet op;
283  ogg_packet oh;
284  size_t granulepos = 0;
285  size_t packetno = 0;
286  long serialno = -1;
287  bool is_audio = false;
288 
289  virtual bool writePacket(ogg_packet &op, int flag = 0) {
290  LOGD("writePacket: %d", (int)op.bytes);
291  long result = oggz_write_feed(p_oggz, &op, serialno, flag, NULL);
292  if (result < 0 && result != OGGZ_ERR_OUT_OF_MEMORY) {
293  LOGE("oggz_write_feed: %d", (int)result);
294  return false;
295  }
296  return true;
297  }
298 
299  virtual bool writeHeader() {
300  TRACED();
301  oh.packet = (uint8_t *)&cfg;
302  oh.bytes = sizeof(AudioInfo);
303  oh.granulepos = 0;
304  oh.packetno = packetno++;
305  oh.b_o_s = true;
306  oh.e_o_s = false;
307  is_audio = false;
308  return writePacket(oh);
309  }
310 
311  virtual bool writeFooter() {
312  TRACED();
313  op.packet = (uint8_t *)nullptr;
314  op.bytes = 0;
315  op.granulepos = granulepos;
316  op.packetno = packetno++;
317  op.b_o_s = false;
318  op.e_o_s = true;
319  is_audio = false;
320  return writePacket(op, OGGZ_FLUSH_AFTER);
321  }
322 
323  // Final Stream Callback
324  static size_t ogg_io_write(void *user_handle, void *buf, size_t n) {
325  LOGD("ogg_io_write: %d", (int)n);
326  OggContainerOutput *self = (OggContainerOutput *)user_handle;
327  if (self == nullptr) {
328  LOGE("self is null");
329  return 0;
330  }
331  // self->out.write((uint8_t *)buf, n);
332  writeSamples<uint8_t>(self->p_out, (uint8_t *)buf, n);
333  // 0 = continue
334  return 0;
335  }
336 };
337 
351  public:
352  // Empty Constructor - the output stream must be provided with begin()
353  OggContainerEncoder() = default;
354 
355  OggContainerEncoder(AudioEncoder *encoder) { setEncoder(encoder); }
356 
357  OggContainerEncoder(AudioEncoder &encoder) { setEncoder(&encoder); }
358 
360  void setOutput(Print &print) override { p_ogg->setOutput(print); }
361 
363  const char *mime() override { return mime_pcm; }
364 
366  virtual void setAudioInfo(AudioInfo info) override {
368  p_ogg->setAudioInfo(info);
369  if (p_codec != nullptr) p_codec->setAudioInfo(info);
370  }
371 
372  virtual bool begin(AudioInfo from) override {
373  setAudioInfo(from);
374  return begin();
375  }
376 
378  virtual bool begin() override {
379  TRACED();
380  p_ogg->begin();
381  if (p_codec==nullptr) return false;
382  p_codec->setOutput(*p_ogg);
383  return p_codec->begin(p_ogg->audioInfo());
384  }
385 
387  void end() override {
388  TRACED();
389  if (p_codec != nullptr) p_codec->end();
390  p_ogg->end();
391  }
392 
394  virtual size_t write(const uint8_t *data, size_t len) override {
395  if (!p_ogg->isOpen() || data == nullptr) return 0;
396  LOGD("OggContainerEncoder::write: %d", (int)len);
397  size_t result = 0;
398  if (p_codec == nullptr) {
399  result = p_ogg->write((const uint8_t *)data, len);
400  } else {
401  result = p_codec->write(data, len);
402  }
403  return result;
404  }
405 
406  operator bool() override { return p_ogg->isOpen(); }
407 
408  bool isOpen() { return p_ogg->isOpen(); }
409 
410  protected:
411  AudioEncoder *p_codec = nullptr;
412  OggContainerOutput ogg;
413  OggContainerOutput *p_ogg = &ogg;
414 
415  void setEncoder(AudioEncoder *enc) { p_codec = enc; }
416 
418  void setOggOutput(OggContainerOutput *out) { p_ogg = out; }
419 };
420 
421 } // namespace audio_tools
Docoding of encoded audio into PCM data.
Definition: AudioCodecsBase.h:16
virtual void setAudioInfo(AudioInfo from) override
for most decoders this is not needed
Definition: AudioCodecsBase.h:26
Encoding of PCM data.
Definition: AudioCodecsBase.h:84
void setAudioInfo(AudioInfo from) override
Defines the sample rate, number of channels and bits per sample.
Definition: AudioCodecsBase.h:93
virtual void addNotifyAudioChange(AudioInfoSupport &bi)
Adds target to be notified about audio changes.
Definition: AudioTypes.h:162
Supports changes to the sampling rate, bits and channels.
Definition: AudioTypes.h:139
Abstract Audio Ouptut class.
Definition: AudioOutput.h:22
virtual void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition: AudioOutput.h:46
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
Parent class for all container formats.
Definition: AudioCodecsBase.h:74
void addNotifyAudioChange(AudioInfoSupport &bi) override
Define object which need to be notified if the basinfo is changing.
Definition: AudioEncoded.h:74
void end() override
Ends the processing.
Definition: AudioEncoded.h:165
bool begin() override
Starts the processing - sets the status to active.
Definition: AudioEncoded.h:136
virtual void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition: AudioEncoded.h:87
void setOutput(Print &outputStream)
Defines/Changes the output target.
Definition: AudioEncoded.h:96
Decoder for Ogg Container. Decodes a packet from an Ogg container. The Ogg begin segment contains the...
Definition: ContainerOgg.h:26
OggContainerDecoder()
Construct a new OggContainerDecoder object.
Definition: ContainerOgg.h:32
void setOutput(Print &print) override
Defines the output Stream.
Definition: ContainerOgg.h:47
void addNotifyAudioChange(AudioInfoSupport &bi) override
Adds target to be notified about audio changes.
Definition: ContainerOgg.h:49
AudioInfo audioInfo() override
provides the actual input AudioInfo
Definition: ContainerOgg.h:54
Encoder for Ogg Container. Encodes a packet for an Ogg container. The Ogg begin segment contains the ...
Definition: ContainerOgg.h:350
virtual size_t write(const uint8_t *data, size_t len) override
Writes raw data to be encoded and packaged.
Definition: ContainerOgg.h:394
void setOutput(Print &print) override
Defines the output Stream.
Definition: ContainerOgg.h:360
virtual bool begin() override
starts the processing using the actual AudioInfo
Definition: ContainerOgg.h:378
const char * mime() override
Provides "audio/pcm".
Definition: ContainerOgg.h:363
void end() override
stops the processing
Definition: ContainerOgg.h:387
void setOggOutput(OggContainerOutput *out)
Replace the ogg output class.
Definition: ContainerOgg.h:418
virtual void setAudioInfo(AudioInfo info) override
We actually do nothing with this.
Definition: ContainerOgg.h:366
Output class for the OggContainerEncoder. Each write is ending up as container entry.
Definition: ContainerOgg.h:209
virtual size_t write(const uint8_t *data, size_t len) override
Writes raw data to be encoded and packaged.
Definition: ContainerOgg.h:250
virtual bool begin() override
starts the processing using the actual AudioInfo
Definition: ContainerOgg.h:218
void end() override
stops the processing
Definition: ContainerOgg.h:239
void setOutput(Print &print)
Defines the output Stream.
Definition: ContainerOgg.h:215
Definition: NoArduino.h:58
virtual int availableForWrite()
provides the number of entries that are available to write
Definition: Buffers.h:369
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AudioConfig.h:868
static const char * mime_pcm
Mime type for PCM.
Definition: AudioTypes.h:523
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