2

I want to record a audio track and save it 2 diffrent .wav files. The audio tracks should be saved with a delay of ca. 6 seconds and each .wav should be 12 seconds long.

I tried to do it with multiprocessing and pyaudio, but i cant manage to get it working

Please note that i am a beginner in python and that this is my first post on stackoverflow!

def func1():
  #Record and save a 12 seconds long .wav 
def func2():
  #Record and save a 12 seconds long .wav 
if __name__ == '__main__':
  p1 = Process(target=func1)
  p1.start()
  p2 = Process(target=func2)
  p2.start()
  p1.join()
  p2.join()

#start func2 6 seconds after func1


I would expect a data structure like this:
|---1.wav---|---1.wav---|---1.wav---|
      |---2.wav---|---2.wav---|---2.wav---|
     6sec  12sec 18sec 24sec 30sec 36sec 42sec

EDIT: I came up with a bit of code that seems to work kind of well. It has a delay of .144 seconds. I am happy about improvement od this code. This code uses threading instead of multiprocessing.

import pyaudio
import wave
from threading import Thread
import time
from datetime import datetime

FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
CHUNK = 1024
CHUNK1 = 1024
RECORD_SECONDS = 12
WAVE_OUTPUT_FILENAME1 = name = "outputs/output_1"+datetime.now().strftime("%m:%d:%Y-") 
WAVE_OUTPUT_FILENAME2 = name = "outputs/output_2"+datetime.now().strftime("%m:%d:%Y-") 


def func1():
    while 1==1:
        global FORMAT
        global CHANNELS
        global RATE
        global CHUNK
        global RECORD_SECONDS
        global WAVE_OUTPUT_FILENAME1
        WAVE_OUTPUT_FILENAME1 = name = "outputs/output1_"#+datetime.now().strftime("%m:%d:%Y-") 
        audio = pyaudio.PyAudio()
        stream = audio.open(format=FORMAT, channels=CHANNELS,
                        rate=RATE, input=True,
                        frames_per_buffer=CHUNK)
        print("recording...")
        frames = []
        WAVE_OUTPUT_FILENAME1 = WAVE_OUTPUT_FILENAME1+datetime.now().strftime("%H;%M;%S.%f--") 
        for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
            data = stream.read(CHUNK)
            frames.append(data)
        WAVE_OUTPUT_FILENAME1 = WAVE_OUTPUT_FILENAME1 + datetime.now().strftime("%H;%M;%S.%f")+".wav"
        print("finished recording")


        # stop Recording
        stream.stop_stream()
        stream.close()
        audio.terminate()



        waveFile = wave.open(WAVE_OUTPUT_FILENAME1, 'wb')
        waveFile.setnchannels(CHANNELS)
        waveFile.setsampwidth(audio.get_sample_size(FORMAT))
        waveFile.setframerate(RATE)
        waveFile.writeframes(b''.join(frames))
        waveFile.close() 

def func2():
    time.sleep(6)
    while 1==1:
        global FORMAT
        global CHANNELS
        global RATE
        global CHUNK1
        global RECORD_SECONDS
        global WAVE_OUTPUT_FILENAME2
        WAVE_OUTPUT_FILENAME2 = name = "outputs/output2_"#+datetime.now().strftime("%m:%d:%Y-") 
        audio = pyaudio.PyAudio()
        stream = audio.open(format=FORMAT, channels=CHANNELS,
                        rate=RATE, input=True,
                        frames_per_buffer=CHUNK1)
        print("recording...")
        frames = []
        WAVE_OUTPUT_FILENAME2 = WAVE_OUTPUT_FILENAME2+datetime.now().strftime("%H;%M;%S.%f--") 
        for i in range(0, int(RATE / CHUNK1 * RECORD_SECONDS)):
            data = stream.read(CHUNK1)
            frames.append(data)
        WAVE_OUTPUT_FILENAME2 = WAVE_OUTPUT_FILENAME2 + datetime.now().strftime("%H;%M;%S.%f")+".wav"
        print("finished recording")


        # stop Recording
        stream.stop_stream()
        stream.close()
        audio.terminate()

        waveFile = wave.open(WAVE_OUTPUT_FILENAME2, 'wb')
        waveFile.setnchannels(CHANNELS)
        waveFile.setsampwidth(audio.get_sample_size(FORMAT))
        waveFile.setframerate(RATE)
        waveFile.writeframes(b''.join(frames))
        waveFile.close() 

if __name__ == '__main__':
    Thread(target = func1).start()
    Thread(target = func2).start()
  • Do you really want to keep overwriting your WAV files? According to your ASCII figure it looks like that. Also, do your really need to create files in the first place (especially if they are re-written all the time)? What is supposed to happen with those files? If you shortly describe what you actually want to do, we might be able to give you better suggestions! – Matthias Jul 26 '19 at 16:02
  • I dont rewrite the WAV files i give each file a individual name with a timestamp @Matthias. – Moritz Pfennig Jul 26 '19 at 18:50
  • Can you please update your ASCII figure accordingly? – Matthias Jul 27 '19 at 08:21

1 Answers1

1

why do you think you need multiprocessing? I think it just complicates things

how about just recording in 6 second (or smaller) chunks/frames and write the correct frames to each file.

I've got a bit carried away, and written a nice class to do this:

import pyaudio
import wave
import time


class OverlappedRecorder:
    def __init__(
        self, secs_per_file, secs_between_file, *,
        num_channels=2, sample_rate=48000,
        sample_format=pyaudio.paInt16,
    ):
        # various constants needed later
        self.num_channels = num_channels
        self.sample_width = pyaudio.get_sample_size(sample_format)
        self.sample_rate = sample_rate
        self.frames_between_start = int(secs_between_file * sample_rate)
        self.frames_per_file = int(secs_per_file * sample_rate)

        # mutable state needed to keep everything going
        self.files = []
        self.frames_till_next_file = 0

        self.pa = pyaudio.PyAudio()
        self.stream = self.pa.open(
            format=sample_format, channels=num_channels,
            rate=sample_rate, frames_per_buffer=1024,
            input=True, start=False,
            stream_callback=self._callback,
        )

    def sleep_while_active(self):
        while self.stream.is_active():
            time.sleep(0.2)

    def begin_wave_file(self):
        "internal function to start a new WAV file"
        path = time.strftime(
            'recording-%Y-%m-%d-%H.%M.%S.wav',
            time.localtime()
        )
        file = wave.open(path, 'wb')
        file.setnchannels(self.num_channels)
        file.setsampwidth(self.sample_width)
        file.setframerate(self.sample_rate)
        self.files.append(file)

    # context manager stuff, recording starts when entered using "with"
    def __enter__(self):
        self.stream.start_stream()
        return self

    # exiting shuts everything down
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.stream.stop_stream()
        self.stream.close()
        self.pa.terminate()

        for file in self.files:
            file.close()

    # called by pyaudio when a new set of frames are ready
    def _callback(self, data, frame_count, time_info, status):
        self.frames_till_next_file -= frame_count
        # see if we need to start a new file
        if self.frames_till_next_file < 0:
            self.frames_till_next_file += self.frames_between_start
            self.begin_wave_file()

        # can't remove from lists while iterating
        # keep a list of files to close and remove later
        done = []
        for file in self.files:
            remain = self.frames_per_file - file.getnframes()

            # add appropriate amount of data to all open files
            if frame_count < remain:
                file.writeframesraw(data)
            else:
                remain *= self.sample_width * self.num_channels
                file.writeframesraw(data[:remain])
                done.append(file)

        # close anything that finished
        for file in done:
            file.close()
            self.files.remove(file)

        # tell pyaudio to keep going
        return (None, pyaudio.paContinue)

basic usage is: create an object, enter it using with and it'll start recording, and when you exit it'll stop and clean up.

rec = OverlappedRecorder(12, 6)
with rec:
    time.sleep(30)

will let it run for 30 seconds, or you could do:

with OverlappedRecorder(12, 6) as rec:
    rec.sleep_while_active()

to let it run until you hit Ctrl+C to kill the program, or you could put a call to input() in there to make it stop when you press enter, or whatever else you like.

a few comments on the code you posted:

  • you only need to declare global variables if you're going to modify them
  • why do you have seperate functions? why not just have a single function, and just delay start()ing the second Thread
  • why are you setting WAVE_OUTPUT_FILENAME1 so many times? just save the start_time and end_time, then format the string in one go
  • you don't have to read() in chunks, if you know it's going to fit in memory just read everything all in one go
  • you shouldn't need to keep starting and stopping recording, just open it once in each thread and if you're lucky samples will accumulate in the buffer while you're writing the wav file to disk

something like:

import pyaudio
import wave
import time
from datetime import datetime
from threading import Thread

FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 12


def recorder(prefix):
    audio = pyaudio.PyAudio()
    stream = audio.open(
        format=FORMAT, channels=CHANNELS,
        rate=RATE, input=True,
    )
    try:
        while True:
            start_time = datetime.now()
            print("recording started", start_time)

            data = stream.read(RATE * RECORD_SECONDS, False)

            end_time = datetime.now()
            print("finished", end_time)

            name = f'{prefix}{start_time:%Y-%m-%d-%H-%M-%S.%f}-{end_time:%H-%M-%S.%f}.wav'
            print("writing", name)
            with wave.open(name, 'wb') as waveFile:
                waveFile.setnchannels(CHANNELS)
                waveFile.setsampwidth(audio.get_sample_size(FORMAT))
                waveFile.setframerate(RATE)
                waveFile.writeframes(data)
    finally:
        stream.stop_stream()
        stream.close()
        audio.terminate()


if __name__ == '__main__':
    Thread(target=recorder, args=('outputs/output_1-',)).start()
    time.sleep(6)
    Thread(target=recorder, args=('outputs/output_2-',)).start()

a few differences:

  • the version using threading is much less code!
  • my version allows an arbitrary number of files without using up multiple OS threads for each file (there's the Python thread and pyaudio has an internal thread looking after audio buffers)
  • my version saves partial files

hope all the helps / makes sense!

Sam Mason
  • 15,216
  • 1
  • 41
  • 60
  • I dont know if this is possible with pyaudio. Atleast not for me. – Moritz Pfennig Jul 25 '19 at 18:28
  • Thank you for all the work @Sam Mason. I will try to look over it the next days! – Moritz Pfennig Jul 26 '19 at 18:52
  • I have a question concerning one part of the code and its function: `done = [] for file in self.files: remain = self.frames_per_file - file.getnframes() # add appropriate amount of data to all open files if frame_count < remain: file.writeframesraw(data) else: remain *= self.sample_width * self.num_channels file.writeframesraw(data[:remain]) done.append(file)` Is it to fill up the file untill it has exactly 12 seconds in it?@Sam Mason – Moritz Pfennig Jul 29 '19 at 09:21