f6d12eded5ac5aa9fb9aa4e84ceeb82b66a0567d
[platform/upstream/gstreamer.git] / ext / libav / gstavscale.c
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  * This file:
4  * Copyright (C) 2005 Luca Ognibene <luogni@tin.it>
5  * Copyright (C) 2006 Martin Zlomek <martin.zlomek@itonis.tv>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include <libavcodec/avcodec.h>
28
29 #include <gst/gst.h>
30 #include <gst/base/gstbasetransform.h>
31 #include <gst/video/video.h>
32
33 #include "gstffmpeg.h"
34 #include "gstffmpegcodecmap.h"
35
36 typedef struct _GstFFMpegScale
37 {
38   GstBaseTransform element;
39
40   GstPad *sinkpad, *srcpad;
41
42   gint in_width, in_height;
43   gint out_width, out_height;
44
45   enum PixelFormat pixfmt;
46
47   ImgReSampleContext *res;
48 } GstFFMpegScale;
49
50 typedef struct _GstFFMpegScaleClass
51 {
52   GstBaseTransformClass parent_class;
53 } GstFFMpegScaleClass;
54
55 #define GST_TYPE_FFMPEGSCALE \
56         (gst_ffmpegscale_get_type())
57 #define GST_FFMPEGSCALE(obj) \
58         (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FFMPEGSCALE,GstFFMpegScale))
59 #define GST_FFMPEGSCALE_CLASS(klass) \
60         (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_FFMPEGSCALE,GstFFMpegScaleClass))
61 #define GST_IS_FFMPEGSCALE(obj) \
62         (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_FFMPEGSCALE))
63 #define GST_IS_FFMPEGSCALE_CLASS(klass) \
64         (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_FFMPEGSCALE))
65
66 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
67     GST_PAD_SRC,
68     GST_PAD_ALWAYS,
69     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
70     );
71
72 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
73     GST_PAD_SINK,
74     GST_PAD_ALWAYS,
75     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
76     );
77
78 GST_BOILERPLATE (GstFFMpegScale, gst_ffmpegscale, GstBaseTransform,
79     GST_TYPE_BASE_TRANSFORM);
80
81 static void gst_ffmpegscale_finalize (GObject * object);
82
83 static GstCaps *gst_ffmpegscale_transform_caps (GstBaseTransform * trans,
84     GstPadDirection direction, GstCaps * caps);
85 static void gst_ffmpegscale_fixate_caps (GstBaseTransform * trans,
86     GstPadDirection direction, GstCaps * caps, GstCaps * othercaps);
87 static gboolean gst_ffmpegscale_get_unit_size (GstBaseTransform * trans,
88     GstCaps * caps, guint * size);
89 static gboolean gst_ffmpegscale_set_caps (GstBaseTransform * trans,
90     GstCaps * incaps, GstCaps * outcaps);
91 static GstFlowReturn gst_ffmpegscale_transform (GstBaseTransform * trans,
92     GstBuffer * inbuf, GstBuffer * outbuf);
93
94 static gboolean gst_ffmpegscale_handle_src_event (GstPad * pad,
95     GstEvent * event);
96
97 static void
98 gst_ffmpegscale_base_init (gpointer g_class)
99 {
100   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
101
102   gst_element_class_add_static_pad_template (element_class, &src_factory);
103   gst_element_class_add_static_pad_template (element_class, &sink_factory);
104   gst_element_class_set_static_metadata (element_class, "libav Scale element",
105       "Filter/Converter/Video/Scaler",
106       "Converts video from one resolution to another",
107       "Luca Ognibene <luogni@tin.it>");
108 }
109
110 static void
111 gst_ffmpegscale_class_init (GstFFMpegScaleClass * klass)
112 {
113   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
114   GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass);
115
116   gobject_class->finalize = gst_ffmpegscale_finalize;
117
118   trans_class->transform_caps =
119       GST_DEBUG_FUNCPTR (gst_ffmpegscale_transform_caps);
120   trans_class->fixate_caps = GST_DEBUG_FUNCPTR (gst_ffmpegscale_fixate_caps);
121   trans_class->get_unit_size =
122       GST_DEBUG_FUNCPTR (gst_ffmpegscale_get_unit_size);
123   trans_class->set_caps = GST_DEBUG_FUNCPTR (gst_ffmpegscale_set_caps);
124   trans_class->transform = GST_DEBUG_FUNCPTR (gst_ffmpegscale_transform);
125
126   trans_class->passthrough_on_same_caps = TRUE;
127 }
128
129 static void
130 gst_ffmpegscale_init (GstFFMpegScale * scale, GstFFMpegScaleClass * klass)
131 {
132   GstBaseTransform *trans = GST_BASE_TRANSFORM (scale);
133
134   gst_pad_set_event_function (trans->srcpad, gst_ffmpegscale_handle_src_event);
135
136   scale->pixfmt = AV_PIX_FMT_NB;
137   scale->res = NULL;
138 }
139
140 static void
141 gst_ffmpegscale_finalize (GObject * object)
142 {
143   GstFFMpegScale *scale = GST_FFMPEGSCALE (object);
144
145   if (scale->res != NULL)
146     img_resample_close (scale->res);
147
148   G_OBJECT_CLASS (parent_class)->finalize (object);
149 }
150
151 static GstCaps *
152 gst_ffmpegscale_transform_caps (GstBaseTransform * trans,
153     GstPadDirection direction, GstCaps * caps)
154 {
155   GstCaps *retcaps;
156   int i;
157
158   retcaps = gst_caps_copy (caps);
159
160   for (i = 0; i < gst_caps_get_size (retcaps); i++) {
161     GstStructure *structure = gst_caps_get_structure (retcaps, i);
162
163     gst_structure_set (structure,
164         "width", GST_TYPE_INT_RANGE, 16, 4096,
165         "height", GST_TYPE_INT_RANGE, 16, 4096, NULL);
166     gst_structure_remove_field (structure, "pixel-aspect-ratio");
167   }
168
169   return retcaps;
170 }
171
172 static void
173 gst_ffmpegscale_fixate_caps (GstBaseTransform * trans,
174     GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
175 {
176   GstStructure *instructure = gst_caps_get_structure (caps, 0);
177   GstStructure *outstructure = gst_caps_get_structure (othercaps, 0);
178   const GValue *in_par, *out_par;
179
180   in_par = gst_structure_get_value (instructure, "pixel-aspect-ratio");
181   out_par = gst_structure_get_value (outstructure, "pixel-aspect-ratio");
182
183   if (in_par && out_par) {
184     GValue out_ratio = { 0, };  /* w/h of output video */
185     int in_w, in_h, in_par_n, in_par_d, out_par_n, out_par_d;
186     int count = 0, w = 0, h = 0, num, den;
187
188     /* if both width and height are already fixed, we can't do anything
189      * about it anymore */
190     if (gst_structure_get_int (outstructure, "width", &w))
191       ++count;
192     if (gst_structure_get_int (outstructure, "height", &h))
193       ++count;
194     if (count == 2) {
195       GST_DEBUG_OBJECT (trans, "dimensions already set to %dx%d, not fixating",
196           w, h);
197       return;
198     }
199
200     gst_structure_get_int (instructure, "width", &in_w);
201     gst_structure_get_int (instructure, "height", &in_h);
202     in_par_n = gst_value_get_fraction_numerator (in_par);
203     in_par_d = gst_value_get_fraction_denominator (in_par);
204     out_par_n = gst_value_get_fraction_numerator (out_par);
205     out_par_d = gst_value_get_fraction_denominator (out_par);
206
207     g_value_init (&out_ratio, GST_TYPE_FRACTION);
208     gst_value_set_fraction (&out_ratio, in_w * in_par_n * out_par_d,
209         in_h * in_par_d * out_par_n);
210     num = gst_value_get_fraction_numerator (&out_ratio);
211     den = gst_value_get_fraction_denominator (&out_ratio);
212     GST_DEBUG_OBJECT (trans,
213         "scaling input with %dx%d and PAR %d/%d to output PAR %d/%d",
214         in_w, in_h, in_par_n, in_par_d, out_par_n, out_par_d);
215     GST_DEBUG_OBJECT (trans, "resulting output should respect ratio of %d/%d",
216         num, den);
217
218     /* now find a width x height that respects this display ratio.
219      * prefer those that have one of w/h the same as the incoming video
220      * using wd / hd = num / den */
221
222     /* start with same height, because of interlaced video */
223     /* check hd / den is an integer scale factor, and scale wd with the PAR */
224     if (in_h % den == 0) {
225       GST_DEBUG_OBJECT (trans, "keeping video height");
226       h = in_h;
227       w = h * num / den;
228     } else if (in_w % num == 0) {
229       GST_DEBUG_OBJECT (trans, "keeping video width");
230       w = in_w;
231       h = w * den / num;
232     } else {
233       GST_DEBUG_OBJECT (trans, "approximating but keeping video height");
234       h = in_h;
235       w = h * num / den;
236     }
237     GST_DEBUG_OBJECT (trans, "scaling to %dx%d", w, h);
238
239     /* now fixate */
240     gst_structure_fixate_field_nearest_int (outstructure, "width", w);
241     gst_structure_fixate_field_nearest_int (outstructure, "height", h);
242   } else {
243     gint width, height;
244
245     if (gst_structure_get_int (instructure, "width", &width)) {
246       if (gst_structure_has_field (outstructure, "width"))
247         gst_structure_fixate_field_nearest_int (outstructure, "width", width);
248     }
249     if (gst_structure_get_int (instructure, "height", &height)) {
250       if (gst_structure_has_field (outstructure, "height"))
251         gst_structure_fixate_field_nearest_int (outstructure, "height", height);
252     }
253   }
254 }
255
256 static gboolean
257 gst_ffmpegscale_get_unit_size (GstBaseTransform * trans, GstCaps * caps,
258     guint * size)
259 {
260   GstStructure *structure = gst_caps_get_structure (caps, 0);
261   gint width, height;
262   AVCodecContext *ctx;
263
264   if (!gst_structure_get_int (structure, "width", &width))
265     return FALSE;
266   if (!gst_structure_get_int (structure, "height", &height))
267     return FALSE;
268
269   ctx = avcodec_alloc_context ();
270   ctx->width = width;
271   ctx->height = height;
272   ctx->pix_fmt = AV_PIX_FMT_NB;
273   gst_ffmpeg_caps_with_codectype (CODEC_TYPE_VIDEO, caps, ctx);
274   if (ctx->pix_fmt == AV_PIX_FMT_NB) {
275     av_free (ctx);
276     return FALSE;
277   }
278
279   *size =
280       (guint) av_image_get_buffer_size (pix->pix_fmt, ctx->width, ctx->height,
281       1);
282
283   av_free (ctx);
284
285   return TRUE;
286 }
287
288 static gboolean
289 gst_ffmpegscale_set_caps (GstBaseTransform * trans, GstCaps * incaps,
290     GstCaps * outcaps)
291 {
292   GstFFMpegScale *scale = GST_FFMPEGSCALE (trans);
293   GstStructure *instructure = gst_caps_get_structure (incaps, 0);
294   GstStructure *outstructure = gst_caps_get_structure (outcaps, 0);
295   gint par_num, par_den;
296   AVCodecContext *ctx;
297
298   if (!gst_structure_get_int (instructure, "width", &scale->in_width))
299     return FALSE;
300   if (!gst_structure_get_int (instructure, "height", &scale->in_height))
301     return FALSE;
302
303   if (!gst_structure_get_int (outstructure, "width", &scale->out_width))
304     return FALSE;
305   if (!gst_structure_get_int (outstructure, "height", &scale->out_height))
306     return FALSE;
307
308   if (gst_structure_get_fraction (instructure, "pixel-aspect-ratio",
309           &par_num, &par_den)) {
310     gst_structure_set (outstructure,
311         "pixel-aspect-ratio", GST_TYPE_FRACTION,
312         par_num * scale->in_width / scale->out_width,
313         par_den * scale->in_height / scale->out_height, NULL);
314   }
315
316   ctx = avcodec_alloc_context ();
317   ctx->width = scale->in_width;
318   ctx->height = scale->in_height;
319   ctx->pix_fmt = AV_PIX_FMT_NB;
320   gst_ffmpeg_caps_with_codectype (CODEC_TYPE_VIDEO, incaps, ctx);
321   if (ctx->pix_fmt == AV_PIX_FMT_NB) {
322     av_free (ctx);
323     return FALSE;
324   }
325
326   scale->pixfmt = ctx->pix_fmt;
327
328   av_free (ctx);
329
330   scale->res = img_resample_init (scale->out_width, scale->out_height,
331       scale->in_width, scale->in_height);
332
333   return TRUE;
334 }
335
336 static GstFlowReturn
337 gst_ffmpegscale_transform (GstBaseTransform * trans, GstBuffer * inbuf,
338     GstBuffer * outbuf)
339 {
340   GstFFMpegScale *scale = GST_FFMPEGSCALE (trans);
341   AVPicture in_frame, out_frame;
342
343   gst_buffer_copy_metadata (outbuf, inbuf, GST_BUFFER_COPY_TIMESTAMPS);
344
345   gst_ffmpeg_avpicture_fill (&in_frame,
346       GST_BUFFER_DATA (inbuf),
347       scale->pixfmt, scale->in_width, scale->in_height);
348
349   gst_ffmpeg_avpicture_fill (&out_frame,
350       GST_BUFFER_DATA (outbuf),
351       scale->pixfmt, scale->out_width, scale->out_height);
352
353   img_resample (scale->res, &out_frame, &in_frame);
354
355   return GST_FLOW_OK;
356 }
357
358 static gboolean
359 gst_ffmpegscale_handle_src_event (GstPad * pad, GstEvent * event)
360 {
361   GstFFMpegScale *scale;
362   GstStructure *structure;
363   gdouble pointer;
364   gboolean res;
365
366   scale = GST_FFMPEGSCALE (gst_pad_get_parent (pad));
367
368   switch (GST_EVENT_TYPE (event)) {
369     case GST_EVENT_NAVIGATION:
370       event =
371           GST_EVENT (gst_mini_object_make_writable (GST_MINI_OBJECT (event)));
372
373       structure = (GstStructure *) gst_event_get_structure (event);
374       if (gst_structure_get_double (structure, "pointer_x", &pointer)) {
375         gst_structure_set (structure,
376             "pointer_x", G_TYPE_DOUBLE,
377             pointer * scale->in_width / scale->out_width, NULL);
378       }
379       if (gst_structure_get_double (structure, "pointer_y", &pointer)) {
380         gst_structure_set (structure,
381             "pointer_y", G_TYPE_DOUBLE,
382             pointer * scale->in_height / scale->out_height, NULL);
383       }
384       break;
385     default:
386       break;
387   }
388
389   res = gst_pad_event_default (pad, event);
390
391   gst_object_unref (scale);
392
393   return res;
394 }
395
396 gboolean
397 gst_ffmpegscale_register (GstPlugin * plugin)
398 {
399   return gst_element_register (plugin, "avvideoscale",
400       GST_RANK_NONE, GST_TYPE_FFMPEGSCALE);
401 }