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

pyAudio循环播放UDP音频流时持续出现pop爆音问题求助

  • 未做修改的原始代码来自公开参考教程,原始代码本身也存在相同的爆音问题

大家好🐱

我在播放时听到爆音(pop sound) - 爆音出现频率是多少? - 全程持续出现

问题根因

爆音来自音频输出缓冲下溢(underrun):当前代码把UDP收包和音频流写操作放在同一个线程,sock.recvfrom()是阻塞调用,只要网络抖动导致UDP包延迟到达,pyAudio的输出流就会因为无数据可播出现短暂断流,反复断流恢复就会产生全程持续的咔嗒爆音。最终保存的wav文件是将所有收到的完整数据按顺序拼接写入,不存在数据断档,因此不会出现这类异常声响。

原代码还存在一处资源释放错误:stream.terminate()写法有误,terminate()是PyAudio实例的方法,不属于stream对象,运行到退出逻辑时会抛出异常。

修复方案

不需要用多进程,用内置队列做缓冲+单独的收包线程即可解决,逻辑比多进程简单很多,也不存在跨进程变量通信的问题:

  1. 开一个后台守护线程专门负责接收UDP音频包,收到的数据全部存入先进先出队列,和播放逻辑完全解耦
  2. 播放主循环持续从队列取数据写入音频流,如果队列暂时为空、数据还没到,就写入对应长度的静音填充,保证音频流永远不会因为缺数据断流
  3. 打开音频流时显式指定frames_per_buffer参数,和UDP收包的CHUNK大小对齐,减少缓冲不匹配导致的异常
  4. 修正资源释放的逻辑错误

修复后的可运行代码如下:

import pyaudio
import socket
import wave
from datetime import datetime
import threading
from queue import Queue

# 基础配置
UDP_IP = "0.0.0.0"
UDP_PORT = 1234
FORMAT = pyaudio.paInt32  # 显式指定格式,避免直接写魔法值32出兼容问题
CHANNELS = 1
RATE = 8000
CHUNK = 1024
SILENCE = b'\x00' * CHUNK  # 单块静音数据,缺数据时填充用
BUFFER_MAX_SIZE = 20  # 队列最大缓冲音频块数量,避免延迟过高

# 初始化UDP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))
sock.settimeout(0.1)  # 收包超时设为0.1秒,避免线程卡死

# 初始化音频缓冲队列
audio_queue = Queue(maxsize=BUFFER_MAX_SIZE)
frames = []

def udp_receive_thread():
    """后台线程:持续接收UDP包存入队列"""
    while True:
        try:
            data, addr = sock.recvfrom(CHUNK)
            # 队列满了就丢弃最老的音频块,避免播放延迟累积
            if audio_queue.full():
                try:
                    audio_queue.get_nowait()
                except:
                    pass
            audio_queue.put(data)
        except socket.timeout:
            continue

# 启动后台收包线程
recv_thread = threading.Thread(target=udp_receive_thread, daemon=True)
recv_thread.start()

# 初始化音频输出流
audio = pyaudio.PyAudio()
stream = audio.open(
    format=FORMAT,
    channels=CHANNELS,
    rate=RATE,
    output=True,
    frames_per_buffer=CHUNK  # 显式指定缓冲块大小,和收包块对齐
)
print("Listening", UDP_IP)

def save_wav():
    print("saving...")
    WAVE_OUTPUT_FILENAME = datetime.now().strftime("%Y%m%d-%H%M%S")
    waveFile = wave.open(WAVE_OUTPUT_FILENAME + '.wav', 'wb')
    waveFile.setnchannels(CHANNELS)
    waveFile.setsampwidth(audio.get_sample_size(FORMAT))
    waveFile.setframerate(RATE)
    waveFile.writeframes(b''.join(frames))
    waveFile.close()

try:
    while True:
        # 队列有数据就播放真实音频,无数据就填充静音保证流不中断
        if not audio_queue.empty():
            play_data = audio_queue.get()
            frames.append(play_data)
        else:
            play_data = SILENCE
        stream.write(play_data, exception_on_underflow=False)  # 关闭下溢抛错,增加运行容错
except KeyboardInterrupt:
    print("KeyboardInterrupt Ctrl+C")
    save_wav()
    stream.stop_stream()
    stream.close()
    audio.terminate()  # 修正原代码的资源释放错误,调用PyAudio实例的terminate方法
    sock.close()

调优说明

如果运行后还是有轻微爆音,可以适当调大BUFFER_MAX_SIZE的值增加缓冲长度,代价是播放延迟会略微升高,一般把值设在10-30区间,就能在低延迟和无爆音之间取得不错的平衡。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 16:27:47