按住按钮录制音频,释放时重播并覆盖上一个文件



我想在按下按钮时录制音频,并在松开按钮时重播。

它第一次运行良好,但当我再录制一次时,它会将新记录附加到现有记录上。我不知道如何替换这个文件而不是附加到它上面。我试着在开始录制时删除它,但它无法创建新的。基本上,我想覆盖文件,而不是将声音附加到文件中

from pynput import keyboard
import time
import pyaudio
import wave
import sched
from pygame import mixer
CHUNK = 8192
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"
p = pyaudio.PyAudio()
frames = []

def callback(in_data, frame_count, time_info, status):
frames.append(in_data)
return in_data, pyaudio.paContinue

class MyListener(keyboard.Listener):
def __init__(self):
super(MyListener, self).__init__(self.on_press, self.on_release)
self.key_pressed = None
self.wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
self.wf.setnchannels(CHANNELS)
self.wf.setsampwidth(p.get_sample_size(FORMAT))
self.wf.setframerate(RATE)
def on_press(self, key):
if key.char == 'r':
self.key_pressed = True
return True
def on_release(self, key):
if key.char == 'r':
self.key_pressed = False
return True

listener = MyListener()
listener.start()
started = False
stream = None

def recorder():
global started, p, stream, frames
if listener.key_pressed and not started:
# Start the recording
try:
stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=CHUNK,
stream_callback=callback)
print("Stream active:", stream.is_active())
started = True
print("start Stream")
except:
raise
elif not listener.key_pressed and started:
try:
started = False
print("Stop recording")
stream.stop_stream()
stream.close()
listener.wf.writeframes(b''.join(frames))
mixer.init()
mixer.music.load("output.wav")
mixer.music.set_volume(0.8)
mixer.music.play()
except:
raise
# Reschedule the recorder function in 100 ms.
task.enter(0.1, 1, recorder, ())

print("Press and hold the 'r' key to begin recording")
print("Release the 'r' key to end recording")
task = sched.scheduler(time.time, time.sleep)
task.enter(0.1, 1, recorder, ())
task.run()

我发现的一些问题:

  • 写入波形文件后不会关闭它
  • 写入之间不重置frames缓冲区
  • Pygame似乎正在锁定文件,因此第二次写入尝试失败。我不知道如何让Pygame解锁文件,所以我切换到了Playsound
from pynput import keyboard
import time
import pyaudio
import wave
import sched
from playsound import playsound
CHUNK = 8192
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"
p = pyaudio.PyAudio()
frames = []
def callback(in_data, frame_count, time_info, status):
frames.append(in_data)
return in_data, pyaudio.paContinue
class MyListener(keyboard.Listener):
def __init__(self):
super(MyListener, self).__init__(self.on_press, self.on_release)
self.key_pressed = None
def on_press(self, key):
if key.char == 'r':
self.key_pressed = True
return True
def on_release(self, key):
if key.char == 'r':
self.key_pressed = False
return True
listener = MyListener()
listener.start()
started = False
stream = None
def recorder():
global started, p, stream, frames
if listener.key_pressed and not started:
# Start the recording
try:
# Reset buffer
frames = []
stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=CHUNK,
stream_callback=callback)
print("Stream active:", stream.is_active())
started = True
print("start Stream")
except:
raise
elif not listener.key_pressed and started:
try:
started = False
print("Stop recording")
stream.stop_stream()
stream.close()
# Using with will close the file automatically
with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf:
wf.setnchannels(CHANNELS)
wf.setsampwidth(p.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(b''.join(frames))
# No issues with playsound locking the file
playsound(WAVE_OUTPUT_FILENAME)
except:
raise
# Reschedule the recorder function in 100 ms.
task.enter(0.1, 1, recorder, ())

print("Press and hold the 'r' key to begin recording")
print("Release the 'r' key to end recording")
task = sched.scheduler(time.time, time.sleep)
task.enter(0.1, 1, recorder, ())
task.run()

为了好玩,我把你的程序改为只使用回调,而不是不断轮询:

from pynput import keyboard
import pyaudio
import wave
from playsound import playsound
CHUNK = 8192
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"
p = pyaudio.PyAudio()
class recorder():
def __init__(self):
self.recording = False

def start(self):
if self.recording: return
try:
self.frames = []
self.stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=CHUNK,
stream_callback=self.callback)
print("Stream active:", self.stream.is_active())
print("start Stream")
self.recording = True
except:
raise
def stop(self):
if not self.recording: return
self.recording = False
print("Stop recording")
self.stream.stop_stream()
self.stream.close()
with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf:
wf.setnchannels(CHANNELS)
wf.setsampwidth(p.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(b''.join(self.frames))
playsound(WAVE_OUTPUT_FILENAME)
def callback(self, in_data, frame_count, time_info, status):
self.frames.append(in_data)
return in_data, pyaudio.paContinue
class MyListener(keyboard.Listener):
def __init__(self):
super(MyListener, self).__init__(self.on_press, self.on_release)
self.recorder = recorder();
def on_press(self, key):
if key.char == 'r':
self.recorder.start()
return True
def on_release(self, key):
if key.char == 'r':
self.recorder.stop();
return True
# Any other key ends the program
return False
print("Press and hold the 'r' key to begin recording")
print("Release the 'r' key to end recording")
# Collect events until released
with MyListener() as listener:
listener.join()

audiomath提供了一些高级类,可以帮助完成这样的任务。与pyaudio一样,它也封装在PortAudio周围,以允许录制和播放。下面是一个基于听力数学的简短列表。

我猜你的手指放在r按钮上的时间可能不会超过几秒钟。在这种情况下,在内存中预先分配几秒钟的空间(如果足够的话(并记录到内存中/从内存中回放会更有效。对于你想保留的声音,你总是可以在结尾说player.sound.Write('output.wav')。或者,您可以取消对保存每次尝试的行的注释。

如果出于任何原因,您确实希望在录制时流式打开到某个文件,您可以在构造函数中提供一个文件名,该文件名将传递给Recorder实例(驻留在内存中的self.buffer仍然存在:它是一个保留最后N秒的循环缓冲区(。不过,流式传输到文件需要外部ffmpeg安装。请注意,听力学的播放会将整个内容重新加载到内存中,因此没有什么能真正提高整体内存效率。。。

由于使用了ffmpeg,您还可以直接流式传输到除.wav之外的其他音频格式(如果需要的话(。

import time
import pynput
import audiomath as am  
class MyListener(pynput.keyboard.Listener):
def __init__(self, filename=None, buffer_length_seconds=10):
"""
Note:

If you specify a long enough `buffer_length_seconds` for your
purposes, then you do not need to record to file and so do not
need to specify `filename`.

If you specify `filename`, you need to have installed `ffmpeg`
(see the help for `audiomath.ffmeg.Install`).
"""
super(MyListener, self).__init__(self.on_press, self.on_release)
self.key_pressed = None

self.queue = []
self.filename = filename
self.buffer_length_seconds = buffer_length_seconds
self.new_recorder()

def new_recorder( self ):
self.buffer = am.Sound(self.buffer_length_seconds, nChannels=2, fs=44100)
self.recorder = am.Recorder(self.buffer, loop=True, recording=False, filename=self.filename)

def on_press(self, key):
try: key.char
except: return
if key.char == 'r' and not self.key_pressed:
print('recording...')
self.recorder.Record()
self.key_pressed = True
return True
def on_release(self, key):
try: key.char
except: return
if key.char == 'r' and self.key_pressed:
print('finished')
self.recorder.Stop()
self.recorder = None # seems to be necessary when filename is used, to ensure the ffmpeg process and file are closed and cleaned up
if self.filename:
player = am.Player( self.filename )
else:
player = am.Player( self.buffer )
# self.buffer.Write( 'output.wav')  # if you want to ensure every attempt gets saved. File will be overwritten.
self.queue.append( player )
self.new_recorder()
self.key_pressed = False
return True
#with MyListener('output.wav') as listener:  # this variant uses a file (and needs ffmpeg)
with MyListener() as listener:               # this variant just records to memory
while True:
if listener.queue:
player = listener.queue.pop( 0 )
player.Play()
time.sleep(0.1)

最新更新