GStreamer: problems with seek and mux operations

Bellaccini, Niccolo niccolo.bellaccini at verizonconnect.com
Wed Feb 9 14:58:22 UTC 2022


Hi,

I'm trying to write a pipeline that reads a video from a source file, seeks
on that video and finally writes the seeked video to a local file.
I used filesrc, decodebin, filesink and a muxer (like matroskamux or
mp4mux) as GStreamer elements.
I noticed that with some muxer, like matroskamux, the seek operations (as
gst_element_seek_simple(...) method) fails and no seek is performed on the
final video. Instead, If I use for example another muxer (like mp4mux or
avimux) the seek on the final video is performed but this one results not
playable by any media player (since in practice is saved as raw data not as
video).
Am I missing something? Do you know if is it possible to seek on a video
read from filesrc and save the seeked video (in playable format) on local?

I also send you in attachment the code I'm using for this task in case it
could be useful.
Thank you,

Niccolò

--------------------------------------------------------------------------------------------------------------------------------------------
PLEASE CONSIDER THE ENVIRONMENT BEFORE PRINTING THIS EMAIL

VERIZON CONNECT CONFIDENTIALITY NOTICE
This message is intended for the addressee only and may contain confidential and/or privileged material. Any review, re-transmission, dissemination, reliance upon or other use of this information by persons or entities other than the addressee is prohibited. If you receive this in error, please contact the sender and delete this message. Thank you.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.freedesktop.org/archives/gstreamer-devel/attachments/20220209/449c8195/attachment.htm>
-------------- next part --------------
#include <gst/gst.h>

typedef struct _CustomData {
    GstElement* pipeline;
    GstElement* source;
    GstElement* decoder;
    GstElement* video_converter;
    GstElement* encoder;
    GstElement* parser;
    GstElement* muxer;
    GstElement* sink;

    gboolean playing;
    gboolean terminate;
    gboolean seek_enabled;
    gboolean seek_done;
    gint64 duration;

} CustomData;

static void pad_added_handler(GstElement* src, GstPad* pad, GstElement* data);

static void handle_message(CustomData* data, GstMessage* msg) {
    GError* err;
    gchar* debug_info;

    switch (GST_MESSAGE_TYPE(msg)) {
        case GST_MESSAGE_ERROR:
            gst_message_parse_error(msg, &err, &debug_info);
            g_printerr("Error received from element %s: %s\n", GST_OBJECT_NAME(msg->src), err->message);
            g_printerr("Debugging information: %s\n", debug_info ? debug_info : "none");
            g_clear_error(&err);
            g_free(debug_info);
            data->terminate = TRUE;
            break;
        case GST_MESSAGE_EOS:
            g_print("\nEOS reached.\n");
            data->terminate = TRUE;
            break;
        case GST_MESSAGE_DURATION:
            // The duration has changed, mark the current one as invalid
            data->duration = GST_CLOCK_TIME_NONE;
            break;
        case GST_MESSAGE_STATE_CHANGED:
            // we are only interested in pipeline change states
            if (GST_MESSAGE_SRC(msg) == GST_OBJECT(data->pipeline)) {
                GstState old_state, new_state, pending_state;
                gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state);
                g_print("Pipeline state change from %s to %s. \n", gst_element_state_get_name(old_state), gst_element_state_get_name(new_state));

                data->playing = (new_state == GST_STATE_PLAYING);
                if (data->playing) {
                    // Check if seeking is possible
                    GstQuery* query;
                    gint64 start, end;
                    query = gst_query_new_seeking(GST_FORMAT_TIME);

                    if (gst_element_query(data->pipeline, query)) {
                        gst_query_parse_seeking(query, NULL, &data->seek_enabled, &start, &end);
                        if (data->seek_enabled) {
                            g_print("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n",
                                    GST_TIME_ARGS(start), GST_TIME_ARGS(end));
                        }
                        else {
                            g_print("Seeking is DISABLED for this stream\n");
                        }
                    }
                    else {
                        g_printerr("Seeking query failed\n");
                    }
                }
            } break;
        default:
            g_printerr("unexpected message received. \n");
            break;
    }
}

static void seek_to_time (GstElement *pipeline, gint64 time_nanoseconds) {
    if (!gst_element_seek (pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
                           GST_SEEK_TYPE_SET, time_nanoseconds,
                           GST_SEEK_TYPE_SET, 20 * GST_SECOND)) {
        g_print ("\n!!SEEK FAILED!!\n");
    } else{
        g_print("\nPERFORMING SEEK!!\n");
    }
}

int main(int argc, char *argv[]){
    CustomData data;
    GstBus *bus;
    GstMessage* msg;
    GstStateChangeReturn ret; // The last return value of an element state change

    data.playing = FALSE;
    data.terminate = FALSE;
    data.seek_enabled = FALSE;
    data.seek_done = FALSE;
    data.duration = GST_CLOCK_TIME_NONE;

    gst_init(&argc, &argv);

    data.source = gst_element_factory_make("filesrc", "source");
    g_object_set(data.source, "location", "2022_2_3T111736.mkv", NULL);
    data.decoder = gst_element_factory_make("decodebin", "decoder");
    data.video_converter = gst_element_factory_make("videoconvert", "converter");
    data.encoder = gst_element_factory_make("x264enc", "encoder");
    data.muxer = gst_element_factory_make("mp4mux", "muxer");
    data.sink = gst_element_factory_make("filesink", "sink");
    g_object_set(data.sink, "location", "seeked_video.mp4", NULL);
    data.pipeline = gst_pipeline_new("retrieve-pipeline");

    if (!data.pipeline || !data.source || !data.decoder || !data.video_converter || !data.encoder || !data.muxer || !data.sink) {
        g_printerr("Not all elements could be created. \n");
        return -1;
    }

    gst_bin_add_many(GST_BIN(data.pipeline), data.source, data.decoder, data.video_converter, data.encoder, data.muxer, data.sink, NULL);

    // Connect each element of the pipeline
    if(!gst_element_link_many(data.source, data.decoder, NULL)) {
        g_printerr("Source could not be linked to the decoder. \n");
        gst_object_unref(data.pipeline);
        return -1;
    }
    // Decoder must be connected at runtime (on demand) since its source pad are created sometimes and not by default
    g_signal_connect(data.decoder, "pad-added", G_CALLBACK(pad_added_handler), data.video_converter);
    if(!gst_element_link_many(data.video_converter,  data.encoder, data.muxer, data.sink, NULL)) {
        g_printerr("Converter and/or Muxer could not be linked to the sink. \n");
        gst_object_unref(data.pipeline);
        return -1;
    }

    // Start playing
    ret = gst_element_set_state(data.pipeline, GST_STATE_PLAYING);
    if (ret == GST_STATE_CHANGE_FAILURE) {
        g_printerr("Unable to set the pipeline to the playing state.\n");
        gst_object_unref(data.pipeline);
        return -1;
    }

    bus = gst_element_get_bus(data.pipeline);
    do{
        msg = gst_bus_timed_pop_filtered (bus, 2 * GST_MSECOND,GstMessageType (GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION));
        if(msg != NULL){
            handle_message(&data, msg);
        }else {
            if(data.playing){
                gint64 current = -1;

                // Query current position of the stream
                if (!gst_element_query_position(data.pipeline, GST_FORMAT_TIME, &current)) {
                    g_printerr("Could not query current position.\n");
                }

                // Query stream duration
                // If we didn't know it yet, query the stream duration
                if (!GST_CLOCK_TIME_IS_VALID(data.duration)) {
                    if (!gst_element_query_duration(data.pipeline, GST_FORMAT_TIME, &data.duration)) {
                        g_printerr("Could not query current duration.\n");
                    }
                }

                // Print current position and total duration
                g_print("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\r",
                        GST_TIME_ARGS(current), GST_TIME_ARGS(data.duration));

                if(data.seek_enabled && !data.seek_done){
                    seek_to_time(data.pipeline, 5000000000);
                    data.seek_done = TRUE;
                }
            }
        }
    }while(!data.terminate);

    // Free resources
    gst_object_unref(bus);
    gst_element_set_state(data.pipeline, GST_STATE_NULL);
    gst_object_unref(data.pipeline);
    return 0;
}

static void pad_added_handler(GstElement* src, GstPad* new_pad, GstElement* data) {
    GstPad* sink_pad = gst_element_get_static_pad(data, "sink");
    GstPadLinkReturn ret;
    GstCaps* new_pad_caps = NULL;
    GstStructure* new_pad_struct = NULL;
    const gchar* new_pad_type = NULL;

    g_print("\nReceived new pad '%s' from '%s'.\n", GST_PAD_NAME(new_pad), GST_ELEMENT_NAME(src));

    if (gst_pad_is_linked(sink_pad)) {
        g_print("Pads are already linked. Ignoring. \n");
        gst_object_unref(sink_pad);
        return;
    }
    else {
        g_print("Processing to linking.\n");
    }

    // Check the new pad's type
    new_pad_caps = gst_pad_get_current_caps(new_pad);
    new_pad_struct = gst_caps_get_structure(new_pad_caps, 0);
    new_pad_type = gst_structure_get_name(new_pad_struct);
    g_print("New pad type %s \n", new_pad_type);

    ret = gst_pad_link(new_pad, sink_pad);
    g_print("%s -> %s\n", gst_pad_get_name(new_pad), gst_pad_get_name(sink_pad));
    if (GST_PAD_LINK_FAILED(ret)) {
        g_print("Type is '%s' but dinamically link failed.\n", new_pad_type);
    }
    else {
        g_print("Link dinamically succeeded (type '%s').\n", new_pad_type);
    }

    if (new_pad_caps != NULL)
        gst_caps_unref(new_pad_caps);

    // Unreference the sink pad
    gst_object_unref(sink_pad);

}


More information about the gstreamer-devel mailing list