9

I am currently trying to record some utterances, in which the record session should start when a key is pressed and held down, and stop when it is released. I made the python script for recording and storing the data..

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

Problem with the script is the audio file doesn't seem to record anything, the duration of the file when i play it is unknown?..

I am not sure i understand what could be wrong here..?

Update:

new version with output:

from pynput import keyboard
import time
import pyaudio
import StringIO
import multiprocessing
from multiprocessing import Process, Queue, queues
import wave

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

p = pyaudio.PyAudio()
frames = []

stream_queue = Queue()



class MyListener(keyboard.Listener):
    def __init__(self):
        super(MyListener, self).__init__(on_press=self.on_press, on_release=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 ("Stream active? " + str(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):
        print "callback"
        if self.key_pressed == True:
            #stream_queue.put(in_data)
            frames.append(data)
            return (in_data, pyaudio.paContinue)

        elif self.key_pressed == False:
            #stream_queue.put(in_data)
            frames.append(data)
            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 -  Key is down")

    elif listener.key_pressed == True and started == True:
        print("stream has started and key is still down")
        print("Stream is active? " + str(listener.stream.is_active()))
        print("Stream is stopped? " + str(listener.stream.is_stopped()))
        print("Stream is time? " + str(listener.stream.get_time()))

    elif listener.key_pressed == False and started == True:
        print("Key has been released")
        listener.stream.stop_stream()
        listener.stream.close()
        print("stream has been closed")
        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

output:

python File2.py 
Stream active? True
callback
Start stream -  Key is down
stream has started and key is still down
Stream is active? False
Stream is stopped? False
Stream is time? 134638.797766
stream has started and key is still down
Stream is active? False
Stream is stopped? False
Stream is time? 134638.902259
stream has started and key is still down
Stream is active? False
Stream is stopped? False
Stream is time? 134639.006739
stream has started and key is still down
Stream is active? False
Stream is stopped? False
Stream is time? 134639.111282
stream has started and key is still down
Stream is active? False
Stream is stopped? False
Stream is time? 134639.215573
stream has started and key is still down
Stream is active? False
Stream is stopped? False
Stream is time? 134639.320448
stream has started and key is still down
Stream is active? False
Stream is stopped? False
Stream is time? 134639.424682
stream has started and key is still down
Stream is active? False
Stream is stopped? False
Stream is time? 134639.528631
stream has started and key is still down
Stream is active? False
Stream is stopped? False
Stream is time? 134639.633699
stream has started and key is still down
Stream is active? False
Stream is stopped? False
Stream is time? 134639.738129
stream has started and key is still down
Stream is active? False
Stream is stopped? False
Stream is time? 134639.842747
Key has been released
stream has been closed
^CTraceback (most recent call last):
  File "File2.py", line 67, in <module>
    time.sleep(0.1)
KeyboardInterrupt
MacBook-Pro:~$ play output.wav 

output.wav:

 File Size: 44        
  Encoding: Signed PCM    
  Channels: 2 @ 16-bit   
Samplerate: 44100Hz      
Replaygain: off         
  Duration: unknown      

In:0.00% 00:00:00.00 [00:00:00.00] Out:0     [      |      ]        Clip:0    
Done.

Things that seems weird to me is

  • The stream is not active after the listener.stream.start_stream()
  • callback print message callback is only being printed once, but should be printed each time the callback stores data to the frames, which only apparently happen once.
  • the output.wav file duration is unknown? why?
Smo
  • 137
  • 1
  • 9

2 Answers2

5

I have this first edition of you request.Sorry for all this global.I am not familiar with the pynput,so i just follow the DOC,use the simplest example of pynput. so here the code which work fine with win7 and python3.holding the space to start record,and escto exit the scripts.

from pynput import keyboard
import pyaudio
import wave
import time

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

record_on = False
complete_tag = False
frames = []

def callback(in_data, frame_count, time_info, status):
    print("callback called")
    callback_flag = pyaudio.paContinue
    # global record_on
    if record_on:
        # global frames
        frames.append(in_data)
    if complete_tag:
        callback_flag = pyaudio.paComplete

    return in_data, callback_flag

def on_press(key):
    global record_on
    print(record_on)
    if key == keyboard.Key.space:
        record_on = True

def on_release(key):
    global record_on
    global complete_tag
    record_on = False
    complete_tag = True
    if key == keyboard.Key.esc:
        return False

if __name__ == '__main__':
    p = pyaudio.PyAudio()
    stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                frames_per_buffer=CHUNK,
                stream_callback=callback)
    with keyboard.Listener(
            on_press=on_press,
            on_release=on_release) as listener:
        listener.join()
    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()

UPDATE:

I just rewrite your callback,and it may work fine for you but not for me.

def callback(self,in_data, frame_count, time_info, status):
    print("callback")
    if self.key_pressed == True:
        #stream_queue.put(in_data)
        print("record")
        frames.append(in_data)
        return (in_data, pyaudio.paContinue)

    elif self.key_pressed == False:
        #stream_queue.put(in_data)
        frames.append(in_data)
        return (in_data, pyaudio.paComplete)

    else:
        print("not record")
        return (in_data,pyaudio.paContinue)

you don't understand the callback,when you called p.open with callback,the callback will be called when the data come from hardware.so the logic should write in the callback like my version instead of while 1: time.sleep(0.1).

so,all your problem is after the first call of the callback,the stream receivePAabort,then stream stop.so the callback just be called once,so your .wav file just have metadata and have no duration.

and i change your entire code to

from pynput import keyboard
import pyaudio
import wave

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

class MyListener(keyboard.Listener):
    def __enter__(self):
        self.p = pyaudio.PyAudio()
        self.stream = self.p.open(format=FORMAT,
                             channels=CHANNELS,
                             rate=RATE,
                             input=True,
                             frames_per_buffer=CHUNK,
                             stream_callback = self.callback)
        self.start()
        return self
    def __init__(self):
        super(MyListener, self).__init__(on_press=self.on_press, on_release=self.on_release)
        self.key_pressed = False
        self.complete_tag = False
        self.frames = []

    def on_press(self, key):
        if key == keyboard.Key.space:
            self.key_pressed = True

    def on_release(self, key):
        if key == keyboard.Key.space:
            self.key_pressed = False
            self.complete = True
        if key == keyboard.Key.esc:
            return False

    def callback(self,in_data, frame_count, time_info, status):
        callback_flag = pyaudio.paContinue
        if self.key_pressed:
            self.frames.append(in_data)
        if self.complete_tag:
            callback_flag = pyaudio.paComplete
        return in_data, callback_flag

    def __exit__(self, exc_type, exc_value, traceback):
        self.stream.stop_stream()
        self.stream.close()
        self.p.terminate()
        self.stop()
        wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
        wf.setnchannels(CHANNELS)
        wf.setsampwidth(self.p.get_sample_size(FORMAT))
        wf.setframerate(RATE)
        wf.writeframes(b''.join(self.frames))
        wf.close()

with MyListener() as listener:
        listener.join()
obgnaw
  • 3,007
  • 11
  • 25
  • Yeah... this solution works... but yes, but I am still a bit confused on why mine version doesn't work... I seem to have localised the problem being the callback not being called, or only being called once when the `p.open` but it doesn't get called anywhere else... – Smo Jul 11 '17 at 17:02
2

You have not filled up your list, frames.

you should do this to populate your list:

for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
    data = stream.read(CHUNK)
    frames.append(data)

Because you are using an empty list here:

wf.writeframes(b''.join(frames))

I hope this works for you.

Cheers!

NightHowler
  • 167
  • 1
  • 11
  • well.. i am using the the callback, so when `listener.stream.start_stream()` is called, it should record. – Smo Jul 08 '17 at 08:57
  • @Smo starting the stream doesn't magically decide how to consume the data or where to store it. Did you try running this yet? EDIT: You should reread your code. You're never actually reading from the stream or putting information into `frames`, which is exactly what this answer is explaining how to do. – eenblam Jul 11 '17 at 14:19
  • but your solution are using a prespecified duration, which what I am trying to do with the keyboard event. – Smo Jul 11 '17 at 16:47
  • I seemed to have narrowed the problem down to being that the callback never gets called.. – Smo Jul 11 '17 at 16:49
  • @eenblam i know this solution will work, but not as i want it to.. and you are right.. I am currently not storing the data anywhere.. but the problem seem to be that the callback is not being called.. – Smo Jul 11 '17 at 17:11
  • @Smo Okay, double checked docs. You're right to not call `read()` since you don't want to block. Next: how sure are you that the callback isn't being called? It's executed in a separate thread. – eenblam Jul 11 '17 at 20:23
  • @eenblam I added a print statement in the callback such that it would be printed each time the callback is being called. The statement is only being printed once, when `self.stream` is being initialised. It should be called each time entering the while loop, but that doesn't seem to be the case. – Smo Jul 11 '17 at 21:19
  • @smo "It should be called each time entering the while loop" ... should it? 1. If the key is pressed and you're currently recording, your while loop should just continue given your flow control statements. 2. The callback's execution depends instead on the arrival of new data into the buffer, not what's going on in the while loop, apart from the start/stop events. ... Or do you mean each time you enter the `if listener.key_pressed == True and started == False:` block? – eenblam Jul 11 '17 at 21:30
  • I added a new version which i hope will clarify things @eenblam – Smo Jul 11 '17 at 21:55