You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Raspberry Pi Pico W上用CircuitPython流式播放URL中的MP3音频

Raspberry Pi Pico W MP3音频流式播放改造方案

原代码需等待MP3文件完全下载后才能播放,针对这个问题,我们可以通过内存缓冲区+自定义数据流对象实现边下载边播放的流式效果,无需等待完整下载。

修改后的完整代码

import adafruit_requests
import wifi
import socketpool
import ssl
import board
import audiomp3
import audiobusio
import os
from collections import deque

# 初始化I2S音频输出
audio = audiobusio.I2SOut(board.GP10, board.GP11, board.GP9)

MP3_URL = "http://codeskulptor-demos.commondatastorage.googleapis.com/descent/background%20music.mp3"
CHUNK_SIZE = 1024
# 预加载缓冲区大小,确保有足够数据启动播放
PRELOAD_SIZE = 8 * CHUNK_SIZE
# 播放时的缓冲区上限,避免占用过多内存
BUFFER_LIMIT = 32 * CHUNK_SIZE

# 连接WiFi
wifi.radio.connect(os.getenv('WIFI_SSID'), os.getenv('WIFI_PASSWORD'))
pool = socketpool.SocketPool(wifi.radio)
requests = adafruit_requests.Session(pool, ssl.create_default_context())

# 自定义数据流类,模拟文件对象供MP3解码器读取
class StreamBuffer:
    def __init__(self):
        self.buffer = deque()
        self.total_received = 0
        self.download_complete = False

    def write(self, data):
        # 控制缓冲区大小,防止内存溢出
        while sum(len(chunk) for chunk in self.buffer) > BUFFER_LIMIT:
            pass
        self.buffer.append(data)
        self.total_received += len(data)

    def read(self, size=-1):
        # 读取指定大小的数据,缓冲区不足时等待(直到下载完成)
        result = b""
        while len(result) < size or size == -1:
            if not self.buffer:
                if self.download_complete:
                    break
                continue
            chunk = self.buffer.popleft()
            if size == -1:
                result += chunk
            else:
                take = min(size - len(result), len(chunk))
                result += chunk[:take]
                if take < len(chunk):
                    self.buffer.appendleft(chunk[take:])
        return result

try:
    # 初始化缓冲区
    stream_buffer = StreamBuffer()
    
    # 发送GET请求,开启流式响应
    response = requests.get(MP3_URL, stream=True)
    
    # 预加载数据,确保有足够内容启动播放
    print("预加载音频数据...")
    preloaded = 0
    for chunk in response.iter_content(chunk_size=CHUNK_SIZE):
        if chunk:
            stream_buffer.write(chunk)
            preloaded += len(chunk)
            if preloaded >= PRELOAD_SIZE:
                break
    
    # 初始化MP3解码器,传入自定义数据流对象
    mp3_decoder = audiomp3.MP3Decoder(stream_buffer)
    # 根据音频实际采样率调整,保持原代码设置
    mp3_decoder.sample_rate = 24000
    
    # 开始播放
    print("开始播放...")
    audio.play(mp3_decoder)
    
    # 后台继续下载剩余数据到缓冲区
    for chunk in response.iter_content(chunk_size=CHUNK_SIZE):
        if chunk:
            stream_buffer.write(chunk)
        # 检查播放状态,播放结束则停止下载
        if not audio.playing:
            break
    
    # 标记下载完成,确保解码器能读完剩余数据
    stream_buffer.download_complete = True
    
    # 等待播放结束
    while audio.playing:
        pass
    
    print("播放完成")

except Exception as e:
    print("播放或下载出错:\n", str(e))

关键改动说明

  • 自定义StreamBuffer类:模拟文件的read和write方法,将网络下载的音频块存入双端队列缓冲区,供MP3解码器读取,实现数据流的衔接。
  • 预加载机制:先下载一部分数据(8个块),确保解码器有足够的初始数据启动播放,避免播放卡顿。
  • 缓冲区大小控制:设置缓冲区上限,防止过多数据占用Pico W有限的内存。
  • 流式响应处理:使用stream=True开启请求的流式响应,边下载边将数据写入缓冲区,同时解码器从缓冲区读取数据播放,实现边下边播。

内容的提问来源于stack exchange,提问作者Dukeofduke

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 21:37:46