1+ /* *
2+ * @file ContainerOSC.h
3+ * @author Phil Schatzmann
4+ * @brief A simple container format which uses OSC messages to
5+ * tramsmit Header records with audio info and Audio records with the audio
6+ * data.
7+ *
8+ * @version 0.1
9+ * @date 2025-05-20
10+ *
11+ * @copyright Copyright (c) 2022
12+ *
13+ */
14+ #pragma once
15+ #include < string.h>
16+
17+ #include " AudioTools/AudioCodecs/AudioCodecsBase.h"
18+ #include " AudioTools/AudioCodecs/MultiDecoder.h"
19+ #include " AudioTools/Communication/OSCData.h"
20+ #include " AudioTools/CoreAudio/AudioBasic/StrView.h"
21+
22+ namespace audio_tools {
23+
24+ /* *
25+ * @brief Wraps the encoded data into OSC info and daata segments so that we
26+ * can recover the audio configuration and orignial segments. We assume that a
27+ * full segment is written with each call of write();
28+ * @ingroup codecs
29+ * @ingroup encoder
30+ * @author Phil Schatzmann
31+ * @copyright GPLv3
32+ */
33+ class OSCContainerEncoder : public AudioEncoder {
34+ public:
35+ OSCContainerEncoder () = default ;
36+ OSCContainerEncoder (AudioEncoder &encoder) { p_codec = &encoder; }
37+
38+ void setEncoder (AudioEncoder *encoder) { p_codec = encoder; }
39+
40+ void setOutput (Print &outStream) {
41+ LOGD (" OSCContainerEncoder::setOutput" );
42+ p_out = &outStream;
43+ }
44+
45+ bool begin () override {
46+ TRACED ();
47+ // target.begin();
48+ bool rc = p_codec->begin ();
49+ p_codec->setAudioInfo (audioInfo ());
50+ is_beginning = true ;
51+ writeAudioInfo ();
52+ return rc;
53+ }
54+
55+ void setAudioInfo (AudioInfo info) override {
56+ TRACED ();
57+ writeAudioInfo ();
58+ AudioWriter::setAudioInfo (info);
59+ }
60+
61+ // / Add data segment. On first write we also add a AudioInfo header
62+ size_t write (const uint8_t *data, size_t len) {
63+ LOGD (" OSCContainerEncoder::write: %d" , (int )len);
64+ if (packet_count % repeat_info == 0 ) {
65+ writeAudioInfo ();
66+ }
67+ writeAudio (data, len);
68+ return len;
69+ }
70+
71+ void end () { p_codec->end (); }
72+
73+ operator bool () { return true ; };
74+
75+ virtual const char *mime () { return " audio/OSC" ; };
76+
77+ void setRepeatInfoEvery (int packet_count) {
78+ this ->repeat_info = packet_count;
79+ }
80+
81+ protected:
82+ uint64_t packet_count = 0 ;
83+ int repeat_info = 0 ;
84+ bool is_beginning = true ;
85+ AudioEncoder *p_codec = nullptr ;
86+ Print *p_out = nullptr ;
87+
88+ void writeAudio (const uint8_t *data, size_t len) {
89+ LOGD (" writeAudio: %d" , (int )len);
90+ uint8_t osc_data[len + 20 ]; // 20 is guess to cover address & fmt
91+ OSCData osc{osc_data, sizeof (osc_data)};
92+ osc.setAddress (" /audio/data" );
93+ osc.setFormat (" b" );
94+ osc.write (data, len);
95+ p_out->write (osc_data, osc.size ());
96+ }
97+
98+ void writeAudioInfo () {
99+ LOGD (" writeAudioInfo" );
100+ uint8_t osc_data[100 ];
101+ OSCData osc{osc_data, sizeof (osc_data)};
102+ osc.setAddress (" /audio/info" );
103+ osc.setFormat (" iiis" );
104+ osc.write ((int32_t )audioInfo ().sample_rate );
105+ osc.write ((int32_t )audioInfo ().channels );
106+ osc.write ((int32_t )audioInfo ().bits_per_sample );
107+ osc.write (p_codec->mime ());
108+ p_out->write (osc_data, osc.size ());
109+ }
110+ };
111+
112+ /* *
113+ * @brief Decodes the provided data from the OSC segments. I recommend to assign
114+ * a MultiDecoder so that we can support muiltiple audio types.
115+ * @ingroup codecs
116+ * @ingroup decoder
117+ * @author Phil Schatzmann
118+ * @copyright GPLv3
119+ */
120+ class OSCContainerDecoder : public ContainerDecoder {
121+ public:
122+ OSCContainerDecoder () = default ;
123+ OSCContainerDecoder (AudioDecoder &decoder) { setDecoder (decoder); }
124+ OSCContainerDecoder (MultiDecoder &decoder) { setDecoder (decoder); }
125+
126+ void setDecoder (AudioDecoder &decoder) { p_codec = &decoder; }
127+
128+ void setDecoder (MultiDecoder &decoder) {
129+ p_codec = &decoder;
130+ p_multi_decoder = &decoder;
131+ }
132+
133+ void setOutput (Print &outStream) {
134+ LOGD (" OSCContainerDecoder::setOutput" )
135+ p_out = &outStream;
136+ }
137+
138+ bool begin () {
139+ TRACED ();
140+ is_first = true ;
141+ osc.setReference (this );
142+ osc.addCallback (" /audio/info" , parseInfo, OSCCompare::StartsWith);
143+ osc.addCallback (" /audio/data" , parseData, OSCCompare::StartsWith);
144+ return true ;
145+ }
146+
147+ void end () { TRACED (); }
148+
149+ size_t write (const uint8_t *data, size_t len) {
150+ LOGD (" write: %d" , (int )len);
151+ if (!osc.parse ((uint8_t *)data, len)) {
152+ return 0 ;
153+ }
154+ return len;
155+ }
156+
157+ operator bool () { return true ; };
158+
159+ // / Provides the mime type from the encoder
160+ const char *mime () { return mime_str.c_str (); };
161+
162+ protected:
163+ bool is_first = true ;
164+ AudioDecoder *p_codec = nullptr ;
165+ MultiDecoder *p_multi_decoder = nullptr ;
166+ SingleBuffer<uint8_t > buffer{0 };
167+ Print *p_out = nullptr ;
168+ OSCData osc;
169+ Str mime_str;
170+
171+ static bool parseData (OSCData &osc, void *ref) {
172+ OSCBinaryData data = osc.readData ();
173+ OSCContainerDecoder *decoder = static_cast <OSCContainerDecoder *>(ref);
174+ if (decoder->p_codec != nullptr ) {
175+ decoder->p_codec ->write (data.data , data.len );
176+ }
177+ return true ;
178+ }
179+
180+ static bool parseInfo (OSCData &osc, void *ref) {
181+ AudioInfo info;
182+ info.sample_rate = osc.readInt32 ();
183+ info.channels = osc.readInt32 ();
184+ info.bits_per_sample = osc.readInt32 ();
185+ const char *mime = osc.readString ();
186+
187+ OSCContainerDecoder *decoder = static_cast <OSCContainerDecoder *>(ref);
188+ if (decoder != nullptr ) {
189+ decoder->setAudioInfo (info);
190+ decoder->mime_str = mime;
191+ // select the right decoder based on the mime type
192+ if (decoder->p_multi_decoder )
193+ decoder->p_multi_decoder ->selectDecoder (mime);
194+ }
195+
196+ return true ;
197+ }
198+ };
199+
200+ } // namespace audio_tools
0 commit comments