Is it possible to remux an mp4 file from a certain key frame?

Hi,

I’m trying to create a program that generates a video clip from a bigger mp4 file. It works by decoding and encoding, but I wonder whether it is possible to just copy the video stream from a certain key frame.

In gstreamer, seeking to a specific key frame can be done with the KEY_UNIT flag. If it works, the process can be much faster. The demo program is like this (main.c):

#include <gst/gst.h>

GMainLoop* loop = NULL;
GstElement* pipeline = NULL;

void print_gst_message(GstMessage* msg)
{
    GError* err = NULL;
    gchar* debug_info = NULL;

    if (msg->type == GST_MESSAGE_ERROR) {
        gst_message_parse_error(msg, &err, &debug_info);
        g_printerr("Error: [%s] %s\n", msg->src->name, err->message);
        if (debug_info) {
            g_printerr("Error: [%s]  - %s", msg->src->name, debug_info);
        }
    }
    else if (msg->type == GST_MESSAGE_WARNING) {
        gst_message_parse_warning(msg, &err, &debug_info);
        g_printerr("Warn: [%s] %s\n", msg->src->name, err->message);
        if (debug_info) {
            g_printerr("Warn: [%s]  - %s", msg->src->name, debug_info);
        }
    }
    else if (msg->type == GST_MESSAGE_INFO) {
        gst_message_parse_info(msg, &err, &debug_info);
        g_printerr("Info: [%s] %s\n", msg->src->name, err->message);
        if (debug_info) {
            g_printerr("Info: [%s]  - %s", msg->src->name, debug_info);
        }
    }

    g_clear_error(&err);
    g_free(debug_info);
}

gboolean gst_bus_handler(GstBus* bus, GstMessage* message, gpointer data)
{
    GstMessageType type = message->type;

    if (type == GST_MESSAGE_CLOCK_LOST) {
        gst_element_set_state(pipeline, GST_STATE_PAUSED);
        gst_element_set_state(pipeline, GST_STATE_PLAYING);
    }
    else if (type == GST_MESSAGE_EOS) {
        g_main_loop_quit(loop);
    }
    else if (type == GST_MESSAGE_INFO) {
        print_gst_message(message);
    }
    else if (type == GST_MESSAGE_WARNING) {
        print_gst_message(message);
    }
    else if (type == GST_MESSAGE_ERROR) {
        print_gst_message(message);
    }
    else if (type == GST_MESSAGE_LATENCY) {
        gst_bin_recalculate_latency(GST_BIN(pipeline));
    }

    return TRUE;
}

int main(int argc, char* argv[])
{
    gst_init(NULL, NULL);

    // create the pipeline
    //  - filesrc ---> demux ---> h264parse ---> mp4mux --> filesink
    pipeline = gst_parse_launch(
        "filesrc location=output.mp4 ! qtdemux name=demux"
        " demux.video_0 ! h264parse ! mux.video_0"
        " mp4mux name=mux ! filesink location=clip.mp4", NULL);
    if (!pipeline) {
        return 1;
    }

    // create the main loop
    loop = g_main_loop_new(NULL, FALSE);

    // set gst bus handler
    GstBus* bus = gst_element_get_bus(pipeline);
    guint watch_id = gst_bus_add_watch(bus, gst_bus_handler, NULL);
    gst_object_unref(bus);

    // pause
    GstStateChangeReturn ret = gst_element_set_state(pipeline, GST_STATE_PAUSED);
    if (ret == GST_STATE_CHANGE_FAILURE) {
        g_printerr("Unable to set the pipeline to the PAUSED state\n");
        goto end;
    }

    // seek
#if 1
    if (!gst_element_seek(
        pipeline, 1.0, GST_FORMAT_TIME, (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
        GST_SEEK_TYPE_SET, 30 * GST_SECOND,
        GST_SEEK_TYPE_SET, 90 * GST_SECOND)) {
        g_printerr("Seek failed\n");
        goto end;
    }
#endif

    // play
    ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);
    if (ret == GST_STATE_CHANGE_FAILURE) {
        g_printerr("Unable to set the pipeline to the PLAYING state\n");
    }
    else {
        g_printerr("Entering main loop\n");
        g_main_loop_run(loop);
    }

end:
    g_printerr("Setting pipeline state to NULL\n");
    gst_element_set_state(pipeline, GST_STATE_NULL);
    g_source_remove(watch_id);
    g_main_loop_unref(loop);
    g_printerr("Freeing pipeline\n");
    gst_object_unref(pipeline);
    gst_deinit();
    return 0;
}

I find that if gst_element_seek() is disabled, the pipeline can generate a new file successfully with full range.

If gst_element_seek() is executed, it throws an exception: “GStreamer-CRITICAL **: gst_segment_do_seek: assertion ‘segment->format == format’ failed”.

Is there something wrong with the code?

Testing environment:

  • Windows 11 23H2 64 bit
  • GStreamer 1.24.8 msvc x86_64
  • NVIDIA RTX 3060 Laptop (522.06, 31.0.15.2226)

The video file is generated with nvh264enc / mfaacenc / mp4mux with default settings.

I saw a similar issue here:

https://discourse.gstreamer.org/t/seeking-pipeline-performing-mpeg-ts-mp4-conversion/984