|
|
import time
|
|
|
import wave
|
|
|
from pathlib import Path
|
|
|
from threading import Thread
|
|
|
|
|
|
from pyaudio import PyAudio, paInt16, paContinue, paComplete
|
|
|
|
|
|
|
|
|
class AudioRecord(PyAudio):
|
|
|
|
|
|
def __init__(self, channels=2):
|
|
|
super().__init__()
|
|
|
self.chunk = 1024 # 每个缓冲区的帧数
|
|
|
self.format_sample = paInt16 # 采样位数
|
|
|
self.channels = channels # 声道: 1,单声道;2,双声道
|
|
|
self.fps = 44100 # 采样频率
|
|
|
self.input_dict = None
|
|
|
self.output_dict = None
|
|
|
self.stream = None
|
|
|
self.filename = '~test.wav'
|
|
|
self.wf = None
|
|
|
self.stop_flag = False
|
|
|
self.kill = False
|
|
|
|
|
|
def callback_input(self, in_data, frame_count, time_info, status):
|
|
|
"""录制回调函数"""
|
|
|
self.wf.writeframes(in_data)
|
|
|
if not self.stop_flag:
|
|
|
return (in_data, paContinue)
|
|
|
else:
|
|
|
return (in_data, paComplete)
|
|
|
|
|
|
def callback_output(self, in_data, frame_count, time_info, status):
|
|
|
"""播放回调函数"""
|
|
|
data = self.wf.readframes(frame_count)
|
|
|
return (data, paContinue)
|
|
|
|
|
|
def open_stream(self, name):
|
|
|
"""打开录制流"""
|
|
|
input_device_index = self.get_device_index(name, True) if name else None
|
|
|
return self.open(format=self.format_sample,
|
|
|
channels=self.channels,
|
|
|
rate=self.fps,
|
|
|
frames_per_buffer=self.chunk,
|
|
|
input=True,
|
|
|
input_device_index=input_device_index,
|
|
|
stream_callback=self.callback_input
|
|
|
)
|
|
|
|
|
|
def audio_record_run(self, name=None):
|
|
|
"""音频录制"""
|
|
|
self.wf = self.save_audio_file(self.filename)
|
|
|
self.stream = self.open_stream(name)
|
|
|
self.stream.start_stream()
|
|
|
while self.stream.is_active():
|
|
|
time.sleep(0.1)
|
|
|
self.wf.close()
|
|
|
if self.kill:
|
|
|
Path(self.filename).unlink()
|
|
|
self.duration = self.get_duration()
|
|
|
self.terminate_run()
|
|
|
|
|
|
def run(self, filename=None, name=None, record=True):
|
|
|
"""音频录制启动"""
|
|
|
if record:
|
|
|
if filename:
|
|
|
self.filename = filename
|
|
|
thread_1 = Thread(target=self.audio_record_run, args=(name,))
|
|
|
else:
|
|
|
if not filename:
|
|
|
raise Exception('未输入音频文件名,不能播放,请输入后再试!')
|
|
|
thread_1 = Thread(target=self.read_audio, args=(filename, name,))
|
|
|
thread_1.start()
|
|
|
|
|
|
def read_audio(self, filename, name=None):
|
|
|
"""音频播放"""
|
|
|
output_device_index = self.get_device_index(name, False) if name else None
|
|
|
with wave.open(filename, 'rb') as self.wf:
|
|
|
# 获取音频长度
|
|
|
self.duration = self.get_duration()
|
|
|
self.stream = self.open(format=self.get_format_from_width(self.wf.getsampwidth()),
|
|
|
channels=self.wf.getnchannels(),
|
|
|
rate=self.wf.getframerate(),
|
|
|
output=True,
|
|
|
output_device_index=output_device_index,
|
|
|
stream_callback=self.callback_output
|
|
|
)
|
|
|
self.stream.start_stream()
|
|
|
while self.stream.is_active():
|
|
|
time.sleep(0.1)
|
|
|
print(self.duration)
|
|
|
self.terminate_run()
|
|
|
|
|
|
def get_duration(self):
|
|
|
"""获取音频时长"""
|
|
|
return round(self.wf.getnframes() / self.wf.getframerate(), 2)
|
|
|
|
|
|
def get_in_out_devices(self):
|
|
|
"""获取系统输入输出设备"""
|
|
|
self.input_dict = {}
|
|
|
self.output_dict = {}
|
|
|
for i in range(self.get_device_count()):
|
|
|
devinfo = self.get_device_info_by_index(i)
|
|
|
if not devinfo['hostApi'] and int(devinfo['defaultSampleRate']) == self.fps \
|
|
|
and '映射器' not in devinfo['name']:
|
|
|
if devinfo['maxInputChannels']:
|
|
|
self.input_dict[devinfo['name'].split(' ')[0]] = i
|
|
|
elif devinfo['maxOutputChannels']:
|
|
|
self.output_dict[devinfo['name'].split(' ')[0]] = i
|
|
|
|
|
|
def get_device_index(self, name, inp=True):
|
|
|
"""获取选定设备索引"""
|
|
|
if inp and self.input_dict:
|
|
|
return self.input_dict.get(name, -1)
|
|
|
elif not inp and self.output_dict:
|
|
|
return self.output_dict.get(name, -1)
|
|
|
|
|
|
def save_audio_file(self, filename):
|
|
|
"""音频文件保存"""
|
|
|
wf = wave.open(filename, 'wb')
|
|
|
wf.setnchannels(self.channels)
|
|
|
wf.setsampwidth(self.get_sample_size(self.format_sample))
|
|
|
wf.setframerate(self.fps)
|
|
|
return wf
|
|
|
|
|
|
def terminate_run(self):
|
|
|
"""结束流录制或流播放"""
|
|
|
if self.stream is not None:
|
|
|
self.stream.stop_stream()
|
|
|
self.stream.close()
|
|
|
self.stream = None
|
|
|
self.wf = None
|
|
|
self.terminate()
|