InterpolationControlSource with Gst.parse_launch() - gstreamer

My app (in Python), loads the Gstreamer library, parses and launches a pipeline spec that composites subtitles from an SRT file on top of a prepared video from an MP4 file, then creates a control source with a binding to the 'alpha' property of the sink pad of the videomixer element that is linked to the subtitle image source.
First I wrote a small proof-of-concept which works like a champ. If you can run it with an X-windows server (in Unix or Linux for example), you will see a black square on a green background. After a second, the black square gradually fades out over several seconds.
My app has a pipeline that is a bit more complex. Below is a summary of the relevant code:
pipeline_spec = '''
videomixer name=mixer ! ... other stuff downstream
filesrc location=sample_videos/my-video.mp4 ! decodebin name=demuxer ! mixer.sink_0
filesrc location=subtitles.srt ! subparse ! textrender ! mixer.sink_1
demuxer. ! audioconvert ! audioresample ! faac ! muxer.
'''
self.pipeline = Gst.parse_launch(pipeline_spec)
mixer = self.pipeline.get_by_name('mixer')
#vidpad = mixer.get_static_pad('sink_0')
srtpad = mixer.get_static_pad('sink_1')
self.logger.debug([ pad.name for pad in mixer.pads ])
cs = GstController.InterpolationControlSource()
cs.set_property('mode', GstController.InterpolationMode.LINEAR)
binding = GstController.DirectControlBinding.new(srtpad, 'alpha', cs)
cs.add_control_binding(binding)
with open(srtfilepath) as srtfile:
for timestamps in parsesrt.parse(srtfile):
start, end = timestamps
self._set_subtitle_fade(alpha_cs, start, end)
def _set_fade_effect(self, controlsource, start, duration, alpha_begin, alpha_end):
controlsource.set(start, alpha_begin)
controlsource.set(start + duration, alpha_end)
self.logger.debug('set fade-{0} from {1} to {2}'.format('in' if alpha_begin < alpha_end else 'out', start, start + duration))
def _set_subtitle_fade(self, controlsource, start_subtitle, end_subtitle):
self._set_fade_effect(controlsource, start_subtitle, self.DURATION_FADEIN, 0, 1)
self._set_fade_effect(controlsource, end_subtitle - self.DURATION_FADEOUT, self.DURATION_FADEOUT, 1, 0)
One difference between the two pipelines is that in the first example, the videomixer pads are request pads. But in the real app, they turn out to be static pads. And only 'sink_1' is present in the log statement.
DEBUG, ['src', 'sink_1']
I'm not sure why this is so or whether it makes a difference.
When I run the app in a web server and check in a browser, the subtitles appear but they do not fade in or out.
I checked the timestamps and they look good. They are in nanoseconds (10^9).
set fade-in from 2440000000 to 3440000000
set fade-out from 2375000000 to 4375000000
set fade-in from 7476000000 to 8476000000
...
So what stone have I left unturned?

The other big difference between your first and second prototypes is videotestsrc changing to filesrc ! decodebin. gst_parse_launch won't immediately connect decodebin to videomixer. What'll happen is this:
Pipeline is parsed but decodebin doesn't know the contents of filesrc until it de-muxes it. It could be audio or a Powerpoint presentation or a PGP signature or anything. So it returns no src pads initially.
You play the pipeline. decodebin begins receiving data from filesrc, identifies the content as mp4, and demuxes it. It discovers it has video content that matches pads for videomixer and makes the connection to the first open pad.
So what you probably need to do is listen for the pad-added event on decodebin, check that it's the right pad, and then make your binding.
def decodebin_pad_added(self, decodebin, pad):
#return if pad is wrong type
#make the binding to the pad
decodebin.connect("pad_added", decodebin_pad_added)
You can see know that this behavior will be present by running gst-inspect-1.0 on the element in question and examining the pads. You can see that decodebin has a "sometimes" pad template vs. a constant pad that's present on subparse:
subparse:
Pads:
...
SRC: 'src'
Implementation:
Has custom eventfunc(): gst_sub_parse_src_event
Has custom queryfunc(): gst_sub_parse_src_query
Has custom iterintlinkfunc(): gst_pad_iterate_internal_links_default
Pad Template: 'src'
decodebin:
Pad Templates:
SRC template: 'src_%u'
Availability: Sometimes
Capabilities:
ANY

Related

gst_element_request_pad_simple fails for hlssink2

My appsrc pipeline is as follows:
appsrc-openh264enc-h264parse-hlssink2
I first retrieve each element with gst_element_factory_make successfully, each element is checked for nullptrs.
Then I add/link all the elements with 'always' presence via:
gst_bin_add_many(pipeline, appsrc,h264Encoder,h264Parse, NULL);
assert(gst_element_link_many(pipeline, (GstElement*)appsrc, h264Encoder, h264Parse, NULL));
According to the gstreamer plugin docs, h264parse as a 'src' has 'always' but hlssink2 as 'video' is 'request'. So, I try to retrieve the pad to link the two:
//hlspad returns null
GstPad* hlspad = gst_element_request_pad_simple(hlssink2, "video");
//videoParsePad is non-null
GstPad* videoParsePad = gst_element_get_static_pad(h264Parse, "src");
This is native side on Android, anyone know why this isn't working? Everything should be compatible
Using hlssink instead of hlssink2 works, good enough workaround

Google Text to Speech – words not read after break

I'm trying to get Google TTS to read aloud a short set of words and pause between each word. An example of the kind of SSML I send to the Google Cloud:
<speak>chaume<break time="3s"/> cuivré, relatif au cuivre</speak>
The first word gets read, then the voice pauses for three seconds, but everything that comes after gets dropped down. I have successfully had TTS read longer sentences that contained breaks, such as this one, with identical code:
<speak>Se pure vagolavano allora per una Parma stupenda, prima dello <break time="3s"/>scempio della Bassa dei Magnani orrendamente ricostruita.</speak>
There does not seam to be any difference between the two samples, what is it that goes wrong with the first one?
My very slightly customized version of the synthesizing function is the following:
def synthesize_text(ssml_text,file_name,tts_lang,tts_voice_name):
"""Synthesizes speech from the input string of text."""
client = texttospeech.TextToSpeechClient(credentials=credentials)
input_text = texttospeech.SynthesisInput(ssml=ssml_text)
# Note: the voice can also be specified by name.
# Names of voices can be retrieved with client.list_voices().
voice = texttospeech.VoiceSelectionParams(
language_code=tts_lang,
name=tts_voice_name,
ssml_gender=texttospeech.SsmlVoiceGender.FEMALE,
)
audio_config = texttospeech.AudioConfig(
audio_encoding=texttospeech.AudioEncoding.MP3
)
response = client.synthesize_speech(
request={"input": input_text, "voice": voice, "audio_config": audio_config}
)
# The response's audio_content is binary.
with open(f"{home}/Documents/{file_name}.mp3", "wb") as out:
out.write(response.audio_content)
Well it proved enough to delete the following lines:
name=tts_voice_name,
ssml_gender=texttospeech.SsmlVoiceGender.FEMALE
The voice_name was fr-FR-Standard-A, a WaveNet voice. Whereas the language code was fr-CA. I'm quite sure the discrepancy caused the strange behaviour.

Gstreamer 1.0 - Creating custom message/event/signal

I am writing a custom plugin for gstreamer 1.0 in C.
This plugin perform some processing on frames and should send an event to the application whenever some conditions are met.
It should not block the pipeline not interfere with it, just a signal so the application can trigger an action unrelated to the pipeline on the side.
The processing is working well but ... i don't know what to do next.
There is a lot of already existing message like EOS or seek but how do i create my own?
The message should contain custom data and therefore i must create one myself that i could send.
Either by sending events or signal i could not find any examples/documentations/explainations on how to handle custom events from a plugin.
I don't even have a sample code to start with.
Any insight would be appreciated.
Take a look at the fpsdisplaysink element:
https://github.com/GStreamer/gst-plugins-bad/blob/master/gst/debugutils/fpsdisplaysink.c
This one emits signals which the application can connect to. Most interesting probably the signal creation:
g_signal_new ("fps-measurements", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 3, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
and the periodically triggering of said signal:
g_signal_emit (G_OBJECT (self),
fpsdisplaysink_signals[SIGNAL_FPS_MEASUREMENTS], 0, rr, dr,
average_fps);
Detailed information should be found at the GLib signals documentation:
https://developer.gnome.org/gobject/stable/gobject-Signals.html
#
Alternatively you create your own GstMessage and post it on the bus. See the GstMessage documentation:
https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/GstMessage.html
GstMessage *gst_message_new_application (GstObject *src,
GstStructure *structure);
You can then wrap your data inside the GstStructure. And then post the message to the bus with gst_bus_post().
Thank you Florian for your insight which helped me a lot.
I ended up using gst_message_new and gst_post_bus.
For those who might be interested here is the code in python where i implemented a run loop.
def connect(bus, name):
def _connect(f):
bus.connect(name, f)
return f
return _connect
....
bus = self.pipeline.get_bus()
bus.add_signal_watch()
ret = self.pipeline.set_state(Gst.State.PLAYING)
if ret == Gst.StateChangeReturn.FAILURE:
logger.error("ERROR: Unable to set the pipeline to the playing state")
loop = GObject.MainLoop()
print()
#connect(bus, "message::"+Gst.MessageType.get_name(Gst.MessageType.ERROR))
def on_error(bus, message):
err, dbg = message.parse_error()
print("ERROR:", message.src.get_name().encode('utf-8'), ":", err.message.encode('utf-8'))
if dbg:
print("debugging info:", dbg)
loop.quit()
#connect(bus, "message::"+Gst.MessageType.get_name(Gst.MessageType.EOS))
def on_eos(bus, message):
logger.info("End-Of-Stream reached")
loop.quit()
.... other events
try:
loop.run()
except KeyboardInterrupt:
pass
print("START : Pipeline has stopped")
self.pipeline.set_state(Gst.State.NULL)

Infinite loop in Erlang process

I'm very new to Erlang and tried to implement a simple class that has some methods to simulate a database. insert() just inserts a key -> value in the process map, and retrieve() just returns the value from the map. However, I am getting stuck in the loop(). What am I doing wrong?
-module(db).
-export([start/0,stop/0,retrieve/1,insert/2]).
start() ->
register(db, spawn(fun() ->
loop()
end)
),
{started}.
insert(Key, Value) ->
rpc({insert, Key, Value}).
retrieve(Key) ->
rpc({retrieve, Key}).
stop() ->
rpc({stop}).
rpc(Request) ->
db ! {self(), Request},
receive
{db, Reply} ->
Reply
end.
loop() ->
receive
{rpc, {insert, Key, Value}} ->
put(Key, Value),
rpc ! {db, done},
loop();
{rpc, {retrieve, Key}} ->
Val = get(Key),
rpc ! {db, Val},
loop();
{rpc, {stop}} ->
exit(db,ok),
rpc ! {db, stopped}
end.
So, after compiling:
I first call db:start().
and then when trying db:insert("A", 1)., it gets stucked.
Thank you
The problem is in loop/0 function. You're using rpc atom to pattern match the messages received ({rpc, {insert, Key, Value}}), but, as you can see on rpc/1 function, you always send messages with the format {self(), Request} to db process.
self() function returns a PID in the format <X.Y.Z>, which will never match against the atom rpc
For example, let's say you're trying to insert some data using the function insert/2 and self() would return the PID <0.36.0>. When rpc/1 sends the message, on the line db ! {self(), {insert, Key, Value}}, loop/0 will receive {<0.36.0>, {insert, Key, Value}} message, which will never match against {rpc, {insert, Key, Value}}, because rpc is an atom.
The solution is to change rpc atom to a variable, like this:
loop() ->
receive
{Rpc, {insert, Key, Value}} ->
put(Key, Value),
Rpc ! {db, done},
loop();
{Rpc, {retrieve, Key}} ->
Val = get(Key),
Rpc ! {db, Val},
loop();
{Rpc, {stop}} ->
Rpc ! {db, stopped},
exit(whereis(db),ok)
end.
Erlang variables start with capital letters, that's why I used Rpc, instead of rpc.
P.S.: Actually, you had two other problems:
In the last part of loop/0, where you handle stop message, you call exit(db, ok) before you actually answer to rpc. In that case, you'd never receive the {db, stopped} message back from db process, which would be dead by that time. That's why I've changed the order, putting the exit/2 call after Rpc ! {db, stopped}.
When you call exit/2, you were passing db, which is an atom, as the first argument, but exit/2 function expects an PID as first argument, which would raise a badarg error. That's why I've changed it to exit(whereis(db), ok).
Let's walk through this a bit more carefully. What do you mean by "rpc"? "Remote Procedure Call" -- sure. But everything in Erlang is an rpc, so we tend not to use that term. Instead we distinguish between synchronous messages (where the caller blocks, waiting on a response) and aynchronous messages (where the caller just fires off a message and runs off without a care in the world). We tend to use the term "call" for a synch message and "cast" for an asynch message.
We can write that easily, as a call looks a lot like your rpc above, with the added idiom in Erlang of adding a unique reference value to tag the message and monitoring the process we sent a message to just in case it crashes (so we don't get left hanging, waiting for a response that will never come... which we'll touch on in your code in a bit):
% Synchronous handler
call(Proc, Request) ->
Ref = monitor(process, Proc),
Proc ! {self(), Ref, Request},
receive
{Ref, Res} ->
demonitor(Ref, [flush]),
Res;
{'DOWN', Ref, process, Proc, Reason} ->
{fail, Reason}
after 1000 ->
demonitor(Ref, [flush]),
{fail, timeout}
end.
Cast is a bit easier:
cast(Proc, Message) ->
Proc ! Message,
ok.
The definition of call above means that the process we are sending to will receive a message of the form {SenderPID, Reference, Message}. Note that this is different than {sender, reference, message}, as lower-case values are atoms, meaning they are their own values.
When we receive messages we are matching on the shape and values of the message received. That means if I have
receive
{number, X} ->
do_stuff(X)
end
in my code and the process sitting in that receive get a message {blah, 25} it will not match. If it receives another message {number, 26} then it will match, that receive will call do_stuff/1 and the process will continue on. (These two things -- the difference between atoms and Variables and the way matching in receive works -- is why your code is hanging.) The initial message, {blah, 25} will still be in the mailbox, though, at the front of the queue, so the next receive has a chance to match on it. This property of mailboxes is immensely useful sometimes.
But what does a catch-all look like?
Above you are expecting three kinds of messages:
{insert, Key, Value}
{retrieve, Key}
stop
You dressed them up differently, but that's the business end of what you are trying to do. Running the insert message through the call/2 function I wrote above it would wind up looking like this: {From, Ref, {insert, Key, Value}}. So if we expect any response from the process's receive loop we will need to match on that exact form. How do we catch unexpected messages or badly formed ones? At the end of the receive clause we can put a single naked variable to match on anything else:
loop(State) ->
receive
{From, Ref, {insert, Key, Value}} ->
NewState = insert(Key, Value, State),
From ! {Ref, ok},
loop(NewState);
{From, Ref, {retrieve, Key}} ->
Value = retrieve(Key, State),
From ! {Ref, {ok, Value}},
loop(State);
{From, Ref, stop} ->
ok = io:format("~tp: ~tp told me to stop!~n", [self(), From]),
From ! {Ref, shutting_down},
exit(normal)
Unexpected ->
ok = io:format("~tp: Received unexpected message: ~tp~n",
[self(), Unexpected]),
loop(State)
end.
You will notice that I am not using the process dictionary. DO NOT USE THE PROCESS DICTIONARY. This isn't what it is for. You'll overwrite something important. Or drop something important. Or... bleh, just don't do it. Use a dict or map or gb_tree or whatever instead, and pass it through as the process' State variable. This will become a very natural thing for you once you start writing OTP code later on.
Toy around with these things a bit and you will soon be happily spamming your processes to death.

Use metadata in filename when saving harbor input Liquidsoap

So I have an instance of Liquidsoap, which I am using to stream to an Icecast server.
I'd like to record any live broadcasts that take place automatically, which I am now doing and is working well.
What I'd like to do is use the metadata (specifically the songname) of the live show when creating the mp3 archive.
#!/usr/local/bin/liquidsoap
set("log.file",true)
set("log.file.path","/var/log/liquidsoap/radiostation.log")
set("log.stdout",true)
set("log.level",3)
#-------------------------------------
set("harbor.bind_addr","0.0.0.0")
#-------------------------------------
backup_playlist = playlist("/home/radio/playlists/playlist.pls",conservative=true,reload_mode="watch")
output.dummy(fallible=true,backup_playlist)
#-------------------------------------
live_dj = input.harbor(id="live",port=9000,password="XXX", "live")
date = '%m-%d-%Y'
time = '%H:%M:%S'
output.file(%mp3, "/var/www/recorded-shows/#{Title} - Recorded On #{date} At #{time}.mp3", live_dj, fallible=true)
#time_stamp = '%m-%d-%Y, %H:%M:%S'
#output.file(%mp3, "/var/www/recorded-shows/live_dj_#{time_stamp}.mp3", live_dj, fallible=true)
#-------------------------------------
on_fail = single("/home/radio/fallback/Enei -The Moment Feat DRS.mp3")
#-------------------------------------
source = fallback(track_sensitive=false,
[live_dj, backup_playlist, on_fail])
# We output the stream to icecast
output.icecast(%mp3,id="icecast",
mount="myradio.mp3",
host="localhost", password="XXX",
icy_metadata="true",description="cool radio",
url="http://myradio.fm",
source)
I have added #{title} where I would like my song title to appear, sadly though I am unable to get this populate.
My Dj's use BUTT and the show title is connected as part of their connection, so the data should be available pre recording.
Any advice is much appreciated!
This is far from being as easy as it seems.
The title metadata is dynamic, thus not available as a variable on script initialization
The filename argument of output.file is compiled when the script is initialized
A solution would consist in:
Defining a variable reference title to populate with live metadata
Output to a temporary file
Rename the file on close using on_close argument with output.file (in this case, we can just prepend the title)
This would give the following code (on a Linux box, change mv with ren on Windows):
date = '%m-%d-%Y'
time = '%H:%M:%S'
# Title is a reference
title = ref ""
# Populate with metadata
def get_title(m)
title := m['title']
end
live_dj = on_metadata(get_title,live_dj)
# Rename file on close
def on_close(filename)
# Generate new file name
new_filename = "#{path.dirname(filename)}/#{!title} - #{basename(filename)}"
# Rename file
system("mv '#{filename}' '#{new_filename}'")
end
output.file(on_close=on_close, %mp3, "/var/www/recorded-shows/Recorded On #{date} At #{time}.mp3", live_dj, fallible=true)
I tested a similar scenario and it works just well. Just beware that this will create a new file every time a DJ disconnects or updates the title. Also keep in mind that time stamps will be resolved by output.file.
This is based on the following example from a Liquidsoap dev: https://github.com/savonet/liquidsoap/issues/661#issuecomment-439935854)