mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-23 14:36:24 +00:00
An example to show how to detect frame type using h264parser Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/8338>
368 lines
10 KiB
C
368 lines
10 KiB
C
/* 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;
|
|
}
|