1 /* GStreamer
2  * Copyright (C) <2014> Sreerenj Balachandran <sreerenjb@gnome.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 
23 #include <string.h>
24 #include <stdlib.h>             /* free */
25 
26 #include <gst/video/video.h>
27 #include <gst/video/gstvideometa.h>
28 
29 #include "gstwebpenc.h"
30 
31 #define GST_CAT_DEFAULT webpenc_debug
32 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
33 
34 enum
35 {
36   PROP_0,
37   PROP_LOSSLESS,
38   PROP_QUALITY,
39   PROP_SPEED,
40   PROP_PRESET
41 };
42 
43 #define DEFAULT_LOSSLESS FALSE
44 #define DEFAULT_QUALITY 90
45 #define DEFAULT_SPEED 4
46 #define DEFAULT_PRESET WEBP_PRESET_PHOTO
47 
48 static void gst_webp_enc_set_property (GObject * object, guint prop_id,
49     const GValue * value, GParamSpec * pspec);
50 static void gst_webp_enc_get_property (GObject * object, guint prop_id,
51     GValue * value, GParamSpec * pspec);
52 static gboolean gst_webp_enc_start (GstVideoEncoder * benc);
53 static gboolean gst_webp_enc_stop (GstVideoEncoder * benc);
54 static gboolean gst_webp_enc_set_format (GstVideoEncoder * encoder,
55     GstVideoCodecState * state);
56 static GstFlowReturn gst_webp_enc_handle_frame (GstVideoEncoder * encoder,
57     GstVideoCodecFrame * frame);
58 static gboolean gst_webp_enc_propose_allocation (GstVideoEncoder * encoder,
59     GstQuery * query);
60 
61 static GstStaticPadTemplate webp_enc_sink_factory =
62 GST_STATIC_PAD_TEMPLATE ("sink",
63     GST_PAD_SINK,
64     GST_PAD_ALWAYS,
65     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ I420, YV12, RGB, RGBA}"))
66     );
67 static GstStaticPadTemplate webp_enc_src_factory =
68 GST_STATIC_PAD_TEMPLATE ("src",
69     GST_PAD_SRC,
70     GST_PAD_ALWAYS,
71     GST_STATIC_CAPS ("image/webp, "
72         "framerate = (fraction) [0/1, MAX], "
73         "width = (int) [ 16, 16383 ], " "height = (int) [ 16, 16383 ]")
74     );
75 
76 enum
77 {
78   GST_WEBP_PRESET_DEFAULT,
79   GST_WEBP_PRESET_PICTURE,
80   GST_WEBP_PRESET_PHOTO,
81   GST_WEBP_PRESET_DRAWING,
82   GST_WEBP_PRESET_ICON,
83   GST_WEBP_PREET_TEXT
84 };
85 
86 static const GEnumValue preset_values[] = {
87   {GST_WEBP_PRESET_DEFAULT, "Default", "none"},
88   {GST_WEBP_PRESET_PICTURE, "Digital picture,inner shot", "picture"},
89   {GST_WEBP_PRESET_PHOTO, "Outdoor photo, natural lighting", "photo"},
90   {GST_WEBP_PRESET_DRAWING, "Hand or Line drawing", "drawing"},
91   {GST_WEBP_PRESET_ICON, "Small-sized colorful images", "icon"},
92   {GST_WEBP_PREET_TEXT, "text-like", "text"},
93   {0, NULL, NULL},
94 };
95 
96 #define GST_WEBP_ENC_PRESET_TYPE (gst_webp_enc_preset_get_type())
97 static GType
gst_webp_enc_preset_get_type(void)98 gst_webp_enc_preset_get_type (void)
99 {
100   static GType preset_type = 0;
101 
102   if (!preset_type) {
103     preset_type = g_enum_register_static ("GstWebpEncPreset", preset_values);
104   }
105   return preset_type;
106 }
107 
108 #define gst_webp_enc_parent_class parent_class
109 G_DEFINE_TYPE (GstWebpEnc, gst_webp_enc, GST_TYPE_VIDEO_ENCODER);
110 
111 static void
gst_webp_enc_class_init(GstWebpEncClass * klass)112 gst_webp_enc_class_init (GstWebpEncClass * klass)
113 {
114   GObjectClass *gobject_class;
115   GstElementClass *element_class;
116   GstVideoEncoderClass *venc_class;
117 
118   gobject_class = (GObjectClass *) klass;
119   element_class = (GstElementClass *) klass;
120   venc_class = (GstVideoEncoderClass *) klass;
121 
122   parent_class = g_type_class_peek_parent (klass);
123 
124   gobject_class->set_property = gst_webp_enc_set_property;
125   gobject_class->get_property = gst_webp_enc_get_property;
126   gst_element_class_add_static_pad_template (element_class,
127       &webp_enc_sink_factory);
128   gst_element_class_add_static_pad_template (element_class,
129       &webp_enc_src_factory);
130   gst_element_class_set_static_metadata (element_class, "WEBP image encoder",
131       "Codec/Encoder/Image", "Encode images in WEBP format",
132       "Sreerenj Balachandran <sreerenjb@gnome.org>");
133 
134   venc_class->start = gst_webp_enc_start;
135   venc_class->stop = gst_webp_enc_stop;
136   venc_class->set_format = gst_webp_enc_set_format;
137   venc_class->handle_frame = gst_webp_enc_handle_frame;
138   venc_class->propose_allocation = gst_webp_enc_propose_allocation;
139 
140   g_object_class_install_property (gobject_class, PROP_LOSSLESS,
141       g_param_spec_boolean ("lossless", "Lossless",
142           "Enable lossless encoding",
143           DEFAULT_LOSSLESS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
144   g_object_class_install_property (gobject_class, PROP_QUALITY,
145       g_param_spec_float ("quality", "quality-level",
146           "quality level, between 0 (smallest file) and 100 (biggest)",
147           0, 100, DEFAULT_QUALITY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
148   g_object_class_install_property (gobject_class, PROP_SPEED,
149       g_param_spec_uint ("speed", "Compression Method",
150           "quality/speed trade-off (0=fast, 6=slower-better)",
151           0, 6, DEFAULT_SPEED, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
152   g_object_class_install_property (gobject_class, PROP_PRESET,
153       g_param_spec_enum ("preset", "preset tuning",
154           "Preset name for visual tuning",
155           GST_WEBP_ENC_PRESET_TYPE, DEFAULT_PRESET,
156           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
157 
158   GST_DEBUG_CATEGORY_INIT (webpenc_debug, "webpenc", 0,
159       "WEBP encoding element");
160 }
161 
162 static void
gst_webp_enc_init(GstWebpEnc * webpenc)163 gst_webp_enc_init (GstWebpEnc * webpenc)
164 {
165   GST_PAD_SET_ACCEPT_TEMPLATE (GST_VIDEO_ENCODER_SINK_PAD (webpenc));
166 
167   webpenc->lossless = DEFAULT_LOSSLESS;
168   webpenc->quality = DEFAULT_QUALITY;
169   webpenc->speed = DEFAULT_SPEED;
170   webpenc->preset = DEFAULT_PRESET;
171 
172   webpenc->use_argb = FALSE;
173   webpenc->rgb_format = GST_VIDEO_FORMAT_UNKNOWN;
174 }
175 
176 static gboolean
gst_webp_enc_set_format(GstVideoEncoder * encoder,GstVideoCodecState * state)177 gst_webp_enc_set_format (GstVideoEncoder * encoder, GstVideoCodecState * state)
178 {
179   GstWebpEnc *enc = GST_WEBP_ENC (encoder);
180   GstVideoCodecState *output_state;
181   GstVideoInfo *info;
182   GstVideoFormat format;
183 
184   info = &state->info;
185   format = GST_VIDEO_INFO_FORMAT (info);
186 
187   if (GST_VIDEO_INFO_IS_YUV (info)) {
188     switch (format) {
189       case GST_VIDEO_FORMAT_I420:
190       case GST_VIDEO_FORMAT_YV12:
191         enc->webp_color_space = WEBP_YUV420;
192         break;
193       default:
194         break;
195     }
196   } else {
197     if (GST_VIDEO_INFO_IS_RGB (info)) {
198       enc->rgb_format = format;
199       enc->use_argb = 1;
200     }
201   }
202 
203   if (enc->input_state)
204     gst_video_codec_state_unref (enc->input_state);
205   enc->input_state = gst_video_codec_state_ref (state);
206 
207   output_state =
208       gst_video_encoder_set_output_state (GST_VIDEO_ENCODER (enc),
209       gst_caps_new_empty_simple ("image/webp"), enc->input_state);
210   gst_video_codec_state_unref (output_state);
211 
212   return TRUE;
213 }
214 
215 static gboolean
gst_webp_set_picture_params(GstWebpEnc * enc)216 gst_webp_set_picture_params (GstWebpEnc * enc)
217 {
218   GstVideoInfo *info;
219   gboolean ret = TRUE;
220 
221   info = &enc->input_state->info;
222 
223   if (!WebPPictureInit (&enc->webp_picture)) {
224     ret = FALSE;
225     goto failed_pic_init;
226   }
227 
228   enc->webp_picture.use_argb = enc->use_argb;
229   if (!enc->use_argb)
230     enc->webp_picture.colorspace = enc->webp_color_space;
231 
232   enc->webp_picture.width = GST_VIDEO_INFO_WIDTH (info);
233   enc->webp_picture.height = GST_VIDEO_INFO_HEIGHT (info);
234 
235   WebPMemoryWriterInit (&enc->webp_writer);
236   enc->webp_picture.writer = WebPMemoryWrite;
237   enc->webp_picture.custom_ptr = &enc->webp_writer;
238 
239   return ret;
240 
241 failed_pic_init:
242   {
243     GST_ERROR_OBJECT (enc, "Failed to Initialize WebPPicture !");
244     return ret;
245   }
246 }
247 
248 static GstFlowReturn
gst_webp_enc_handle_frame(GstVideoEncoder * encoder,GstVideoCodecFrame * frame)249 gst_webp_enc_handle_frame (GstVideoEncoder * encoder,
250     GstVideoCodecFrame * frame)
251 {
252   GstWebpEnc *enc = GST_WEBP_ENC (encoder);
253   GstBuffer *out_buffer = NULL;
254   GstVideoFrame vframe;
255 
256   GST_LOG_OBJECT (enc, "got new frame");
257 
258   gst_webp_set_picture_params (enc);
259 
260   if (!gst_video_frame_map (&vframe, &enc->input_state->info,
261           frame->input_buffer, GST_MAP_READ))
262     return GST_FLOW_ERROR;
263 
264   if (!enc->use_argb) {
265     enc->webp_picture.y = GST_VIDEO_FRAME_COMP_DATA (&vframe, 0);
266     enc->webp_picture.u = GST_VIDEO_FRAME_COMP_DATA (&vframe, 1);
267     enc->webp_picture.v = GST_VIDEO_FRAME_COMP_DATA (&vframe, 2);
268 
269     enc->webp_picture.y_stride = GST_VIDEO_FRAME_COMP_STRIDE (&vframe, 0);
270     enc->webp_picture.uv_stride = GST_VIDEO_FRAME_COMP_STRIDE (&vframe, 1);
271 
272   } else {
273     switch (enc->rgb_format) {
274       case GST_VIDEO_FORMAT_RGB:
275         WebPPictureImportRGB (&enc->webp_picture,
276             GST_VIDEO_FRAME_COMP_DATA (&vframe, 0),
277             GST_VIDEO_FRAME_COMP_STRIDE (&vframe, 0));
278         break;
279       case GST_VIDEO_FORMAT_RGBA:
280         WebPPictureImportRGBA (&enc->webp_picture,
281             GST_VIDEO_FRAME_COMP_DATA (&vframe, 0),
282             GST_VIDEO_FRAME_COMP_STRIDE (&vframe, 0));
283         break;
284       default:
285         break;
286     }
287   }
288 
289   if (WebPEncode (&enc->webp_config, &enc->webp_picture)) {
290     WebPPictureFree (&enc->webp_picture);
291 
292     out_buffer = gst_buffer_new_allocate (NULL, enc->webp_writer.size, NULL);
293     if (!out_buffer) {
294       GST_ERROR_OBJECT (enc, "Failed to create output buffer");
295       gst_video_frame_unmap (&vframe);
296       return GST_FLOW_ERROR;
297     }
298     gst_buffer_fill (out_buffer, 0, enc->webp_writer.mem,
299         enc->webp_writer.size);
300     free (enc->webp_writer.mem);
301   } else {
302     GST_ERROR_OBJECT (enc, "Failed to encode WebPPicture");
303     gst_video_frame_unmap (&vframe);
304     return GST_FLOW_ERROR;
305   }
306 
307   gst_video_frame_unmap (&vframe);
308   frame->output_buffer = out_buffer;
309   return gst_video_encoder_finish_frame (encoder, frame);
310 }
311 
312 static gboolean
gst_webp_enc_propose_allocation(GstVideoEncoder * encoder,GstQuery * query)313 gst_webp_enc_propose_allocation (GstVideoEncoder * encoder, GstQuery * query)
314 {
315   gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
316   return
317       GST_VIDEO_ENCODER_CLASS (parent_class)->propose_allocation (encoder,
318       query);
319 }
320 
321 static void
gst_webp_enc_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)322 gst_webp_enc_set_property (GObject * object, guint prop_id,
323     const GValue * value, GParamSpec * pspec)
324 {
325   GstWebpEnc *webpenc = GST_WEBP_ENC (object);
326 
327   switch (prop_id) {
328     case PROP_LOSSLESS:
329       webpenc->lossless = g_value_get_boolean (value);
330       break;
331     case PROP_QUALITY:
332       webpenc->quality = g_value_get_float (value);
333       break;
334     case PROP_SPEED:
335       webpenc->speed = g_value_get_uint (value);
336       break;
337     case PROP_PRESET:
338       webpenc->preset = g_value_get_enum (value);
339       break;
340     default:
341       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
342       break;
343   }
344 
345 }
346 
347 static void
gst_webp_enc_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)348 gst_webp_enc_get_property (GObject * object, guint prop_id, GValue * value,
349     GParamSpec * pspec)
350 {
351   GstWebpEnc *webpenc = GST_WEBP_ENC (object);
352 
353   switch (prop_id) {
354     case PROP_LOSSLESS:
355       g_value_set_boolean (value, webpenc->lossless);
356       break;
357     case PROP_QUALITY:
358       g_value_set_float (value, webpenc->quality);
359       break;
360     case PROP_SPEED:
361       g_value_set_uint (value, webpenc->speed);
362       break;
363     case PROP_PRESET:
364       g_value_set_enum (value, webpenc->preset);
365       break;
366     default:
367       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
368       break;
369   }
370 }
371 
372 static gboolean
gst_webp_enc_start(GstVideoEncoder * benc)373 gst_webp_enc_start (GstVideoEncoder * benc)
374 {
375   GstWebpEnc *enc = (GstWebpEnc *) benc;
376 
377   if (!WebPConfigPreset (&enc->webp_config, enc->preset, enc->quality)) {
378     GST_ERROR_OBJECT (enc, "Failed to Initialize WebPConfig ");
379     return FALSE;
380   }
381 
382   enc->webp_config.lossless = enc->lossless;
383   enc->webp_config.method = enc->speed;
384   if (!WebPValidateConfig (&enc->webp_config)) {
385     GST_ERROR_OBJECT (enc, "Failed to Validate the WebPConfig");
386     return FALSE;
387   }
388   return TRUE;
389 }
390 
391 static gboolean
gst_webp_enc_stop(GstVideoEncoder * benc)392 gst_webp_enc_stop (GstVideoEncoder * benc)
393 {
394   GstWebpEnc *enc = GST_WEBP_ENC (benc);
395   if (enc->input_state)
396     gst_video_codec_state_unref (enc->input_state);
397   return TRUE;
398 }
399 
400 gboolean
gst_webp_enc_register(GstPlugin * plugin)401 gst_webp_enc_register (GstPlugin * plugin)
402 {
403   return gst_element_register (plugin, "webpenc",
404       GST_RANK_PRIMARY, GST_TYPE_WEBP_ENC);
405 }
406