arduino-audio-tools
AudioServer.h
1 #pragma once
2 
3 #include "AudioConfig.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 "AudioCodecs/CodecWAV.h"
19 #include "AudioTools.h"
20 
21 namespace audio_tools {
22 
24 typedef void (*AudioServerDataCallback)(Print *out);
25 
35 template<class Client,class Server>
36 class AudioServerT {
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  copier.copy();
134  } else {
135  copier.copy(*converter_ptr);
136  }
137  // if we limit the size of the WAV the encoder gets automatically
138  // closed when all has been sent
139  if (!client_obj) {
140  LOGI("stop client...");
141  client_obj.stop();
142  active = false;
143  }
144  }
145  } else {
146  LOGI("client was not connected");
147  }
148  }
149  return active;
150  }
151 
153  void setConverter(BaseConverter *c) { converter_ptr = c; }
154 
156  Stream &out() { return client_obj; }
157 
159  Client *out_ptr() { return &client_obj; }
160 
162  bool isClientConnected() { return client_obj.connected(); }
163 
165  void setCopyBufferSize(int size){
166  copier.resize(size);
167  }
168 
169  protected:
170  // WIFI
171 #ifdef ESP32
172  Server server;
173 #else
174  Server server{80};
175 #endif
176  Client client_obj;
177  char *password = nullptr;
178  char *network = nullptr;
179 
180  // Content
181  const char *content_type = nullptr;
182  AudioServerDataCallback callback = nullptr;
183  Stream *in = nullptr;
184  StreamCopy copier;
185  BaseConverter *converter_ptr = nullptr;
186 
187  void setupServer(int port) {
188  Server tmp(port);
189  server = tmp;
190  }
191 
192 #ifdef USE_WIFI
193  void connectWiFi() {
194  TRACED();
195  if (WiFi.status() != WL_CONNECTED && network != nullptr &&
196  password != nullptr) {
197  WiFi.begin(network, password);
198  while (WiFi.status() != WL_CONNECTED) {
199  Serial.print(".");
200  delay(500);
201  }
202 #ifdef ESP32
203  WiFi.setSleep(false);
204 #endif
205  Serial.println();
206  }
207  Serial.print("IP address: ");
208  Serial.println(WiFi.localIP());
209  }
210 #endif
211 
212  virtual void sendReplyHeader() {
213  TRACED();
214  // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
215  // and a content-type so the client knows what's coming, then a blank line:
216  client_obj.println("HTTP/1.1 200 OK");
217  LOGI("Reply: HTTP/1.1 200 OK");
218  if (content_type != nullptr) {
219  client_obj.print("Content-type:");
220  client_obj.println(content_type);
221  LOGI("Content-type: %s", content_type);
222  }
223  client_obj.println();
224  if (!client_obj.connected()){
225  LOGE("connection was closed");
226  }
227  }
228 
229  virtual void sendReplyContent() {
230  TRACED();
231  if (callback != nullptr) {
232  // provide data via Callback
233  LOGI("sendReply - calling callback");
234  callback(&client_obj);
235  client_obj.stop();
236  } else if (in != nullptr) {
237  // provide data for stream
238  LOGI("sendReply - Returning audio stream...");
239  copier.begin(client_obj, *in);
240  if (!client_obj.connected()){
241  LOGE("connection was closed");
242  }
243  }
244  }
245 
246  // Handle an new client connection and return the data
247  void processClient() {
248  // LOGD("processClient");
249  if (client_obj) { // if you get a client,
250  LOGI("New Client:"); // print a message out the serial port
251  String currentLine =
252  ""; // make a String to hold incoming data from the client
253  while (client_obj.connected()) { // loop while the client's connected
254  if (client_obj
255  .available()) { // if there's bytes to read from the client,
256  char c = client_obj.read(); // read a byte, then
257  if (c == '\n') { // if the byte is a newline character
258  LOGI("Request: %s", currentLine.c_str());
259  // if the current line is blank, you got two newline characters in a
260  // row. that's the end of the client HTTP request, so send a
261  // response:
262  if (currentLine.length() == 0) {
263  sendReplyHeader();
264  sendReplyContent();
265  // break out of the while loop:
266  break;
267  } else { // if you got a newline, then clear currentLine:
268  currentLine = "";
269  }
270  } else if (c != '\r') { // if you got anything else but a carriage
271  // return character,
272  currentLine += c; // add it to the end of the currentLine
273  }
274  }
275  }
276  }
277  }
278 };
279 
280 #ifdef USE_WIFI
281 using AudioServer = AudioServerT<WiFiClient, WiFiServer>;
282 using AudioServerWiFi = AudioServerT<WiFiClient, WiFiServer>;
283 #endif
284 
285 #ifdef USE_ETHERNET
286 using AudioServer = AudioServerT<EthernetClient, EthernetServer>;
287 using AudioServerEthernet = AudioServerT<EthernetClient, EthernetServer>;
288 #endif
289 
299 class AudioEncoderServer : public AudioServer {
300  public:
305  AudioEncoderServer(AudioEncoder *encoder, int port = 80) : AudioServer(port) {
306  this->encoder = encoder;
307  }
308 
315  AudioEncoderServer(AudioEncoder *encoder, const char *network,
316  const char *password, int port = 80)
317  : AudioServer(network, password, port) {
318  this->encoder = encoder;
319  }
320 
324  ~AudioEncoderServer() {}
325 
334  bool begin(Stream &in, int sample_rate, int channels,
335  int bits_per_sample = 16, BaseConverter *converter = nullptr) {
336  TRACED();
337  this->in = &in;
338  setConverter(converter);
339  audio_info.sample_rate = sample_rate;
340  audio_info.channels = channels;
341  audio_info.bits_per_sample = bits_per_sample;
342  encoder->setAudioInfo(audio_info);
343  // encoded_stream.begin(&client_obj, encoder);
344  encoded_stream.setOutput(&client_obj);
345  encoded_stream.setEncoder(encoder);
346  encoded_stream.begin(audio_info);
347  return AudioServer::begin(in, encoder->mime());
348  }
349 
358  bool begin(Stream &in, AudioInfo info, BaseConverter *converter = nullptr) {
359  TRACED();
360  this->in = &in;
361  this->audio_info = info;
362  setConverter(converter);
363  encoder->setAudioInfo(audio_info);
364  encoded_stream.setOutput(&client_obj);
365  encoded_stream.setEncoder(encoder);
366  if (!encoded_stream.begin(audio_info)){
367  LOGE("encoder begin failed");
368  stop();
369  }
370 
371  return AudioServer::begin(in, encoder->mime());
372  }
373 
381  bool begin(AudioStream &in, BaseConverter *converter = nullptr) {
382  TRACED();
383  this->in = &in;
384  this->audio_info = in.audioInfo();
385  setConverter(converter);
386  encoder->setAudioInfo(audio_info);
387  encoded_stream.setOutput(&client_obj);
388  encoded_stream.setEncoder(encoder);
389  encoded_stream.begin(audio_info);
390 
391  return AudioServer::begin(in, encoder->mime());
392  }
393 
401  bool begin(AudioServerDataCallback cb, int sample_rate, int channels,
402  int bits_per_sample = 16) {
403  TRACED();
404  audio_info.sample_rate = sample_rate;
405  audio_info.channels = channels;
406  audio_info.bits_per_sample = bits_per_sample;
407  encoder->setAudioInfo(audio_info);
408 
409  return AudioServer::begin(cb, encoder->mime());
410  }
411 
412  // provides a pointer to the encoder
413  AudioEncoder *audioEncoder() { return encoder; }
414 
415  protected:
416  // Sound Generation - use EncodedAudioOutput with is more efficient then EncodedAudioStream
417  EncodedAudioOutput encoded_stream;
418  AudioInfo audio_info;
419  AudioEncoder *encoder = nullptr;
420 
421  // moved to be part of reply content to avoid timeout issues in Chrome
422  void sendReplyHeader() override {}
423 
424  void sendReplyContent() override {
425  TRACED();
426  // restart encoder
427  if (encoder) {
428  encoder->end();
429  encoder->begin();
430  }
431 
432  if (callback != nullptr) {
433  // encoded_stream.begin(out_ptr(), encoder);
434  encoded_stream.setOutput(out_ptr());
435  encoded_stream.setEncoder(encoder);
436  encoded_stream.begin();
437 
438  // provide data via Callback to encoded_stream
439  LOGI("sendReply - calling callback");
440  // Send delayed header
441  AudioServer::sendReplyHeader();
442  callback(&encoded_stream);
443  client_obj.stop();
444  } else if (in != nullptr) {
445  // provide data for stream: in -copy> encoded_stream -> out
446  LOGI("sendReply - Returning encoded stream...");
447  // encoded_stream.begin(out_ptr(), encoder);
448  encoded_stream.setOutput(out_ptr());
449  encoded_stream.setEncoder(encoder);
450  encoded_stream.begin();
451 
452  copier.begin(encoded_stream, *in);
453  if (!client_obj.connected()){
454  LOGE("connection was closed");
455  }
456  // Send delayed header
457  AudioServer::sendReplyHeader();
458  }
459  }
460 };
461 
470 class AudioWAVServer : public AudioEncoderServer {
471  public:
476  AudioWAVServer(int port = 80) : AudioEncoderServer(new WAVEncoder(), port) {}
477 
484  AudioWAVServer(const char *network, const char *password, int port = 80)
485  : AudioEncoderServer(new WAVEncoder(), network, password, port) {}
486 
488  ~AudioWAVServer() {
489  AudioEncoder *encoder = audioEncoder();
490  if (encoder != nullptr) {
491  delete encoder;
492  }
493  }
494 
495  // provides a pointer to the encoder
496  WAVEncoder &wavEncoder() { return *static_cast<WAVEncoder *>(encoder); }
497 };
498 
499 } // namespace audio_tools
500 
501 #endif
void stop()
Public generic methods.
Definition: AudioRuntime.h:27
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AnalogAudioArduino.h:12