Reputation: 369
So, this works:
gst-launch-1.0 filesrc location=Truck.H264 ! tsdemux ! h264parse ! avdec_h264 ! xvimagesink
And this works:
GstElement* pipeline = gst_parse_launch_full(
"gst-launch-1.0 filesrc location=Truck.H264 ! tsdemux ! h264parse ! avdec_h264 ! xvimagesink",
NULL, GST_PARSE_FLAG_NONE, NULL );
But this does not work:
GstElement* pipeline = gst_pipeline_new(nullptr);
GstElement* filesrc = gst_element_factory_make("filesrc", nullptr);
g_object_set( G_OBJECT(filesrc), "location", "Truck.H264", NULL);
GstElement* tsdemux = gst_element_factory_make("tsdemux", nullptr);
GstElement* h264parse = gst_element_factory_make("h264parse", nullptr);
GstElement* avdec_h264 = gst_element_factory_make("avdec_h264", nullptr);
GstElement* xvimagesink = gst_element_factory_make("xvimagesink", nullptr);
gst_bin_add_many(GST_BIN(pipeline), filesrc, tsdemux, queue, h264parse, avdec_h264, xvimagesink, nullptr);
gst_element_link_many(filesrc, tsdemux, h264parse, avdec_h264, xvimagesink, nullptr);
Now in my inexperienced mind these are all the same and should work the same way. I guess not though.
So I broke the linking down into separate steps and found the problem is here:
gst_element_link(tsdemux, h264parse);
I figured maybe gst_element_link is not as smart at gst_parse_launch_full and doesn't know which pads to link up so I tried:
gst_element_link_pads(tsdemux,"video_%01x_%05x", h264parse, "sink");
And:
GstCaps* caps=gst_caps_new_simple("video/x-h264",
"stream-format",G_TYPE_STRING,"byte-stream",
"alignment",G_TYPE_STRING,"nal",
nullptr);
gst_element_link_filtered(tsdemux, h264parse, caps);
But that didn't work.
I tried sticking a queue between tsdemux and h264parse, but then the error moved to linking tsdemux and the queue:
gst_element_link(tsdemux, queue);
I thought I could link anything to a queue. I guess not.
I tried some other silly things that I didn't think would work, and they didn't, and now I am out of ideas.
I'm probably missing something that every gstreamer programmer knows. If you are one of them, could you please share it with me?
Upvotes: 1
Views: 3538
Reputation: 191
I am following up on the answer provided by user1145922.
I modified the gstreamer docs hello world
example found here: https://gstreamer.freedesktop.org/documentation/application-development/basics/helloworld.html?gi-language=c
I created a working code example for both my ubuntu laptop and a Jetson Xavier depending on the #define at top of the script. The magic is in the g_signal_connect
function which uses the on_pad_added
to handle a dynamically added pad! This means that when the "pad-added" event is thrown, the two pads are linked just like we want.
I called the file "watch_video.c" and ran it like this:
gcc -Wall watch_video.c -o watch_video $(pkg-config --cflags --libs gstreamer-1.0)
./watch_video file.ts
Here is the code!
#include <gst/gst.h>
#include <glib.h>
/* If running on Jetson change to 1 */
#define JETSON 0
static gboolean bus_call(GstBus *bus, GstMessage *msg, gpointer data) {
GMainLoop *loop = (GMainLoop *) data;
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS:
g_print("End of stream\n");
g_main_loop_quit(loop);
break;
case GST_MESSAGE_ERROR: {
gchar *debug;
GError *error;
gst_message_parse_error(msg, &error, &debug);
g_free(debug);
g_printerr("Error: %s\n", error->message);
g_error_free(error);
g_main_loop_quit(loop);
break;
}
default:
break;
}
return TRUE;
}
static void on_pad_added(GstElement *element, GstPad *pad, gpointer data)
{
GstPad *sinkpad;
GstElement *decoder = (GstElement *) data;
/* We can now link this pad with the its corresponding sink pad */
g_print("Dynamic pad created, linking demuxer/decoder\n");
sinkpad = gst_element_get_static_pad(decoder, "sink");
gst_pad_link(pad, sinkpad);
gst_object_unref(sinkpad);
}
int main(int argc, char *argv[])
{
GMainLoop *loop;
GstElement *pipeline, *source, *demuxer, *parser, *decoder, *sink;
GstBus *bus;
guint bus_watch_id;
/* Initialisation */
gst_init(&argc, &argv);
loop = g_main_loop_new(NULL, FALSE);
/* Check input arguments */
if (argc != 2) {
g_printerr("Usage: %s <mpegts filename>\n", argv[0]);
return -1;
}
/* Create gstreamer elements */
pipeline = gst_pipeline_new("video-player");
if (!pipeline) {
g_printerr ("Pipeline could not be created: [pipeline]. Exiting.\n");
return EXIT_FAILURE;
}
source = gst_element_factory_make("filesrc", "file-source");
demuxer = gst_element_factory_make ("tsdemux", "mpegts-demux");
parser = gst_element_factory_make("h264parse", "h264parse-decoder");
if(!JETSON){
decoder = gst_element_factory_make("avdec_h264", "avdec_h264-decoder");
sink = gst_element_factory_make("autovideosink", "video-output");
}
else{
decoder = gst_element_factory_make("nvv4l2decoder", "nvv4l2-decoder");
sink = gst_element_factory_make("nvoverlaysink", "video-output");
}
if(!source || !demuxer || !parser || !decoder || !sink){
g_printerr ("An element could not be created. Exiting.\n");
return EXIT_FAILURE;
}
/* Set up the pipeline */
/* set the input filename to the source element */
g_object_set(G_OBJECT(source), "location", argv[1], NULL);
/* add a message handler */
bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
bus_watch_id = gst_bus_add_watch(bus, bus_call, loop);
gst_object_unref(bus);
/* add all elements into the pipeline: file-source | ts-demuxer | h264parse | decoder | video-output */
gst_bin_add_many(GST_BIN(pipeline), source, demuxer, parser, decoder, sink, NULL);
/* note that the demuxer will be linked to the decoder dynamically.
* The source pad(s) will be created at run time, by the demuxer when it detects the amount and nature of streams.
* Therefore we connect a callback function which will be executed when the "pad-added" is emitted.
*/
gst_element_link(source, demuxer);
gst_element_link_many(parser, decoder, sink, NULL);
g_signal_connect(demuxer, "pad-added", G_CALLBACK(on_pad_added), parser); // link dynamic pad
/* Set the pipeline to "playing" state*/
g_print("Now playing: %s\n", argv[1]);
gst_element_set_state(pipeline, GST_STATE_PLAYING);
/* Iterate */
g_print("Running...\n");
g_main_loop_run(loop);
/* Out of the main loop, clean up nicely */
g_print("Returned, stopping playback\n");
gst_element_set_state(pipeline, GST_STATE_NULL);
g_print("Deleting pipeline\n");
gst_object_unref(GST_OBJECT(pipeline));
g_source_remove(bus_watch_id);
g_main_loop_unref(loop);
return 0;
}
Upvotes: 2
Reputation: 369
OK, I'm a little wiser today. The embarrassing part is that I read this in the documentation and didn't realize the importance of it.
The answer is that not all elements are created with their pads. Sometimes pads are created dynamically when they are needed. The important part is that I can't link an element until its pads are created. So to do that I had to create a function to link the elements when the pad is made like so:
static void linkElements(GstElement* element,GstPad* sourcePad, gpointer sinkElement){
GstPad* sinkPad=gst_element_get_static_pad((GstElement*)sinkElement,"sink");
gst_pad_link(sourcePad,sinkPad);
gst_object_unref(sinkPad);
}
Then I replaced the link element command:
gst_element_link(tsdemux, h264parse);
with a command that will get my linkElements function called when a "pad-added" event is thrown:
g_signal_connect(tsdemux,"pad-added",G_CALLBACK(linkElements),h264parse);
Upvotes: 2