These protocol operations should not raise exceptions, but sometimes
they do. Catch the exception and extract the errno value if available.
At the same time, harmonize the argument types for the underlying C
routines
* Don't consume in the case of "indata overflow".
Doing so leaves us at a bad boundary within the MP3 data
and can continue decoding from an inappropriate spot
i.e., one that looks likede mp3 data but is NOT.
because there are many crashing bugs in the helix mp3 library on
invalid input data, we must do our best to avoid them, and this
is one way to do that.
* clear the output buffer in the case there's not a sync word in the
buffer. this can also happen when too little data is available.
this changes more "stuttering" conditions into "silent" conditions.
With these changes, I can get through 3+ plays of "idea.mp3" from a local
http server with long pauses (but not stuttering glitches or safe mode
crashes).
I was also able to play through 10+ minutes of http://ice2.somafm.com/dronezone-128-mp3
without crashing or "end of stream", though again there are pauses
due to packet loss.
I think this is good now, except for the problems that arise when
the socket layer doesn't deliver a fresh packet for a long time.
You can now, e.g.,
```
with open("/whatever.mp3") as mp3_file:
mp3_file.seek(16000*30)
decoder.file = mp3_file
i2s.play(decoder)
```
to start about 30 seconds into a 128kbit/s CBR track.
If a track is looped, the loop will start at the beginning.
This also changes the behavior if the track is started & stopped: it will
continue from where it left off, except if it had prevously run to
completion. To get other behavior, you can seek the file and then re-assign
the file property.
This gets MP3 playback of a soma.fm stream working for up to a minute
at a time, though it's still vulnerable to network glitches.
* the buffer can empty, in which case a single block of audio
plays repeatedly in max headroom stutter fashion
* the server eventually (after 1 to 5 5 minutes) stops getting packets
at all. At this point stream playback stops, with the internal error
indicating a problem MP3 decoding (which doesn't quite make sense):
-9, ERR_MP3_INVALID_HUFFCODES.
* other combinations of audiomixer buffer & mp3 buffer might give
different results
```py
import time
import adafruit_connection_manager
import adafruit_requests
import audiobusio
import audiomixer
import audiomp3
import board
import wifi
pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio)
requests = adafruit_requests.Session(pool, ssl_context)
# todo: parse PLS files like https://somafm.com/nossl/dronezone.pls
# todo: figure out why https URLs don't work at all (missing select?)
# STREAMING_URL = "http://ice2.somafm.com/dronezone-128-mp3"
STREAMING_URL = "http://ice4.somafm.com/tikitime-128-mp3"
def get_mp3_stream():
if STREAMING_URL.startswith("http:") or STREAMING_URL.startswith("https:"):
return requests.get(STREAMING_URL, headers={"connection": "close"}).socket
return open(STREAMING_URL, "rb")
mixer_buffer_size = 1152 * 2
mp3_buffer = bytearray(32768)
with audiobusio.I2SOut(
bit_clock=board.D12, word_select=board.D13, data=board.D11
) as i2s, get_mp3_stream() as stream, audiomp3.MP3Decoder(
stream, mp3_buffer
) as sample, audiomixer.Mixer(
channel_count=2, sample_rate=44100, buffer_size=mixer_buffer_size
) as m:
v = m.voice[0]
print(sample)
i2s.play(m)
v.play(sample, loop=False)
while v.playing:
time.sleep(0.1)
```
An mp3 decoder (note that this needs `audiocore.get_buffer`, not
enabled on devices):
```py
import sys
import audiomp3
import audiocore
GET_BUFFER_DONE, GET_BUFFER_MORE_DATA, GET_BUFFER_ERROR = range(3)
with audiomp3.MP3Decoder(sys.argv[1]) as decoder, open(sys.argv[2], "wb") as target:
while True:
res, samples = audiocore.get_buffer(decoder)
if res != GET_BUFFER_ERROR:
target.write(samples)
if res != GET_BUFFER_MORE_DATA:
break
```
this doesn't actually add any tests though
This can sort-of play MP3s from a http request, but the buffering is
not good enough to play glitch-free. A new kind of buffer that can
read ahead further without blocking is needed.
This runs in the Renode simulator and enables easier tracing and
debugging of the CircuitPython core. This port can also serve as
a starting point for new ports because it implements the minimal
necessary for the CP core to run.
It is enabled on:
* SAMD51 boards with 1MB flash (SKU ending in 20A)
* Feather RP2040 DVI. Others have PIO usb host.
* All ESP32 boards.
* All nRF boards
Fixes#8676
If there's only one device on the bus, the chip_select pin of the
peripheral can be fixed in hardware, therefore lowering the number of
pins required on the microcontroller side.
This patch allows this by making the chip_select pin optional.