pyAudio循环播放UDP音频流时持续出现pop爆音问题求助
- 未做修改的原始代码来自公开参考教程,原始代码本身也存在相同的爆音问题
大家好🐱
我在播放时听到爆音(pop sound) - 爆音出现频率是多少? - 全程持续出现
问题根因
爆音来自音频输出缓冲下溢(underrun):当前代码把UDP收包和音频流写操作放在同一个线程,sock.recvfrom()是阻塞调用,只要网络抖动导致UDP包延迟到达,pyAudio的输出流就会因为无数据可播出现短暂断流,反复断流恢复就会产生全程持续的咔嗒爆音。最终保存的wav文件是将所有收到的完整数据按顺序拼接写入,不存在数据断档,因此不会出现这类异常声响。
原代码还存在一处资源释放错误:stream.terminate()写法有误,terminate()是PyAudio实例的方法,不属于stream对象,运行到退出逻辑时会抛出异常。
修复方案
不需要用多进程,用内置队列做缓冲+单独的收包线程即可解决,逻辑比多进程简单很多,也不存在跨进程变量通信的问题:
- 开一个后台守护线程专门负责接收UDP音频包,收到的数据全部存入先进先出队列,和播放逻辑完全解耦
- 播放主循环持续从队列取数据写入音频流,如果队列暂时为空、数据还没到,就写入对应长度的静音填充,保证音频流永远不会因为缺数据断流
- 打开音频流时显式指定
frames_per_buffer参数,和UDP收包的CHUNK大小对齐,减少缓冲不匹配导致的异常 - 修正资源释放的逻辑错误
修复后的可运行代码如下:
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
相关产品推荐
相关产品推荐

