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: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5994>
This commit is contained in:
Mathieu Duponchelle 2020-10-23 00:15:32 +02:00 committed by GStreamer Marge Bot
parent 351f823704
commit a6409525ef
3 changed files with 96 additions and 6 deletions

View file

@ -37,13 +37,15 @@ enum
PROP_LOSSLESS, PROP_LOSSLESS,
PROP_QUALITY, PROP_QUALITY,
PROP_SPEED, PROP_SPEED,
PROP_PRESET PROP_PRESET,
PROP_ANIMATED,
}; };
#define DEFAULT_LOSSLESS FALSE #define DEFAULT_LOSSLESS FALSE
#define DEFAULT_QUALITY 90 #define DEFAULT_QUALITY 90
#define DEFAULT_SPEED 4 #define DEFAULT_SPEED 4
#define DEFAULT_PRESET WEBP_PRESET_PHOTO #define DEFAULT_PRESET WEBP_PRESET_PHOTO
#define DEFAULT_ANIMATED FALSE
static void gst_webp_enc_set_property (GObject * object, guint prop_id, static void gst_webp_enc_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec); const GValue * value, GParamSpec * pspec);
@ -57,6 +59,7 @@ static GstFlowReturn gst_webp_enc_handle_frame (GstVideoEncoder * encoder,
GstVideoCodecFrame * frame); GstVideoCodecFrame * frame);
static gboolean gst_webp_enc_propose_allocation (GstVideoEncoder * encoder, static gboolean gst_webp_enc_propose_allocation (GstVideoEncoder * encoder,
GstQuery * query); GstQuery * query);
static GstFlowReturn gst_webp_enc_finish (GstVideoEncoder * benc);
static GstStaticPadTemplate webp_enc_sink_factory = static GstStaticPadTemplate webp_enc_sink_factory =
GST_STATIC_PAD_TEMPLATE ("sink", GST_STATIC_PAD_TEMPLATE ("sink",
@ -134,6 +137,7 @@ gst_webp_enc_class_init (GstWebpEncClass * klass)
"Sreerenj Balachandran <sreerenjb@gnome.org>"); "Sreerenj Balachandran <sreerenjb@gnome.org>");
venc_class->start = gst_webp_enc_start; venc_class->start = gst_webp_enc_start;
venc_class->finish = gst_webp_enc_finish;
venc_class->stop = gst_webp_enc_stop; venc_class->stop = gst_webp_enc_stop;
venc_class->set_format = gst_webp_enc_set_format; venc_class->set_format = gst_webp_enc_set_format;
venc_class->handle_frame = gst_webp_enc_handle_frame; 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", "Preset name for visual tuning",
GST_WEBP_ENC_PRESET_TYPE, DEFAULT_PRESET, GST_WEBP_ENC_PRESET_TYPE, DEFAULT_PRESET,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); 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, GST_DEBUG_CATEGORY_INIT (webpenc_debug, "webpenc", 0,
"WEBP encoding element"); "WEBP encoding element");
@ -172,6 +180,7 @@ gst_webp_enc_init (GstWebpEnc * webpenc)
webpenc->quality = DEFAULT_QUALITY; webpenc->quality = DEFAULT_QUALITY;
webpenc->speed = DEFAULT_SPEED; webpenc->speed = DEFAULT_SPEED;
webpenc->preset = DEFAULT_PRESET; webpenc->preset = DEFAULT_PRESET;
webpenc->animated = DEFAULT_ANIMATED;
webpenc->use_argb = FALSE; webpenc->use_argb = FALSE;
webpenc->rgb_format = GST_VIDEO_FORMAT_UNKNOWN; 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); gst_video_codec_state_unref (enc->input_state);
enc->input_state = gst_video_codec_state_ref (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 = output_state =
gst_video_encoder_set_output_state (GST_VIDEO_ENCODER (enc), gst_video_encoder_set_output_state (GST_VIDEO_ENCODER (enc),
gst_caps_new_empty_simple ("image/webp"), enc->input_state); gst_caps_new_empty_simple ("image/webp"), enc->input_state);
@ -254,7 +276,6 @@ gst_webp_enc_handle_frame (GstVideoEncoder * encoder,
GstVideoCodecFrame * frame) GstVideoCodecFrame * frame)
{ {
GstWebpEnc *enc = GST_WEBP_ENC (encoder); GstWebpEnc *enc = GST_WEBP_ENC (encoder);
GstBuffer *out_buffer = NULL;
GstVideoFrame vframe; GstVideoFrame vframe;
GST_LOG_OBJECT (enc, "got new frame"); 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); WebPPictureFree (&enc->webp_picture);
out_buffer = gst_buffer_new_allocate (NULL, enc->webp_writer.size, NULL); 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, gst_buffer_fill (out_buffer, 0, enc->webp_writer.mem,
enc->webp_writer.size); enc->webp_writer.size);
free (enc->webp_writer.mem); free (enc->webp_writer.mem);
frame->output_buffer = out_buffer;
} else { } else {
GST_ERROR_OBJECT (enc, "Failed to encode WebPPicture"); GST_ERROR_OBJECT (enc, "Failed to encode WebPPicture");
gst_video_frame_unmap (&vframe); gst_video_frame_unmap (&vframe);
@ -309,7 +348,7 @@ gst_webp_enc_handle_frame (GstVideoEncoder * encoder,
} }
gst_video_frame_unmap (&vframe); gst_video_frame_unmap (&vframe);
frame->output_buffer = out_buffer;
return gst_video_encoder_finish_frame (encoder, frame); 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: case PROP_PRESET:
webpenc->preset = g_value_get_enum (value); webpenc->preset = g_value_get_enum (value);
break; break;
case PROP_ANIMATED:
webpenc->animated = g_value_get_boolean (value);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -367,6 +409,9 @@ gst_webp_enc_get_property (GObject * object, guint prop_id, GValue * value,
case PROP_PRESET: case PROP_PRESET:
g_value_set_enum (value, webpenc->preset); g_value_set_enum (value, webpenc->preset);
break; break;
case PROP_ANIMATED:
g_value_set_boolean (value, webpenc->animated);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -389,14 +434,53 @@ gst_webp_enc_start (GstVideoEncoder * benc)
GST_ERROR_OBJECT (enc, "Failed to Validate the WebPConfig"); GST_ERROR_OBJECT (enc, "Failed to Validate the WebPConfig");
return FALSE; return FALSE;
} }
return TRUE; 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 static gboolean
gst_webp_enc_stop (GstVideoEncoder * benc) gst_webp_enc_stop (GstVideoEncoder * benc)
{ {
GstWebpEnc *enc = GST_WEBP_ENC (benc); GstWebpEnc *enc = GST_WEBP_ENC (benc);
if (enc->input_state) if (enc->input_state)
gst_video_codec_state_unref (enc->input_state); gst_video_codec_state_unref (enc->input_state);
if (enc->anim_enc)
WebPAnimEncoderDelete (enc->anim_enc);
return TRUE; return TRUE;
} }

View file

@ -22,6 +22,7 @@
#include <gst/gst.h> #include <gst/gst.h>
#include <gst/video/video.h> #include <gst/video/video.h>
#include <webp/encode.h> #include <webp/encode.h>
#include <webp/mux.h>
G_BEGIN_DECLS G_BEGIN_DECLS
@ -54,10 +55,14 @@ struct _GstWebpEnc
gfloat quality; gfloat quality;
guint speed; guint speed;
gint preset; gint preset;
gboolean animated;
gboolean use_argb; gboolean use_argb;
GstVideoFormat rgb_format; GstVideoFormat rgb_format;
WebPAnimEncoder *anim_enc;
int next_timestamp;
WebPEncCSP webp_color_space; WebPEncCSP webp_color_space;
struct WebPConfig webp_config; struct WebPConfig webp_config;
struct WebPPicture webp_picture; struct WebPPicture webp_picture;

View file

@ -5,13 +5,14 @@ webp_sources = [
] ]
webp_dep = dependency('libwebp', version : '>= 0.2.1', required : get_option('webp')) 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', gstwebp = library('gstwebp',
webp_sources, webp_sources,
c_args : gst_plugins_bad_args, c_args : gst_plugins_bad_args,
include_directories : [configinc], include_directories : [configinc],
dependencies : [gstvideo_dep, webp_dep], dependencies : [gstvideo_dep, webp_dep, webpmux_dep],
install : true, install : true,
install_dir : plugins_install_dir, install_dir : plugins_install_dir,
) )