Im my last blog I was describing the DLNA Architecture. So we are ready now to dig into my first __Arduino DLNA Sketch__.
By combining my arduino-dlna with the arduino-audio-tools library it is quite easy to build a Network Audio Renderer that can e.g. be controlled via an Android Application: I can recommend Hi-Fi-Cast that let’s you select the Music Sources and Playback Devices.
When running this sketch you should find and entry named __ArduinoMediaRenderer__ under the playback devices in your mobile application.
Here is the logic: First we include all relevant classes from the DLNA and AudioTools:
#include "WiFi.h"
#include "DLNA.h"
#include "AudioTools.h"
#include "AudioTools/AudioLibs/AudioBoardStream.h"
#include "AudioTools/Communication/AudioHttp.h"
#include "AudioTools/Disk/AudioSourceURL.h"
#include "AudioTools/AudioCodecs/CodecHelix.h"
#include "AudioTools/Concurrency/RTOS.h"
#include "AudioTools/Concurrency/AudioPlayerThreadSafe.h"
Code language: PHP (php)
Next we set up the global objects for the DLNA Media Renderer: it need a HttpServer and UDPService!
const int port = 9000;
WiFiServer wifi(port);
HttpServer<WiFiClient, WiFiServer> server(wifi);
UDPService<WiFiUDP> udp;
DLNAMediaRenderer<WiFiClient> media_renderer(server, udp);
Code language: HTML, XML (xml)
For playing the audio we can use an AudioPlayer with AudioSourceDynamicURL, a MultiDecoder and we send the output to an AudioBoardStream. Replace this with an I2SStream if you want to use a simple I2S DAC.
URLStream url;
AudioSourceDynamicURL source(url);
AudioBoardStream i2s(AudioKitEs8388V1); // or e.g. I2SStream i2s;
MultiDecoder multi_decoder;
AACDecoderHelix dec_aac;
MP3DecoderHelix dec_mp3;
WAVDecoder dec_wav;
AudioPlayer player(source, i2s, multi_decoder);
Code language: JavaScript (javascript)
Finally we set up some data structure to handle multiple tasks. The AudioPlayer is not thread save, so we need to use the AudioPlayerThreadSafe class which needs the player defined above and a thread save queue:
QueueRTOS<AudioPlayerCommand> queue(20, portMAX_DELAY, 5);
AudioPlayerThreadSafe<QueueRTOS> player_save(player, queue);
Task dlna_task("dlna", 8000, 10, 0);
Code language: HTML, XML (xml)
In the Arduino setup we configure and start all the objects defined above:
void setup() {
// setup logger
Serial.begin(115200);
DlnaLogger.begin(Serial, DlnaLogLevel::Warning);
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);
// start Wifi
setupWifi();
// setup MultiDecoder
multi_decoder.addDecoder(dec_aac, "audio/aac");
multi_decoder.addDecoder(dec_mp3, "audio/mpeg");
multi_decoder.addDecoder(dec_wav, "audio/wav");
// configure player: EOF handling
player.setOnEOFCallback(onEOF);
// start I2S
i2s.begin();
// setup media renderer (use event callbacks to handle audio at app level)
media_renderer.setBaseURL(WiFi.localIP(), port);
media_renderer.setMediaEventHandler(handleMediaEvent);
// start device
if (!media_renderer.begin()) {
Serial.println("MediaRenderer failed to start");
}
dlna_task.begin([]() {
media_renderer.loop();
});
}
Code language: PHP (php)
In a nutshell we
setup the logger
setup the Wifi
configure the multidecoder by adding individual decoders with their mime
the player is started by DLNA, but we need to register a callback to notify DLNA when the playback has finished
we start I2S
To configure the media renderer we
- define the base URL which consits of the IP address and a port that will be used to announce where the services are availabe
- register the event hanlder which will forward the DLNA actions to the AudioPlayer
Then we can start DLNA
Because handling the DLNA processing is adding quite some lag, we start it’s processing in a separate Task and we do the Audio processing in the loop. So this is quite short:
void loop() {
// if we have nothing to copy, be nice to other tasks
if (player_save.copy()==0) delay(200);
}
Code language: JavaScript (javascript)
The media event handler gets called whenever DLNA receives an Action command, so we need to handle all SET_URI, PLAY, STOP, PAUSE, SET_VOLUME and SET_MUTE actions by calling the corresponding actions in the thread save audio player API:
void handleMediaEvent(MediaEvent ev, DLNAMediaRenderer<WiFiClient>& mr) {
switch (ev) {
case MediaEvent::SET_URI:
Serial.print("Event: SET_URI ");
Serial.println(mr.getCurrentUri());
source.clear();
source.setTimeoutAutoNext(1000);
player_save.setPath(mr.getCurrentUri());
player_save.begin(0, false);
break;
case MediaEvent::PLAY:
Serial.println("Event: PLAY");
player_save.setActive(true);
break;
case MediaEvent::STOP:
Serial.println("Event: STOP");
player_save.end();
url.end();
break;
case MediaEvent::PAUSE:
Serial.println("Event: PAUSE");
player_save.setActive(false);
break;
case MediaEvent::SET_VOLUME:
Serial.print("Event: SET_VOLUME ");
Serial.println(mr.getVolume());
player_save.setVolume(static_cast<float>(mr.getVolume()) / 100.0);
break;
case MediaEvent::SET_MUTE:
Serial.print("Event: SET_MUTE ");
Serial.println(mr.isMuted() ? 1 : 0);
player_save.setMuted(mr.isMuted());
break;
default:
Serial.println("Event: OTHER");
}
}
Code language: PHP (php)
Last, but not least we need to register the onEOF() handler which just calls media_renderer.setPlaybackCompleted(); This is needed to inform the Control Point that we finshed playing and that we require the next URL to be played back. We also reset the player and data source to prevent that the same URL is reapeated automatically.
void onEOF(AudioPlayer& player) {
if (source.size() > 0) {
Serial.println("*** onEOF() ***");
player_save.end();
source.clear();
media_renderer.setPlaybackCompleted();
}
}
Code language: JavaScript (javascript)
I think it is quite cool that it is so easy to build a custom Open Source Wireless Network Speaker just with a Microcontroller. The complete example can be found in the AudioTools project.
Dependencies
You need to install the following libraries
- https://github.com/pschatzmann/arduino-audio-tools
- https://github.com/pschatzmann/arduino-dlna
- https://github.com/pschatzmann/arduino-libhelix
- Optional: https://github.com/pschatzmann/arduino-audio-driver if you want to use an AI Thinker AudioKit as output device
0 Comments