arduino-audio-tools
ContainerOgg.h
1 #pragma once
2 
3 #include "AudioCodecs/AudioEncoded.h"
4 #include "AudioCodecs/CodecOpus.h"
5 #include "AudioTools/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 void *in_ptr, size_t in_size) override {
104  LOGD("write: %d", (int)in_size);
105 
106  // fill buffer
107  size_t size_consumed = buffer.writeArray((uint8_t *)in_ptr, in_size);
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 < in_size) {
114  size_consumed += buffer.writeArray((uint8_t *)in_ptr + size_consumed,
115  in_size - 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 *in_ptr, size_t in_size) override {
251  if (in_ptr == nullptr) return 0;
252  LOGD("OggContainerOutput::write: %d", (int)in_size);
253  assert(cfg.channels != 0);
254 
255  // encode the data
256  uint8_t *data = (uint8_t *)in_ptr;
257  op.packet = (uint8_t *)data;
258  op.bytes = in_size;
259  if (op.bytes > 0) {
260  int bytes_per_sample = cfg.bits_per_sample / 8;
261  granulepos += op.bytes / bytes_per_sample; // sample
262  op.granulepos = granulepos;
263  op.b_o_s = false;
264  op.e_o_s = false;
265  op.packetno = packetno++;
266  is_audio = true;
267  if (!writePacket(op, OGGZ_FLUSH_AFTER)) {
268  return 0;
269  }
270  }
271  // trigger pysical write
272  while ((oggz_write(p_oggz, in_size)) > 0)
273  ;
274 
275  return in_size;
276  }
277  bool isOpen() { return is_open; }
278 
279  protected:
280  Print *p_out = nullptr;
281  bool is_open = false;
282  OGGZ *p_oggz = nullptr;
283  ogg_packet op;
284  ogg_packet oh;
285  size_t granulepos = 0;
286  size_t packetno = 0;
287  long serialno = -1;
288  bool is_audio = false;
289 
290  virtual bool writePacket(ogg_packet &op, int flag = 0) {
291  LOGD("writePacket: %d", (int)op.bytes);
292  long result = oggz_write_feed(p_oggz, &op, serialno, flag, NULL);
293  if (result < 0 && result != OGGZ_ERR_OUT_OF_MEMORY) {
294  LOGE("oggz_write_feed: %d", (int)result);
295  return false;
296  }
297  return true;
298  }
299 
300  virtual bool writeHeader() {
301  TRACED();
302  oh.packet = (uint8_t *)&cfg;
303  oh.bytes = sizeof(AudioInfo);
304  oh.granulepos = 0;
305  oh.packetno = packetno++;
306  oh.b_o_s = true;
307  oh.e_o_s = false;
308  is_audio = false;
309  return writePacket(oh);
310  }
311 
312  virtual bool writeFooter() {
313  TRACED();
314  op.packet = (uint8_t *)nullptr;
315  op.bytes = 0;
316  op.granulepos = granulepos;
317  op.packetno = packetno++;
318  op.b_o_s = false;
319  op.e_o_s = true;
320  is_audio = false;
321  return writePacket(op, OGGZ_FLUSH_AFTER);
322  }
323 
324  // Final Stream Callback
325  static size_t ogg_io_write(void *user_handle, void *buf, size_t n) {
326  LOGD("ogg_io_write: %d", (int)n);
327  OggContainerOutput *self = (OggContainerOutput *)user_handle;
328  if (self == nullptr) {
329  LOGE("self is null");
330  return 0;
331  }
332  // self->out.write((uint8_t *)buf, n);
333  writeSamples<uint8_t>(self->p_out, (uint8_t *)buf, n);
334  // 0 = continue
335  return 0;
336  }
337 };
338 
352  public:
353  // Empty Constructor - the output stream must be provided with begin()
354  OggContainerEncoder() = default;
355 
356  OggContainerEncoder(AudioEncoder *encoder) { setEncoder(encoder); }
357 
358  OggContainerEncoder(AudioEncoder &encoder) { setEncoder(&encoder); }
359 
361  void setOutput(Print &print) override { p_ogg->setOutput(print); }
362 
364  const char *mime() override { return mime_pcm; }
365 
367  virtual void setAudioInfo(AudioInfo info) override {
369  p_ogg->setAudioInfo(info);
370  if (p_codec != nullptr) p_codec->setAudioInfo(info);
371  }
372 
373  virtual bool begin(AudioInfo from) override {
374  setAudioInfo(from);
375  return begin();
376  }
377 
379  virtual bool begin() override {
380  TRACED();
381  p_ogg->begin();
382  if (p_codec==nullptr) return false;
383  p_codec->setOutput(*p_ogg);
384  return p_codec->begin(p_ogg->audioInfo());
385  }
386 
388  void end() override {
389  TRACED();
390  if (p_codec != nullptr) p_codec->end();
391  p_ogg->end();
392  }
393 
395  virtual size_t write(const void *in_ptr, size_t in_size) override {
396  if (!p_ogg->isOpen() || in_ptr == nullptr) return 0;
397  LOGD("OggContainerEncoder::write: %d", (int)in_size);
398  size_t result = 0;
399  if (p_codec == nullptr) {
400  result = p_ogg->write((const uint8_t *)in_ptr, in_size);
401  } else {
402  result = p_codec->write(in_ptr, in_size);
403  }
404  return result;
405  }
406 
407  operator bool() override { return p_ogg->isOpen(); }
408 
409  bool isOpen() { return p_ogg->isOpen(); }
410 
411  protected:
412  AudioEncoder *p_codec = nullptr;
413  OggContainerOutput ogg;
414  OggContainerOutput *p_ogg = &ogg;
415 
416  void setEncoder(AudioEncoder *enc) { p_codec = enc; }
417 
419  void setOggOutput(OggContainerOutput *out) { p_ogg = out; }
420 };
421 
422 } // namespace audio_tools
Docoding of encoded audio into PCM data.
Definition: AudioEncoded.h:18
virtual void setAudioInfo(AudioInfo from) override
for most decoders this is not needed
Definition: AudioEncoded.h:28
Encoding of PCM data.
Definition: AudioEncoded.h:88
void setAudioInfo(AudioInfo from) override
Defines the sample rate, number of channels and bits per sample.
Definition: AudioEncoded.h:97
virtual void addNotifyAudioChange(AudioInfoSupport &bi)
Adds target to be notified about audio changes.
Definition: AudioTypes.h:158
Supports changes to the sampling rate, bits and channels.
Definition: AudioTypes.h:136
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: AudioEncoded.h:78
void addNotifyAudioChange(AudioInfoSupport &bi) override
Define object which need to be notified if the basinfo is changing.
Definition: AudioEncoded.h:320
void end() override
Ends the processing.
Definition: AudioEncoded.h:410
bool begin() override
Starts the processing - sets the status to active.
Definition: AudioEncoded.h:385
virtual void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition: AudioEncoded.h:336
void setOutput(Print &outputStream)
Defines/Changes the output target.
Definition: AudioEncoded.h:345
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:351
void setOutput(Print &print) override
Defines the output Stream.
Definition: ContainerOgg.h:361
virtual size_t write(const void *in_ptr, size_t in_size) override
Writes raw data to be encoded and packaged.
Definition: ContainerOgg.h:395
virtual bool begin() override
starts the processing using the actual AudioInfo
Definition: ContainerOgg.h:379
const char * mime() override
Provides "audio/pcm".
Definition: ContainerOgg.h:364
void end() override
stops the processing
Definition: ContainerOgg.h:388
void setOggOutput(OggContainerOutput *out)
Replace the ogg output class.
Definition: ContainerOgg.h:419
virtual void setAudioInfo(AudioInfo info) override
We actually do nothing with this.
Definition: ContainerOgg.h:367
Output class for the OggContainerEncoder. Each write is ending up as container entry.
Definition: ContainerOgg.h:209
virtual bool begin() override
starts the processing using the actual AudioInfo
Definition: ContainerOgg.h:218
virtual size_t write(const uint8_t *in_ptr, size_t in_size) override
Writes raw data to be encoded and packaged.
Definition: ContainerOgg.h:250
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:365
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AnalogAudio.h:10
static const char * mime_pcm
Mime type for PCM.
Definition: AudioTypes.h:488
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