如何在用户每次按键时录制音频?

问题描述 投票:0回答:4

如何不确定地录制用户的音频,当且仅当用户按下 ctrl 键时并在用户按下 ctrl+c 键时关闭录音循环? 到目前为止,基于一些在线示例构建了此脚本:

from pynput import keyboard
import time, os
import pyaudio
import wave
import sched
import sys
from playsound import playsound


CHUNK = 8192
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
WAVE_OUTPUT_FILENAME = "mic.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):

        try:
            if key.ctrl:
                self.key_pressed = True
            return True
        except AttributeError:
            sys.exit()





    def on_release(self, key):

        if key.ctrl:
            self.key_pressed = False
        return True


listener = MyListener()


listener.start()
started = False
stream = None

def recorder():
    global started, p, stream, frames

    while True:

        try:
            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 KeyboardInterrupt:
                    print('\nRecording finished: ' + repr(WAVE_OUTPUT_FILENAME))
                    quit()

            elif not listener.key_pressed and started:

                print("Stop recording")
                listener.wf.writeframes(b''.join(frames))
                listener.wf.close()
                print("You should have a wav file in the current directory")
                print('-> Playing recorded sound...')
                playsound(str(os.getcwd())+'/mic.wav')
                os.system('python "/Users/user/rec.py"')

        except KeyboardInterrupt:
            print('\nRecording finished: ' + repr(WAVE_OUTPUT_FILENAME))
            quit()
        except AttributeError:
            quit()





print ("-> Press and hold the 'ctrl' key to record your audio")
print ("-> Release the 'ctrl' key to end recording")


recorder()

问题是效率确实很低,比如电脑开始发热。我发现让程序继续运行并录制不同音频样本的唯一方法是:

os.system('python "/Users/user/rec.py"')
。为了完成程序,我尝试用以下方法捕获异常:

except AttributeError:
       sys.exit()

或使用用户输入:

if key.ctrl_c:
   sys.exit()

基于 pyinput docs,我尝试有效地利用监听器。但是,对于这个特定场景,使用这些侦听器的推荐方式是什么?

python python-3.x input pyaudio
4个回答
6
投票

至于你的计算机看起来工作非常困难的主要问题,那是因为你使用了 while 循环来不断检查记录键何时被释放。在这个循环中,计算机将尽可能快地循环而不会休息。

更好的解决方案是使用事件驱动编程,让操作系统定期通知您事件,并在事件发生时检查您是否想要执行任何操作。这听起来可能很复杂,但幸运的是

pynput
为您完成了大部分艰苦的工作。

如果您跟踪录制或播放的状态,则在下次发生控制键按下事件时开始新录制也相当简单,而无需为每个新录制递归调用整个新进程的“黑客”操作。键盘侦听器内的事件循环将继续进行,直到其中一个回调函数返回

False
或引发
self.stopException()

我创建了一个简单的

listener
类,类似于您最初的尝试,它调用录音机或播放器实例(我稍后会介绍)来启动和停止。我还必须同意 Anwarvic 的观点,即
<ctl-c>
应该被保留作为停止脚本的紧急方式,因此我将停止命令更改为字母
q

class listener(keyboard.Listener):
    def __init__(self, recorder, player):
        super().__init__(on_press = self.on_press, on_release = self.on_release)
        self.recorder = recorder
        self.player = player
    
    def on_press(self, key):
        if key is None: #unknown event
            pass
        elif isinstance(key, keyboard.Key): #special key event
            if key.ctrl and self.player.playing == 0:
                self.recorder.start()
        elif isinstance(key, keyboard.KeyCode): #alphanumeric key event
            if key.char == 'q': #press q to quit
                if self.recorder.recording:
                    self.recorder.stop()
                return False #this is how you stop the listener thread
            if key.char == 'p' and not self.recorder.recording:
                self.player.start()
                
    def on_release(self, key):
        if key is None: #unknown event
            pass
        elif isinstance(key, keyboard.Key): #special key event
            if key.ctrl:
                self.recorder.stop()
        elif isinstance(key, keyboard.KeyCode): #alphanumeric key event
            pass

if __name__ == '__main__':
    r = recorder("mic.wav")
    p = player("mic.wav")
    l = listener(r, p)
    print('hold ctrl to record, press p to playback, press q to quit')
    l.start() #keyboard listener is a thread so we start it here
    l.join() #wait for the tread to terminate so the program doesn't instantly close

有了这个结构,我们就需要一个具有启动和停止功能的记录器类,它不会阻止(异步)侦听器线程继续接收按键事件。 PyAudio 的文档提供了一个非常好的异步输出示例,因此我只是将其应用于输入。稍微重新安排一下,并设置一个标志,让我们的听众知道我们稍后何时录音,并且我们有一个录音机类:

class recorder:
    def __init__(self, 
                 wavfile, 
                 chunksize=8192, 
                 dataformat=pyaudio.paInt16, 
                 channels=2, 
                 rate=44100):
        self.filename = wavfile
        self.chunksize = chunksize
        self.dataformat = dataformat
        self.channels = channels
        self.rate = rate
        self.recording = False
        self.pa = pyaudio.PyAudio()

    def start(self):
        #we call start and stop from the keyboard listener, so we use the asynchronous 
        # version of pyaudio streaming. The keyboard listener must regain control to 
        # begin listening again for the key release.
        if not self.recording:
            self.wf = wave.open(self.filename, 'wb')
            self.wf.setnchannels(self.channels)
            self.wf.setsampwidth(self.pa.get_sample_size(self.dataformat))
            self.wf.setframerate(self.rate)
            
            def callback(in_data, frame_count, time_info, status):
                #file write should be able to keep up with audio data stream (about 1378 Kbps)
                self.wf.writeframes(in_data) 
                return (in_data, pyaudio.paContinue)
            
            self.stream = self.pa.open(format = self.dataformat,
                                       channels = self.channels,
                                       rate = self.rate,
                                       input = True,
                                       stream_callback = callback)
            self.stream.start_stream()
            self.recording = True
            print('recording started')
    
    def stop(self):
        if self.recording:         
            self.stream.stop_stream()
            self.stream.close()
            self.wf.close()
            
            self.recording = False
            print('recording finished')

最后,我们创建一个音频播放器,当您按

p
时可以播放音频。我将 PyAudio 示例放入一个线程中,每次按下按钮时都会创建该线程,以便可以创建多个相互重叠的播放器。我们还跟踪有多少玩家正在玩,因此我们不会在文件已被玩家使用时尝试录制。 (我还在顶部添加了我的导入)

from threading import Thread, Lock
from pynput import keyboard
import pyaudio
import wave

class player:
    def __init__(self, wavfile):
        self.wavfile = wavfile
        self.playing = 0 #flag so we don't try to record while the wav file is in use
        self.lock = Lock() #muutex so incrementing and decrementing self.playing is safe
    
    #contents of the run function are processed in another thread so we use the blocking
    # version of pyaudio play file example: http://people.csail.mit.edu/hubert/pyaudio/#play-wave-example
    def run(self):
        with self.lock:
            self.playing += 1
        with wave.open(self.wavfile, 'rb') as wf:
            p = pyaudio.PyAudio()
            stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                            channels=wf.getnchannels(),
                            rate=wf.getframerate(),
                            output=True)
            data = wf.readframes(8192)
            while data != b'':
                stream.write(data)
                data = wf.readframes(8192)

            stream.stop_stream()
            stream.close()
            p.terminate()
            wf.close()
        with self.lock:
            self.playing -= 1
        
    def start(self):
        Thread(target=self.run).start()

我不能保证这完全没有错误,但如果您对其工作原理/如何使其工作有任何疑问,请随时发表评论。


4
投票

我真的不建议使用

ctrl + c
来做任何事情而不是打断。另外,我不建议一直按住某个按钮来继续录音。我的建议是用一键录音,一键停止;这就是为什么在下面的代码中我使用
s
s开始录音,使用
q
quit。它是完全可配置的,您可以根据自己的喜好进行更改:

安装依赖项

pip install pyaudio numpy scipy 
sudo pip install keyboard

录音机

用麦克风录制的简单课程:

import pyaudio
import keyboard
import numpy as np
from scipy.io import wavfile


class Recorder():
    def __init__(self, filename):
        self.audio_format = pyaudio.paInt16
        self.channels = 1
        self.sample_rate = 44100
        self.chunk = int(0.03*self.sample_rate)
        self.filename = filename
        self.START_KEY = 's'
        self.STOP_KEY = 'q'


    def record(self):
        recorded_data = []
        p = pyaudio.PyAudio()

        stream = p.open(format=self.audio_format, channels=self.channels,
                        rate=self.sample_rate, input=True,
                        frames_per_buffer=self.chunk)
        while(True):
            data = stream.read(self.chunk)
            recorded_data.append(data)
            if keyboard.is_pressed(self.STOP_KEY):
                print("Stop recording")
                # stop and close the stream
                stream.stop_stream()
                stream.close()
                p.terminate()
                #convert recorded data to numpy array
                recorded_data = [np.frombuffer(frame, dtype=np.int16) for frame in recorded_data]
                wav = np.concatenate(recorded_data, axis=0)
                wavfile.write(self.filename, self.sample_rate, wav)
                print("You should have a wav file in the current directory")
                break


    def listen(self):
        print(f"Press `{self.START_KEY}` to start and `{self.STOP_KEY}` to quit!")
        while True:
            if keyboard.is_pressed(self.START_KEY):
                self.record()
                break

要使用此类,只需调用

listen()
方法,如下所示:

recorder = Recorded("mic.wav") #name of output file
recorder.listen()

0
投票

试试这个:

import sys
try:
   #Your code here

except KeyboardInterrupt:
    sys.exit()

0
投票

我尝试使用 pynput 和 pyaudio 创建一个脚本,可以在按住按钮时录制音频并将其保存在波形文件中(我使用 F8 按住进行录制)。

from pynput.keyboard import Key
from pynput import keyboard
import pyaudio  
import wave

audio = pyaudio.PyAudio()
stream = audio.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=1024)
frames = []

last_pressed_key = None

def on_press(key):
    global last_pressed_key
    if last_pressed_key == None and key == Key.f8:
        last_pressed_key = key

        print('Recording Started')
    elif key == last_pressed_key:
        pass

def on_release(key):
    global last_pressed_key
    if last_pressed_key is not None and key == last_pressed_key:
        print("Recording Stopped")
        last_pressed_key = None  # Reset after release

        global frames
        filename = f"recording_{len(frames)}.wav"
        with wave.open(filename, "wb") as wav_file:
            wav_file.setnchannels(1)
            wav_file.setsampwidth(audio.get_sample_size(pyaudio.paInt16))
            wav_file.setframerate(16000)
            wav_file.writeframes(b''.join(frames))
            wav_file.close()
        
        frames = []
    else:
        pass

listener = keyboard.Listener(on_press=on_press,on_release=on_release)
listener.start()


while True:
    if last_pressed_key == Key.f8:
        data = stream.read(1024)
        frames.append(data)
        continue
    else:
        pass
© www.soinside.com 2019 - 2024. All rights reserved.