earlephilhower / ESP8266Audio

Arduino library to play MOD, WAV, FLAC, MIDI, RTTTL, MP3, and AAC files on I2S DACs or with a software emulated delta-sigma DAC on the ESP8266 and ESP32
GNU General Public License v3.0
2.07k stars 439 forks source link

Webradio Example on Raspberry Pico W #622

Open ullibak opened 1 year ago

ullibak commented 1 year ago

Hi, I am trying to run the webradio.ino example on a Pico W. The structure of the .ino file is

#include <Arduino.h>
#if defined(ARDUINO_ARCH_RP2040)
void setup() {}
void loop() {}
#else
(here comes the rest of the code)
#endif

So this means that if a board with a RP2040 is used, the compiler does essentially nothing.

If I comment out these preprocessor directives and choose the Pico W as board in die Arduino IDE, I get a series of error messages, the first of them is

Compilation error: 'AudioFileSourceICYStream' does not name a type; did you mean 'AudioFileSourceBuffer'?

Has anyone managed to get this (or other) examples to run on a Pico W?

Thx!

ullibak commented 1 year ago

After digging through the files and some research, I found a way to make the webradio example run on a Pico W. The http connection seems to be stable, but the sound quality needs just some improvement. Feels like almost being there...

This is what I did:

Modified the preprocessor directives in AudioFileSourceHTTPStream.h;

#if defined(ESP32) || defined(ESP8266)
  #pragma once

  #include <Arduino.h>
  #ifdef ESP32
    #include <HTTPClient.h>
  #else
    #include <ESP8266HTTPClient.h>
  #endif
  #include "AudioFileSource.h"
#endif      // moved this endif from last line to here

#if defined(ARDUINO_ARCH_RP2040)   // added directives for RP2040
  #pragma once
  #include <Arduino.h>
  #include <HTTPClient.h>
  #include "AudioFileSource.h"
#endif

Modified the preprocessor directives in AudioFileSourceHTTPStream.cpp;

#if defined(ESP32) || defined(ESP8266) || defined(ARDUINO_ARCH_RP2040) //added RP2040

Here is the code I used:


/* Example Arduino code for Raspberry Pico W
  Streams mp3 from http, buffers, decodes and outputs through i2s on a 1334A DAC board from Adafruit:
  https://learn.adafruit.com/adafruit-i2s-stereo-decoder-uda1334a

  Pin connections:
    UDA1334A:    Pico W:
        BCLK  =  GPIO26
        WSEL  =  GPIO27
        DIN   =  GPIO28
*/
#include <Arduino.h>
//#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#include <WiFi.h>
#include <HTTPClient.h>
#include "AudioFileSourceHTTPStream.h"
#include "AudioFileSourceBuffer.h"
#include "AudioGeneratorMP3.h"
#include "AudioOutputI2S.h"

AudioFileSourceHTTPStream *file = NULL;
AudioFileSourceBuffer *buff = NULL;
AudioGenerator *decoder = NULL;
AudioOutputI2S *out = NULL;

const char* ssid = "your SSID";
const char* password = "your password";
const char* url = "http://airspectrum.cdnstream1.com:8000/1261_192";

// Called when there's a warning or error (like a buffer underflow or decode hiccup)
void StatusCallback(void *cbData, int code, const char *string)
{
  const char *ptr = reinterpret_cast<const char *>(cbData);
  // Note that the string may be in PROGMEM, so copy it to RAM for printf
  char s1[64];
  strncpy_P(s1, string, sizeof(s1));
  s1[sizeof(s1)-1]=0;
  Serial.printf("STATUS(%s) '%d' = '%s'\n", ptr, code, s1);
  Serial.flush();
}

const int preallocateBufferSize = 16*1024;
const int preallocateCodecSize = 85332; // AAC+SBR codec max mem needed

void *preallocateBuffer = NULL;
void *preallocateCodec = NULL;

void setup()
{
  Serial.begin(115200);
  delay(3000);
  Serial.printf_P(PSTR("Connecting to WiFi\n"));
  WiFi.disconnect();
  delay(1000);
  WiFi.softAPdisconnect(true);
  delay(1000);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  // Try forever
  while (WiFi.status() != WL_CONNECTED) {
    Serial.printf_P(PSTR("...Connecting to WiFi\n"));
    delay(1000);
  }
  Serial.printf_P(PSTR("Connected\n"));
  Serial.print("RSSI: ");
  Serial.println(WiFi.RSSI());
  Serial.printf_P(PSTR("Local IP:"));
  Serial.println(WiFi.localIP());

  // First, preallocate all the memory needed for the buffering and codecs, never to be freed
  preallocateBuffer = malloc(preallocateBufferSize);
  preallocateCodec = malloc(preallocateCodecSize);
  if (!preallocateBuffer || !preallocateCodec) {
    Serial.printf_P(PSTR("FATAL ERROR:  Unable to preallocate %d bytes for app\n"), preallocateBufferSize+preallocateCodecSize);
    while (1) delay(1000); // Infinite halt
  }

  file = new AudioFileSourceHTTPStream(url); 
  buff = new AudioFileSourceBuffer(file, preallocateBuffer, preallocateBufferSize);
  Serial.printf_P(PSTR("created buffer\n"));
  buff->RegisterStatusCB(StatusCallback, NULL);

  out = new AudioOutputI2S();
  //out->SetPinout(int bclkPin, int wclkPin, int doutPin), default is bclkPin = 26, wclkPin = 27, doutPin = 28
  //out->SetPinout(10, 11, 9); 
  //out->SetGain(0.02);  //some DAC have very high output

  decoder = new AudioGeneratorMP3(preallocateCodec, preallocateCodecSize);
  //decoder = new AudioGeneratorMP3a();
  Serial.printf_P(PSTR("created decoder\n"));
  decoder->RegisterStatusCB(StatusCallback, NULL);
  Serial.printf_P("Decoder start...\n");
  decoder->begin(buff, out);
}

uint32_t getTotalHeap(void) {
   extern char __StackLimit, __bss_end__;
   return &__StackLimit  - &__bss_end__;
}
uint32_t getFreeHeap(void) {
   struct mallinfo m = mallinfo();
   return getTotalHeap() - m.uordblks;
}

void loop()
{
  static int lastms = 0;
  if (millis()-lastms > 10000) {
    lastms = millis();
    Serial.printf_P(PSTR("Running for %d seconds...Free mem=%d \n"), lastms/1000, getFreeHeap());
  }
  decoder->loop();
}

With the modifications described above, the code compiled. (Although, I ran into some difficulties because of a conflicting library, see https://github.com/earlephilhower/arduino-pico/issues/1127 It compiled after removing the "SdFat_-_Adafruit_Fork" library.)

The output of the speakers is a terrible noise, but the Pico W seems to establish a stable connection to the radio station via http.

Adding a larger buffer for i2s in AudioOutputI2S.cpp helped a lot:

#elif defined(ARDUINO_ARCH_RP2040)
AudioOutputI2S::AudioOutputI2S(long sampleRate, pin_size_t sck, pin_size_t data) {
    i2sOn = false;
    mono = false;
    bps = 16;
    channels = 2;
    hertz = sampleRate;
    bclkPin = sck;
    doutPin = data;
    SetGain(1.0);
    i2s.setBuffers(5,2048);   // <--- added this line
}
#endif

Although the sound quality dramatically improved by adding this buffer, it ist still not good. The "underlying" sound is good, but there are cracks at a rate of approx 10 Hz. I tried to play a short mp3 file at a sample rate of 44100 Hz from memory, but also got crackling sound.

Is there not enough processing power in the RP2040 to decode a mp3 (stream)? I tried to overclock the processor up to 240 MHz, but there was no improvement. So I think the processing power is not the problem. Played with buffers, but no improvement...

Now I'm stuck! Any ideas on how to proceed from here woud be greatly appreciated. Is there a way to further analyze where the problem could be?

Many Thx!

LinusHeu commented 1 year ago

Maybe you experienced this bug in arduino-pico which caused clicking/cracks: https://github.com/earlephilhower/arduino-pico/pull/1500 https://github.com/earlephilhower/arduino-pico/discussions/1491

It's fixed in the latest release.

ullibak commented 1 year ago

Yes, this solves the problem! Thanks a lot, @LinusHeu !

Installing version 3.2.2 and applying the modifications described above for AudioFileSourceHTTPStream.h and AudioFileSourceHTTPStream.cpp as well as increasing the buffer in AudioOutputI2S.cpp led to a clean sound without clicking/cracks. Not sure though, if i2s.setBuffers(5,2048) is really the ideal setting, but it works well for this http stream...

gregsadetsky commented 8 months ago

Thanks a lot @ullibak! I was able to make the required fixes to stream internet radio with my pimoroni pico audio pack (a different rp2040 i2s board)

the i2s.setBuffers call really made the difference! cheers

PicoGhost commented 5 months ago

I have the same project than @gregsadetsky : build a webradio with a Pimoroni Pico Audio board.

I followed @ullibak ullibak steps to modify AudioFileSourceHTTPStream.h, AudioFileSourceHTTPStream.cpp and AudioOutputI2S.cpp

The sound is still not perfect with missing part. increase CPU to 250MHz helped a bit. I also compile with maximum optimisation -O3

Any other idea?

gregsadetsky commented 5 months ago

@PicoGhost if it can help, all of the code for my internet radio client is here -- https://github.com/gregsadetsky/wfmu-rp2040-pimoroni-radio

ullibak commented 5 months ago

@PicoGhost: When I worked on the webradio, I once experienced clicks and other noises or stuttering and I was absolutely unable to correlate them to sofware settings such as buffers etc. After hours of altering the code, compiler settings and processor speed without any success, I discovered that I had a hardware problem. I used Pimoroni PicoAudio as DAC and found that the contacts of the PicoAudio were bent in such a way that they made bad contact to the pins of the Pico. The contacts are gold plated, but the spring force is weak so that they plastically deform if the Pico is unplugged and re-plugged multiple times. I had to bend the contacts back towards the middle of the hole using a tiny watchmaker's screwdriver and that solved the problem.

PicoAudio

Another problem that occured later turned out to be a power supply problem. I used a cheap USB power supply but this obviously was good enough to charge a mobile phone but not good enough to power a Pico and a DAC.