Den Music Player Daemon (mpd) per Fernbedienung steuern (Teil2)

Dies ist der zweite Teil des Tutorials „Den Music Player Daemon (mpd) per Fernbedienung steuern“. Den vorherigen Teil könnt ihr hier finden.

In diesem Tutorial soll es darum gehen, wie der mpd über TCP gesteuert wird. Es wäre alternativ auch möglich, den mpd per Aufruf des Kommandozeilentools mpc zu steuern. Dieses ist aber nicht so mächtig und liefert so gut wie keine Rückantworten an den Aufrufer zurück und ist somit unbrauchbar. Außerdem ermöglicht der Ansatz über TCP, dass das Kontrollprogramm nicht auf dem selben Gerät wie der mpd laufen muss. So könnte zum Beispiel der Raspberry Pi (RPi) nur zum Auswerten der auf der Fernbedienung gedrückten Tasten dienen und entfernt über TCP den mpd steuern.

Das MPD Kommunikationsprotokoll
Wie schon angesprochen, ist es möglich den mpd über TCP zu steuern. Der Client (unser Kontrollprogramm) sendet ein Kommando an den mpd-Server und erhält (meistens) eine Antwort von diesem. Diese Antwort enthält entweder ein OK oder eine Fehlermeldung in der Form „ACK + Fehlermeldung“ zurück. Die Details zum Protokoll des mpd-Servers sind hier einsehbar.

Die MPD Kommandos

Der mpd-Server kennt insgesamt 6 verschiedene Kommandotypen (Details):

  • Admin
  • Database
  • Miscellaneous
  • Playback Commands
  • Playlist Commands
  • Scope specifiers

Für die Fernbedienung interessieren uns nur die Admin, Playlist und die Playback Kommandos.

Die Kommunikation mit dem mpd-Server gestaltet sich wie folgt:

  1. Verbindung zum Server öffnen und ein ‚OK‘ erwarten.
  2. Ein möglicherweise laufendes Playback per Stop-Kommando anhalten und die Playlist des mpd per Clear-Kommando leeren.
  3. Die Liste aller Playlists aufrufen, welche beim mpd-Server registriert sind. Wir werden nur die erste Playlist für die Sammlung von Internet Radio Streams verwenden.
  4. Die erste Playlist in den mpd-Server laden.
  5. Wenn der Benutzer über die Fernbedienung den Stream wechselt, erst per Stop-Kommando das Playback anhalten und dann per playid-Kommando den neuen Stream auswählen und das Abspielen starten.
  6. Wenn der Benutzer länger als drei Sekunden eine Taste gedrückt halt, so wird das Playback per Stop-Kommando angehalten.

Das folgende UML-Diagramm zeigt den Ablauf der Session-Initialisierung mit dem MPD (Punkte 1-4):

Initialisierung der MPD-Server Session

Initialisierung der MPD-Server Session

Und das nächste Diagramm zeigt vereinfacht, wie das Kontrollprogramm das USB-Gerät einliest und in Abhängigkeit der gedrückten Tasten das Playback stoppt und den vorherigen oder folgenden Radio Stream aus der Playlist abspielt. Der Client aus dem vorherigen Diagramm ist hier aufgeschlüsselt in ‚main‘ und ‚inputDeviceUSB‘.

Vereinfachte Darstellung der Kontrolllogik

Vereinfachte Darstellung der Kontrolllogik.

Auf die Darstellung des Zustandsautomaten der Steuerung zum Wechseln zwischen den Radio Streams verzichte ich, weil dieser nur aus wenigen Zuständen besteht.

Beispielcode
Nach all der Theorie kommen wir zu dem Beispielcode in C++.

1. Verbindung mit dem mpd-Server aufbauen

Um eine Verbindung zum mpd-Server aufzubauen, wird ein Socket im IPv4 (AF_INET)-Modus und als Stream-Socket geöffnet.

int sockFd;
// open the socket
sockFd = socket(AF_INET, SOCK_STREAM, 0);
if (sockFd < 0) {
     // error handling
}

Dann wird versucht, den Host über seinen Namen (z.B. raspi1, localhost, usw.) aufzulösen und die IP-Adresse zu ermitteln.

struct hostent *server = NULL;
// resolve the IP address
server = gethostbyname(mpdServerUrl);
if (server == NULL) {
  // error handling
}

Hat dies geklappt, folgt der finale Schritt, um den geöffneten Socket mit dem Server zu verbinden.

int portNo = 6600;
struct sockaddr_in serverAddr;

bzero((char *) &serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
bcopy((char *)server->h_addr, (char *)&serverAddr.sin_addr.s_addr, server->h_length);
serverAddr.sin_port = htons(portNo);

if (connect(sockFd,(struct sockaddr *) &serverAddr,sizeof(serverAddr)) < 0) {
  // error handling
}

2. Kommandos senden und Serverantwort lesen
Das Absetzen eines Kommandos an mpd erfolgt dann einfach über das Schreiben per write() auf den verbundenen Socket. Kommandos an mpd müssen immer mit einer leeren Zeile abgeschlossen werden, so dass z.B. das Kommando stop wie folgt im Sourcecode aussehen könnte.

// write command to mpd
const char* cmd = "stop\n";
int writtenBytes = write(sockFd, cmd, strlen(cmd));
if (writtenBytes < 1) {
  // error handling
}

Direkt danach wird die Response eingelesen und dann auf ‚OK‘ geprüft.

const int BUF_SIZE = 2048;
char buf[BUF_SIZE];

int readBytes = read(sockFd, buf, BUF_SIZE - 1);
if (readBytes < 0) {
  // error handling
}
buf[readBytes] = ''; // set null terminator at end of the response

const char mpdOkStr[] = "OK";
if (strstr(buf, mpdOkStr) != NULL) {
  // response is ok
} else {
  // error handling
}

Hier ist zu beachten, dass vorher der Socket in den non-blocking Mode versetzt werden sollte, weil sonst das Lesen auf dem Socket blockieren könnte. Außerdem ist es sinnvoll, read() so oft aufzurufen, bis keine Bytes mehr gelesen worden. In dem Beispielcode werden nur maximal die ersten 2047 Bytes eingelesen. Das letzte Byte wird für den Nullterminator verwendet, damit strstr() das Ende des Strings erkennt.

Im dritten und letzten Teil des Tutorials stelle ich das Kontrollprogramm vor und wie dieses im Zusammenspiel mit mpd zum Laufen gebracht wird.

Links zum Thema

Advertisements

I'm an independent german IT consultant for Java, J2E and C++ - in my spare time I work on embedded devices, digital Image- and Signal processing, riding motorcycle, doing sports and do many other things.

Tagged with: , , , , , ,
Veröffentlicht in Raspberry Pi

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: