diff --git a/subprojects/gst-plugins-bad/tests/examples/codecparsers/meson.build b/subprojects/gst-plugins-bad/tests/examples/codecparsers/meson.build index 38aaed6190..860aa92e77 100644 --- a/subprojects/gst-plugins-bad/tests/examples/codecparsers/meson.build +++ b/subprojects/gst-plugins-bad/tests/examples/codecparsers/meson.build @@ -9,3 +9,9 @@ executable('parse-vp8', 'parse-vp8.c', dependencies : [gstcodecparsers_dep, gst_dep], c_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'], install: false) + +executable('parse-h264-drop-frames', 'parse-h264-drop-frames.c', + include_directories : [configinc], + dependencies : [gstcodecparsers_dep, gst_dep], + c_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'], + install: false) diff --git a/subprojects/gst-plugins-bad/tests/examples/codecparsers/parse-h264-drop-frames.c b/subprojects/gst-plugins-bad/tests/examples/codecparsers/parse-h264-drop-frames.c new file mode 100644 index 0000000000..2938c8a1c0 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/codecparsers/parse-h264-drop-frames.c @@ -0,0 +1,368 @@ +/* GStreamer + * Copyright (C) 2025 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + +typedef struct +{ + GstH264NalParser *parser; + guint nalu_len_size; + gboolean drop_p; +} ParserData; + +static void +parser_data_free (ParserData * data) +{ + gst_h264_nal_parser_free (data->parser); + g_free (data); +} + +static GstH264ParserResult +handle_nalu (GstH264NalParser * parser, GstH264NalUnit * nalu, + gboolean * is_pframe, gboolean * is_bframe) +{ + GstH264ParserResult ret = GST_H264_PARSER_OK; + GstH264SliceHdr slice; + + switch (nalu->type) { + case GST_H264_NAL_SPS: + case GST_H264_NAL_PPS: + /* Stores SPS and PPS, required to parse slice */ + ret = gst_h264_parser_parse_nal (parser, nalu); + break; + case GST_H264_NAL_SLICE_IDR: + /* slice indicates IDR already, do not need to parse slice */ + break; + case GST_H264_NAL_SLICE: + case GST_H264_NAL_SLICE_DPA: + case GST_H264_NAL_SLICE_DPB: + case GST_H264_NAL_SLICE_DPC: + case GST_H264_NAL_SLICE_EXT: + /* To detect frame type, we should parse slice header */ + ret = gst_h264_parser_parse_slice_hdr (parser, nalu, &slice, + FALSE, FALSE); + if (ret == GST_H264_PARSER_OK) { + if (GST_H264_IS_B_SLICE (&slice)) + *is_bframe = TRUE; + + if (GST_H264_IS_P_SLICE (&slice)) + *is_pframe = TRUE; + } + break; + default: + break; + } + + return ret; +} + +static GstPadProbeReturn +parse_src_probe_bytestream (GstPad * pad, GstPadProbeInfo * info, + ParserData * data) +{ + GstH264NalParser *parser = data->parser; + GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER (info); + GstMapInfo map; + GstH264ParserResult ret = GST_H264_PARSER_OK; + GstH264NalUnit nalu; + gboolean is_bframe = FALSE; + gboolean is_pframe = FALSE; + + memset (&nalu, 0, sizeof (nalu)); + + gst_buffer_map (buffer, &map, GST_MAP_READ); + + ret = gst_h264_parser_identify_nalu (parser, map.data, 0, map.size, &nalu); + + /* This is a case where last nalu in bytestream AU, + * since there's no next startcode. This is expected and not an error */ + if (ret == GST_H264_PARSER_NO_NAL_END) + ret = GST_H264_PARSER_OK; + + while (ret == GST_H264_PARSER_OK) { + ret = handle_nalu (parser, &nalu, &is_pframe, &is_bframe); + + /* Prepare to parse next nalu if any */ + if (ret == GST_H264_PARSER_OK) { + ret = gst_h264_parser_identify_nalu (parser, map.data, + nalu.offset + nalu.size, map.size, &nalu); + } + + /* Again, this is expected case and not an error */ + if (ret == GST_H264_PARSER_NO_NAL_END) + ret = GST_H264_PARSER_OK; + } + + gst_buffer_unmap (buffer, &map); + + if (is_bframe) { + gst_println ("Dropping bframe %" GST_PTR_FORMAT, buffer); + return GST_PAD_PROBE_DROP; + } + + if (is_pframe && data->drop_p) { + gst_println ("Dropping P frame %" GST_PTR_FORMAT, buffer); + return GST_PAD_PROBE_DROP; + } + + return GST_PAD_PROBE_OK; +} + +static void +parse_codec_data (ParserData * data, GstMapInfo * map) +{ + GstH264DecoderConfigRecord *config = NULL; + GstH264NalUnit *nalu; + guint i; + GstH264ParserResult ret; + + ret = gst_h264_parser_parse_decoder_config_record (data->parser, + map->data, map->size, &config); + if (ret != GST_H264_PARSER_OK) { + gst_printerrln ("Couldn't parse codec data"); + return; + } + + data->nalu_len_size = config->length_size_minus_one + 1; + for (i = 0; i < config->sps->len; i++) { + GstH264SPS sps; + + nalu = &g_array_index (config->sps, GstH264NalUnit, i); + + if (nalu->type != GST_H264_NAL_SPS) + continue; + + ret = gst_h264_parser_parse_sps (data->parser, nalu, &sps); + if (ret != GST_H264_PARSER_OK) { + gst_printerrln ("Couldn't parse SPS"); + goto out; + } + + gst_h264_sps_clear (&sps); + } + + for (i = 0; i < config->pps->len; i++) { + GstH264PPS pps; + + nalu = &g_array_index (config->pps, GstH264NalUnit, i); + if (nalu->type != GST_H264_NAL_PPS) + continue; + + ret = gst_h264_parser_parse_pps (data->parser, nalu, &pps); + + if (ret != GST_H264_PARSER_OK) { + gst_printerrln ("Couldn't parse SPS"); + goto out; + } + + gst_h264_pps_clear (&pps); + } + +out: + gst_h264_decoder_config_record_free (config); +} + +static GstPadProbeReturn +parse_src_probe_avc (GstPad * pad, GstPadProbeInfo * info, ParserData * data) +{ + GstH264NalParser *parser = data->parser; + GstBuffer *buffer; + GstMapInfo map; + GstH264ParserResult ret = GST_H264_PARSER_OK; + GstH264NalUnit nalu; + gboolean is_bframe = FALSE; + gboolean is_pframe = FALSE; + + /* Extract codec data and parse SPS/PPS */ + if (GST_IS_EVENT (GST_PAD_PROBE_INFO_DATA (info))) { + GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info); + if (GST_EVENT_TYPE (event) == GST_EVENT_CAPS) { + GstCaps *caps; + GstStructure *s; + const GValue *value; + + gst_event_parse_caps (event, &caps); + s = gst_caps_get_structure (caps, 0); + value = gst_structure_get_value (s, "codec_data"); + if (value && G_VALUE_TYPE (value) == GST_TYPE_BUFFER) { + buffer = (GstBuffer *) g_value_dup_boxed (value); + gst_buffer_map (buffer, &map, GST_MAP_READ); + + parse_codec_data (data, &map); + gst_buffer_unmap (buffer, &map); + gst_buffer_unref (buffer); + } + } + + return GST_PAD_PROBE_OK; + } + + buffer = GST_PAD_PROBE_INFO_BUFFER (info); + + memset (&nalu, 0, sizeof (nalu)); + + gst_buffer_map (buffer, &map, GST_MAP_READ); + ret = gst_h264_parser_identify_nalu_avc (parser, + map.data, 0, map.size, data->nalu_len_size, &nalu); + + while (ret == GST_H264_PARSER_OK) { + ret = handle_nalu (parser, &nalu, &is_pframe, &is_bframe); + + /* Prepare to parse next nalu if any */ + if (ret == GST_H264_PARSER_OK) { + ret = gst_h264_parser_identify_nalu_avc (parser, map.data, + nalu.offset + nalu.size, map.size, data->nalu_len_size, &nalu); + } + } + + gst_buffer_unmap (buffer, &map); + + if (is_bframe) { + gst_println ("Dropping B frame %" GST_PTR_FORMAT, buffer); + return GST_PAD_PROBE_DROP; + } + + if (is_pframe && data->drop_p) { + gst_println ("Dropping P frame %" GST_PTR_FORMAT, buffer); + return GST_PAD_PROBE_DROP; + } + + return GST_PAD_PROBE_OK; +} + +static gboolean +bus_handler (GstBus * bus, GstMessage * msg, GMainLoop * loop) +{ + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_ERROR: + gst_printerrln ("Got ERROR"); + g_main_loop_quit (loop); + break; + case GST_MESSAGE_EOS: + gst_println ("Got EOS"); + g_main_loop_quit (loop); + break; + default: + break; + } + + return G_SOURCE_CONTINUE; +} + +gint +main (gint argc, gchar ** argv) +{ + GstElement *pipeline; + GstElement *parse; + GError *err = NULL; + GstPad *pad; + GMainLoop *loop; + ParserData *data; + gchar *location = NULL; + gchar *pipeline_str = NULL; + gboolean use_avc = FALSE; + gboolean drop_p = FALSE; + GstBus *bus; + guint bus_watch_id; + GOptionEntry options[] = { + {"use-avc", 0, 0, G_OPTION_ARG_NONE, &use_avc, + "Use stream-format=avc instead of byte-stream", NULL} + , + {"drop-p", 0, 0, G_OPTION_ARG_NONE, &drop_p, "Drop P frames", NULL} + , + {"location", 0, 0, G_OPTION_ARG_STRING, &location, + "H.264 encoded test file location", NULL}, + {NULL} + }; + GOptionContext *option_ctx; + gboolean ret; + + option_ctx = g_option_context_new ("GstH264Parser example"); + g_option_context_add_main_entries (option_ctx, options, NULL); + g_option_context_add_group (option_ctx, gst_init_get_option_group ()); + ret = g_option_context_parse (option_ctx, &argc, &argv, &err); + g_option_context_free (option_ctx); + + if (!ret) { + gst_printerrln ("Option parsing failed: %s", err->message); + g_clear_error (&err); + return 1; + } + + if (!location) { + gst_printerrln ("Location must be specified"); + return 1; + } + + pipeline_str = g_strdup_printf ("filesrc location=%s ! parsebin ! " + "h264parse name=parse ! video/x-h264,stream-format=%s,alignment=au ! " + "decodebin ! videoconvert ! autovideosink", location, + use_avc ? "avc" : "byte-stream"); + + pipeline = gst_parse_launch (pipeline_str, &err); + g_free (pipeline_str); + + if (!pipeline) { + gst_printerrln ("Couldn't create pipeline, error: %s", err->message); + return 1; + } + + data = g_new0 (ParserData, 1); + data->parser = gst_h264_nal_parser_new (); + data->nalu_len_size = 4; + data->drop_p = drop_p; + + loop = g_main_loop_new (NULL, FALSE); + + parse = gst_bin_get_by_name (GST_BIN (pipeline), "parse"); + pad = gst_element_get_static_pad (parse, "src"); + + if (use_avc) { + /* In case of avc format, SPS/PPS is signalled via caps. Probe will + * parse caps to extract SPS/PPS in addition to buffers */ + gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER | + GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + (GstPadProbeCallback) parse_src_probe_avc, data, + (GDestroyNotify) parser_data_free); + } else { + gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, + (GstPadProbeCallback) parse_src_probe_bytestream, data, + (GDestroyNotify) parser_data_free); + } + + gst_object_unref (parse); + gst_object_unref (pad); + + bus = gst_element_get_bus (pipeline); + bus_watch_id = gst_bus_add_watch (bus, (GstBusFunc) bus_handler, loop); + + gst_element_set_state (pipeline, GST_STATE_PLAYING); + + g_main_loop_run (loop); + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); + + g_source_remove (bus_watch_id); + g_main_loop_unref (loop); + + return 0; +}