title: how to merge the output from two threads periodically in python
body: <p>I am trying to save video and audio simultaneously and <strong>periodically</strong> into mp4 files of fixed time length. I know in order to achieve this goal, I have to run two individual threads, one recording a video and the other recording the audio in parallel, thanks to the post <strong><a href="https://stackoverflow.com/questions/14140495/how-to-capture-a-video-and-audio-in-python-from-a-camera-or-webcam">How to capture a video (AND audio) in python, from a camera (or webcam)</a></strong>. I have modified the code as below, now it can save the video and audio simultaneously into separate files of fixed duration. My question is, how can I merge the video and audio files on the fly soon as they are generated by the two individual threads? I can merge the video and audio files after the two threadings are done, but this is not what I want.</p>
  <pre><code>#!/usr/bin/env python
  # -*- coding: utf-8 -*-
  # VideoRecorder.py
  
  from __future__ import print_function, division
  import numpy as np
  import cv2
  import pyaudio
  import wave
  import threading
  import time
  from datetime import datetime
  import subprocess
  import os
  import ffmpeg
  
  LOOP_DURATION = 5
  
  
  class VideoRecorder():
      &quot;Video class based on openCV&quot;
      def __init__(self, name=&quot;temp_video.avi&quot;, fourcc=&quot;MJPG&quot;, sizex=640, sizey=480, camindex=0, fps=10):
          self.open = True
          self.device_index = camindex
          self.fps = fps                  # fps should be the minimum constant rate at which the camera can
          self.fourcc = fourcc            # capture images (with no decrease in speed over time; testing is required)
          self.frameSize = (sizex, sizey) # video formats and sizes also depend and vary according to the camera used
          self.video_filename = name
          self.video_cap = cv2.VideoCapture(self.device_index)
          self.video_cap.set(cv2.CAP_PROP_FPS, self.fps)
          self.video_writer = cv2.VideoWriter_fourcc(*self.fourcc)
          self.video_out = cv2.VideoWriter(self.video_filename, self.video_writer, self.fps, self.frameSize)
          self.frame_counts = 1
          self.start_time = time.time()
  
      def record(self):
          &quot;Video starts being recorded&quot;
          # counter = 1
          timer_start = time.time()
          timer_current = 0
          while self.open:
              start = time.time()
              now = datetime.now()
              if not os.path.isdir(&quot;output&quot;):
                  os.mkdir(&quot;output&quot;)
              self.video_filename = os.path.join(&quot;./output&quot;, now.strftime(&quot;%y-%m-%d-%H_%M_%S&quot;) + '.avi')
              self.video_out = cv2.VideoWriter(self.video_filename, self.video_writer, self.fps, self.frameSize)
              while time.time() - start &lt; LOOP_DURATION:
                  ret, video_frame = self.video_cap.read()
                  self.video_out.write(video_frame)
  
  
              # if ret:
              #     self.video_out.write(video_frame)
              #     # print(str(counter) + &quot; &quot; + str(self.frame_counts) + &quot; frames written &quot; + str(timer_current))
              #     self.frame_counts += 1
              #     # print(self.frame_counts)
              #     # counter += 1
              #     # timer_current = time.time() - timer_start
              #     # time.sleep(1/self.fps)
              #     # gray = cv2.cvtColor(video_frame, cv2.COLOR_BGR2GRAY)
              #     # cv2.imshow('video_frame', video_frame)
              #     # cv2.waitKey(1)
              # else:
              #     break
  
      def stop(self):
          &quot;Finishes the video recording therefore the thread too&quot;
          if self.open:
              self.open=False
              self.video_out.release()
              self.video_cap.release()
              cv2.destroyAllWindows()
  
      def start(self):
          &quot;Launches the video recording function using a thread&quot;
          video_thread = threading.Thread(target=self.record)
          video_thread.start()
  
  class AudioRecorder():
      &quot;Audio class based on pyAudio and Wave&quot;
      def __init__(self, filename=&quot;temp_audio.wav&quot;, rate=44100, fpb=1024, channels=2):
          self.open = True
          self.rate = rate
          self.frames_per_buffer = fpb
          self.channels = channels
          self.format = pyaudio.paInt16
          self.audio_filename = filename
          self.audio = pyaudio.PyAudio()
          self.stream = self.audio.open(format=self.format,
                                        channels=self.channels,
                                        rate=self.rate,
                                        input=True,
                                        frames_per_buffer = self.frames_per_buffer)
          self.audio_frames = []
  
      def record(self):
          &quot;Audio starts being recorded&quot;
          self.stream.start_stream()
          while self.open:
              start = time.time()
              now = datetime.now()
              if not os.path.isdir(&quot;output&quot;):
                  os.mkdir(&quot;output&quot;)
              self.audio_filename = os.path.join(&quot;./output&quot;, now.strftime(&quot;%y-%m-%d-%H_%M_%S&quot;) + '.wav')
              while time.time() - start &lt; LOOP_DURATION:
                  data = self.stream.read(self.frames_per_buffer)
                  self.audio_frames.append(data)
              waveFile = wave.open(self.audio_filename, 'wb')
              waveFile.setnchannels(self.channels)
              waveFile.setsampwidth(self.audio.get_sample_size(self.format))
              waveFile.setframerate(self.rate)
              waveFile.writeframes(b''.join(self.audio_frames))
              waveFile.close()
              self.audio_frames = []
              if not self.open:
                  break
  
      def stop(self):
          &quot;Finishes the audio recording therefore the thread too&quot;
          if self.open:
              self.open = False
              self.stream.stop_stream()
              self.stream.close()
              self.audio.terminate()
              waveFile = wave.open(self.audio_filename, 'wb')
              waveFile.setnchannels(self.channels)
              waveFile.setsampwidth(self.audio.get_sample_size(self.format))
              waveFile.setframerate(self.rate)
              waveFile.writeframes(b''.join(self.audio_frames))
              waveFile.close()
  
      def start(self):
          &quot;Launches the audio recording function using a thread&quot;
          audio_thread = threading.Thread(target=self.record)
          audio_thread.start()
  
  def start_AVrecording(filename=&quot;test&quot;):
      global video_thread
      global audio_thread
      video_thread = VideoRecorder()
      audio_thread = AudioRecorder()
      audio_thread.start()
      video_thread.start()
      return filename
  
  
  def stop_AVrecording(filename=&quot;test&quot;):
      audio_thread.stop()
      frame_counts = video_thread.frame_counts
      elapsed_time = time.time() - video_thread.start_time
      recorded_fps = frame_counts / elapsed_time
      print(&quot;total frames &quot; + str(frame_counts))
      print(&quot;elapsed time &quot; + str(elapsed_time))
      print(&quot;recorded fps &quot; + str(recorded_fps))
      video_thread.stop()
  
      # Makes sure the threads have finished
      while threading.active_count() &gt; 1:
          print(f&quot;threading.active_count(): {threading.active_count()}&quot;)
          time.sleep(1)
  
      video_stream = ffmpeg.input(video_thread.video_filename)
      audio_stream = ffmpeg.input(audio_thread.audio_filename)
      now = datetime.now()
      output_file = os.path.join(&quot;./output&quot;, now.strftime(&quot;%y-%m-%d-%H_%M_%S&quot;) + '.mp4')
      ffmpeg.output(audio_stream, video_stream, output_file).run(overwrite_output=True)
  
      # # Merging audio and video signal
      # if abs(recorded_fps - 6) &gt;= 0.01:    # If the fps rate was higher/lower than expected, re-encode it to the expected
      #     print(&quot;Re-encoding&quot;)
      #     cmd = &quot;ffmpeg -r &quot; + str(recorded_fps) + &quot; -i temp_video.avi -pix_fmt yuv420p -r 6 temp_video2.avi&quot;
      #     subprocess.call(cmd, shell=True)
      #     print(&quot;Muxing&quot;)
      #     cmd = &quot;ffmpeg -y -ac 2 -channel_layout stereo -i temp_audio.wav -i temp_video2.avi -pix_fmt yuv420p &quot; + filename + &quot;.avi&quot;
      #     subprocess.call(cmd, shell=True)
      # else:
      #     print(&quot;Normal recording\nMuxing&quot;)
      #     cmd = &quot;ffmpeg -y -ac 2 -channel_layout stereo -i temp_audio.wav -i temp_video.avi -pix_fmt yuv420p &quot; + filename + &quot;.avi&quot;
      #     subprocess.call(cmd, shell=True)
      #     print(&quot;..&quot;)
  
  def file_manager(filename=&quot;test&quot;):
      &quot;Required and wanted processing of final files&quot;
      local_path = os.getcwd()
      if os.path.exists(str(local_path) + &quot;/temp_audio.wav&quot;):
          os.remove(str(local_path) + &quot;/temp_audio.wav&quot;)
      if os.path.exists(str(local_path) + &quot;/temp_video.avi&quot;):
          os.remove(str(local_path) + &quot;/temp_video.avi&quot;)
      if os.path.exists(str(local_path) + &quot;/temp_video2.avi&quot;):
          os.remove(str(local_path) + &quot;/temp_video2.avi&quot;)
      # if os.path.exists(str(local_path) + &quot;/&quot; + filename + &quot;.avi&quot;):
      #     os.remove(str(local_path) + &quot;/&quot; + filename + &quot;.avi&quot;)
  
  if __name__ == '__main__':
  
      start_AVrecording()
      time.sleep(30)
      stop_AVrecording()
  
  </code></pre>