mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-03-13 07:02:53 +00:00
examples: Add h264parser example
An example to show how to detect frame type using h264parser Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/8338>
This commit is contained in:
parent
31a3172bfe
commit
a91275a71e
2 changed files with 374 additions and 0 deletions
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,368 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2025 Seungha Yang <seungha@centricular.com>
|
||||
*
|
||||
* 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 <gst/gst.h>
|
||||
#include <gst/codecparsers/gsth264parser.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
Loading…
Reference in a new issue