From a6409525ef5694706b8267037c3f5ac8f6aefb82 Mon Sep 17 00:00:00 2001 From: Mathieu Duponchelle Date: Fri, 23 Oct 2020 00:15:32 +0200 Subject: [PATCH] webpenc: add support for animated WebP The libwebp API doesn't match very well with the GstVideoEncoder API, as it only delivers an unframed bitstream once all pictures have been processed, which means we can only push a single buffer manually on our srcpad on finish(). Supporting animated webp is still valuable, and the feature is behind an opt-in property. Part-of: --- .../gst-plugins-bad/ext/webp/gstwebpenc.c | 92 ++++++++++++++++++- .../gst-plugins-bad/ext/webp/gstwebpenc.h | 5 + .../gst-plugins-bad/ext/webp/meson.build | 5 +- 3 files changed, 96 insertions(+), 6 deletions(-) diff --git a/subprojects/gst-plugins-bad/ext/webp/gstwebpenc.c b/subprojects/gst-plugins-bad/ext/webp/gstwebpenc.c index 788215f8dd..05d64cb822 100644 --- a/subprojects/gst-plugins-bad/ext/webp/gstwebpenc.c +++ b/subprojects/gst-plugins-bad/ext/webp/gstwebpenc.c @@ -37,13 +37,15 @@ enum PROP_LOSSLESS, PROP_QUALITY, PROP_SPEED, - PROP_PRESET + PROP_PRESET, + PROP_ANIMATED, }; #define DEFAULT_LOSSLESS FALSE #define DEFAULT_QUALITY 90 #define DEFAULT_SPEED 4 #define DEFAULT_PRESET WEBP_PRESET_PHOTO +#define DEFAULT_ANIMATED FALSE static void gst_webp_enc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); @@ -57,6 +59,7 @@ static GstFlowReturn gst_webp_enc_handle_frame (GstVideoEncoder * encoder, GstVideoCodecFrame * frame); static gboolean gst_webp_enc_propose_allocation (GstVideoEncoder * encoder, GstQuery * query); +static GstFlowReturn gst_webp_enc_finish (GstVideoEncoder * benc); static GstStaticPadTemplate webp_enc_sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", @@ -134,6 +137,7 @@ gst_webp_enc_class_init (GstWebpEncClass * klass) "Sreerenj Balachandran "); venc_class->start = gst_webp_enc_start; + venc_class->finish = gst_webp_enc_finish; venc_class->stop = gst_webp_enc_stop; venc_class->set_format = gst_webp_enc_set_format; venc_class->handle_frame = gst_webp_enc_handle_frame; @@ -156,6 +160,10 @@ gst_webp_enc_class_init (GstWebpEncClass * klass) "Preset name for visual tuning", GST_WEBP_ENC_PRESET_TYPE, DEFAULT_PRESET, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_ANIMATED, + g_param_spec_boolean ("animated", "Animated", + "Encode an animated webp, instead of several pictures", + DEFAULT_ANIMATED, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); GST_DEBUG_CATEGORY_INIT (webpenc_debug, "webpenc", 0, "WEBP encoding element"); @@ -172,6 +180,7 @@ gst_webp_enc_init (GstWebpEnc * webpenc) webpenc->quality = DEFAULT_QUALITY; webpenc->speed = DEFAULT_SPEED; webpenc->preset = DEFAULT_PRESET; + webpenc->animated = DEFAULT_ANIMATED; webpenc->use_argb = FALSE; webpenc->rgb_format = GST_VIDEO_FORMAT_UNKNOWN; @@ -208,6 +217,19 @@ gst_webp_enc_set_format (GstVideoEncoder * encoder, GstVideoCodecState * state) gst_video_codec_state_unref (enc->input_state); enc->input_state = gst_video_codec_state_ref (state); + if (enc->anim_enc) { + WebPAnimEncoderDelete (enc->anim_enc); + enc->anim_enc = NULL; + } + + if (enc->animated) { + WebPAnimEncoderOptions enc_options = { {0} + }; + WebPAnimEncoderOptionsInit (&enc_options); + enc->anim_enc = + WebPAnimEncoderNew (info->width, info->height, &enc_options); + } + output_state = gst_video_encoder_set_output_state (GST_VIDEO_ENCODER (enc), gst_caps_new_empty_simple ("image/webp"), enc->input_state); @@ -254,7 +276,6 @@ gst_webp_enc_handle_frame (GstVideoEncoder * encoder, GstVideoCodecFrame * frame) { GstWebpEnc *enc = GST_WEBP_ENC (encoder); - GstBuffer *out_buffer = NULL; GstVideoFrame vframe; GST_LOG_OBJECT (enc, "got new frame"); @@ -290,7 +311,24 @@ gst_webp_enc_handle_frame (GstVideoEncoder * encoder, } } - if (WebPEncode (&enc->webp_config, &enc->webp_picture)) { + if (enc->animated) { + /* in milliseconds */ + int timestamp = frame->pts / 1000000; + + enc->next_timestamp = (frame->pts + frame->duration) / 1000000; + + if (!WebPAnimEncoderAdd (enc->anim_enc, &enc->webp_picture, + timestamp, &enc->webp_config)) { + GST_ERROR_OBJECT (enc, "Failed to add WebPPicture: %d (%s)", + enc->webp_picture.error_code, + WebPAnimEncoderGetError (enc->anim_enc)); + gst_video_frame_unmap (&vframe); + return GST_FLOW_ERROR; + } + WebPPictureFree (&enc->webp_picture); + } else if (WebPEncode (&enc->webp_config, &enc->webp_picture)) { + GstBuffer *out_buffer; + WebPPictureFree (&enc->webp_picture); out_buffer = gst_buffer_new_allocate (NULL, enc->webp_writer.size, NULL); @@ -302,6 +340,7 @@ gst_webp_enc_handle_frame (GstVideoEncoder * encoder, gst_buffer_fill (out_buffer, 0, enc->webp_writer.mem, enc->webp_writer.size); free (enc->webp_writer.mem); + frame->output_buffer = out_buffer; } else { GST_ERROR_OBJECT (enc, "Failed to encode WebPPicture"); gst_video_frame_unmap (&vframe); @@ -309,7 +348,7 @@ gst_webp_enc_handle_frame (GstVideoEncoder * encoder, } gst_video_frame_unmap (&vframe); - frame->output_buffer = out_buffer; + return gst_video_encoder_finish_frame (encoder, frame); } @@ -341,6 +380,9 @@ gst_webp_enc_set_property (GObject * object, guint prop_id, case PROP_PRESET: webpenc->preset = g_value_get_enum (value); break; + case PROP_ANIMATED: + webpenc->animated = g_value_get_boolean (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -367,6 +409,9 @@ gst_webp_enc_get_property (GObject * object, guint prop_id, GValue * value, case PROP_PRESET: g_value_set_enum (value, webpenc->preset); break; + case PROP_ANIMATED: + g_value_set_boolean (value, webpenc->animated); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -389,14 +434,53 @@ gst_webp_enc_start (GstVideoEncoder * benc) GST_ERROR_OBJECT (enc, "Failed to Validate the WebPConfig"); return FALSE; } + return TRUE; } +static GstFlowReturn +gst_webp_enc_finish (GstVideoEncoder * benc) +{ + GstWebpEnc *enc = (GstWebpEnc *) benc; + WebPData data = { 0 }; + GstFlowReturn ret = GST_FLOW_OK; + + if (enc->animated) { + if (!WebPAnimEncoderAdd (enc->anim_enc, NULL, enc->next_timestamp, + &enc->webp_config)) { + GST_ERROR_OBJECT (enc, "Failed to flush animation encoder"); + ret = GST_FLOW_ERROR; + goto done; + } + + if (WebPAnimEncoderAssemble (enc->anim_enc, &data)) { + GstBuffer *out = gst_buffer_new_allocate (NULL, data.size, NULL); + + gst_buffer_fill (out, 0, data.bytes, data.size); + + WebPDataClear (&data); + + ret = gst_pad_push (benc->srcpad, out); + } else { + GST_ERROR_OBJECT (enc, "Failed to assemble output animation"); + ret = GST_FLOW_ERROR; + goto done; + } + } + +done: + return ret; +} + static gboolean gst_webp_enc_stop (GstVideoEncoder * benc) { GstWebpEnc *enc = GST_WEBP_ENC (benc); if (enc->input_state) gst_video_codec_state_unref (enc->input_state); + + if (enc->anim_enc) + WebPAnimEncoderDelete (enc->anim_enc); + return TRUE; } diff --git a/subprojects/gst-plugins-bad/ext/webp/gstwebpenc.h b/subprojects/gst-plugins-bad/ext/webp/gstwebpenc.h index b0bbb3036d..ec75e6ad7c 100644 --- a/subprojects/gst-plugins-bad/ext/webp/gstwebpenc.h +++ b/subprojects/gst-plugins-bad/ext/webp/gstwebpenc.h @@ -22,6 +22,7 @@ #include #include #include +#include G_BEGIN_DECLS @@ -54,10 +55,14 @@ struct _GstWebpEnc gfloat quality; guint speed; gint preset; + gboolean animated; gboolean use_argb; GstVideoFormat rgb_format; + WebPAnimEncoder *anim_enc; + int next_timestamp; + WebPEncCSP webp_color_space; struct WebPConfig webp_config; struct WebPPicture webp_picture; diff --git a/subprojects/gst-plugins-bad/ext/webp/meson.build b/subprojects/gst-plugins-bad/ext/webp/meson.build index 7271c9aa82..1d9f63a59a 100644 --- a/subprojects/gst-plugins-bad/ext/webp/meson.build +++ b/subprojects/gst-plugins-bad/ext/webp/meson.build @@ -5,13 +5,14 @@ webp_sources = [ ] webp_dep = dependency('libwebp', version : '>= 0.2.1', required : get_option('webp')) +webpmux_dep = dependency('libwebpmux', version : '>= 0.2.1', required : get_option('webp')) -if webp_dep.found() +if webp_dep.found() and webpmux_dep.found() gstwebp = library('gstwebp', webp_sources, c_args : gst_plugins_bad_args, include_directories : [configinc], - dependencies : [gstvideo_dep, webp_dep], + dependencies : [gstvideo_dep, webp_dep, webpmux_dep], install : true, install_dir : plugins_install_dir, )