PyAudio录制音频出现严重加速问题的解决求助
解决录制音频加速问题的方案
我写了个脚本自动录制鹦鹉对Spotify歌曲的反应,但录出来的音频严重加速。问题可能出在录制函数的内层while循环或音频保存部分,以下是有问题的代码:
import os, spotipy, json, datetime from spotipy.oauth2 import SpotifyOAuth from dotenv import load_dotenv # Audio import pyaudio, wave # Database import mysql.connector rate = 48000 def save_audio(audio, frames, song): # Absolute path to audio_files directory audio_file_path = os.path.abspath("audio_files") if os.path.exists(os.path.join(audio_file_path, song + ".wav")): # Check current largest number curr_largest_num = 1 while True: if os.path.exists(f"{os.path.join(audio_file_path, song)} ({curr_largest_num}).wav"): curr_largest_num += 1 else: break # Save file with that number + 1 with wave.open(f"{os.path.join(audio_file_path, song)} ({curr_largest_num}).wav", "wb") as waveFile: waveFile.setnchannels(2) waveFile.setsampwidth(audio.get_sample_size(pyaudio.paInt16)) waveFile.setframerate(rate) waveFile.writeframes(b''.join(frames)) else: with wave.open(os.path.join(audio_file_path, song + ".wav"), "wb") as waveFile: waveFile.setnchannels(2) waveFile.setsampwidth(audio.get_sample_size(pyaudio.paInt16)) waveFile.setframerate(rate) waveFile.writeframes(b''.join(frames)) def record(): try: load_dotenv() PASSWORD = os.getenv("PASSWORD") # Connect to the database mydb = mysql.connector.connect( host = "localhost", user = "root", passwd = PASSWORD, database = "parrot_music_taste" ) mycursor = mydb.cursor() # Spotify client_id = os.getenv("CLIENT_ID") client_secret = os.getenv("CLIENT_SECRET") redirect_uri = "http://localhost:8888/callback" scope = 'user-read-currently-playing' sp = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, scope=scope)) curr_playing = sp.current_user_playing_track() # Wait for Spotify to play the song if(not curr_playing["is_playing"]): print("waiting to play song") while not curr_playing["is_playing"]: curr_playing = sp.current_user_playing_track() print("setup done") # This loop records all songs while True: audio = pyaudio.PyAudio() stream = audio.open(format=pyaudio.paInt16, channels=2, rate=rate, input=True, input_device_index = 2, frames_per_buffer=1024) frames = [] song = sp.current_user_playing_track()["item"]["name"] # This loop records one song while True: # Record data = stream.read(1024) frames.append(data) # Check if the song changed curr_playing = sp.current_user_playing_track() if curr_playing["item"]["name"] != song: print("song changed") stream.stop_stream() stream.close() audio.terminate() audio_file_path = os.path.abspath("audio_files") with wave.open(os.path.join(audio_file_path, song + ".wav"), "wb") as waveFile: waveFile.setnchannels(2) waveFile.setsampwidth(audio.get_sample_size(pyaudio.paInt16)) waveFile.setframerate(rate) waveFile.writeframes(b''.join(frames)) break song = curr_playing["item"]["name"] except KeyboardInterrupt: print("KeyboardInterrupt") record()
另外有一段代码能正常运行,我搞不清问题出在哪:
import pyaudio, wave, os audio_file_path = os.path.abspath("audio_files") p = pyaudio.PyAudio() stream = p.open(format=pyaudio.paInt16, channels=1, rate=44100, input=True, input_device_index = 2, frames_per_buffer=1024) print("recording") frames = [] try: while True: data = stream.read(1024) frames.append(data) except KeyboardInterrupt: print("KeyboardInterrupt") stream.stop_stream() stream.close() p.terminate() with wave.open(f"{os.path.join(audio_file_path, 'recording')}.wav", "wb") as waveFile: waveFile.setnchannels(1) waveFile.setsampwidth(p.get_sample_size(pyaudio.paInt16)) waveFile.setframerate(44100) waveFile.writeframes(b''.join(frames)) frames.clear()
问题根源
- 采样率不匹配:问题代码用了48000Hz采样率,但正常代码是44100Hz,你的音频输入设备大概率默认工作在44100Hz,强行设置更高采样率会导致播放时速度被拉高。
- 频繁API调用阻塞录制:内层循环每读取一帧音频就调用Spotify API检查歌曲切换,网络请求的延迟会打断音频采样的连续性,导致帧数据异常,最终播放加速。
- 重复初始化音频设备:外层循环每次录制新歌曲都重新创建PyAudio和stream对象,频繁初始化设备会导致资源冲突,干扰采样稳定性。
修复步骤
- 统一采样率:把问题代码中的
rate = 48000改成rate = 44100,和正常代码以及设备默认采样率保持一致。 - 降低API调用频率:不要每帧都检查歌曲,设置1秒左右的间隔,用时间戳控制调用时机,避免阻塞音频读取。
- 只初始化一次音频设备:把PyAudio和stream的创建移到外层循环外,全程复用同一个设备连接,减少资源冲突。
- 复用已有的保存函数:内层循环里直接写了保存逻辑,改成调用你已经写好的
save_audio函数,避免代码重复和潜在错误。
修改后的完整代码
import os, spotipy, time from spotipy.oauth2 import SpotifyOAuth from dotenv import load_dotenv import pyaudio, wave import mysql.connector rate = 44100 # 统一采样率为44100Hz def save_audio(audio, frames, song): audio_file_path = os.path.abspath("audio_files") # 确保音频目录存在 if not os.path.exists(audio_file_path): os.makedirs(audio_file_path) base_file = os.path.join(audio_file_path, song) # 处理重复文件名 if os.path.exists(f"{base_file}.wav"): curr_num = 1 while os.path.exists(f"{base_file} ({curr_num}).wav"): curr_num += 1 save_path = f"{base_file} ({curr_num}).wav" else: save_path = f"{base_file}.wav" # 保存音频文件 with wave.open(save_path, "wb") as waveFile: waveFile.setnchannels(2) waveFile.setsampwidth(audio.get_sample_size(pyaudio.paInt16)) waveFile.setframerate(rate) waveFile.writeframes(b''.join(frames)) def record(): try: load_dotenv() PASSWORD = os.getenv("PASSWORD") # 数据库连接(如果暂时用不上可以注释,减少资源消耗) # mydb = mysql.connector.connect( # host = "localhost", # user = "root", # passwd = PASSWORD, # database = "parrot_music_taste" # ) # mycursor = mydb.cursor() # 初始化Spotify客户端 client_id = os.getenv("CLIENT_ID") client_secret = os.getenv("CLIENT_SECRET") redirect_uri = "http://localhost:8888/callback" scope = 'user-read-currently-playing' sp = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, scope=scope)) # 等待Spotify开始播放歌曲 curr_playing = sp.current_user_playing_track() while not curr_playing or not curr_playing["is_playing"]: print("等待歌曲播放...") time.sleep(1) curr_playing = sp.current_user_playing_track() print("准备就绪,开始录制") # 只初始化一次音频设备和流 audio = pyaudio.PyAudio() stream = audio.open(format=pyaudio.paInt16, channels=2, rate=rate, input=True, input_device_index=2, frames_per_buffer=1024) last_check_time = time.time() current_song = curr_playing["item"]["name"] frames = [] while True: # 读取音频帧 data = stream.read(1024) frames.append(data) # 每隔1秒检查一次歌曲是否切换 now = time.time() if now - last_check_time >= 1: last_check_time = now curr_playing = sp.current_user_playing_track() # 处理歌曲停止的情况 if not curr_playing or not curr_playing["is_playing"]: print("歌曲停止播放") break # 处理歌曲切换的情况 new_song = curr_playing["item"]["name"] if new_song != current_song: print(f"歌曲切换:{current_song} → {new_song}") # 保存当前歌曲的录音 save_audio(audio, frames, current_song) frames = [] # 清空帧列表准备录制下一首 current_song = new_song except KeyboardInterrupt: print("\n录制中断,正在保存当前音频...") # 保存中断前的音频 if 'frames' in locals() and len(frames) > 0: save_audio(audio, frames, current_song) finally: # 确保释放所有资源 if 'stream' in locals(): stream.stop_stream() stream.close() if 'audio' in locals(): audio.terminate() # if 'mydb' in locals(): # mydb.close() record()
内容的提问来源于stack exchange,提问作者Socks
相关产品推荐
相关产品推荐

