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

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()

问题根源

  1. 采样率不匹配:问题代码用了48000Hz采样率,但正常代码是44100Hz,你的音频输入设备大概率默认工作在44100Hz,强行设置更高采样率会导致播放时速度被拉高。
  2. 频繁API调用阻塞录制:内层循环每读取一帧音频就调用Spotify API检查歌曲切换,网络请求的延迟会打断音频采样的连续性,导致帧数据异常,最终播放加速。
  3. 重复初始化音频设备:外层循环每次录制新歌曲都重新创建PyAudio和stream对象,频繁初始化设备会导致资源冲突,干扰采样稳定性。

修复步骤

  1. 统一采样率:把问题代码中的rate = 48000改成rate = 44100,和正常代码以及设备默认采样率保持一致。
  2. 降低API调用频率:不要每帧都检查歌曲,设置1秒左右的间隔,用时间戳控制调用时机,避免阻塞音频读取。
  3. 只初始化一次音频设备:把PyAudio和stream的创建移到外层循环外,全程复用同一个设备连接,减少资源冲突。
  4. 复用已有的保存函数:内层循环里直接写了保存逻辑,改成调用你已经写好的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 13:37:00