Skip to content

Communication DLNA

pschatzmann edited this page Oct 22, 2026 · 2 revisions

DLNA (UPnP AV) is how media players, NAS systems, smart TVs and phone apps find each other in the home network and play media on each other. With arduino-dlna (install it) your microcontroller can become a DLNA media renderer: it appears as a speaker in DLNA control apps (e.g. BubbleUPnP, foobar2000 or the media server of your NAS), which tell it which URL to play, and arduino-audio-tools plays it. It can also be a DLNA media server that provides a list of internet radios or the files of its SD card to the players in your network. See Communication for the general considerations.

Media renderer

From dlna-audio-renderer.ino: arduino-dlna handles the DLNA protocol, and reports what the controller wants as events; the sketch implements them with an AudioPlayer that plays URLs:

#include "WiFi.h"
#include "dlna.h"  // https://github.com/pschatzmann/arduino-dlna
#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"

// DLNA
const int port = 9000;
WiFiServer wifi(port);
HttpServer<WiFiClient, WiFiServer> server(wifi);
UDPService<WiFiUDP> udp;
DLNAMediaRenderer<WiFiClient> media_renderer(server, udp);

// AudioPlayer that plays the URL it gets from the controller
URLStream url;
AudioSourceDynamicURL source(url);
AudioBoardStream i2s(AudioKitEs8388V1);
MultiDecoder multi_decoder;
AACDecoderHelix dec_aac;
MP3DecoderHelix dec_mp3;
WAVDecoder dec_wav;
AudioPlayer player(source, i2s, multi_decoder);

// the DLNA events come from another task: use a thread-safe player
QueueRTOS<AudioPlayerCommand> queue(20, portMAX_DELAY, 5);
AudioPlayerThreadSafe<QueueRTOS> player_save(player, queue);
Task dlna_task("dlna", 8000, 10, 0);

void handleMediaEvent(MediaEvent ev, DLNAMediaRenderer<WiFiClient>& mr) {
  switch (ev) {
    case MediaEvent::SET_URI:
      source.clear();
      player_save.setPath(mr.getCurrentUri());
      player_save.begin(0, media_renderer.isActive());
      break;
    case MediaEvent::PLAY: player_save.setActive(true); break;
    case MediaEvent::PAUSE: player_save.setActive(false); break;
    case MediaEvent::STOP: player_save.end(); url.end(); break;
    case MediaEvent::SET_VOLUME: player_save.setVolume(mr.getVolume() / 100.0); break;
    case MediaEvent::SET_MUTE: player_save.setMuted(mr.isMuted()); break;
    default: break;
  }
}

In setup() the decoders are registered with the MultiDecoder (audio/aac, audio/mpeg, audio/wav), the renderer is started with media_renderer.setBaseURL(WiFi.localIP(), port), setMediaEventHandler(handleMediaEvent) and begin(), and a task runs media_renderer.loop(). When a file has finished, the example reports it with media_renderer.setPlaybackCompleted() (see the full example).

Media server

A DLNAMediaServer announces itself in the network, and DLNA players and control apps (e.g. VLC, BubbleUPnP, Kodi, smart TVs - or an Arduino renderer from above) can browse its content and play it. The server only provides the directory: each item has a URL, which the player fetches directly. The media server is part of arduino-dlna and does not need arduino-audio-tools.

You provide the content with two callbacks:

  • setPrepareDataCallback(): called for each Browse or Search request. Select the matching items: for BrowseChildren the children of objectID, for BrowseMetadata the item objectID itself, for Search the items that match the filter. Report how many there are in numberReturned and totalMatches.
  • setGetDataCallback(): returns the selected items one by one.

From device-media-server.ino, which provides a folder of internet radios:

#include "WiFi.h"
#include "dlna.h"  // https://github.com/pschatzmann/arduino-dlna

const int port = 9000;
WiFiServer wifi(port);
HttpServer<WiFiClient, WiFiServer> server(wifi);
UDPService<WiFiUDP> udp;
DLNAMediaServer<WiFiClient> mediaServer(server, udp);

// id, parent id, restricted, title, URL, mime type, class
const MediaItem items[] = {
  {"1", "0", true, "Radios", "", "", MediaItemClass::Folder},
  {"2", "1", true, "Blues on Radio (MP3)", "https://0n-blues.radionetz.de/0n-blues.mp3", "audio/mpeg", MediaItemClass::Radio},
  {"3", "1", true, "Blues on Radio (AAC)", "https://0n-blues.radionetz.de/0n-blues.aac", "audio/aac", MediaItemClass::Radio},
};
std::vector<MediaItem> result_items;

void prepareData(const char* objectID, ContentQueryType queryType,
                 const char* filter, int startingIndex, int requestedCount,
                 const char* sortCriteria, int& numberReturned,
                 int& totalMatches, int& updateID, void* reference) {
  result_items.clear();
  for (auto& item : items) {
    switch (queryType) {
      case ContentQueryType::Search:
        if (item.itemClass != MediaItemClass::Folder) result_items.push_back(item);
        break;
      case ContentQueryType::BrowseMetadata:
        if (StrView(item.id).equals(objectID)) result_items.push_back(item);
        break;
      case ContentQueryType::BrowseChildren:
        if (StrView(item.parentID).equals(objectID)) result_items.push_back(item);
        break;
    }
  }
  numberReturned = result_items.size();
  totalMatches = result_items.size();
}

bool getData(int index, MediaItem& item, void* reference) {
  if (index < 0 || index >= result_items.size()) return false;
  item = result_items[index];
  return true;
}

void setup() {
  // ... connect WiFi
  mediaServer.setFriendlyName("ArduinoMediaServer");  // the name the players show
  mediaServer.setBaseURL(WiFi.localIP(), port);
  mediaServer.setPrepareDataCallback(prepareData);
  mediaServer.setGetDataCallback(getData);
  mediaServer.begin();
}

void loop() { mediaServer.loop(); }

The root folder has the id "0". MediaItemClass tells the player what an item is: Folder, Radio, Music ...

Files from the SD card

To provide the files of an SD card, the device-media-server-sdfat example uses the SdFatContentProvider (tools/SdFatContentProvider.h): it scans the file system and implements both callbacks. The URLs of the items point back to the ESP32, so the sketch also needs an HTTP handler that sends the file:

SdFs sd;
FsFile file;
SdFatContentProvider<SdFs> contentProvider;

// GET /files?ID=<id>: send the file with this id
void handleFileRequest(IHttpServer* server_ptr, const char* requestPath, HttpRequestHandlerLine* hl) {
  StrView path{requestPath};
  int id = StrView(path.c_str() + path.indexOf("ID=") + 3).toInt();
  TreeNode& node = contentProvider.getTreeNodeById(id);
  file.close();
  file = sd.open(node.path().c_str(), O_READ);
  if (!file) { server_ptr->replyNotFound(); return; }
  server_ptr->reply(node.getMime().c_str(), file, file.size(), 200);
}

void setup() {
  // ... connect WiFi, sd.begin(...)
  mediaServer.setBaseURL(WiFi.localIP(), port);
  mediaServer.setFriendlyName("SdFat Media Server");
  contentProvider.begin(sd, "/");
  contentProvider.setPath("files");
  server.on("/files*", T_GET, "text/html", handleFileRequest);
  mediaServer.setPrepareDataCallback([](const char* objectID, ContentQueryType queryType, const char* filter,
      int startingIndex, int requestedCount, const char* sortCriteria, int& numberReturned,
      int& totalMatches, int& updateID, void* reference) {
    contentProvider.prepareData(objectID, queryType, filter, startingIndex, requestedCount,
                                sortCriteria, numberReturned, totalMatches, updateID, reference);
  });
  mediaServer.setGetDataCallback([](int index, MediaItem& item, void* reference) -> bool {
    return contentProvider.getData(index, item, reference);
  });
  mediaServer.begin();
}

There are also examples for Ethernet and for running the server in a separate task.

Things to consider

  • Formats: the controller may send any format the media server provides - register a decoder for each format you want to support; a MultiDecoder selects it from the content type.
  • Threads: DLNA requests arrive in their own task while the audio is played in loop(): change the player only via AudioPlayerThreadSafe.
  • Media server and renderer together: a media server serves the files, a renderer plays them, and a control point (e.g. a phone app) connects the two. arduino-dlna also provides control points, see arduino-dlna/examples.
  • Memory: a media server with a big SD card keeps the directory tree in memory: use PSRAM or keep the number of files small.

Clone this wiki locally