Sounddevice
python">import timeimport sounddevice as sd
import numpy as np
from scipy.io.wavfile import writeSAMPLE_RATE = 16000 # 采样率(Hz)
DURATION = 5 # 录音时长(秒)def save():# 录音参数print("开始录音...")audio_data = sd.rec(int(SAMPLE_RATE * DURATION), samplerate=SAMPLE_RATE, channels=1, dtype=np.int16, device=0)sd.wait() # 等待录音结束print("录音完成!")# 保存到本地write("recorded_audio.wav", SAMPLE_RATE, audio_data)print("音频已保存为 recorded_audio.wav")def main():audio_data = []start_tiem = time.time()with sd.InputStream(samplerate=SAMPLE_RATE, channels=1, dtype=np.int16) as stream:while time.time() - start_tiem < DURATION:data, _ = stream.read(1024) # 每次读取 1024 帧audio_data.append(data)# 录音结束后保存音频if not audio_data:returnaudio_np = np.concatenate(audio_data, axis=0)write("recorded_audio.wav", SAMPLE_RATE, audio_np)print("音频已保存为 recorded_audio.wav")if __name__ == "__main__":save()# main()
第一种写法:
使用 sd.rec() 直接录制整个音频
sd.rec() 直接申请一个完整录音时长的 NumPy 数组,并将音频数据填充进去。
录音过程中,数据会自动存入 audio_data 数组,无需 while 循环和 append()。
录音过程中不会实时处理数据
sd.rec() 录音时,Python 代码会暂停执行,直到 sd.wait() 结束录音。
适用于无需实时处理的情况,比如普通语音录制。
计算开销低
直接使用 NumPy 数组存储数据,避免 append() 和 list → NumPy 转换,提高效率。
适用于短时录音(几秒到几十秒),不适合长时间录音(因为 sd.rec() 一次性分配内存)。
第二种写法:
使用 sd.InputStream() 逐块读取数据
通过 stream.read(1024) 每次读取 1024 帧(≈ 64ms),直到录音时间结束。
适用于实时处理音频数据(例如语音识别、音频波形分析)。
适用于长时间录音
由于数据是分块读取并存入 audio_data,不会一次性占用大量内存。
适合长时间录音(如 1 分钟、10 分钟甚至更长)。
计算开销较高
audio_data.append(data) 每次循环都会增加列表长度,最终需要 np.concatenate() 转换为 NumPy 数组,可能会增加内存碎片和计算开销。
适用于需要边录边处理的场景,而不是一次性录音。
Gradio控制
python">import gradio as gr
import sounddevice as sd
import numpy as np
from scipy.io.wavfile import write
import threading
import os
import time
import whisper
import noisereduce as nr # 导入噪声去除库# 录音参数
SAMPLE_RATE = 16000 # 采样率(Hz)
CHANNELS = 1 # 单声道
audio_data = [] # 存储音频数据
recording = False # 录音状态
denoise = False
audio_file_path = "recorded_audio.wav" # 音频文件路径
recording_thread = None # 录音线程# 加载 Whisper 模型
model = whisper.load_model("small") # 可以选择 "tiny", "base", "small", "medium", "large"def create_empty_audio():""" 如果音频文件不存在,则创建一个 1 秒的静音 WAV 文件 """if not os.path.exists(audio_file_path):print("🔍 'recorded_audio.wav' 不存在,创建空白音频...")silent_audio = np.zeros(SAMPLE_RATE, dtype=np.int16) # 生成 1 秒的静音write(audio_file_path, SAMPLE_RATE, silent_audio)def record_audio():""" 录音线程函数 """global recording, audio_data, audio_file_pathaudio_data = []with sd.InputStream(samplerate=SAMPLE_RATE, channels=CHANNELS, dtype=np.int16) as stream:while recording:data, _ = stream.read(1024) # 每次读取 1024 帧audio_data.append(data)# 录音结束后保存音频if audio_data:audio_np = np.concatenate(audio_data, axis=0)if denoise:# 使用 noisereduce 去噪print("🎧 正在去除背景噪声...")audio_np = nr.reduce_noise(y=audio_np, sr=SAMPLE_RATE) # 去噪处理write(audio_file_path, SAMPLE_RATE, audio_np) # 保存去噪后的音频文件def transcribe_audio(file_path):""" 使用 Whisper 模型进行语音识别 """prompt = "如果使用了中文,请使用简体中文来表示文本内容"result = model.transcribe(file_path, initial_prompt=prompt) # 传入录音文件进行识别return result['text'] # 返回识别结果def toggle_recording():""" 控制录音开始/停止,并在结束后刷新音频文件 """global recording, recording_threadif not recording:recording = Truerecording_thread = threading.Thread(target=record_audio, daemon=True)recording_thread.start()return "录音中... 点击停止", None, "" # 录音时不更新音频和识别文本else:recording = Falseif recording_thread:recording_thread.join() # ✅ 等待录音线程完成,确保音频已写入time.sleep(0.2) # ✅ 额外等待 200ms,确保文件完全写入transcription = transcribe_audio(audio_file_path) # 获取识别结果return "开始录音", audio_file_path, transcription # 返回文件路径和识别结果# 启动时检查音频文件是否存在
create_empty_audio()# Gradio UI
with gr.Blocks() as app:gr.Markdown("## 🎙️ 语音录制与识别")# 开始/停止录音按钮record_btn = gr.Button("开始录音")audio_player = gr.Audio(audio_file_path, label="最新录音", interactive=False)transcription_output = gr.Textbox(label="识别结果", interactive=False) # 显示识别结果record_btn.click(toggle_recording, outputs=[record_btn, audio_player, transcription_output]) # 录音结束后自动更新音频和显示识别结果# 运行 Gradio
app.launch()