182 lines
6.2 KiB
Python
182 lines
6.2 KiB
Python
import pyaudio
|
||
import wave
|
||
import numpy as np
|
||
import time
|
||
from pydub import AudioSegment
|
||
|
||
# 录音参数
|
||
FORMAT = pyaudio.paFloat32 # 32-bit 浮点格式
|
||
CHANNELS = 1
|
||
RATE = 44100
|
||
CHUNK = 1024
|
||
OUTPUT_FILENAME = "output.wav"
|
||
THRESHOLD = 0.008 # 适配 paFloat32(范围是 [-1.0, 1.0])
|
||
SILENCE_DURATION = 2 # 静音时间(秒)
|
||
|
||
# **🔍 列出所有可用设备并解释用途**
|
||
def list_audio_devices():
|
||
audio = pyaudio.PyAudio()
|
||
device_info = []
|
||
|
||
print("\n🎤 **可用的录音设备列表**\n")
|
||
print(f"{'ID':<5}{'设备名称':<35}{'输入通道数':<15}{'设备类型'}")
|
||
print("="*80)
|
||
|
||
for i in range(audio.get_device_count()):
|
||
dev = audio.get_device_info_by_index(i)
|
||
name = dev['name'].lower()
|
||
channels = dev['maxInputChannels']
|
||
|
||
if channels > 0:
|
||
# 设备类型判断
|
||
if "stereo mix" in name or "what you hear" in name:
|
||
device_type = "✅ 立体声混音(推荐)"
|
||
elif "loopback" in name:
|
||
device_type = "🔄 环回录音(可选)"
|
||
elif "mic" in name or "microphone" in name:
|
||
device_type = "🎤 麦克风(不推荐)"
|
||
else:
|
||
device_type = "🎧 其他音频设备"
|
||
|
||
print(f"{i:<5}{dev['name']:<35}{channels:<15}{device_type}")
|
||
device_info.append((i, dev['name'], device_type))
|
||
|
||
audio.terminate()
|
||
print("\n✅ **如果 '立体声混音' 存在,优先使用它**,否则尝试 '环回' 或手动选择。\n")
|
||
return device_info
|
||
|
||
# **🔍 自动选择最佳录音设备**
|
||
def get_best_device():
|
||
audio = pyaudio.PyAudio()
|
||
best_device = None
|
||
fallback_device = None
|
||
|
||
print("\n🔍 **正在尝试自动选择最佳录音设备...**")
|
||
|
||
for i in range(audio.get_device_count()):
|
||
dev = audio.get_device_info_by_index(i)
|
||
name = dev['name'].lower()
|
||
channels = dev['maxInputChannels']
|
||
|
||
if channels > 0:
|
||
# **优先选择“立体声混音”**
|
||
if "stereo mix" in name or "what you hear" in name:
|
||
print(f"✅ 选择设备: {dev['name']} (ID: {i}) - 立体声混音(最佳)")
|
||
best_device = i
|
||
break
|
||
|
||
# **其次选择“环回(loopback)”**
|
||
if "loopback" in name and best_device is None:
|
||
print(f"🔄 选择设备: {dev['name']} (ID: {i}) - 环回录音")
|
||
best_device = i
|
||
|
||
# **如果没有立体声混音或环回,选择一个非麦克风的可用设备**
|
||
if "mic" not in name and "microphone" not in name and fallback_device is None:
|
||
fallback_device = i
|
||
|
||
audio.terminate()
|
||
|
||
if best_device is not None:
|
||
return best_device
|
||
elif fallback_device is not None:
|
||
print(f"⚠️ 没有找到 '立体声混音',使用默认设备 (ID: {fallback_device})")
|
||
return fallback_device
|
||
else:
|
||
print("\n❌ 没有检测到合适的设备,请手动选择一个设备 ID:")
|
||
device_list = list_audio_devices()
|
||
selected_device = input("🔹 请输入你要使用的设备 ID(数字): ")
|
||
try:
|
||
selected_device = int(selected_device)
|
||
return selected_device
|
||
except ValueError:
|
||
raise RuntimeError("❌ 设备 ID 无效,程序终止!")
|
||
|
||
# **🎤 运行设备列表**
|
||
device_list = list_audio_devices()
|
||
|
||
# **🔧 选择最佳设备**
|
||
device_index = get_best_device()
|
||
|
||
# 初始化 PyAudio
|
||
audio = pyaudio.PyAudio()
|
||
try:
|
||
if device_index is not None:
|
||
stream = audio.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True,
|
||
input_device_index=device_index,
|
||
frames_per_buffer=CHUNK)
|
||
else:
|
||
stream = audio.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True,
|
||
frames_per_buffer=CHUNK)
|
||
|
||
print("🎤 只录制系统声音,麦克风已禁用!")
|
||
print("🎤 等待声音触发录音...")
|
||
|
||
# 等待声音触发
|
||
while True:
|
||
data = stream.read(CHUNK, exception_on_overflow=False)
|
||
audio_data = np.frombuffer(data, dtype=np.float32) # 读取 32-bit 浮点数据
|
||
volume = np.max(np.abs(audio_data)) # 计算音量
|
||
|
||
if volume > THRESHOLD:
|
||
print("🎙 检测到声音,开始录音...")
|
||
break
|
||
|
||
frames = [data]
|
||
silent_start = None
|
||
|
||
# 开始录音,直到检测到 2 秒以上静音
|
||
while True:
|
||
data = stream.read(CHUNK, exception_on_overflow=False)
|
||
audio_data = np.frombuffer(data, dtype=np.float32)
|
||
|
||
# **放大音量**
|
||
volume_boost = 5.0 # 放大 5 倍
|
||
audio_data = np.clip(audio_data * volume_boost, -1.0, 1.0) # 避免溢出
|
||
frames.append(audio_data.tobytes())
|
||
|
||
volume = np.max(np.abs(audio_data)) # 计算当前音量
|
||
|
||
if volume < THRESHOLD:
|
||
if silent_start is None:
|
||
silent_start = time.time()
|
||
elif time.time() - silent_start >= SILENCE_DURATION:
|
||
print("🤫 检测到静音超过 2 秒,停止录音...")
|
||
break
|
||
else:
|
||
silent_start = None
|
||
|
||
print("🎼 录音结束,正在保存文件...")
|
||
|
||
# 关闭流
|
||
stream.stop_stream()
|
||
stream.close()
|
||
audio.terminate()
|
||
|
||
# **转换 `paFloat32` 录音数据为 `paInt16`(标准 WAV 格式)**
|
||
int_frames = []
|
||
for frame in frames:
|
||
float_data = np.frombuffer(frame, dtype=np.float32) # 读取浮点数据
|
||
int_data = np.int16(float_data * 32767) # 转换为 16-bit PCM
|
||
int_frames.append(int_data.tobytes())
|
||
|
||
# 保存为 WAV
|
||
wf = wave.open(OUTPUT_FILENAME, 'wb')
|
||
wf.setnchannels(CHANNELS)
|
||
wf.setsampwidth(2) # 16-bit PCM
|
||
wf.setframerate(RATE)
|
||
wf.writeframes(b''.join(int_frames))
|
||
wf.close()
|
||
|
||
print(f"✅ 录音已保存为 {OUTPUT_FILENAME}")
|
||
|
||
# **自动归一化音量**
|
||
audio = AudioSegment.from_wav(OUTPUT_FILENAME)
|
||
normalized_audio = audio.apply_gain(-audio.dBFS)
|
||
normalized_audio.export("output_loud.wav", format="wav")
|
||
|
||
print("✅ 录音音量已调整,保存为 output_loud.wav")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 录音失败: {e}")
|
||
audio.terminate()
|