From fd588a50e415feb0ab21c4a3386bd426c8c9043b Mon Sep 17 00:00:00 2001 From: Benjamin Gaignard Date: Wed, 4 Jan 2023 10:35:20 +0100 Subject: [PATCH] codec2json: Add vp82json element This element convert vp8 frame header into human readable json data. Part-of: --- .../docs/plugins/gst_plugins_cache.json | 37 ++ .../ext/codec2json/gstvp82json.c | 371 ++++++++++++++++++ .../ext/codec2json/gstvp82json.h | 33 ++ .../ext/codec2json/meson.build | 27 ++ .../gst-plugins-bad/ext/codec2json/plugin.c | 49 +++ subprojects/gst-plugins-bad/ext/meson.build | 1 + subprojects/gst-plugins-bad/meson_options.txt | 1 + 7 files changed, 519 insertions(+) create mode 100644 subprojects/gst-plugins-bad/ext/codec2json/gstvp82json.c create mode 100644 subprojects/gst-plugins-bad/ext/codec2json/gstvp82json.h create mode 100644 subprojects/gst-plugins-bad/ext/codec2json/meson.build create mode 100644 subprojects/gst-plugins-bad/ext/codec2json/plugin.c diff --git a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json index 77d7b2b235..6859520a68 100644 --- a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json +++ b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json @@ -5359,6 +5359,43 @@ "tracers": {}, "url": "Unknown package origin" }, + "codec2json": { + "description": "Plugin with feature to annotate and format CODEC bitstream in JSON", + "elements": { + "vp82json": { + "author": "Benjamin Gaignard ", + "description": "VP8 to json element", + "hierarchy": [ + "GstVp82json", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "klass": "Transform", + "pad-templates": { + "sink": { + "caps": "video/x-vp8:\n", + "direction": "sink", + "presence": "always" + }, + "src": { + "caps": "text/x-json:\n format: vp8\n", + "direction": "src", + "presence": "always" + } + }, + "rank": "none" + } + }, + "filename": "gstcodec2json", + "license": "LGPL", + "other-types": {}, + "package": "GStreamer Bad Plug-ins", + "source": "gst-plugins-bad", + "tracers": {}, + "url": "Unknown package origin" + }, "codecalpha": { "description": "CODEC Alpha Utilities", "elements": { diff --git a/subprojects/gst-plugins-bad/ext/codec2json/gstvp82json.c b/subprojects/gst-plugins-bad/ext/codec2json/gstvp82json.c new file mode 100644 index 0000000000..e55e070f8b --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/codec2json/gstvp82json.c @@ -0,0 +1,371 @@ +/* + * gstvp82json.c - VP8 parsed bistream to json + * + * Copyright (C) 2023 Collabora + * Author: Benjamin Gaignard + * + * 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. + */ + +/** + * SECTION:element-vp82json + * @title: vp82json + * + * Convert VP8 bitstream parameters to JSON formated text. + * + * ## Example launch line + * ``` + * gst-launch-1.0 filesrc location=/path/to/vp8/file ! parsebin ! vp82json ! filesink location=/path/to/json/file + * ``` + * + * Since: 1.24 + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "gstvp82json.h" + +GST_DEBUG_CATEGORY (gst_vp8_2_json_debug); +#define GST_CAT_DEFAULT gst_vp8_2_json_debug + +struct _GstVp82json +{ + GstElement parent; + + GstPad *sinkpad, *srcpad; + guint frame_counter; + GstVp8Parser parser; + + JsonObject *json; +}; + +static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-vp8") + ); + +static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("text/x-json,format=vp8")); + + +#define gst_vp8_2_json_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstVp82json, gst_vp8_2_json, + GST_TYPE_ELEMENT, + GST_DEBUG_CATEGORY_INIT (gst_vp8_2_json_debug, "vp82json", 0, + "VP8 to json")); + +static void +gst_vp8_2_json_finalize (GObject * object) +{ + GstVp82json *self = GST_VP8_2_JSON (object); + + json_object_unref (self->json); +} + +static gchar * +get_string_from_json_object (JsonObject * object) +{ + JsonNode *root; + JsonGenerator *generator; + gchar *text; + + /* Make it the root node */ + root = json_node_init_object (json_node_alloc (), object); + generator = json_generator_new (); + json_generator_set_indent (generator, 2); + json_generator_set_indent_char (generator, ' '); + json_generator_set_pretty (generator, TRUE); + json_generator_set_root (generator, root); + text = json_generator_to_data (generator, NULL); + + /* Release everything */ + g_object_unref (generator); + json_node_free (root); + return text; +} + +static GstFlowReturn +gst_vp8_2_json_chain (GstPad * sinkpad, GstObject * object, GstBuffer * in_buf) +{ + GstVp82json *self = GST_VP8_2_JSON (object); + JsonObject *json = self->json; + GstVp8ParserResult pres; + GstVp8FrameHdr frame_hdr; + JsonObject *range; + GstBuffer *out_buf; + gchar *json_string; + guint length; + GstMapInfo in_map, out_map; + GstFlowReturn ret = GST_FLOW_OK; + + if (!gst_buffer_map (in_buf, &in_map, GST_MAP_READ)) { + GST_ERROR_OBJECT (self, "Cannot map buffer"); + return GST_FLOW_ERROR; + } + + pres = + gst_vp8_parser_parse_frame_header (&self->parser, &frame_hdr, in_map.data, + in_map.size); + if (pres != GST_VP8_PARSER_OK) { + GST_WARNING_OBJECT (self, "Cannot parse frame header"); + goto unmap; + } + + json_object_set_int_member (json, "frame number", self->frame_counter++); + + if (frame_hdr.key_frame) + json_object_set_string_member (json, "frame type", "keyframe"); + else + json_object_set_string_member (json, "frame type", "interframe"); + + json_object_set_int_member (json, "version", frame_hdr.version); + json_object_set_int_member (json, "show frame", frame_hdr.show_frame); + json_object_set_int_member (json, "data chunk size", + frame_hdr.data_chunk_size); + json_object_set_int_member (json, "first part size", + frame_hdr.first_part_size); + + if (frame_hdr.key_frame) { + JsonArray *array, *token_probs, *mv_probs, *y_prob, *uv_prob; + JsonObject *quant_indices, *mode_probs; + guint i, j, k, l; + + json_object_set_int_member (json, "width", frame_hdr.width); + json_object_set_int_member (json, "height", frame_hdr.height); + json_object_set_int_member (json, "horizontal scale code", + frame_hdr.horiz_scale_code); + json_object_set_int_member (json, "vertical scale code", + frame_hdr.vert_scale_code); + json_object_set_int_member (json, "color space", frame_hdr.color_space); + json_object_set_int_member (json, "clamping type", frame_hdr.clamping_type); + json_object_set_int_member (json, "filter type", frame_hdr.filter_type); + json_object_set_int_member (json, "loop filter level", + frame_hdr.loop_filter_level); + json_object_set_int_member (json, "sharpness level", + frame_hdr.sharpness_level); + json_object_set_int_member (json, "log2 nbr of dct partitions", + frame_hdr.log2_nbr_of_dct_partitions); + + array = json_array_new (); + for (i = 0; i < 8; i++) + json_array_add_int_element (array, frame_hdr.partition_size[i]); + + json_object_set_array_member (json, "partition size", array); + + quant_indices = json_object_new (); + json_object_set_int_member (quant_indices, "y ac qi", + frame_hdr.quant_indices.y_ac_qi); + json_object_set_int_member (quant_indices, "y dc delta", + frame_hdr.quant_indices.y_dc_delta); + json_object_set_int_member (quant_indices, "y2 dc delta", + frame_hdr.quant_indices.y2_dc_delta); + json_object_set_int_member (quant_indices, "y2 ac delta", + frame_hdr.quant_indices.y2_ac_delta); + json_object_set_int_member (quant_indices, "uv dc delta", + frame_hdr.quant_indices.uv_dc_delta); + json_object_set_int_member (quant_indices, "uv ac delta", + frame_hdr.quant_indices.uv_ac_delta); + json_object_set_object_member (json, "quant indices", quant_indices); + + token_probs = json_array_new (); + for (i = 0; i < 4; i++) + for (j = 0; j < 8; j++) + for (k = 0; k < 3; k++) + for (l = 0; l < 11; l++) + json_array_add_int_element (token_probs, + frame_hdr.token_probs.prob[i][j][k][l]); + + json_object_set_array_member (json, "token probabilities", token_probs); + + mv_probs = json_array_new (); + for (i = 0; i < 2; i++) + for (j = 0; j < 19; j++) + json_array_add_int_element (mv_probs, frame_hdr.mv_probs.prob[i][j]); + + json_object_set_array_member (json, "motion vector probabilities", + mv_probs); + + mode_probs = json_object_new (); + y_prob = json_array_new (); + for (i = 0; i < 4; i++) + json_array_add_int_element (y_prob, frame_hdr.mode_probs.y_prob[i]); + + json_object_set_array_member (mode_probs, "y probabilities", y_prob); + + uv_prob = json_array_new (); + for (i = 0; i < 3; i++) + json_array_add_int_element (uv_prob, frame_hdr.mode_probs.uv_prob[i]); + + json_object_set_array_member (mode_probs, "uv probabilities", uv_prob); + + json_object_set_object_member (json, "mode probabilities", mode_probs); + + json_object_set_int_member (json, "refresh entropy probs", + frame_hdr.refresh_entropy_probs); + json_object_set_int_member (json, "refresh last", frame_hdr.refresh_last); + } else { + json_object_set_int_member (json, "refresh golden frame", + frame_hdr.refresh_golden_frame); + json_object_set_int_member (json, "refresh alternate frame", + frame_hdr.refresh_alternate_frame); + json_object_set_int_member (json, "copy buffer to golden", + frame_hdr.copy_buffer_to_golden); + json_object_set_int_member (json, "copy buffer to alternate", + frame_hdr.copy_buffer_to_alternate); + json_object_set_int_member (json, "sign bias golden", + frame_hdr.sign_bias_golden); + json_object_set_int_member (json, "sign bias alternate", + frame_hdr.sign_bias_alternate); + json_object_set_int_member (json, "mb no skip coeff", + frame_hdr.mb_no_skip_coeff); + json_object_set_int_member (json, "prob skip false", + frame_hdr.prob_skip_false); + json_object_set_int_member (json, "prob intra", frame_hdr.prob_intra); + json_object_set_int_member (json, "prob last", frame_hdr.prob_last); + json_object_set_int_member (json, "prob gf", frame_hdr.prob_gf); + } + + range = json_object_new (); + + json_object_set_int_member (range, "rd range", frame_hdr.rd_range); + json_object_set_int_member (range, "rd value", frame_hdr.rd_value); + json_object_set_int_member (range, "rd count", frame_hdr.rd_count); + + json_object_set_object_member (json, "range decoder", range); + + json_object_set_int_member (json, "header size", frame_hdr.header_size); + + json_string = get_string_from_json_object (json); + length = strlen (json_string); + out_buf = gst_buffer_new_allocate (NULL, length, NULL); + gst_buffer_map (out_buf, &out_map, GST_MAP_WRITE); + if (length) + memcpy (&out_map.data[0], json_string, length); + gst_buffer_unmap (out_buf, &out_map); + + g_free (json_string); + + gst_buffer_copy_into (out_buf, in_buf, + GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS | + GST_BUFFER_COPY_METADATA, 0, -1); + gst_pad_push (self->srcpad, out_buf); + +unmap: + gst_buffer_unmap (in_buf, &in_map); + gst_buffer_unref (in_buf); + + return ret; +} + +static gboolean +gst_vp8_2_json_set_caps (GstVp82json * self, GstCaps * caps) +{ + GstCaps *src_caps = + gst_caps_new_simple ("text/x-json", "format", G_TYPE_STRING, "vp8", NULL); + GstEvent *event; + + event = gst_event_new_caps (src_caps); + gst_caps_unref (src_caps); + + return gst_pad_push_event (self->srcpad, event); +} + +static gboolean +gst_vp8_2_json_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) +{ + GstVp82json *self = GST_VP8_2_JSON (parent); + gboolean res = TRUE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CAPS: + { + GstCaps *caps; + + gst_event_parse_caps (event, &caps); + res = gst_vp8_2_json_set_caps (self, caps); + gst_event_unref (event); + break; + } + default: + res = gst_pad_event_default (pad, parent, event); + break; + } + + return res; +} + +static GstStateChangeReturn +gst_vp8_2_json_change_state (GstElement * element, GstStateChange transition) +{ + GstVp82json *self = GST_VP8_2_JSON (element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + self->frame_counter = 0; + break; + default: + break; + } + + return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); +} + +static void +gst_vp8_2_json_class_init (GstVp82jsonClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gstelement_class->change_state = gst_vp8_2_json_change_state; + gobject_class->finalize = gst_vp8_2_json_finalize; + + gst_element_class_add_static_pad_template (gstelement_class, &src_factory); + gst_element_class_add_static_pad_template (gstelement_class, &sink_factory); + + gst_element_class_set_static_metadata (gstelement_class, "Vp82json", + "Transform", + "VP8 to json element", + "Benjamin Gaignard "); +} + +static void +gst_vp8_2_json_init (GstVp82json * self) +{ + self->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink"); + gst_pad_set_chain_function (self->sinkpad, gst_vp8_2_json_chain); + gst_element_add_pad (GST_ELEMENT (self), self->sinkpad); + gst_pad_set_event_function (self->sinkpad, + GST_DEBUG_FUNCPTR (gst_vp8_2_json_sink_event)); + + self->srcpad = gst_pad_new_from_static_template (&src_factory, "src"); + gst_element_add_pad (GST_ELEMENT (self), self->srcpad); + + self->json = json_object_new (); + self->frame_counter = 0; + + gst_vp8_parser_init (&self->parser); +} diff --git a/subprojects/gst-plugins-bad/ext/codec2json/gstvp82json.h b/subprojects/gst-plugins-bad/ext/codec2json/gstvp82json.h new file mode 100644 index 0000000000..eb2df9f85c --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/codec2json/gstvp82json.h @@ -0,0 +1,33 @@ +/* GStreamer + * Copyright (C) 2023 Benjamin Gaignard + * + * 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. + */ + +#ifndef __GST_VP8_2_JSON_H__ +#define __GST_VP8_2_JSON_H__ + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_VP8_2_JSON (gst_vp8_2_json_get_type()) +G_DECLARE_FINAL_TYPE (GstVp82json, + gst_vp8_2_json, GST, VP8_2_JSON, GstElement); + +G_END_DECLS + +#endif /* __GST_VP8_2_TXT_H__ */ diff --git a/subprojects/gst-plugins-bad/ext/codec2json/meson.build b/subprojects/gst-plugins-bad/ext/codec2json/meson.build new file mode 100644 index 0000000000..586790c759 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/codec2json/meson.build @@ -0,0 +1,27 @@ +codec2json_sources = files([ + 'gstvp82json.c', + 'plugin.c', +]) + +cp_args = [ + '-DGST_USE_UNSTABLE_API', + '-DBUILDING_GST_CODECS', + '-DG_LOG_DOMAIN="GStreamer-Codec2json"' +] + +json_dep = dependency('json-glib-1.0', version : '>=1.6.6', fallback : ['json-glib', 'json_glib_dep'], required: get_option('codec2json')) + +if json_dep.found() + gstcodec2json = library('gstcodec2json', + codec2json_sources, + c_args : gst_plugins_bad_args + cp_args, + include_directories : [configinc], + version : libversion, + soversion : soversion, + darwin_versions : osxversion, + install : true, + install_dir : plugins_install_dir, + dependencies : [gstvideo_dep, gstcodecs_dep, json_dep], + ) + plugins += [gstcodec2json] +endif diff --git a/subprojects/gst-plugins-bad/ext/codec2json/plugin.c b/subprojects/gst-plugins-bad/ext/codec2json/plugin.c new file mode 100644 index 0000000000..c1e5bc89d0 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/codec2json/plugin.c @@ -0,0 +1,49 @@ +/* + * Gstreamer + * + * Copyright (C) 2023 Collabora + * Author: Benjamin Gaignard + * + * 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. + */ + +/** + * plugin-codec2json: + * + * Since: 1.24 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstvp82json.h" + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "vp82json", GST_RANK_NONE, + GST_TYPE_VP8_2_JSON)) + return FALSE; + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + codec2json, + "Plugin with feature to annotate and format CODEC bitstream in JSON", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/subprojects/gst-plugins-bad/ext/meson.build b/subprojects/gst-plugins-bad/ext/meson.build index 17195f83d8..31ca793b01 100644 --- a/subprojects/gst-plugins-bad/ext/meson.build +++ b/subprojects/gst-plugins-bad/ext/meson.build @@ -6,6 +6,7 @@ subdir('bs2b') subdir('bz2') subdir('chromaprint') subdir('closedcaption') +subdir('codec2json') subdir('colormanagement') subdir('curl') subdir('dash') diff --git a/subprojects/gst-plugins-bad/meson_options.txt b/subprojects/gst-plugins-bad/meson_options.txt index 55f8e9e0c0..71fcc800c9 100644 --- a/subprojects/gst-plugins-bad/meson_options.txt +++ b/subprojects/gst-plugins-bad/meson_options.txt @@ -15,6 +15,7 @@ option('audiovisualizers', type : 'feature', value : 'auto') option('autoconvert', type : 'feature', value : 'auto') option('bayer', type : 'feature', value : 'auto') option('camerabin2', type : 'feature', value : 'auto') +option('codec2json', type : 'feature', value : 'auto') option('codecalpha', type : 'feature', value : 'auto') option('codectimestamper', type : 'feature', value : 'auto') option('coloreffects', type : 'feature', value : 'auto')