[MicroPython] Audiodaten mit I²S senden

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Benutzeravatar
Dennis89
User
Beiträge: 1349
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo zusammen,

ich versuche mit einem ESP32 über I²S Audiodaten an einen Verstärker, den HifiBerry MiniAmp zu senden. Für den ersten Test habe ich eine *.mp3-Datei auf den ESP32 geladen und versuche die mit folgendem Code abzuspielen.

Code: Alles auswählen

from machine import I2S
from machine import Pin


SCK_PIN = 14
WS_PIN = 12
SD_PIN = 13
MUTE_PIN = 16

def main():
    audio_out = I2S(
        0,                                  
        sck=Pin(SCK_PIN), ws=Pin(WS_PIN), sd=Pin(SD_PIN),   
        mode=I2S.TX,
        bits=16,
        format=I2S.STEREO,                        
        rate=44100, 
        ibuf=20000
    )
    mute = Pin(Pin(MUTE_PIN), Pin.OUT)
    mute.value(1)
    with open("corazones.mp3", 'rb') as music:
        for line in music.readlines():
             audio_out.write(line)   

if __name__ == '__main__':
    main()

Es kommt allerdings nur ein Rauschen aus den Boxen. Die Länge des Rauschens passt ca. zu der Länge des Liedes. Mit dem Anschluss bin ich mir relativ sicher, auch das `mute` funktioniert. Ich weis aber nicht, ob ich die Bytes vor dem senden noch irgendwie "aufbearbeiten" muss oder ob ich die 1:1 so senden kann. Daher wollte ich euch mal fragen, ob der Code in der Theorie so passt oder nicht. Ich suche gerade den Fehler im Code und in der Verdrahtung und würde gerne mal ein Teil ausschließen können.
Der Link zur MP-Doku:
https://docs.micropython.org/en/latest/ ... e.I2S.html

Die gewählte `rate` entspricht wohl CD-Qualität und `MONO` oder `STEREO` macht auch kein unterschied, genauso wenig der Wechsel von Kanal 0 auf 1, oder 32 anstatt 16Bits.

Vielen Dank und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 13468
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Dennis89: Man kann ein MP3 nicht einfach so an etwas verfüttern was Sample-Daten haben will. Das ist ja ein komprimiertes Datenformat.

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
Benutzeravatar
Dennis89
User
Beiträge: 1349
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für die schnelle Antwort.

Wäre WAV dann richtig? Ich wollte es versuchen, aber selbst wenn ich die Datei so kürze, dass das Lied noch 2 Sekunden lang ist, bekomme ich einen `MemoryError: memory allocation failed, allocating 8192 bytes`.
Hab leider keine Hardware da um die Datei auf einer SD-Karte abzulegen.

Die Idee, die ich noch habe, wäre eventuell die Bytes vom Laptop mittels http auf dem ESP32 zu streamen. (?)

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
DeaD_EyE
User
Beiträge: 1107
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

MP3 muss dekodiert werden. Soweit ich weiß, ist es ein Algorithmus, der vom Fraunhofer-Institut entwickelt worden ist. Die verlustbehaftete Kompression wird durch eine Furnier-Transformation erreicht und mit einem psychoakustischen Modell werden Frequenzen entfernt, die wir Menschen nicht wahrnehmen. Man kann es auch mit JPEG vergleichen. Auch dort wird eine Furnier-Transformation angewandt. D.h. um es in Rohform zu bekommen, muss es dekodiert werden. Es gibt aber auch Player, die direkt von einer SD-Karte MP3-Dateien abspielen können und via UART gesteuert werden. Link: https://www.berrybase.de/mp3-player-mod ... erstaerker


WAV würde gehen, da es nicht komprimiert ist und der Datenstrom bereits in der passenden Form vorliegt. Man kann sogar einfach den Header ignorieren. So hab ich das gemacht. MP3 geht auch, nur musst du dann auch Ciruitpython wechseln. Es gibt noch keinen MP3-Dekoder, der in C geschrieben ist und als MPY verfügbar ist. Zumindest nicht, als ich das letzte mal nachgesehen habe.

Für den ESP32C3 wird es z.B. in der Beta-Version 9.2.0 unterstützt. Download: https://circuitpython.org/board/seeed_xiao_esp32c3/
Doku zu audiomp3: https://docs.circuitpython.org/en/lates ... /audiomp3/

Bedenke, dass bei Cirtuitpython einiges anders ist. Mir gefällt das nicht, aber wenn ich ein Modul benötige, dass nur in Circuitpython vorhanden ist, dann nehme ich das.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
Dennis89
User
Beiträge: 1349
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

danke für die Antwort und die Links.

Ziel war eigentlich nur mal zu testen, ob ich den Amp und den ESP32 zusammen nutzen kann, weil die schon ewig hier liegen. Daher habe ich ehrlich gesagt, einfach nur eine Musik-Datei gewählt, die von der Größe drauf passte. Falls die Zeit es mit macht und der Amp funktioniert, wäre eher streamen von Spotify oder so, das langfristige Ziel.


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1349
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

ich mache noch was falsch und bitte euch noch mal um Hilfe.
Ich habe eine *.wav Datei auf dem Laptop, lese die Bytes aus und sende sie mit `socket` an den ESP32. Der ESP32 bekommt auch was, hab das mal testweise mit `print` geschaut, aber aus den Boxen kommt nicht mal mehr ein Rauschen.

Mit der Buffergröße von `recv` habe ich auch schon gespielt, ändert aber nichts. Denke das hat vielleicht nachher einen Einfluss ob und wie flüssig die Wiedergabe ist, aber zum testen reicht es mir, wenn ansatzweise so etwas wie Musik aus den Boxen kommt. Habt ihr mir einen Rat?

ESP32 (`with`-statement wird wohl in MP nicht unterstützt, zumindest kam die Meldung, das es keine __exit__ gibt):

Code: Alles auswählen

from machine import I2S, Pin, UART
import network
import socket


SCK_PIN = 14
WS_PIN = 12
SD_PIN = 13
MUTE_PIN = 16

SSID = "W-lan"
PASSWORD = "XXXXXXXX"

HOST = ''       
PORT = 12345

def connect_wlan():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(SSID, PASSWORD)
    while True:
        if wlan.isconnected():
            print("Verbunden")
            print(wlan.ifconfig())
            break


def main():
    connect_wlan()

    audio_out = I2S(
        0,                                  
        sck=Pin(SCK_PIN), ws=Pin(WS_PIN), sd=Pin(SD_PIN),   
        mode=I2S.TX,
        bits=16,
        format=I2S.MONO,                        
        rate=44100, 
        ibuf=20000
    )
    mute = Pin(Pin(MUTE_PIN), Pin.OUT)
    mute.value(1)
    
    socket_connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    socket_connection.bind((HOST, PORT))
    socket_connection.listen(1)
    connection, _ = socket_connection.accept()
    while True:
        music = connection.recv(1024)
        if not music:
            connection.close()
            break
        audio_out.write(music)


if __name__ == '__main__':
    main()
PC:

Code: Alles auswählen

#!/usr/bin/env python
from pathlib import Path
import socket


PATH_TO_MUSICFILE = (
    Path().home() / "Musik/01_01_2024_11_55_noaa-18_s_24_305_W_137.9125.wav"
)

IP = "192.168.0.216"
PORT = 12345


def main():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as socket_connection:
        socket_connection.connect((IP, PORT))
        for line in PATH_TO_MUSICFILE.read_bytes():
            socket_connection.sendall(bytearray(line))


if __name__ == "__main__":
    main()
Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 18034
Registriert: Sonntag 21. Oktober 2012, 17:20

Warum sendest du jedes Byte einzeln?

Code: Alles auswählen

WAV_HEADER_SIZE = 44
data = PATH_TO_MUSICFILE.read_bytes()
socket_connection.sendall(data[WAV_HEADER_SIZE:])
Benutzeravatar
Dennis89
User
Beiträge: 1349
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen,

jetzt funktioniert es, danke!

Ich habe die einzeln gesendet, weil ich dachte dass das für den ESP einfacher zu verarbeiten wäre. Ich wusste/weis auch nicht wie das `recv` genau funktioniert. Es blockiert bis der Buffer, Größe gebe ich ja an, voll ist. Aber was passiert mit den Bytes die ankommen, wärend ich den Buffer weg schreibe? Oder stehen die alle wie in einer Warteschlange an? Muss ja jetzt eigentlich, wenn ich das ganze Lied auf einmal sende.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
snafu
User
Beiträge: 6778
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Dennis89
Es gibt einen Puffer für versendete Daten und einen für ankommende Daten. send() schreibt bis der Sende-Puffer voll ist oder alle Daten verschickt wurden. recv() liest bis der Puffer leer ist oder die angegebene Maximalgröße erreicht ist. Um den Rest kümmert sich die Socket-Infrastruktur der beteiligten Systeme.

Da send() oft nicht alles in einem Rutsch verschicken kann, sollte man - wie bereits gezeigt wurde - bevorzugt sendall() verwenden. Mit sendall() wird so oft send() mit dem Rest der Daten aufgerufen bis alles versendet wurde oder der Server einen Fehler zurückgibt.

So lange alles flüssig läuft, würde ich mir keine großen Gedanken um die beteiligten Puffer oder Warteschlagen machen. Außer dass ich bei der "bufsize" für recv() wohl mit 8 Kb anstatt mit 2 Kb arbeiten würde.
Benutzeravatar
Dennis89
User
Beiträge: 1349
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für die Erklärung, ich denke das habe ich verstanden 🙂

Fall‘s jemand über das Thema stolpert und ähnliches vor hat, ich hab das hier mal ink. Anschlussplan dokumentiert:
https://github.com/Dennis-89/MiniAmpESP32

Je nach dem wie es weiter geht, werde ich das detaillierter updaten.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Antworten