2

I am trying to record from my microphone while i press down a button, problem is that the library I am using is not able to detect on hold event. It only detect on press, which happens once, which means that the microphone only records one sample..

import pyaudio
import wave
from pynput import keyboard

CHUNK = 8192
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"

p = pyaudio.PyAudio()
stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                frames_per_buffer=CHUNK)         
frames = []

def on_press(key):
    if key == keyboard.Key.cmd_l:
        print('- Started recording -'.format(key))
        try:
            data = stream.read(CHUNK)
            frames.append(data)
        except IOError: 
            print 'warning: dropped frame' # can replace with 'pass' if no message desired 
    else:
        print('incorrect character {0}, press cmd_l'.format(key))


def on_release(key):
    print('{0} released'.format(
        key))
    if key == keyboard.Key.cmd_l:
        print('{0} stop'.format(key))
        keyboard.Listener.stop
        return False

print("* recording")


with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
    listener.join()

print("* done recording")

stream.stop_stream()
stream.close()
p.terminate()

wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(p.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(b''.join(frames))
wf.close()

I am using pynput for keyboard events and pyaudio for recording. it seems to record when cmd_l is pressed down and stops when i release. problem is the audio file generated doesn't contain anything, or is pretty short as in 0.19s long.

I guess it could have to do with stream.read has to be called multiple times, and the keypress is only being recorded once hence only one sample is being recorded.

but if that is the case how do i make it call stream.read multiple times, while also detecting also detecting when it is being released? or are there libraries that support on_hold mechanism on keyboards?

Updated approach with threading:

from pynput import keyboard
import time
import pyaudio
import wave

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):
    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.stream = p.open(format=FORMAT,
                             channels=CHANNELS,
                             rate=RATE,
                             input=True,
                             frames_per_buffer=CHUNK,
                             stream_callback = self.callback)
        print self.stream.is_active()
    
    def on_press(self, key):
        if key == keyboard.Key.cmd_l:
            self.key_pressed = True
    
    def on_release(self, key):
        if key == keyboard.Key.cmd_l:
            self.key_pressed = False

    def callback(self,in_data, frame_count, time_info, status):
        if self.key_pressed == True:
            return (in_data, pyaudio.paContinue)
        elif self.key_pressed == False:
            return (in_data, pyaudio.paComplete)
        else:
            return (in_data,pyaudio.paAbort)


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

while True:
    time.sleep(0.1)
    if listener.key_pressed == True and started == False:
        started = True
        listener.stream.start_stream()
        print "start Stream"

    elif listener.key_pressed == False and started == True:
        print "Something coocked"
        listener.stream.stop_stream()
        listener.stream.close()
        p.terminate()

        wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
        wf.setnchannels(CHANNELS)
        wf.setsampwidth(p.get_sample_size(FORMAT))
        wf.setframerate(RATE)
        wf.writeframes(b''.join(frames))
        wf.close()

        started = False

I tried a different approach introducing two threads, which one monitoring the keyboard, and the other one sorts out the recording. But this solution keeps dropping the frames, hence nothing is actually being recorded?..

how come?

Community
  • 1
  • 1
Smo
  • 137
  • 1
  • 9

2 Answers2

1

You can do something like in this answer where you run a function periodically on a separate thread. Seems to be the easiest solution.

K. Kirsz
  • 1,384
  • 10
  • 11
0

Here's an example of how you might do it: I have modified your code by making a separate thread do the recording. The keyboard press/release sets the state of an Event, and the thread records chunks in a loop when the Event is set. A separate Event is used to tell the thread to terminate after recording is done. This records multiple 32Kb chunks for me (8192 * 2 chans * 16 bits).

import pyaudio
import wave
from pynput import keyboard
import threading

CHUNK = 8192
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"

p = pyaudio.PyAudio()

stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                frames_per_buffer=CHUNK)         
frames = []

recordingEvent = threading.Event()    # set to activate recording
exitEvent = threading.Event()         # set to stop recording thread


def on_press(key):
    if key == keyboard.Key.ctrl:
        print('- Started recording -'.format(key))
        recordingEvent.set()
    else:
        print('incorrect character {0}, press cmd_l'.format(key))


def on_release(key):
    print('{0} released'.format(key))
    if key == keyboard.Key.ctrl:
        print('{0} stop'.format(key))
        recordingEvent.clear()
        keyboard.Listener.stop
        return False


def do_recording():
    while (not exitEvent.is_set()):
        if (recordingEvent.wait(0.1)):
            try:
                data = stream.read(CHUNK)
                # print len(data)
                frames.append(data)
            except IOError: 
                print 'warning: dropped frame' # can replace with 'pass' if no message desired 


class myRecorder(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    def run(self):
        do_recording()


# start recorder thread
recordingThread = myRecorder()
recordingThread.start()

# monitor keyboard
with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
    listener.join()

# stop recorder thread
exitEvent.set()    
recordingThread.join()

print("* done recording")

stream.stop_stream()
stream.close()
p.terminate()

wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(p.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(b''.join(frames))
wf.close()

NB I used the Ctrl key instead of the Cmd key, as it suited my machine better. Hope this is useful.

AS Mackay
  • 2,831
  • 9
  • 19
  • 25
  • I tried a similar solution (i think, you seem to have more threads than I have?)... but I can't record with yours or mine, it keeps giving IOerror. – Smo Jul 07 '17 at 13:20