microbit-foundation / micropython-microbit-v2

Temporary home for MicroPython for micro:bit v2 as we stablise it before pushing upstream
MIT License
41 stars 22 forks source link

Effective way to get data in and out of an AudioFrame #194

Open microbit-carlos opened 2 months ago

microbit-carlos commented 2 months ago

This is mostly an issue to discuss what is the most effective way to get data into and from an AudioFrame and if anything needs to be implemented to improve this.

For uses cases where we want to send and receive microphone recordings, one of the approaches would be to record a few seconds of audio into an AudioFrame, break it down into smaller chunks to be sent, and then on the other side we need to stitch them back together.

When trying a to build a few examples with radio we tried a few different methods and had some struggles, so these are some of the notes from that.

Options for breaking down a larger AudioFrame into smaller chunks

So my suggestion would be to go from something like:

my_audio = microphone.record(duration=AUDIO_DURATION)
audio_mv = memoryview(my_audio)
for i in range(0, len(my_audio), PACKET_SIZE):
    radio.send_bytes(my_audio[i:i+PACKET_SIZE])

To skip the memory view and be able to use slices directly (https://github.com/microbit-foundation/micropython-microbit-v2/issues/188):

my_audio = microphone.record(duration=AUDIO_DURATION)
for i in range(0, len(my_audio), PACKET_SIZE):
    radio.send_bytes(my_audio[i:i+PACKET_SIZE])

It's a small change, but I think it can help avoid users converting to a bytes object instead:

my_audio = microphone.record(duration=AUDIO_DURATION)
audio_bytes = bytes(my_audio)
for i in range(0, len(audio_bytes), PACKET_SIZE):
    radio.send_bytes(audio_bytes[i:i+PACKET_SIZE])

Options for combining smaller chunks into a larger AudioFrame

So my suggestion would be to go from something like:

my_audio = audio.AudioFrame(duration=AUDIO_DURATION)
audio_bytes = bytearray()
packets_received = 0
while packets_received < TOTAL_PACKETS:
    radio_data = radio.receive_bytes()
    if radio_data:
        audio_bytes.extend(radio_data)
        packets_received += 1
my_audio.copyfrom(audio_bytes)
audio.play(my_audio)

To something like this:

my_audio = audio.AudioFrame(duration=AUDIO_DURATION)
packets_received = 0
while packets_received < TOTAL_PACKETS:
    radio_data = radio.receive_bytes()
    if radio_data:
        my_audio.copyfrom(radio_data, packets_received * PACKET_SIZE)
        packets_received += 1
audio.play(my_audio)

It's only two lines but it could save us from expensive reallocations that will also do more memory fragmentation.

microbit-carlos commented 2 months ago

A lot of this functionality could be covered via: