gstreamer pipeline in python not saving .mp4 video correctly - python

I have a script in Python where I record the stream from four cameras to mp4 through gstreamer. I define a signal so that the capture terminates if Ctrl-C is pressed, and it works fine. In the gstreamer pipeline itself, I have a property added at the source numbuffers = 600 because I want the capture to stop after 600 frames anyway if I don't press Ctrl-C before then.
My problem is this, if I interrupt through the keyboard all four mp4 videos are saved correctly, but if I let it finish by itself after the 600 frames the second to fourth videos are fine while the first video will have "no playable stream", even if having the same size as the other videos.
I don't understand why only the first video is not saved or closed correctly, any hints?
This is my code:
import gi
import signal
import threading
import logging
from time import time, sleep
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GObject
logging.basicConfig(level=logging.INFO)
def on_message(bus: Gst.Bus, message: Gst.Message, loop: GObject.MainLoop):
mtype = message.type
"""
Gstreamer Message Types and how to parse
"""
if mtype == Gst.MessageType.EOS:
logging.info("End-of-stream\n")
loop.quit()
elif mtype == Gst.MessageType.ERROR:
err, debug = message.parse_error()
logging.info("Warning: %s: %s\n" % (err, debug))
loop.quit()
elif mtype == Gst.MessageType.WARNING:
err, debug = message.parse_warning()
logging.info("Error: %s: %s\n" % (err, debug))
return True
def signal_handler(signal, frame):
for i in range(0,n_cams):
pipelines[i].send_event(Gst.Event.new_eos())
signal.signal(signal.SIGINT, signal_handler)
# Initialize
GObject.threads_init()
Gst.init(None)
n_cams = 4
buses = []
pipelines = []
for i in range(0,n_cams):
logging.info("Starting camera " + str(i))
command = ("nvarguscamerasrc sensor-id={} num-buffers=600 ! "
"video/x-raw(memory:NVMM), width=(int)1920, height=(int)1080, format=(string)NV12, framerate=(fraction)30/1 ! "
"queue max-size-buffers=4 leaky=2 ! "
"nvv4l2h265enc control-rate=1 bitrate=8000000 ! video/x-h265, stream-format=(string)byte-stream ! "
"h265parse ! qtmux ! filesink location=test_{}.mp4").format(i)
logging.info("Parse launch " + command)
pipelines.append(Gst.parse_launch(command))
loop = GObject.MainLoop()
for i in range(0,n_cams):
buses.append(pipelines[i].get_bus())
buses[i].add_signal_watch()
buses[i].connect("message", on_message, loop)
logging.info("Starting pipelines")
for i in range(0,n_cams):
pipelines[i].set_state(Gst.State.PLAYING)
loop.run()
# stop
for i in range(0,n_cams):
pipelines[i].send_event(Gst.Event.new_eos())
pipelines[i].set_state(Gst.State.NULL)

I think you are getting lucky in the ctrl-c case in the first place. In the ctrl-case when you send an EOS, you should wait for the EOS to reach the bus before setting the pipeline sate to NULL.
Similar problem with your actual described problem. You have 4 pipelines but only one main loop. You quit the loop after the first pipeline reaches EOS. Instead you should wait until all pipelines have reached EOS.
P.s. I think the send_event after # stop does not actually do anything.

Related

How to stream audio in GStreamer between 2 python scripts that work on the same computer?

I have 2 pieces of code that works fine up until now. Code A (FileRecord) creates a pipeline and writes whatever it hears from mic to an .ogg file named file.ogg. Code B (FilePlayer) must start after the user stops manual (Ctrl + C) the execution of script A and plays in pulsesink (user's headset) whatever recorder recorded during execution(file.ogg).
My goal is to make the 2 pieces of code work simultaneously and playing file from code B while code A records.
Code A (FileRecord)
from time import sleep
import gi
gi.require_version("Gst","1.0")
from gi.repository import Gst
from gi.repository import GLib
import signal
signal.signal(signal.SIGTSTP, signal.SIG_IGN) #When Ctrl + Z is pressed file is not playable (this command ignores Ctrl + Z)
print("In order to stop press: Ctrl C.")
Gst.init()
main_loop = GLib.MainLoop()
main_loop_thread = Thread(target = main_loop.run)
main_loop_thread.start()
#buffer = gst_buffer_new ()
pipeline = Gst.parse_launch('autoaudiosrc ! audioconvert ! tee name="source" ! queue ! vorbisenc ! oggmux ! filesink location=file.ogg')
pipeline.set_state(Gst.State.PLAYING)
try:
while True:
sleep(0.1)
except KeyboardInterrupt:
pass
pipeline.set_state(Gst.State.NULL)
main_loop.quit()
main_loop_thread.join()
Code B (FilePlayer)
from time import sleep
import gi
gi.require_version("Gst","1.0")
from gi.repository import Gst
from gi.repository import GLib
import signal
Gst.init()
main_loop = GLib.MainLoop()
main_loop_thread = Thread(target = main_loop.run)
main_loop_thread.start()
pipeline = Gst.parse_launch('filesrc location=file.ogg ! decodebin ! pulsesink')
pipeline.set_state(Gst.State.PLAYING)
try:
while True:
sleep(0.1)
except KeyboardInterrupt:
pass
pipeline.set_state(Gst.State.NULL)
main_loop.quit()
main_loop_thread.join()
I don't know how to achieve sound stream. Please help me!

subprocess isn't killed after macOS alert is closed

I am trying to write a python script which displays a macOS alert and starts an alarm at the same time.
The alarm sound should be stopped after the alert is closed, however it isn't.
def show_alert(message="Flashlight alarm"):
"""Display a macOS dialog."""
message = json.dumps(str(message))
exit_status = os.system("osascript dialog.scpt {0}".format(message))
return exist_status
def play_alarm(file_name = "beep.wav", repeat=3):
"""Repeat the sound specified to mimic an alarm."""
process = subprocess.Popen(['sh', '-c', 'while :; do afplay "$1"; done', '_', file_name], shell=False)
return process
def alert_after_timeout(timeout, message, sound = True):
"""After timeout seconds, show an alert and play the alarm sound."""
time.sleep(timeout)
process = None
if sound:
process = play_alarm()
exit_status = show_alert(message)
if process is not None:
os.killpg(os.getpgid(process.pid), signal.SIGINT)
process.kill()
# also, this below line doesn't seem to open an alert.
show_alert(exit_status)
alert_after_timeout(1, "1s alarm")
The above code should display a macOS alert after starting to loop an alarm sound (in the file beep.wav). When the alert is closed, the alarm sound should instantly stop.
The AppleScript file dialog.scpt triggers the alert, it is only a few lines long:
on run argv
tell application "System Events" to display dialog (item 1 of argv) with icon file (path of container of (path to me) & "Icon.png")
end run
I admit I don't know why you cannot kill your process running in a shell, using subprocess to mimic running as background..., and the fact that no other command runs after that means that there's probably a deadlock somewhere. So let's drop that solution.
Let me propose a more pythonic solution. The audio play part was adapted from how to play wav file in python? but now plays in a loop and works with python 3 as well.
The idea is to start a thread that plays a sound in a loop using only python modules. The thread is aware of a global variable. If the stop_audio variable is set, then the thread knows it has to quit the infinite loop and stop playing.
you control the flag from the other procedure. Once the message has been clicked on, set the flag, audio stops playing immediately.
import pyaudio
import wave
import threading
# global variable used to gently tell the thread to stop playing
stop_audio = False
def show_alert(message="Flashlight alarm"):
"""Display a macOS dialog."""
message = json.dumps(str(message))
exit_status = os.system("osascript dialog.scpt {0}".format(message))
return exit_status
# initialize audio
def play_alarm(file_name = "beep.wav"):
#define stream chunk
chunk = 1024
#open a wav format music
f = wave.open(file_name,"rb")
#instantiate PyAudio
p = pyaudio.PyAudio()
#open stream
stream = p.open(format = p.get_format_from_width(f.getsampwidth()),
channels = f.getnchannels(),
rate = f.getframerate(),
output = True)
while not stop_audio:
f.rewind()
#read data
data = f.readframes(chunk)
#play stream
while data and not stop_audio:
stream.write(data)
data = f.readframes(chunk)
#stop stream
stream.stop_stream()
stream.close()
#close PyAudio
p.terminate()
def alert_after_timeout(timeout, message, sound = True):
"""After timeout seconds, show an alert and play the alarm sound."""
time.sleep(timeout)
process = None
if sound:
t = threading.Thread(target=play_alarm,args=("beep.wav",))
t.start()
exit_status = show_alert(message)
global stop_sound
if sound:
stop_sound = True # tell the thread to exit
t.join()
show_alert(exit_status)
alert_after_timeout(1, "1s alarm")
Note that I've dropped the repeat=3 parameter as it wasn't used and I made no sense of it.
An alternative without using pyaudio would be to call the external player in a loop, replace play_alarm above by this:
def play_alarm(file_name = "beep.wav"):
global stop_sound
while not stop_sound:
subprocess.call(["afplay",file_name])
when stop_sound is True, the sound keeps on playing till the end, but doesn't resume. So the effect is not instantaneous, but it's simple.
And another alternative to cut the sound in a more reactive way:
def play_alarm(file_name = "beep.wav"):
global stop_sound
while not stop_sound:
process = subprocess.Popen(["afplay",file_name])
while not stop_sound:
if process.poll() is not None:
break # process has ended
time.sleep(0.1) # wait 0.1s before testing process & stop_sound flag
if stop_sound:
process.kill() # kill if exit by stop_sound

Raspberry Pi crash during python program (OR how is it possible to kill Linux???)

I build a keypad activated sensor with a Raspberry Pi and Python. Everything seems to go well but after a few minutes and several keypad entries, the Pi has a total crash and switches off immediately - no error messages pop up.
The script will continously wait for a keypad entry and if the code is correct, switch on a sensor, if it's not you have to try again. If the sensor is activated you have to put in the correct numbers to avoid an alarm being triggered after 30s)
Could anyone point me in the direction of what might be the problem? Here are the things I've tried so far without success
1) Exchange Pi to new Pi 2
2) Different OS, both NOOBS and Raspbian Wheezy
3) Different sensor (accelerometer vs IR sensor)
4) Disconnect monitor, keyboard and use SSH connection via Cygwin
5) Get log file until crash - log file was empty after reboot
python bad_script &> full_log.txt
6) Different log file command: causes instant crash and is also empty after reboot:
python script.py >> /logdir/script.py.log 2>&1
The question is: how am I able to crash Linux? If it's a memory problem, isn't there a prevention in linux to stop processed before they appear?
Here is the full script I am running:
import sys
from time import sleep
import threading
import signal
from matrix_keypad import RPi_GPIO1
import RPi.GPIO as GPIO
import smbus
import time
passcode = [1,2,3,4] # this is the correct code you have to enter
kp = RPi_GPIO1.keypad(columnCount = 3)
alarm_active = threading.Event() # shared variable used to control the sensor monitoring thread
alarm_active.clear() # Clear the alarm initially
monitor_thread = None # Global variable used to store the monitoring thread
#Set up all the pins correctio
GPIO.setmode(GPIO.BCM)
PINIR=7
GPIO.setup(7, GPIO.IN) # infrad-sensor
#Now activate the kaypad and listen for keypad inputs
def digit():
r = None
while r == None:
r = kp.getKey()
return r
def get_keycode():
# Function to loop around reading 4 keypresses in a row
# Compare against chosen code
# If match, switch the alarm state
entered = []
while len(entered) < 4:
key = digit()
sleep(0.5)
print key
entered.append( key )
if entered == passcode:
entered = []
print "Code correct"
switch_alarm_state()
else:
# Just clear the keypad buffer if the wrong code went in
# Could say "BAD CODE!" here, or even force immediate alarm perhaps
print "Wrong Code - Try again"
GPIO.output(27, True) # let red LED blink as indicator that code was wrong
time.sleep(1)
GPIO.output(27, False)
entered = []
def switch_alarm_state():
# Function to control the state of the alarm
# If the alarm should be on, run a thread monitoring the sensor
# If the alarm should be off, make sure the thread is stopped
global monitor_thread
if alarm_active.is_set():
# If the alarm is currently set, stop it
print "Alarm was abolished"
GPIO.output(17, False) #switch green LED off
alarm_active.clear() # Signals to the monitor thread that we wish to shut it down
monitor_thread.join() # Wait until the monitor thread has stopped
else:
# Alarm is not currently set, so start the sensor monitor thread going
print "Alarm was activated"
GPIO.output(17, True)
monitor_thread = threading.Thread( target=sensor_monitor )
alarm_active.set()
monitor_thread.start()
def sensor_monitor():
# Function to be run in a separate thread, monitoring the sensor
alarm_timer = None # Variable to hold a timer object that will fire the alarm
while alarm_active.is_set():
#print xrota
if GPIO.input(PINIR):
print "Alarm has been triggered"
if alarm_timer is None:
alarm_timer = threading.Timer( 30.0, fire_alarm )
alarm_timer.start()
sleep(0.5)
# The alarm must have been deactivated to get here
# Stop any outstanding alarms and shutdown
if alarm_timer is not None:
alarm_timer.cancel()
return
def fire_alarm():
# Here you implement the actions to be carried out by an alarm
print "Alarm is send to server"
msg = "Alarm"
publish.single("alarm/demo",msg, hostname="52.17.194.125")
def shutdown_handler( signum, frame ):
# Shut down the child thread nicely
alarm_active.clear()
monitor_thread.join()
if __name__ == '__main__': # This is the Python way to check you are being directly run, and not just imported by another script
signal.signal( signal.SIGINT, shutdown_handler ) # If you stop the script with ctrl+C, make sure it shuts down properly
signal.signal( signal.SIGTERM, shutdown_handler )
try:
while True:
get_keycode()
except KeyboardInterrupt:
GPIO.cleanup()
print "Quit program"

SIGTERM signal not received by python on shutdown command

I have been programming using python for the raspberryPi for several months now and I am trying to make my scripts "well behaved" and wrap up (close files and make sure no writes to SD are being perfomed) upon reception of SIGTERM.
Following advice on SO (1, 2) I am able to handle SIGTERM if I kill the process manually (i.e. kill {process number}) but if I send the shutdown command (i.e. shutdown -t 30 now) my handler never gets called.
I also tried registering for all signals and checking the signal being send on the shutdown event but I am not getting any.
Here's simple example code:
import time
import signal
import sys
def myHandler(signum, frame):
print "Signal #, ", signum
sys.exit()
for i in [x for x in dir(signal) if x.startswith("SIG")]:
try:
signum = getattr(signal, i)
signal.signal(signum, myHandler)
print "Handler added for {}".format(i)
except RuntimeError,m:
print "Skipping %s"%i
except ValueError:
break
while True:
print "goo"
time.sleep(1)
Any ideas will be greatly appreciated .. =)
this code works for me on the raspberry pi, i can see the correct output in the file output.log after the restart:
logging.basicConfig(level=WARNING,
filename='output.log',
format='%(message)s')
def quit():
#cleaning code here
logging.warning('exit')
sys.exit(0)
def handler(signum=None, frame=None):
quit()
for sig in [signal.SIGTERM, signal.SIGHUP, signal.SIGQUIT, signal.SIGKILL]:
signal.signal(sig, handler)
def restart():
command = '/sbin/shutdown -r now'
process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
output = process.communicate()[0]
logging.warning('%s'%output)
restart()
maybe your terminal handles the signal before the python script does, so you can't actually see anything. Try to see the output in a file (with the logging module or the way as you like).

GStreamer: textoverlay is not dynamically updated during play

I wanted to see the current CPU load on top of the video image (source is /dev/video0), and I thought textoverlay element would be perfect for this.
I have constructed a (seemingly) working pipeline, except that the textoverlay keeps showing the value originally set to it.
The pipeline is currently like this:
v4l2src > qtdemux > queue > ffmpegcolorspace > textoverlay > xvimagesink
And code looks like this (I have removed bunch of gtk window, thread handling code and some other signal handling, and only left the relevant part):
#!/usr/bin/env python
import sys, os, time, signal
import pygtk, gtk, gobject
import pygst
pygst.require("0.10")
import gst
# For cpu load stats
import psutil
from multiprocessing import Process, Value, Lock # For starting threads
class Video:
def __init__(self):
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
vbox = gtk.VBox()
window.add(vbox)
self.movie_window = gtk.DrawingArea()
vbox.add(self.movie_window)
window.show_all()
# Set up the gstreamer pipeline
self.pipeline = gst.Pipeline("pipeline")
self.camera = gst.element_factory_make("v4l2src","camera")
self.camera.set_property("device","""/dev/video0""")
self.pipeline.add(self.camera)
# Demuxer
self.demuxer = gst.element_factory_make("qtdemux","demuxer")
# Create a dynamic callback for the demuxer
self.demuxer.connect("pad-added", self.demuxer_callback)
self.pipeline.add(self.demuxer)
# Demuxer doesnt have static pads, but they are created at runtime, we will need a callback to link those
self.videoqueue = gst.element_factory_make("queue","videoqueue")
self.pipeline.add(self.videoqueue)
self.videoconverter = gst.element_factory_make("ffmpegcolorspace","videoconverter")
self.pipeline.add(self.videoconverter)
## Text overlay stuff
self.textoverlay = gst.element_factory_make("textoverlay","textoverlay")
self.overlay_text = "cpu load, initializing"
self.textoverlay.set_property("text",self.overlay_text)
self.textoverlay.set_property("halign", "left")
self.textoverlay.set_property("valign", "top")
self.textoverlay.set_property("shaded-background","true")
self.pipeline.add(self.textoverlay)
self.videosink = gst.element_factory_make("xvimagesink","videosink")
self.pipeline.add(self.videosink)
self.camera.link(self.videoqueue)
gst.element_link_many(self.videoqueue, self.videoconverter, self.textoverlay, self.videosink)
bus = self.pipeline.get_bus()
bus.add_signal_watch()
bus.enable_sync_message_emission()
# Start stream
self.pipeline.set_state(gst.STATE_PLAYING)
# CPU stats calculator thread
cpu_load_thread = Process(target=self.cpu_load_calculator, args=())
cpu_load_thread.start()
def demuxer_callback(self, dbin, pad):
if pad.get_property("template").name_template == "video_%02d":
print "Linking demuxer & videopad"
qv_pad = self.videoqueue.get_pad("sink")
pad.link(qv_pad)
def cpu_load_calculator(self):
cpu_num = len( psutil.cpu_percent(percpu=True))
while True:
load = psutil.cpu_percent(percpu=True)
self.parsed_load = ""
for i in range (0,cpu_num):
self.parsed_load = self.parsed_load + "CPU%d: %s%% " % (i, load[i])
print self.textoverlay.get_property("text") # Correctly prints previous cycle CPU load
self.textoverlay.set_property("text",self.parsed_load)
time.sleep(2)
c = Video()
gtk.threads_init()
gtk.main()
The cpu_load_calculator keeps running in the background, and before I set the new value, I print out the previous using the get_property() function, and it is set properly. However on the actual video outputwindow, it keeps to the initial value..
How can I make the textoverlay to update properly also to the video window ?
The problem is that you are trying to update textoverlay from different Process. And processes unlike threads run in separate address space.
You can switch to threads:
from threading import Thread
...
# CPU stats calculator thread
cpu_load_thread = Thread(target=self.cpu_load_calculator, args=())
cpu_load_thread.start()
Or you can run cpu_load_calculator loop from the main thread. This will work because self.pipeline.set_state(gst.STATE_PLAYING) starts it's own thread in background.
So this will be enough:
# Start stream
self.pipeline.set_state(gst.STATE_PLAYING)
# CPU stats calculator loop
self.cpu_load_calculator()

Categories