mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-26 19:51:11 +00:00
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:
parent
351f823704
commit
a6409525ef
3 changed files with 96 additions and 6 deletions
|
@ -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 <sreerenjb@gnome.org>");
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <webp/encode.h>
|
||||
#include <webp/mux.h>
|
||||
|
||||
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;
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue