arduino-audio-tools
Loading...
Searching...
No Matches
AudioServer.h
1#pragma once
2
3#include "AudioToolsConfig.h"
4#if defined(USE_AUDIO_SERVER) && (defined(USE_ETHERNET) || defined(USE_WIFI))
5
6#ifdef USE_WIFI
7#ifdef ESP8266
8#include <ESP8266WiFi.h>
9#else
10#include <WiFi.h>
11#endif
12#endif
13
14#ifdef USE_ETHERNET
15#include <Ethernet.h>
16#endif
17
18#include "AudioTools.h"
19#include "AudioTools/AudioCodecs/CodecWAV.h"
20
21namespace audio_tools {
22
24typedef void (*AudioServerDataCallback)(Print *out);
25
35template <class Client, class Server>
37 public:
42 AudioServerT(int port = 80) {
43 // the client returns 0 for avialableForWrite()
44 copier.setCheckAvailableForWrite(false);
45 setupServer(port);
46 }
47
54 AudioServerT(const char *network, const char *password, int port = 80) {
55 this->network = (char *)network;
56 this->password = (char *)password;
57 // the client returns 0 for avialableForWrite()
58 copier.setCheckAvailableForWrite(false);
59 setupServer(port);
60 }
61
69 bool begin(Stream &in, const char *contentType) {
70 TRACED();
71 this->in = &in;
72 this->content_type = contentType;
73
74#ifdef USE_WIFI
75 connectWiFi();
76#endif
77 // start server
78 server.begin();
79 return true;
80 }
81
88 bool begin(AudioServerDataCallback cb, const char *contentType) {
89 TRACED();
90 this->in = nullptr;
91 this->callback = cb;
92 this->content_type = contentType;
93
94#ifdef USE_WIFI
95 connectWiFi();
96#endif
97
98 // start server
99 server.begin();
100 return true;
101 }
102
111 bool copy() { return doLoop(); }
112
117 bool doLoop() {
118 // LOGD("doLoop");
119 bool active = true;
120 if (!client_obj.connected()) {
121#if USE_SERVER_ACCEPT
122 client_obj = server.accept(); // listen for incoming clients
123#else
124 client_obj = server.available(); // listen for incoming clients
125#endif
126 processClient();
127 } else {
128 // We are connected: copy input from source to wav output
129 if (client_obj) {
130 if (callback == nullptr) {
131 LOGD("copy data...");
132 if (converter_ptr == nullptr) {
133 sent += copier.copy();
134 } else {
135 sent += copier.copy(*converter_ptr);
136 }
137
138 if (max_bytes > 0 && sent >= max_bytes) {
139 LOGI("range exhausted...");
140 client_obj.stop();
141 active = false;
142 }
143
144 // if we limit the size of the WAV the encoder gets automatically
145 // closed when all has been sent
146 if (!client_obj) {
147 LOGI("stop client...");
148 client_obj.stop();
149 active = false;
150 }
151 }
152 } else {
153 LOGI("client was not connected");
154 }
155 }
156 return active;
157 }
158
160 void setConverter(BaseConverter *c) { converter_ptr = c; }
161
163 Stream &out() { return client_obj; }
164
166 Client *out_ptr() { return &client_obj; }
167
169 bool isClientConnected() { return client_obj.connected(); }
170
172 void setCopyBufferSize(int size) { copier.resize(size); }
173
174 protected:
175 // WIFI
176#ifdef ESP32
177 Server server;
178#else
179 Server server{80};
180#endif
181 Client client_obj;
182 char *password = nullptr;
183 char *network = nullptr;
184 size_t max_bytes = 0;
185 size_t sent = 0;
186
187 // Content
188 const char *content_type = nullptr;
189 AudioServerDataCallback callback = nullptr;
190 Stream *in = nullptr;
191 StreamCopy copier;
192 BaseConverter *converter_ptr = nullptr;
193
194 void setupServer(int port) {
195 Server tmp(port);
196 server = tmp;
197 }
198
199#ifdef USE_WIFI
200 void connectWiFi() {
201 TRACED();
202 if (WiFi.status() != WL_CONNECTED && network != nullptr &&
203 password != nullptr) {
204 WiFi.begin(network, password);
205 while (WiFi.status() != WL_CONNECTED) {
206 Serial.print(".");
207 delay(500);
208 }
209#ifdef ESP32
210 WiFi.setSleep(false);
211#endif
212 Serial.println();
213 }
214 Serial.print("IP address: ");
215 Serial.println(WiFi.localIP());
216 }
217#endif
218
219 virtual void sendReplyHeader() {
220 TRACED();
221
222 // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
223 // and a content-type so the client knows what's coming, then a blank line:
224 const char *response;
225 if (max_bytes > 0) {
226 response = "HTTP/1.1 206 OK";
227 } else {
228 response = "HTTP/1.1 200 OK";
229 }
230 client_obj.println(response);
231 LOGI("%s", response);
232 if (content_type != nullptr) {
233 client_obj.print("Content-type:");
234 client_obj.println(content_type);
235 LOGI("Content-type: %s", content_type);
236 }
237 client_obj.println();
238 if (!client_obj.connected()) {
239 LOGE("connection was closed");
240 }
241 }
242
243 virtual void sendReplyContent() {
244 TRACED();
245 if (callback != nullptr) {
246 // provide data via Callback
247 LOGI("sendReply - calling callback");
248 callback(&client_obj);
249 client_obj.stop();
250 } else if (in != nullptr) {
251 // provide data for stream
252 LOGI("sendReply - Returning audio stream...");
253 copier.begin(client_obj, *in);
254 if (!client_obj.connected()) {
255 LOGE("connection was closed");
256 }
257 }
258 }
259
260 // Handle an new client connection and return the data
261 void processClient() {
262 // LOGD("processClient");
263 if (client_obj) { // if you get a client,
264 LOGI("New Client:"); // print a message out the serial port
265 String currentLine =
266 ""; // make a String to hold incoming data from the client
267 long firstbyte = 0;
268 long lastbyte = 0;
269 while (client_obj.connected()) { // loop while the client's connected
270 if (client_obj
271 .available()) { // if there's bytes to read from the client,
272 char c = client_obj.read(); // read a byte, then
273 if (c == '\n') { // if the byte is a newline character
274 LOGI("Request: %s", currentLine.c_str());
275 if (currentLine.startsWith(String("Range: bytes="))) {
276 int minuspos = currentLine.indexOf('-', 13);
277
278 // toInt returns 0 if it's an invalid conversion, so it's "safe"
279 firstbyte = currentLine.substring(13, minuspos).toInt();
280 lastbyte = currentLine.substring(minuspos + 1).toInt();
281 }
282 // if the current line is blank, you got two newline characters in a
283 // row. that's the end of the client HTTP request, so send a
284 // response:
285 if (currentLine.length() == 0) {
286 sendReplyHeader();
287 sendReplyContent();
288 max_bytes = lastbyte - firstbyte;
289 sent = 0;
290 // break out of the while loop:
291 break;
292 } else { // if you got a newline, then clear currentLine:
293 currentLine = "";
294 }
295 } else if (c != '\r') { // if you got anything else but a carriage
296 // return character,
297 currentLine += c; // add it to the end of the currentLine
298 }
299 }
300 }
301 }
302 }
303};
304
305#ifdef USE_WIFI
306using AudioServer = AudioServerT<WiFiClient, WiFiServer>;
307using AudioServerWiFi = AudioServerT<WiFiClient, WiFiServer>;
308#endif
309
310#ifdef USE_ETHERNET
311using AudioServer = AudioServerT<EthernetClient, EthernetServer>;
312using AudioServerEthernet = AudioServerT<EthernetClient, EthernetServer>;
313#endif
314
325 public:
330 AudioEncoderServer(AudioEncoder *encoder, int port = 80) : AudioServer(port) {
331 this->encoder = encoder;
332 }
333
340 AudioEncoderServer(AudioEncoder *encoder, const char *network,
341 const char *password, int port = 80)
342 : AudioServer(network, password, port) {
343 this->encoder = encoder;
344 }
345
350
359 bool begin(Stream &in, int sample_rate, int channels,
360 int bits_per_sample = 16, BaseConverter *converter = nullptr) {
361 TRACED();
362 this->in = &in;
363 setConverter(converter);
364 audio_info.sample_rate = sample_rate;
365 audio_info.channels = channels;
366 audio_info.bits_per_sample = bits_per_sample;
367 encoder->setAudioInfo(audio_info);
368 // encoded_stream.begin(&client_obj, encoder);
369 encoded_stream.setOutput(&client_obj);
370 encoded_stream.setEncoder(encoder);
371 encoded_stream.begin(audio_info);
372 return AudioServer::begin(in, encoder->mime());
373 }
374
383 bool begin(Stream &in, AudioInfo info, BaseConverter *converter = nullptr) {
384 TRACED();
385 this->in = &in;
386 this->audio_info = info;
387 setConverter(converter);
388 encoder->setAudioInfo(audio_info);
389 encoded_stream.setOutput(&client_obj);
390 encoded_stream.setEncoder(encoder);
391 if (!encoded_stream.begin(audio_info)) {
392 LOGE("encoder begin failed");
393 stop();
394 }
395
396 return AudioServer::begin(in, encoder->mime());
397 }
398
406 bool begin(AudioStream &in, BaseConverter *converter = nullptr) {
407 TRACED();
408 this->in = &in;
409 this->audio_info = in.audioInfo();
410 setConverter(converter);
411 encoder->setAudioInfo(audio_info);
412 encoded_stream.setOutput(&client_obj);
413 encoded_stream.setEncoder(encoder);
414 encoded_stream.begin(audio_info);
415
416 return AudioServer::begin(in, encoder->mime());
417 }
418
426 bool begin(AudioServerDataCallback cb, int sample_rate, int channels,
427 int bits_per_sample = 16) {
428 TRACED();
429 audio_info.sample_rate = sample_rate;
430 audio_info.channels = channels;
431 audio_info.bits_per_sample = bits_per_sample;
432 encoder->setAudioInfo(audio_info);
433
434 return AudioServer::begin(cb, encoder->mime());
435 }
436
437 // provides a pointer to the encoder
438 AudioEncoder *audioEncoder() { return encoder; }
439
440 protected:
441 // Sound Generation - use EncodedAudioOutput with is more efficient then
442 // EncodedAudioStream
443 EncodedAudioOutput encoded_stream;
444 AudioInfo audio_info;
445 AudioEncoder *encoder = nullptr;
446
447 // moved to be part of reply content to avoid timeout issues in Chrome
448 void sendReplyHeader() override {}
449
450 void sendReplyContent() override {
451 TRACED();
452 // restart encoder
453 if (encoder) {
454 encoder->end();
455 encoder->begin();
456 }
457
458 if (callback != nullptr) {
459 // encoded_stream.begin(out_ptr(), encoder);
460 encoded_stream.setOutput(out_ptr());
461 encoded_stream.setEncoder(encoder);
462 encoded_stream.begin();
463
464 // provide data via Callback to encoded_stream
465 LOGI("sendReply - calling callback");
466 // Send delayed header
467 AudioServer::sendReplyHeader();
468 callback(&encoded_stream);
469 client_obj.stop();
470 } else if (in != nullptr) {
471 // provide data for stream: in -copy> encoded_stream -> out
472 LOGI("sendReply - Returning encoded stream...");
473 // encoded_stream.begin(out_ptr(), encoder);
474 encoded_stream.setOutput(out_ptr());
475 encoded_stream.setEncoder(encoder);
476 encoded_stream.begin();
477
478 copier.begin(encoded_stream, *in);
479 if (!client_obj.connected()) {
480 LOGE("connection was closed");
481 }
482 // Send delayed header
483 AudioServer::sendReplyHeader();
484 }
485 }
486};
487
497 public:
502 AudioWAVServer(int port = 80) : AudioEncoderServer(new WAVEncoder(), port) {}
503
510 AudioWAVServer(const char *network, const char *password, int port = 80)
511 : AudioEncoderServer(new WAVEncoder(), network, password, port) {}
512
515 AudioEncoder *encoder = audioEncoder();
516 if (encoder != nullptr) {
517 delete encoder;
518 }
519 }
520
521 // provides a pointer to the encoder
522 WAVEncoder &wavEncoder() { return *static_cast<WAVEncoder *>(encoder); }
523};
524
525} // namespace audio_tools
526
527#endif
Encoding of PCM data.
Definition AudioCodecsBase.h:96
void setAudioInfo(AudioInfo from) override
Defines the sample rate, number of channels and bits per sample.
Definition AudioCodecsBase.h:105
virtual const char * mime()=0
Provides the mime type of the encoded result.
A simple Arduino Webserver which streams the audio using the indicated encoder.. This class is based ...
Definition AudioServer.h:324
~AudioEncoderServer()
Destructor release the memory.
Definition AudioServer.h:349
AudioEncoderServer(AudioEncoder *encoder, const char *network, const char *password, int port=80)
Construct a new Audio Server object.
Definition AudioServer.h:340
bool begin(Stream &in, AudioInfo info, BaseConverter *converter=nullptr)
Start the server. You need to be connected to WiFI before calling this method.
Definition AudioServer.h:383
bool begin(AudioServerDataCallback cb, int sample_rate, int channels, int bits_per_sample=16)
Start the server. The data must be provided by a callback method.
Definition AudioServer.h:426
bool begin(Stream &in, int sample_rate, int channels, int bits_per_sample=16, BaseConverter *converter=nullptr)
Start the server. You need to be connected to WiFI before calling this method.
Definition AudioServer.h:359
bool begin(AudioStream &in, BaseConverter *converter=nullptr)
Start the server. You need to be connected to WiFI before calling this method.
Definition AudioServer.h:406
AudioEncoderServer(AudioEncoder *encoder, int port=80)
Construct a new Audio Server object that supports an AudioEncoder We assume that the WiFi is already ...
Definition AudioServer.h:330
A simple Arduino Webserver which streams the result This class is based on the WiFiServer class....
Definition AudioServer.h:36
void setConverter(BaseConverter *c)
defines a converter that will be used when the audio is rendered
Definition AudioServer.h:160
bool isClientConnected()
Checks if any clinent has connnected.
Definition AudioServer.h:169
bool begin(AudioServerDataCallback cb, const char *contentType)
Start the server. The data must be provided by a callback method.
Definition AudioServer.h:88
bool copy()
Add this method to your loop Returns true while the client is connected. (The same functionality like...
Definition AudioServer.h:111
Client * out_ptr()
Provides a pointer to the WiFiClient.
Definition AudioServer.h:166
AudioServerT(const char *network, const char *password, int port=80)
Construct a new Audio WAV Server object.
Definition AudioServer.h:54
AudioServerT(int port=80)
Construct a new Audio Server object We assume that the WiFi is already connected.
Definition AudioServer.h:42
void setCopyBufferSize(int size)
Changes the copy buffer size.
Definition AudioServer.h:172
bool doLoop()
Add this method to your loop Returns true while the client is connected.
Definition AudioServer.h:117
bool begin(Stream &in, const char *contentType)
Start the server. You need to be connected to WiFI before calling this method.
Definition AudioServer.h:69
Stream & out()
Provides the output stream.
Definition AudioServer.h:163
Base class for all Audio Streams. It support the boolean operator to test if the object is ready with...
Definition BaseStream.h:122
virtual AudioInfo audioInfo() override
provides the actual input AudioInfo
Definition BaseStream.h:153
A simple Arduino Webserver which streams the audio as WAV data. This class is based on the AudioEncod...
Definition AudioServer.h:496
AudioWAVServer(const char *network, const char *password, int port=80)
Construct a new Audio WAV Server object.
Definition AudioServer.h:510
~AudioWAVServer()
Destructor: release the allocated encoder.
Definition AudioServer.h:514
AudioWAVServer(int port=80)
Construct a new Audio WAV Server object We assume that the WiFi is already connected.
Definition AudioServer.h:502
Abstract Base class for Converters A converter is processing the data in the indicated array.
Definition BaseConverter.h:24
Definition NoArduino.h:169
bool begin() override
Starts the processing - sets the status to active.
Definition AudioEncoded.h:149
void setOutput(Print *outputStream)
Defines the output.
Definition AudioEncoded.h:98
Definition NoArduino.h:62
void resize(int len)
resizes the copy buffer
Definition StreamCopy.h:308
size_t copy()
copies the data from the source to the destination and returns the processed number of bytes
Definition StreamCopy.h:95
void setCheckAvailableForWrite(bool flag)
Activates the check that we copy only if available for write returns a value.
Definition StreamCopy.h:288
Definition NoArduino.h:142
A simple WAV file encoder. If no AudioEncoderExt is specified the WAV file contains PCM data,...
Definition CodecWAV.h:469
void stop()
Public generic methods.
Definition AudioRuntime.h:18
StreamCopyT< uint8_t > StreamCopy
We provide the typeless StreamCopy.
Definition StreamCopy.h:433
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
void(* AudioServerDataCallback)(Print *out)
Calback which writes the sound data to the stream.
Definition AudioServer.h:24
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:53
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